/* /Components/Layout/ForgeLayout.razor.rz.scp.css */
.forge-shell[b-qqrsijv1vx] { min-height: 100vh; background: #12110f; color: #eee8dc; font-family: Georgia, serif; }
.forge-header[b-qqrsijv1vx] { display: flex; align-items: center; justify-content: space-between; padding: 1rem 1.5rem; border-bottom: 1px solid #4b4031; }
.forge-brand[b-qqrsijv1vx] { color: #e6b85c; font-size: 1.25rem; text-decoration: none; }
.forge-header button[b-qqrsijv1vx] { color: #eee8dc; background: transparent; border: 1px solid #766247; padding: .45rem .8rem; cursor: pointer; }
.forge-nav[b-qqrsijv1vx] { display: flex; flex-wrap: wrap; gap: .75rem 1.5rem; padding: .9rem 1.5rem; background: #1b1814; }
.forge-nav a[b-qqrsijv1vx] { color: #d8c49f; text-decoration: none; }
.forge-content[b-qqrsijv1vx] { max-width: 96rem; margin: 0 auto; padding: 2rem 1.5rem; }

/* Running product version, linking to its notes. margin-right:auto keeps it beside the brand
   instead of being spread to the middle by the header's space-between. */
.version-badge[b-qqrsijv1vx] {
    margin-right: auto;
    margin-left: .9rem;
    font-size: .68rem;
    font-weight: 800;
    letter-spacing: .08em;
    text-transform: lowercase;
    color: #9b8a6d;
    text-decoration: none;
    border: 1px solid #4b4031;
    border-radius: 999px;
    padding: .18rem .6rem;
}

.version-badge:hover[b-qqrsijv1vx] {
    color: #e6b85c;
    border-color: #766247;
}

/* Issue #377. The server-health banner. Scoped HERE because the markup is ForgeLayout's own — a rule for
   it living in the status page's sheet could never match, which is the fault behind #342. */
.forge-health-banner[b-qqrsijv1vx] {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: .5rem 1rem;
    margin: 0;
    padding: .7rem 1.5rem;
    font-size: .9rem;
    color: #f5ece0;
    /* Full-bleed and above the content, not inside .forge-content's max-width: a warning indented to
       match the page reads as part of the page. */
    border-top: 1px solid rgba(0, 0, 0, .35);
}

.forge-health-banner.failed[b-qqrsijv1vx] { background: #6d211c; }

.forge-health-banner.warning[b-qqrsijv1vx] { background: #6b4d16; }

.forge-health-banner span[b-qqrsijv1vx] { opacity: .9; }

.forge-health-banner a[b-qqrsijv1vx] {
    margin-left: auto;
    color: #f5ece0;
    font-weight: 700;
    white-space: nowrap;
}
/* /Components/Layout/ImmersiveLayout.razor.rz.scp.css */
/* A small pill in the corner, above the page, on every signed-in page but the game itself. */
.whats-new-link[b-0al7g1bnez] {
    position: fixed;
    top: .75rem;
    right: 1rem;
    z-index: 50;
    padding: .4rem .75rem;
    border: 1px solid #dcb76366;
    border-radius: 999px;
    background: #0d1220cc;
    color: #f2d38e;
    font: 600 .78rem/1 system-ui, sans-serif;
    letter-spacing: .02em;
    text-decoration: none;
}

.whats-new-link:hover[b-0al7g1bnez],
.whats-new-link:focus-visible[b-0al7g1bnez] {
    border-color: #dcb763;
    color: #ffe3a6;
}
/* /Components/Layout/MainLayout.razor.rz.scp.css */
#blazor-error-ui[b-51o5x1xqmb] {
    color-scheme: light only;
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

    #blazor-error-ui .dismiss[b-51o5x1xqmb] {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
    }

/* app.css lays this nav out with justify-content:space-between, which distributes the FREE space
   between the links. The version badge's margin-left:auto below consumes all of that free space to
   pin itself right — correct for the badge, but it leaves space-between with nothing to distribute,
   so the remaining links collapse against each other ("FenreachVoice labAccount"). An explicit gap
   restores the spacing independently of however much free space is left over. */
.app-nav[b-51o5x1xqmb] {
    gap: 1.5rem;
}

/* Running product version, linking to its notes. Scoped CSS does not cross components, so the
   Forge header carries its own copy of this rule rather than sharing one. */
.version-badge[b-51o5x1xqmb] {
    margin-left: auto;
    font-size: .68rem !important;
    font-weight: 800 !important;
    letter-spacing: .08em;
    text-transform: lowercase;
    color: #8b96b8 !important;
    border: 1px solid #27334f;
    border-radius: 999px;
    padding: .18rem .6rem;
}

.version-badge:hover[b-51o5x1xqmb] {
    color: #dcb763 !important;
    border-color: #dcb76366;
}
/* /Components/Layout/ReconnectModal.razor.rz.scp.css */
.components-reconnect-first-attempt-visible[b-wiqe4xiepg],
.components-reconnect-repeated-attempt-visible[b-wiqe4xiepg],
.components-reconnect-failed-visible[b-wiqe4xiepg],
.components-pause-visible[b-wiqe4xiepg],
.components-resume-failed-visible[b-wiqe4xiepg],
.components-rejoining-animation[b-wiqe4xiepg] {
    display: none;
}

#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible[b-wiqe4xiepg],
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation[b-wiqe4xiepg],
#components-reconnect-modal.components-reconnect-paused .components-pause-visible[b-wiqe4xiepg],
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible[b-wiqe4xiepg],
#components-reconnect-modal.components-reconnect-retrying[b-wiqe4xiepg],
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible[b-wiqe4xiepg],
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation[b-wiqe4xiepg],
#components-reconnect-modal.components-reconnect-failed[b-wiqe4xiepg],
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible[b-wiqe4xiepg] {
    display: block;
}


#components-reconnect-modal[b-wiqe4xiepg] {
    background-color: white;
    width: 20rem;
    margin: 20vh auto;
    padding: 2rem;
    border: 0;
    border-radius: 0.5rem;
    box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
    opacity: 0;
    transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
    animation: components-reconnect-modal-fadeOutOpacity-b-wiqe4xiepg 0.5s both;
    &[open]

{
    animation: components-reconnect-modal-slideUp-b-wiqe4xiepg 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity-b-wiqe4xiepg 0.5s ease-in-out 0.3s;
    animation-fill-mode: both;
}

}

#components-reconnect-modal[b-wiqe4xiepg]::backdrop {
    background-color: rgba(0, 0, 0, 0.4);
    animation: components-reconnect-modal-fadeInOpacity-b-wiqe4xiepg 0.5s ease-in-out;
    opacity: 1;
}

@keyframes components-reconnect-modal-slideUp-b-wiqe4xiepg {
    0% {
        transform: translateY(30px) scale(0.95);
    }

    100% {
        transform: translateY(0);
    }
}

@keyframes components-reconnect-modal-fadeInOpacity-b-wiqe4xiepg {
    0% {
        opacity: 0;
    }

    100% {
        opacity: 1;
    }
}

@keyframes components-reconnect-modal-fadeOutOpacity-b-wiqe4xiepg {
    0% {
        opacity: 1;
    }

    100% {
        opacity: 0;
    }
}

.components-reconnect-container[b-wiqe4xiepg] {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1rem;
}

#components-reconnect-modal p[b-wiqe4xiepg] {
    margin: 0;
    text-align: center;
}

#components-reconnect-modal button[b-wiqe4xiepg] {
    border: 0;
    background-color: #6b9ed2;
    color: white;
    padding: 4px 24px;
    border-radius: 4px;
}

    #components-reconnect-modal button:hover[b-wiqe4xiepg] {
        background-color: #3b6ea2;
    }

    #components-reconnect-modal button:active[b-wiqe4xiepg] {
        background-color: #6b9ed2;
    }

.components-rejoining-animation[b-wiqe4xiepg] {
    position: relative;
    width: 80px;
    height: 80px;
}

    .components-rejoining-animation div[b-wiqe4xiepg] {
        position: absolute;
        border: 3px solid #0087ff;
        opacity: 1;
        border-radius: 50%;
        animation: components-rejoining-animation-b-wiqe4xiepg 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
    }

        .components-rejoining-animation div:nth-child(2)[b-wiqe4xiepg] {
            animation-delay: -0.5s;
        }

@keyframes components-rejoining-animation-b-wiqe4xiepg {
    0% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 0;
    }

    4.9% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 0;
    }

    5% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 1;
    }

    100% {
        top: 0px;
        left: 0px;
        width: 80px;
        height: 80px;
        opacity: 0;
    }
}
/* /Components/Pages/Changelog.razor.rz.scp.css */
.changelog-shell[b-r1iezlb7wv] {
    max-width: 52rem;
    margin: 0 auto;
    padding: 2rem 1.25rem 4rem;
}

.eyebrow[b-r1iezlb7wv] {
    font-size: .72rem;
    font-weight: 800;
    letter-spacing: .16em;
    color: #dcb763;
    margin: 0 0 .4rem;
}

.changelog-head h1[b-r1iezlb7wv] {
    margin: 0 0 .5rem;
    font-family: Georgia, 'Times New Roman', serif;
    font-size: clamp(1.9rem, 4vw, 2.6rem);
}

/* The releases' own styles live with ReleaseNotesList, which FenReach's popup renders too. */
/* /Components/Pages/CharacterAutomation.razor.rz.scp.css */
/* How one character fights on its own (Advanced Profiles, P4). Shares the studio/home vocabulary the
   other authoring pages use, so this reads as part of the same product rather than a bolted-on report. */

.profile-blurb[b-l5s48k623a] {
    margin: .4rem 0 0;
    max-width: 68ch;
    font-size: .85rem;
    line-height: 1.55;
    color: #a99a7c;
}

/* ⚠️ Deliberately prominent. It is the honest limit of the page — what it can and cannot tell you —
   and a limit nobody reads is a limit nobody knows about. */
.scope-note[b-l5s48k623a] {
    margin: 0 0 1.1rem;
    padding: .6rem .75rem;
    border-left: 3px solid #6b5a34;
    border-radius: 0 6px 6px 0;
    background: #241d10;
    max-width: 78ch;
    font-size: .8rem;
    line-height: 1.55;
    color: #b3a68a;
}

.scope-note span[b-l5s48k623a] {
    display: block;
    margin-top: .4rem;
    color: #c9b072;
}

.rule-list[b-l5s48k623a] {
    margin: .6rem 0 0;
    padding: 0;
    list-style: none;
    counter-reset: rule;
}

.rule-row[b-l5s48k623a] {
    display: flex;
    gap: .7rem;
    align-items: baseline;
    margin: .3rem 0;
    padding: .5rem .6rem;
    border: 1px solid #3a3020;
    border-radius: 8px;
    background: #1d1810;
}

/* A rule a feat put there is marked, because that is the question the page answers. */
.rule-row.from-fragment[b-l5s48k623a] {
    border-color: #6b5a34;
    background: #241d10;
}

.rule-band[b-l5s48k623a] {
    flex-shrink: 0;
    min-width: 6.5rem;
    font-size: .65rem;
    font-weight: 800;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: #8b7c5e;
}

.rule-body[b-l5s48k623a] {
    display: flex;
    flex-direction: column;
    gap: .15rem;
    min-width: 0;
}

.rule-name[b-l5s48k623a] {
    font-size: .88rem;
    font-weight: 600;
    color: #e8dcc0;
}

.rule-reads[b-l5s48k623a] {
    font-size: .78rem;
    color: #b3a68a;
}

.rule-why[b-l5s48k623a] {
    font-size: .72rem;
    font-style: italic;
    color: #9dc98a;
}

.trace-list[b-l5s48k623a],
.capability-list[b-l5s48k623a] {
    margin: .5rem 0 0;
    padding-left: 1.1rem;
    font-size: .8rem;
    line-height: 1.6;
    color: #b3a68a;
}

.capability-list[b-l5s48k623a] {
    display: flex;
    flex-wrap: wrap;
    gap: .35rem;
    padding-left: 0;
    list-style: none;
}

.capability-list code[b-l5s48k623a] {
    padding: .12rem .4rem;
    border: 1px solid #3a3020;
    border-radius: 5px;
    background: #241d10;
    font-size: .75rem;
    color: #c9b072;
}

.back-link[b-l5s48k623a] {
    display: inline-block;
    margin-top: 1rem;
    font-size: .8rem;
    color: #c9b072;
}
/* /Components/Pages/CharacterSelect.razor.rz.scp.css */
.characters-shell[b-7ord96bpm1]{min-height:100vh;box-sizing:border-box;padding:clamp(1.5rem,4vw,4rem);color:#e7eaf2;background:linear-gradient(rgba(8,12,20,.78),rgba(8,12,20,.9)),url('/images/bg-characters.jpg');background-size:cover;background-position:center}
.eyebrow[b-7ord96bpm1]{font-size:.72rem;font-weight:800;letter-spacing:.16em;color:#dcb763;margin:0 0 .5rem}
h1[b-7ord96bpm1]{margin:0 0 .4rem;font-family:Georgia,serif;font-size:clamp(1.8rem,4vw,2.6rem);color:#faf7ef}
.subhead[b-7ord96bpm1]{margin:0 0 2rem;color:#c3cbe6}
.muted[b-7ord96bpm1]{color:#aab5cf}
.small[b-7ord96bpm1]{font-size:.82rem;line-height:1.5}

/* Own characters left, the rolling offer right. The offer is wider because it carries five cards to the
   left column's variable few. Collapses to one column before either side has to clip. */
.two-columns[b-7ord96bpm1]{display:grid;grid-template-columns:minmax(16rem,1fr) minmax(0,1.6fr);gap:2.5rem;align-items:start;max-width:1400px}
@media (max-width:60rem){.two-columns[b-7ord96bpm1]{grid-template-columns:1fr;gap:2rem}}

.column-head[b-7ord96bpm1]{margin:0 0 .9rem;font-family:Georgia,serif;font-size:1.25rem;color:#f0e2bd}
.column-head-row[b-7ord96bpm1]{display:flex;align-items:baseline;justify-content:space-between;gap:1rem;flex-wrap:wrap}

.own-grid[b-7ord96bpm1]{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:1.2rem}
.offer-grid[b-7ord96bpm1]{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:1.2rem;margin-top:1rem}

.char-card[b-7ord96bpm1],.pregen-card[b-7ord96bpm1]{display:flex;flex-direction:column;border:1px solid #303b57;border-radius:16px;overflow:hidden;background:#0e1320cc;text-decoration:none;color:inherit;box-shadow:0 18px 50px #03060d70;transition:transform .15s ease,border-color .15s ease}
.char-card img[b-7ord96bpm1],.pregen-card img[b-7ord96bpm1]{width:100%;aspect-ratio:3/4;object-fit:cover;display:block}

/* The fallback stands in the portrait's box rather than collapsing the card, so a roster of mixed
   portrait states still lines up. */
.portrait-fallback[b-7ord96bpm1]{width:100%;aspect-ratio:3/4;display:flex;align-items:center;justify-content:center;font-family:Georgia,serif;font-size:3rem;color:#dcb763;background:linear-gradient(160deg,#161d2e,#0b0f1a)}

.char-info[b-7ord96bpm1]{padding:.9rem 1rem 1.1rem;text-align:left}
.char-info h3[b-7ord96bpm1]{margin:0 0 .35rem;font-family:Georgia,serif;font-size:1.05rem;color:#faf7ef}
.char-info p[b-7ord96bpm1]{margin:0 0 .8rem;font-size:.8rem;line-height:1.5;color:#aab5cf}
.char-card.playable:hover[b-7ord96bpm1],.char-card.busy:hover[b-7ord96bpm1]{transform:translateY(-4px);border-color:#dcb763}

.select-badge[b-7ord96bpm1]{display:inline-flex;align-items:center;gap:.4rem;font-weight:800;font-size:.78rem;color:#16110a;background:linear-gradient(135deg,#c69446,#e8c36a);border-radius:999px;padding:.42rem .85rem;text-decoration:none}
/* A character already on an adventure is the one you most likely came for, so "Continue" reads cooler
   than the gold call to action rather than dimmer than it. */
.char-card.busy .select-badge[b-7ord96bpm1]{color:#eaf1ff;background:linear-gradient(135deg,#2c4a7a,#3f6ba8)}

.char-card.dead[b-7ord96bpm1]{cursor:default}
.char-card.dead img[b-7ord96bpm1],.char-card.dead .portrait-fallback[b-7ord96bpm1]{filter:grayscale(.8) brightness(.5)}
.char-card.dead .char-info[b-7ord96bpm1]{opacity:.8}
.tag-dead[b-7ord96bpm1]{display:inline-flex;align-items:center;gap:.4rem;font-weight:700;font-size:.74rem;color:#e6b9b9;border:1px solid #5a3030;background:#241417;border-radius:999px;padding:.36rem .75rem}
.obituary[b-7ord96bpm1]{margin:.7rem 0 0;font-style:italic;font-size:.76rem;line-height:1.5;color:#9aa4bd}

/* A pre-gen card is a BUTTON -- the whole card opens the mock sheet -- so the browser's own button
   styling has to be unwound before the shared card rules above can show through. */
.pregen-card[b-7ord96bpm1]{font:inherit;padding:0;cursor:pointer;text-align:left;appearance:none}
.pregen-card:hover[b-7ord96bpm1]{transform:translateY(-4px);border-color:#dcb763}
.pregen-card.opened[b-7ord96bpm1]{border-color:#dcb763;box-shadow:0 0 0 1px #dcb76366,0 18px 50px #03060d70}

.create-button[b-7ord96bpm1]{display:inline-flex;align-items:center;gap:.45rem;font-weight:800;font-size:.85rem;color:#16110a;background:linear-gradient(135deg,#c69446,#e8c36a);border-radius:999px;padding:.6rem 1.2rem;text-decoration:none}
.create-button.standalone[b-7ord96bpm1]{margin-top:1.5rem}

.reroll[b-7ord96bpm1]{font:inherit;font-size:.8rem;font-weight:700;color:#e7eaf2;background:#151d2e;border:1px solid #3a4763;border-radius:999px;padding:.5rem 1rem;cursor:pointer}
.reroll:hover:not(:disabled)[b-7ord96bpm1]{border-color:#dcb763;color:#f0e2bd}
.reroll:disabled[b-7ord96bpm1]{opacity:.6;cursor:progress}

.empty-state[b-7ord96bpm1]{border:1px solid #303b57;border-radius:16px;background:#0e1320cc;padding:1.6rem;box-shadow:0 18px 50px #03060d70}
.empty-state h3[b-7ord96bpm1]{margin:0 0 .6rem;font-family:Georgia,serif;font-size:1.15rem;color:#faf7ef}
.empty-state p[b-7ord96bpm1]{margin:0 0 1.2rem;font-size:.88rem;line-height:1.6;color:#aab5cf}

.mock-sheet[b-7ord96bpm1]{margin-top:2.5rem;max-width:1400px;border:1px solid #3a4763;border-radius:16px;background:#0e1320e6;padding:1.5rem;box-shadow:0 18px 50px #03060d70}
.mock-head[b-7ord96bpm1]{display:flex;align-items:center;justify-content:space-between;gap:1rem}
.mock-head h2[b-7ord96bpm1]{margin:0;font-family:Georgia,serif;font-size:1.4rem;color:#faf7ef}
.close-sheet[b-7ord96bpm1]{font:inherit;color:#aab5cf;background:none;border:none;font-size:1.1rem;cursor:pointer;padding:.2rem .5rem}
.close-sheet:hover[b-7ord96bpm1]{color:#f0e2bd}

/* The mock sheet's stat strip. Wraps rather than scrolls -- six abilities plus HP and AC is a fixed,
   small set, and a horizontal scrollbar on eight numbers reads as a layout fault. */
.stat-row[b-7ord96bpm1]{display:flex;flex-wrap:wrap;gap:.6rem;margin:1.2rem 0}
.stat[b-7ord96bpm1]{display:flex;flex-direction:column;align-items:center;gap:.15rem;min-width:3.6rem;padding:.55rem .7rem;border:1px solid #303b57;border-radius:10px;background:#131a2b}
.stat-label[b-7ord96bpm1]{font-size:.65rem;font-weight:800;letter-spacing:.1em;color:#8f9ab6}
.stat-value[b-7ord96bpm1]{font-family:Georgia,serif;font-size:1.25rem;color:#f0e2bd}

.take-button[b-7ord96bpm1]{font:inherit;margin-top:1.2rem;display:inline-flex;align-items:center;gap:.45rem;font-weight:800;font-size:.9rem;color:#16110a;background:linear-gradient(135deg,#c69446,#e8c36a);border:none;border-radius:999px;padding:.7rem 1.4rem;cursor:pointer}
.take-button:hover:not(:disabled)[b-7ord96bpm1]{filter:brightness(1.08)}
.take-button:disabled[b-7ord96bpm1]{opacity:.55;cursor:not-allowed}
.mint-error[b-7ord96bpm1]{margin:1rem 0 0;font-size:.85rem;line-height:1.5;color:#e6b9b9;border:1px solid #5a3030;background:#241417;border-radius:10px;padding:.7rem .9rem}

/* A character on its way out: present, clearly leaving, and recoverable. Greyed like the dead, but the
   colour is amber rather than red — it has not gone yet and the whole point is that it can come back. */
.char-card.retiring[b-7ord96bpm1]{opacity:.9;border-color:#5d4a2a}
.char-card.retiring .portrait-fallback[b-7ord96bpm1]{filter:grayscale(.7) brightness(.6)}
.retiring-head[b-7ord96bpm1]{margin-top:2rem}
.tag-retiring[b-7ord96bpm1]{display:inline-flex;align-items:center;gap:.4rem;font-weight:700;font-size:.74rem;color:#e8c36a;border:1px solid #5d4a2a;background:#241f14;border-radius:999px;padding:.36rem .75rem}
.select-badge.restore[b-7ord96bpm1]{font:inherit;border:none;cursor:pointer;color:#eaf1ff;background:linear-gradient(135deg,#2c4a7a,#3f6ba8)}
.select-badge.restore:disabled[b-7ord96bpm1]{opacity:.6;cursor:progress}

/* ⛔ A SPAN, not a disabled anchor — `disabled` does nothing on <a> and the link would still navigate.
   It has to look unavailable AND be unclickable, which only a non-link can be. */
.create-button.disabled[b-7ord96bpm1]{opacity:.5;cursor:not-allowed;filter:grayscale(.5)}
.slots-note[b-7ord96bpm1]{margin:.6rem 0 0}

/* ⚠️ A SUB-heading of "Your characters", not a peer of it. It was .column-head, which put it at the same
   rank as the two real column titles and made it compete with them — Dave read that as "badly formed,
   larger than all the others", and he was right about the rank even though the size matched. */
.retiring-head[b-7ord96bpm1]{margin:2rem 0 .8rem;font-family:Georgia,serif;font-size:.78rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:#8f9ab6}

/* ⚠️ The card's actions are a ROW, not two things that happened to land beside each other. As a bare
   text link the Retire control sat on the gold pill's baseline, a third its height and touching it —
   Dave read that as "too small, too close, wrong colour", and all three were the same fault. */
.card-actions[b-7ord96bpm1]{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap;margin-top:.2rem}

/* Retiring is undoable for 30 days, so this is an outlined button rather than a solid red one —
   destructive styling would promise a finality this does not have. It goes red on hover, where the
   intent is already clear. */
.retire-link[b-7ord96bpm1]{font:inherit;font-size:.74rem;font-weight:700;color:#b7a0a3;background:transparent;border:1px solid #5a4044;border-radius:999px;padding:.42rem .85rem;cursor:pointer;white-space:nowrap}
.retire-link:hover[b-7ord96bpm1]{border-color:#c9545c;color:#ffe6e7;background:#3a1e22}
.retire-confirm[b-7ord96bpm1]{margin:.5rem 0 .5rem;font-size:.76rem;line-height:1.45;color:#ded5c0}
.retire-actions[b-7ord96bpm1]{display:flex;gap:.4rem;flex-wrap:wrap}
.retire-yes[b-7ord96bpm1]{font:inherit;font-size:.72rem;font-weight:800;color:#ffe6e7;background:#7c2a30;border:1px solid #c9545c;border-radius:999px;padding:.34rem .75rem;cursor:pointer}
.retire-yes:disabled[b-7ord96bpm1],.retire-no:disabled[b-7ord96bpm1]{opacity:.6;cursor:progress}
.retire-no[b-7ord96bpm1]{font:inherit;font-size:.72rem;font-weight:700;color:#b7c2df;background:transparent;border:1px solid #3a4763;border-radius:999px;padding:.34rem .75rem;cursor:pointer}

/* The name is the way into the sheet, so it has to look like a way in without becoming a blue link in a
   dark fantasy roster. Underline on hover only. */
.sheet-link[b-7ord96bpm1]{color:inherit;text-decoration:none}
.sheet-link:hover[b-7ord96bpm1]{color:#f0e2bd;text-decoration:underline;text-underline-offset:.18em}


/* IN MEMORIAM (issue #1030). Matched to .retiring-head, which is the other "these are not your playable
   roster" heading on this page — two headings doing the same job must not look like two designs. */
.memoriam-head[b-7ord96bpm1]{margin:2.4rem 0 .5rem;font-family:Georgia,serif;font-size:.78rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:#c98f97}
.memoriam-note[b-7ord96bpm1]{margin:0 0 .9rem}
/* /Components/Pages/CharacterSheet.razor.rz.scp.css */
.sheet-shell[b-8trdu0dw7h]{min-height:100vh;box-sizing:border-box;padding:clamp(1.5rem,4vw,4rem);color:#e7eaf2;background:linear-gradient(rgba(8,12,20,.78),rgba(8,12,20,.92)),url('/images/bg-characters.jpg');background-size:cover;background-position:center}
.eyebrow[b-8trdu0dw7h]{font-size:.72rem;font-weight:800;letter-spacing:.16em;color:#dcb763;margin:0 0 .5rem}
.sheet-head[b-8trdu0dw7h]{display:flex;align-items:baseline;justify-content:space-between;gap:1rem;flex-wrap:wrap;margin-bottom:1.6rem}
h1[b-8trdu0dw7h]{margin:0;font-family:Georgia,serif;font-size:clamp(1.8rem,4vw,2.6rem);color:#faf7ef}
.back-link[b-8trdu0dw7h]{color:#dcb763;text-decoration:none;font-size:.85rem;font-weight:700}
.back-link:hover[b-8trdu0dw7h]{text-decoration:underline}
.muted[b-8trdu0dw7h]{color:#aab5cf}
.small[b-8trdu0dw7h]{font-size:.8rem;line-height:1.5}
.sheet-error[b-8trdu0dw7h]{color:#e6b9b9;border:1px solid #5a3030;background:#241417;border-radius:12px;padding:.9rem 1.1rem}

/* THE PHOTO CONTROLS, which live in the sheet's own header toolbar rather than on this page.
   ⛔ They sat on the page until 2026-09-09 and were UNREACHABLE the whole time: the sheet is a modal
   whose backdrop is fixed to the whole viewport, so it covered them. The panel's frame, fallback initial
   and explanatory paragraph went with them — the sheet already draws the portrait in its avatar disc,
   and a second copy of the same picture two inches above it was never the point.
   ⚠️ Styled from THIS stylesheet even though they render inside ActorSheet, because a RenderFragment
   keeps the scope of the file that WROTE it. That is the same rule that stops ActorSheet reaching in
   here. → memory `a-scoped-rule-cannot-reach-a-child`
   ⚠️ Sized down to sit on one line of a header beside a name and a close button — the page-sized pill
   these grew from pushed the close button onto a second row. */
.portrait-button[b-8trdu0dw7h]{font:inherit;font-size:.72rem;font-weight:800;white-space:nowrap;color:#16110a;background:linear-gradient(135deg,#c69446,#e8c36a);border:none;border-radius:999px;padding:.4rem .85rem;cursor:pointer}
.portrait-button:hover:not(:disabled)[b-8trdu0dw7h]{filter:brightness(1.08)}
.portrait-button:disabled[b-8trdu0dw7h]{opacity:.55;cursor:progress}
/* ⚠️ Remove is outlined, not solid — it is the lesser action and must not compete with Replace. */
.portrait-button.ghost[b-8trdu0dw7h]{color:#b7a0a3;background:transparent;border:1px solid #5a4044}
.portrait-button.ghost:hover:not(:disabled)[b-8trdu0dw7h]{border-color:#c9545c;color:#ffe6e7;background:#3a1e22;filter:none}


/* THE TOMBSTONE (issue #1030). A dead hero's sheet stays open and says so.
   ⚠️ A block-level <section> with its own border rather than a tint on .sheet-shell: a background
   wash reads as a theme, and this has to read as a STATEMENT about this one character.
   ⚠️ Muted red, the same family .sheet-error and .tag-dead already use, so the page has one
   vocabulary for "something has gone wrong here" rather than a third invented colour. */
.sheet-tombstone[b-8trdu0dw7h]{margin:0 0 1.8rem;border:1px solid #5a3030;border-left:4px solid #9c4048;background:linear-gradient(#241417,#1b1013);border-radius:12px;padding:1.1rem 1.3rem}
.sheet-tombstone h2[b-8trdu0dw7h]{margin:0 0 .6rem;font-family:Georgia,serif;font-size:1.15rem;font-weight:700;color:#f0d2d2}
.tombstone-obituary[b-8trdu0dw7h]{margin:0 0 .6rem;font-style:italic;font-size:.92rem;line-height:1.6;color:#e6c9c9}
.tombstone-when[b-8trdu0dw7h]{margin:0 0 .5rem;font-size:.78rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:#b98f95}
.tombstone-note[b-8trdu0dw7h]{margin:0}
/* ⚠️ The eyebrow says IN MEMORIAM here, and gold is the wrong voice for it. */
.sheet-fallen .eyebrow[b-8trdu0dw7h]{color:#c98f97}
/* /Components/Pages/CreateCharacter.razor.rz.scp.css */
.home-shell[b-pm69k7c2nm]{min-height:100vh;padding:clamp(1.25rem,3vw,3rem);max-width:960px;margin:auto;color:#e7eaf2;background:linear-gradient(rgba(8,12,20,.82),rgba(8,12,20,.92)),url('/images/bg-play.jpg');background-size:cover;background-position:center;background-attachment:fixed}
.home-header[b-pm69k7c2nm]{margin-bottom:1.6rem}
.home-header h1[b-pm69k7c2nm]{margin:0;font-family:Georgia,serif;font-size:clamp(1.55rem,3vw,2.35rem);color:#faf7ef}
.eyebrow[b-pm69k7c2nm]{font-size:.7rem;font-weight:800;letter-spacing:.14em;color:#8b96b6;margin:0 0 .4rem}
.home-card[b-pm69k7c2nm]{background:linear-gradient(145deg,#141a2ae6,#0e1320e6);border:1px solid #27334f;border-radius:16px;box-shadow:0 22px 70px #03060d70;padding:1.1rem 1.2rem;margin-bottom:1rem}
.muted[b-pm69k7c2nm]{color:#8e9ab8;font-size:.82rem}
.step-head[b-pm69k7c2nm]{display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;margin-bottom:.7rem}
.step-label[b-pm69k7c2nm]{font-size:.72rem;font-weight:800;letter-spacing:.12em;color:#b7c2df;margin:0;text-transform:uppercase}
.step-actions[b-pm69k7c2nm]{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap}

.slot-stack[b-pm69k7c2nm]{display:grid;gap:.45rem}
.inline-choice[b-pm69k7c2nm]{display:flex;align-items:center;gap:.7rem;flex-wrap:wrap;padding:.5rem .9rem;border:1px solid #2b364f;border-radius:11px;background:#111827a8}
.inline-label[b-pm69k7c2nm]{flex:0 0 8.5rem;font-size:.67rem;font-weight:800;letter-spacing:.11em;text-transform:uppercase;color:#8b96b6}

.pill-row[b-pm69k7c2nm]{display:flex;gap:.4rem;flex-wrap:wrap}
.pill-row.wrap[b-pm69k7c2nm]{margin-top:.2rem}
.pill[b-pm69k7c2nm]{border:1px solid #303b57;border-radius:999px;background:#111827d9;color:#cdd6ea;padding:.38rem .8rem;font-size:.76rem;cursor:pointer}
.pill:hover:not(:disabled)[b-pm69k7c2nm]{border-color:#4a5c88}
.pill.selected[b-pm69k7c2nm]{border-color:#e8c36a;color:#f5e3b0;box-shadow:0 0 0 1px #e8c36a55}
.pill.granted[b-pm69k7c2nm]{border-color:#3b7a57;color:#9fd3b4;opacity:.9}
.pill:disabled[b-pm69k7c2nm]{cursor:not-allowed}

.pool-row[b-pm69k7c2nm]{display:flex;gap:.45rem;flex-wrap:wrap;margin-bottom:.8rem}
.pool[b-pm69k7c2nm]{border:1px solid #303b57;border-radius:999px;background:#0d1322;color:#9da8c5;padding:.3rem .75rem;font-size:.71rem;cursor:help}
.pool strong[b-pm69k7c2nm]{color:#e8c36a;font-size:.8rem;margin:0 .15rem}
.pool.done[b-pm69k7c2nm]{border-color:#2c4a3a;color:#7f9d8d}
.pool.done strong[b-pm69k7c2nm]{color:#7f9d8d}

.ability-grid[b-pm69k7c2nm]{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:.5rem}
.ability[b-pm69k7c2nm]{display:flex;flex-direction:column;align-items:center;gap:.3rem;padding:.65rem .5rem;border:1px solid #2b364f;border-radius:12px;background:#0f1626d9}
.ability.flawed[b-pm69k7c2nm]{border-color:#5b3a3f}
.ability-head[b-pm69k7c2nm]{display:flex;align-items:center;gap:.35rem}
.ability-name[b-pm69k7c2nm]{font-size:.68rem;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:#8b96b6;cursor:help}
.ability-flaw[b-pm69k7c2nm]{font-size:.58rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:#e08b93;border:1px solid #6d3f46;border-radius:999px;padding:.08rem .35rem;cursor:help}
.ability-value[b-pm69k7c2nm]{display:flex;align-items:baseline;gap:.4rem}
.ability-value strong[b-pm69k7c2nm]{font-family:Georgia,serif;font-size:1.5rem;color:#faf7ef;line-height:1}
.ability-mod[b-pm69k7c2nm]{font-size:.82rem;font-weight:700;color:#e8c36a}
.ability-steps[b-pm69k7c2nm]{display:flex;gap:.3rem}
.step[b-pm69k7c2nm]{width:2rem;height:1.75rem;border:1px solid #34405c;border-radius:8px;background:#141b2b;color:#cdd6ea;font-size:.9rem;line-height:1;cursor:pointer;display:grid;place-items:center}
.step:hover:not(:disabled)[b-pm69k7c2nm]{border-color:#e8c36a;color:#f5e3b0}
.step:disabled[b-pm69k7c2nm]{opacity:.28;cursor:not-allowed}
.ability-note[b-pm69k7c2nm]{display:flex;gap:.3rem;flex-wrap:wrap;justify-content:center;min-height:.9rem}
.ability-note span[b-pm69k7c2nm]{font-size:.62rem;color:#6f7c99;cursor:help}
.ability-note .chosen[b-pm69k7c2nm]{color:#9fb2d8}

.multi-slot[b-pm69k7c2nm]{display:flex;align-items:flex-start;gap:.7rem;padding:.55rem .7rem .55rem .9rem;border:1px solid #2b364f;border-radius:11px;background:#111827a8;flex-wrap:wrap}
.slot-label[b-pm69k7c2nm]{flex:0 0 8.5rem;padding-top:.35rem;font-size:.67rem;font-weight:800;letter-spacing:.11em;text-transform:uppercase;color:#8b96b6}
.chip-area[b-pm69k7c2nm]{flex:1;min-width:12rem;display:flex;gap:.35rem;flex-wrap:wrap;align-items:center}
.chip[b-pm69k7c2nm]{display:inline-flex;align-items:center;gap:.35rem;border:1px solid #3c4a6d;border-radius:999px;background:#16203699;color:#e7eaf2;padding:.28rem .35rem .28rem .7rem;font-size:.76rem}
.chip.granted[b-pm69k7c2nm]{border-color:#3b7a57;color:#c6e6d5}
.chip em[b-pm69k7c2nm]{font-style:normal;font-size:.65rem;color:#8b96b6}
.chip button[b-pm69k7c2nm]{width:1.25rem;height:1.25rem;border-radius:999px;border:0;background:#232d44;color:#9da8c5;cursor:pointer;font-size:.6rem;line-height:1;display:grid;place-items:center}
/* The stepper is one control, not three loose buttons: an oval a shade lighter than the chip binds
   −, the count and ＋ together, so the destructive ✕ beside them reads as separate at a glance. */
.chip .stepper[b-pm69k7c2nm]{display:inline-flex;align-items:center;gap:.1rem;padding:.1rem .2rem;border-radius:999px;background:#26334e;box-shadow:inset 0 0 0 1px #ffffff12}
.chip .stepper b[b-pm69k7c2nm]{min-width:1.1rem;text-align:center;font-size:.72rem;font-variant-numeric:tabular-nums;color:#dfe6f5}
.chip .stepper button[b-pm69k7c2nm]{background:transparent;color:#b7c2df}
.chip .stepper button:hover:not(:disabled)[b-pm69k7c2nm]{background:#38496f;color:#f5e3b0}
.chip .stepper button:disabled[b-pm69k7c2nm]{opacity:.35;cursor:not-allowed}
/* Red at rest, not only on hover: this sits a few pixels from the stepper's ＋ and must never be
   mistaken for it (Dave clicked one expecting the other). */
.chip .chip-remove[b-pm69k7c2nm]{background:#3a1e22;color:#e08b93}
.chip .chip-remove:hover[b-pm69k7c2nm]{background:#7c2a30;color:#ffe6e7}
.chip-add[b-pm69k7c2nm]{border:1px dashed #3f4d70;border-radius:999px;background:transparent;color:#b7c2df;padding:.3rem .8rem;font-size:.74rem;cursor:pointer}
.chip-add:hover[b-pm69k7c2nm]{border-color:#e8c36a;color:#f5e3b0}
.slot-chip-inline[b-pm69k7c2nm]{border:1px solid #34405c;border-radius:999px;background:transparent;color:#9da8c5;padding:.26rem .65rem;font-size:.66rem;font-weight:700;cursor:pointer}
.slot-chip-inline:hover[b-pm69k7c2nm]{border-color:#e8c36a;color:#f5e3b0}

.budget-bar[b-pm69k7c2nm]{height:.35rem;border-radius:999px;background:#1a2338;overflow:hidden;margin-bottom:.8rem}
.budget-bar span[b-pm69k7c2nm]{display:block;height:100%;background:linear-gradient(90deg,#c69446,#e8c36a);transition:width .18s ease}
.budget-bar span.over[b-pm69k7c2nm]{background:linear-gradient(90deg,#8d4046,#d4646d)}
.over-budget[b-pm69k7c2nm]{color:#ffb2b6}

.create-row[b-pm69k7c2nm]{display:flex;gap:.7rem;flex-wrap:wrap;margin:.4rem 0}
.create-row input[b-pm69k7c2nm],.create-row textarea[b-pm69k7c2nm]{flex:1;min-width:220px;background:#141b2b;color:#eef2fb;border:1px solid #34405c;border-radius:10px;padding:.75rem;font:inherit}
.create-row textarea[b-pm69k7c2nm]{resize:vertical}
/* #869 — sex and voice, side by side because one chooses the default for the other. ⚠️ Scoped CSS,
   which reaches these because they are written directly in the .razor. A control rendered by a CHILD
   component would need the ::deep escape hatch instead. */
.create-identity[b-pm69k7c2nm]{align-items:flex-end}
.identity-field[b-pm69k7c2nm]{display:flex;flex-direction:column;gap:.3rem;flex:0 1 14rem;color:#aab5cf;font-size:.78rem}
.identity-field select[b-pm69k7c2nm]{background:#141b2b;color:#eef2fb;border:1px solid #34405c;border-radius:10px;padding:.7rem;font:inherit}
/* The resolved default, or why there is not one. Full width so it wraps under both pickers. */
.identity-why[b-pm69k7c2nm]{flex-basis:100%;margin:.2rem 0 0;color:#8b96b8;font-size:.75rem;line-height:1.5}
.identity-why a[b-pm69k7c2nm]{color:#dcb763}
.create-button[b-pm69k7c2nm]{border:0;border-radius:10px;padding:.8rem 1.4rem;font-weight:800;background:linear-gradient(135deg,#c69446,#e8c36a);color:#16110a;cursor:pointer}
.create-button:disabled[b-pm69k7c2nm]{opacity:.55;cursor:not-allowed}
.ghost-button[b-pm69k7c2nm]{border:1px solid #34405c;border-radius:9px;background:transparent;color:#b7c2df;padding:.45rem .85rem;font-size:.75rem;font-weight:700;cursor:pointer}
.ghost-button:hover[b-pm69k7c2nm]{border-color:#e8c36a;color:#f5e3b0}
.ghost-button:disabled[b-pm69k7c2nm]{opacity:.55;cursor:wait}
.create-error[b-pm69k7c2nm]{margin:.6rem 0 0;padding:.6rem;border-radius:8px;font-size:.78rem;background:#422326;color:#ffb2b6;border:1px solid #8d4046}

.review-grid[b-pm69k7c2nm]{display:grid;grid-template-columns:180px 1fr;gap:1.2rem;align-items:start}
.portrait-box[b-pm69k7c2nm]{display:flex;flex-direction:column;gap:.6rem}
.portrait-box img[b-pm69k7c2nm]{width:100%;aspect-ratio:1;object-fit:cover;border-radius:12px;border:1px solid #34405c}
.portrait-empty[b-pm69k7c2nm]{width:100%;aspect-ratio:1;display:grid;place-items:center;border-radius:12px;border:1px dashed #34405c;color:#5d6b8e;font-size:.8rem}
.portrait-actions[b-pm69k7c2nm]{display:flex;flex-direction:column;gap:.4rem}
/* Gold and solid against the ghost button beside it: the free path is the one that should read as the
   thing to do (#30). The order in the markup is the ticket; this is only what makes it look like it. */
.portrait-choose[b-pm69k7c2nm]{border:0;border-radius:9px;background:linear-gradient(135deg,#c69446,#e8c36a);color:#16110a;padding:.5rem .85rem;font-size:.78rem;font-weight:800;cursor:pointer}
.portrait-choose:hover[b-pm69k7c2nm]{filter:brightness(1.08)}
.portrait-cost[b-pm69k7c2nm]{margin:0;font-size:.68rem;line-height:1.45;color:#78849f}
.sheet[b-pm69k7c2nm]{list-style:none;margin:0 0 .8rem;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:.4rem}
.sheet li[b-pm69k7c2nm]{font-size:.8rem;color:#9da8c5;background:#0d1322d9;border:1px solid #27334f;border-radius:8px;padding:.45rem .6rem}
.sheet strong[b-pm69k7c2nm]{color:#e7eaf2;margin-right:.3rem}

@media (max-width:640px){
    .review-grid[b-pm69k7c2nm]{grid-template-columns:1fr}
    .inline-label[b-pm69k7c2nm],.slot-label[b-pm69k7c2nm]{flex-basis:100%;padding-top:0}
}

/* ⭐ A step announces that it still wants something, so a slot revealed LATE is not missed. Dave,
   2026-09-08: he chose things slightly out of order, an ancestry feat slot appeared after heritage --
   correct behaviour -- and nothing said so, "it just blended in with the background".

   ⛔⛔ THIS USED TO BE `:has(.slot.empty)` AND IT WAS SILENTLY INERT ON A THIRD OF THE WIZARD. Reading
   the DOM only works where the DOM has slots in it: six steps are built from ChoiceSlot, but Ability
   scores is a stepper and Trained skills is a row of pills, so there was nothing for :has() to find and
   those two never coloured. It looked like it worked. Dave found it by walking a character.

   ⭐ The class now comes from StepState(), which asks the RULES -- the same TaggedProblems that blocks
   creation -- so a green step and a refusal to save cannot disagree. Never put a :has() rule back here.

   ⚠️ Still deliberately quiet. A wash and a hairline inset, not a red box: a wizard walked down once
   would read eight loud rows as eight errors rather than eight things still to do. */
.home-card.step-wanting[b-pm69k7c2nm] {
    border-color: #6d454e;
    box-shadow: 0 22px 70px #03060d70, inset 0 0 0 1px #6d454e26;
}

.home-card.step-ready[b-pm69k7c2nm] {
    border-color: #375c47;
    box-shadow: 0 22px 70px #03060d70, inset 0 0 0 1px #375c4726;
}

/* The step-1 name clash. Red enough to stop somebody typing a whole character under a name that will be
   refused, quiet enough not to shout at an empty field they are still filling in. */
.name-taken[b-pm69k7c2nm] {
    margin: .45rem 0 0;
    font-size: .8rem;
    line-height: 1.5;
    color: #e6b9b9;
    border: 1px solid #5a3030;
    background: #241417;
    border-radius: 10px;
    padding: .55rem .8rem;
}

/* ⛔ A ranged weapon with nothing to shoot (Dave, 2026-09-10). Amber: it is a warning, not a refusal
   — an archer who genuinely wants three javelins may finish anyway. Styled here because a class name
   with no rule draws nothing and errors nowhere. */
.ammo-warning[b-pm69k7c2nm] {
    margin: 0.35rem 0 0;
    color: #e8d08a;
    font-size: 0.82rem;
}
/* /Components/Pages/FenReach.razor.rz.scp.css */
/*
    FenReach prototype. Mobile-first on purpose — the product is a phone game, so the narrow layout is
    the real one and the desktop view is the accommodation rather than the other way round.
*/

/* ⛔ THE COLUMN IS DOUBLED: 46rem → 92rem (Dave, 2026-09-10). "make the text area larger - wider.
   Right now its only using about 25% of my screen. Double that." His number, not a judgement of mine —
   if the lines end up too long to read comfortably, THIS is the one value to change.
   ⚠️ The shell is full-width now and the SECTIONS are what get centred, so the scene below can run edge
   to edge behind them rather than stopping at the column. */
.fen-shell[b-1nvogau30c] {
    /* ⛔⛔ DECLARED HERE, NOT ON `:root`. This is a SCOPED stylesheet: Blazor rewrites every selector
       with the component's scope attribute, so `:root` becomes `:root[b-xxxxx]` and matches nothing at
       all. The custom property would never be defined, `min(var(--fen-column), 100%)` would be invalid,
       and the whole width declaration would be dropped — the bands would silently run full-width and
       nothing would report a fault. `.fen-shell` is this component's own markup, so it carries the
       attribute and its children inherit the value.
       → memory `a-custom-property-that-names-nothing`, `scoped-css-cannot-reach-a-built-element` */
    --fen-column: 92rem;

    display: flex;
    flex-direction: column;
    height: 100dvh;
    color: #d9deea;
    font-family: Georgia, 'Times New Roman', serif;

    /* ⭐⭐ THE SCENE IS PAINTED ART, NOT A GRADIENT (Dave picked `marsh-dusk`, 2026-09-10).

       ⛔ Three rounds of CSS gradients arrived as "still plain looking", and our own teardown of
       Everweave had already said why: their backgrounds are painted `.webp` files, and "no amount of
       `conic-gradient` tuning reaches it, because the mottling is drawn by an artist and simply
       composited". → `plan/everweave-dice-roller-teardown.md`, memory `a-look-is-not-a-number`

       ⚠️ Generated once and committed, never per request — 45 KB of webp, down from a 2.3 MB PNG,
       which matters because this is the most-served image on the surface.

       The two layers ON TOP of it are what keep it furniture: a scrim that deepens toward the bottom
       where the composer sits, and a vignette that closes the corners. Dave's brief was "very dim so
       it doesnt clash with the GM UI", and the picture is allowed to be handsome only where nobody is
       reading. Dial it either way by changing the two alphas below, not the art. */
    /* ⚠️ THE SCRIM IS THE DOMINANT LAYER, and it was doing all the killing: 60% black at the top,
       80% at the middle, 95% at the bottom. Under a 95%-opaque panel that left the painting visible at
       about ONE PERCENT — which is why two rounds of improving the art changed nothing anybody could
       see. These four alphas are the dial; the art and the panel are not.
          scrim   38% -> 55% -> 78%   (top -> middle -> bottom)
          vignette                72% at the outer edge
       Raise them to calm it down, lower them to let the fen through. */
    background:
        linear-gradient(180deg, #05070d61 0%, #05070d8c 50%, #05070dc7 100%),
        radial-gradient(145% 125% at 50% 38%, transparent 20%, #03050ab8 100%),
        url("/brand/backdrop-marsh-dusk.webp") center / cover no-repeat,
        #070b13;
}

/* ⭐ Each band is centred at the column width and the scene shows past it, which is what makes the page
   read as a lit room rather than a strip. One rule, so the bands cannot drift apart. */
.fen-top[b-1nvogau30c],
.fen-transcript[b-1nvogau30c],
.fen-rail[b-1nvogau30c],
.fen-composer[b-1nvogau30c],
.fen-pick[b-1nvogau30c],
.fen-start[b-1nvogau30c] {
    /* ⚠️ A GUTTER, not 100%. With the bands flush to the edges the transcript covered essentially the
       whole viewport and the scene behind it was reduced to a strip along the top — all that work
       arriving as "still plain looking". Two rems of breathing room is what lets a background BE one. */
    width: min(var(--fen-column), calc(100% - 5rem));
    margin-inline: auto;
    /* ⛔⛔ BORDER-BOX, and it is not a tidy-up. Every one of these bands has horizontal padding, and
       under the default content-box that padding is ADDED to `width: 100%` — so each band came out
       wider than the screen and the whole page scrolled sideways. Measured: a 1265px viewport against a
       1318px document. It reads as a clipped "Leave" link in the corner, which looks like a layout
       nobody finished rather than a box-model mistake. */
    box-sizing: border-box;
}

/* --- top bar ------------------------------------------------------------------------------- */

.fen-top[b-1nvogau30c] {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .7rem 1rem;
    border-bottom: 1px solid #dcb7631f;
    background: linear-gradient(180deg, #0a0e18b3, #0a0e1859);
    backdrop-filter: blur(5px);
    -webkit-backdrop-filter: blur(5px);
    flex: 0 0 auto;
}

.fen-mark[b-1nvogau30c] {
    font-weight: 700;
    letter-spacing: .06em;
    color: #f0e2bd;
}

.fen-campaign[b-1nvogau30c] {
    font-size: .85rem;
    color: #8e97a8;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.fen-spacer[b-1nvogau30c] { flex: 1 1 auto; }

/* ⚠️ `.fen-mode` and `.fen-mode-on` were deleted with the Pure/Assisted switch on 2026-09-10 — the
   comparison is over and Assisted won. Rules for markup nobody renders draw nothing and hide nothing;
   they just make the next reader look for the control. */

.fen-chrome[b-1nvogau30c] {
    font-size: .8rem;
    color: #8e97a8;
    text-decoration: none;
}

.fen-chrome:hover[b-1nvogau30c] { color: #dcb763; }

/* ⭐ Report Bug (Dave, 2026-09-15). ⚠️ Borrowed from .fen-chrome with a hairline; nobody has chosen this look. */
.fen-bug[b-1nvogau30c] {
    font-size: .8rem;
    padding: .2rem .6rem;
    border: 1px solid #dcb76359;
    border-radius: .25rem;
    background: none;
    color: #c9b88e;
    cursor: pointer;
}

.fen-bug:hover[b-1nvogau30c] { color: #dcb763; border-color: #dcb763; }
.fen-bug:disabled[b-1nvogau30c] { opacity: .5; cursor: default; }

.fen-notice[b-1nvogau30c] { margin: .5rem 0 0; color: #b9c3d3; font-size: .85rem; }

/* --- the transcript ------------------------------------------------------------------------ */

/* ⭐ THE GM SCREEN (Dave, 2026-09-10): "give the GM screen a nice border". It is the thing being read,
   so it is the only surface on the page that is lifted off the scene — a panel with a warm hairline, a
   soft gold bloom outside it and a darker plate inside. Everything else stays flat on purpose; two lit
   surfaces would compete and the transcript would stop being the obvious place to look. */
.fen-transcript[b-1nvogau30c] {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 1.4rem 1.6rem;
    display: flex;
    flex-direction: column;
    gap: .9rem;
    margin: 1rem auto;
    /* ⚠️ The hairline was 25% gold and read as nothing at all. A frame either frames or it does not. */
    border: 1px solid #dcb763a6;
    border-radius: 10px;
    /* ⭐⭐ FROSTED, NOT OPAQUE, and this is what finally let the art be seen. At 95% the panel covered
       essentially the whole viewport and the painting behind it was reduced to a 16px gutter — which is
       why two rounds of "make the background better" changed nothing a person could notice. The problem
       was never the background; it was that nothing let it through.

       ⚠️ The blur is what buys the transparency. Dropping the panel to 78% over a painted scene would
       put mottled brushwork directly behind body text; blurring what shows through keeps the letters on
       a calm field while the colour and the shape of the art still read. A background is not worth a
       word of legibility, and this way it does not cost one. */
    background:
        linear-gradient(180deg, #0f1524c7 0%, #0a1018cc 55%, #0c1220d6 100%);
    backdrop-filter: blur(7px) saturate(115%);
    -webkit-backdrop-filter: blur(7px) saturate(115%);
    box-shadow:
        0 0 0 1px #00000080,
        0 0 28px -6px #dcb7632e,
        0 24px 60px -30px #000,
        inset 0 1px 0 #ffffff1a,
        inset 0 0 60px -30px #dcb76326;
}

/* ⭐ A lit edge along the top of the screen, so the frame reads as a lamp above a table rather than a
   rectangle somebody drew. Its own element would have been markup for decoration; a pseudo-element on
   the panel costs nothing and cannot be selected, copied or read out. */
.fen-transcript[b-1nvogau30c]::before {
    content: "";
    position: sticky;
    top: -1.4rem;
    display: block;
    height: 1px;
    margin: -1.4rem -1.6rem .4rem;
    background: linear-gradient(90deg, transparent, #dcb76399 20%, #dcb763cc 50%, #dcb76399 80%, transparent);
}

.fen-line[b-1nvogau30c] {
    display: flex;
    flex-direction: column;
    gap: .15rem;
    line-height: 1.6;
}

.fen-speaker[b-1nvogau30c] {
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: #7d879a;
}

.fen-text[b-1nvogau30c] { white-space: pre-wrap; }

/* The player's own line reads as an aside rather than as the story. */
.fen-player[b-1nvogau30c] { align-items: flex-end; text-align: right; }
.fen-player .fen-text[b-1nvogau30c] { color: #aeb6c6; font-style: italic; }
.fen-player .fen-speaker[b-1nvogau30c] { color: #5f6a7d; }

.fen-dm .fen-text[b-1nvogau30c] { color: #e8edf7; }
.fen-dm .fen-speaker[b-1nvogau30c] { color: #dcb763; }

/* Rolls are engine output, and showing them is a large part of the pitch. */
/* One engine line can be a list: where everyone stands in a fight, one combatant to a line (Dave, 2026-09-16). */
.fen-engine .fen-text[b-1nvogau30c] {
    white-space: pre-line;
    font-family: ui-monospace, 'Cascadia Mono', Consolas, monospace;
    font-size: .82rem;
    color: #9fb4c9;
    background: #ffffff08;
    border-left: 2px solid #3f5468;
    padding: .3rem .6rem;
}

/* ⭐ Where the story starts: the opening line of a new table, in the GM's gold rather than a grey hint.
   ⚠️ Values borrowed from `.fen-dm` and `.fen-run-name`; nobody has chosen this look at this size, and
   Dave reviews the interface himself. → memory `a-look-is-not-a-number` */
.fen-opening .fen-speaker[b-1nvogau30c] { color: #dcb763; }
.fen-opening .fen-text[b-1nvogau30c] { color: #f0e2bd; }

/* ⭐ The world's welcome (Dave, 2026-09-13: "we need some sort of welcome to the world message"): the first lines of
   a new session, in the opening line's gold. A companion speaking in his own voice reads as the story, not an aside.
   ⚠️ Values borrowed from `.fen-opening` and `.fen-dm`; nobody has chosen this look. → memory `a-look-is-not-a-number` */
.fen-welcome .fen-speaker[b-1nvogau30c] { color: #dcb763; }
.fen-welcome .fen-text[b-1nvogau30c] { color: #f0e2bd; }
.fen-npc .fen-speaker[b-1nvogau30c] { color: #9dc98a; }
.fen-npc .fen-text[b-1nvogau30c] { color: #e8edf7; }

.fen-system .fen-text[b-1nvogau30c] { color: #e0a3a3; }
.fen-thinking .fen-text[b-1nvogau30c] { color: #8e97a8; font-style: italic; }

/* ⭐ #1404 (Dave's ruling, 2026-09-23): the player presses Continue for the next round of a fight they cannot act in.
   ⚠️ Values borrowed wholesale from `.passed-travel-go`, the other inline control that sits inside a transcript line.
   Nobody has chosen this look at this size and Dave reviews the interface himself. → memory `a-look-is-not-a-number` */
.fen-continue-round[b-1nvogau30c] {
    font: inherit;
    font-style: normal;
    font-size: .82rem;
    margin-left: .5rem;
    padding: .1rem .55rem;
    border-radius: 4px;
    border: 1px solid rgba(232, 208, 138, .55);
    background: transparent;
    color: #e8d08a;
    cursor: pointer;
}

.fen-continue-round:hover:not(:disabled)[b-1nvogau30c] { background: rgba(232, 208, 138, .12); }
.fen-quiet[b-1nvogau30c] { color: #7d879a; font-size: .85rem; }

/* --- assisted-mode rail --------------------------------------------------------------------- */

.fen-rail[b-1nvogau30c] {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
    padding: .6rem 1rem;
    border-top: 1px solid #dcb7631a;
    background: transparent;
    flex: 0 0 auto;
}

.fen-rail button[b-1nvogau30c] {
    font: inherit;
    font-size: .8rem;
    padding: .35rem .8rem;
    border: 1px solid #ffffff22;
    border-radius: 6px;
    background: transparent;
    color: #d9deea;
    cursor: pointer;
}

.fen-rail button:disabled[b-1nvogau30c] { opacity: .45; cursor: default; }

/* --- composer ------------------------------------------------------------------------------- */

.fen-composer[b-1nvogau30c] {
    flex: 0 0 auto;
    padding: .8rem 1rem 1.1rem;
    border-top: 1px solid #dcb7631a;
    background: transparent;
}

.fen-composer form[b-1nvogau30c] { display: flex; gap: .5rem; }

.fen-composer input[b-1nvogau30c] {
    flex: 1 1 auto;
    font: inherit;
    font-size: .95rem;
    padding: .6rem .8rem;
    border-radius: 8px;
    border: 1px solid #ffffff22;
    background: #121826;
    color: #e8edf7;
}

.fen-composer input:focus[b-1nvogau30c] { outline: 2px solid #dcb763; outline-offset: 1px; }

.fen-composer button[b-1nvogau30c] {
    font: inherit;
    font-weight: 700;
    padding: .6rem 1.2rem;
    border-radius: 8px;
    border: 0;
    background: #dcb763;
    color: #0c111c;
    cursor: pointer;
}

.fen-composer button:disabled[b-1nvogau30c] { opacity: .4; cursor: default; }

.fen-failure[b-1nvogau30c] { margin: .5rem 0 0; color: #e0a3a3; font-size: .85rem; }

/* --- campaign picker ------------------------------------------------------------------------ */

.fen-pick[b-1nvogau30c] { padding: 1.5rem 1rem; }
.fen-pick h1[b-1nvogau30c] { font-size: 1.3rem; color: #f0e2bd; margin: 0 0 1rem; }
.fen-runs[b-1nvogau30c] { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .5rem; }

.fen-runs button[b-1nvogau30c] {
    width: 100%;
    text-align: left;
    font: inherit;
    padding: .8rem 1rem;
    border-radius: 8px;
    border: 1px solid #ffffff1c;
    background: #121826;
    color: #d9deea;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    gap: .2rem;
}

.fen-runs button:hover[b-1nvogau30c] { border-color: #dcb763; }
.fen-run-name[b-1nvogau30c] { font-weight: 700; color: #f0e2bd; }

/* ⭐ Dave, 2026-09-13: the hero in each campaign, and a way to leave it. The name sits under the campaign's own;
   Leave is a small, quiet control beneath the row — never full-width like the row itself, so a walk-away cannot be
   mistaken for "sit at this table".
   ⚠️ Values borrowed from `.fen-quiet`, `.fen-failure` and the row above; nobody has chosen this look.
   → memory `a-look-is-not-a-number` */
/* ⭐ Dave, 2026-09-13: "move the Leave button inside the campaign's card on the right hand side … add some statistics
   to that card." The CARD carries the border; inside it the open area takes the room and Leave sits on the right.
   ⚠️ Values borrowed from `.fen-runs button` above; nobody has chosen this look. → memory `a-look-is-not-a-number` */
/* ⭐ Dave, 2026-09-13: finished campaigns hidden by default behind "Show finished campaigns". Quiet, like a filter,
   not a call to action. ⚠️ Values borrowed from `.fen-quiet`; nobody has chosen this look. */
.fen-show-finished[b-1nvogau30c] {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    margin: 0 0 .6rem;
    color: #aeb6c6;
    font-size: .85rem;
    cursor: pointer;
}
.fen-show-finished input[b-1nvogau30c] { accent-color: #dcb763; }

.fen-run-card[b-1nvogau30c] {
    display: flex;
    align-items: stretch;
    gap: .75rem;
    border-radius: 8px;
    border: 1px solid #ffffff1c;
    background: #121826;
}
.fen-run-card:hover[b-1nvogau30c] { border-color: #dcb763; }
.fen-runs button.fen-run-open[b-1nvogau30c] {
    flex: 1;
    min-width: 0;
    border: none;
    background: transparent;
}
.fen-runs button.fen-run-open:hover[b-1nvogau30c] { border-color: transparent; }
.fen-run-heroes[b-1nvogau30c] { display: block; color: #aeb6c6; font-size: .85rem; }
.fen-run-stats[b-1nvogau30c] { display: block; color: #9dc98a; font-size: .8rem; }
.fen-run-side[b-1nvogau30c] {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    justify-content: center;
    gap: .35rem;
    padding: .6rem .9rem .6rem 0;
    max-width: 16rem;
    text-align: right;
}
.fen-runs button.fen-leave[b-1nvogau30c],
.fen-runs button.fen-keep[b-1nvogau30c] {
    width: auto;
    padding: .25rem .7rem;
    font-size: .8rem;
    border-radius: 6px;
    background: transparent;
}
.fen-runs button.fen-leave[b-1nvogau30c] { color: #e0a3a3; border: 1px solid #e0a3a355; }
.fen-runs button.fen-leave:hover[b-1nvogau30c] { border-color: #e0a3a3; }
.fen-runs button.fen-keep[b-1nvogau30c] { color: #aeb6c6; border: 1px solid #ffffff1c; }
.fen-leave-confirm[b-1nvogau30c] { margin: 0; color: #d9deea; font-size: .8rem; line-height: 1.4; }

/* --- setting out ---------------------------------------------------------------------------- */
/* ⛔ The door FenReach did not have: it could join a campaign and not start one, so the character
   wizard sent every finished hero out to the legacy head. Styled to match the picker above rather
   than invented — a seat and a campaign row are the same kind of thing to press.
   ⚠️ Values borrowed from `.fen-runs` and `.fen-composer` above; nothing here is a look anybody has
   chosen at this size, and Dave reviews the interface himself. → memory `a-look-is-not-a-number` */

.fen-start[b-1nvogau30c] { padding: 0 1rem 1.5rem; border-top: 1px solid #ffffff12; margin-top: 1.5rem; }
.fen-start h2[b-1nvogau30c] { font-size: 1.05rem; color: #f0e2bd; margin: 1.2rem 0 .5rem; }
.fen-party[b-1nvogau30c] { list-style: none; margin: .75rem 0; padding: 0; display: flex; flex-wrap: wrap; gap: .5rem; }

.fen-seat[b-1nvogau30c] {
    font: inherit;
    text-align: left;
    padding: .55rem .85rem;
    border-radius: 8px;
    border: 1px solid #ffffff1c;
    background: #121826;
    color: #d9deea;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    gap: .15rem;
}

.fen-seat:hover:not(:disabled)[b-1nvogau30c] { border-color: #dcb763; }
.fen-seat.chosen[b-1nvogau30c] { border-color: #dcb763; background: #1b2231; }
/* ⚠️ The dead and the away are shown, not hidden — marked and unpressable. A character somebody spent
   hours on must be mourned rather than silently vanish. → CharacterAvailability */
.fen-seat:disabled[b-1nvogau30c] { opacity: .45; cursor: not-allowed; }
.fen-seat-name[b-1nvogau30c] { font-weight: 700; color: #f0e2bd; }

.fen-start-row[b-1nvogau30c] { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }

.fen-start-row input[b-1nvogau30c] {
    flex: 1 1 14rem;
    min-width: 0;
    font: inherit;
    padding: .55rem .75rem;
    border-radius: 8px;
    border: 1px solid #ffffff1c;
    background: #0d1220;
    color: #d9deea;
}

.fen-go[b-1nvogau30c] {
    font: inherit;
    font-weight: 700;
    padding: .55rem 1.1rem;
    border-radius: 8px;
    border: 1px solid #dcb763;
    background: #dcb763;
    color: #1a1408;
    cursor: pointer;
}

.fen-go:disabled[b-1nvogau30c] { opacity: .5; cursor: not-allowed; }

/* --- the road on offer --------------------------------------------------------------------- */

/* ⚠️ A BAND, not a dialog. A journey is something the party is being told about while they talk, and a
   modal over the transcript would stop the conversation to ask a question the conversation just raised.
   It sits between the rail and the composer, where the answer is about to be typed anyway. */
.fen-offer[b-1nvogau30c] {
    width: min(var(--fen-column), calc(100% - 5rem));
    margin-inline: auto;
    box-sizing: border-box;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .75rem;
    flex: 0 0 auto;
    padding: .7rem 1rem;
    border: 1px solid #dcb76340;
    border-radius: 10px;
    background: #0d122099;
}

.fen-offer-where[b-1nvogau30c] { margin: 0; display: flex; flex-direction: column; gap: .15rem; flex: 1 1 14rem; }

.fen-offer-row[b-1nvogau30c] { display: flex; gap: .5rem; }

.fen-offer-row button:not(.fen-go)[b-1nvogau30c] {
    font: inherit;
    font-size: .8rem;
    padding: .55rem .9rem;
    border-radius: 8px;
    border: 1px solid #ffffff22;
    background: transparent;
    color: #d9deea;
    cursor: pointer;
}

.fen-offer-row button:disabled[b-1nvogau30c] { opacity: .5; cursor: default; }

/* ⭐ Dave, 2026-09-14: a route waiting on the party's answer is outlined in yellow and pulses, so it is not missed
   while reading the table. Held still for anyone whose system asks for less motion. */
.fen-offer-route[b-1nvogau30c] {
    border: 2px solid #f2c94c;
    animation: fen-offer-flash-b-1nvogau30c 1.1s ease-in-out infinite alternate;
}

@keyframes fen-offer-flash-b-1nvogau30c {
    from { border-color: #f2c94c; box-shadow: 0 0 .75rem #f2c94c66; }
    to { border-color: #f2c94c33; box-shadow: 0 0 0 transparent; }
}

@media (prefers-reduced-motion: reduce) {
    .fen-offer-route[b-1nvogau30c] { animation: none; }
}

/* --- the quest board ------------------------------------------------------------------------ */

/* ⭐ A hub, not a card: a list you scan and a detail you drop into. Sized for several jobs from the
   start, because Dave's brief is explicit that more than one is the ordinary case. */
.fen-quests[b-1nvogau30c] {
    width: min(34rem, 100%);
    max-height: min(40rem, calc(100vh - 3rem));
    overflow: auto;
    padding: 1.1rem 1.35rem 1.35rem;
    border: 1px solid #796241;
    border-radius: .75rem;
    background: #17130f;
    color: #eee8dc;
    box-shadow: 0 30px 90px #000c;
}

/* The change notes are longer reading than a quest list, so their popup is wider. */
.fen-quests.fen-whats-new[b-1nvogau30c] { width: min(46rem, 100%); }

.fen-quests header[b-1nvogau30c] {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-bottom: .8rem;
}

.fen-quests h2[b-1nvogau30c] {
    flex: 1 1 auto;
    margin: 0;
    color: #f2d38e;
    font: 600 1.3rem/1.2 Georgia, serif;
}

.fen-quest-list[b-1nvogau30c] { margin: 0; padding: 0; list-style: none; display: grid; gap: .4rem; }

.fen-quest-list button[b-1nvogau30c] {
    display: grid;
    grid-template-columns: 1.4rem 1fr;
    align-items: baseline;
    gap: .1rem .6rem;
    width: 100%;
    font: inherit;
    text-align: left;
    padding: .6rem .75rem;
    border-radius: 8px;
    border: 1px solid #ffffff1c;
    background: #0d1220;
    color: #d9deea;
    cursor: pointer;
}

.fen-quest-list button:hover[b-1nvogau30c] { border-color: #dcb763; }

.fen-quest-list .fen-quiet[b-1nvogau30c] { grid-column: 2; }

/* ⭐ WOW's own vocabulary, and the colours carry the meaning: gold for a job going, silver for one
   that is gated, and the ? for a reward waiting. ⚠️ The glyph is aria-hidden and the row still reads
   its name and giver — colour and a punctuation mark are not an accessible label on their own. */
.fen-quest-mark[b-1nvogau30c] {
    grid-row: span 2;
    font: 700 1.15rem/1 Georgia, serif;
    text-align: center;
}

/* ⛔⛔ THE MAP'S OWN TWO PENS, and this file used to hold three. `colorFor` in the map client answers
   ONE question — can the party act on this right now — so gold is available OR turn-in, and silver is
   under way OR locked. A hand-in was drawn green here, which was both a third colour in a two-colour
   vocabulary and a break of the other half of Dave's ruling: no red and no green, because those read as
   danger and safety and every quest mark is an invitation.
   ⚠️ Values taken from QUEST_MARKER_COLORS so the button, the row and the pin are one colour.
   → ClientApp/src/quest-markers.ts */
.fen-quest-mark.gold[b-1nvogau30c] { color: #ffcf40; }
.fen-quest-mark.silver[b-1nvogau30c] { color: #c9cfd8; }

/* --- the pulse ------------------------------------------------------------------------------- */

/* ⭐⭐ Dave, 2026-09-10: "a border around the button (a fairly large border - easily seen) should
   pulsate in the appropriate color."

   ⚠️ An OUTLINE rather than a border, deliberately: a border would resize the button as it appeared and
   nudge the rail sideways every time the party walked into range. An outline is drawn outside the box
   and moves nothing. `outline-offset` gives it air so it reads as a halo rather than a thick edge.

   ⛔ It animates the outline's COLOUR and a box-shadow, never a layout property — a pulse that animated
   width or padding would relayout the rail sixty times a second for as long as the party stood there. */
.fen-rail button.fen-pulse[b-1nvogau30c] {
    outline: 3px solid transparent;
    outline-offset: 3px;
    border-radius: 6px;
    animation: fen-pulse-b-1nvogau30c 1.6s ease-in-out infinite;
}

.fen-rail button.fen-pulse.gold[b-1nvogau30c] { --fen-pulse: #ffcf40; }

/* The sheet's Level up button (Dave, 2026-09-15): the rail's gold, steady, beside the sheet's name. */
.fen-level-up[b-1nvogau30c] {
    padding: .35rem .75rem;
    border: 1px solid #ffcf40;
    border-radius: 6px;
    background: #3a2f18;
    color: #ffcf40;
    font-weight: 700;
    cursor: pointer;
}

.fen-level-up:hover[b-1nvogau30c] { background: #4a3c1d; }

/* A level earned but held back by the pre-alpha cap (#1534): the Level up button's gold, quietened, since there is
   nothing to press. */
.fen-level-held[b-1nvogau30c] {
    margin: 0;
    padding: .3rem .6rem;
    border: 1px dashed color-mix(in srgb, #ffcf40 55%, transparent);
    border-radius: 6px;
    color: color-mix(in srgb, #ffcf40 80%, #c9cfd8);
    font-size: .9rem;
}
.fen-rail button.fen-pulse.silver[b-1nvogau30c] { --fen-pulse: #c9cfd8; }

@keyframes fen-pulse-b-1nvogau30c {
    0%, 100% {
        outline-color: color-mix(in srgb, var(--fen-pulse) 25%, transparent);
        box-shadow: 0 0 0 0 transparent;
    }
    50% {
        outline-color: var(--fen-pulse);
        box-shadow: 0 0 14px 2px color-mix(in srgb, var(--fen-pulse) 45%, transparent);
    }
}

/* ⛔ Somebody who has asked their system not to animate gets a STEADY ring rather than nothing at all.
   The ring is the information; the pulsing is only how it asks to be looked at. */
@media (prefers-reduced-motion: reduce) {
    .fen-rail button.fen-pulse[b-1nvogau30c] {
        animation: none;
        outline-color: var(--fen-pulse);
    }
}

.fen-quest-name[b-1nvogau30c] { font-weight: 700; color: #eee8dc; }

.fen-quest-who[b-1nvogau30c] { margin: 0 0 .5rem; color: #d7aa55; font: 700 .7rem/1.2 ui-monospace, monospace; letter-spacing: .12em; text-transform: uppercase; }

.fen-quest-summary[b-1nvogau30c] { margin: 0 0 .8rem; line-height: 1.5; }

.fen-quest-objectives[b-1nvogau30c] { margin: 0 0 .9rem; padding-left: 1.1rem; display: grid; gap: .25rem; color: #c8c0b0; }

/* ⚠️ #1106. The quest's own card strikes what is done exactly as "Under way" does, so a card with Hand it in
   on it does not show the objective the engine has met as still to do. */
.fen-quest-objectives li.met[b-1nvogau30c] { color: #8b8577; text-decoration: line-through; }

.fen-quest-objectives .fen-quiet[b-1nvogau30c] { margin-left: .4rem; }

.fen-quest-locked[b-1nvogau30c] {
    margin: 0 0 .9rem;
    padding: .55rem .7rem;
    border-left: 3px solid #9aa3b2;
    background: #ffffff08;
    color: #c8c0b0;
}

/* ⭐ #998. What the job pays, on the card that is about to hand it in. Deliberately quieter than the
   button beside it and louder than the summary above it: it is the last thing read before pressing.
   ⚠️ Values borrowed from `.fen-quest-ready` and `.fen-quest-locked`; nobody has chosen this look.
   → memory `a-look-is-not-a-number` */
.fen-quest-pays[b-1nvogau30c] {
    margin: 0 0 .9rem;
    color: #ffcf40;
    font-weight: 700;
}

.fen-quest-actions[b-1nvogau30c] { display: flex; gap: .5rem; flex-wrap: wrap; }

.fen-quest-actions button:not(.fen-go)[b-1nvogau30c] {
    font: inherit;
    font-size: .8rem;
    padding: .55rem .9rem;
    border-radius: 8px;
    border: 1px solid #ffffff22;
    background: transparent;
    color: #d9deea;
    cursor: pointer;
}

.fen-quest-actions button:disabled[b-1nvogau30c] { opacity: .5; cursor: default; }

.fen-quest-said[b-1nvogau30c] { margin: .9rem 0 0; color: #f2d38e; }

/* --- the log half ---------------------------------------------------------------------------- */

.fen-quest-head[b-1nvogau30c] {
    margin: .2rem 0 .5rem;
    color: #d7aa55;
    font: 700 .68rem/1.2 ui-monospace, monospace;
    letter-spacing: .13em;
    text-transform: uppercase;
}

.fen-quest-head + .fen-quest-list + .fen-quest-head[b-1nvogau30c] { margin-top: 1.1rem; }

.fen-quest-log[b-1nvogau30c] { margin: 0; padding: 0; list-style: none; display: grid; gap: .7rem; }

.fen-quest-log > li > p[b-1nvogau30c] { margin: 0 0 .2rem; }

.fen-quest-log ul[b-1nvogau30c] { margin: 0; padding-left: 1.1rem; display: grid; gap: .2rem; color: #c8c0b0; }

/* ⚠️ Struck through AND dimmed. Colour alone would leave a met objective indistinguishable for a reader
   who cannot see the difference, and this list is mostly what a player checks progress against. */
.fen-quest-log li.met[b-1nvogau30c] { color: #8b8577; text-decoration: line-through; }

.fen-quest-log .fen-quiet[b-1nvogau30c] { margin-left: .4rem; }

/* ⭐ #1255. A step still to do reads as the step, and is a button that puts it in the box. */
.fen-quest-step[b-1nvogau30c] {
    all: unset;
    cursor: pointer;
    color: inherit;
    text-decoration: underline dotted;
    text-underline-offset: .2em;
}

.fen-quest-step:hover[b-1nvogau30c],
.fen-quest-step:focus-visible[b-1nvogau30c] { color: #e8d9a8; text-decoration-style: solid; }

/* ⭐ #998. Who to take a finished quest back to, under its name in the log. Quiet, because it is a
   direction rather than a call to action — the call to action is the chip above it. */
.fen-quest-recipient[b-1nvogau30c] { margin: .1rem 0 .25rem; }

/* ⭐⭐ #1511. Travel here — the road to where a carried quest points, priced without asking the DM.
   ⚠️ Deliberately QUIETER than `.fen-go`: it is an offer to price a journey, not the act of setting out,
   and the gold call-to-action on this panel belongs to Accept and Hand it in. Borrowed wholesale from
   `.fen-quest-actions button:not(.fen-go)` so the two secondary controls in this window match rather than
   being two people's idea of a button. → memory `a-look-is-not-a-number` */
.fen-quest-travel[b-1nvogau30c] {
    margin: .15rem 0 .45rem;
    font: inherit;
    font-size: .75rem;
    padding: .35rem .75rem;
    border-radius: 8px;
    border: 1px solid #ffffff22;
    background: transparent;
    color: #d9deea;
    cursor: pointer;
}

.fen-quest-travel:hover:not(:disabled)[b-1nvogau30c],
.fen-quest-travel:focus-visible[b-1nvogau30c] { border-color: #ffcf4066; color: #f2d38e; }

.fen-quest-travel:disabled[b-1nvogau30c] { opacity: .5; cursor: default; }

.fen-quest-ready[b-1nvogau30c] {
    margin-left: .5rem;
    padding: .05rem .4rem;
    border-radius: 4px;
    background: #ffcf4026;
    color: #ffcf40;
    font: 700 .65rem/1.4 ui-monospace, monospace;
    letter-spacing: .1em;
    text-transform: uppercase;
}

/* --- the choosers -------------------------------------------------------------------------- */

/* ⭐ Two of them — whose sheet, and which shop — and they share every rule because they are the same
   gesture: the rail knows there is more than one answer and refuses to guess.

   ⛔ `--dm-layer-dialog`, the SAME token ActorSheet and MerchantShop draw their own backdrops at, so a
   chooser and the screen it opens are on one layer and cannot half-cover each other. A z-index invented
   here would eventually lose to one of them. → memory `a-mark-drawn-under-what-it-belongs-inside` */
.fen-choose-backdrop[b-1nvogau30c] {
    position: fixed;
    inset: 0;
    z-index: var(--dm-layer-dialog, 1000);
    display: grid;
    place-items: center;
    padding: 1.5rem;
    background: #050403bf;
    backdrop-filter: blur(5px);
}

.fen-choose[b-1nvogau30c] {
    width: min(26rem, 100%);
    padding: 1.2rem 1.35rem 1.35rem;
    border: 1px solid #796241;
    border-radius: .75rem;
    background: #17130f;
    color: #eee8dc;
    box-shadow: 0 30px 90px #000c;
}

.fen-choose h2[b-1nvogau30c] {
    margin: 0 0 .9rem;
    color: #f2d38e;
    font: 600 1.15rem/1.2 Georgia, serif;
}

.fen-choose ul[b-1nvogau30c] { margin: 0; padding: 0; list-style: none; display: grid; gap: .4rem; }

.fen-choose button[b-1nvogau30c] {
    display: flex;
    flex-direction: column;
    gap: .15rem;
    width: 100%;
    font: inherit;
    text-align: left;
    padding: .55rem .75rem;
    border-radius: 8px;
    border: 1px solid #ffffff1c;
    background: #0d1220;
    color: #d9deea;
    cursor: pointer;
}

.fen-choose button:hover[b-1nvogau30c] { border-color: #dcb763; }

.fen-shop-name[b-1nvogau30c] { font-weight: 700; }



/* --- what the four dialogs add to the shared card (#1436–#1439) -----------------------------------

   ⛔⛔ THESE RULES LIVE HERE AND NOT IN EncounterCard.razor.css, AND THAT IS NOT A FILING PREFERENCE.
   The buttons and paragraphs below are authored in THIS page's <Note> fragments, so the elements carry
   FenReach's scope attribute; a rule written in the card's own scoped sheet would compile against the
   card's attribute and match nothing at all. Play.razor.css carries the identical pair for the identical
   reason, which is why there are two copies of three short rules rather than one shared one.
   → memory `scoped-css-cannot-reach-a-built-element`

   ⚠️ UNTUNED. Nothing here was measured against a screenshot; it is the plainest thing that reads as a
   secondary control. It wants Dave's eye. → memory `a-look-is-not-a-number` */

/* ⚠️ Deliberately NOT the card's primary action. "Carry On", "Go In" and "Play on" each end their card
   and must stay the loudest thing on it; a second full-width button beside one would make the player
   read twice to find the way out of a screen they see after every fight. */
.encounter-search[b-1nvogau30c] {
    margin: 0;
    padding: .3rem .85rem;
    border: 1px solid #6b5a3f;
    border-radius: 6px;
    background: #1d1812;
    color: #c9b48e;
    font: inherit;
    font-size: .82rem;
    cursor: pointer;
}

.encounter-search:hover[b-1nvogau30c],
.encounter-search:focus-visible[b-1nvogau30c] {
    border-color: #dcb763;
    color: #f3e7cf;
}

.encounter-search:disabled[b-1nvogau30c] {
    opacity: .5;
    cursor: default;
}

/* The honest sentence when a fight left nothing to find (#1437). */
.encounter-loot-empty[b-1nvogau30c] {
    margin: 0;
    font-style: italic;
}

/* The tombstone's second paragraph — "this campaign has ended" — which is quieter than the first. */
.fen-death-say[b-1nvogau30c] {
    margin: 0 0 .9rem;
    font-size: .9rem;
    line-height: 1.6;
    color: #c6ccda;
}
/* /Components/Pages/ForgeAccountDetail.razor.rz.scp.css */
.account-header h1[b-0n2xzta036] { margin: 0.2rem 0; overflow-wrap: anywhere; }
.eyebrow[b-0n2xzta036] { letter-spacing: 0.08em; font-size: 0.75rem; opacity: 0.7; margin: 0; }
.ident[b-0n2xzta036] { font-family: monospace; font-size: 0.8rem; opacity: 0.7; margin: 0; }

.status[b-0n2xzta036] { padding: 0.5rem 0.75rem; margin: 0.75rem 0; border-left: 3px solid currentColor; }
.status.error[b-0n2xzta036] { color: #b03030; }
.status.ok[b-0n2xzta036] { color: #2d6a3e; }

.tabs[b-0n2xzta036] { display: flex; gap: 0.25rem; margin: 1rem 0 0; border-bottom: 1px solid rgba(128,128,128,0.35); }
.tab[b-0n2xzta036] { border: none; background: none; padding: 0.4rem 0.9rem; cursor: pointer; border-bottom: 2px solid transparent; }
.tab.active[b-0n2xzta036] { border-bottom-color: currentColor; font-weight: 600; }

.panel[b-0n2xzta036] { margin: 1.25rem 0; }
.panel h2[b-0n2xzta036] { font-size: 1rem; margin: 0 0 0.5rem; }
.note[b-0n2xzta036] { font-size: 0.8rem; opacity: 0.75; max-width: 72ch; }

dl.facts[b-0n2xzta036] { display: grid; grid-template-columns: max-content 1fr; gap: 0.25rem 1rem; margin: 0 0 0.5rem; font-size: 0.9rem; }
dl.facts dt[b-0n2xzta036] { opacity: 0.7; }
dl.facts dd[b-0n2xzta036] { margin: 0; }

table.matrix[b-0n2xzta036], table.rows[b-0n2xzta036] { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
table.matrix th[b-0n2xzta036], table.matrix td[b-0n2xzta036], table.rows th[b-0n2xzta036], table.rows td[b-0n2xzta036] { text-align: left; padding: 0.35rem 0.6rem; border-bottom: 1px solid rgba(128,128,128,0.25); vertical-align: top; }
table.matrix td.yes[b-0n2xzta036] { color: #2d6a3e; }
table.matrix td.no[b-0n2xzta036] { color: #b03030; }

.actions[b-0n2xzta036] { margin-top: 0.75rem; display: flex; flex-direction: column; gap: 0.5rem; max-width: 40rem; }
.actions label[b-0n2xzta036] { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.85rem; }
.actions .buttons[b-0n2xzta036] { display: flex; gap: 0.5rem; }
.actions .danger[b-0n2xzta036] { color: #b03030; }

.balance[b-0n2xzta036] { list-style: none; padding: 0; margin: 0 0 0.5rem; display: flex; gap: 1.5rem; }

.grant[b-0n2xzta036] { display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; align-items: flex-end; max-width: 52rem; }
.grant label[b-0n2xzta036] { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.85rem; }
.grant label.wide[b-0n2xzta036] { flex: 1 1 18rem; }
.grant label.wide input[b-0n2xzta036] { width: 100%; }

.flag[b-0n2xzta036] { display: inline-block; padding: 0 0.4rem; margin-left: 0.25rem; border-radius: 0.2rem; font-size: 0.75rem; border: 1px solid currentColor; opacity: 0.7; }
/* /Components/Pages/ForgeAccounts.razor.rz.scp.css */
.accounts-header[b-s1fo6v8xzu] { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; }
.accounts-header p[b-s1fo6v8xzu] { max-width: 60ch; }
.eyebrow[b-s1fo6v8xzu] { letter-spacing: 0.08em; font-size: 0.75rem; opacity: 0.7; margin: 0; }
.tool-link[b-s1fo6v8xzu] { white-space: nowrap; }

.filters[b-s1fo6v8xzu] { display: flex; flex-wrap: wrap; gap: 0.75rem 1rem; align-items: flex-end; margin: 1rem 0; }
.filters label[b-s1fo6v8xzu] { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.85rem; }
.filters label.check[b-s1fo6v8xzu] { flex-direction: row; align-items: center; gap: 0.35rem; }
.filters .search[b-s1fo6v8xzu] { flex: 1 1 22rem; }
.filters .search input[b-s1fo6v8xzu] { width: 100%; }

.status.error[b-s1fo6v8xzu] { border-left: 3px solid #b03030; padding: 0.5rem 0.75rem; margin: 0.5rem 0; }
.count[b-s1fo6v8xzu] { font-size: 0.85rem; opacity: 0.85; }

table.accounts[b-s1fo6v8xzu] { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
table.accounts th[b-s1fo6v8xzu], table.accounts td[b-s1fo6v8xzu] { text-align: left; padding: 0.4rem 0.6rem; border-bottom: 1px solid rgba(128,128,128,0.25); vertical-align: top; }
table.accounts tr.row:hover[b-s1fo6v8xzu] { background: rgba(128,128,128,0.08); }
.display-name[b-s1fo6v8xzu] { display: block; font-size: 0.8rem; opacity: 0.7; }

.flag[b-s1fo6v8xzu] { display: inline-block; padding: 0 0.4rem; margin-right: 0.25rem; border-radius: 0.2rem; font-size: 0.75rem; border: 1px solid currentColor; }
.flag.banned[b-s1fo6v8xzu] { color: #b03030; }
.flag.closed[b-s1fo6v8xzu] { color: #8a6d1f; }
.flag.locked[b-s1fo6v8xzu] { color: #8a6d1f; }
.flag.unconfirmed[b-s1fo6v8xzu] { opacity: 0.7; }
.flag.ok[b-s1fo6v8xzu] { opacity: 0.55; }

.paging[b-s1fo6v8xzu] { display: flex; gap: 0.5rem; margin: 1rem 0; }
/* /Components/Pages/ForgeAdmin.razor.rz.scp.css */
.admin-header[b-qwmzro06je]{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem}.admin-header[b-qwmzro06je]{margin-bottom:1rem}.admin-header h1[b-qwmzro06je]{margin:.1rem 0}.admin-status[b-qwmzro06je]{padding:.7rem 1rem;border-left:3px solid var(--forge-success-border);background:var(--forge-success-surface)}.admin-status.error[b-qwmzro06je]{border-color:var(--forge-danger-border-alt);background:var(--forge-danger-surface)}.admin-card[b-qwmzro06je]{border:1px solid var(--forge-border-card);background:var(--forge-surface-5);padding:1rem;border-radius:.35rem;min-width:0}.admin-card h2[b-qwmzro06je]{margin:.1rem 0}.admin-summary-grid[b-qwmzro06je],.admin-tools-grid[b-qwmzro06je]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem;margin-bottom:1rem}.count[b-qwmzro06je]{border:1px solid var(--forge-border-panel);border-radius:999px;padding:.25rem .55rem}.cost-row[b-qwmzro06je],.campaign-row[b-qwmzro06je]{display:grid;gap:.2rem;border-top:1px solid #332f28;padding:.65rem 0}.cost-row span[b-qwmzro06je],.campaign-row span[b-qwmzro06je]{font-size:.83rem;color:#aaa}.cost-row em[b-qwmzro06je]{justify-self:start;padding:.15rem .4rem;border-radius:.25rem}.cost-row .closed[b-qwmzro06je]{color:#8fd0a0;background:var(--forge-success-badge)}.cost-row .open[b-qwmzro06je]{color:#f1a293;background:#351916}.pack-browser[b-qwmzro06je]{margin-bottom:1rem}table[b-qwmzro06je]{width:100%;border-collapse:collapse;font-size:.88rem}th[b-qwmzro06je],td[b-qwmzro06je]{text-align:left;vertical-align:top;border-bottom:1px solid #353027;padding:.55rem}td small[b-qwmzro06je],td em[b-qwmzro06je]{display:block}.source[b-qwmzro06je]{max-width:18rem;word-break:break-all;color:#aaa}.origin[b-qwmzro06je],.draft[b-qwmzro06je]{display:inline-block;padding:.15rem .35rem;border-radius:.2rem;margin-right:.25rem}.origin.file[b-qwmzro06je]{background:#26364a}.origin.store[b-qwmzro06je]{background:#3c2d4e}.draft[b-qwmzro06je]{background:#59451f}.invalid[b-qwmzro06je]{color:#ef9d8e}.admin-tools-grid .admin-card[b-qwmzro06je]{display:grid;gap:.7rem;align-content:start}.admin-card label[b-qwmzro06je]{display:grid;gap:.25rem}.bundle-input[b-qwmzro06je]{min-height:12rem;font-family:ui-monospace,monospace;font-size:.8rem}.inline-fields[b-qwmzro06je]{display:grid;grid-template-columns:2fr 1fr;gap:.6rem}.validation-result[b-qwmzro06je],.diff-result[b-qwmzro06je]{border-left:3px solid;padding:.65rem}.validation-result.valid[b-qwmzro06je],.diff-result.valid[b-qwmzro06je]{border-color:var(--forge-success-border);background:var(--forge-success-surface)}.validation-result.invalid[b-qwmzro06je],.diff-result.invalid[b-qwmzro06je]{border-color:var(--forge-danger-border-alt);background:var(--forge-danger-surface)}.validation-result p[b-qwmzro06je]{display:grid;grid-template-columns:minmax(4rem,auto) auto 1fr;gap:.5rem}.diff-result article[b-qwmzro06je]{display:grid;gap:.25rem;border-top:1px solid #4a302d;padding:.5rem 0}.diff-result del[b-qwmzro06je],.diff-result ins[b-qwmzro06je]{font-family:ui-monospace,monospace;font-size:.75rem;word-break:break-all}.diff-result ins[b-qwmzro06je]{color:#9bd3a7}.seed-results[b-qwmzro06je]{list-style:none;padding:0}.seed-results li[b-qwmzro06je]{display:grid;grid-template-columns:1fr auto auto;gap:.5rem;border-top:1px solid #332f28;padding:.45rem 0}@media(max-width:900px){.admin-summary-grid[b-qwmzro06je],.admin-tools-grid[b-qwmzro06je]{grid-template-columns:1fr}.admin-header[b-qwmzro06je]{flex-direction:column}.validation-result p[b-qwmzro06je]{grid-template-columns:1fr}}

/* Pack versions housekeeping (issue #58). */
.pack-versions .num[b-qwmzro06je] { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.prune-refusal[b-qwmzro06je] { margin: .75rem 0 0; padding: .75rem 1rem .75rem 2rem; border: 1px solid #673840; border-radius: .5rem; background: #241a1d; color: #ffd7db; font-size: .85rem; }
.prune-refusal li + li[b-qwmzro06je] { margin-top: .3rem; }

/* Bulk cleanup, dry run first (issue #404). */
.prune-plan[b-qwmzro06je] { margin: .75rem 0; display: grid; gap: .6rem; justify-items: start; }
.prune-plan-report[b-qwmzro06je] { width: 100%; border: 1px solid var(--forge-border-panel); border-radius: .5rem; padding: .75rem 1rem; background: var(--forge-surface-5); font-size: .85rem; display: grid; gap: .6rem; }
.prune-plan-report p[b-qwmzro06je] { margin: 0; }
.prune-media-warning[b-qwmzro06je] { color: #ffd9a8; }
.prune-candidates[b-qwmzro06je] { list-style: none; margin: 0; padding: 0; max-height: 16rem; overflow-y: auto; }
.prune-candidates li[b-qwmzro06je] { display: grid; grid-template-columns: 1fr auto; gap: .5rem; border-top: 1px solid #332f28; padding: .4rem 0; }
.prune-candidates em[b-qwmzro06je] { grid-column: 1 / -1; color: #aaa; font-size: .78rem; word-break: break-all; }

/* The generated image vault (#560). Every class below is one this page renders; the shared admin-card,
   section-heading, count and admin-actions rules come from the Forge sheet and are not redeclared. */
.vault-figures[b-qwmzro06je] {
    display: flex;
    flex-wrap: wrap;
    gap: 1.6rem;
    margin: 0 0 .6rem;
}

.vault-figures div[b-qwmzro06je] {
    display: flex;
    flex-direction: column;
    gap: .15rem;
}

.vault-figures dt[b-qwmzro06je] {
    color: var(--forge-text-faint);
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .04em;
}

.vault-figures dd[b-qwmzro06je] {
    margin: 0;
    color: var(--forge-gold);
    font-size: 1.35rem;
}

/* The path is the actionable part of this card -- it is what an operator backs up -- so it is stated
   plainly rather than tucked into a tooltip. */
.vault-root[b-qwmzro06je] {
    margin: 0 0 .8rem;
    color: var(--forge-text-dim);
    font-size: .85rem;
}

.vault-message[b-qwmzro06je] {
    color: var(--forge-success);
    font-size: .85rem;
    align-self: center;
}

/* Unprotected art is the one figure on this card that is bad news, so it does not read as gold. */
.vault-figures dd.vault-warn[b-qwmzro06je] {
    color: var(--forge-danger);
}

/* ── The unstick console (Dave, 2026-09-10) ─────────────────────────────────────────────────────
   ⛔⛔ These four names were rendered with NO rule anywhere the page can reach, so the console
   built to rescue a stuck quest drew as unstyled markup: an undifferentiated destructive button,
   a secondary button that looked primary, and the repair block running together with the campaign
   row above it. Caught by ForgeClassCoverageTests (#122) on the full suite, not by looking.
   → memory `scoped-css-cannot-reach-a-built-element` */

/* The way IN to the console. Secondary on purpose — repairing a campaign by hand is the rare path,
   and it must not read like the primary thing to do on this row. */
.ghost[b-qwmzro06je] {
    background: transparent;
    border: 1px solid var(--forge-border-nested);
    color: var(--forge-text-dim);
    border-radius: 6px;
    padding: .3rem .7rem;
    font-size: .82rem;
    cursor: pointer;
}

.ghost:hover:not(:disabled)[b-qwmzro06je] {
    border-color: var(--forge-gold);
    color: var(--forge-gold);
}

.ghost:disabled[b-qwmzro06je] {
    opacity: .5;
    cursor: default;
}

/* One quest's repair block, boxed so several of them do not run together into one wall. */
.quest-repair[b-qwmzro06je] {
    display: flex;
    flex-direction: column;
    gap: .4rem;
    margin: .5rem 0;
    padding: .6rem .7rem;
    border: 1px solid var(--forge-border-nested);
    border-radius: 8px;
    background: rgba(0, 0, 0, .18);
}

/* An objective and its checkbox on one line, so the box belongs to the text beside it. */
.quest-objective[b-qwmzro06je] {
    display: flex;
    align-items: center;
    gap: .45rem;
    font-size: .85rem;
    color: var(--forge-text-dim);
}

.quest-objective em[b-qwmzro06je] {
    color: var(--forge-text-faint);
    font-size: .78rem;
}

/* ⚠️ This one writes to a live campaign's history. It should look like it does — the whole point of
   the reason box beside it is that somebody traces this back in six months. */
.danger[b-qwmzro06je] {
    align-self: flex-start;
    background: rgba(122, 30, 24, .35);
    border: 1px solid var(--forge-danger);
    color: var(--forge-danger);
    border-radius: 6px;
    padding: .35rem .8rem;
    font-weight: 600;
    cursor: pointer;
}

.danger:hover:not(:disabled)[b-qwmzro06je] {
    background: rgba(122, 30, 24, .55);
}

.danger:disabled[b-qwmzro06je] {
    opacity: .45;
    cursor: default;
}
/* /Components/Pages/ForgeAdventures.razor.rz.scp.css */
/* The studio wears the world workspace's clothes on purpose: same shell, same cards, same colours, so
   moving between building a world and writing a story does not feel like changing application.

   #100 moved the rules this page stated IDENTICALLY to ForgeWorlds into wwwroot/forge.css — the
   workspace grid, the section nav, the loading panel, the card headings. What is left here is what
   genuinely differs: this page rounds its corners, sets box-sizing and font:inherit, and runs its
   controls full width. Unifying those means deciding whether the world editor adopts the studio's
   look, which is a design call and still open on #100. */

.workspace-loading h1[b-j0l75kkbhu] { color: var(--forge-gold-bright); }
.eyebrow[b-j0l75kkbhu] { margin: 0; color: var(--forge-text-eyebrow); font-size: .68rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; }

.world-context-header[b-j0l75kkbhu] { position: sticky; top: 0; z-index: 8; display: grid; grid-template-columns: minmax(16rem, 1fr) auto; align-items: center; gap: 1rem; margin: -1rem -1rem 1rem; padding: .9rem 1rem; border: 1px solid #5a4932; border-radius: .4rem; background: #15120ff2; box-shadow: 0 10px 30px #08060499; backdrop-filter: blur(8px); }
.world-context-title[b-j0l75kkbhu] { display: grid; gap: .1rem; }
.world-context-title a[b-j0l75kkbhu] { color: #d8c49f; font-size: .78rem; text-decoration: none; }
.world-context-title h1[b-j0l75kkbhu] { margin: .1rem 0; color: var(--forge-gold-bright); font-size: 1.5rem; }
.world-context-title p[b-j0l75kkbhu] { margin: 0; color: var(--forge-text-dim); font-size: .8rem; }
.world-save-state[b-j0l75kkbhu] { display: grid; justify-items: end; gap: .25rem; min-width: 9rem; }
.world-save-state .validation.valid[b-j0l75kkbhu] { color: var(--forge-success); }
.world-save-state .validation.invalid[b-j0l75kkbhu] { color: var(--forge-danger-alt); }
.world-save-state button[b-j0l75kkbhu] { width: fit-content; color: var(--forge-gold-ink); background: var(--forge-gold); border: 1px solid var(--forge-gold); padding: .4rem .7rem; font-weight: 700; cursor: pointer; border-radius: .25rem; }
.world-save-state button:disabled[b-j0l75kkbhu] { color: #b7a98f; background: transparent; border-color: #5d5142; cursor: default; }

.library-status[b-j0l75kkbhu] { padding: .75rem 1rem; margin: 0 0 1rem; border-left: 3px solid var(--forge-success-border); background: var(--forge-success-surface); color: var(--forge-text-label); }
.library-status.error[b-j0l75kkbhu] { border-color: var(--forge-danger-border-alt); background: var(--forge-danger-surface); }

.world-section-content[b-j0l75kkbhu] { min-width: 0; display: grid; gap: 1rem; }

.forge-editor-card[b-j0l75kkbhu] { display: grid; gap: 1rem; margin: 0; padding: 1.1rem; border: 1px solid var(--forge-border-editor); border-radius: .4rem; background: var(--forge-surface-6); }
.forge-editor-card h3[b-j0l75kkbhu] { font-size: 1rem; margin-top: .3rem; }
.forge-editor-card p[b-j0l75kkbhu] { margin: .15rem 0; color: var(--forge-text-muted); }
.section-heading button[b-j0l75kkbhu] { white-space: nowrap; }
.forge-editor-card label[b-j0l75kkbhu] { display: grid; gap: .3rem; color: var(--forge-text-label); font-size: .85rem; }
.forge-editor-card input[b-j0l75kkbhu], .forge-editor-card select[b-j0l75kkbhu], .forge-editor-card textarea[b-j0l75kkbhu] { min-width: 0; box-sizing: border-box; width: 100%; color: var(--forge-text-input); background: var(--forge-surface-input); border: 1px solid var(--forge-border-input); padding: .5rem; border-radius: .25rem; font: inherit; }
.forge-editor-card textarea[b-j0l75kkbhu] { resize: vertical; line-height: 1.45; }
.forge-editor-card button[b-j0l75kkbhu] { width: fit-content; color: var(--forge-text); background: var(--forge-surface-button); border: 1px solid var(--forge-border-quiet); padding: .5rem .75rem; cursor: pointer; border-radius: .25rem; }
.forge-editor-card button:hover[b-j0l75kkbhu] { background: #3b3225; }
.forge-editor-card button.quiet[b-j0l75kkbhu] { color: #d8c49f; background: transparent; border-color: var(--forge-border-quiet); }
.forge-editor-card button.danger[b-j0l75kkbhu] { color: var(--forge-danger); border-color: var(--forge-danger-border); background: transparent; }
.forge-editor-card button.danger:hover[b-j0l75kkbhu] { background: #3a2019; }

/* Field rows wrap instead of overflowing: a scene detail panel has a lot of small fields and a
   fixed column count turns into a horizontal scrollbar the moment the window narrows. */
.editor-grid[b-j0l75kkbhu] { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); align-items: end; gap: .7rem; }
.editor-grid button.danger[b-j0l75kkbhu] { align-self: end; }
/* #568. `align-items: end` is right for a row of bare fields — the inputs sit on one line however far
   the captions above them wrap. It is wrong the moment the cells carry descriptions of DIFFERENT
   lengths, because then it bottom-aligns three paragraphs and scatters the three controls up the row.
   A MODIFIER rather than a change to the rule above: .editor-grid dresses nearly every section of this
   4,400-line page, and re-laying all of them out to straighten one row is not a trade worth making. */
.editor-grid.top[b-j0l75kkbhu] { align-items: start; }
.editor-nested-card[b-j0l75kkbhu] { display: grid; gap: .8rem; padding: .9rem; border: 1px solid var(--forge-border-nested); border-radius: .3rem; background: var(--forge-surface-1); }
label.stacked[b-j0l75kkbhu] { display: grid; gap: .3rem; }
label.checkbox[b-j0l75kkbhu] { display: flex; align-items: center; gap: .45rem; }
/* ⛔ #567, and fixed at the ROOT rather than per-list. The rule above runs every control in the card
   full width, which a text box wants and a checkbox emphatically does not: a 100%-wide checkbox in a
   flex row eats the line and shoves its own label to the far edge of the screen, which is what made
   "Provoked by" unreadable. Every checkbox and radio on the page opts out at once — .checkbox,
   .checkbox-row, .den-provocation-list and .entry-outcome-kinds were all wearing it.
   ⚠️ Deliberately NOT prefixed with .forge-editor-card. Scoped CSS reaches this page's own elements
   wherever they are rendered, and the wandering entry editor now lives inside a ForgeDialog (#570) —
   outside any card, where a prefixed opt-out would stop applying and app.css's bare `input { width: 70% }`
   would put the same fault back. It cannot leak into child components: scoped CSS never crosses a
   component boundary, so CreaturePicker and ConditionBuilder keep dressing their own controls. */
input[type="checkbox"][b-j0l75kkbhu],
input[type="radio"][b-j0l75kkbhu] { flex: 0 0 auto; width: auto; }
.level-band[b-j0l75kkbhu] { display: grid; grid-template-columns: 1fr 1fr; gap: .4rem; }
.effect-kind[b-j0l75kkbhu] { align-self: center; color: #9fc0bd; font-size: .8rem; }
.muted[b-j0l75kkbhu] { color: var(--forge-text-faint); font-size: .85rem; }
.library-actions[b-j0l75kkbhu] { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; }
.outline-name[b-j0l75kkbhu] { font-weight: 600; }

/* #564. The scene picker and its Add button on one line, with the whole editor directly beneath. The
   button aligns to the BOTTOM of the cell so it sits on the select rather than on the caption above it. */
.outline-picker[b-j0l75kkbhu] { display: grid; grid-template-columns: minmax(12rem, 1fr) auto; align-items: end; gap: .6rem; }
.outline-picked[b-j0l75kkbhu] { display: flex; flex-wrap: wrap; align-items: baseline; gap: .15rem .8rem; margin: 0; }
.outline-picked code[b-j0l75kkbhu] { color: var(--forge-gold); font-size: .8rem; }

/* #565. The button and the sentence that says what it costs, on one line. */
.scene-picture-launch[b-j0l75kkbhu] { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .8rem; }

details > summary[b-j0l75kkbhu] { cursor: pointer; color: var(--forge-text-label); padding: .3rem 0; font-size: .85rem; }

/* Pick a row, edit it in the card below. Everything on a list is a summary, never a form: a
   hundred-scene adventure has to show a hundred rows, not a hundred open editors. */
.picker-grid[b-j0l75kkbhu] { width: 100%; border-collapse: collapse; font-size: .88rem; }
.picker-grid thead th[b-j0l75kkbhu] { position: sticky; top: 0; padding: .4rem .6rem; text-align: left; color: var(--forge-text-eyebrow); font-size: .68rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; background: var(--forge-surface-6); border-bottom: 1px solid var(--forge-border-nav); }
.picker-grid tbody tr[b-j0l75kkbhu] { cursor: pointer; border-bottom: 1px solid #2a241c; }
.picker-grid tbody tr:hover[b-j0l75kkbhu] { background: var(--forge-surface-hover); }
.picker-grid tbody tr.selected[b-j0l75kkbhu] { background: var(--forge-surface-selected); box-shadow: inset 3px 0 0 var(--forge-gold); }
.picker-grid td[b-j0l75kkbhu] { padding: .45rem .6rem; color: var(--forge-text-label); vertical-align: top; }
.picker-grid td strong[b-j0l75kkbhu] { color: #f0e6d4; font-weight: 600; }
.picker-grid td small[b-j0l75kkbhu] { display: block; color: var(--forge-text-faint); font: .72rem/1.3 ui-monospace, monospace; }
.picker-grid td.ordinal[b-j0l75kkbhu], .picker-grid th.ordinal[b-j0l75kkbhu] { width: 2.5rem; color: var(--forge-text-faint); }
.picker-grid td.numeric[b-j0l75kkbhu], .picker-grid th.numeric[b-j0l75kkbhu] { text-align: right; white-space: nowrap; }
.picker-grid td.marks[b-j0l75kkbhu] { white-space: nowrap; }
.picker-grid td.marks span[b-j0l75kkbhu] { margin-right: .25rem; }

.pill[b-j0l75kkbhu] { display: inline-block; margin-left: .4rem; padding: 0 .4rem; border-radius: 999px; font-size: .68rem; color: #cdbb99; background: var(--forge-surface-chip); border: 1px solid #4b4031; }
.pill.entry[b-j0l75kkbhu] { color: var(--forge-gold-bright); border-color: #7a6134; }
.pill.boss[b-j0l75kkbhu] { color: #f0b0ac; border-color: #7a4340; }
.pill.warn[b-j0l75kkbhu] { color: #e0b49c; border-color: #6d4a35; }

.plain-list[b-j0l75kkbhu] { display: grid; gap: .3rem; margin: 0; padding: 0; list-style: none; }
.plain-list li[b-j0l75kkbhu] { padding: .4rem .6rem; border: 1px solid #3e3529; border-radius: .3rem; background: var(--forge-surface-1); color: var(--forge-text-label); }
.plain-list small[b-j0l75kkbhu] { color: var(--forge-text-faint); margin-left: .4rem; }

.readiness[b-j0l75kkbhu] { display: grid; gap: .3rem; margin: 0; padding-left: 1.2rem; color: var(--forge-text-label); }
.readiness li.ok[b-j0l75kkbhu] { color: var(--forge-success); }
.readiness li.warn[b-j0l75kkbhu] { color: #f0cc80; }
.readiness li.bad[b-j0l75kkbhu] { color: var(--forge-danger-alt); }
.readiness-ok[b-j0l75kkbhu] { color: var(--forge-success); }
.readiness .declare-tactics[b-j0l75kkbhu] { margin-left: .6rem; padding: .15rem .55rem; font-size: .85em; color: inherit; border: 1px solid currentColor; border-radius: .3rem; background: transparent; cursor: pointer; }
.readiness .declare-tactics:hover[b-j0l75kkbhu] { background: color-mix(in srgb, currentColor 12%, transparent); }
.diagnostics[b-j0l75kkbhu] { display: grid; gap: .35rem; margin: 0; padding-left: 1.2rem; color: #e0b49c; }
.diagnostics code[b-j0l75kkbhu] { color: #b7a98f; font-size: .78rem; }

@media (max-width: 900px) {
    /* .world-workspace and .world-section-nav fold in forge.css now (#295); their bases live there
       too, so the override could move without losing the specificity tie. .world-context-header
       stays because ITS base is still this page's, and this page's sheet loads last. */
    .world-context-header[b-j0l75kkbhu] { grid-template-columns: 1fr; position: static; margin: 0 0 1rem; }
    .world-save-state[b-j0l75kkbhu] { justify-items: start; }
    .outline-row[b-j0l75kkbhu] { grid-template-columns: 1fr; }
}

/* Issue #95's campaign editor used to declare .campaign-row here: a grid of inline fields for one arc.
   Both lists under a campaign are DraftListEditors now (#128 for the adventures, #673 for the arcs), so
   the fields live in a dialog and the row is a sentence. The rules went with the markup rather than
   being left behind — a scoped rule matching nothing reads to the next person as a style that broke. */

/* Issue #128: the list behind a dialog is a summary, not a row of inputs. Clickable as a whole, because
   the whole row is the target — hunting for a small edit pencil is the thing this pattern removes. */
.summary-row[b-j0l75kkbhu]{display:flex;align-items:center;gap:.7rem;padding:.5rem .7rem;margin:.35rem 0;
    border:1px solid #303b57;border-radius:8px;cursor:pointer}
.summary-row:hover[b-j0l75kkbhu]{border-color:#43537c;background:#161f36}
/* Issue #133 brought <strong>/<code> rows here — the ways through, and a merchant's two stock lists —
   so the row now has a name and an id as well as its sentence. */
.summary-row strong[b-j0l75kkbhu]{color:#f0e6d4;white-space:nowrap}
.summary-row code[b-j0l75kkbhu]{color:#8e9ab8;font-size:.75rem;white-space:nowrap}
/* min-width:0 is what lets the ellipsis happen at all — a flex item's default min-width is its content,
   so without it the row grows instead of truncating and pushes the delete button off the card. */
.summary-row .summary-text[b-j0l75kkbhu]{flex:1;min-width:0;font-size:.8rem;color:#c3cbe6;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.summary-row .danger[b-j0l75kkbhu]{margin-left:auto}

/* The library and the adventure's own maps are two different lists and now read as two. */
.map-import h3[b-j0l75kkbhu]{margin:0 0 .4rem;font-size:.8rem;letter-spacing:.06em;text-transform:uppercase;color:#8e9ab8}
.map-card-actions[b-j0l75kkbhu]{display:flex;align-items:center;gap:.5rem}

/* Issue #106. Five shipped shops that nothing offered until now — the picker sits above the shelves it
   fills, because that is the order somebody works in: choose the shop, then adjust what is on it. */
.merchant-template[b-j0l75kkbhu] { display: grid; gap: .4rem; margin: .6rem 0; padding: .6rem;
    border: 1px solid var(--forge-border-nested); border-radius: 8px; background: var(--forge-surface-1); }
.merchant-template label[b-j0l75kkbhu] { display: grid; gap: .3rem; }
.merchant-template p[b-j0l75kkbhu] { margin: 0; }
.merchant-template em[b-j0l75kkbhu] { display: block; opacity: .8; font-style: normal; }
/* Applying a template OVERWRITES. Said before the click, not after. */
.merchant-template p.warn[b-j0l75kkbhu] { color: #f0cc80; font-size: .85rem; }

/* Denizens (issue #229 layer 2). Two rows that pair a plain-English explanation with the button that
   opens the board, because the squares themselves are picked in a dialog and what is left here has to
   say what was picked without making anybody read coordinates to find out. */
.den-ground[b-j0l75kkbhu] { display: grid; gap: .5rem; margin: .6rem 0; }
.den-ground-row[b-j0l75kkbhu] { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem;
    padding: .6rem; border: 1px solid var(--forge-border-nested); border-radius: 8px;
    background: var(--forge-surface-1); }
.den-ground-row > div[b-j0l75kkbhu] { flex: 1; min-width: 0; }
.den-ground-row strong[b-j0l75kkbhu] { color: var(--forge-text); }
.den-ground-row p[b-j0l75kkbhu] { margin: .2rem 0 0; }
/* The picked squares, which can be a long run of them — wrapped rather than truncated, because a
   patrol's whole point is the shape of the route and an ellipsis would hide the end of it.
   ⚠️ Reached through the element and not through `.hint`: that class is deliberately unstyled on this
   page and listed as such, and a descendant rule naming it would quietly make the inventory a lie —
   the coverage guard checks both directions (#122). */
.den-ground-row > div > p[b-j0l75kkbhu] { overflow-wrap: anywhere; }

.den-provocations[b-j0l75kkbhu] { display: grid; gap: .35rem; margin: .6rem 0; padding: .6rem;
    border: 1px solid var(--forge-border-nested); border-radius: 8px; background: var(--forge-surface-1); }
.den-provocations > strong[b-j0l75kkbhu] { color: var(--forge-text); }
.den-provocations p[b-j0l75kkbhu] { margin: 0; }
.den-provocation-list[b-j0l75kkbhu] { display: grid; gap: .25rem; margin: .3rem 0 0; padding: 0; list-style: none; }
.den-provocation-list label[b-j0l75kkbhu] { display: flex; flex-wrap: wrap; align-items: baseline; gap: .1rem .45rem; cursor: pointer; }
.den-provocation-list code[b-j0l75kkbhu] { color: var(--forge-text-dim); font-size: .75rem; }
/* An id the map no longer has. A remark with a way out, not an error — the pack is genuinely broken
   until it is dealt with, but the author is one click from dealing with it. */
.den-provocation-orphan[b-j0l75kkbhu] { display: flex; align-items: center; gap: .5rem; margin: .3rem 0 0;
    padding: .35rem .55rem; border: 1px solid var(--forge-danger-border); border-radius: 6px;
    background: var(--forge-danger-surface); color: var(--forge-danger); font-size: .8rem; }

/* The DM's d100 grid (issue #229 layer 1, work order Phase 2). Dave: "a spreadsheet with names of
   possible creatures and then a D100 grid". The roll column is the whole reason it reads as a table
   rather than as a form — an author scans the ranges, not the weights. */
.d100-grid[b-j0l75kkbhu] { width: 100%; border-collapse: collapse; margin: .5rem 0; font-size: .85rem; }
.d100-grid th[b-j0l75kkbhu] { text-align: left; padding: .35rem .5rem; color: var(--forge-text-eyebrow);
    font-size: .7rem; letter-spacing: .06em; text-transform: uppercase;
    border-bottom: 1px solid var(--forge-border-nested); }
.d100-grid td[b-j0l75kkbhu] { padding: .3rem .5rem; border-bottom: 1px solid var(--forge-border-quiet);
    vertical-align: middle; }
.d100-grid tr.selected td[b-j0l75kkbhu] { background: var(--forge-surface-selected); }
/* Tabular figures so the ranges line up as a column of numbers rather than as text. A proportional
   font makes 01–35 and 36–00 different widths and the grid stops reading as a grid. */
.d100-roll[b-j0l75kkbhu] { width: 5.5rem; white-space: nowrap; font-variant-numeric: tabular-nums; }
.d100-roll code[b-j0l75kkbhu] { color: var(--forge-gold); }
.d100-weight[b-j0l75kkbhu] { width: 4rem; }
/* #569. Wider than the weight beside it: a dice expression is `2d4+1`, not a single digit. */
.d100-count[b-j0l75kkbhu] { width: 5rem; text-align: right; }

/* #571. Five short values on one line instead of five rows. `end` so the controls sit on a line however
   far the captions above them wrap, and the destructive button pushed out of the run of fields it was
   sitting among. */
.wandering-table-head[b-j0l75kkbhu] { display: flex; flex-wrap: wrap; align-items: end; gap: .6rem .8rem; }
.wandering-table-head > label[b-j0l75kkbhu] { flex: 1 1 11rem; min-width: 0; }
.wandering-table-head > label.field-id[b-j0l75kkbhu] { flex: 1 1 9rem; }
.wandering-table-head > label.field-levels[b-j0l75kkbhu] { flex: 0 0 auto; }
.wandering-table-head > button.danger[b-j0l75kkbhu] { margin-left: auto; }
.level-pair[b-j0l75kkbhu] { display: flex; align-items: center; gap: .35rem; }
.level-pair input[b-j0l75kkbhu] { width: 4.5rem; }
.level-pair-to[b-j0l75kkbhu] { color: var(--forge-text-faint); font-size: .8rem; }
/* The whole outcome cell is the target — hunting for a small edit control is the pattern #128 removed. */
.d100-outcome[b-j0l75kkbhu] { display: block; width: 100%; text-align: left; background: none; border: 0;
    padding: 0; color: inherit; cursor: pointer; }
.d100-outcome strong[b-j0l75kkbhu] { display: block; color: var(--forge-text); }
.d100-outcome small[b-j0l75kkbhu] { display: block; color: var(--forge-text-muted); }

/* One line of the table, opened up: the three kinds of outcome, and the fields each kind needs. */
.entry-outcome[b-j0l75kkbhu] { display: grid; gap: .45rem; margin: .6rem 0; padding: .6rem;
    border: 1px solid var(--forge-border-nested); border-radius: 8px;
    background: var(--forge-surface-1); }
.entry-outcome > strong[b-j0l75kkbhu] { color: var(--forge-text); }
.entry-outcome p[b-j0l75kkbhu] { margin: 0; }
.entry-outcome-kinds[b-j0l75kkbhu] { display: flex; flex-wrap: wrap; gap: .35rem 1.1rem; }
.entry-outcome-kinds label[b-j0l75kkbhu] { display: flex; align-items: center; gap: .4rem; cursor: pointer; }
.checkbox-row[b-j0l75kkbhu] { display: flex; align-items: flex-start; gap: .5rem; margin: .5rem 0; }
.checkbox-row strong[b-j0l75kkbhu] { color: var(--forge-text); }

/* "Roll it": which lines can actually fire. Deliberately NOT a chart — the distribution is the weights,
   and what an author cannot see is which rows are dead. */
.d100-actions[b-j0l75kkbhu] { display: flex; gap: .5rem; }
.roll-preview[b-j0l75kkbhu] { margin: .6rem 0; padding: .6rem; border: 1px solid var(--forge-border-nested);
    border-radius: 8px; background: var(--forge-surface-1); }
.roll-preview-head[b-j0l75kkbhu] { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.roll-preview-head strong[b-j0l75kkbhu] { color: var(--forge-text); }
.roll-preview p[b-j0l75kkbhu] { margin: .2rem 0 .4rem; }
.roll-preview-list[b-j0l75kkbhu] { display: grid; gap: .2rem; margin: 0; padding: 0; list-style: none;
    font-size: .82rem; }
.roll-preview-list li[b-j0l75kkbhu] { display: grid; grid-template-columns: 3.5rem minmax(0, 12rem) 1fr;
    gap: .5rem; align-items: baseline; padding: .15rem 0; }
.roll-preview-share[b-j0l75kkbhu] { text-align: right; font-variant-numeric: tabular-nums; color: var(--forge-gold); }
.roll-preview-name[b-j0l75kkbhu] { color: var(--forge-text); overflow-wrap: anywhere; }
.roll-preview-why[b-j0l75kkbhu] { color: var(--forge-text-muted); overflow-wrap: anywhere; }
/* A dead line is struck through rather than hidden: the author needs to see it is still on the table. */
.roll-preview-list li.cannot .roll-preview-name[b-j0l75kkbhu] { text-decoration: line-through;
    color: var(--forge-text-dim); }
.roll-preview-list li.cannot .roll-preview-why[b-j0l75kkbhu] { color: var(--forge-danger); }
.roll-preview-dead[b-j0l75kkbhu] { margin: .4rem 0 0; padding: .35rem .55rem; border-radius: 6px;
    border: 1px solid var(--forge-danger-border); background: var(--forge-danger-surface);
    color: var(--forge-danger); font-size: .82rem; }

/* How often, per map — read-only (D4: one dial, and it lives on the map). */
.pressure-readout[b-j0l75kkbhu] { display: grid; gap: .15rem; margin: .4rem 0; padding: 0; list-style: none;
    font-size: .82rem; }
.pressure-readout li[b-j0l75kkbhu] { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem; }
.pressure-readout code[b-j0l75kkbhu] { color: var(--forge-gold); }

/* The other shelf. World and dependency tables merge with the adventure's at roll time, and an author
   who cannot see them double-authors the same wolf. */
.world-shelf[b-j0l75kkbhu] { margin: .5rem 0; padding: .5rem .6rem; border: 1px solid var(--forge-border-nested);
    border-radius: 8px; background: var(--forge-surface-1); font-size: .82rem; }
.world-shelf summary[b-j0l75kkbhu] { cursor: pointer; color: var(--forge-text); }
.world-shelf p[b-j0l75kkbhu] { margin: .3rem 0; }
.world-shelf ul[b-j0l75kkbhu] { display: grid; gap: .15rem; margin: .3rem 0 0; padding: 0; list-style: none; }
.world-shelf li[b-j0l75kkbhu] { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem; }
.world-shelf code[b-j0l75kkbhu] { color: var(--forge-gold); }

/* ── The artifacts section's four borrowed names (#597) ───────────────────────────────────────────
   ⛔ These four were RENDERED by the artifacts card from the day it landed and declared nowhere this
   page can reach: .check and .field-note live in ForgeSettings.razor.css, .tool-hint in
   ForgeMapStudio.razor.css, and scoped CSS never crosses components. Nothing errors when a class
   matches nothing — the checkboxes simply drew as stacked grid items with the label above the box,
   and the hints drew at body size. Found by ForgeClassCoverageTests, not by looking.

   Copied in intent from the sheets they came from rather than invented: same layout, this page's own
   type scale and tokens. */

/* A checkbox and its words on one line. Without the flex the .editor-grid cell stacks them. */
label.check[b-j0l75kkbhu] { display: flex; align-items: center; gap: .45rem; }
label.check input[b-j0l75kkbhu] { flex: 0 0 auto; width: 1rem; height: 1rem; min-width: 1rem; margin: 0;
    accent-color: var(--forge-gold); }

/* A paragraph of guidance under a heading.
   ⚠️ NO colour here, and that is not an omission. `.forge-editor-card p` above already colours every
   paragraph in the card and outranks a bare class, so a colour written here loses silently — which is
   how `.muted`'s own colour has been losing on this page all along. The card decides the shade; this
   rule decides the size and the spacing, which nothing else claims. */
/* ⭐ `.hint` is the SAME THING under an older name, and both are rendered on this page — 15 of one and
   5 of the other, for a paragraph of guidance under a heading (#552). It is declared only as
   `.scene-graph-toolbar .hint` in SceneGraphCanvas.razor.css, which this page cannot reach and which
   means something else there: an amber PROBLEM colour on a canvas toolbar, not an advisory line in an
   editor card. So the look promoted here is this page's own — the rule already beside it, shared rather
   than copied, so the twenty paragraphs cannot drift into two looks. */
.hint[b-j0l75kkbhu],
.tool-hint[b-j0l75kkbhu] { margin: .15rem 0 .5rem; font-size: .8rem; line-height: 1.45; }

/* ── #552's remaining three, promoted the way #597 promoted the last four ─────────────────────────
   ⛔ All three were RENDERED here and declared only where scoped CSS cannot cross: .ghost-button in
   CreateCharacter.razor.css, .hint in SceneGraphCanvas.razor.css, .secondary in LevelUpPanel.razor.css.
   Unlike #552's other four — .map-card, .map-card-heading, .map-rooms and .can, which `git log --all -S`
   found had NEVER been declared anywhere and which Dave ruled should leave the markup — these three have
   a look on record, so restoring one is not inventing one.

   ⚠️ Restored in INTENT, in this page's tokens, exactly as #597's block above says. Copying the
   declarations verbatim would import another surface's palette: LevelUpPanel's `.secondary` is a
   slate-blue play-surface fill (#1a2133) and this page is gold-on-brown. What is carried across is what
   the name MEANS — quieter than the action beside it — said in the vocabulary this sheet already uses
   for exactly that, `.forge-editor-card button.quiet`.

   ⚠️ This is visible: it lands on ~20 paragraphs, 5 controls and 1 anchor at once, and it has not been
   looked at in a browser. */

/* One anchor, sitting beside a real <button class="danger">. Until now the pair read as a LINK next to a
   button, which is why `text-decoration` is here and is not in the sheet this came from: the whole point
   of the name is that an anchor stops looking like an anchor. Sized and rounded like this page's own
   buttons rather than CreateCharacter's, so the pair matches. */
.ghost-button[b-j0l75kkbhu] { display: inline-block; padding: .5rem .75rem; border: 1px solid var(--forge-border-quiet);
    border-radius: .25rem; background: transparent; color: #d8c49f; font-size: .85rem; font-weight: 700;
    text-decoration: none; white-space: nowrap; cursor: pointer; }
.ghost-button:hover[b-j0l75kkbhu] { border-color: var(--forge-gold); color: var(--forge-gold-bright); }

/* The quieter of two controls in a row — Export beside Delete, Set beside the field it commits, Add
   entry and Roll it under the d100 grid. Identical to `.quiet` above and deliberately not merged into
   it: the two names are rendered by different sections and either could be retired without the other.
   ⚠️ Anchored to `.forge-editor-card button` because that rule is (0,2,1) and a bare `.secondary` is
   (0,1,0) — a colour written unanchored here would lose silently, which the `.tool-hint` note above
   records happening to `.muted` on this page. */
.forge-editor-card button.secondary[b-j0l75kkbhu] { color: #d8c49f; background: transparent;
    border-color: var(--forge-border-quiet); }
/* The file picker dressed as a button (`.artifact-import > span`), which is the fifth of those controls
   and is quieter than the "Add artifact" button beside it. Same anchoring reason. */
.artifact-import > span.secondary[b-j0l75kkbhu] { color: #d8c49f; background: transparent; }

/* A note attached to one field, sitting under the control it is about. */
.field-note[b-j0l75kkbhu] { display: block; margin-top: .25rem; color: var(--forge-text-muted); font-size: .8rem; }

/* A field that wants the whole row rather than one auto-fit column — a description, not a name. */
label.wide[b-j0l75kkbhu] { display: grid; gap: .3rem; grid-column: 1 / -1; }

/* ── How hard an artifact is (#600) ───────────────────────────────────────────────────────────────
   The same three-part row the Map Studio's object tool uses — an action, a number, a way to take it
   away — deliberately NOT the same class names. `.object-dc-row` and `.region-remove` are the
   studio's, they are declared in the studio's scoped sheet which this page cannot reach, and
   `region-remove` is a name for something that is not a region. Copied in SHAPE, in this page's own
   tokens rather than the studio's blues. */
.artifact-dcs[b-j0l75kkbhu] { display: grid; gap: .3rem; margin: .3rem 0 .6rem; padding: .5rem;
    border: 1px solid var(--forge-border-nested); border-radius: 8px;
    background: var(--forge-surface-1); }
.artifact-dc-row[b-j0l75kkbhu] { display: flex; align-items: center; gap: .35rem; }
.artifact-dc-row code[b-j0l75kkbhu] { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis;
    color: var(--forge-gold); font-size: .8rem; }
.artifact-dc-row select[b-j0l75kkbhu] { flex: 1 1 auto; min-width: 0; }
.artifact-dc-row input[type=number][b-j0l75kkbhu] { width: 4rem; }

/* Takes one difficulty off. Danger-coloured because it removes an authored number, and small because
   there is one per row. */
.artifact-dc-remove[b-j0l75kkbhu] { padding: .28rem .45rem; border-radius: 7px;
    border: 1px solid var(--forge-danger-border); background: var(--forge-danger-surface);
    color: var(--forge-danger); font-size: .75rem; cursor: pointer; }

/* ── Importing an artifact (#597 Phase 5) ─────────────────────────────────────────────────────────
   A file picker dressed as the button beside it. The <span> carries the look; the real <input type=file>
   sits on top of it at zero opacity so the browser's own "Choose file / no file selected" chrome never
   shows — that text is a lie here, because there is nothing to choose until an author has a file. */
.artifact-import[b-j0l75kkbhu] { position: relative; display: inline-flex; align-items: center; gap: 0; }
.artifact-import > span[b-j0l75kkbhu] { display: inline-block; padding: .5rem .75rem; border-radius: .25rem;
    border: 1px solid var(--forge-border-quiet); background: var(--forge-surface-button);
    color: var(--forge-text); font-size: .85rem; white-space: nowrap; }
/* ⚠️ Covers the label rather than being hidden: a display:none input cannot be clicked, and a
   visually-hidden one loses the pointer target that makes the span behave like a button. */
.artifact-import input[type=file][b-j0l75kkbhu] { position: absolute; inset: 0; width: 100%; height: 100%;
    opacity: 0; cursor: pointer; }

/* The voice button, on a scene and on a den. Reads as a setting you open rather than an action you
   fire — this only ever raises a popup, and styling it like the page's commit buttons would promise
   something it does not do. */
.voice-line[b-j0l75kkbhu] { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin: .4rem 0 .2rem; }
.voice-button[b-j0l75kkbhu] { display: inline-flex; align-items: center; gap: .35rem; padding: .45rem .7rem;
    border: 1px solid var(--forge-border-input); border-radius: 8px;
    background: var(--forge-surface-input); color: var(--forge-text-input);
    font: inherit; cursor: pointer; white-space: nowrap; }
.voice-button:hover[b-j0l75kkbhu] { border-color: var(--forge-gold); color: var(--forge-gold); }
/* /Components/Pages/ForgeArena.razor.rz.scp.css */
/* Only the classes this page adds. The shell — .forge-library, .library-header, .section-heading,
   .empty-note, .quiet — comes from wwwroot/forge.css, promoted there by #100. Restating them here is
   exactly the duplication that issue existed to remove.

   ⚠️ .primary was in that list for months and #100 never promoted it — the comment was written
   believing a promotion had happened that #124 had actually refused, and it is why nobody re-checked
   for months ([[a-comment-that-outran-its-code]]). forge.css does declare `.forge-content .primary`
   now (#552), but it is NOT what draws this page's "Begin": that button sits in .library-header, so
   `.forge-library .library-header button` has always painted it gold at a higher specificity. The
   line is corrected rather than simply re-pointed, because the useful thing it can say is which rule
   actually reaches the button — not which sheet the name appears in. */

.arena-grid[b-2zq8h2h3sn]{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));
    padding:0 1.25rem 1.5rem}

.arena-panel[b-2zq8h2h3sn]{border:1px solid #4c4233;border-radius:12px;background:#191713;padding:1rem 1.1rem;
    display:flex;flex-direction:column;gap:.75rem}
.arena-panel .count[b-2zq8h2h3sn]{font-size:1.4rem;font-weight:800;color:#e6b85c;font-variant-numeric:tabular-nums}

.arena-list[b-2zq8h2h3sn]{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.4rem}
.arena-list li[b-2zq8h2h3sn]{display:flex;align-items:center;gap:.5rem;padding:.4rem .55rem;border:1px solid #3b3428;
    border-radius:9px;background:#141210}
.arena-list .who[b-2zq8h2h3sn]{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
    font-size:.85rem;color:#e8dcc4}
/* app.css sets a bare input{width:70%} globally (issue #126), so every control here has to say otherwise. */
.arena-list select[b-2zq8h2h3sn],.arena-list input[b-2zq8h2h3sn]{width:auto;box-sizing:border-box}
.arena-list .count-field input[b-2zq8h2h3sn]{width:3.5rem}
.arena-list .boss-field[b-2zq8h2h3sn],.arena-row .boss-field[b-2zq8h2h3sn]{display:flex;align-items:center;gap:.3rem;
    font-size:.75rem;color:#c9b791;white-space:nowrap}
.arena-list .boss-field input[b-2zq8h2h3sn],.arena-row .boss-field input[b-2zq8h2h3sn]{width:auto}

.arena-add[b-2zq8h2h3sn]{display:flex;gap:.5rem}
.arena-add select[b-2zq8h2h3sn]{flex:1;min-width:0;box-sizing:border-box;width:auto}
.arena-add button[b-2zq8h2h3sn]{white-space:nowrap}

/* Search, not a catalogue. The list is capped server-side and scrolls, so it stays the same height
   whether the library holds a thousand creatures or ten thousand. */
.arena-search[b-2zq8h2h3sn]{display:flex;align-items:center;gap:.5rem}
.arena-search input[b-2zq8h2h3sn]{flex:1;min-width:0;width:auto;box-sizing:border-box}
.arena-search .searching[b-2zq8h2h3sn]{color:#e6b85c;font-weight:800}

.arena-results[b-2zq8h2h3sn]{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.25rem;
    max-height:15rem;overflow-y:auto}
.arena-results button[b-2zq8h2h3sn]{display:flex;align-items:center;gap:.6rem;width:100%;text-align:left;
    border:1px solid #3b3428;border-radius:8px;background:#141210;color:#e8dcc4;padding:.35rem .55rem;
    font-size:.8rem;cursor:pointer}
.arena-results button:hover[b-2zq8h2h3sn]{background:#211d16;border-color:#8a6f37}
.arena-results .name[b-2zq8h2h3sn]{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.arena-results .lvl[b-2zq8h2h3sn]{font-size:.7rem;color:#9c8f76;font-variant-numeric:tabular-nums;white-space:nowrap}

.arena-row[b-2zq8h2h3sn]{display:flex;flex-wrap:wrap;gap:.75rem}
.arena-row label[b-2zq8h2h3sn]{display:flex;flex-direction:column;gap:.25rem;font-size:.72rem;
    text-transform:uppercase;letter-spacing:.06em;color:#9c8f76}
.arena-row input[b-2zq8h2h3sn],.arena-row select[b-2zq8h2h3sn]{width:100%;box-sizing:border-box}

.arena-hint[b-2zq8h2h3sn]{margin:0;font-size:.75rem;line-height:1.5;color:#9c8f76}

.arena-error[b-2zq8h2h3sn]{margin:0 1.25rem 1rem;padding:.6rem .8rem;border:1px solid #7a3b34;border-radius:9px;
    background:#2a1512;color:#f0c4bd;font-size:.8rem}
/* /Components/Pages/ForgeAssets.razor.rz.scp.css */
/* Scoped to this page. The Forge card/field shell this page used to restate — .character-editor-*,
   .field-grid.four, .section-heading, .draft-state and the .editor-status validity colours — now lives
   in wwwroot/forge.css under .forge-content, which every ForgeLayout page is already inside (#191).
   What is left here is either genuinely this page's own, or a rule the other pages disagree with.
   The --forge-* custom properties are global, so the palette was always genuinely shared. */

.character-editor-header h1[b-nqapa8pstj] { margin: .2rem 0; color: var(--forge-gold); }
.character-editor-header p[b-nqapa8pstj] { margin: 0; }
.eyebrow[b-nqapa8pstj] { color: var(--forge-text-dim); letter-spacing: .12em; font-size: .74rem; }

/* Not shared: ForgeAutomation's .row-actions has no flex-wrap. See #295. */
.row-actions[b-nqapa8pstj] { display: flex; flex-wrap: wrap; gap: .6rem; }

.character-editor-card button:disabled[b-nqapa8pstj], .character-editor-actions button:disabled[b-nqapa8pstj] { opacity: .5; cursor: default; }
.character-editor-card code[b-nqapa8pstj] { color: var(--forge-gold); }

/* .editor-status has no rule here at all any more. Its padding/border/background tail is shared from
   forge.css (#313) and this page is the one of the four that was never sticky, so it has nothing of
   its own left to say. */

/* The results themselves. auto-fill rather than a fixed count: this grid is looked at on a laptop and on a
   wide monitor, and a page of 48 tiles should reflow rather than scroll for a mile. */
.asset-grid[b-nqapa8pstj] { display: grid; grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); gap: .75rem; }

.asset-tile[b-nqapa8pstj] { margin: 0; display: grid; gap: .4rem; padding: .5rem; border: 1px solid var(--forge-border-nested); background: var(--forge-surface-1); }

/* contain, not cover: these are cut-out sprites and battlemaps of wildly different aspect ratios, and cropping
   a token to fill a square cuts the head off the thing you are trying to identify.
   The checkerboard is what makes a transparent token's own silhouette readable against the dark surface. */
.asset-tile img[b-nqapa8pstj] {
    width: 100%;
    height: 10rem;
    object-fit: contain;
    background-color: var(--forge-surface-3);
    background-image:
        linear-gradient(45deg, rgba(255, 255, 255, .04) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, .04) 75%),
        linear-gradient(45deg, rgba(255, 255, 255, .04) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, .04) 75%);
    background-size: 16px 16px;
    background-position: 0 0, 8px 8px;
}

/* The whole tile is a button, so it has to shed every button default the shared sheet gives it. */
.asset-open[b-nqapa8pstj] { display: grid; gap: .4rem; width: 100%; padding: 0; border: 0; background: none; text-align: left; cursor: pointer; font: inherit; color: inherit; }
.asset-open:hover strong[b-nqapa8pstj] { color: var(--forge-gold); }
.asset-open:focus-visible[b-nqapa8pstj] { outline: 2px solid var(--forge-gold); outline-offset: 2px; }

.asset-tile figcaption[b-nqapa8pstj] { display: grid; gap: .3rem; }
/* One line, ellipsised — the tooltip carries the rest. A wrapping name pushes the facets out of alignment
   across the grid and makes a page of results impossible to scan. */
.asset-tile strong[b-nqapa8pstj] { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.asset-chip.strong[b-nqapa8pstj] { color: var(--forge-text); border-color: var(--forge-border-input); }

/* --- the detail dialog ------------------------------------------------------------------------- */
.detail-facts dl[b-nqapa8pstj] { display: grid; grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); gap: .5rem 1rem; margin: 0; }
.detail-facts dt[b-nqapa8pstj] { color: var(--forge-text-dim); font-size: .72rem; letter-spacing: .06em; text-transform: uppercase; }
.detail-facts dd[b-nqapa8pstj] { margin: 0; color: var(--forge-text); overflow-wrap: anywhere; }

.detail-gallery[b-nqapa8pstj] { display: grid; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); gap: .75rem; margin-top: 1rem; }
.detail-variant[b-nqapa8pstj] { margin: 0; }
.detail-variant button[b-nqapa8pstj] { display: grid; gap: .35rem; width: 100%; padding: .4rem; border: 1px solid var(--forge-border-nested); background: var(--forge-surface-1); cursor: pointer; text-align: left; font: inherit; color: inherit; }
/* The selected variant is what "Open in Map Studio" will use, so it has to be unmistakable rather than subtle. */
.detail-variant.selected button[b-nqapa8pstj] { border-color: var(--forge-gold); box-shadow: inset 0 0 0 1px var(--forge-gold); }
.detail-variant button:focus-visible[b-nqapa8pstj] { outline: 2px solid var(--forge-gold); outline-offset: 2px; }
.detail-variant img[b-nqapa8pstj] {
    width: 100%; height: 12rem; object-fit: contain;
    background-color: var(--forge-surface-3);
    background-image:
        linear-gradient(45deg, rgba(255, 255, 255, .04) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, .04) 75%),
        linear-gradient(45deg, rgba(255, 255, 255, .04) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, .04) 75%);
    background-size: 16px 16px; background-position: 0 0, 8px 8px;
}
.asset-tile strong[b-nqapa8pstj] { color: var(--forge-text); font-size: .9rem; overflow-wrap: anywhere; }

.asset-facets[b-nqapa8pstj] { display: flex; flex-wrap: wrap; gap: .25rem; }
.asset-chip[b-nqapa8pstj] { color: var(--forge-text-dim); border: 1px solid var(--forge-border-quiet); border-radius: .8rem; padding: .05rem .45rem; font-size: .7rem; }
.asset-chip.warn[b-nqapa8pstj] { color: var(--forge-danger-alt); border-color: var(--forge-danger-border); }
/* #381: an untouched map. Dimmer than a plain chip so a page of forty-eight reads as a backlog at a
   glance rather than as forty-eight equally weighted labels. */
.asset-chip.muted[b-nqapa8pstj] { color: var(--forge-text-dim); border-color: transparent; opacity: .7; }
.asset-meta[b-nqapa8pstj] { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; color: var(--forge-text-dim); font-size: .74rem; }

@media (max-width: 1050px) {
    .field-grid.four[b-nqapa8pstj] { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 700px) {
    /* .character-editor-header folds in forge.css; .section-heading has to stay because forge.css's
       shared .section-heading rule sets display:flex at every width. */
    .section-heading[b-nqapa8pstj] { display: grid; }
    .field-grid.four[b-nqapa8pstj] { grid-template-columns: 1fr; }
}

/* #381: the per-facet sign-off list in the detail panel. A plain list — the checkbox is the control and
   the chips beside it are read-only facts, so nothing here should look clickable except the box. */
.facet-list[b-nqapa8pstj] { list-style: none; margin: .2rem 0 0; padding: 0; display: grid; gap: .35rem; }
.facet-list label[b-nqapa8pstj] { display: flex; align-items: center; gap: .4rem; font-size: .82rem; cursor: pointer; }
.facet-list input[type="checkbox"][b-nqapa8pstj] { cursor: pointer; }
/* The facet name takes the slack so the chips line up down the right rather than jittering with
   name length — a column of ticks is meant to be scannable. */
.facet-list label > span:first-of-type[b-nqapa8pstj] { flex: 1 1 auto; }

/* #361: the "keep typing" hint beside the search box. Sits under the input inside its <label>, which is
   already a flex column, so it needs no positioning of its own — only to stop looking like a result. */
.search-hint[b-nqapa8pstj] {
    display: block;
    margin-top: .3rem;
    font-size: .78rem;
    opacity: .75;
}
/* /Components/Pages/ForgeAutomation.razor.rz.scp.css */
/* This page shipped without a stylesheet of its own, borrowing class names — field-grid, drawer-section,
   sub-editor — from ForgeCreatures and ForgeSandbox. Blazor isolates component styles, so none of them
   ever arrived and every label sat beside a full-width input. The repository's standing rule is that a
   component owns its own look; this is that file. */

.studio-header[b-rr4w2xidto]{display:flex;justify-content:space-between;align-items:flex-start;gap:1.5rem;
    flex-wrap:wrap;margin-bottom:1.2rem}
.studio-header .eyebrow[b-rr4w2xidto]{margin:0;font-size:.7rem;letter-spacing:.14em;color:#8b7c5e}
.studio-header h1[b-rr4w2xidto]{margin:.2rem 0 .4rem;font-size:1.6rem}
.studio-header p[b-rr4w2xidto]{margin:0;max-width:62ch;font-size:.85rem;line-height:1.5;color:#a99a7c}
.header-actions[b-rr4w2xidto]{display:flex;gap:.5rem;flex-shrink:0}

.studio-section[b-rr4w2xidto]{margin:0 0 1.1rem;padding:.9rem 1rem;border:1px solid #3a3020;border-radius:12px;
    background:#181410}
.studio-section h3[b-rr4w2xidto]{margin:0;font-size:.95rem}
.studio-section h4[b-rr4w2xidto]{margin:0;font-size:.85rem;letter-spacing:.04em;color:#c9b072}
.section-heading[b-rr4w2xidto]{display:flex;justify-content:space-between;align-items:center;gap:1rem;
    margin:1rem 0 .5rem;flex-wrap:wrap}
.section-heading:first-child[b-rr4w2xidto]{margin-top:0}
.row-actions[b-rr4w2xidto]{display:flex;gap:.35rem}

/* The grid the labels needed all along: a label sits ABOVE its control, not beside it. */
.field-grid[b-rr4w2xidto]{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:.7rem;
    margin:.6rem 0}
.field-grid label[b-rr4w2xidto],label.field-block[b-rr4w2xidto]{display:flex;flex-direction:column;gap:.25rem;font-size:.7rem;
    font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:#9a8b6a}
label.field-block[b-rr4w2xidto]{margin:.6rem 0}
.field-grid input[b-rr4w2xidto],.field-grid select[b-rr4w2xidto],label.field-block textarea[b-rr4w2xidto],label.field-block input[b-rr4w2xidto]{padding:.35rem .45rem;
    border:1px solid #3a3020;border-radius:6px;background:#241d10;color:#e8dcc0;font-size:.85rem;
    text-transform:none;letter-spacing:0;font-weight:400;width:100%;box-sizing:border-box}

.reading-note[b-rr4w2xidto],.empty-note[b-rr4w2xidto]{margin:.4rem 0 .8rem;font-size:.78rem;line-height:1.5;color:#8b7c5e}
.reading-note strong[b-rr4w2xidto]{color:#c9b072}
.empty-note code[b-rr4w2xidto]{background:#241d10;padding:.05rem .25rem;border-radius:4px}

/* One rule in the LIST: a number, what it is for, and what it actually says (issue #134). The WHEN/THEN
   form that used to live here moved into the dialog — .rule-card and .rule-head went with it, since the
   card was only ever the frame around an always-open form. */
/* flex-wrap so the diagnostics below can claim a whole line of their own rather than being squeezed in
   beside the buttons. */
.rule-line[b-rr4w2xidto]{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem;margin:.35rem 0;padding:.5rem .6rem;
    border:1px solid #3a3020;border-radius:10px;background:#1b1610}
/* Only rendered when there is actually something wrong — see the gate in the markup, which is why there
   is no :empty rule here. */
.rule-line-errors[b-rr4w2xidto]{flex-basis:100%}
/* min-width:0 so a long sentence wraps inside the row instead of pushing the buttons off the edge. */
.rule-reading[b-rr4w2xidto]{flex:1;min-width:0;display:flex;flex-direction:column;gap:.1rem}
.rule-label-text[b-rr4w2xidto]{font-size:.88rem;font-weight:600;color:#e8dcc0}
.rule-sentence[b-rr4w2xidto]{font-size:.8rem;color:#b3a68a}
.rule-number[b-rr4w2xidto]{flex-shrink:0;width:1.5rem;height:1.5rem;display:grid;place-items:center;border-radius:50%;
    background:#3a3020;color:#e2c98f;font-size:.75rem;font-weight:800}
.rule-label[b-rr4w2xidto]{flex:1;min-width:0;padding:.2rem .35rem;border:1px solid transparent;border-radius:5px;
    background:transparent;color:#e8dcc0;font-size:.88rem;font-weight:600}
.rule-label:hover[b-rr4w2xidto],.rule-label:focus[b-rr4w2xidto]{border-color:#3a3020;background:#241d10;outline:none}
.rule-label[b-rr4w2xidto]::placeholder{color:#7d7059;font-weight:400;font-style:italic}

.clause[b-rr4w2xidto]{display:flex;gap:.6rem;padding:.55rem .6rem}
.clause.then[b-rr4w2xidto]{border-top:1px dashed #322a1c}
.clause-word[b-rr4w2xidto]{flex-shrink:0;width:3.2rem;padding-top:.3rem;font-size:.68rem;font-weight:800;
    letter-spacing:.1em;color:#8b7c5e}
.clause.then .clause-word[b-rr4w2xidto]{color:#9dc98a}
.clause-body[b-rr4w2xidto]{flex:1;min-width:0;display:flex;flex-direction:column;gap:.35rem}

.criterion[b-rr4w2xidto]{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem;padding:.3rem .45rem;
    border:1px solid #322a1c;border-radius:7px;background:#201a12}
.criterion-name[b-rr4w2xidto]{font-size:.8rem;color:#e8dcc0}
.criterion label[b-rr4w2xidto]{display:inline-flex;align-items:center;gap:.25rem;font-size:.68rem;
    letter-spacing:.04em;text-transform:uppercase;color:#8b7c5e}
.criterion label.grow[b-rr4w2xidto]{flex:1;min-width:12rem}
.criterion label.check[b-rr4w2xidto]{text-transform:none;letter-spacing:0;font-size:.75rem}
.criterion input[b-rr4w2xidto],.criterion select[b-rr4w2xidto]{padding:.2rem .35rem;border:1px solid #3a3020;border-radius:5px;
    background:#241d10;color:#e8dcc0;font-size:.8rem;text-transform:none;letter-spacing:0}
.criterion input[type=number][b-rr4w2xidto]{width:4.5rem}
.criterion input[type=checkbox][b-rr4w2xidto]{width:auto}
.criterion label.grow input[b-rr4w2xidto]{width:100%}
.criterion .drop[b-rr4w2xidto]{margin-left:auto;border:none;background:transparent;color:#7d7059;cursor:pointer;
    font-size:.8rem;padding:0 .2rem}
.criterion .drop:hover[b-rr4w2xidto]{color:#e08a7a}

.always[b-rr4w2xidto]{margin:0;padding:.3rem .45rem;font-size:.78rem;font-style:italic;color:#7d7059}
.add-condition[b-rr4w2xidto]{align-self:flex-start;padding:.25rem .4rem;border:1px dashed #3a3020;border-radius:6px;
    background:transparent;color:#9a8b6a;font-size:.78rem;cursor:pointer}
.add-condition:hover[b-rr4w2xidto]{border-color:#5a4a2e;color:#c9b072}

/* Inside the dialog: the sentence the rule currently reads as, updating while it is edited, so the
   author sees the thing the list will show rather than guessing from the fields. */
.rule-preview[b-rr4w2xidto]{margin:0;padding:.4rem .55rem;border-left:2px solid #5a4a2e;border-radius:0 6px 6px 0;
    background:#211a11;color:#d8c79f;font-size:.82rem;font-style:italic}

/* ⛔ An advisory, NOT an error: the validator deliberately accepts an action key this library has never
   heard of, because another pack may define it. Amber, not red — nothing here refuses to save. */
.action-warning[b-rr4w2xidto]{margin:.15rem 0 0;padding:.35rem .5rem;border:1px solid #5a4a2e;border-radius:6px;
    background:#241d10;color:#d8bd7c;font-size:.74rem}
.action-warning code[b-rr4w2xidto]{color:#e8dcc0}

/* The profile switcher. */
.profile-tabs[b-rr4w2xidto]{display:flex;flex-wrap:wrap;gap:.35rem;margin-top:.5rem}
.profile-tab[b-rr4w2xidto]{display:flex;flex-direction:column;align-items:flex-start;gap:.1rem;padding:.35rem .6rem;
    border:1px solid #3a3020;border-radius:8px;background:#1b1610;color:#c9b072;cursor:pointer}
.profile-tab.active[b-rr4w2xidto]{border-color:#7a6231;background:#2a2213;color:#f0e2bd}
.profile-tab .name[b-rr4w2xidto]{font-size:.85rem;font-weight:600}
.profile-tab .scope[b-rr4w2xidto]{font-size:.65rem;letter-spacing:.08em;text-transform:uppercase;color:#8b7c5e}

.status-note[b-rr4w2xidto],.status-error[b-rr4w2xidto]{margin:0 0 .8rem;padding:.5rem .7rem;border-radius:8px;font-size:.82rem}
.status-note[b-rr4w2xidto]{border:1px solid #33452e;background:#1e2a1c;color:#9dc98a}
.status-error[b-rr4w2xidto]{border:1px solid #5a3428;background:#2a1c18;color:#e08a7a}

button[b-rr4w2xidto]{padding:.3rem .6rem;border:1px solid #3a3020;border-radius:7px;background:#241d10;color:#c9b072;
    font-size:.8rem;cursor:pointer}
button:hover:not(:disabled)[b-rr4w2xidto]{background:#2e2514;color:#e2c98f}
button:disabled[b-rr4w2xidto]{opacity:.4;cursor:default}
button.danger[b-rr4w2xidto]{border-color:#5a3428;color:#d99a8c}
button.danger:hover:not(:disabled)[b-rr4w2xidto]{background:#2a1c18;color:#e8a898}
.drawer-primary[b-rr4w2xidto]{border-color:#7a6231;background:#3a2f18;color:#f0e2bd;font-weight:700}

/* The libraries that already exist. Without this the studio could only ever start from nothing, so the
   six profiles the game ships with were invisible to the tool meant for editing them. */
.library-list[b-rr4w2xidto]{display:flex;flex-wrap:wrap;gap:.4rem;margin:.6rem 0}
.library-pick[b-rr4w2xidto]{display:flex;flex-direction:column;align-items:flex-start;gap:.15rem;padding:.4rem .7rem;
    border:1px solid #3a3020;border-radius:9px;background:#1b1610;color:#c9b072;cursor:pointer;text-align:left}
.library-pick:hover:not(:disabled)[b-rr4w2xidto]{border-color:#5a4a2e;background:#221c13}
.library-pick.active[b-rr4w2xidto]{border-color:#7a6231;background:#2a2213;color:#f0e2bd}
.library-pick .name[b-rr4w2xidto]{font-size:.88rem;font-weight:600}
.library-pick .meta[b-rr4w2xidto]{font-size:.68rem;letter-spacing:.05em;text-transform:uppercase;color:#8b7c5e}
.draft-flag[b-rr4w2xidto]{margin-left:.3rem;padding:0 .25rem;border-radius:4px;background:#3a2f18;color:#e2c98f;
    font-style:normal}
.pack-fields[b-rr4w2xidto]{margin-top:.5rem}
.pack-fields summary[b-rr4w2xidto]{cursor:pointer;font-size:.72rem;font-weight:700;letter-spacing:.06em;
    text-transform:uppercase;color:#8b7c5e;padding:.2rem 0}
.pack-fields summary:hover[b-rr4w2xidto]{color:#c9b072}
/* /Components/Pages/ForgeBugReports.razor.rz.scp.css */
/* ⚠️ Plain on purpose: an operator's list, borrowing the Forge's own table and pre styles. */
.bugs-header[b-zv4q2nboi0] { margin-bottom: 1rem; }

.bugs[b-zv4q2nboi0] {
    width: 100%;
    border-collapse: collapse;
    font-size: .85rem;
}

.bugs th[b-zv4q2nboi0],
.bugs td[b-zv4q2nboi0] {
    padding: .35rem .5rem;
    border-bottom: 1px solid #8883;
    text-align: left;
    vertical-align: top;
}

.bug-row[b-zv4q2nboi0] { cursor: pointer; }
.bug-row:hover[b-zv4q2nboi0],
.bug-row.open[b-zv4q2nboi0] { background: #8881; }

/* The opened report, under its row: the same tint, so the two read as one. */
.bug-detail[b-zv4q2nboi0] { background: #8881; }

.bug-id[b-zv4q2nboi0] { font-family: monospace; font-size: .75rem; overflow-wrap: anywhere; }

.bug-said[b-zv4q2nboi0] { white-space: pre-wrap; }

.bug-picture[b-zv4q2nboi0] {
    display: block;
    max-width: min(60rem, 100%);
    margin: .5rem 0;
    border: 1px solid #8884;
}

.bug-context[b-zv4q2nboi0] {
    max-height: 32rem;
    overflow: auto;
    padding: .5rem;
    background: #8881;
    font-size: .75rem;
    white-space: pre-wrap;
}
/* /Components/Pages/ForgeCampaignRepair.razor.rz.scp.css */
.repair-header p[b-2tcr0d49ib] { max-width: 70ch; }
.eyebrow[b-2tcr0d49ib] { letter-spacing: 0.08em; font-size: 0.75rem; opacity: 0.7; margin: 0; }

.form[b-2tcr0d49ib] { display: flex; flex-wrap: wrap; gap: 0.75rem 1rem; align-items: flex-end; max-width: 56rem; margin: 1rem 0; }
.form label[b-2tcr0d49ib] { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.85rem; }
.form label.wide[b-2tcr0d49ib] { flex: 1 1 24rem; }
.form label.wide input[b-2tcr0d49ib] { width: 100%; }
.form .buttons[b-2tcr0d49ib] { display: flex; gap: 0.5rem; }
.form .danger[b-2tcr0d49ib] { color: #b03030; }

.status[b-2tcr0d49ib] { padding: 0.5rem 0.75rem; margin: 0.75rem 0; border-left: 3px solid currentColor; max-width: 70ch; }
.status.error[b-2tcr0d49ib] { color: #b03030; }
.status.ok[b-2tcr0d49ib] { color: #2d6a3e; }

.plan h2[b-2tcr0d49ib] { font-size: 1rem; margin: 1rem 0 0.5rem; }
.plan pre[b-2tcr0d49ib] { font-size: 0.8rem; overflow-x: auto; padding: 0.75rem; border: 1px solid rgba(128,128,128,0.35); }
/* /Components/Pages/ForgeCreatures.razor.rz.scp.css */
/* The card/field shell this page and ForgeAssets both stated is now in wwwroot/forge.css under
   .forge-content (#191). What stays here is the selectors this page ADDS to those shared rules —
   .nested-heading, .character-row label, the card's h3/h4 and its textarea — plus its own. */

.nested-heading[b-768fkxnl16] { display: flex; align-items: start; justify-content: space-between; gap: 1rem; }
/* Not shared: ForgeAutomation's .row-actions has no flex-wrap. See #295. */
.row-actions[b-768fkxnl16] { display: flex; flex-wrap: wrap; gap: .6rem; }
.character-editor-card h3[b-768fkxnl16], .character-editor-card h4[b-768fkxnl16] { margin: 0; color: var(--forge-gold); }
.character-row label[b-768fkxnl16] { display: grid; gap: .3rem; color: var(--forge-text-label); }
.character-editor-card textarea[b-768fkxnl16] { min-width: 0; color: var(--forge-text-input); background: var(--forge-surface-input); border: 1px solid var(--forge-border-input); padding: .5rem; }
.character-editor-actions .primary[b-768fkxnl16] { color: var(--forge-gold-ink); background: var(--forge-gold); border-color: var(--forge-gold); font-weight: 700; }
button.danger[b-768fkxnl16] { color: var(--forge-danger); border-color: var(--forge-danger-border); }
.library-loader[b-768fkxnl16] { display: grid; grid-template-columns: minmax(18rem, 1fr) auto; align-items: end; gap: .75rem; }
.character-nested-card[b-768fkxnl16] { display: grid; gap: 1rem; padding: .9rem; border: 1px solid var(--forge-border-nested); background: var(--forge-surface-1); }
.creature-basics[b-768fkxnl16] { display: grid; grid-template-columns: 1.2fr 1.4fr 1.2fr .55fr .65fr .65fr .65fr .65fr; gap: .55rem; }
.creature-basics .span-all[b-768fkxnl16] { grid-column: 1 / -1; }
.sub-editor[b-768fkxnl16] { display: grid; gap: .6rem; padding-top: .7rem; border-top: 1px solid #3f3528; }
/* .modifier-row / .condition-row / .attack-row / .extra-damage went with the inline rows they styled
   (issue #131). Those lists are DraftListEditor now, and it carries its own scoped sheet — a rule left
   here could not have reached it anyway, since scoped CSS does not cross a component boundary. */
.character-row[b-768fkxnl16] { display: grid; grid-template-columns: 1.25fr 1.3fr 1fr 1fr 1fr .5fr 1fr 1fr auto; align-items: end; gap: .55rem; padding: .75rem; border: 1px solid var(--forge-border-nested); background: var(--forge-surface-1); }
.json-textarea[b-768fkxnl16] { font: .84rem/1.45 Consolas, monospace; }
/* Not shared: this page's sticky offset is its own. The padding/border/background tail and the
   .error/.success colours are in forge.css (#191, #313). */
.editor-status[b-768fkxnl16] { position: sticky; top: .5rem; z-index: 5; }
.invalid[b-768fkxnl16] { color: var(--forge-danger-alt); border-color: var(--forge-danger-border); }
.valid[b-768fkxnl16] { color: var(--forge-success); border-color: var(--forge-success-border-alt); }
@media (max-width: 1050px) {
    .field-grid.four[b-768fkxnl16], .creature-basics[b-768fkxnl16], .character-row[b-768fkxnl16] { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 700px) {
    /* .character-editor-header folds in forge.css; these two do not. */
    .section-heading[b-768fkxnl16], .nested-heading[b-768fkxnl16] { display: grid; }
    .field-grid.four[b-768fkxnl16], .library-loader[b-768fkxnl16], .creature-basics[b-768fkxnl16], .character-row[b-768fkxnl16] { grid-template-columns: 1fr; }
}

.creature-images .muted[b-768fkxnl16] { color: var(--forge-text-dim); margin: 0; }
.image-album[b-768fkxnl16] { display: grid; grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); gap: .75rem; }
.album-item[b-768fkxnl16] { margin: 0; display: grid; gap: .35rem; padding: .4rem; border: 1px solid var(--forge-border-nested); background: var(--forge-surface-1); }
.album-item.is-portrait[b-768fkxnl16] { border-color: var(--forge-gold); box-shadow: 0 0 0 1px var(--forge-gold) inset; }
.album-item img[b-768fkxnl16] { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; background: var(--forge-surface-well); }
.album-item figcaption[b-768fkxnl16] { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; }
.album-item figcaption button[b-768fkxnl16] { font-size: .8rem; padding: .3rem .5rem; }
.album-badge[b-768fkxnl16] { color: var(--forge-gold); font-weight: 700; font-size: .8rem; }

/* --- binding a library token to a creature (#443) -------------------------------------------------
   ⚠️ Duplicated in ForgeCreatures.razor.css and ForgeLibrary.razor.css because Blazor's scoped CSS does
   not cross components and both editors offer the same choice. Small enough to copy; if it grows, it is
   promoted to the shared sheet rather than copied a third time. */

.token-binding[b-768fkxnl16] {
    margin: .5rem 0;
}

.token-search[b-768fkxnl16] {
    display: flex;
    gap: .4rem;
    align-items: center;
}

.token-search input[b-768fkxnl16] {
    flex: 1 1 auto;
    min-width: 0;
}

.token-results[b-768fkxnl16] {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin-top: .5rem;
}

.token-option[b-768fkxnl16] {
    margin: 0;
    width: 96px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .25rem;
}

.token-option img[b-768fkxnl16] {
    width: 96px;
    height: 96px;
    object-fit: contain;
    /* A token is a cut-out with transparency; a plate behind it keeps a dark silhouette readable
       against the dark page without flattening the alpha the adoption went to trouble to keep. */
    background: #161d2b;
    border: 1px solid #2b3550;
    border-radius: 4px;
}

.token-option figcaption[b-768fkxnl16] {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .2rem;
    font-size: .68rem;
    text-align: center;
}

.bound-token img[b-768fkxnl16] {
    max-width: 220px;
    height: auto;
}
/* /Components/Pages/ForgeDebugLog.razor.rz.scp.css */
.debug-header[b-mtlwpxpdhu], .bulk-bar[b-mtlwpxpdhu], .filters[b-mtlwpxpdhu], summary[b-mtlwpxpdhu] { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.debug-header[b-mtlwpxpdhu] { margin-bottom: 1.25rem; }
.debug-header h1[b-mtlwpxpdhu] { margin: .15rem 0; }
.debug-status[b-mtlwpxpdhu], .empty-state[b-mtlwpxpdhu], .filters[b-mtlwpxpdhu], .bulk-bar[b-mtlwpxpdhu], .debug-entry[b-mtlwpxpdhu] { border: 1px solid #3f4652; border-radius: .55rem; background: #171b22; }
.debug-status[b-mtlwpxpdhu], .empty-state[b-mtlwpxpdhu] { padding: 1rem; margin: 1rem 0; }
.debug-status.error[b-mtlwpxpdhu] { border-color: #a94b55; color: #ffb8be; }
.filters[b-mtlwpxpdhu] { padding: 1rem; flex-wrap: wrap; justify-content: flex-start; }
.filters label[b-mtlwpxpdhu] { display: grid; gap: .3rem; min-width: 11rem; }
.filters input[b-mtlwpxpdhu], .filters select[b-mtlwpxpdhu] { min-height: 2.25rem; }
.bulk-bar[b-mtlwpxpdhu] { margin: 1rem 0; padding: .7rem 1rem; }
.bulk-bar div[b-mtlwpxpdhu] { display: flex; gap: .5rem; }
.danger[b-mtlwpxpdhu] { color: #ffb8be; border-color: #a94b55; }
.entry-list[b-mtlwpxpdhu] { display: grid; gap: .55rem; }
.debug-entry[b-mtlwpxpdhu] { display: grid; grid-template-columns: 2.25rem 1fr; padding: .45rem .65rem; }
.select-entry[b-mtlwpxpdhu] { display: grid; place-items: start center; padding-top: .35rem; }
.debug-entry details[b-mtlwpxpdhu] { min-width: 0; }
.debug-entry summary[b-mtlwpxpdhu] { cursor: pointer; justify-content: flex-start; flex-wrap: wrap; min-height: 2rem; }
.debug-entry summary time[b-mtlwpxpdhu] { margin-left: auto; color: #aeb7c4; }
.debug-entry summary code[b-mtlwpxpdhu] { font-size: .75rem; }
.kind[b-mtlwpxpdhu] { padding: .18rem .45rem; border-radius: 999px; background: #30405c; font-size: .75rem; }
.kind.tool_execution[b-mtlwpxpdhu] { background: #405337; }
.kind.calculation[b-mtlwpxpdhu] { background: #5c4930; }
.kind.error[b-mtlwpxpdhu] { background: #673840; }
pre[b-mtlwpxpdhu] { overflow: auto; max-height: 45rem; padding: 1rem; background: #0f1217; border-radius: .4rem; white-space: pre-wrap; overflow-wrap: anywhere; }
.load-more[b-mtlwpxpdhu] { display: block; margin: 1rem auto; }
@media (max-width: 760px) {
    .debug-header[b-mtlwpxpdhu], .bulk-bar[b-mtlwpxpdhu] { align-items: stretch; flex-direction: column; }
    .bulk-bar div[b-mtlwpxpdhu] { flex-wrap: wrap; }
    .debug-entry summary time[b-mtlwpxpdhu] { margin-left: 0; width: 100%; }
}
/* /Components/Pages/ForgeEncounterCardPreview.razor.rz.scp.css */
/* ⚠️ Above the card's veil, which is `position: fixed; inset: 0` at z-index 60.

   That veil is doing its job — it is what stops a player clicking the board behind an un-ignorable card
   — and it covers this bench's switcher along with everything else. Lifting the bar over it is a property
   of the BENCH and says nothing about the card. */
.card-bench[b-vyjlka0po4] {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 80;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .6rem;
    padding: .6rem 1rem;
    background: #0d0b09ee;
    border-bottom: 1px solid #4a3d28;
    color: #c9b48e;
    font-size: .82rem;
}

.card-bench strong[b-vyjlka0po4] { color: #d8b96a; }

.card-bench button[b-vyjlka0po4] {
    padding: .3rem .7rem;
    border: 1px solid #7d6a48;
    border-radius: .2rem;
    background: #241e17;
    color: #f3e7cf;
    cursor: pointer;
    font: inherit;
}

.card-bench button:hover[b-vyjlka0po4] { background: #3a3021; }
/* /Components/Pages/ForgeHome.razor.rz.scp.css */
.forge-tool-grid[b-qf0wyb37g4] { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: 1rem; margin-top: 2rem; }
.forge-tool-grid a[b-qf0wyb37g4] { display: grid; gap: .6rem; min-height: 7rem; padding: 1.2rem; color: var(--forge-text); text-decoration: none; border: 1px solid #55462f; background: #1b1814; }
.forge-tool-grid strong[b-qf0wyb37g4] { color: var(--forge-gold); }
.forge-tool-grid span[b-qf0wyb37g4] { color: #bcb09d; line-height: 1.4; }
/* /Components/Pages/ForgeLibrary.razor.rz.scp.css */
.library-context-header[b-bjkahoqos5] { position: sticky; top: 0; z-index: 8; display: grid; grid-template-columns: minmax(18rem, 1fr) auto auto; align-items: center; gap: 1rem; margin: -1rem -1rem 1rem; padding: 1rem 1.2rem; border-bottom: 1px solid #57472f; background: rgba(20, 17, 13, .97); backdrop-filter: blur(8px); }
.library-context-header h1[b-bjkahoqos5], .library-card h2[b-bjkahoqos5], .catalog-shell h2[b-bjkahoqos5], .catalog-card h3[b-bjkahoqos5], .library-table h3[b-bjkahoqos5] { margin: 0; color: var(--forge-gold); }
.library-context-header p[b-bjkahoqos5], .library-card p[b-bjkahoqos5], .catalog-shell p[b-bjkahoqos5] { margin: .25rem 0 0; }
.library-save-state[b-bjkahoqos5] { display: grid; gap: .15rem; min-width: 12rem; font-size: .82rem; color: #b8aa95; }
.library-save-state strong[b-bjkahoqos5] { color: var(--forge-text-input); }
.header-actions[b-bjkahoqos5], .section-heading[b-bjkahoqos5], .catalog-heading[b-bjkahoqos5], .card-title[b-bjkahoqos5], .card-actions[b-bjkahoqos5], .record-actions[b-bjkahoqos5] { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
.library-workspace[b-bjkahoqos5] { display: grid; grid-template-columns: 15rem minmax(0, 1fr); align-items: start; gap: 1.15rem; }
.library-section-nav[b-bjkahoqos5] { position: sticky; top: 7.5rem; display: grid; gap: .3rem; max-height: calc(100vh - 8rem); overflow: auto; padding: .75rem; border: 1px solid var(--forge-border-nav); border-radius: .4rem; background: var(--forge-surface-4); }
.library-section-nav > p[b-bjkahoqos5] { margin: .55rem .5rem .25rem; color: var(--forge-text-eyebrow); font-size: .68rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; }
.library-section-nav a[b-bjkahoqos5] { display: grid; gap: .15rem; padding: .55rem .65rem; color: #d8c9b0; border-left: 3px solid transparent; text-decoration: none; }
.library-section-nav a:hover[b-bjkahoqos5] { background: var(--forge-surface-hover); }
.library-section-nav a.active[b-bjkahoqos5] { color: var(--forge-gold-bright); border-left-color: var(--forge-gold); background: var(--forge-surface-selected); }
.library-section-nav a small[b-bjkahoqos5] { color: var(--forge-text-faint); font-size: .68rem; line-height: 1.3; }
.library-main[b-bjkahoqos5], .catalog-shell[b-bjkahoqos5], .sheet-stack[b-bjkahoqos5] { display: grid; gap: 1rem; min-width: 0; }
.library-card[b-bjkahoqos5], .catalog-card[b-bjkahoqos5], .create-panel[b-bjkahoqos5], .empty-state[b-bjkahoqos5] { padding: 1rem; border: 1px solid var(--forge-border-editor); border-radius: .5rem; background: var(--forge-surface-6); }
.library-card label[b-bjkahoqos5], .catalog-filters label[b-bjkahoqos5], .create-panel label[b-bjkahoqos5], .publish-card label[b-bjkahoqos5] { display: grid; gap: .3rem; color: var(--forge-text-label); }
.library-card input[b-bjkahoqos5], .library-card select[b-bjkahoqos5], .library-card textarea[b-bjkahoqos5], .catalog-filters input[b-bjkahoqos5], .catalog-filters select[b-bjkahoqos5], .create-panel select[b-bjkahoqos5], .publish-card input[b-bjkahoqos5], .publish-card select[b-bjkahoqos5] { min-width: 0; padding: .55rem; color: var(--forge-text-input); border: 1px solid var(--forge-border-input); background: var(--forge-surface-input); }
.library-card button[b-bjkahoqos5], .catalog-shell button[b-bjkahoqos5], .header-actions button[b-bjkahoqos5] { width: fit-content; padding: .52rem .8rem; color: var(--forge-text); border: 1px solid var(--forge-border-quiet); background: var(--forge-surface-button); cursor: pointer; }
.library-card button:disabled[b-bjkahoqos5], .catalog-shell button:disabled[b-bjkahoqos5], .header-actions button:disabled[b-bjkahoqos5] { opacity: .45; cursor: default; }
.primary[b-bjkahoqos5], .primary-link[b-bjkahoqos5] { padding: .52rem .8rem; color: var(--forge-gold-ink) !important; border-color: var(--forge-gold) !important; border-radius: .15rem; background: var(--forge-gold) !important; font-weight: 800; text-decoration: none; }
button.danger[b-bjkahoqos5] { color: var(--forge-danger); border-color: var(--forge-danger-border); }
.catalog-heading[b-bjkahoqos5] { align-items: end; }
.catalog-filters[b-bjkahoqos5] { display: grid; grid-template-columns: minmax(16rem, 2fr) repeat(5, minmax(8rem, 1fr)); gap: .65rem; padding: .85rem; border: 1px solid var(--forge-border-nested); background: var(--forge-surface-1); }
.create-panel[b-bjkahoqos5] { display: grid; grid-template-columns: minmax(16rem, 1fr) minmax(15rem, 1fr) auto auto; align-items: end; gap: .75rem; border-color: var(--forge-border-gold); }
.create-library-panel[b-bjkahoqos5] { display: grid; grid-template-columns: minmax(14rem, 1.5fr) repeat(4, minmax(8rem, 1fr)) auto; align-items: end; gap: .65rem; }
.catalog-grid[b-bjkahoqos5] { display: grid; grid-template-columns: repeat(auto-fill, minmax(21rem, 1fr)); gap: .8rem; }
.catalog-card[b-bjkahoqos5] { display: grid; grid-template-columns: 5rem minmax(0, 1fr); gap: .85rem; }
.catalog-card-body[b-bjkahoqos5] { min-width: 0; }
.catalog-card-body > p[b-bjkahoqos5] { color: #b8aa95; }
.catalog-card .card-actions[b-bjkahoqos5] { grid-column: 1 / -1; justify-content: flex-end; padding-top: .75rem; border-top: 1px solid #3e3427; }
.record-portrait[b-bjkahoqos5] { display: grid; place-items: center; width: 5rem; height: 6.5rem; overflow: hidden; border: 1px solid #69583f; border-radius: .35rem; color: #d5b46f; background: radial-gradient(circle at 50% 30%, #3a3023, #17130f); font: 800 1.5rem Georgia, serif; }
.record-portrait img[b-bjkahoqos5] { width: 100%; height: 100%; object-fit: cover; }
.record-meta[b-bjkahoqos5], .tag-list[b-bjkahoqos5] { display: flex; flex-wrap: wrap; gap: .35rem; margin-top: .65rem; }
.record-meta span[b-bjkahoqos5], .tag-list span[b-bjkahoqos5], .status-pill[b-bjkahoqos5] { padding: .22rem .45rem; border: 1px solid #554833; border-radius: 999px; color: #b9ac98; background: #221d16; font-size: .72rem; }
.status-pill.valid[b-bjkahoqos5], .valid[b-bjkahoqos5] { color: var(--forge-success); }
.status-pill.invalid[b-bjkahoqos5], .invalid[b-bjkahoqos5] { color: var(--forge-danger-alt); }
.status-pill.draft[b-bjkahoqos5] { color: var(--forge-gold); }
.status-pill.published[b-bjkahoqos5] { color: #a9c9df; }
.worlds[b-bjkahoqos5] { font-size: .78rem; }
.catalog-issues[b-bjkahoqos5] { margin-top: .65rem; padding: .55rem .65rem; border: 1px solid #71483e; border-radius: .3rem; color: var(--forge-danger); background: #251713; font-size: .78rem; }
.catalog-issues summary[b-bjkahoqos5] { cursor: pointer; font-weight: 700; }
.catalog-issues ul[b-bjkahoqos5] { margin: .45rem 0 0; padding-left: 1.1rem; }
.catalog-issues li[b-bjkahoqos5] { margin: .25rem 0; }
.sheet-hero[b-bjkahoqos5] { display: grid; grid-template-columns: 11rem minmax(0, 1fr); gap: 1.25rem; }
.portrait-editor[b-bjkahoqos5] { display: grid; align-content: start; justify-items: start; gap: .55rem; }
.portrait-editor .record-portrait[b-bjkahoqos5] { width: 10rem; height: 13rem; }
/* ::deep: the only file input in the portrait editor is an <InputFile>, which does not carry this
   component's scope attribute, so this rule has never applied and the picker has been rendering at its
   full browser default width (#233). Deliberately left narrow — .library-card input, which would also
   have reached it, stays scoped so the picker keeps its native chrome rather than gaining a dark fill
   it was never designed with. */
.portrait-editor[b-bjkahoqos5]  input[type=file] { width: 10rem; font-size: .72rem; }
.portrait-editor small[b-bjkahoqos5] { color: #938570; }
.sheet-identity[b-bjkahoqos5] { display: grid; align-content: start; gap: .75rem; }
.field-grid[b-bjkahoqos5] { display: grid; gap: .65rem; }
.field-grid.two[b-bjkahoqos5] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.field-grid.three[b-bjkahoqos5] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.field-grid.six[b-bjkahoqos5] { grid-template-columns: repeat(6, minmax(7rem, 1fr)); }
.stat-ribbon[b-bjkahoqos5] { display: grid; grid-template-columns: repeat(4, 1fr); gap: .65rem; }
.stat-ribbon div[b-bjkahoqos5], .source-badge[b-bjkahoqos5] { display: grid; place-items: center; min-height: 5rem; padding: .65rem; border: 1px solid #745e3d; background: var(--forge-surface-notice); }
.stat-ribbon span[b-bjkahoqos5], .source-badge span[b-bjkahoqos5] { color: #a99b87; font-size: .72rem; text-transform: uppercase; letter-spacing: .08em; }
.stat-ribbon strong[b-bjkahoqos5] { color: #f2c86f; font: 700 1.8rem Georgia, serif; }
.sheet-columns[b-bjkahoqos5] { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; align-items: start; }
.section-heading[b-bjkahoqos5] { align-items: start; margin-bottom: .75rem; }
/* .collection-row and its .compact / .attack variants went with the inline creature rows they styled
   (issue #131). Those three lists are the shared CreatureModifierEditor / CreatureAttackEditor /
   CreatureConditionEditor now, and each carries its own chrome — this sheet could not reach into them. */
.advanced-panel summary[b-bjkahoqos5] { color: var(--forge-gold); cursor: pointer; }
.advanced-panel label[b-bjkahoqos5] { margin-top: .8rem; }
.advanced-panel textarea[b-bjkahoqos5] { font: .83rem/1.45 Consolas, monospace; }
.validation-card li[b-bjkahoqos5] { margin: .35rem 0; }
.record-actions[b-bjkahoqos5] { justify-content: flex-start; }
.record-actions > div[b-bjkahoqos5] { margin-right: auto; }
.delete-confirm[b-bjkahoqos5] { color: var(--forge-danger); }
/* Not shared: this page's sticky offset and its zeroed margin are its own. The padding/border/background
   tail and the .error/.success colours are in forge.css (#191, #313). */
.editor-status[b-bjkahoqos5] { position: sticky; top: 7.2rem; z-index: 7; margin: 0; }
.media-grid[b-bjkahoqos5] { display: grid; grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr)); gap: .75rem; }
.media-card[b-bjkahoqos5] { display: grid; grid-template-columns: 5rem 1fr; gap: .7rem; padding: .75rem; border: 1px solid var(--forge-border-editor); background: var(--forge-surface-6); }
.media-card div:not(.record-portrait)[b-bjkahoqos5] { display: grid; align-content: start; gap: .25rem; min-width: 0; }
.media-card code[b-bjkahoqos5] { overflow: hidden; color: #9d8e79; font-size: .67rem; text-overflow: ellipsis; }
.media-card a[b-bjkahoqos5] { grid-column: 1 / -1; color: var(--forge-gold); }
.library-table[b-bjkahoqos5] { display: grid; gap: .55rem; }
.library-table article[b-bjkahoqos5] { display: grid; grid-template-columns: minmax(15rem, 1fr) 10rem 7rem 8rem auto; align-items: center; gap: .75rem; padding: .8rem; border: 1px solid #4c402f; background: #19150f; }
.library-table article p[b-bjkahoqos5] { color: #9d8e79; font-size: .78rem; }
.publish-card[b-bjkahoqos5] { display: grid; grid-template-columns: minmax(15rem, 1fr) minmax(14rem, 1fr); align-items: end; gap: 1rem; }
.publish-card > div[b-bjkahoqos5] { grid-column: 1 / -1; }
.check-label[b-bjkahoqos5] { grid-template-columns: auto 1fr !important; align-items: center; justify-content: start; }
.check-label input[b-bjkahoqos5] { width: auto; margin: 0; }
.empty-state[b-bjkahoqos5] { padding: 2rem; text-align: center; color: #a99b87; }
@media (max-width: 1150px) {
    .library-context-header[b-bjkahoqos5] { grid-template-columns: 1fr auto; }
    .library-context-header > div:first-child[b-bjkahoqos5] { grid-column: 1 / -1; }
    .catalog-filters[b-bjkahoqos5] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .field-grid.six[b-bjkahoqos5] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .library-table article[b-bjkahoqos5] { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 760px) {
    .library-context-header[b-bjkahoqos5] { position: static; display: grid; grid-template-columns: 1fr; margin: -1rem -1rem 1rem; }
    .library-context-header > div:first-child[b-bjkahoqos5] { grid-column: auto; }
    .library-workspace[b-bjkahoqos5], .sheet-hero[b-bjkahoqos5], .sheet-columns[b-bjkahoqos5], .publish-card[b-bjkahoqos5] { grid-template-columns: 1fr; }
    .library-section-nav[b-bjkahoqos5] { position: static; display: flex; overflow-x: auto; max-height: none; }
    /* `.library-section-nav a b` used to be listed here too. NavItem renders a <span> and a <small>
       and no <b> at all, so it was hiding nothing — dead rather than broken (#233). */
    .library-section-nav > p[b-bjkahoqos5], .library-section-nav a small[b-bjkahoqos5] { display: none; }
    .library-section-nav a[b-bjkahoqos5] { flex: 0 0 auto; }
    .catalog-filters[b-bjkahoqos5], .create-panel[b-bjkahoqos5], .create-library-panel[b-bjkahoqos5], .field-grid.two[b-bjkahoqos5], .field-grid.three[b-bjkahoqos5], .field-grid.six[b-bjkahoqos5], .stat-ribbon[b-bjkahoqos5] { grid-template-columns: 1fr; }
    .catalog-card[b-bjkahoqos5] { grid-template-columns: 4rem minmax(0, 1fr); }
    .catalog-card .record-portrait[b-bjkahoqos5] { width: 4rem; height: 5rem; }
    .library-table article[b-bjkahoqos5] { grid-template-columns: 1fr; }
    .record-actions[b-bjkahoqos5] { align-items: stretch; flex-direction: column; }
}

.ai-portrait-actions[b-bjkahoqos5] { display: flex; flex-wrap: wrap; gap: .4rem; width: 10rem; }
.ai-portrait-actions button[b-bjkahoqos5] { font-size: .78rem; padding: .4rem .55rem; }
.portrait-editor .muted[b-bjkahoqos5] { color: var(--forge-text-dim); margin: 0; font-size: .82rem; }
.portrait-editor .image-album[b-bjkahoqos5] { display: grid; grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr)); gap: .5rem; width: 100%; max-width: 26rem; }
.portrait-editor .album-item[b-bjkahoqos5] { margin: 0; display: grid; gap: .3rem; padding: .3rem; border: 1px solid var(--forge-border-nested); background: var(--forge-surface-1); }
.portrait-editor .album-item.is-portrait[b-bjkahoqos5] { border-color: var(--forge-gold); box-shadow: 0 0 0 1px var(--forge-gold) inset; }
.portrait-editor .album-item img[b-bjkahoqos5] { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; background: var(--forge-surface-well); }
.portrait-editor .album-item figcaption[b-bjkahoqos5] { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; }
.portrait-editor .album-item figcaption button[b-bjkahoqos5] { font-size: .72rem; padding: .25rem .4rem; }
.album-badge[b-bjkahoqos5] { color: var(--forge-gold); font-weight: 700; font-size: .75rem; }
.uploaded-portrait[b-bjkahoqos5] { width: 100%; max-width: 20rem; color: #938570; }
.uploaded-portrait summary[b-bjkahoqos5] { cursor: pointer; font-size: .82rem; }

.catalog-pager[b-bjkahoqos5] { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; justify-content: center; padding-top: 1rem; }
.catalog-pager span[b-bjkahoqos5] { color: #d5c9b3; }
.catalog-filters .search-field[b-bjkahoqos5] { display: grid; gap: .35rem; align-content: start; }
.catalog-filters .search-scope-inline[b-bjkahoqos5] { display: flex; flex-direction: row; align-items: center; gap: .4rem; color: var(--forge-text-dim); font-size: .8rem; white-space: nowrap; }
.catalog-filters .search-scope-inline input[b-bjkahoqos5] { width: auto; margin: 0; }

/* --- binding a library token to a creature (#443) -------------------------------------------------
   ⚠️ Duplicated in ForgeCreatures.razor.css and ForgeLibrary.razor.css because Blazor's scoped CSS does
   not cross components and both editors offer the same choice. Small enough to copy; if it grows, it is
   promoted to the shared sheet rather than copied a third time. */

.token-binding[b-bjkahoqos5] {
    margin: .5rem 0;
}

.token-search[b-bjkahoqos5] {
    display: flex;
    gap: .4rem;
    align-items: center;
}

.token-search input[b-bjkahoqos5] {
    flex: 1 1 auto;
    min-width: 0;
}

.token-results[b-bjkahoqos5] {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin-top: .5rem;
}

.token-option[b-bjkahoqos5] {
    margin: 0;
    width: 96px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .25rem;
}

.token-option img[b-bjkahoqos5] {
    width: 96px;
    height: 96px;
    object-fit: contain;
    /* A token is a cut-out with transparency; a plate behind it keeps a dark silhouette readable
       against the dark page without flattening the alpha the adoption went to trouble to keep. */
    background: #161d2b;
    border: 1px solid #2b3550;
    border-radius: 4px;
}

.token-option figcaption[b-bjkahoqos5] {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .2rem;
    font-size: .68rem;
    text-align: center;
}

.bound-token img[b-bjkahoqos5] {
    max-width: 220px;
    height: auto;
}

/* ── The Overview (#376) ──────────────────────────────────────────────────────────────────────────
   Scoped, not promoted. The .overview-* names belong to this one page; the shared sheet is for chrome
   that two pages already draw the same way, and inventing a shared vocabulary for a surface with one
   caller is how forge.css would grow rules nobody can safely change later. */

.overview-ribbon[b-bjkahoqos5] { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: .65rem; }
.overview-ribbon article[b-bjkahoqos5] { display: grid; gap: .1rem; padding: .8rem; border: 1px solid #40372c; border-radius: .3rem; background: #12100d; }
.overview-ribbon strong[b-bjkahoqos5] { color: var(--forge-gold-bright); font-size: 1.6rem; font-variant-numeric: tabular-nums; }
.overview-ribbon span[b-bjkahoqos5] { color: var(--forge-text-dim); font-size: .78rem; }
.overview-ribbon small[b-bjkahoqos5] { color: var(--forge-text-faint); font-size: .68rem; }

.overview-table[b-bjkahoqos5] { width: 100%; border-collapse: collapse; font-size: .84rem; }
.overview-table th[b-bjkahoqos5] { padding: .35rem .5rem; text-align: left; color: var(--forge-text-eyebrow); font-size: .66rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; border-bottom: 1px solid var(--forge-border-nav); }
.overview-table td[b-bjkahoqos5] { padding: .4rem .5rem; color: var(--forge-text-label); border-bottom: 1px solid #2a241c; vertical-align: top; }
.overview-table td.numeric[b-bjkahoqos5], .overview-table th.numeric[b-bjkahoqos5] { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.overview-table a[b-bjkahoqos5] { color: var(--forge-gold); text-decoration: none; }
.overview-table a:hover[b-bjkahoqos5] { text-decoration: underline; }
.overview-table code[b-bjkahoqos5] { color: var(--forge-text-faint); font-size: .72rem; }
.overview-table small[b-bjkahoqos5] { display: block; color: var(--forge-text-faint); font-size: .72rem; }
/* A zero is information, not an error — quieted so the rows carrying a number are the ones that read. */
.overview-table tr.zero td[b-bjkahoqos5] { color: var(--forge-text-faint); }
.overview-table em[b-bjkahoqos5] { margin-left: .4rem; padding: 0 .35rem; border: 1px solid #554833; border-radius: 999px; color: #93856f; background: #221d16; font-size: .64rem; font-style: normal; }

/* ⛔ The bars are sized in PIXELS by the markup, not in percent.
   The first cut wrote `height: N%` on the bar and measured 0px for all six in Chrome: a percentage
   height resolves against a DEFINITE ancestor height, and this container had `min-height` (indefinite)
   with a `height: 100%` child, so every bar collapsed. It looked like a chart with no data — the
   [[a-constant-standing-in-for-a-measurement]] family, found only because the heights were measured
   rather than glanced at. Pixels resolve against nothing and cannot do that. */
.overview-histogram[b-bjkahoqos5] { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); align-items: end; gap: .5rem; }
.overview-histogram > div[b-bjkahoqos5] { display: grid; justify-items: center; gap: .2rem; }
.overview-histogram .bar[b-bjkahoqos5] { display: block; width: 100%; border-radius: .2rem .2rem 0 0; background: linear-gradient(#e6b85c, #8a6a2f); }
.overview-histogram strong[b-bjkahoqos5] { color: var(--forge-gold-bright); font-size: .9rem; font-variant-numeric: tabular-nums; }
.overview-histogram small[b-bjkahoqos5] { color: var(--forge-text-dim); font-size: .7rem; }

.overview-failed[b-bjkahoqos5] { border-color: var(--forge-danger-border); background: #211411; }
.overview-failed h3[b-bjkahoqos5] { color: var(--forge-danger); }

/* A wide table scrolls inside its own card rather than widening the workspace: the grid column it sits
   in is minmax(0, 1fr), so a table that overflows it pushes the whole page sideways instead. */
.overview-shell .library-card[b-bjkahoqos5] { overflow-x: auto; }

/* Reusing a picture already drawn (#560). Sits below the token search and borrows its result grid --
   `.token-search` and `.token-results` are declared above and are deliberately not redeclared here. */
/* ⛔ max-width:100%, NOT a rem figure. `.portrait-editor` lays itself out at ~269px inside a 176px grid
   column, so anything given a wider max-width overflows to the right and slides UNDER `.sheet-identity`,
   which paints later and swallows the click. The button rendered, looked enabled, and did nothing —
   `document.elementFromPoint` at its centre returned the identity panel. Constrained here rather than by
   widening the hero column, which is a layout Dave has already looked at. */
.portrait-editor .image-reuse[b-bjkahoqos5] {
    width: 100%;
    max-width: 100%;
    min-width: 0;
    margin-top: .5rem;
}

/* Wrap rather than overflow, for the same reason. */
.portrait-editor .image-reuse .token-search[b-bjkahoqos5] { flex-wrap: wrap; }

/* What the picture is, under its name: the whole reason a shared library is findable. */
.portrait-editor .image-tags[b-bjkahoqos5] {
    display: block;
    width: 100%;
    color: var(--forge-text-faint);
    font-size: .68rem;
}

/* ⚠️ Gold rather than muted: "three other creatures wear this" is the fact that decides whether Delete
   is safe, so it must not read as decoration. */
.portrait-editor .image-worn[b-bjkahoqos5] {
    display: block;
    width: 100%;
    color: var(--forge-gold);
    font-size: .68rem;
}
/* /Components/Pages/ForgeLibraryGap.razor.rz.scp.css */
/* The worklist behind one Overview number (#558).

   ⚠️ Every custom property named here is one forge.css actually declares. The first draft of this sheet
   invented `--forge-ink`, `--forge-line` and `--forge-gold-dim`, none of which exist — and because a
   var() with a fallback renders perfectly happily, the page came up looking almost right with blue links
   in a warm-brown Forge (#125's exact complaint). A name that resolves to nothing is silent; the fallback
   is what hides it.

   `library-card`, `library-context-header`, `eyebrow`, `overview-table` and `numeric` come from the
   shared Forge sheet and are deliberately NOT redeclared here — redeclaring a shared name inside a scoped
   sheet is how two definitions of one look start. What follows is only what this page adds. */

.gap-scope[b-vwylrpyft6] {
    display: flex;
    gap: .4rem;
    align-items: center;
}

.gap-scope a[b-vwylrpyft6] {
    padding: .3rem .7rem;
    border: 1px solid var(--forge-border-card);
    border-radius: .35rem;
    background: var(--forge-surface-button);
    color: var(--forge-text-dim);
    text-decoration: none;
    font-size: .85rem;
}

.gap-scope a:hover[b-vwylrpyft6] {
    color: var(--forge-text);
    border-color: var(--forge-border-quiet);
}

.gap-scope a.current[b-vwylrpyft6] {
    color: var(--forge-gold);
    border-color: var(--forge-border-gold);
    background: var(--forge-surface-selected);
}

.gap-count[b-vwylrpyft6] {
    margin: 0 0 .8rem;
    color: var(--forge-text-muted);
}

.gap-count strong[b-vwylrpyft6] {
    font-size: 1.4rem;
    color: var(--forge-gold);
}

/* The cap notice sits beside the count rather than under the table: it qualifies the number, and a reader
   who stops at the number has to see it. */
.gap-capped[b-vwylrpyft6] {
    color: var(--forge-text-faint);
    font-size: .85rem;
    margin-left: .4rem;
}

.gap-table code[b-vwylrpyft6] {
    font-size: .85rem;
    color: var(--forge-text-faint);
}

/* Level and the in-use badge share one cell and must sit on one line — stacked, they doubled the height
   of every row for a five-character word. */
.gap-detail[b-vwylrpyft6] {
    display: flex;
    align-items: baseline;
    gap: .5rem;
    white-space: nowrap;
}

.gap-detail small[b-vwylrpyft6] {
    color: var(--forge-text-faint);
    font-size: .8rem;
}

.gap-inuse[b-vwylrpyft6] {
    padding: .1rem .4rem;
    border-radius: .25rem;
    border: 1px solid var(--forge-border-gold);
    background: var(--forge-surface-selected);
    color: var(--forge-gold);
    font-size: .68rem;
    text-transform: uppercase;
    letter-spacing: .05em;
}

/* ⛔ The Forge is warm brown and gold. A default-blue anchor here is #125's complaint arriving on a new
   page, so the one link in every row states its colour. */
.gap-open[b-vwylrpyft6] {
    color: var(--forge-gold);
    text-decoration: none;
    font-size: .85rem;
    white-space: nowrap;
}

.gap-open:hover[b-vwylrpyft6] {
    color: var(--forge-gold-bright);
    text-decoration: underline;
}

.gap-error h3[b-vwylrpyft6] {
    color: var(--forge-danger);
}

.gap-back[b-vwylrpyft6] {
    margin-top: 1rem;
}

.gap-back a[b-vwylrpyft6] {
    color: var(--forge-text-dim);
    text-decoration: none;
}

.gap-back a:hover[b-vwylrpyft6] {
    color: var(--forge-gold);
}
/* /Components/Pages/ForgeMapStudio.razor.rz.scp.css */
/* The Map Studio (epic #170).
   Editor chrome, not page chrome: the canvas is the content and everything else is a rail around it. The
   whole surface is one viewport-height flex column that never scrolls -- a drawing tool where the drawing
   moves when you flick the wheel is unusable, so the page itself is pinned and only the panels scroll. */

.studio-primary[b-v7peukx7sc] {
    display: flex;
    flex-direction: column;
    gap: .6rem;
    /* Everything the Forge layout puts above and below this page: its own header strip, plus the content
       area's 2rem of vertical padding. Measured rather than guessed -- the first version subtracted only
       the padding and pushed the toolbar 119px off the bottom of the screen. dvh so a mobile browser's
       collapsing address bar does not do the same thing again. */
    height: calc(100dvh - 13rem);
    min-height: 28rem;
}

.studio-header[b-v7peukx7sc] {
    display: flex;
    align-items: baseline;
    gap: .6rem;
    flex: 0 0 auto;
}

.studio-header h1[b-v7peukx7sc] {
    margin: 0;
    font-size: 1.05rem;
    letter-spacing: .01em;
}

/* .studio-sub ("drawing surface · early") and .studio-back ("Map Builder →") went with the builder in
   #180. Both were markup this sheet is the only scope for, so nothing else can be reaching them. */

.studio-status[b-v7peukx7sc] {
    margin: 0;
    font-size: .74rem;
}

.studio-status.success[b-v7peukx7sc] { color: #7fc99a; }
.studio-status.error[b-v7peukx7sc] { color: #e08b7f; }

/* Which document is open and where saving lands (#439). Permanent rather than a status line, because
   #375 happened while the studio's behaviour was stated once, in passing, and then scrolled away. */
.studio-mode[b-v7peukx7sc] {
    display: inline-flex;
    align-items: baseline;
    gap: .45rem;
    padding: .12rem .5rem;
    border-radius: 999px;
    border: 1px solid transparent;
    font-size: .72rem;
    min-width: 0;
}

.studio-mode strong[b-v7peukx7sc] {
    font-weight: 600;
    white-space: nowrap;
}

/* The sentence yields before the label does: on a narrow window the author keeps "Adventure overlay",
   which is the half that says what they are about to change. */
.studio-mode > span[b-v7peukx7sc] {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    opacity: .8;
}

/* Warm for the one that reaches other people's adventures, cool for the two that do not. */
.studio-mode.master[b-v7peukx7sc] {
    border-color: #7a5a2e;
    background: #2c2417;
    color: #e0c48b;
}

.studio-mode.overlay[b-v7peukx7sc] {
    border-color: #2e5a7a;
    background: #17242c;
    color: #8bc4e0;
}

.studio-mode.owned[b-v7peukx7sc] {
    border-color: #3a3f47;
    background: #1b1e23;
    color: #9aa3ad;
}

/* --- the three-column workspace ------------------------------------------------------------- */

.studio-workspace[b-v7peukx7sc] {
    flex: 1 1 auto;
    min-height: 0;
    display: grid;
    grid-template-columns: 15rem minmax(0, 1fr) 13rem;
    gap: .6rem;
}

.studio-tools[b-v7peukx7sc],
.studio-library[b-v7peukx7sc] {
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: .55rem;
    padding-right: .2rem;
}

/* The card itself is StudioPanel's element now, so its chrome lives in StudioPanel.razor.css — a rule
   written here would be scoped to THIS component and match nothing. The rules below still work because
   Blazor appends the scope to the final selector only: `.tool-block label` becomes `.tool-block
   label[b-page]`, and the label is this page's markup even when the card around it is not. */

.tool-block label[b-v7peukx7sc] {
    display: flex;
    flex-direction: column;
    gap: .2rem;
    font-size: .7rem;
    color: #9aa6bf;
}

.tool-block input[b-v7peukx7sc],
.tool-block select[b-v7peukx7sc] {
    background: #0d1420;
    border: 1px solid #2c3852;
    border-radius: 6px;
    color: #e6ecf8;
    padding: .3rem .4rem;
    font-size: .78rem;
    min-width: 0;
}

.tool-block input:focus-visible[b-v7peukx7sc],
.tool-block select:focus-visible[b-v7peukx7sc] {
    outline: 2px solid #6f9ede;
    outline-offset: -1px;
}

.dim-fields[b-v7peukx7sc] {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: .4rem;
}

.tool-hint[b-v7peukx7sc] {
    margin: 0;
    font-size: .68rem;
    line-height: 1.45;
    color: #78849f;
}

.studio-secondary[b-v7peukx7sc] {
    background: #1b2740;
    border: 1px solid #33415f;
    border-radius: 7px;
    color: #cfdcf3;
    padding: .35rem .5rem;
    font-size: .74rem;
    cursor: pointer;
}

.studio-secondary:hover:not(:disabled)[b-v7peukx7sc] { background: #24334f; }
.studio-secondary:disabled[b-v7peukx7sc] { opacity: .5; cursor: default; }

/* --- tools ----------------------------------------------------------------------------------- */

.tool-tabs[b-v7peukx7sc] {
    display: grid;
    grid-auto-flow: column;
    gap: .3rem;
}

.tool-tabs button[b-v7peukx7sc] {
    background: #0f1726;
    border: 1px solid #2c3852;
    border-radius: 7px;
    color: #a9b6d2;
    padding: .32rem .3rem;
    font-size: .74rem;
    cursor: pointer;
}

.tool-tabs button:hover[b-v7peukx7sc] { background: #18233a; }

.tool-tabs button.active[b-v7peukx7sc] {
    background: #2b4570;
    border-color: #5b82c4;
    color: #eaf2ff;
}

.tool-grid[b-v7peukx7sc] {
    display: grid;
    /* Eleven tools. At 4.1rem the rail fit two columns, six rows and 181px -- which pushed the options
       for the tool you had just picked past the bottom of the window, the exact complaint this panel
       rework exists to answer (Dave, 2026-08-03). Every label is one short word, so three fit. */
    grid-template-columns: repeat(auto-fill, minmax(3.15rem, 1fr));
    gap: .3rem;
}

.tool-grid button[b-v7peukx7sc] {
    background: #0f1726;
    border: 1px solid #2c3852;
    border-radius: 7px;
    color: #a9b6d2;
    padding: .32rem .3rem;
    font-size: .74rem;
    cursor: pointer;
}

.tool-grid button:hover[b-v7peukx7sc] { background: #18233a; }

.tool-grid button.active[b-v7peukx7sc] {
    background: #2b4570;
    border-color: #5b82c4;
    color: #eaf2ff;
}

/* The three layers that decide what a square does: the picture, the room, the square. Shown together
   because the value being edited is meaningless on its own -- "difficult" over painted mud in a flooded
   room means something quite different from "difficult" over stone in no room at all. */
.terrain-chain[b-v7peukx7sc]{margin:0 0 .8rem;display:grid;gap:.3rem}
.chain-row[b-v7peukx7sc]{display:flex;gap:.6rem;margin:0;font-size:.76rem;color:#c3cbe6}
.chain-what[b-v7peukx7sc]{flex:0 0 9rem;color:#8593b3}
.chain-says[b-v7peukx7sc]{flex:1;min-width:0}
.chain-quiet .chain-says[b-v7peukx7sc]{color:#6d7a97}
/* Said where the row already is, because "why is this square a different colour?" is the question the
   author is holding when they open this. */
.chain-unpainted[b-v7peukx7sc]{display:block;font-style:normal;color:#e5b07a;font-size:.72rem}
/* The one sentence that says what is actually in force, after all three. */
.terrain-verdict[b-v7peukx7sc]{margin:.8rem 0 0;padding:.5rem .6rem;border-radius:8px;background:#131a2c;
    border:1px solid #303b57;font-size:.78rem;color:#e6ebf7}

/* What is selected, and the one destructive button in the rail. Sits where the Erase checkbox does in a
   drawing mode, so the card does not change height when you switch between them. */
.selection-bar[b-v7peukx7sc] {
    display: flex;
    align-items: center;
    gap: .4rem;
    min-height: 1.5rem;
}

.selection-what[b-v7peukx7sc] {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: .76rem;
    color: #e6ebf7;
}

/* Where the selection is (#215). Tabular figures so the numbers do not jitter as the pointer moves, and
   monospaced-feeling against the name beside it — a coordinate is data, not prose. */
.selection-where[b-v7peukx7sc] {
    flex: 0 0 auto;
    font-variant-numeric: tabular-nums;
    font-size: .7rem;
    color: #93a2c0;
    background: #171e2c;
    border: 1px solid #2b3448;
    border-radius: 5px;
    padding: .05rem .3rem;
}

.selection-none[b-v7peukx7sc] {
    flex: 1;
    font-size: .72rem;
    color: #6d7a97;
}

.selection-delete[b-v7peukx7sc] {
    background: #3a1f28;
    border: 1px solid #7a3a4a;
    border-radius: 6px;
    color: #f0b6c2;
    padding: .22rem .5rem;
    font-size: .72rem;
    cursor: pointer;
}

.selection-delete:hover[b-v7peukx7sc] { background: #4e2833; }

.erase-toggle[b-v7peukx7sc] {
    flex-direction: row !important;
    align-items: center;
    gap: .35rem !important;
    font-size: .72rem;
    cursor: pointer;
}

.erase-toggle input[b-v7peukx7sc] { width: auto; }

/* --- rooms ----------------------------------------------------------------------------------- */

.region-list[b-v7peukx7sc] {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .25rem;
}

.region-pick[b-v7peukx7sc] {
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: .4rem;
    background: #0f1726;
    border: 1px solid #2c3852;
    border-radius: 7px;
    color: #cfdcf3;
    padding: .3rem .45rem;
    font-size: .74rem;
    cursor: pointer;
    text-align: left;
}

.region-list li.active .region-pick[b-v7peukx7sc] {
    border-color: #5b82c4;
    background: #1d2a45;
}

.region-count[b-v7peukx7sc] {
    font-variant-numeric: tabular-nums;
    font-size: .66rem;
    color: #7e8caa;
}

/* --- a hazard the library asset owns (#439) -----------------------------------------------------
   The canvas fades the master's traps to 55%; the rail says the same thing in a word, because a list
   entry has no opacity to read against its neighbours until there IS a neighbour. Both are needed:
   an overlay may hold only the asset's hazards and nothing of its own. */

.region-list li.borrowed .region-pick[b-v7peukx7sc] {
    /* Matches READ_ONLY_ALPHA in read-only-layers.ts. The two say one thing and should look like it. */
    opacity: .55;
}

.region-owner[b-v7peukx7sc] {
    margin-left: auto;
    padding: 0 .3rem;
    border: 1px solid #46536e;
    border-radius: 3px;
    font-size: .6rem;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: #9fb0cc;
}

/* --- the encounter roster (#187) ---------------------------------------------------------------
   One row per creature, in participant order, and the number matches the one drawn on the token. The
   three states are visually distinct on purpose: which of these you can pick up is the whole rule. */

.foe-roster[b-v7peukx7sc] {
    list-style: none;
    margin: .4rem 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .2rem;
}

.foe-seat[b-v7peukx7sc] {
    display: flex;
    align-items: center;
    gap: .4rem;
    background: #0f1726;
    border: 1px solid #2c3852;
    border-radius: 7px;
    padding: .25rem .4rem;
    font-size: .72rem;
    color: #cfdcf3;
}

/* Grab, not pointer: the cursor is the only thing that says a row can be picked up before you try. */
.foe-seat[draggable="true"][b-v7peukx7sc] { cursor: grab; }

.foe-seat[draggable="true"]:active[b-v7peukx7sc] { cursor: grabbing; }

.foe-seat.placed[b-v7peukx7sc] { border-color: #8f5a55; background: #1d1414; }

.foe-seat.next[b-v7peukx7sc] { border-color: #5b82c4; background: #1d2a45; }

.foe-seat.waiting[b-v7peukx7sc] { opacity: .55; }

.foe-seat-number[b-v7peukx7sc] {
    flex: 0 0 auto;
    min-width: 1.2rem;
    text-align: center;
    font-variant-numeric: tabular-nums;
    font-size: .66rem;
    color: #9fb0ce;
}

.foe-seat-name[b-v7peukx7sc] {
    flex: 1 1 auto;
    /* The names are authored and can be long; the row must not push the rail wider than its column. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.foe-seat-state[b-v7peukx7sc] {
    flex: 0 0 auto;
    font-size: .62rem;
    color: #7e8caa;
}

.region-edit[b-v7peukx7sc] {
    display: flex;
    flex-direction: column;
    gap: .3rem;
    padding: .4rem .1rem .1rem;
}

.region-remove[b-v7peukx7sc] {
    background: #3a1f22;
    border: 1px solid #6b3239;
    border-radius: 7px;
    color: #e7b3b3;
    padding: .28rem .4rem;
    font-size: .72rem;
    cursor: pointer;
}

.region-remove:hover[b-v7peukx7sc] { background: #4a262a; }

/* A palette of colour, so it reads as paint rather than as a list of words. */
.material-grid[b-v7peukx7sc] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(1.5rem, 1fr));
    gap: .28rem;
}

.material-swatch[b-v7peukx7sc] {
    aspect-ratio: 1;
    border: 1px solid #00000055;
    border-radius: 5px;
    background: var(--swatch);
    cursor: pointer;
    padding: 0;
    box-shadow: inset 0 0 0 1px #ffffff12;
}

.material-swatch:hover[b-v7peukx7sc] { transform: scale(1.08); }

.material-swatch.active[b-v7peukx7sc] {
    outline: 2px solid #f4cb68;
    outline-offset: 1px;
}

/* --- layers ---------------------------------------------------------------------------------- */

.layer-list[b-v7peukx7sc] {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .1rem;
}

.layer-list li[b-v7peukx7sc] {
    display: flex;
    align-items: center;
    gap: .4rem;
    font-size: .74rem;
    color: #b3bfd8;
    padding: .12rem 0;
}

.layer-eye[b-v7peukx7sc] {
    background: none;
    border: 0;
    color: #6f7d99;
    cursor: pointer;
    font-size: .8rem;
    line-height: 1;
    padding: .1rem .15rem;
}

.layer-eye.on[b-v7peukx7sc] { color: #7fb0e8; }
.layer-eye.off[b-v7peukx7sc] { color: #4d566d; }
.layer-eye:focus-visible[b-v7peukx7sc] { outline: 2px solid #6f9ede; border-radius: 4px; }

/* --- the stage ------------------------------------------------------------------------------- */

.studio-stage[b-v7peukx7sc] {
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    gap: .45rem;
}

.studio-bar[b-v7peukx7sc] {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: .3rem;
    background: #141c2c;
    border: 1px solid #26314a;
    border-radius: 10px;
    padding: .35rem .45rem;
}

.studio-bar button[b-v7peukx7sc] {
    background: #0f1726;
    border: 1px solid #2c3852;
    border-radius: 7px;
    color: #b9c6e2;
    padding: .3rem .55rem;
    font-size: .76rem;
    cursor: pointer;
}

.studio-bar button:hover:not(:disabled)[b-v7peukx7sc] { background: #1c2840; }
.studio-bar button:disabled[b-v7peukx7sc] { opacity: .38; cursor: default; }

.studio-bar-gap[b-v7peukx7sc] { flex: 1 1 auto; }

.studio-zoom[b-v7peukx7sc] {
    font-variant-numeric: tabular-nums;
    font-size: .72rem;
    color: #8b98b6;
    min-width: 3rem;
    text-align: right;
}

.studio-dirty[b-v7peukx7sc] {
    font-size: .72rem;
    color: #f0c46a;
}

.studio-primary-action[b-v7peukx7sc] {
    background: #2f5c3a !important;
    border-color: #47895a !important;
    color: #e8ffee !important;
}

.studio-primary-action:hover:not(:disabled)[b-v7peukx7sc] { background: #376d45 !important; }

/* The island's home. Pixi sizes its canvas to this element, so it must have a real height of its own
   rather than being sized by the canvas it contains -- which would collapse to nothing. */
.studio-canvas[b-v7peukx7sc] {
    flex: 1 1 auto;
    min-height: 0;
    border: 1px solid #26314a;
    border-radius: 10px;
    overflow: hidden;
    background: #0b111d;
    position: relative;
}

/* ::deep because PixiJS creates this <canvas> at runtime and appends it to the host div, so it carries
   no scope attribute and `.studio-canvas canvas` compiled to a selector that could never match (#233).
   Without display:block the canvas is an inline box and sits on the text baseline, leaving a descender
   gap under it inside the host. */
.studio-canvas[b-v7peukx7sc]  canvas { display: block; }

/* --- library --------------------------------------------------------------------------------- */

.library-list[b-v7peukx7sc] {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .2rem;
}

.library-open[b-v7peukx7sc] {
    width: 100%;
    text-align: left;
    background: #0f1726;
    border: 1px solid #2c3852;
    border-radius: 7px;
    color: #cfdcf3;
    padding: .32rem .45rem;
    font-size: .74rem;
    cursor: pointer;
}

.library-open:hover:not(:disabled)[b-v7peukx7sc] { background: #1c2840; }
.library-open:disabled[b-v7peukx7sc] { opacity: .5; cursor: default; }

/* The delete beside each entry (#180). A fixed-width second column rather than a float, so a long map
   name shortens the button's neighbour instead of pushing it off the panel. */
.library-row[b-v7peukx7sc] {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: .2rem;
}

.library-delete[b-v7peukx7sc] {
    background: #0f1726;
    border: 1px solid #2c3852;
    border-radius: 7px;
    color: #8e9ab8;
    padding: .32rem .45rem;
    font-size: .74rem;
    line-height: 1;
    cursor: pointer;
}

.library-delete:hover:not(:disabled)[b-v7peukx7sc] { background: #3a1f24; border-color: #7a3a44; color: #f0c8ce; }
.library-delete:disabled[b-v7peukx7sc] { opacity: .5; cursor: default; }

/* What a resize pushed off the edge, and names this build has no item for. Both are things the author
   has to be TOLD — a square that is quietly no longer part of the map looks exactly like one that was
   never drawn. */
.studio-outside[b-v7peukx7sc] {
    margin: .5rem 0 0;
    padding: .45rem .5rem;
    border-radius: 7px;
    border: 1px solid #7a5a3a;
    background: #1d1a14;
    color: #e5b07a;
    font-size: .7rem;
    line-height: 1.45;
}

@media (max-width: 1100px) {
    .studio-workspace[b-v7peukx7sc] { grid-template-columns: 13rem minmax(0, 1fr); }
    .studio-library[b-v7peukx7sc] { display: none; }
}

/* Issue #173's importer. The report is not decoration: what a square grid could not hold has to be read
   before an adventure is built on the map, not discovered when a party walks through a missing wall. */
.studio-import[b-v7peukx7sc]{display:grid;gap:.3rem;margin-top:.6rem;font-size:.75rem;color:#8e9ab8}
.import-report[b-v7peukx7sc]{margin-top:.6rem;padding:.6rem;border-radius:8px;border:1px solid #303b57;background:#131a2c;font-size:.74rem}
.import-report.lossy[b-v7peukx7sc]{border-color:#7a5a3a}
.import-report p[b-v7peukx7sc]{margin:0 0 .35rem}
.import-report p:last-child[b-v7peukx7sc]{margin-bottom:0}
.import-loss[b-v7peukx7sc]{color:#e5b07a}
.import-note[b-v7peukx7sc]{color:#8e9ab8}

/* The "what should this import replace?" choice. Styled here rather than in ForgeDialog because dialog
   content keeps its CALLER's scope — Blazor's scoped CSS never crosses a component boundary, which this
   repo has learned the hard way more than once. */
.import-stakes[b-v7peukx7sc]{margin:0 0 .8rem;font-size:.82rem;color:#c3cbe6}
.import-choice[b-v7peukx7sc]{display:grid;gap:.6rem}
.import-choice label[b-v7peukx7sc]{display:grid;grid-template-columns:auto 1fr;gap:.6rem;align-items:start;
    padding:.7rem;border-radius:8px;border:1px solid #303b57;background:#131a2c;cursor:pointer}
.import-choice label:hover[b-v7peukx7sc]{border-color:#4a5a80}
.import-choice input[b-v7peukx7sc]{margin-top:.15rem}
.import-choice span[b-v7peukx7sc]{display:grid;gap:.25rem;font-size:.78rem;color:#8e9ab8;line-height:1.45}
.import-choice strong[b-v7peukx7sc]{color:#e6ebf7;font-size:.84rem}
/* The destructive half says so in the same place the eye already is, not in a warning box after it. */
.import-choice em[b-v7peukx7sc]{color:#e5b07a;font-style:normal}

/* The legend. Its swatches carry the SAME two letters the canvas draws, so an entry and its token on the
   map cannot say different things. */
.legend-list[b-v7peukx7sc]{list-style:none;margin:0;padding:0;display:grid;gap:.4rem;font-size:.74rem;color:#c3cbe6}
.legend-list li[b-v7peukx7sc]{display:flex;align-items:center;gap:.5rem}
.legend-token[b-v7peukx7sc]{flex:none;display:inline-flex;align-items:center;justify-content:center;
    width:1.6rem;height:1.6rem;border-radius:50%;font-size:.62rem;font-weight:600;color:#dff0d8;
    background:#243043;border:2px solid #8fbf7a}
.legend-token.spawn[b-v7peukx7sc]{border-color:#4fbf7a;background:#16321f}
.legend-token.foe[b-v7peukx7sc]{border-color:#e5877a;background:#331a18}
.legend-token.light[b-v7peukx7sc]{border-color:#e0d060;background:#332e14;color:#e0d060}
.legend-token.note[b-v7peukx7sc]{border-color:#a48fd8;background:#241d33}
.legend-empty[b-v7peukx7sc]{color:#8e9ab8}

/* Map objects (issue #196). The editor for one object is taller than any other zone's because an object
   is the only layer whose point is the numbers on it — a lock's difficulty by action. */
.object-edit[b-v7peukx7sc]{gap:.45rem}
.object-flags[b-v7peukx7sc]{display:grid;grid-template-columns:1fr 1fr;gap:.2rem .5rem;font-size:.72rem;color:#c3cbe6}
.object-flags label[b-v7peukx7sc]{display:flex;align-items:center;gap:.3rem;margin:0}
.object-dcs[b-v7peukx7sc]{display:grid;gap:.25rem;padding:.4rem;border-radius:6px;border:1px solid #303b57;background:#131a2c}
.object-dc-head[b-v7peukx7sc]{font-size:.68rem;text-transform:uppercase;letter-spacing:.05em;color:#8e9ab8}
.object-dc-row[b-v7peukx7sc]{display:flex;align-items:center;gap:.3rem}
.object-dc-row code[b-v7peukx7sc]{flex:1 1 auto;font-size:.7rem;color:#9fd4ff;overflow:hidden;text-overflow:ellipsis}
.object-dc-row input[type=number][b-v7peukx7sc]{width:3.4rem}
.object-dc-row select[b-v7peukx7sc]{flex:1 1 auto;min-width:0}

/* An object's own states (issue #201). Same rows as the difficulties above it, because they are the same
   gesture — a key, a value, and a way to take it away again. */
.object-states[b-v7peukx7sc]{display:grid;gap:.25rem;padding:.4rem;border-radius:6px;border:1px solid #303b57;background:#131a2c}
.object-states input[b-v7peukx7sc]{min-width:0}

/* The audit (issue #199). Sits in a 15rem rail, so every finding is prose that wraps and a row of
   coordinate buttons — no columns, no table, nothing that needs width the rail does not have. */
.audit-list[b-v7peukx7sc]{list-style:none;margin:.6rem 0 0;padding:0;display:grid;gap:.7rem}
.audit-group h4[b-v7peukx7sc]{margin:0 0 .3rem;font-size:.72rem;text-transform:uppercase;letter-spacing:.05em;color:#8e9ab8}
.audit-finding[b-v7peukx7sc]{margin-bottom:.45rem;padding:.45rem .5rem;border-radius:6px;font-size:.74rem;
    line-height:1.35;color:#c3cbe6;border-left:3px solid #303b57;background:#131a2c}
/* A warning is a rule the table will enforce that nobody can see, or something in the file that reaches
   no table at all. A note is true and worth knowing. They must not read alike. */
.audit-finding.warn[b-v7peukx7sc]{border-left-color:#e5b07a}
.audit-finding.note[b-v7peukx7sc]{border-left-color:#4c5a7d}
.audit-finding p[b-v7peukx7sc]{margin:0}
.audit-count[b-v7peukx7sc]{display:inline-block;min-width:1.4rem;margin-right:.3rem;padding:0 .3rem;border-radius:4px;
    background:#243043;color:#dfe6f7;font-weight:700;text-align:center}
.audit-cells[b-v7peukx7sc]{margin-top:.35rem !important;display:flex;flex-wrap:wrap;gap:.25rem}
.audit-cell[b-v7peukx7sc]{padding:.1rem .35rem;border-radius:4px;border:1px solid #3c4a6b;background:#1b2438;
    color:#9fd4ff;font-size:.7rem;font-family:ui-monospace,monospace;cursor:pointer}
.audit-cell:hover[b-v7peukx7sc]{border-color:#5ad1ff;color:#dff2ff}
.audit-more[b-v7peukx7sc]{align-self:center;font-size:.7rem;color:#8e9ab8}
.audit-clean[b-v7peukx7sc]{margin:.6rem 0 0;font-size:.74rem;color:#8fbf7a}
.audit-stale[b-v7peukx7sc]{margin:.5rem 0 0;font-size:.72rem;color:#e5b07a}

/* ---- AI Helper ------------------------------------------------------------------------------- */
/* The dialog's own chrome is ForgeDialog's; these are the controls inside it, and they are scoped
   here because scoped CSS never crosses a component boundary. */

.ai-helper-button[b-v7peukx7sc] { color: var(--forge-gold-ink); background: var(--forge-gold); border-color: var(--forge-gold); font-weight: 700; }
.ai-helper-button:disabled[b-v7peukx7sc] { opacity: .5; }

.ai-helper[b-v7peukx7sc] { display: grid; gap: .9rem; min-width: min(38rem, 80vw); }
.ai-prompt[b-v7peukx7sc] { display: grid; gap: .3rem; }
.ai-prompt textarea[b-v7peukx7sc] { width: 100%; resize: vertical; font: inherit; padding: .5rem; }

.ai-sections[b-v7peukx7sc] { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: .3rem .8rem; border: 1px solid var(--forge-border-nested); padding: .6rem .8rem; }
.ai-sections legend[b-v7peukx7sc] { padding: 0 .3rem; font-size: .74rem; letter-spacing: .06em; text-transform: uppercase; }
.ai-section[b-v7peukx7sc] { display: flex; align-items: center; gap: .4rem; cursor: pointer; }

.ai-model[b-v7peukx7sc] { display: grid; grid-template-columns: 1fr 1.4fr auto; align-items: end; gap: .6rem; }
.ai-model label[b-v7peukx7sc] { display: grid; gap: .3rem; }

.ai-context-note[b-v7peukx7sc] { font-size: .78rem; opacity: .8; margin: 0; }

.ai-result[b-v7peukx7sc] { display: grid; gap: .4rem; padding: .7rem; border: 1px solid var(--forge-border-nested); background: var(--forge-surface-1); }
.ai-narration[b-v7peukx7sc] { margin: 0; }
.ai-changes[b-v7peukx7sc], .ai-problems[b-v7peukx7sc] { margin: 0; padding-left: 1.1rem; }
/* Problems are stated, never hidden: an edit that half-worked must not read as a success. */
.ai-problems[b-v7peukx7sc] { color: var(--forge-danger-alt); }
.ai-touched[b-v7peukx7sc] { margin: 0; font-weight: 700; }
.ai-cost[b-v7peukx7sc] { margin: 0; font-size: .74rem; opacity: .7; }

@media (max-width: 700px) {
    .ai-model[b-v7peukx7sc] { grid-template-columns: 1fr; }
}

/* The Add-a-room button and the rooms-overlay eye on one line. The eye is the SAME setting as the one
   in the Layers panel — put here because this is where somebody thinking about rooms looks for it
   (Dave, 2026-08-05). */
.region-actions[b-v7peukx7sc] { display: flex; align-items: center; gap: .4rem; }
.region-actions .studio-secondary[b-v7peukx7sc] { flex: 1; }
/* The walls panel puts the kind buttons on this row so the eye sits beside them rather than below, where
   it would be a scroll away from the hand that is drawing. min-width:0 or the grid refuses to shrink and
   pushes the eye off the rail. */
.region-actions .tool-grid[b-v7peukx7sc] { flex: 1; min-width: 0; }

/* Independent of the tool and of Select/Freehand/Rectangle, and it survives all of them plus a reload.
   Sits under the mode tabs because it modifies what those modes do on a click. */
.popup-toggle[b-v7peukx7sc] {
    display: flex; align-items: center; gap: .45rem;
    margin: .5rem 0 0; font-size: .78rem; color: #b9c4dc; cursor: pointer;
}
.popup-toggle input[b-v7peukx7sc] { margin: 0; }

/* A passage in the rooms list (#217) — walkable floor that is not a place. Quiet, and the same cold grey
   the canvas draws it in, so the rail and the map agree at a glance. */
.region-kind[b-v7peukx7sc] {
    flex: 0 0 auto;
    font-size: .6rem;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: #8fa3c0;
    border: 1px solid #3a4559;
    border-radius: 4px;
    padding: 0 .25rem;
}

/* An authored contradiction, said where it was authored. */
.region-problem[b-v7peukx7sc] {
    color: #f0b6c2;
}

/* The controls a correction pass reaches for constantly, duplicated onto the toolbar (Dave, 2026-08-06).
   Deliberately the same shape as the rail's tool tabs so they read as the same control in two places
   rather than as two different things. */
.quick-tools[b-v7peukx7sc] {
    display: flex;
    gap: .2rem;
}

.quick-tools button[b-v7peukx7sc] {
    background: #0f1726;
    border: 1px solid #2c3852;
    border-radius: 7px;
    color: #a9b6d2;
    padding: .25rem .5rem;
    font-size: .74rem;
    cursor: pointer;
}

.quick-tools button:hover[b-v7peukx7sc] { background: #18233a; }

.quick-tools button.active[b-v7peukx7sc] {
    background: #2b4570;
    border-color: #5b82c4;
    color: #eaf2ff;
}

/* One line instead of a list of every room on the map. */
.region-chooser[b-v7peukx7sc] {
    display: flex;
    flex-direction: column;
    gap: .2rem;
    font-size: .7rem;
    color: #9aa6bf;
}

.region-chooser select[b-v7peukx7sc] {
    background: #0d1420;
    border: 1px solid #2c3852;
    border-radius: 6px;
    color: #e6ecf8;
    padding: .3rem .4rem;
    font-size: .78rem;
    max-width: 100%;
}

/* The terrains a map may be laid down for (#753).

   ⛔⛔ `.tool-block label` is `flex-direction: column` — every label in this rail stacks its control ABOVE
   its text, which is right for "Biome" over a select and catastrophic for a checkbox: the box rendered
   160px wide with the word wrapped underneath, so the ticked box for `forest` sat between `desert` and
   `forest` and read as if desert were ticked. Not untidy — WRONG, and an author would have marked the
   wrong terrain.

   ⚠️ The first attempt was a `.studio-check` class that existed in the markup and in no stylesheet at
   all, so it styled nothing and the column rule above went on winning. A class name is not a style.

   Two columns because there are seventeen of them, following `.object-flags`, which solved exactly this
   two hundred lines up and which this deliberately copies rather than reinvents. */
.studio-country-terrains[b-v7peukx7sc] {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: .2rem .5rem;
    border: 1px solid #2c3852;
    border-radius: 6px;
    padding: .5rem .6rem .6rem;
    margin: .5rem 0 0;
}

.studio-country-terrains legend[b-v7peukx7sc] { font-size: .7rem; color: #9aa6bf; padding: 0 .3rem; }

/* The legend's explanation and the disabled note speak for the whole set, not for one column of it. */
.studio-country-terrains .studio-hint[b-v7peukx7sc] { grid-column: 1 / -1; margin: 0 0 .2rem; }

.studio-country-terrains label[b-v7peukx7sc] {
    flex-direction: row;
    align-items: center;
    gap: .35rem;
    margin: 0;
    font-size: .72rem;
    color: #c3cbe6;
}

/* ⛔ `width: auto` and no growing: `.tool-block input` sets a full-width box for the text and select
   fields this rail is mostly made of, and a checkbox that obeys it is the whole bug. Same fix, and same
   reason, as `.erase-toggle input`. */
.studio-country-terrains input[type=checkbox][b-v7peukx7sc] { width: auto; flex: 0 0 auto; margin: 0; }
/* /Components/Pages/ForgeSettings.razor.rz.scp.css */
.settings-header[b-yjuthrzgwm],.save-bar[b-yjuthrzgwm]{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem}.settings-header[b-yjuthrzgwm]{margin-bottom:1rem}.settings-header h1[b-yjuthrzgwm]{margin:.1rem 0}.settings-header button[b-yjuthrzgwm],.save-bar button[b-yjuthrzgwm],.settings-card button[b-yjuthrzgwm]{color:var(--forge-gold-ink);background:var(--forge-gold);border:1px solid var(--forge-gold);padding:.55rem .8rem;font-weight:700;cursor:pointer}.settings-header button:disabled[b-yjuthrzgwm],.save-bar button:disabled[b-yjuthrzgwm],.settings-card button:disabled[b-yjuthrzgwm]{opacity:.55;cursor:wait}.settings-status[b-yjuthrzgwm]{position:sticky;top:.5rem;z-index:5;margin:1rem 0;padding:.75rem 1rem;border-left:3px solid var(--forge-success-border);background:var(--forge-success-surface)}.settings-status.error[b-yjuthrzgwm]{border-color:var(--forge-danger-border-alt);background:var(--forge-danger-surface)}.settings-notice[b-yjuthrzgwm]{display:grid;gap:.25rem;margin:1rem 0;padding:.85rem 1rem;border:1px solid var(--forge-border-panel);background:#201b14}.settings-notice span[b-yjuthrzgwm]{color:#c9bda9}.settings-notice code[b-yjuthrzgwm]{word-break:break-all;color:var(--forge-gold)}.settings-card[b-yjuthrzgwm]{display:grid;gap:1rem;margin:1rem 0;padding:1rem;border:1px solid var(--forge-border-card);border-radius:.35rem;background:var(--forge-surface-5)}.settings-card h2[b-yjuthrzgwm]{margin:.1rem 0;color:var(--forge-gold)}.settings-card p[b-yjuthrzgwm]{margin:.3rem 0;color:var(--forge-text-muted)}.field-grid[b-yjuthrzgwm],.secret-grid[b-yjuthrzgwm]{display:grid;gap:.8rem}.field-grid.two[b-yjuthrzgwm],.secret-grid[b-yjuthrzgwm]{grid-template-columns:repeat(2,minmax(0,1fr))}.settings-card label[b-yjuthrzgwm]{display:grid;gap:.3rem;color:var(--forge-text-label)}.settings-card input[b-yjuthrzgwm],.settings-card select[b-yjuthrzgwm]{min-width:0;padding:.5rem;color:var(--forge-text-input);background:var(--forge-surface-input);border:1px solid var(--forge-border-input)}.secret-field[b-yjuthrzgwm]{display:grid;gap:.55rem;padding:.8rem;border:1px solid #3d352a;background:var(--forge-surface-2)}.secret-field.compact[b-yjuthrzgwm]{grid-template-columns:minmax(12rem,.5fr) minmax(16rem,1fr) auto;align-items:center}.secret-field>div[b-yjuthrzgwm]{display:grid;gap:.25rem}.secret-state[b-yjuthrzgwm]{font-size:.8rem}.secret-state.configured[b-yjuthrzgwm]{color:var(--forge-success)}.secret-state.missing[b-yjuthrzgwm]{color:#e7a08f}.check[b-yjuthrzgwm]{display:flex!important;align-items:center;gap:.45rem!important}.check input[b-yjuthrzgwm]{min-width:auto}.apply-badge[b-yjuthrzgwm]{white-space:nowrap;padding:.2rem .5rem;border-radius:999px;font-size:.78rem}.apply-badge.live[b-yjuthrzgwm]{color:var(--forge-success);background:var(--forge-success-badge)}.apply-badge.restart[b-yjuthrzgwm]{color:#e8c27c;background:#382c14}table[b-yjuthrzgwm]{width:100%;border-collapse:collapse;font-size:.85rem}th[b-yjuthrzgwm],td[b-yjuthrzgwm]{text-align:left;border-bottom:1px solid #353027;padding:.5rem;vertical-align:top}td input[b-yjuthrzgwm],td select[b-yjuthrzgwm]{width:100%;box-sizing:border-box}td output[b-yjuthrzgwm]{display:block;padding:.5rem;color:var(--forge-text-label);font-variant-numeric:tabular-nums}.field-note[b-yjuthrzgwm]{display:block;max-width:24rem;margin-top:.25rem;color:var(--forge-text-muted)}.field-note.error[b-yjuthrzgwm]{color:#e7a08f}.muted[b-yjuthrzgwm]{display:block;padding:.5rem;color:#857b6c}.save-bar[b-yjuthrzgwm]{position:sticky;bottom:0;align-items:center;margin-top:1rem;padding:.8rem 1rem;border:1px solid var(--forge-border-panel);background:var(--forge-surface-1);box-shadow:0 -8px 20px #12110f}.save-bar span[b-yjuthrzgwm]{color:var(--forge-text-muted)}@media(max-width:900px){.settings-header[b-yjuthrzgwm],.section-heading[b-yjuthrzgwm],.save-bar[b-yjuthrzgwm]{display:grid}.field-grid.two[b-yjuthrzgwm],.secret-grid[b-yjuthrzgwm],.secret-field.compact[b-yjuthrzgwm]{grid-template-columns:1fr}}
.settings-card .check[b-yjuthrzgwm]{display:inline-flex!important;align-items:center;justify-content:flex-start;justify-self:start;width:fit-content;gap:.45rem!important;cursor:pointer}
.settings-card .check input[type="checkbox"][b-yjuthrzgwm]{flex:0 0 auto;width:1rem;height:1rem;min-width:1rem;margin:0;padding:0;accent-color:var(--forge-gold)}
/* /Components/Pages/ForgeStatus.razor.rz.scp.css */
/* Issue #377. Scoped to this page: the banner's own rules live with ForgeLayout, because a scoped rule
   cannot reach a child component and splitting them the other way is how a style silently stops applying. */

.status-header[b-rjxemgsntc] {
    margin-bottom: 1.5rem;
}

.status-header h1[b-rjxemgsntc] {
    margin: .25rem 0 .5rem;
}

.status-summary[b-rjxemgsntc] {
    padding: .65rem .9rem;
    border-radius: .4rem;
    font-weight: 600;
}

.status-summary.ok[b-rjxemgsntc] {
    background: rgba(60, 120, 70, .18);
    border: 1px solid rgba(60, 120, 70, .5);
}

.status-summary.bad[b-rjxemgsntc] {
    background: rgba(150, 45, 40, .18);
    border: 1px solid rgba(150, 45, 40, .6);
}

.status-list[b-rjxemgsntc] {
    list-style: none;
    margin: 1rem 0 0;
    padding: 0;
    display: grid;
    gap: .75rem;
}

.status-item[b-rjxemgsntc] {
    padding: .85rem 1rem;
    border-radius: .4rem;
    border: 1px solid rgba(255, 255, 255, .12);
    /* The left edge carries the verdict, so the list can be read down the side at a glance. */
    border-left-width: 4px;
}

.status-item.ok[b-rjxemgsntc] {
    border-left-color: rgba(90, 160, 100, .9);
}

.status-item.warning[b-rjxemgsntc] {
    border-left-color: rgba(210, 160, 60, .95);
}

.status-item.failed[b-rjxemgsntc] {
    border-left-color: rgba(190, 60, 50, 1);
    background: rgba(150, 45, 40, .1);
}

.status-line[b-rjxemgsntc] {
    display: flex;
    align-items: baseline;
    gap: .55rem;
}

.status-pip[b-rjxemgsntc] {
    font-weight: 700;
}

.status-state[b-rjxemgsntc] {
    margin-left: auto;
    font-size: .72rem;
    letter-spacing: .08em;
    opacity: .75;
}

.status-detail[b-rjxemgsntc] {
    margin: .4rem 0 0;
}

.status-remedy[b-rjxemgsntc] {
    margin: .45rem 0 0;
    /* The remedy is the point of the whole page, so it is never the quiet half of the entry. */
    opacity: .95;
}
/* /Components/Pages/ForgeUsage.razor.rz.scp.css */
.usage-header[b-6xrhq13gil] { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1.25rem; }
.usage-header h1[b-6xrhq13gil] { margin: .15rem 0; }

.usage-status[b-6xrhq13gil], .empty-state[b-6xrhq13gil], .filters[b-6xrhq13gil], .panel[b-6xrhq13gil], .drill[b-6xrhq13gil], .note[b-6xrhq13gil] { border: 1px solid #3f4652; border-radius: .55rem; background: #171b22; }
.usage-status[b-6xrhq13gil], .empty-state[b-6xrhq13gil], .note[b-6xrhq13gil] { padding: 1rem; margin: 1rem 0; }
.usage-status.error[b-6xrhq13gil] { border-color: #a94b55; color: #ffb8be; }
.note[b-6xrhq13gil] { color: #aeb7c4; font-size: .85rem; }
.note.warn[b-6xrhq13gil] { border-color: #8a6f37; color: #f4d58d; }

.filters[b-6xrhq13gil] { padding: 1rem; display: grid; gap: .85rem; }
.ranges[b-6xrhq13gil] { display: flex; align-items: center; flex-wrap: wrap; gap: .4rem; }
.ranges > span[b-6xrhq13gil] { color: #aeb7c4; margin-right: .35rem; }
.chip[b-6xrhq13gil] { padding: .3rem .75rem; border-radius: 999px; border: 1px solid #3f4652; background: #1f242d; cursor: pointer; }
.chip.active[b-6xrhq13gil] { background: #30405c; border-color: #5570a0; }
.range-detail[b-6xrhq13gil] { display: flex; flex-wrap: wrap; gap: 1rem; }
.range-detail label[b-6xrhq13gil] { display: grid; gap: .3rem; min-width: 11rem; }
.range-detail input[b-6xrhq13gil], .range-detail select[b-6xrhq13gil] { min-height: 2.25rem; }

.drill[b-6xrhq13gil] { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .6rem 1rem; margin: 1rem 0; }

.totals[b-6xrhq13gil] { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: .6rem; margin: 1rem 0; }
.totals article[b-6xrhq13gil] { border: 1px solid #3f4652; border-radius: .55rem; background: #171b22; padding: .8rem .9rem; }
.totals article.headline[b-6xrhq13gil] { border-color: #5570a0; background: #1a2130; }
.totals .label[b-6xrhq13gil] { margin: 0; color: #aeb7c4; font-size: .78rem; text-transform: uppercase; letter-spacing: .04em; }
.totals .figure[b-6xrhq13gil] { margin: .3rem 0 .15rem; font-size: 1.45rem; font-weight: 600; }
.totals .sub[b-6xrhq13gil] { margin: 0; color: #8f98a6; font-size: .78rem; }

.panel[b-6xrhq13gil] { margin: 1rem 0; padding: 1rem; }
.panel > header[b-6xrhq13gil] { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-bottom: .75rem; }
.panel > header h2[b-6xrhq13gil] { margin: 0; font-size: 1.05rem; }
.panel > header span[b-6xrhq13gil] { color: #8f98a6; font-size: .82rem; }
.panel > header label[b-6xrhq13gil] { display: flex; align-items: center; gap: .45rem; }
.panel .empty[b-6xrhq13gil] { color: #8f98a6; margin: .5rem 0 0; }

table[b-6xrhq13gil] { width: 100%; border-collapse: collapse; font-size: .85rem; display: block; overflow-x: auto; }
thead th[b-6xrhq13gil] { text-align: left; padding: .4rem .55rem; border-bottom: 1px solid #3f4652; color: #aeb7c4; font-weight: 500; white-space: nowrap; }
tbody td[b-6xrhq13gil] { padding: .42rem .55rem; border-bottom: 1px solid #262c35; vertical-align: middle; }
tbody tr:last-child td[b-6xrhq13gil] { border-bottom: none; }
tbody tr:hover[b-6xrhq13gil] { background: #1c212a; }
.num[b-6xrhq13gil] { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.strong[b-6xrhq13gil] { font-weight: 600; }
.bad[b-6xrhq13gil] { color: #ffb8be; }
.detail[b-6xrhq13gil] { display: block; color: #8f98a6; font-size: .75rem; }
code[b-6xrhq13gil] { font-size: .78rem; }
time[b-6xrhq13gil] { color: #aeb7c4; white-space: nowrap; }

.share[b-6xrhq13gil] { position: relative; min-width: 7rem; }
.share .bar[b-6xrhq13gil] { position: absolute; left: .55rem; top: 50%; transform: translateY(-50%); height: .55rem; background: #3f5c8a; border-radius: 999px; max-width: calc(100% - 3.6rem); }
.share .pct[b-6xrhq13gil] { position: relative; float: right; font-variant-numeric: tabular-nums; color: #aeb7c4; }

.pill[b-6xrhq13gil] { display: inline-block; padding: .15rem .5rem; border-radius: 999px; font-size: .72rem; white-space: nowrap; }
.pill.open[b-6xrhq13gil] { background: #30405c; }
.pill.closed[b-6xrhq13gil] { background: #3a3f48; color: #c6cdd8; }
.pill.ok[b-6xrhq13gil] { background: #35502f; }
.pill.warn[b-6xrhq13gil] { background: #5c4930; }
.pill.bad[b-6xrhq13gil] { background: #673840; color: #ffd7db; }

@media (max-width: 760px) {
    .usage-header[b-6xrhq13gil] { align-items: stretch; flex-direction: column; }
    .range-detail[b-6xrhq13gil] { flex-direction: column; }
}

/* Price book editor (issue #42). The table already scrolls horizontally; these only style the parts
   the read-only version never had - the two sub-headings, the inputs, and the validation list. */
.panel h3[b-6xrhq13gil] { margin: 1.25rem 0 .35rem; font-size: .95rem; color: #dfe5ee; }
.panel h3:first-of-type[b-6xrhq13gil] { margin-top: .5rem; }

.panel input[b-6xrhq13gil], .panel select[b-6xrhq13gil] { background: #10141a; color: #e7ecf3; border: 1px solid #3f4652; border-radius: .35rem; padding: .3rem .4rem; font: inherit; font-size: .82rem; }
.panel input:focus-visible[b-6xrhq13gil], .panel select:focus-visible[b-6xrhq13gil] { outline: 2px solid #6f9ad6; outline-offset: 1px; }
.panel td input[type="number"][b-6xrhq13gil] { width: 7rem; text-align: right; font-variant-numeric: tabular-nums; }
.panel td input:not([type="number"])[b-6xrhq13gil] { width: 11rem; }

.price-errors[b-6xrhq13gil] { margin: 1rem 0; padding: .75rem 1rem .75rem 2rem; border: 1px solid #673840; border-radius: .55rem; background: #241a1d; color: #ffd7db; font-size: .85rem; }
.price-errors li + li[b-6xrhq13gil] { margin-top: .3rem; }
/* /Components/Pages/ForgeVariantStudio.razor.rz.scp.css */
/* ⛔ ISSUE #656. This page shipped with NO stylesheet at all, and eleven of the thirteen class names
   its markup renders were declared by nothing it could reach. Nothing errors when a class matches
   nothing: `sandbox-header`, `drawer-section`, `roster-editor-row` and the rest simply drew the
   browser's defaults inside the Forge's chrome and read as a deliberately plain page.

   ⭐⭐ WHERE THE NAMES CAME FROM, AND WHY NOBODY HAD TO CHOOSE A LOOK. #656 was filed asking somebody
   to open the page and decide between writing rules and dropping the names, because two thirds of it
   only renders after a live LLM call and designing that half blind is how a mop-up turns into three
   new tickets. It turned out not to be a design question at all. The Variant Studio landed on
   2026-07-24 (d447ac25) as a copy of the ForgeSandbox tool-drawer's markup, and ForgeSandbox.razor.css
   declared TEN of the eleven names. Blazor isolates component styles, so the rules never crossed even
   while that page existed; then 4aa58f29 deleted ForgeSandbox and its sheet on 2026-07-31 and took the
   only copy of the intended look with it.

   So these rules are RECOVERED, not invented — `git show 4aa58f29^:DungeonMaster.Web/Components/Pages/
   ForgeSandbox.razor.css` is the source, and the literal colours are kept exactly as they were rather
   than re-expressed in the Forge's newer custom properties, so the restoration stays checkable against
   that file byte for byte.

   ⚠️ SIX DELIBERATE DEVIATIONS, each because this page's markup differs from the drawer's, each named
   so the next person can tell a repair from a drift:
     1. .roster-editor-row was `auto minmax(0,1fr) auto auto` for a drawer row carrying an add and a
        remove button. A proposal row has two children, so the two trailing columns named nothing.
     2. .library-filters was `1fr 8rem` for a search box beside a kind filter. This page's filter is
        the search box alone.
     3. .library-preview was `auto minmax(0,1fr)` for an avatar beside a name block. This page draws no
        avatar.
     4. .map-fields was `repeat(3,1fr)` for three fields. This page has two, and auto-fit lets them sit
        side by side and fold on their own rather than leaving a third of the row empty.
     5. .drawer-primary carried `width:100%`, which is right inside a `min(44rem,94vw)` drawer and
        absurd across a full-width page. Dropped; the measure comes back as a max-width on the labels.
     6. The drawer's `!important`s existed to beat `.drawer-section label` and `.drawer-section button`
        in that same sheet's own cascade. The same two rules are here, so the same fights are won with
        specificity instead — #124 refused a promotion in this codebase precisely because it would have
        needed a new `!important`, and there is no reason to add one where a selector will do. */

/* The page head. `.eyebrow` is the quieter half of #656: the coverage guard counts it as covered
   because app.css declares `.login-shell .eyebrow`, and a name declared under a root this page is not
   inside reaches nothing. It was drawing as an ordinary paragraph. */
.sandbox-header[b-sqz7gk0cyi] { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem;
    margin-bottom: 1rem; }
.sandbox-header h1[b-sqz7gk0cyi] { margin: .12rem 0; color: var(--forge-gold-bright); font-size: 2rem; }
.sandbox-header p[b-sqz7gk0cyi] { max-width: 52rem; margin: .3rem 0; color: var(--forge-text-muted); }
.eyebrow[b-sqz7gk0cyi] { margin: 0; color: #c99543; font: 700 .68rem/1.2 ui-monospace, monospace;
    letter-spacing: .13em; }

/* One numbered step of the workflow. The h3 rule is what makes "1 · Choose a base creature" read as a
   step heading rather than as a second page title. */
.drawer-section[b-sqz7gk0cyi] { margin-bottom: 1rem; padding: 1rem; border: 1px solid #40362a; background: #1b1813; }
.drawer-section > p[b-sqz7gk0cyi] { color: #9f9586; line-height: 1.45; }
.drawer-section h3[b-sqz7gk0cyi] { margin: 0; color: #dcb96f; font-size: .85rem; text-transform: uppercase;
    letter-spacing: .06em; }
/* Deviation 5's other half: the caption sits ABOVE its control, and the control stops at the measure
   the drawer these rules were written for actually had. */
.drawer-section label[b-sqz7gk0cyi] { display: grid; gap: .35rem; max-width: 44rem; }
.drawer-section input[b-sqz7gk0cyi],
.drawer-section select[b-sqz7gk0cyi],
.drawer-section textarea[b-sqz7gk0cyi] { box-sizing: border-box; width: 100%; min-width: 0; padding: .55rem;
    color: #eee4d5; border: 1px solid #584a36; background: #100e0b; font: inherit; }
.drawer-section button[b-sqz7gk0cyi] { color: #d9c7aa; border: 1px solid #655238;
    background: var(--forge-surface-notice); padding: .45rem .6rem; cursor: pointer; }

/* app.css declares a bare `.empty-note` in the play surface's blue-greys, and a bare selector reaches
   everything. Put it back in the Forge's palette here, where the scoped attribute wins on weight
   whatever the link order is. */
.empty-note[b-sqz7gk0cyi] { color: #8a8073; font-style: italic; }

/* Step 1 — the creature library. */
.library-filters[b-sqz7gk0cyi] { display: grid; grid-template-columns: 1fr; gap: .55rem; margin-bottom: .65rem; }
.library-results[b-sqz7gk0cyi] { display: grid; gap: .4rem; max-height: 26rem; overflow: auto; }
.library-result[b-sqz7gk0cyi] { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
    border: 1px solid #383027; background: #15120f; }
/* Specificity rather than the drawer's `!important`: this has to beat `.drawer-section button` above,
   and (0,2,0) does it. */
.drawer-section button.library-preview[b-sqz7gk0cyi] { display: grid; grid-template-columns: minmax(0, 1fr);
    gap: .65rem; align-items: center; padding: .55rem; text-align: left; border: 0;
    background: transparent; }
.library-preview > div[b-sqz7gk0cyi] { display: grid; }
.library-preview strong[b-sqz7gk0cyi] { color: #e5d6bd; }
.library-preview small[b-sqz7gk0cyi] { color: #9b917f; }
.library-preview em[b-sqz7gk0cyi] { color: #6f685e; font-size: .63rem; font-style: normal; }

/* ⚠️ NOT recovered either, and it could not have been: the drawer these rules came from shipped the
   WHOLE library and filtered it in the browser, so it had nothing to be honest about. #800 made this
   list a capped server-side search, and a capped list that reads as a complete one is the one thing it
   must never do. Same words and same job as ForgeArena's `.arena-hint`, kept here rather than promoted
   into forge.css because two rules is not yet a shared pattern (#124's rule on promotion). */
.library-hint[b-sqz7gk0cyi] { margin: .45rem 0 0; color: #8a8073; font-size: .72rem; }

/* ⛔ NOT recovered — the one rule here with no ancestor, and it is a fault of its own rather than a
   taste call. The drawer's `.library-preview` was an ADD button: press it and the creature joined the
   roster, so there was no such thing as the selected one. This page's is a SELECTOR — the whole of
   step 2 is disabled until `_selectedCreatureId` is set — and it renders `active` for it. `active` is
   declared under `.world-section-nav` and `.rail-list` in forge.css and under `.initiative-list` in
   app.css, none of which this button is inside, so choosing a creature changed nothing on screen. The
   tokens are the ones the Forge already says "this row is the chosen one" with (.picker-grid
   tbody tr.selected). Filed as its own issue rather than folded in silently. */
.drawer-section button.library-preview.active[b-sqz7gk0cyi] { background: var(--forge-surface-selected);
    box-shadow: inset 3px 0 0 var(--forge-gold); }

/* Step 2 — the ask. */
.drawer-primary[b-sqz7gk0cyi] { border: 1px solid #d1a955; color: #18130b; background: #d1a955; padding: .65rem .9rem;
    font-weight: 800; cursor: pointer; }

/* Step 3 — one proposal. */
.roster-editor-row[b-sqz7gk0cyi] { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .6rem;
    align-items: center; padding: .55rem 0; border-bottom: 1px solid #332c24; }
.roster-editor-row > div[b-sqz7gk0cyi] { display: grid; }
.roster-editor-row small[b-sqz7gk0cyi] { color: #8e8578; }
/* Beats `.drawer-section label` above on weight, which is what the drawer's two `!important`s were
   for: a checkbox and its words on one line, not stacked. */
.drawer-section label.automation-toggle[b-sqz7gk0cyi] { display: flex; align-items: center; gap: .3rem;
    max-width: none; font-size: .72rem; }
.automation-toggle input[b-sqz7gk0cyi] { width: auto; }

/* ⚠️ RENAMED, and the old name was the reason this one sat undecided. The markup said
   `status-error` — a name for the page's STATUS LINE, borrowed for a validator's complaint about one
   proposal — and the open question was whether it should therefore take `.editor-status`, which would
   give every violation its own padded, bordered box inside a row. It should not: it is inline text
   about the line it sits on, so it says what it is now and takes the colour the rest of the Forge
   says "this one cannot go through" in. */
.proposal-violation[b-sqz7gk0cyi] { display: block; color: var(--forge-danger); }

/* Step 4 — where the approved variants land. */
.map-fields[b-sqz7gk0cyi] { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: .55rem; }

/* ⚠️ LAST, and that is load-bearing. A media query adds no specificity, so a base rule restated below
   one of these overrides wins at every width and the fold silently stops happening. */
@media (max-width: 560px) {
    .library-filters[b-sqz7gk0cyi],
    .map-fields[b-sqz7gk0cyi] { grid-template-columns: 1fr; }
}
/* /Components/Pages/ForgeWorlds.razor.rz.scp.css */
.world-editor-header[b-6k87f5k96r], .nested-card-heading[b-6k87f5k96r] { display: flex; align-items: start; justify-content: space-between; gap: 1rem; }
.world-editor-actions[b-6k87f5k96r], .field-grid[b-6k87f5k96r], .placement-row[b-6k87f5k96r], .table-row[b-6k87f5k96r] { display: grid; gap: .75rem; }
.world-editor-actions[b-6k87f5k96r] { grid-template-columns: repeat(3, auto); }
.forge-editor-card[b-6k87f5k96r] { display: grid; gap: 1rem; margin: 1rem 0; padding: 1.1rem; border: 1px solid var(--forge-border-editor); border-radius: .4rem; background: var(--forge-surface-6); }
.field-grid.two[b-6k87f5k96r] { grid-template-columns: repeat(2, minmax(12rem, 1fr)); }
/* Three across, for the world character station: a map id and two small coordinates read as one
   answer, and .two left the pair of numbers stretched across half the dialog each. */
.field-grid.three[b-6k87f5k96r] { grid-template-columns: minmax(12rem, 2fr) minmax(6rem, 1fr) minmax(6rem, 1fr); }
/* .field-grid.four is in forge.css (#191); this page's 900px fold below still outweighs it. */
.field-grid.grow[b-6k87f5k96r] { flex: 1; }
.forge-editor-card label[b-6k87f5k96r], .locale-row label[b-6k87f5k96r], .route-row label[b-6k87f5k96r], .adventure-row label[b-6k87f5k96r], .encounter-entry label[b-6k87f5k96r] { display: grid; gap: .3rem; color: var(--forge-text-label); }
.forge-editor-card input[b-6k87f5k96r], .forge-editor-card select[b-6k87f5k96r], .forge-editor-card textarea[b-6k87f5k96r] { min-width: 0; color: var(--forge-text-input); background: var(--forge-surface-input); border: 1px solid var(--forge-border-input); padding: .5rem; }
.forge-editor-card button[b-6k87f5k96r], .world-editor-actions button[b-6k87f5k96r] { width: fit-content; color: var(--forge-text); background: var(--forge-surface-button); border: 1px solid var(--forge-border-quiet); padding: .5rem .75rem; cursor: pointer; }
.world-editor-actions .primary[b-6k87f5k96r], .ai-authoring-card .primary[b-6k87f5k96r] { color: var(--forge-gold-ink); background: var(--forge-gold); border-color: var(--forge-gold); font-weight: 700; }
button.danger[b-6k87f5k96r] { color: var(--forge-danger); border-color: var(--forge-danger-border); }
.editor-nested-card[b-6k87f5k96r] { display: grid; gap: .8rem; padding: .9rem; border: 1px solid var(--forge-border-nested); background: var(--forge-surface-1); }

/* ⛔ #122. Two class names this page's MARKUP uses that this page's SHEET never declared — nineteen
   `label.stacked` and fifteen `.muted` in ForgeWorlds.razor. ForgeAdventures.razor.css declares both,
   so the studio looked right and the world editor did not, and no rule "stopped applying": there was
   never a rule to stop. Nothing errors when a class matches nothing, which is why this survived.

   ⚠️ #122's recorded diagnosis — "a missing box-sizing stops the world page's inputs filling their
   grid cell" — is WRONG, and this is what it was actually seeing. Measured in Chrome at 1280px, with
   this page's rules and the studio's rules rendered side by side:
     · inside .forge-editor-card, a world input measures 479.9px in a 479.9px cell — it already fills
       it, exactly as the studio's does. `label { display: grid }` makes the control a grid item and a
       grid item stretches, and Chrome's UA sheet already computes `box-sizing: border-box` for form
       controls. Adding either changes nothing there.
     · inside a ForgeDialog, `label.stacked` computed to `display: block` and the input measured
       177.0px — the browser's default `size=20` — in a 700.8px label. The studio's identical dialog
       measured 700.8px. THAT is the field that does not fill, and it is this rule that fixes it.
   `.muted` computed to rgb(231,234,242) at 16px here against rgb(143,131,114) at 13.6px in the studio:
   every "no regions yet" hint on this page was rendering as ordinary body copy.

   Stated here rather than promoted to forge.css on purpose. Both names are generic — `.muted` is used
   by nine components including /play's home page, `stacked` reaches whatever a future page calls
   stacked — so an unscoped rule is one rename away from styling somebody else's markup. That is #124's
   problem (primitives with no majority), not this fix's. Written character-for-character as
   ForgeAdventures states them, so the drift guard sees agreement rather than a new divergence. */
label.stacked[b-6k87f5k96r] { display: grid; gap: .3rem; }
.muted[b-6k87f5k96r] { color: var(--forge-text-faint); font-size: .85rem; }

.locale-list[b-6k87f5k96r] { display: grid; gap: .5rem; }
/* The area tier (issue #356). Inset and quieter than the region card it sits inside, because the nesting
   IS the information: a locale in an area and a locale in a region look identical otherwise, and which
   one you are looking at decides what an encounter table can scope itself to. */
.area-list[b-6k87f5k96r] { display: grid; gap: .6rem; }
.area-card[b-6k87f5k96r] { margin-left: 1rem; background: var(--forge-surface-2, var(--forge-surface-1)); }
.area-card h4[b-6k87f5k96r] { margin: 0; font-size: .95rem; }
.locale-row[b-6k87f5k96r], .route-row[b-6k87f5k96r], .adventure-row[b-6k87f5k96r], .encounter-entry[b-6k87f5k96r] { display: grid; grid-template-columns: 1.1fr 1.4fr .55fr .55fr 1.3fr auto; align-items: end; gap: .55rem; padding: .65rem; border: 1px solid #3e3529; }
.route-row[b-6k87f5k96r] { grid-template-columns: 1.1fr 1fr 1fr .55fr 1.2fr 1fr auto; }
.adventure-row[b-6k87f5k96r] { grid-template-columns: 1fr 1.4fr 1fr auto; }
.placement-row[b-6k87f5k96r] { grid-template-columns: 1fr 1fr 1.6fr auto; align-items: end; }
.table-row[b-6k87f5k96r] { grid-template-columns: 1.4fr 1fr .7fr .7fr auto; align-items: end; }
.encounter-entry[b-6k87f5k96r] { grid-template-columns: 1fr .55fr 1fr 2fr auto; margin: .6rem 0; }
.encounter-entry .span-2[b-6k87f5k96r] { grid-column: span 2; }
.locale-row.selected[b-6k87f5k96r], .route-row.selected[b-6k87f5k96r] { border-color: var(--forge-gold); }
.graph-scroll[b-6k87f5k96r] { overflow: auto; padding-bottom: .35rem; }
.draft-state[b-6k87f5k96r] { margin: 0; }
.export-controls[b-6k87f5k96r] { display: grid; grid-template-columns: minmax(15rem, 1fr) auto auto; align-items: end; gap: .75rem; }
.check-label[b-6k87f5k96r] { display: flex !important; align-items: center; }
.check-label input[b-6k87f5k96r] { flex: 0 0 auto; width: 1rem; height: 1rem; min-width: 1rem; margin: 0; padding: 0; accent-color: var(--forge-gold); }
.export-unavailable[b-6k87f5k96r] { margin: 0; color: #d9a58f; }
.publish-blockers[b-6k87f5k96r] { padding: .8rem; color: var(--forge-danger); border: 1px solid var(--forge-danger-border); background: #211411; }
.publish-blockers code[b-6k87f5k96r] { color: var(--forge-gold); }

/* ⭐ What this publish would do to the version it replaces (#922). Deliberately NOT .publish-blockers:
   a diff is not a blocker — the common case is "3 added, 12 changed, nothing removed", which is
   reassurance and must not be dressed as an error. The DANGEROUS variant borrows the blocker's colours,
   because that case genuinely is one. */
.publish-diff[b-6k87f5k96r] { padding: .8rem; border: 1px solid var(--forge-rule); background: #16130f; }
.publish-diff code[b-6k87f5k96r] { color: var(--forge-gold); }
.publish-diff ul[b-6k87f5k96r] { margin: .4rem 0 0; padding-left: 1.2rem; }
.publish-diff li[b-6k87f5k96r] { margin: .15rem 0; }
.publish-diff .diff-warning[b-6k87f5k96r] { margin: .5rem 0 0; }
.publish-diff.dangerous[b-6k87f5k96r] { color: var(--forge-danger); border-color: var(--forge-danger-border); background: #211411; }
/* Not shared: this page's sticky offset is its own. The padding/border/background tail and the
   .error/.success colours are in forge.css (#191, #313). */
.editor-status[b-6k87f5k96r] { position: sticky; top: .5rem; z-index: 5; }
.invalid[b-6k87f5k96r] { color: var(--forge-danger-alt); border-color: var(--forge-danger-border); }
.valid[b-6k87f5k96r] { color: var(--forge-success); border-color: var(--forge-success-border-alt); }
.subsection[b-6k87f5k96r] { margin-top: .5rem; }
.ai-authoring-controls[b-6k87f5k96r] { display: grid; grid-template-columns: minmax(10rem, .35fr) minmax(20rem, 1fr) auto; align-items: end; gap: .75rem; }
.ai-description textarea[b-6k87f5k96r] { resize: vertical; }
.ai-note[b-6k87f5k96r] { margin: 0; color: #d9a58f; }
.ai-failure[b-6k87f5k96r] { padding: .8rem; border: 1px solid var(--forge-danger-border); background: #211411; color: var(--forge-danger); }
.ai-failure pre[b-6k87f5k96r] { max-height: 20rem; overflow: auto; padding: .75rem; white-space: pre-wrap; color: #e7ded0; background: #100d0a; }
.ai-failure code[b-6k87f5k96r] { color: var(--forge-gold); }
.world-context-header[b-6k87f5k96r] { position: sticky; top: 0; z-index: 8; display: grid; grid-template-columns: minmax(16rem, 1fr) minmax(13rem, .65fr) auto auto; align-items: center; gap: 1rem; margin: -1rem -1rem 1rem; padding: .9rem 1rem; border: 1px solid #5a4932; border-radius: .4rem; background: #15120ff2; box-shadow: 0 10px 30px #08060499; backdrop-filter: blur(8px); }
.world-context-title a[b-6k87f5k96r] { color: #bfa36f; font-size: .8rem; text-decoration: none; }
.world-context-title .eyebrow[b-6k87f5k96r] { margin: .25rem 0 0; }
.world-context-title h1[b-6k87f5k96r] { margin: .1rem 0; color: var(--forge-gold-bright); font-size: 1.45rem; }
.world-context-title p:last-child[b-6k87f5k96r] { margin: 0; color: var(--forge-text-dim); font-size: .78rem; }
.world-switcher[b-6k87f5k96r] { display: grid; gap: .3rem; color: var(--forge-text-label); font-size: .8rem; }
.world-switcher select[b-6k87f5k96r] { min-width: 0; width: 100%; box-sizing: border-box; padding: .55rem; color: var(--forge-text-input); background: var(--forge-surface-input); border: 1px solid #806a49; }
.world-save-state[b-6k87f5k96r] { display: grid; gap: .18rem; min-width: 8.5rem; }
.world-save-state strong[b-6k87f5k96r] { color: var(--forge-text); }
.world-save-state .valid[b-6k87f5k96r] { color: var(--forge-success); }
.world-save-state .invalid[b-6k87f5k96r] { color: var(--forge-danger-alt); }
.world-editor-actions[b-6k87f5k96r] { display: flex; flex-wrap: wrap; justify-content: flex-end; }
.world-editor-actions button:disabled[b-6k87f5k96r] { opacity: .5; cursor: default; }
.primary-link[b-6k87f5k96r] { display: inline-flex; align-items: center; width: fit-content; padding: .55rem .75rem; color: var(--forge-gold-ink); background: var(--forge-gold); border: 1px solid var(--forge-gold); font-weight: 700; text-decoration: none; }
.world-section-nav > p[b-6k87f5k96r] { margin: .55rem .5rem .25rem; color: var(--forge-text-eyebrow); font-size: .68rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; }
.world-section-nav .future span[b-6k87f5k96r]::after { content: "Soon"; margin-left: .4rem; padding: .1rem .3rem; color: #b7a98f; background: var(--forge-surface-chip); border-radius: 999px; font-size: .58rem; text-transform: uppercase; }
.world-section-content[b-6k87f5k96r] { min-width: 0; }
.world-section-content > .forge-editor-card:first-of-type[b-6k87f5k96r] { margin-top: 0; }
.world-metrics[b-6k87f5k96r] { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: .65rem; }
.world-metrics article[b-6k87f5k96r] { display: grid; gap: .2rem; padding: .8rem; border: 1px solid #40372c; background: #12100d; }
.world-metrics strong[b-6k87f5k96r] { color: var(--forge-gold-bright); font-size: 1.45rem; }
.world-metrics span[b-6k87f5k96r] { color: var(--forge-text-dim); font-size: .75rem; }
.pack-identity input[readonly][b-6k87f5k96r] { color: #9f9585; background: #1b1814; }
.future-section[b-6k87f5k96r] { min-height: 14rem; align-content: center; }
.library-actions[b-6k87f5k96r] { display: flex; flex-wrap: wrap; align-items: center; gap: .8rem; }
.library-actions span[b-6k87f5k96r] { color: var(--forge-text-dim); font-size: .8rem; }
.revision-list[b-6k87f5k96r] { display: grid; gap: .55rem; }
.revision-list article[b-6k87f5k96r] { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .75rem; border: 1px solid #40372c; background: #12100d; }
.revision-list article.current[b-6k87f5k96r] { border-color: #6f8e5d; }
.revision-list article div[b-6k87f5k96r] { display: grid; gap: .15rem; }
.revision-list article span[b-6k87f5k96r] { color: var(--forge-text-dim); font-size: .76rem; }
.revision-list .current-badge[b-6k87f5k96r] { padding: .2rem .5rem; color: var(--forge-success); background: var(--forge-success-badge); border-radius: 999px; }
@media (max-width: 900px) {
    .field-grid.four[b-6k87f5k96r], .field-grid.three[b-6k87f5k96r], .field-grid.two[b-6k87f5k96r], .locale-row[b-6k87f5k96r], .route-row[b-6k87f5k96r], .adventure-row[b-6k87f5k96r], .placement-row[b-6k87f5k96r], .table-row[b-6k87f5k96r], .encounter-entry[b-6k87f5k96r] { grid-template-columns: 1fr; }
    .world-editor-header[b-6k87f5k96r], .section-heading[b-6k87f5k96r], .nested-card-heading[b-6k87f5k96r] { display: grid; }
    .world-editor-actions[b-6k87f5k96r], .export-controls[b-6k87f5k96r], .ai-authoring-controls[b-6k87f5k96r] { grid-template-columns: 1fr; }
    /* .world-workspace and .world-section-nav fold in forge.css now (#295); their bases live there
       too, so the override could move without losing the specificity tie. .world-context-header
       stays because ITS base is still this page's, and this page's sheet loads last. */
    .world-context-header[b-6k87f5k96r] { grid-template-columns: 1fr; position: static; margin: 0 0 1rem; }
    .world-section-nav > p[b-6k87f5k96r] { grid-column: 1 / -1; }
    .world-metrics[b-6k87f5k96r] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Adventures are authored in their own studio now (issue #96); this world only says where they happen. */
.world-adventure-list[b-6k87f5k96r] { display: grid; gap: .4rem; margin: 0; padding: 0; list-style: none; }
.world-adventure-list li[b-6k87f5k96r] { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; padding: .55rem .7rem; border: 1px solid #3e3529; border-radius: .3rem; background: var(--forge-surface-1); }
.world-adventure-list strong[b-6k87f5k96r] { color: var(--forge-gold-bright); }
.world-adventure-list code[b-6k87f5k96r] { color: var(--forge-text-faint); font-size: .75rem; }
.world-adventure-list a[b-6k87f5k96r] { margin-left: auto; color: var(--forge-gold); font-size: .8rem; text-decoration: none; }

/* Issue #3: the pin placer. The picture is a backdrop and the pins are drawn over it, exactly as they
   are at play time — an author has to see what a player will see, or the placement is guesswork. */
.pin-placer[b-6k87f5k96r]{position:relative;line-height:0;margin:.6rem 0;border-radius:8px;overflow:hidden}
.pin-placer img[b-6k87f5k96r]{display:block;width:100%;height:auto;cursor:crosshair}
/* Centred on its own coordinate: a marker hung from its top-left corner lies by half its own size. */
.pin-placer .pin[b-6k87f5k96r]{position:absolute;width:12px;height:12px;margin:-6px 0 0 -6px;border-radius:50%;
    background:#8fa4d8;border:2px solid #fff;box-shadow:0 0 0 1px rgba(0,0,0,.55);pointer-events:none}
.pin-placer .pin.active[b-6k87f5k96r]{background:#e8503a}
.locale-picture-list[b-6k87f5k96r]{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fill,minmax(15rem,1fr));margin-top:.8rem}

/* Issue #132: the summary row. Every list here is now pick-a-row-then-edit-in-a-dialog, so the row's
   whole job is to be scannable and to say what the thing IS without being a form. The same shape the
   adventure studio already uses, kept visually consistent with it. */
.summary-row[b-6k87f5k96r]{display:flex;align-items:center;gap:.7rem;padding:.5rem .7rem;margin:.35rem 0;
    border:1px solid #3e3529;border-radius:.3rem;background:var(--forge-surface-1);cursor:pointer}
.summary-row:hover[b-6k87f5k96r]{border-color:var(--forge-gold)}
.summary-row.selected[b-6k87f5k96r]{border-color:var(--forge-gold);box-shadow:inset 3px 0 0 var(--forge-gold)}
.summary-row strong[b-6k87f5k96r]{color:#f0e6d4;white-space:nowrap}
.summary-row code[b-6k87f5k96r]{color:var(--forge-text-faint);font-size:.75rem;white-space:nowrap}
/* min-width:0 is what lets the ellipsis happen at all — a flex item's default min-width is its content,
   so without it the row grows instead of truncating and pushes the delete button off the card. */
.summary-row .summary-text[b-6k87f5k96r]{flex:1;min-width:0;font-size:.8rem;color:var(--forge-text-label);
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.summary-row .danger[b-6k87f5k96r]{margin-left:auto}

/* The way out to the two overland surfaces (#435, #438). Above the graph rather than buried in it: the
   graph is this page's own editor and those are other pages entirely, so they read as departures. */
.graph-links[b-6k87f5k96r]{display:flex;gap:1rem;margin:0 0 .6rem;font-size:.8rem}
.graph-links a[b-6k87f5k96r]{color:var(--forge-gold);text-decoration:none}
.graph-links a:hover[b-6k87f5k96r]{text-decoration:underline}

/* The world-character dialog's tab strip (Dave, 2026-09-03).

   ⚠️ Styled here rather than shared, deliberately: this is the first dialog on the page that needed
   tabs, and one use is not a pattern. Promote it the day a second dialog wants the same strip. */
.dialog-tabs[b-6k87f5k96r] {
    display: flex;
    gap: 0.25rem;
    margin-bottom: 0.9rem;
    border-bottom: 1px solid var(--border, #3a352d);
}

.dialog-tab[b-6k87f5k96r] {
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    color: inherit;
    cursor: pointer;
    padding: 0.4rem 0.7rem;
    font-size: 0.9rem;
    opacity: 0.65;
}

.dialog-tab:hover[b-6k87f5k96r] { opacity: 0.9; }

/* ⚠️ The active tab keeps full opacity AND the underline. Colour alone would be the only cue for a
   viewer who cannot distinguish it. */
.dialog-tab.active[b-6k87f5k96r] {
    opacity: 1;
    font-weight: 600;
    border-bottom-color: var(--accent, #c9a227);
}

/* ── The places list, which is a view of the drawn map (Dave, 2026-09-09) ───────────────────────── */

/* ⭐ An unnamed region is a REAL state and is marked as one. Dave: "Regions need a name and a name
   marker same as everything else." Rendering it blank, or inventing "Untitled", would both hide the
   fact that four of Fenreach's six regions still need naming. */
.unnamed[b-6k87f5k96r] { font-style: italic; color: #d8a58f; }

/* The kind is the map's word for what sort of place this is — city, inn, homestead. Small and quiet:
   it qualifies the name rather than competing with it. */
.place-kind[b-6k87f5k96r] {
    flex: 0 0 auto;
    padding: .1rem .4rem;
    border: 1px solid #3a4258;
    border-radius: .3rem;
    font-size: .66rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: #9aa6bd;
}

/* ⛔ Written down, on no map. Warm-edged on purpose: this is the state that hid six dead locales for
   months, and it must not look like just another group. */
.orphan-card[b-6k87f5k96r] { border-color: #6b4a3a; background: #1d1512; }
.orphan-card .nested-card-heading h3[b-6k87f5k96r] { color: #e0a882; }

/* The map's own facts inside the locale dialog: shown, never offered as inputs. */
.from-the-map[b-6k87f5k96r] {
    margin-bottom: .8rem;
    padding: .6rem .75rem;
    border: 1px solid #3a4258;
    border-radius: .4rem;
    background: #151b28;
}
.from-the-map p[b-6k87f5k96r] { margin: 0; }
.from-the-map p + p[b-6k87f5k96r] { margin-top: .3rem; }

/* How many roads touch a place, on its row. Quiet — it qualifies the place rather than announcing itself,
   and its absence is a normal state rather than a warning. */
.route-count[b-6k87f5k96r] {
    flex: 0 0 auto;
    font-size: .68rem;
    color: #8fb8a0;
    border: 1px solid #35563f;
    border-radius: .3rem;
    padding: .1rem .4rem;
}

/* The place's connections, inside its panel. Read-only: a route belongs to both its ends. */
.place-routes[b-6k87f5k96r] { margin: .8rem 0; }
.place-routes .plain-list[b-6k87f5k96r] { margin: .2rem 0 0; padding-left: 1.1rem; }
.place-routes .plain-list li[b-6k87f5k96r] { font-size: .82rem; color: #c7cddb; }

/* The region heading is the way into its panel, so it has to look pressable without becoming a button
   in the middle of a heading. Inherits the h3's type and only gains an underline on hover. */
.region-name[b-6k87f5k96r] {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    cursor: pointer;
    text-align: left;
}
.region-name:hover span[b-6k87f5k96r] { text-decoration: underline; }
.region-name:focus-visible[b-6k87f5k96r] { outline: 2px solid #d7aa55; outline-offset: 2px; }

/* ⛔ #122 again, and the same two-name shape as the block above. The map-owns-the-name rebuild added
   `.muted.small` in eleven places and one `.field-label`, and neither was declared anywhere this page
   can reach — scoped CSS does not inherit from another page's sheet, so both drew nothing and nothing
   errored. → memory `scoped-css-cannot-reach-a-built-element`

   ⚠️ Values are BORROWED rather than invented: `.small` is CharacterSheet.razor.css's, and `.field-label`
   reuses the label colour this sheet already sets on `label`. Neither is a look anybody has chosen at
   this size — worth Dave's eye. → memory `a-look-is-not-a-number` */
.small[b-6k87f5k96r] { font-size: .8rem; line-height: 1.5; }
.field-label[b-6k87f5k96r] { color: var(--forge-text-label); font-size: .85rem; margin: 0 0 .3rem; }
/* /Components/Pages/ForgeWorldStudio.razor.rz.scp.css */
/* World mode (issue #435).

   Editor chrome, not page chrome: the canvas is the content and everything else is a rail around it. The
   whole surface is one viewport-height flex column that never scrolls — a drawing tool where the drawing
   moves when you flick the wheel is unusable, so the page is pinned and only the rails scroll. The
   heights are the Map Studio's, measured there rather than guessed here. */

.world-studio[b-6bmofd70b0] {
    display: flex;
    flex-direction: column;
    gap: .6rem;
    height: calc(100dvh - 13rem);
    min-height: 28rem;
}

/* Wraps rather than overlapping. A world's name can be a copyright notice — Blackmarsh's is one — and on
   a narrow window the status line was drawn straight through it. */
.world-studio-header[b-6bmofd70b0] {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: .3rem .6rem;
    flex: 0 0 auto;
}

.world-studio-header h1[b-6bmofd70b0] {
    margin: 0;
    font-size: 1.05rem;
    letter-spacing: .01em;
}

/* Which world, and which asset the tracing lands on. Permanent rather than a status line, for #375's
   reason: the studio stated its behaviour once, in passing, and the author reasonably went on believing
   they were editing something else. */
.world-studio-scope[b-6bmofd70b0] {
    display: inline-flex;
    align-items: baseline;
    gap: .45rem;
    padding: .12rem .5rem;
    border-radius: 999px;
    border: 1px solid #3a4459;
    background: #161d2b;
    font-size: .72rem;
    min-width: 0;
}

/* The name is the half worth keeping, but not at any length: a licence notice in the title bar would
   push the asset id off the strip entirely. */
.world-studio-scope strong[b-6bmofd70b0] {
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 22rem;
}

/* The asset id yields before the world name does: the world is the half that says what is being changed. */
.world-studio-scope > span[b-6bmofd70b0] {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    opacity: .75;
}

/* An area is entered from the world map, so the way back belongs to the page. */
.world-studio-back[b-6bmofd70b0] {
    font-size: .74rem;
    opacity: .8;
    text-decoration: none;
}

.world-studio-back:hover[b-6bmofd70b0] { opacity: 1; text-decoration: underline; }

.world-studio-gap[b-6bmofd70b0] { flex: 1 1 auto; }

.world-studio-dirty[b-6bmofd70b0] { font-size: .72rem; opacity: .8; }
.world-studio-status[b-6bmofd70b0] { margin: 0; font-size: .74rem; }
.world-studio-status.success[b-6bmofd70b0] { color: #7fc99a; }
.world-studio-status.error[b-6bmofd70b0] { color: #e08b7f; }

.world-studio-workspace[b-6bmofd70b0] {
    display: grid;
    grid-template-columns: 16rem minmax(0, 1fr) 17rem;
    gap: .6rem;
    flex: 1 1 auto;
    min-height: 0;
}

/* Only the rails scroll. Two of them, and they are deliberately different jobs: what you are about to do
   on the left, what you have selected on the right. */
.world-studio-rail[b-6bmofd70b0] {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    overflow-y: auto;
    min-height: 0;
    padding-right: .2rem;
}

.tool-grid[b-6bmofd70b0] {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .3rem;
}

.tool-grid .tool[b-6bmofd70b0] {
    padding: .35rem .3rem;
    font-size: .78rem;
    border-radius: .35rem;
    border: 1px solid #2f3a4f;
    background: #1c2434;
    color: inherit;
    cursor: pointer;
}

.tool-grid .tool:hover[b-6bmofd70b0] { border-color: #4a5a78; }

.tool-grid .tool.active[b-6bmofd70b0] {
    border-color: #7fb0e0;
    background: #24344a;
}

.world-studio-canvas-wrap[b-6bmofd70b0] {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: .4rem;
    min-width: 0;
    min-height: 0;
}

/* Wraps rather than overflowing: on a narrow window the Save button used to sit past the right edge and
   the whole page scrolled sideways to reach it, taking the canvas with it. */
.canvas-bar[b-6bmofd70b0] {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .3rem;
    flex: 0 0 auto;
}

.canvas-bar button[b-6bmofd70b0] {
    padding: .3rem .55rem;
    font-size: .76rem;
    border-radius: .3rem;
    border: 1px solid #2f3a4f;
    background: #1c2434;
    color: inherit;
    cursor: pointer;
}

.canvas-bar button:disabled[b-6bmofd70b0] { opacity: .4; cursor: default; }
.canvas-bar button.active[b-6bmofd70b0] { border-color: #7fb0e0; }

/* The Filters and Download panels. Zero height in the column, so opening one never moves the canvas; the
   negative margin cancels the column's gap for the same reason. */
.map-popovers[b-6bmofd70b0] {
    position: relative;
    height: 0;
    margin-top: -.4rem;
    z-index: 4;
}

.map-popovers-row[b-6bmofd70b0] {
    position: absolute;
    top: .2rem;
    left: 0;
    display: flex;
    align-items: flex-start;
    gap: .5rem;
}

.map-popover[b-6bmofd70b0] {
    width: 17rem;
    max-height: 70vh;
    overflow-y: auto;
    padding: .55rem .7rem .65rem;
    font-size: .78rem;
    border-radius: .5rem;
    border: 1px solid #2f3a4f;
    background: #141b28;
    box-shadow: 0 .6rem 1.6rem rgba(0, 0, 0, .45);
}

.map-popover-head[b-6bmofd70b0] { display: flex; align-items: center; justify-content: space-between; }
.map-popover-head h3[b-6bmofd70b0] { margin: 0; font-size: .85rem; }
.map-popover-close[b-6bmofd70b0] {
    border: 0;
    background: none;
    color: inherit;
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
}
.map-popover-hint[b-6bmofd70b0] { margin: .35rem 0 .5rem; opacity: .7; line-height: 1.35; }
.map-popover-problem[b-6bmofd70b0] { margin: .35rem 0; color: #f0a39a; line-height: 1.35; }

.map-filter-group[b-6bmofd70b0] {
    margin: 0 0 .5rem;
    padding: .35rem .5rem .4rem;
    border: 1px solid #263044;
    border-radius: .35rem;
}
.map-filter-group legend[b-6bmofd70b0] { padding: 0 .25rem; font-weight: 600; }

.map-filter-all[b-6bmofd70b0] { display: flex; gap: .3rem; margin-bottom: .25rem; }
.map-filter-all button[b-6bmofd70b0],
.map-popover-foot button[b-6bmofd70b0] {
    padding: .15rem .45rem;
    font-size: .72rem;
    border-radius: .3rem;
    border: 1px solid #2f3a4f;
    background: #1c2434;
    color: inherit;
    cursor: pointer;
}
.map-popover-foot button.primary[b-6bmofd70b0] { border-color: #7fb0e0; background: #24344a; padding: .3rem .8rem; }
.map-popover-foot button:disabled[b-6bmofd70b0] { opacity: .4; cursor: default; }

.map-filter-row[b-6bmofd70b0] { display: flex; align-items: center; gap: .4rem; padding: .1rem 0; cursor: pointer; }
.map-filter-row span[b-6bmofd70b0] { flex: 1 1 auto; }
.map-filter-row em[b-6bmofd70b0] { font-style: normal; opacity: .55; }

.map-download-size[b-6bmofd70b0] { display: flex; align-items: center; justify-content: space-between; gap: .5rem; margin: .1rem 0 .5rem; }
.map-download-size select[b-6bmofd70b0] { flex: 1 1 auto; }

.map-popover-foot[b-6bmofd70b0] { display: flex; align-items: center; justify-content: space-between; gap: .5rem; margin-top: .3rem; }
.map-popover-file[b-6bmofd70b0] { opacity: .55; font-size: .7rem; overflow-wrap: anywhere; }
.canvas-bar button.primary[b-6bmofd70b0] { border-color: #7fb0e0; background: #24344a; }
.canvas-bar-gap[b-6bmofd70b0] { flex: 1 1 auto; }
.canvas-scale[b-6bmofd70b0] { font-size: .72rem; opacity: .7; }

/* The island's own element. Nothing inside it belongs to Blazor: the canvas is Pixi's, and a diff that
   removed it would leave a running ticker drawing into nothing. */
.world-canvas[b-6bmofd70b0] {
    flex: 1 1 auto;
    min-height: 0;
    border-radius: .5rem;
    overflow: hidden;
    border: 1px solid #2a3346;
    background: #0b111d;
}

@media (max-width: 1200px) {
    .world-studio-workspace[b-6bmofd70b0] {
        grid-template-columns: 14rem minmax(0, 1fr);
    }
    /* The selection rail wraps under the canvas rather than squeezing it: at this width the canvas is
       already the thing that has least room, and it is the thing being drawn on. */
    .world-studio-rail:last-child[b-6bmofd70b0] {
        grid-column: 1 / -1;
        flex-direction: row;
        flex-wrap: wrap;
        overflow-x: auto;
    }
}

/* The element inspector (#477) — the selection rail, floated.

   ⛔ It is the SAME rail with a different container, not a second surface: every control inside it is
   where it always was. Two homes for one control is how they drift. */
.world-inspector[b-6bmofd70b0] {
    position: relative;
    z-index: 5;
    /* JS writes a `transform` when it is dragged; nothing here may use transform for anything else. */
    will-change: transform;
    box-shadow: 0 .6rem 1.6rem rgba(0, 0, 0, .45);
    border-radius: .45rem;
    max-height: calc(100vh - 8rem);
    overflow-y: auto;
}

.inspector-grip[b-6bmofd70b0] {
    display: flex;
    align-items: center;
    gap: .4rem;
    padding: .3rem .5rem;
    /* ⚠️ Sticky so a long panel can still be grabbed after scrolling to a dial near the bottom. */
    position: sticky;
    top: 0;
    z-index: 1;
    background: #16202f;
    border-bottom: 1px solid #2f3a4f;
    border-radius: .45rem .45rem 0 0;
    cursor: grab;
    /* ⛔ Or a real mouse drag starts selecting the title and then fights the gesture for the pointer. */
    user-select: none;
    touch-action: none;
}

.inspector-grip:active[b-6bmofd70b0] {
    cursor: grabbing;
}

.inspector-title[b-6bmofd70b0] {
    flex: 1;
    font-size: .74rem;
    letter-spacing: .04em;
    text-transform: uppercase;
    opacity: .85;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.inspector-reset[b-6bmofd70b0],
.inspector-close[b-6bmofd70b0] {
    background: none;
    border: none;
    color: inherit;
    opacity: .6;
    cursor: pointer;
    font-size: .9rem;
    line-height: 1;
    padding: .1rem .25rem;
}

.inspector-reset:hover[b-6bmofd70b0],
.inspector-close:hover[b-6bmofd70b0] {
    opacity: 1;
}

/* ── Generate continent (#475) ────────────────────────────────────────────────────────────────────
   ⛔ These reach INSIDE ForgeDialog's ChildContent, which works only because the fragment keeps its
   caller's scope — Blazor's scoped CSS never crosses a component boundary, so the dialog's own
   stylesheet cannot see any of this and this cannot see the dialog's chrome. Both halves are correct
   and neither can be moved to the other file. */

.generate-grid[b-6bmofd70b0] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: 1rem;
    /* The dialog is tall on a small screen and the sections are independent — scrolling the body beats
       a dialog taller than the window with its own footer off the bottom. */
    max-height: min(66vh, 40rem);
    overflow-y: auto;
    padding-right: .25rem;
}

.generate-section[b-6bmofd70b0] {
    display: flex;
    flex-direction: column;
    gap: .35rem;
    padding: .6rem .7rem;
    border: 1px solid #24304a;
    border-radius: .4rem;
    background: #0d131e;
}

/* Country and the roll want the full width: one is a long list of rows, the other a single line. */
.generate-wide[b-6bmofd70b0] {
    grid-column: 1 / -1;
}

.generate-section h3[b-6bmofd70b0] {
    margin: 0 0 .2rem;
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    opacity: .75;
}

.generate-section label[b-6bmofd70b0] {
    display: flex;
    flex-direction: column;
    gap: .2rem;
    font-size: .74rem;
}

.generate-section input[type="range"][b-6bmofd70b0] {
    width: 100%;
}

/* A dial is checkbox · slider · number, and the number is what stops a slider being a mystery. */
.generate-dial[b-6bmofd70b0] {
    display: grid;
    grid-template-columns: minmax(8rem, 1fr) 2fr auto;
    align-items: center;
    gap: .5rem;
    font-size: .74rem;
}

.generate-toggle[b-6bmofd70b0] {
    flex-direction: row !important;
    align-items: center;
    gap: .35rem;
}

.generate-count[b-6bmofd70b0] {
    flex-direction: row !important;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
}

.generate-count input[b-6bmofd70b0] {
    width: 4.5rem;
}

.generate-value[b-6bmofd70b0] {
    font-variant-numeric: tabular-nums;
    opacity: .8;
    min-width: 2.6rem;
    text-align: right;
}

/* The same swatch the terrain rail draws, so a row in this dialog and a shape on the map are visibly
   the same thing. The colour comes from the server's vocabulary, never from a table here. */
.generate-swatch[b-6bmofd70b0] {
    display: inline-block;
    width: .7rem;
    height: .7rem;
    border-radius: .15rem;
    border: 1px solid #00000060;
    flex: none;
}

.generate-seed[b-6bmofd70b0] {
    display: grid;
    grid-template-columns: auto auto 1fr;
    align-items: end;
    gap: .6rem;
}

.generate-seed input[b-6bmofd70b0] {
    width: 9rem;
}
/* /Components/Pages/Landing.razor.rz.scp.css */
/* Scoped to Landing. The page grew from a single hero into a coming-soon page with content, so the
   shell scrolls normally instead of pinning one block to the bottom of the viewport. */
.landing-shell[b-bjctzwm1nr] {
    min-height: 100vh;
    box-sizing: border-box;
    padding: clamp(2rem, 6vw, 5rem) clamp(1.25rem, 5vw, 5rem) 3rem;
    color: #e7eaf2;
    background: linear-gradient(rgba(8, 12, 20, .45), rgba(8, 12, 20, .93)), url('/images/bg-landing.jpg');
    background-size: cover;
    background-position: center;
    background-attachment: fixed;
}

.landing-inner[b-bjctzwm1nr] {
    max-width: 1080px;
    margin: 0 auto;
}

.hero[b-bjctzwm1nr] {
    padding: clamp(1rem, 8vh, 6rem) 0 clamp(2rem, 6vh, 4rem);
    max-width: 44rem;
}

.eyebrow[b-bjctzwm1nr] {
    font-size: .72rem;
    font-weight: 800;
    letter-spacing: .16em;
    color: #dcb763;
    margin: 0 0 .6rem;
}

.hero h1[b-bjctzwm1nr] {
    margin: 0 0 1rem;
    font-family: Georgia, 'Times New Roman', serif;
    font-size: clamp(2.6rem, 8vw, 4.5rem);
    line-height: 1.05;
    color: #faf7ef;
    text-shadow: 0 4px 24px #000a;
}

.tagline[b-bjctzwm1nr] {
    margin: 0 0 2rem;
    font-size: clamp(1rem, 2.2vw, 1.2rem);
    line-height: 1.65;
    color: #d9deea;
    text-shadow: 0 2px 12px #000a;
}

.hero-actions[b-bjctzwm1nr] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1rem 1.25rem;
}

.play-button[b-bjctzwm1nr] {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    border-radius: 12px;
    padding: 1rem 2.2rem;
    font-weight: 800;
    font-size: 1.05rem;
    letter-spacing: .04em;
    background: linear-gradient(135deg, #c69446, #e8c36a);
    color: #16110a;
    text-decoration: none;
    box-shadow: 0 14px 40px #00000060;
    transition: filter .15s ease, transform .15s ease;
}

.play-button:hover[b-bjctzwm1nr],
.play-button:focus-visible[b-bjctzwm1nr] {
    filter: brightness(1.08);
    transform: translateY(-1px);
}

/* The second door: the lore pages. An outline rather than a second gold slab, so START PLAYING stays the
   obvious first choice. */
.world-button[b-bjctzwm1nr] {
    display: inline-flex;
    align-items: center;
    border-radius: 12px;
    padding: calc(1rem - 2px) 1.8rem;
    font-weight: 800;
    font-size: 1.05rem;
    letter-spacing: .04em;
    border: 2px solid #e8c36a;
    color: #f0e2bd;
    background: #0c111c99;
    text-decoration: none;
    transition: background .15s ease, transform .15s ease;
}

.world-button:hover[b-bjctzwm1nr],
.world-button:focus-visible[b-bjctzwm1nr] {
    background: #e8c36a26;
    transform: translateY(-1px);
}

.hero-note[b-bjctzwm1nr] {
    font-size: .9rem;
    color: #aeb6c6;
}

/* auto-fit + minmax so a narrow viewport drops to one column instead of clipping a track.
   ⛔ The track floor is tuned to the CARD COUNT against .landing-inner's 1080px max-width: at 15rem
   the grid fits 4 across, which left the six cards as a 4 + 2 orphan row. 20rem fits 3, so six lay
   out 3 + 3, then 2, then 1 as the viewport narrows. Re-check this floor if a card is added. */
.features[b-bjctzwm1nr] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
    gap: 1px;
    background: #ffffff14;
    border: 1px solid #ffffff14;
    border-radius: 14px;
    overflow: hidden;
}

.features article[b-bjctzwm1nr] {
    background: #0c111ce6;
    padding: 1.6rem 1.5rem;
}

.features h2[b-bjctzwm1nr] {
    margin: 0 0 .55rem;
    font-family: Georgia, 'Times New Roman', serif;
    font-size: 1.12rem;
    color: #f0e2bd;
}

.features p[b-bjctzwm1nr] {
    margin: 0;
    font-size: .95rem;
    line-height: 1.6;
    color: #c7cddb;
}

.landing-foot[b-bjctzwm1nr] {
    margin-top: 2.5rem;
    text-align: center;
    font-size: .85rem;
    color: #8e97a8;
}

.landing-foot p[b-bjctzwm1nr] {
    margin: 0;
}

@media (max-width: 560px) {
    /* A fixed background repaints on every scroll frame on mobile and is often ignored outright. */
    .landing-shell[b-bjctzwm1nr] { background-attachment: scroll; }
    .hero[b-bjctzwm1nr] { text-align: center; }
    .hero-actions[b-bjctzwm1nr] { justify-content: center; }
}
/* /Components/Pages/Onboarding.razor.rz.scp.css */
.onboarding-shell[b-i2x8ifzieg]{min-height:100vh;box-sizing:border-box;display:flex;align-items:center;justify-content:flex-end;padding:clamp(1.5rem,4vw,4rem);color:#e7eaf2;background:linear-gradient(to right,rgba(8,12,20,.55),rgba(8,12,20,.92)),url('/images/bg-onboarding.jpg');background-size:cover;background-position:center}
.onboarding-card[b-i2x8ifzieg]{width:100%;max-width:440px;background:#0e1320d9;border:1px solid #303b57;border-radius:18px;padding:clamp(1.4rem,3vw,2.2rem);box-shadow:0 22px 70px #03060d80}
.eyebrow[b-i2x8ifzieg]{font-size:.72rem;font-weight:800;letter-spacing:.16em;color:#dcb763;margin:0 0 .5rem}
.onboarding-card h1[b-i2x8ifzieg]{margin:0 0 .4rem;font-family:Georgia,serif;font-size:1.7rem;color:#faf7ef}
.subhead[b-i2x8ifzieg]{margin:0 0 1.6rem;color:#aab5cf;font-size:.9rem}
.onboarding-card label[b-i2x8ifzieg]{display:block;font-size:.78rem;font-weight:700;color:#b7c2df;margin:1rem 0 .45rem}
/* ::deep because both fields on this page are <InputText>, and a child component's root element never
   carries this component's scope attribute — so `.onboarding-card input` compiled to
   `.onboarding-card input[b-xxxxx]` and matched nothing at all. Both inputs on the first page a new
   player sees have been browser-default white boxes since the rule was written (#233, and #227 before
   it, which was the same shape in /play's action composer). */
.onboarding-card[b-i2x8ifzieg]  input{width:100%;box-sizing:border-box;background:#141b2b;color:#eef2fb;border:1px solid #34405c;border-radius:10px;padding:.75rem .85rem;font-size:.95rem}
.languages-label[b-i2x8ifzieg]{margin-top:1.3rem}
.chip-row[b-i2x8ifzieg]{display:flex;flex-wrap:wrap;gap:.5rem}
.chip[b-i2x8ifzieg]{border:1px solid #34405c;background:#141a2a;color:#bfc9e5;border-radius:999px;padding:.5rem 1rem;font-size:.82rem;font-weight:600;cursor:pointer}
.chip.selected[b-i2x8ifzieg]{border-color:#dcb763;background:linear-gradient(135deg,#c69446,#e8c36a);color:#16110a}
.begin-button[b-i2x8ifzieg]{margin-top:1.8rem;width:100%;border:0;border-radius:12px;padding:1rem;font-weight:800;font-size:1rem;background:linear-gradient(135deg,#c69446,#e8c36a);color:#16110a;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:.5rem}
.begin-button:hover[b-i2x8ifzieg]{filter:brightness(1.08)}
@media(max-width:700px){.onboarding-shell[b-i2x8ifzieg]{justify-content:center}}
/* /Components/Pages/Play.razor.rz.scp.css */
.game-shell[b-nuzuh4nngs]{min-height:100vh;padding:clamp(1.25rem,3vw,3rem);max-width:1500px;margin:auto;color:#e7eaf2;background:linear-gradient(rgba(8,12,20,.72),rgba(8,12,20,.85)),url('/images/bg-play.jpg');background-size:cover;background-position:center}.game-header[b-nuzuh4nngs]{justify-content:space-between;margin-bottom:2rem}.eyebrow[b-nuzuh4nngs]{font-size:.7rem;font-weight:800;letter-spacing:.14em;color:#8b96b6;margin:0 0 .4rem}.game-header h1[b-nuzuh4nngs]{font-size:clamp(1.55rem,3vw,2.35rem);color:#faf7ef}.status-dot[b-nuzuh4nngs]{display:inline-block;width:.45rem;height:.45rem;border-radius:50%;background:#6ee7b7;box-shadow:0 0 10px #6ee7b7;margin-right:.4rem}@media(max-width:560px){.game-header[b-nuzuh4nngs]{align-items:flex-start;gap:1rem;flex-direction:column}.game-shell[b-nuzuh4nngs]{padding:1rem}}
/* Each pane is sized to the VIEWPORT, never to the other pane. As a grid row both columns share a
   height taken from whichever is taller, and .story-panel stretched to fill it — so opening a full
   Party tab silently grew the chat window past the fold, and the screen changed shape depending on
   which tool happened to be selected. Fixed heights with internal scrolling make the layout stable
/* ⭐ THE WAY IN (Dave, 2026-08-10). A door the narration promises and the board never drew.

   Sits directly above the sighting stack and shares its geometry, because they are the same kind of thing:
   a short, fixed, top-right note about something the party has just come to. Shut and open are the same
   card deliberately — arriving at a barred door and being TOLD so is the information that was missing, so
   the locked state must not be a quieter or smaller version of the open one. */
.doorway-prompt[b-nuzuh4nngs]{position:fixed;top:1rem;right:1rem;z-index:var(--dm-layer-dialog,1000);max-width:min(22rem,calc(100vw - 2rem));
    border:1px solid #4a6a86;border-left-width:3px;border-left-color:#7fb8d8;border-radius:12px;
    background:linear-gradient(145deg,#101c26f5,#0b1319f5);padding:.7rem .85rem;
    box-shadow:0 18px 50px #03060db0;backdrop-filter:blur(3px)}
/* Shut reads as stone rather than as an error: it is not a refusal, it is a fact about a door. */
.doorway-prompt.shut[b-nuzuh4nngs]{border-left-color:#6b7280;border-color:#3a434f}
.doorway-prompt .eyebrow[b-nuzuh4nngs]{color:#9fd0ea;margin:0 0 .25rem}
.doorway-prompt.shut .eyebrow[b-nuzuh4nngs]{color:#98a2b3}
.doorway-prompt strong[b-nuzuh4nngs]{display:block;font-family:Georgia,serif;font-size:.98rem;color:#eef7ff}
.doorway-detail[b-nuzuh4nngs]{margin:.2rem 0 .5rem;font-size:.72rem;color:#a8bccb;line-height:1.45}
.doorway-enter[b-nuzuh4nngs]{width:100%;border-radius:9px;padding:.4rem .5rem;font-size:.75rem;font-weight:800;
    cursor:pointer;border:1px solid #4d90b8;background:linear-gradient(135deg,#2e6d92,#3f89b4);color:#f2fbff}
.doorway-enter:hover:not(:disabled)[b-nuzuh4nngs]{filter:brightness(1.12)}
.doorway-enter:disabled[b-nuzuh4nngs]{opacity:.5;cursor:default}
/* ⭐ SOMETHING IS THERE (Dave, 2026-08-10; made blocking 2026-08-12).
   A sighting is an offer, not an ambush: the party is told what they can see and chooses whether it is
   worth a fight.

   ⛔ IT WAS A CORNER TOAST AND THAT FAILED IN PLAY. Fixed at top-right, z-index 85, rendering perfectly —
   and Dave, who watches the narration, never saw it while two wolves crossed the map and reached him.
   A notification in a corner is found only by somebody already looking for one. It is now a MODAL over a
   full backdrop, in his words: "an annoying popup - modal. Click it or nothing else works."

   z-index 95 is ABOVE the whole-map overlay (90) — deliberately changed. The old value sat below it so
   you could open the big board to look at what was reported; that reasoning belonged to a card you could
   ignore. A prompt that must be answered cannot be hidden behind another surface. */
/* ⛔ The blocking sighting modal was deleted here (#577). Nothing is ASKED on sighting any more: a
   hostile opens the fight itself and gets the encounter card, and anything else is merely noticed. The
   comment above survives because its lesson does — a prompt that must be answered cannot be hidden
   behind another surface — and it is why the encounter card is modal.

   What is left is the quiet end of that: a strip saying something is over there. It is deliberately
   ignorable, because there is nothing to answer. */
.sighting-notice[b-nuzuh4nngs]{display:flex;flex-wrap:wrap;align-items:baseline;gap:.35rem .9rem;
    margin:0 0 .6rem;padding:.4rem .7rem;border:1px solid #3b3350;border-left:3px solid #b08cd8;
    border-radius:10px;background:#150f20cc;font-size:.76rem;color:#b9aecd}
.sighting-notice-eyebrow[b-nuzuh4nngs]{color:#c3a6e8;font-size:.62rem;font-weight:800;letter-spacing:.12em;
    text-transform:uppercase}
.sighting-notice-item[b-nuzuh4nngs]{display:inline-flex;align-items:baseline;gap:.35rem}
.sighting-notice-item strong[b-nuzuh4nngs]{color:#f4eeff;font-family:Georgia,serif;font-weight:600}

/* The loot line on the end card (#579), which is behind one switch in Play.razor. */
.encounter-loot-empty[b-nuzuh4nngs]{margin:0;font-style:italic}

/* ⭐ Search the area (#968), in the same slot the line above occupies — one or the other, never both.

   ⛔ STYLED HERE AND NOT IN EncounterCard.razor.css, and that is not a filing preference. The button is
   authored in Play.razor's <Note> fragment, so it carries PLAY's scope attribute; a rule written in the
   card's own scoped sheet would compile against the card's attribute and match nothing at all.
   → scoped-css-cannot-reach-a-built-element

   ⚠️ Deliberately NOT styled as the card's primary action. "Carry On" is the button that ends the card
   and it must stay the loudest thing on it; a second full-width button beside it would make the party
   read twice to find the way out of a screen they see after every fight. */
.encounter-search[b-nuzuh4nngs]{margin:0;padding:.3rem .85rem;border:1px solid #6b5a3f;border-radius:6px;
    background:#1d1812;color:#c9b48e;font:inherit;font-size:.82rem;cursor:pointer}
.encounter-search:hover[b-nuzuh4nngs],.encounter-search:focus-visible[b-nuzuh4nngs]{border-color:#d8b96a;color:#f3e7cf}

/* ⭐ The Combat tab takes the whole column, and only it. `max-height` alone leaves the panel sized by its
   CONTENT and parked at the top of its grid cell by `align-self:start` — so a fight with few combatants
   ended below a third of empty column, which is what Dave measured. A definite height is what gives the
   tool stack room to distribute and lets the minimap grow into it.

   Sized to the VIEWPORT and not to the other pane, deliberately: that is the same rule the note above
   states, and it is why .story-panel is `calc(100vh - 12rem)` rather than `100%`. Two panes that size to
   each other change shape depending on which tab happens to be open.

   ⚠️ Beaten on purpose by `.game-shell.expanded .encounter-panel` (three classes to this rule's two),
   which sets height:auto because expanded the shell IS the window and the panes take their size from the

/* Expanded table: the shell leaves the document flow and takes the window, so the site header and
   campaign title stop pushing the action box below the fold. CSS rather than the Fullscreen API —
   "full browser window" is what was asked for, and it needs no gesture permission. */
/* ⛔ `min-height:0` UNDOES `.game-shell`'s `min-height:100vh`, and it is worth a sentence. Expanded, this
   element is `position:fixed;inset:0` — it is ALREADY exactly the viewport, and a 100vh minimum on top of
   `padding:.5rem` made its border box 16px TALLER than the screen it is pinned to. The pane host inherited
   the overflow, so every window's height cap was measured against a host whose bottom was off screen, and
   a window docked to the bottom lost its last 16px under the edge. A minimum that fights an exact size is
   never doing anything but harm. */
.game-shell.expanded[b-nuzuh4nngs]{position:fixed;inset:0;z-index:var(--dm-layer-shell,80);max-width:none;margin:0;padding:.5rem;min-height:0;overflow:hidden;display:flex;flex-direction:column;outline:none}
.expanded-bar[b-nuzuh4nngs]{display:flex;align-items:center;gap:.6rem;padding:.15rem .35rem .5rem;flex:0 0 auto}
.expanded-title[b-nuzuh4nngs]{font-family:Georgia,serif;color:#faf7ef;font-size:.95rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.expanded-meta[b-nuzuh4nngs]{font-size:.7rem;color:#8b96b6;margin-left:auto;white-space:nowrap}
/* The Combat tab only (Dave, 2026-08-10: "about 30% of the left column unused"). A flex column so the
   minimap can be told to take the slack — a grid hands every row its content height and leaves the
   remainder as dead space at the bottom, which is exactly what he was looking at.

   ⚠️ `min-height:0` on the children that scroll, and the minimap opts IN to growing via its own `grows`
   class — this stylesheet cannot reach `.mini-map`, which carries MiniMap's scope attribute and not this
   page's. Styling another component's element from here is the mistake that has cost this repo three
/* Everything except the minimap keeps its natural height and is allowed to be as tall as it needs.

   ⚠️ ::deep IS LOAD-BEARING. Written plainly this compiles to `.combat-stack > *[b-…]`, and since #261
   split the combat surface into panes the direct children of this stack are elements rendered by OTHER
   components, which carry their own scope attribute and never this page's. The rule would have matched
   nothing and the stack would have silently gone back to its content-height layout — the exact 30% of


/* THE MAP'S PLACE (issue #226).
   ============================================================================================
   The board lived in the narration column as a chat tab, which meant looking at it REPLACED the
   conversation — and the fix for that was a strip that re-printed the last narration line underneath,
   which is an admission that the trade was wrong. Dave: "The map is important and right now we are
   hiding it."

   Three things had to change together, and each of the first two is useless without the others:

   1. THE CAP. `.game-shell` was capped at 1500px, so on a wide monitor the rest of the screen was
      thrown away. That cap is what made this a zero-sum argument: at 1500px there is no split of the
      ~1390 usable pixels where BOTH columns grow, so "make the tool column bigger" could only ever
      mean "make the narration smaller" — which is the complaint he opened with. Uncapped at 2560 both
      columns grow by three quarters and nothing is traded. Raising the cap is the whole reason the
      rest of this works.
   2. THE COLUMN. The board moved to the tool strip beside Scene/Combat/Party/World, so the map and the
      narration are now visible at the same time. The re-printed narration line is gone with it.
   3. THE HEIGHT. Prose still needs its lines, so the message bubbles keep a readable measure rather
      than stretching to whatever the wider column now offers. */
.game-shell[b-nuzuh4nngs]{max-width:2200px}
/* ⤳ The 90-character measure moved to wwwroot/play.css (#261 phase 3), with the rest of the bubble
   family. Same reason and same discovery route as the companion bubble: scoped here it had become
/* The board scrolls in here rather than growing the panel. Zoomed in, a forty-square dungeon is wider
   than any column will ever be, and that is what zoom is FOR — the alternative is drawing every map at

/* "Map and party tools" (Dave, 2026-08-10). Everything above the board was a setting stacked on a
   setting — six rows of chrome standing on the map's height for the whole session. One fold now holds
   the lot; the board gets the space back the moment it is shut.

   Folded with `display:none` rather than by not rendering the body, so the panel keeps ONE DOM identity:
   `aria-controls` points at something that exists in both states, and `display:none` takes the controls
   out of the tab order anyway — a hidden control that can still be tabbed into is the usual way this

/* Zoom. BattleMap has taken a DisplayScale since M10c with nothing to drive it. Sits ABOVE the board
   rather than floating over it: a control over the map is a control that covers a square.
   Reset's `margin-left:auto` used to push it up against the ⤢ that closed this row; with ⤢ moved to the

/* The marching order is edited where it is shown. The row holds the select-button and the two arrows
   side by side; the button keeps its own grid so the rank/name/who columns are unchanged. `active` moved

/* Who is Searching (issue #244). Beside the party-follows toggle because they are the two rules about
   what a walk MEANS; one chip per character because searching is a per-character posture — the rogue
/* Sneaking (issue #229 layer 3). Deliberately a COOL lit colour against Searching's warm one: the two
   are mutually exclusive per character, so a glance down the two rows should never leave any doubt

/* ⤢ THE WHOLE MAP. A fixed layer over the page rather than anything inside the layout, so opening it
   cannot reflow what is underneath and closing it restores nothing — because nothing moved.

   z-index 90 clears the expanded shell (80). Both can be open at once and the map has to win: it is the
   thing that was asked for most recently. */
.map-fullscreen[b-nuzuh4nngs]{position:fixed;inset:0;z-index:var(--dm-layer-map,90);display:flex;flex-direction:column;
    background:#070a12f2;padding:.5rem;outline:none}
.map-fullscreen-bar[b-nuzuh4nngs]{display:flex;align-items:center;gap:.6rem;flex:0 0 auto;padding:.1rem .3rem .45rem}
.map-fullscreen-title[b-nuzuh4nngs]{font-family:Georgia,serif;color:#faf7ef;font-size:.95rem;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* The bar carries every control the docked board has, so it wraps rather than pushing the restore
   button off the end on a narrow window. */
.map-fullscreen-bar[b-nuzuh4nngs]{flex-wrap:wrap}

/* `min-height:0` is load-bearing: a flex child defaults to min-content, so without it the board's own
   aspect ratio pushes the box past the bottom of the window and the restore button goes with it. */
.map-fullscreen-board[b-nuzuh4nngs]{flex:1;min-height:0}
/* Fitted: centred, whole board on screen, nothing to scroll. */
.map-fullscreen-board.fitted[b-nuzuh4nngs]{display:flex;align-items:center;justify-content:center;overflow:hidden}
/* Panned: the board takes its natural size at the current zoom and this box scrolls, exactly as the
   docked viewport does — bars hidden and wheel contained included (#386). `overflow:auto` is what the pan
   gesture drags; only the BARS go, and on the whole-map view they cost most of all. */
.map-fullscreen-board.panned[b-nuzuh4nngs]{overflow:auto;overscroll-behavior:contain;scrollbar-width:none}
.map-fullscreen-board.panned[b-nuzuh4nngs]::-webkit-scrollbar{display:none}

/* The strip above the board is GONE as of 2026-08-10. It carried the admin's debug switch and the wind;
   the switch is a setting and folded into the tools panel, and the wind moved onto the board itself. Two
   rows of chrome above a map, for one checkbox and one arrow.

   ⭐ The frame does not scroll and the viewport inside it does, which is the whole reason the wind can sit
   still in the board's corner while the board slides under it. Position an element absolutely inside the
   SCROLLER and it scrolls away with the content — the containing block is the scrolled box. One element

/* ⚠️ GOD MODE (issue #250) — admin-only, and deliberately styled as instrumentation rather than as part
   of the game. Green-on-dark and monospaced because it is a readout: it must be obvious in any
   screenshot that this is a debugging session and not what a player sees, or a screenshot of it gets
/* ⚠️ `flex:0 0 auto` is the whole fix, and `max-height` was never the problem. This is the last child of
   a flex COLUMN, so with the default `flex-shrink:1` it was the thing that gave way when the panel ran
   out of room: the 11rem below is a ceiling, and the box was being squeezed to well under its floor.
   Dave, playing: "impossible to read... it's 1.5 lines of display, but when you scroll it, it scrolls
   2+ lines at a time, making at least one line unreadable for every 3."

   The scroll-jump was the same bug seen from the other end. A viewport 1.5 lines tall cannot land on a
   line boundary, so every wheel notch left a half-line clipped top and bottom. A whole number of lines
   fixes it without any scroll-snapping cleverness.

   `resize:both` because he asked for it to be resizable in game, and a readout is exactly the thing
   whose right size depends on what you are hunting. It needs a non-visible overflow to work, which the
/* Issue #258. A check that fired and found nothing is NOT the same as nothing having happened, so it is
   not drawn in the "nothing here" grey above — it is a real event with real numbers, merely a dull one.
   A check that found something is the loudest line on the panel, because it is the one that changes the

/* ⤳ .end-turn and its spent-turn alarm moved to wwwroot/play.css (#261). It is rendered under the
   composer AND on the combat surface, and those two are now in different components — a scoped rule

/* Left-edge hidden-until-hover icon rail: Character/Missions/Inventory are inert placeholders (title tooltip only), Sound is functional. */
.icon-rail[b-nuzuh4nngs]{position:fixed;top:50%;left:0;transform:translate(-75%,-50%);display:flex;flex-direction:column;gap:.55rem;padding:.6rem;background:#0d1220ee;border:1px solid #27334f;border-left:none;border-radius:0 14px 14px 0;transition:transform .2s ease;z-index:20}
.icon-rail:hover[b-nuzuh4nngs],.icon-rail:focus-within[b-nuzuh4nngs]{transform:translate(0,-50%)}
.rail-icon[b-nuzuh4nngs]{width:2.5rem;height:2.5rem;border:1px solid #34405c;border-radius:10px;background:#141a2a;color:#bfc9e5;font-size:1.15rem;cursor:not-allowed;opacity:.6}
.rail-icon.sound[b-nuzuh4nngs]{cursor:pointer;opacity:1}
.rail-icon.sound.off[b-nuzuh4nngs]{color:#8e9ab8;border-color:#5a3030}

/* Shown in the Encounter panel's place until at least one hostile is introduced -- avoids spoiling unmet creatures. */
.location-card[b-nuzuh4nngs]{margin:1rem;padding:1.1rem;border:1px solid #303b57;border-radius:14px;background:#111827d9}
.location-card p[b-nuzuh4nngs]{margin:0 0 .8rem;font-size:.85rem;line-height:1.6;color:#c3cbe6}
.location-card p:last-child[b-nuzuh4nngs]{margin-bottom:0;color:#8e9ab8;font-style:italic}

/* ⤳ #344. Six `.enter-*` rules deleted here — a one-time audio gate this page has not rendered for a
   long time, on `master` either. They could never have applied: the only components that draw
   `.enter-button` are Login, Register and Account, which carry their own scope attributes, and those
   three are styled by `.login-shell .enter-button` in app.css. Dead in both directions. Dave: "kill the
   dead css - if its needed again, we will re-create it." */
/* ⭐ The floating layout's chrome, in the shell's strip (#345). It lives HERE and not in FloatHost's
   sheet because the shell draws it — a rule scoped to FloatHost could never reach markup this component
   renders, which is the mistake that left the whole window chrome unstyled two commits ago. */
.strip-action[b-nuzuh4nngs]{border:1px solid #303b58;background:#151b2b;color:#bec9e7;border-radius:8px;
    padding:.25rem .6rem;font-size:.72rem;font-weight:700;cursor:pointer;white-space:nowrap}
.strip-action:hover[b-nuzuh4nngs]{background:#1d2540;border-color:#4a5c86}

/* The centred group. `margin-inline:auto` puts it in the CENTRE of the strip, as asked, by taking the
   free space either side of it rather than by guessing a width — and it lives on the GROUP rather than on
   each disclosure because two elements each taking the free space around themselves end up spread across
   the strip, not sitting together in the middle of it. */
.strip-centre[b-nuzuh4nngs]{display:flex;align-items:center;gap:.4rem;margin-inline:auto}
.window-opener[b-nuzuh4nngs]{position:relative;font-size:.72rem}
.window-opener > summary[b-nuzuh4nngs]{list-style:none;cursor:pointer;padding:.25rem .6rem;border-radius:8px;
    border:1px solid #303b58;background:#151b2b;color:#bec9e7;font-weight:700;white-space:nowrap}
.window-opener > summary[b-nuzuh4nngs]::-webkit-details-marker{display:none}
.window-opener > summary:hover[b-nuzuh4nngs]{background:#1d2540;border-color:#4a5c86}
.window-opener[open] > summary[b-nuzuh4nngs]{background:#1d2540;border-color:#dcb763;color:#e8c36a}
/* Absolute so opening the list does not shove the strip's other controls sideways — the strip is one row
   and the list is a menu over the page, not a thing that grows in it. z above the panes' top band (400+),
   because it hangs INTO the pane area even though its owner does not. */
.window-opener-list[b-nuzuh4nngs]{position:absolute;top:calc(100% + .35rem);left:50%;transform:translateX(-50%);
    z-index:600;min-width:11rem;display:flex;flex-direction:column;gap:.2rem;
    padding:.35rem;border:1px solid #303b58;border-radius:10px;background:#0e1320f2;
    box-shadow:0 22px 70px #03060d90}
.window-opener-list button[b-nuzuh4nngs]{text-align:left;border:0;border-radius:6px;background:transparent;
    color:#bec9e7;padding:.3rem .5rem;font-size:.72rem;cursor:pointer}
.window-opener-list button:hover[b-nuzuh4nngs]{background:#1d2540;color:#dfe6f6}
.window-opener-empty[b-nuzuh4nngs]{margin:0;padding:.3rem .5rem;color:#8e9ab8;font-style:italic}

/* The named layouts shelf (#346). It rides on `.window-opener`'s chrome deliberately — it is the same
   kind of control one along in the same strip, and giving it a second look would say the two do different
   kinds of thing. Only what is genuinely new is styled here.

   ⛔ EVERY BUTTON RULE BELOW IS PREFIXED WITH `.layout-book`, and that is arithmetic rather than tidiness.
   These buttons sit inside `.window-opener-list`, whose own `…-list button` rule is (0,1,1); a bare
   `.layout-icon` is (0,1,0) and LOSES to it — silently, with the icons quietly wearing the Windows list's
   padding and alignment. The prefix makes each one (0,2,0) and puts it back in charge of its own element. */
.layout-book .window-opener-list[b-nuzuh4nngs]{min-width:15rem}
.layout-row[b-nuzuh4nngs]{display:flex;align-items:center;gap:.2rem}
/* The name takes the slack so the icons stay put at the right, whatever the layouts are called. */
.layout-book .layout-apply[b-nuzuh4nngs]{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
    font-weight:700}
.layout-book .layout-icon[b-nuzuh4nngs]{flex:none;border:0;border-radius:6px;background:transparent;color:#8e9ab8;
    padding:.3rem .35rem;font-size:.72rem;cursor:pointer;line-height:1}
.layout-book .layout-icon:hover[b-nuzuh4nngs]{background:#1d2540;color:#dfe6f6}
.layout-book .layout-delete:hover[b-nuzuh4nngs]{background:#3a1d24;color:#f0a9b3}
.layout-save[b-nuzuh4nngs]{display:flex;align-items:center;gap:.3rem;margin-top:.25rem;padding-top:.35rem;
    border-top:1px solid #26304a}
.layout-name[b-nuzuh4nngs]{flex:1;min-width:0;border:1px solid #303b58;border-radius:6px;background:#0b1020;
    color:#dfe6f6;padding:.25rem .4rem;font-size:.72rem;font-family:inherit}
.layout-name:focus[b-nuzuh4nngs]{outline:0;border-color:#dcb763}
/* The strip's own button look, restated for the same reason — `.strip-action` alone cannot outrank the
   list rule it now sits inside. */
.layout-save .strip-action[b-nuzuh4nngs]{border:1px solid #303b58;background:#151b2b;color:#bec9e7;
    padding:.25rem .5rem;text-align:center}
.layout-save .strip-action:hover[b-nuzuh4nngs]{background:#1d2540;border-color:#4a5c86}
/* A save button that cannot save says so, rather than being pressed and doing nothing. */
.layout-save .strip-action:disabled[b-nuzuh4nngs]{opacity:.45;cursor:default}
.layout-save .strip-action:disabled:hover[b-nuzuh4nngs]{background:#151b2b;border-color:#303b58}
.layout-note[b-nuzuh4nngs]{margin:.3rem 0 0;padding:.3rem .5rem;color:#a8b3cd;font-style:italic;line-height:1.5}
.layout-book .layout-undo[b-nuzuh4nngs]{margin-left:.4rem;border:0;border-radius:6px;background:#1d2540;color:#e8c36a;
    padding:.15rem .4rem;font-size:.7rem;font-style:normal;font-weight:700;cursor:pointer}
.layout-book .layout-undo:hover[b-nuzuh4nngs]{background:#28324f}

.play-tools[b-nuzuh4nngs]{border-bottom:1px solid #27334f}
/* ⤳ .command-feedback moved to wwwroot/play.css (#261) — it is rendered by the combat pane now. */
/* ⤳ .message.npc .message-body went to wwwroot/play.css with the rest of the bubble family (#261
   phase 3). It was found by reading the COMPILED bundle rather than the source: it had been left
   behind here, where it compiles to `.message.npc .message-body[b-fdu3vypdan]` and can never again
   match markup TableLogView renders. A companion's purple bubble would simply have gone grey. */

.sound-toggle[b-nuzuh4nngs]{margin-left:.6rem;border:0;background:transparent;color:inherit;cursor:pointer;font-size:.9rem}
/* The author's instruments (issue #256). Tinted rather than plain, because unlike its neighbours in this
   strip it is not a view preference — pressing what it opens changes the campaign. */
.designer-toggle[b-nuzuh4nngs]{margin-left:.6rem;border:0;background:transparent;color:#7fb3d5;cursor:pointer;font-size:.9rem}
.designer-toggle:hover[b-nuzuh4nngs]{color:#cfe3f5}
/* ⤳ The scene card, its picture, the read-aloud, the DM notes and the objectives card moved to
   ScenePane.razor.css (#261 phase 3), along with .end-adventure, the shop card, .ask-hint and
   .engine-note from further down and from the base block. They style that pane's own markup now, and a
/* The level-up panel's rules used to live here, and none of them ever applied: LevelUpPanel is its own
   component, so this file's scope attribute could never reach its markup. They now live in
   LevelUpPanel.razor.css, where they work. The three rules above are Play's OWN hero XP bar and stay. */

/* ⛔⛔ #1495 — AND IT IS THE SAME FAULT AS THE PARAGRAPH DIRECTLY ABOVE. The world clock's rules used to
   sit right here and none of them had applied since the clock became `WorldClock.razor`: they compiled to
   `.world-clock[b-fdu3vypdan]`, Play's scope, against markup a different component renders. So /play's
   clock has been drawing as bare text — no box, no gold time, no uppercase date — and nothing reported it.
   Found while mounting the same component on FenReach's HUD, where it would have arrived looking the same.
   They now live in WorldClock.razor.css with the rest of that component's vocabulary. */

/* Loading veil. Held until the first game state arrives rather than until initialisation returns — the
   shell renders long before there is a world in it, and a half-built table you can click is worse than
   an honest wait. Deliberately blocking: the point is that the UI is unavailable, not merely dim. */
.game-shell.loading[b-nuzuh4nngs]{pointer-events:none;user-select:none;opacity:.5;filter:saturate(.55) blur(1.5px)}
.load-veil[b-nuzuh4nngs]{position:fixed;inset:0;z-index:var(--dm-layer-scrim,900);display:flex;align-items:center;justify-content:center;padding:1.5rem;background:#070b12e6;backdrop-filter:blur(4px)}
.load-card[b-nuzuh4nngs]{max-width:26rem;width:100%;text-align:center;padding:2rem 2.25rem;border:1px solid #303b57;border-radius:18px;background:#0e1320f2;box-shadow:0 22px 70px #03060d90}
.load-card h2[b-nuzuh4nngs]{margin:.9rem 0 .5rem;font-family:Georgia,serif;font-size:1.35rem;color:#faf7ef;font-weight:600}
.load-step[b-nuzuh4nngs]{margin:0;font-size:.9rem;color:#f4d58d}
.load-hint[b-nuzuh4nngs]{margin:.7rem 0 0;font-size:.72rem;line-height:1.5;color:#8e9ab8;font-style:italic}
.load-spinner[b-nuzuh4nngs]{width:2.6rem;height:2.6rem;margin:0 auto;border-radius:50%;border:3px solid #27334f;border-top-color:#e8c36a;animation:load-spin-b-nuzuh4nngs 900ms linear infinite}
@keyframes load-spin-b-nuzuh4nngs{to{transform:rotate(360deg)}}
/* A spinner is decoration; a reader who asked for stillness still needs the words. */
@media (prefers-reduced-motion:reduce){.load-spinner[b-nuzuh4nngs]{animation:none;border-top-color:#e8c36a}}
.load-step.stalled[b-nuzuh4nngs]{color:#ffb8be}
.load-actions[b-nuzuh4nngs]{margin:.9rem 0 0;font-size:.78rem;color:#8e9ab8}
.load-actions a[b-nuzuh4nngs]{color:#e8c36a;text-decoration:none}
.load-actions a:hover[b-nuzuh4nngs]{text-decoration:underline}
/* The spinner stops pretending to make progress once we have admitted there is none. */
.load-veil:has(.stalled) .load-spinner[b-nuzuh4nngs]{animation:none;border-top-color:#8d4046}

/* ⭐⭐ #531 — the hold, drawn. Unlike the loading veil above this one is NOT blocking, and the difference
   is the whole design: the load veil covers a table that does not exist yet, and this one covers a table
   the player is entitled to keep reading while the Game Master writes a sentence about it. The refusals
   are real and live elsewhere (Session.Working on the controls, Busy on the board); this is the part that
   says WHY.

   ⛔ `pointer-events:none` on both the veil and the card. A scrim that ate the scroll wheel would take
   away the one thing there is to do while you wait, which is read what just happened. */
.thinking-veil[b-nuzuh4nngs]{position:fixed;inset:0;z-index:var(--dm-layer-scrim,900);display:flex;
    align-items:center;justify-content:center;padding:1.5rem;pointer-events:none;
    background:#070b12b3;backdrop-filter:blur(2px)}
/* ⭐ THE FLOOR, and it costs the table nothing. A narration that comes back in 200 ms would otherwise
   produce a flash of modal — worse than no modal, because a frame you cannot read is just a flicker. The
   alternative on the issue was a MINIMUM VISIBLE TIME, which would have held a table that had already
   finished; an animation delay holds nothing at all. Below the delay the card never reaches an opacity a
   person can see, and it is gone before it arrives.

   ⚠️ `backwards` fill is load-bearing: without it the element paints at full opacity for the length of the
   delay, which is the exact flash this exists to remove. */
.thinking-card[b-nuzuh4nngs]{max-width:30rem;width:100%;text-align:center;padding:1.8rem 2rem;
    border:1px solid #3c4a6d;border-radius:18px;background:#0e1320f5;box-shadow:0 22px 70px #03060d90;
    pointer-events:none;opacity:0;animation:thinking-in-b-nuzuh4nngs 320ms ease-out 400ms forwards backwards}
@keyframes thinking-in-b-nuzuh4nngs{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
.thinking-card h2[b-nuzuh4nngs]{margin:.9rem 0 .6rem;font-family:Georgia,serif;font-size:1.25rem;color:#faf7ef;
    font-weight:600}
/* The engine's own line for the event. Serif and quoted-feeling because it is STORY, not status — the
   whole reason the issue asks for it is that it turns the wait into a beat rather than a spinner. */
.thinking-about[b-nuzuh4nngs]{margin:0;font-family:Georgia,serif;font-size:.95rem;line-height:1.55;color:#f4d58d;
    font-style:italic}
.thinking-hint[b-nuzuh4nngs]{margin:.9rem 0 0;font-size:.72rem;line-height:1.5;color:#8e9ab8}
.thinking-spinner[b-nuzuh4nngs]{width:2.2rem;height:2.2rem;margin:0 auto;border-radius:50%;border:3px solid #27334f;
    border-top-color:#e8c36a;animation:load-spin-b-nuzuh4nngs 900ms linear infinite}
/* Same ruling as the loading spinner: a reader who asked for stillness still needs the words, and the
   words are the half that matters here. The card itself stops sliding in and simply appears. */
@media (prefers-reduced-motion:reduce){
    .thinking-spinner[b-nuzuh4nngs]{animation:none;border-top-color:#e8c36a}
    .thinking-card[b-nuzuh4nngs]{animation:thinking-appear-b-nuzuh4nngs 1ms linear 400ms forwards backwards}
}
@keyframes thinking-appear-b-nuzuh4nngs{from{opacity:0}to{opacity:1}}

/* ⤳ Target-and-strike — .strike-card, .strike-hint, .target-row, .target-chip, .strike-row and the
   .strike-button family — moved to wwwroot/play.css (#261). It is the combat pane's own markup now,
   and the action rows it shares with the out-of-combat panel had to end up in one place. */
/* ⤳ The pulse line and the whole outcome-word family — the ::deep b.hit / b.crit / b.miss / b.fumble
   rules and the .verb chips — moved to TableLogView.razor.css (#261 phase 3), SCOPED, because ::deep is
   load-bearing on every one of them and only works from the sheet of the component that renders the
   MarkupString. The bubble vocabulary they sit inside (.message, .avatar, .message-meta) went the other
   way, to wwwroot/play.css, because three surfaces draw it. The split is deliberate: content shared by
   panes goes global, a rule that must reach INTO runtime-injected markup goes with its renderer. */

/* ⤳ The action rows — .strike-button.action, .action-row, .skill-pick, .cost-pick, .more-actions and
   .glyph — moved to wwwroot/play.css (#261). These are the ones shared by the combat surface and the

/* ⤳ .temp-hp moved to NarrationPane.razor.css (#261 phase 3) with the character chips. */

/* ⤳ .strike-button.action.needs-target moved to wwwroot/play.css (#261). */

/* The floating-layout opt-in (#261 phase 6). Sits with the other view controls in the header strip and
   is styled like them: this is a preference, not an action on the world. */
.layout-toggle[b-nuzuh4nngs]{margin-left:.4rem;border:1px solid #303b58;background:#151b2b;color:#bec9e7;
    border-radius:8px;padding:.2rem .45rem;cursor:pointer;line-height:1}
.layout-toggle:hover[b-nuzuh4nngs]{background:#243050;border-color:#4a5c86}

/* A budget silenced the narrator. Deliberately quiet — it is information, not an error: the words are
   still on screen and the game is still running. Loud styling here would read as a fault. */
.budget-notice[b-nuzuh4nngs] { display: inline-flex; align-items: center; gap: .3rem; font-size: .8rem; color: #e8c27c; }
.budget-notice a[b-nuzuh4nngs] { color: inherit; text-decoration: underline; }

/* #869 — the voice picker in the character sheet's toolbar. Small and quiet: it sits beside the close
   button on a play surface, so it must read as a control and never compete with the sheet itself. */
.sheet-voice[b-nuzuh4nngs]{display:flex;align-items:center;gap:.4rem;font-size:.7rem;color:#8b7a52}
.sheet-voice select[b-nuzuh4nngs]{padding:.3rem .4rem;font-size:.72rem;background:#1d1710;color:#efe3c8;border:1px solid #5b4a2c;border-radius:6px;max-width:11rem}
.sheet-voice select:disabled[b-nuzuh4nngs]{opacity:.6}
/* The card for whoever the party clicked on (Dave, 2026-09-03).

   ⭐ The FRAME is the shared EncounterCard's — portrait, banner, rows — so this person's card cannot
   drift from a place's or a fight's. What lives here is only what goes in its note: the description,
   the greeting they opened with, and each piece of work with its summary above its button. */
.person-background[b-nuzuh4nngs],
.person-nothing[b-nuzuh4nngs] {
    margin: 0 0 0.5rem;
}

/* What they said as the card opened, set apart from the author's description: it is speech. */
.person-greeting[b-nuzuh4nngs] {
    margin: 0 0 0.6rem;
    font-style: italic;
    opacity: 0.85;
}

/* One quest: its name, "the information spew", then the one button that takes it on or hands it in.
   ⛔ The summary comes BEFORE the button on purpose — a player reads what the work is, then decides. */
.person-quest[b-nuzuh4nngs] {
    margin: 0 0 0.6rem;
    padding-top: 0.5rem;
    border-top: 1px solid rgb(255 255 255 / 12%);
}

.person-quest strong[b-nuzuh4nngs] { display: block; margin-bottom: 0.2rem; }
.person-quest p[b-nuzuh4nngs] { margin: 0 0 0.4rem; font-size: 0.9em; opacity: 0.85; }

/* ⚠️ Full width and stacked: these are the only things on this card that DO anything, and a row of
   them would make the longest quest name decide the card's width. */
.person-offer[b-nuzuh4nngs] {
    display: block;
    width: 100%;
    text-align: left;
    padding: 0.35rem 0.5rem;
    border-radius: 0.3rem;
    border: 1px solid var(--border-strong, #3a352d);
    background: var(--surface-sunken, #14120f);
    color: inherit;
    cursor: pointer;
    font-size: 0.85rem;
}

.person-offer:hover:not(:disabled)[b-nuzuh4nngs] { border-color: var(--accent, #c9a227); }
.person-offer:disabled[b-nuzuh4nngs] { opacity: 0.5; cursor: default; }

/* ⭐ The hand-in is the one they walked back across the fen for, so it is the one that is coloured. */
.person-offer.hand-in[b-nuzuh4nngs] { border-color: var(--accent, #c9a227); font-weight: 600; }

/* ⭐ 2026-09-03: the person card is the shared EncounterCard, which veils and centres itself on the
   window. The host is no box at all, here as in MapPane's sheet — a positioned, clipped wrapper would
   pin a modal into a corner and cut it off. */
.map-fullscreen .map-person-card[b-nuzuh4nngs] {
    display: contents;
}
/* /Components/Pages/PlayHome.razor.rz.scp.css */
.home-shell[b-yb9bxzvhwn]{min-height:100vh;padding:clamp(1.25rem,3vw,3rem);max-width:1100px;margin:auto;color:#e7eaf2;background:linear-gradient(rgba(8,12,20,.78),rgba(8,12,20,.9)),url('/images/bg-play.jpg');background-size:cover;background-position:center}
.home-header[b-yb9bxzvhwn]{margin-bottom:2rem}
.home-header h1[b-yb9bxzvhwn]{margin:0;font-family:Georgia,serif;font-size:clamp(1.55rem,3vw,2.35rem);color:#faf7ef}
.eyebrow[b-yb9bxzvhwn]{font-size:.7rem;font-weight:800;letter-spacing:.14em;color:#8b96b6;margin:0 0 .4rem}
.home-card[b-yb9bxzvhwn]{background:linear-gradient(145deg,#141a2ae6,#0e1320e6);border:1px solid #27334f;border-radius:18px;box-shadow:0 22px 70px #03060d70;padding:1.4rem;margin-bottom:1.5rem}
.home-card h2[b-yb9bxzvhwn]{margin:0 0 1rem;font-family:Georgia,serif;font-size:1.2rem}
.section-head[b-yb9bxzvhwn]{display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;margin-bottom:1rem}
.section-head h2[b-yb9bxzvhwn]{margin:0}
.muted[b-yb9bxzvhwn]{color:#8e9ab8;font-size:.85rem}
.campaign-list[b-yb9bxzvhwn]{list-style:none;margin:0;padding:0;display:grid;gap:.6rem}
.campaign-row[b-yb9bxzvhwn]{display:flex;align-items:center;gap:1rem;padding:.9rem 1.1rem;border:1px solid #303b57;border-radius:12px;background:#111827d9;color:#e7eaf2;text-decoration:none}
.campaign-row:hover[b-yb9bxzvhwn]{border-color:#4a5c88}
.campaign-row strong[b-yb9bxzvhwn]{font-size:.95rem}
.campaign-row span[b-yb9bxzvhwn]{font-size:.75rem;color:#9da8c5}
.campaign-row .resume[b-yb9bxzvhwn]{margin-left:auto;color:#e8c36a;font-weight:700}
.campaign-row.past[b-yb9bxzvhwn]{opacity:.72;cursor:default}
.campaign-row.past:hover[b-yb9bxzvhwn]{border-color:#303b57}
.campaign-row .outcome[b-yb9bxzvhwn]{margin-left:auto;color:#b9a0a4;font-weight:700;white-space:nowrap}
.past-heading[b-yb9bxzvhwn]{margin:1.2rem 0 .6rem}
.past-toggle[b-yb9bxzvhwn]{display:flex;align-items:center;gap:.45rem;font-size:.78rem;color:#9da8c5;cursor:pointer}
.past-toggle input[b-yb9bxzvhwn]{accent-color:#e8c36a;cursor:pointer}
.campaign-list li[b-yb9bxzvhwn]{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap}
.campaign-list li > .campaign-row[b-yb9bxzvhwn]{flex:1;min-width:min(100%,320px)}
.leave-confirm[b-yb9bxzvhwn]{flex-basis:100%;display:flex;align-items:center;gap:.6rem;flex-wrap:wrap;margin:0}

/* A campaign playing on content the library has moved past (issue #17). A note, not an alarm: nothing
   is wrong, and the campaign is meant to be finished as it stands. */
.content-age[b-yb9bxzvhwn]{flex-basis:100%;margin:0;font-size:.76rem;line-height:1.45;color:#a79a7c}
.age-mark[b-yb9bxzvhwn]{display:inline-block;margin-right:.4rem;padding:.05rem .4rem;border-radius:999px;
    border:1px solid #6f5c33;background:#1d1a12;color:#e0c88a;font-size:.66rem;font-weight:700;
    letter-spacing:.05em;text-transform:uppercase}
.leave-button[b-yb9bxzvhwn]{border:1px solid #8d4046;border-radius:10px;padding:.62rem 1rem;font-weight:800;font-size:.78rem;background:linear-gradient(135deg,#7c2a30,#a5383f);color:#ffe6e7;cursor:pointer;white-space:nowrap}
.leave-button:hover[b-yb9bxzvhwn]{background:linear-gradient(135deg,#93323a,#c04149);border-color:#c9545c}
.leave-button:disabled[b-yb9bxzvhwn]{opacity:.55;cursor:not-allowed}
.quiet-button[b-yb9bxzvhwn]{border:1px solid #34405c;border-radius:10px;padding:.62rem 1rem;font-weight:700;font-size:.78rem;background:#141b2b;color:#c6cfe6;cursor:pointer;white-space:nowrap}
.quiet-button:hover[b-yb9bxzvhwn]{border-color:#4a5c88}
.quiet-button:disabled[b-yb9bxzvhwn]{opacity:.55;cursor:not-allowed}
.character-grid[b-yb9bxzvhwn]{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:.7rem}
.character-card[b-yb9bxzvhwn]{position:relative;display:flex;gap:.8rem;align-items:center;padding:.8rem;border:1px solid #303b57;border-radius:12px;background:#111827d9}
.character-card.editable:hover[b-yb9bxzvhwn]{border-color:#4a5c88}
/* Sits left of the delete ✕, which owns the far corner. A card with no delete control (one already out on
   an adventure) leaves this gap unfilled, which is fine — the pencil never moves, so its position is
   learnable either way. */
.card-edit[b-yb9bxzvhwn]{position:absolute;top:.45rem;right:2.45rem;width:1.7rem;height:1.7rem;border-radius:999px;border:1px solid #34405c;background:#0d1322;color:#8e9ab8;font-size:.8rem;line-height:1;display:grid;place-items:center;text-decoration:none}
.card-edit:hover[b-yb9bxzvhwn]{border-color:#e8c36a;color:#f5e3b0}
.character-card.busy[b-yb9bxzvhwn]{opacity:.85}
/* Issue #622. Dimmer than busy and turned away from the gold: a card the player is meant to read and
   then leave alone, not one that is merely occupied this week. */
.character-card.dead[b-yb9bxzvhwn]{opacity:.72;border-color:#4b3742}
.character-card img[b-yb9bxzvhwn],.portrait-fallback[b-yb9bxzvhwn]{width:64px;height:64px;border-radius:10px;object-fit:cover;border:1px solid #34405c;flex-shrink:0}
.portrait-fallback[b-yb9bxzvhwn]{display:grid;place-items:center;font-family:Georgia,serif;font-size:1.6rem;color:#5d6b8e;background:#0d1322}
.character-meta[b-yb9bxzvhwn]{display:flex;flex-direction:column;gap:.15rem;min-width:0}
.character-meta strong[b-yb9bxzvhwn]{font-size:.92rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* The name is a control now, but it must still read as the card's title — no button chrome, just the
   underline on hover that says it does something. */
.character-name[b-yb9bxzvhwn]{padding:0;border:0;background:transparent;font:inherit;font-size:.92rem;font-weight:700;color:#faf7ef;text-align:left;cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}
.character-name:hover[b-yb9bxzvhwn]{color:#e8c36a;text-decoration:underline}
.character-meta span[b-yb9bxzvhwn]{font-size:.74rem;color:#9da8c5}
.character-meta em[b-yb9bxzvhwn]{font-size:.7rem;font-style:normal;font-weight:700}
.tag-ready[b-yb9bxzvhwn]{color:#9fd3b4}
.tag-busy[b-yb9bxzvhwn]{color:#e8c36a}
.tag-dead[b-yb9bxzvhwn]{color:#d98f9c}
/* Both parents set the colour of a bare span at (0,1,1), so the obituary needs two classes deep to win
   — a lone .death-circumstance would parse, apply, and then silently lose to .character-meta span. */
.character-meta .death-circumstance[b-yb9bxzvhwn],.option-card .death-circumstance[b-yb9bxzvhwn]{font-size:.7rem;font-style:italic;color:#b08e97;line-height:1.4}
.wizard[b-yb9bxzvhwn]{display:grid;gap:1.4rem}
.step-label[b-yb9bxzvhwn]{font-size:.72rem;font-weight:800;letter-spacing:.12em;color:#b7c2df;margin:0 0 .6rem}
.option-grid[b-yb9bxzvhwn]{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:.7rem}
.option-card[b-yb9bxzvhwn]{position:relative;display:flex;flex-direction:column;gap:.35rem;text-align:left;padding:.9rem 1rem;border:1px solid #303b57;border-radius:12px;background:#111827d9;color:#e7eaf2;cursor:pointer}
.option-card:hover[b-yb9bxzvhwn]{border-color:#4a5c88}
.option-card.selected[b-yb9bxzvhwn]{border-color:#e8c36a;box-shadow:0 0 0 1px #e8c36a55}
.option-card.locked[b-yb9bxzvhwn]{cursor:default;opacity:.6}
.option-card.locked:hover[b-yb9bxzvhwn]{border-color:#303b57}
.option-card strong[b-yb9bxzvhwn]{font-size:.92rem;padding-right:4.2rem}
.option-card em[b-yb9bxzvhwn]{font-size:.7rem;color:#e8c36a;font-style:normal;font-weight:700}
.option-card span[b-yb9bxzvhwn]{font-size:.75rem;line-height:1.45;color:#9da8c5}
.template-badge[b-yb9bxzvhwn]{position:absolute;top:.55rem;right:.6rem;font-style:normal;font-size:.56rem;font-weight:800;letter-spacing:.11em;text-transform:uppercase;color:#8b96b6;background:#1a2338cc;border:1px solid #34405c;border-radius:999px;padding:.22rem .5rem}
.busy-badge[b-yb9bxzvhwn]{color:#e8c36a;border-color:#e8c36a55}
.dead-badge[b-yb9bxzvhwn]{color:#d98f9c;border-color:#d98f9c55}
.seat-row[b-yb9bxzvhwn]{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:.7rem}
.seat[b-yb9bxzvhwn]{position:relative;border:1px solid #303b57;border-radius:12px;background:#111827d9;min-height:74px;display:flex}
.seat.filled[b-yb9bxzvhwn]{border-color:#4a5c88}
.seat.empty[b-yb9bxzvhwn]{flex-direction:column;align-items:center;justify-content:center;gap:.2rem;color:#8e9ab8;cursor:pointer;border-style:dashed;font-size:.8rem}
.seat.empty:hover[b-yb9bxzvhwn]{border-color:#e8c36a;color:#e8c36a}
.seat .plus[b-yb9bxzvhwn]{font-size:1.3rem;line-height:1}
.seat-body[b-yb9bxzvhwn]{flex:1;display:flex;flex-direction:column;gap:.25rem;text-align:left;padding:.8rem 2.2rem .8rem 1rem;background:transparent;border:0;color:#e7eaf2;cursor:pointer;border-radius:12px}
.seat-body:hover[b-yb9bxzvhwn]{background:#16203699}
.seat-body strong[b-yb9bxzvhwn]{font-size:.92rem}
.seat-body span[b-yb9bxzvhwn]{font-size:.72rem;color:#9da8c5}
.seat-remove[b-yb9bxzvhwn]{position:absolute;top:.45rem;right:.45rem;width:1.6rem;height:1.6rem;border-radius:999px;border:1px solid #34405c;background:#0d1322;color:#8e9ab8;cursor:pointer;font-size:.72rem;line-height:1;display:grid;place-items:center}
.seat-remove:hover[b-yb9bxzvhwn]{border-color:#8d4046;color:#ffb2b6}
.selector-backdrop[b-yb9bxzvhwn]{position:fixed;inset:0;background:#04060cb8;display:grid;place-items:center;padding:1.2rem;z-index:var(--dm-layer-dialog,1000)}
.selector-panel[b-yb9bxzvhwn]{position:relative;max-width:900px;width:100%;max-height:85vh;overflow-y:auto;background:linear-gradient(145deg,#141a2af5,#0e1320f5);border:1px solid #34405c;border-radius:18px;box-shadow:0 30px 90px #03060da0;padding:1.4rem}
.selector-panel .seat-remove[b-yb9bxzvhwn]{position:static}
.selector-panel h2[b-yb9bxzvhwn]{margin:0;font-family:Georgia,serif;font-size:1.15rem}
.selector-panel .option-grid[b-yb9bxzvhwn]{margin-bottom:1.1rem}
.create-row[b-yb9bxzvhwn]{display:flex;gap:.7rem;flex-wrap:wrap;align-items:center}
.create-row input[b-yb9bxzvhwn]{flex:1;min-width:220px;background:#141b2b;color:#eef2fb;border:1px solid #34405c;border-radius:10px;padding:.85rem}
/* Issue #249. Matches .create-row input rather than inventing a second field look. */
.wandering-dial[b-yb9bxzvhwn]{display:flex;gap:.7rem;flex-wrap:wrap;align-items:center}
.wandering-dial select[b-yb9bxzvhwn]{flex:1;min-width:220px;background:#141b2b;color:#eef2fb;border:1px solid #34405c;border-radius:10px;padding:.85rem}
.create-button[b-yb9bxzvhwn]{border:0;border-radius:10px;padding:.85rem 1.4rem;font-weight:800;background:linear-gradient(135deg,#c69446,#e8c36a);color:#16110a;cursor:pointer;text-decoration:none;display:inline-block;font-size:.85rem}
.create-button:disabled[b-yb9bxzvhwn]{opacity:.55;cursor:not-allowed}
.create-error[b-yb9bxzvhwn]{margin:.6rem 0 0;padding:.65rem;border-radius:8px;font-size:.78rem;background:#422326;color:#ffb2b6;border:1px solid #8d4046}

/* Which pack and version a card is offering. The library keeps every version it has seeded, so this is how
   you tell at a glance whether a content change is actually live. */
.option-card .pack-stamp[b-yb9bxzvhwn]{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.62rem;letter-spacing:.02em;color:#8e9ab8;opacity:.85}
.option-card.selected .pack-stamp[b-yb9bxzvhwn]{color:#c9b47a}

/* Deleting a character. The same red ✕ used to remove things everywhere else in the app (see the chip
   remove on the creation wizard) rather than a bin glyph — the emoji rendered as two grey bars in Dave's
   browser, which reads as a pause button, and a destructive control has to be unmistakable. Far corner,
   with the pencil shifted left of it: X in the corner is where a close/remove control is looked for. */
.card-delete[b-yb9bxzvhwn]{position:absolute;top:.45rem;right:.45rem;width:1.7rem;height:1.7rem;border-radius:999px;border:1px solid #5c2b31;background:#3a1e22;color:#e08b93;font-size:.8rem;line-height:1;display:grid;place-items:center;cursor:pointer}
.card-delete:hover[b-yb9bxzvhwn]{background:#7c2a30;border-color:#a8434f;color:#ffe6e7}

/* The confirm replaces the card's footer rather than opening a dialog: the character it refers to stays
   visible, which is the whole point of asking. */
.card-confirm[b-yb9bxzvhwn]{position:absolute;inset:auto .45rem .45rem .45rem;display:flex;align-items:center;gap:.35rem;padding:.35rem .45rem;border:1px solid #8d4046;border-radius:10px;background:#1a1013f2;font-size:.66rem;color:#ffd9dd}
.card-confirm span[b-yb9bxzvhwn]{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.card-confirm button[b-yb9bxzvhwn]{border-radius:7px;padding:.2rem .45rem;font-size:.66rem;font-weight:700;cursor:pointer;border:1px solid transparent}
.confirm-yes[b-yb9bxzvhwn]{background:#7d2f38;border-color:#a8434f;color:#ffe4e7}
.confirm-yes:hover:not(:disabled)[b-yb9bxzvhwn]{background:#983b47}
.confirm-no[b-yb9bxzvhwn]{background:#1a2235;border-color:#34405c;color:#bfcaf0}
.confirm-no:hover:not(:disabled)[b-yb9bxzvhwn]{background:#243050}
.card-confirm button:disabled[b-yb9bxzvhwn]{opacity:.5;cursor:default}

.roster-message[b-yb9bxzvhwn]{margin:0 0 .75rem;padding:.5rem .7rem;border-left:2px solid #6a7cae;background:#151b2c99;border-radius:0 8px 8px 0;font-size:.75rem;color:#c3cbe6}
.roster-message.bad[b-yb9bxzvhwn]{border-left-color:#8d4046;background:#42232699;color:#ffb8be}

/* The workbench button in the character sheet's header. Styled here rather than in ActorSheet's own
   sheet, because the button is this page's — the sheet only holds the slot it sits in, and scoped CSS
   would not reach it from there anyway. */
[b-yb9bxzvhwn] .sheet-bench-button {
    padding: .3rem .75rem;
    border: 1px solid #6b5a33;
    border-radius: 999px;
    background: #2a2413;
    color: #e8d9a8;
    font-size: .74rem;
    white-space: nowrap;
    cursor: pointer;
}

[b-yb9bxzvhwn] .sheet-bench-button:hover { background: #3a3119; border-color: #c2a961; }

/* Campaign runs (issue #186): the arc a run of an authored campaign shows between adventures. */
.campaign-arc[b-yb9bxzvhwn]{border:1px solid #303b57;border-radius:12px;background:#111827d9;padding:.9rem 1.1rem;margin-bottom:.8rem}
.campaign-arc-head[b-yb9bxzvhwn]{display:flex;align-items:baseline;gap:.8rem;margin-bottom:.4rem}
.campaign-arc-head strong[b-yb9bxzvhwn]{font-size:.95rem}
.campaign-arc.startable[b-yb9bxzvhwn]{border-style:dashed}
.campaign-entry-list[b-yb9bxzvhwn]{list-style:none;margin:.4rem 0;padding:0;display:flex;flex-direction:column;gap:.35rem}
.campaign-entry[b-yb9bxzvhwn]{display:flex;align-items:center;gap:.8rem;padding:.35rem .5rem;border-radius:8px;background:#0e1320}
.campaign-entry .entry-name[b-yb9bxzvhwn]{font-size:.85rem}
.campaign-entry .entry-state[b-yb9bxzvhwn]{margin-left:auto;font-size:.72rem;color:#9da8c5}
.campaign-entry .entry-state.done[b-yb9bxzvhwn]{color:#7fbf7f}
.campaign-entry .entry-state.locked[b-yb9bxzvhwn]{color:#6b7692}
.entry-begin[b-yb9bxzvhwn]{margin-left:auto;background:#e8c36a;color:#151a28;border:none;border-radius:8px;padding:.3rem .8rem;font-weight:700;cursor:pointer}
.entry-begin:hover[b-yb9bxzvhwn]{background:#f0d08a}
.entry-begin:disabled[b-yb9bxzvhwn]{opacity:.5;cursor:default}
.campaign-arc .entry-begin[b-yb9bxzvhwn]{margin-left:0}
.campaign-entry .entry-begin[b-yb9bxzvhwn]{margin-left:auto}

/* Settings, from the lobby. Right-aligned in the header rather than in a card of its own: it is a way
   OUT of this page, not another thing on it, and a card would compete with the adventures. */
.home-header[b-yb9bxzvhwn] { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.home-settings[b-yb9bxzvhwn] { align-self: center; padding: .5rem .8rem; white-space: nowrap; text-decoration: none;
    border: 1px solid #34405c; border-radius: 10px; background: #141b2b; color: #cfe0ef; }
.home-settings:hover[b-yb9bxzvhwn] { background: #1f2a37; border-color: #7ea3c4; }
/* /Components/Pages/PlaySettings.razor.rz.scp.css */
/* The player's settings. Reads as the play surface's cooler sibling: same slate, less firelight —
   nothing here is urgent and nothing here is a decision the table is waiting on. */

.settings-page[b-tepv55ob6x] { max-width: 62rem; margin: 0 auto; padding: 1rem; display: grid; gap: 1.25rem; }

.settings-top[b-tepv55ob6x] { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.settings-top h1[b-tepv55ob6x] { margin: .15rem 0; }
.eyebrow[b-tepv55ob6x] { margin: 0; font-size: .72rem; letter-spacing: .12em; color: #7c88a8; }
.lede[b-tepv55ob6x] { margin: .2rem 0 0; color: #aeb8d4; max-width: 44rem; }
.settings-top-actions[b-tepv55ob6x] { display: flex; gap: .5rem; flex-wrap: wrap; }

.quiet-button[b-tepv55ob6x] { border: 1px solid #34405c; background: #141b2b; color: #cfe0ef;
    border-radius: 10px; padding: .5rem .8rem; text-decoration: none; white-space: nowrap; }
.quiet-button:hover[b-tepv55ob6x] { background: #1f2a37; border-color: #7ea3c4; }

.settings-saved[b-tepv55ob6x] { margin: 0; padding: .55rem .8rem; border-left: 3px solid #4f9e6a;
    background: rgba(79, 158, 106, .12); color: #cfe8d8; border-radius: 4px; }

.settings-group[b-tepv55ob6x] { display: grid; gap: .8rem; padding: 1rem; border: 1px solid #2a3348;
    border-radius: .5rem; background: #121826; }
.settings-group h2[b-tepv55ob6x] { margin: 0; color: #e8c36a; }
.group-note[b-tepv55ob6x] { margin: 0; color: #93a0bd; max-width: 52rem; font-size: .9rem; }

/* Spend, shown next to the switches that cause it. Separating "what it costs" from "what to turn off"
   onto different screens is how a cost page becomes something nobody acts on. */
.spend-strip[b-tepv55ob6x] { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
    padding: .6rem .8rem; background: #0e1420; border: 1px solid #263049; border-radius: .4rem; }
.spend-label[b-tepv55ob6x] { display: block; font-size: .75rem; color: #7c88a8; }
.spend-figure[b-tepv55ob6x] { font-size: 1.35rem; font-weight: 800; color: #eef2fb; font-variant-numeric: tabular-nums; }
.spend-bar[b-tepv55ob6x] { flex: 1; min-width: 8rem; height: .5rem; background: #1c2333; border-radius: 999px; overflow: hidden; }
.spend-fill[b-tepv55ob6x] { height: 100%; background: linear-gradient(90deg, #4f9e6a, #c69446); }
.spend-fill.spent[b-tepv55ob6x] { background: #8d2033; }
.spend-cap[b-tepv55ob6x] { font-size: .8rem; color: #93a0bd; }

.category-table[b-tepv55ob6x] { width: 100%; border-collapse: collapse; }
.category-table th[b-tepv55ob6x], .category-table td[b-tepv55ob6x] { text-align: left; padding: .55rem .5rem;
    border-bottom: 1px solid #232c40; vertical-align: top; }
.category-table thead th[b-tepv55ob6x] { font-size: .75rem; letter-spacing: .06em; color: #7c88a8; text-transform: uppercase; }
.category-table .num[b-tepv55ob6x] { text-align: right; font-variant-numeric: tabular-nums; }
.category-name[b-tepv55ob6x] { display: block; font-weight: 700; color: #eef2fb; }
.category-note[b-tepv55ob6x] { display: block; font-size: .8rem; color: #8996b5; max-width: 26rem; font-weight: 400; }

.inherit-link[b-tepv55ob6x] { display: block; margin-top: .3rem; padding: 0; border: 0; background: none;
    color: #7aa2d8; font-size: .78rem; text-decoration: underline; cursor: pointer; text-align: left; }

.settings-group select[b-tepv55ob6x], .settings-group input[type="number"][b-tepv55ob6x] {
    min-width: 0; padding: .45rem; color: #eef2fb; background: #141b2b;
    border: 1px solid #34405c; border-radius: 8px; font: inherit; }

/* The one warning on the page. Amber not red: it is a consequence being explained, not a mistake. */
.route-warning[b-tepv55ob6x] { display: block; margin-top: .3rem; max-width: 22rem;
    font-size: .78rem; color: #e8c27c; }

.voice-switch[b-tepv55ob6x] { display: inline-flex; align-items: center; gap: .4rem; color: #cfe0ef; }
.voice-switch input[b-tepv55ob6x] { width: auto; }

.budget-row[b-tepv55ob6x] { display: grid; gap: .4rem; padding-top: .4rem; border-top: 1px solid #232c40; }
.budget-row label[b-tepv55ob6x] { display: grid; gap: .3rem; max-width: 22rem; color: #cfe0ef; }

.field-row[b-tepv55ob6x] { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: .8rem; }
.field-row label[b-tepv55ob6x] { display: grid; gap: .3rem; color: #cfe0ef; font-size: .9rem; }

.link-row[b-tepv55ob6x] { display: grid; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); gap: .6rem; }
.settings-link[b-tepv55ob6x] { display: grid; gap: .15rem; padding: .7rem .8rem; text-decoration: none;
    border: 1px solid #2a3348; border-radius: .4rem; background: #0e1420; color: #cfe0ef; }
.settings-link:hover[b-tepv55ob6x] { border-color: #7ea3c4; background: #151d2e; }
.settings-link strong[b-tepv55ob6x] { color: #eef2fb; }
.settings-link span[b-tepv55ob6x] { font-size: .8rem; color: #8996b5; }

@media (max-width: 720px) {
    .settings-top[b-tepv55ob6x] { display: grid; }
    /* The spend figure and the switches matter more than the per-category cost column on a phone. */
    .category-table thead th:last-child[b-tepv55ob6x], .category-table td:last-child[b-tepv55ob6x] { display: none; }
}
/* /Components/Play/ActionsPane.razor.rz.scp.css */
/* The out-of-combat action panel's own chrome (issue #9), moved here whole when it became a window.

   ⛔ THE COMPONENT OWNS ITS OWN CHROME. These rules were scoped to NarrationPane, which rendered this
   markup; the moment the panel moved into a component of its own they would have matched nothing at all
   — silently, with the build green and the text still in the bundle. Moved in the same commit as the
   markup, which is the only way that mistake stays impossible rather than merely unlikely. */
/* ⚠️ `flex:1 1 100%` is a claim on the composer row it USED to sit in. Kept, because in the tab layout
   it still sits there; in its own window it is the only child and the value costs nothing. */
/* An open window with nothing to offer. Said quietly — it is a prompt, not a warning. Never needed while
   the panel lived under the composer, where "nothing to show" simply meant "draw nothing". */
.ooc-idle[b-c5po4p1rv0]{margin:0;padding:.5rem;font-size:.78rem;line-height:1.6;color:#8e9ac2;font-style:italic}
.ooc-idle strong[b-c5po4p1rv0]{color:#c3cbe6;font-style:normal}

/* The out-of-combat action panel (issue #9), moved with its markup in #261 phase 3. */
/* What a character can do when there is no turn to spend (issue #9). */
.out-of-combat[b-c5po4p1rv0]{flex:1 1 100%;border:1px solid #35406a;border-radius:10px;background:#121728;padding:.55rem .7rem}
/* The fold (issue #226, the height half). Quiet and full-width so it reads as the lid on the box rather
   than as another thing to do — every other control in here is an action, and this one is furniture. */
.ooc-fold[b-c5po4p1rv0]{width:100%;display:flex;align-items:center;gap:.4rem;margin-bottom:.5rem;padding:.15rem 0;
    border:0;background:none;color:#8e9ac2;font:inherit;font-size:.7rem;font-weight:700;
    letter-spacing:.06em;text-transform:uppercase;text-align:left;cursor:pointer}
.ooc-fold:hover[b-c5po4p1rv0]{color:#c3cbe6}
.ooc-who[b-c5po4p1rv0]{display:flex;flex-wrap:wrap;gap:.35rem}
.ooc-actor[b-c5po4p1rv0]{display:inline-flex;align-items:center;gap:.4rem;border:1px solid #3a4570;background:#171e33;
    color:#c3cbe6;border-radius:999px;padding:.28rem .7rem;font-weight:700;font-size:.8rem;cursor:pointer}
.ooc-actor:hover:not(:disabled)[b-c5po4p1rv0]{background:#212a46;border-color:#6a7cae}
.ooc-actor.chosen[b-c5po4p1rv0]{background:#26325a;border-color:#8ea3dd;color:#eaf0ff}
.ooc-actor:disabled[b-c5po4p1rv0]{opacity:.5;cursor:default}
.ooc-actor .hurt[b-c5po4p1rv0]{font-weight:600;font-size:.72rem;color:#e6a5a5;padding:.05rem .35rem;border-radius:999px;background:#3a2126}
.ooc-target[b-c5po4p1rv0]{display:flex;align-items:center;gap:.4rem;margin-top:.5rem;font-size:.75rem;font-weight:700;
    letter-spacing:.06em;text-transform:uppercase;color:#8e9ac2}
.ooc-target select[b-c5po4p1rv0]{border:1px solid #3a4570;background:#171e33;color:#dbe2f5;border-radius:8px;
    padding:.25rem .45rem;font:inherit;text-transform:none;letter-spacing:normal}
.ooc-loading[b-c5po4p1rv0]{margin:.5rem 0 0;font-size:.82rem;color:#8e9ac2}
.ooc-refused[b-c5po4p1rv0]{margin:.35rem 0 0;padding-left:1.1rem;font-size:.78rem;color:#8e9ac2;display:grid;gap:.2rem}
.ooc-refused strong[b-c5po4p1rv0]{color:#c3cbe6}

/* ⤳ #949's equipment list — .ooc-equipment and its rows — moved to EquipmentList.razor.css (#955),
   because #955 mounts that list on the Combat window too and a rule scoped to THIS component would
   have reached nothing there. Renamed .equipment-rows on the way, since the list is no longer
   out-of-combat's. The fold it sits in (.more-actions) was never here: it is shared vocabulary and
   lives under `.game-shell` in wwwroot/play.css. */

/* ── "Recently used" (Dave, 2026-09-07) ─────────────────────────────────────────────────────────────
   The row above the fold is now a memory rather than a hardcoded list, so it needs a word saying what
   it is — an unlabelled handful of buttons reads as "the important ones", which is the claim the old
   hardcoded row made and got wrong. */
.ooc-recent-label[b-c5po4p1rv0] {
    margin: .35rem 0 .15rem;
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: #8b97ad;
}
/* /Components/Play/CombatActionsPane.razor.rz.scp.css */

/* ── Ammunition on the strike button (issue #119, Dave 2026-09-07) ──────────────────────────────────
   The count rides ON the button rather than beside it: "how many arrows have I got" is a question you
   ask while deciding to shoot, not afterwards. */
.ammo-count[b-z9fjv0hmzm] {
    margin-left: .3rem;
    padding: 0 .25rem;
    border-radius: .25rem;
    background: #2b2415;
    color: #d9c68f;
    font-size: .66rem;
    font-style: normal;
    font-weight: 700;
}

.ammo-count.empty[b-z9fjv0hmzm] { background: #3a1e1e; color: #e2a0a0; }

.ammo-pick[b-z9fjv0hmzm] {
    font-size: .68rem;
    max-width: 9rem;
    padding: .05rem .2rem;
}

/* ⛔ Running low, between full and empty (Dave, 2026-09-10). Amber rather than the empty red: it is a
   warning to go and buy arrows, not a refusal. Styled here because a class name with no rule draws
   nothing and errors nowhere. */
.ammo-count.low[b-z9fjv0hmzm] { background: #3a331e; color: #e8d08a; }

/* ⭐ An empty reload weapon (issue #1158). The low-ammunition amber, not the empty red: the Strike still goes
   ahead, it just costs the reload actions too. */
.ammo-count.reload-owed[b-z9fjv0hmzm] { background: #3a331e; color: #e8d08a; }
/* /Components/Play/DebugPane.razor.rz.scp.css */
/* The god-mode readout's own chrome (#250), moved here whole when it became a window (#261).

   ⛔ THE COMPONENT OWNS ITS OWN CHROME. These rules were scoped to MapPane, which rendered this markup;
   the moment the readout moved into a component of its own they would have stopped matching anything at
   all — silently, with the build green and the text still in the bundle. That is the mistake that left
   every floating window unstyled for a whole phase, and it is why this file exists at the same moment
   the markup moved rather than one commit later. */
.debug-pane[b-baihkj1hsj]{display:flex;flex-direction:column;min-height:0}
/* An open window with the overlay switched off. Said quietly — it is a prompt, not a warning. */
.debug-idle[b-baihkj1hsj]{margin:0;padding:.5rem;font-size:.72rem;line-height:1.6;color:#8e9ab8;font-style:italic}
.debug-idle strong[b-baihkj1hsj]{color:#bec9e7;font-style:normal}
/* ⚠️ `flex:0 0 auto` is the whole fix, and `max-height` was never the problem. This is the last child of
   a flex COLUMN, so with the default `flex-shrink:1` it was the thing that gave way when the panel ran
   out of room: the 11rem below is a ceiling, and the box was being squeezed to well under its floor.
   Dave, playing: "impossible to read... it's 1.5 lines of display, but when you scroll it, it scrolls
   2+ lines at a time, making at least one line unreadable for every 3."

   The scroll-jump was the same bug seen from the other end. A viewport 1.5 lines tall cannot land on a
   line boundary, so every wheel notch left a half-line clipped top and bottom. A whole number of lines
   fixes it without any scroll-snapping cleverness.

   `resize:both` because he asked for it to be resizable in game, and a readout is exactly the thing
   whose right size depends on what you are hunting. It needs a non-visible overflow to work, which the
   scroll already provides. */
.debug-readout[b-baihkj1hsj]{flex:0 0 auto;margin:.35rem 0 0;padding:.4rem .5rem;border:1px dashed #5ce1a655;
    border-radius:4px;background:#0b140f;
    /* 12 lines at the .64rem/1.35 below — a whole number of line boxes, so no half-line at the edge. */
    height:10.4rem;min-height:3.5rem;max-height:70vh;
    resize:both;overflow:auto}
.debug-line[b-baihkj1hsj]{margin:0 0 .15rem;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
    font-size:.64rem;line-height:1.35;color:#9fe8c6}
.debug-line strong[b-baihkj1hsj]{color:#5ce1a6}
/* The two states worth telling apart at a glance while walking around. */
.debug-line.unseen-by-party[b-baihkj1hsj]{color:#ffd166}
.debug-line.unseen-by-party strong[b-baihkj1hsj]{color:#ffb703}
.debug-line.seen[b-baihkj1hsj]{color:#7f8ea8}
.debug-line.quiet[b-baihkj1hsj]{color:#5d6b84;font-style:italic}
/* Dimmer than anything living, so a board full of corpses cannot be mistaken at a glance for a
   board full of threats (issue #520). */
.debug-line.dead[b-baihkj1hsj]{color:#6b7280}
.debug-line.dead strong[b-baihkj1hsj]{color:#94a3b8}
/* Issue #258. A check that fired and found nothing is NOT the same as nothing having happened, so it is
   not drawn in the "nothing here" grey above — it is a real event with real numbers, merely a dull one.
   A check that found something is the loudest line on the panel, because it is the one that changes the
   board. */
.debug-line.quiet-check[b-baihkj1hsj]{color:#9fb4d0}
.debug-line.loud-check[b-baihkj1hsj]{color:#ffd166}
.debug-line.loud-check strong[b-baihkj1hsj]{color:#ffb703}
/* A swallowed failure. Red because it is the one line here that means something is WRONG rather than
   merely hidden — the rest of this panel reports a world working as designed. */
.debug-line.engine-warning[b-baihkj1hsj]{color:#ff9d9d}
.debug-line.engine-warning strong[b-baihkj1hsj]{color:#ff6b6b}
/* /Components/Play/DmPane.razor.rz.scp.css */
/* One floating window's own chrome.

   ⭐ THIS FILE EXISTS BECAUSE THE RULES BELOW LIVED IN FloatHost.razor.css AND COULD NOT REACH A SINGLE
   ELEMENT. Scoped CSS stamps the component's `b-…` attribute onto the LAST element of each selector, and
   these elements are rendered HERE, by DmPane — so `.dm-pane-grip` written in the host's sheet compiled to
   `.dm-pane-grip[b-<FloatHost>]` against markup carrying no such attribute. DmPane had no stylesheet at
   all, so its elements carried no scope attribute of any kind, and all ten rules matched nothing: no
   absolute positioning, no height cap, no grip, no resize handle, no docking. Silently, and with a green
   source-scan guard sitting on top of it.

   ⛔ The rule this repo keeps relearning: THE COMPONENT OWNS ITS OWN CHROME. `::deep` from the host would
   also have worked, but it would leave DmPane's appearance defined somewhere DmPane cannot see — and the
   next component to mount one would inherit nothing. See the same lesson written on `.combat-stack ::deep`
   in TabHost.razor.css, which got it right two commits earlier and did not generalise. */

/* ⛔ THE HEIGHT CAP, AND IT IS NOT COSMETIC. A pane allowed to grow past the viewport puts its own resize
   corner permanently below the bottom edge — and the only gesture that could bring it back into reach was
   dragging the pane upward, which is the gesture that docks it. A closed trap, and a hand found it in
   under a minute. The body scrolls instead.

   ⭐ THE CAP IS MEASURED, NOT GUESSED. `calc(100vh - 8rem)` was a stand-in for "the chrome above the
   host, plus room to breathe" — and it was wrong by about an inch and a half, which Dave saw as a window
   that would not grow into the bottom of the screen: "the bottom of the window is a good 1.5 inches from
   the bottom of the screen, wasting quite a bit of real-estate." 8rem was never the right number and no
   single number could be, because the cap depends on how far DOWN the window has been dragged.

   `dm-panes.js` sets `--dm-pane-cap` on each pane as it paints it, from the viewport height minus that
   pane's own top. The fallback below is what a pane wears before the module has spoken, and in the small
   screen fallback where the cap does not apply at all. */
.dm-pane[b-vm3z2vrpzs]{position:absolute;top:0;left:0;display:flex;flex-direction:column;
    max-height:var(--dm-pane-cap,calc(100vh - 8rem));
    border:1px solid #27334f;border-radius:12px;overflow:hidden;
    background:linear-gradient(145deg,#141a2af2,#0e1320f2);box-shadow:0 22px 70px #03060d70;
    /* `will-change` on the composited property, so a drag never promotes and demote-thrashes mid-gesture. */
    will-change:transform}
.dm-pane-grip[b-vm3z2vrpzs]{flex:0 0 auto;display:flex;align-items:center;gap:.5rem;padding:.3rem .45rem .3rem .7rem;
    border-bottom:1px solid #27334f;background:#111827;cursor:move;user-select:none}
/* A pinned window says so with its cursor. `cursor:move` over something that will not move is the same
   broken promise as a resize handle that does not resize. */
.dm-pane.pinned > .dm-pane-grip[b-vm3z2vrpzs]{cursor:default}
/* Both states earn a visible edge, because "why will this not move" and "why is this always in front"
   are the two questions a hidden toggle guarantees. */
.dm-pane.pinned[b-vm3z2vrpzs]{border-color:#5a4a2a}
.dm-pane.on-top[b-vm3z2vrpzs]{border-color:#4a5c86;box-shadow:0 22px 70px #03060d70,0 0 0 1px #4a5c8666}
/* `min-width:0` + ellipsis so a long title yields to the control box rather than shoving it off the
   edge — the controls are the part you cannot do without. */
.dm-pane-title[b-vm3z2vrpzs]{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
    font-size:.72rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:#8b96b6}

/* ⭐ THE TAB STRIP (#347), in the title bar, where the title would be. A group is not a new kind of thing
   on screen: it is a window whose name has become a chooser.
   ⚠️ `min-width:0` and the scroll on the strip rather than the window: four grouped panes must yield to
   the control box, never shove it off the edge — the controls are the part you cannot do without. */
.dm-pane-tabs[b-vm3z2vrpzs]{flex:1;min-width:0;display:flex;align-items:center;gap:.15rem;overflow-x:auto;
    scrollbar-width:none}
.dm-pane-tabs[b-vm3z2vrpzs]::-webkit-scrollbar{display:none}
.dm-pane-tab[b-vm3z2vrpzs]{flex:0 0 auto;max-width:9rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
    border:1px solid transparent;border-radius:6px;background:transparent;color:#6f7b99;
    padding:.15rem .4rem;font-size:.66rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;
    /* The strip sets `cursor:move` for the whole bar; a tab is grabbable in its own right. */
    cursor:pointer}
.dm-pane-tab:hover[b-vm3z2vrpzs]{background:#1d2540;color:#dfe6f6}
/* The showing tab is the one lit, and it is the only one whose content exists in the document at all. */
.dm-pane-tab.active[b-vm3z2vrpzs]{background:#1b2438;border-color:#3a4a6e;color:#e8c36a}

/* ⛔ A view the state says is not a choice: greyed, still legible, and not hoverable.
   Greyed rather than hidden so the strip does not jump about as fights start and end, and so a player
   can still see that a world map exists while they are standing on a battle board. — MapTabRule */
.dm-pane-tab.unavailable[b-vm3z2vrpzs]{opacity:.38;cursor:default}
.dm-pane-tab.unavailable:hover[b-vm3z2vrpzs]{background:transparent;color:#6f7b99}

/* ⭐ WHERE A REORDERED TAB WOULD LAND (#349). Set by dm-panes.js on the neighbour during a drag inside
   the strip. A box-shadow rather than a border: a border would change the tab's WIDTH mid-drag, moving
   every tab after it and therefore moving the gap the pointer is aiming at — a target that runs away
   from the hand as it approaches. Inset so it draws inside the tab's own box and cannot be clipped by
   the strip's overflow. */
.dm-pane-tab.drop-before[b-vm3z2vrpzs]{box-shadow:inset 2px 0 0 #dcb763}
.dm-pane-tab.drop-after[b-vm3z2vrpzs]{box-shadow:inset -2px 0 0 #dcb763}

/* ⭐ WHERE A DROP WOULD LAND. Set by dm-panes.js on the target window during a drag — and a scoped rule
   CAN reach it, because the element itself is rendered by this component and carries its `b-…` attribute.
   (The dragged-tab ghost is the opposite case: script CREATES that element, so no scoped rule can ever
   match it and it is styled from wwwroot/play.css. The distinction is the whole of #261's worst bug.) */
.dm-pane.merge-target[b-vm3z2vrpzs]{border-color:#dcb763;box-shadow:0 22px 70px #03060d70,0 0 0 2px #dcb76366}
.dm-pane.merge-target > .dm-pane-grip[b-vm3z2vrpzs]{background:#2b2413}

/* The control box, on the right of the title bar. `margin-left:auto` is belt to the title's braces.
   ⛔ dm-panes.js excludes this subtree from the drag gesture by class name — see the note in DmPane.razor.
   Renaming this class silently turns every control back into a drag handle. */
.dm-pane-controls[b-vm3z2vrpzs]{flex:0 0 auto;margin-left:auto;display:flex;align-items:center;gap:.15rem;
    /* The grip sets `cursor:move` and `user-select:none` for the whole bar; the controls opt back out. */
    cursor:default}
.dm-pane-control[b-vm3z2vrpzs]{display:inline-flex;align-items:center;justify-content:center;
    width:1.35rem;height:1.35rem;padding:0;border:1px solid transparent;border-radius:6px;
    background:transparent;color:#8b96b6;font-size:.68rem;line-height:1;cursor:pointer}
.dm-pane-control:hover[b-vm3z2vrpzs]{background:#1d2540;border-color:#3a4a6e;color:#dfe6f6}
/* Lit when the toggle is ON, because a toggle whose state you cannot see is a button that does nothing
   twice. Warm, matching every other "this is switched on" chip on the play surface. */
.dm-pane-control.on[b-vm3z2vrpzs]{background:#2b2413;border-color:#dcb763;color:#e8c36a}
/* The close button reads as a window control: a square with an ✕ in it, not a bare glyph, so it is not
   mistaken for the ✕ that deletes a character on the lobby. Red only on hover — destructive-looking
   chrome at rest makes a window feel precarious. */
.dm-pane-close[b-vm3z2vrpzs]{border-color:#3a4a6e}
.dm-pane-close:hover[b-vm3z2vrpzs]{background:#42232699;border-color:#8d4046;color:#ffb8be}

.dm-pane-opacity[b-vm3z2vrpzs]{display:inline-flex;align-items:center;margin:0 .15rem}
/* Deliberately small. It is a comfort control, not a primary one, and the title bar is 1.35rem tall. */
.dm-pane-opacity input[type=range][b-vm3z2vrpzs]{width:3.4rem;height:1rem;accent-color:#dcb763;
    background:transparent;cursor:pointer}

/* ⤳ The right-click menu is styled in FloatHost.razor.css, because FloatHost DRAWS it — one menu for all
   seven windows. Noted here because a pane-shaped thing belongs in the pane's sheet by instinct, and
   following that instinct is precisely how this component's chrome ended up unreachable for a whole
   phase: a scoped rule only ever reaches markup its own component rendered. */

/* A label the eye does not need and a screen reader does. */
.visually-hidden[b-vm3z2vrpzs]{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
    clip:rect(0 0 0 0);white-space:nowrap;border:0}
.dm-pane-body[b-vm3z2vrpzs]{flex:1;min-height:0;overflow:auto;padding:.7rem}

/* ⛔ HIT SLOP. The pane clips to a 12px radius, so a handle flush with the corner shows `nwse-resize`
   along its outermost pixels while the pointer lands outside the clipped region and no gesture starts —
   "works janky as fuck... moving in some directions but not others", which was the shape of the arc being
   sampled at different angles. This handle OVERHANGS: the clickable area is a superset of the area
   showing the cursor rather than a subset. */
.dm-pane-resize[b-vm3z2vrpzs]{position:absolute;right:-6px;bottom:-6px;width:26px;height:26px;
    cursor:nwse-resize;touch-action:none;
    /* Drawn inset from its own box, so it LOOKS like a modest corner grip while catching a wider area. */
    background:linear-gradient(135deg,transparent 46%,#3a4a6e 46%,#3a4a6e 62%,transparent 62%);
    background-position:-6px -6px;background-repeat:no-repeat}
.dm-pane:hover .dm-pane-resize[b-vm3z2vrpzs]{background-image:linear-gradient(135deg,transparent 46%,#5f7ba8 46%,#5f7ba8 62%,transparent 62%)}

/* A docked pane takes its edge and gives up its own width or height. `!important` because the module
   writes an inline width during a resize — and clearing the dock before resizing is exactly what stops
   the two contradicting each other.

   ⛔⛔ `transform:none` IS THE FIX FOR "IT SNAPPED ALL THE WAY TO THE RIGHT". These rules used to set a
   width and a height and leave the pane's TRANSFORM alone — so a docked window kept the offset the drag
   had given it and merely changed shape. Docking bottom made it `width:100%` starting from wherever it
   happened to be, which reads exactly as Dave described it: "snapped that window all the way to the
   right, but in a narrow band up top". It was never moving to an edge at all; nothing here told it to.
   An inline transform loses to `!important`, so this is what actually puts a docked window on its edge. */
.dm-pane.docked-left[b-vm3z2vrpzs],.dm-pane.docked-right[b-vm3z2vrpzs]{transform:none!important;top:0!important;
    height:var(--dm-pane-cap,calc(100vh - 8rem))!important;width:34%!important}
.dm-pane.docked-left[b-vm3z2vrpzs]{left:0!important;right:auto!important}
.dm-pane.docked-right[b-vm3z2vrpzs]{left:auto!important;right:0!important}
.dm-pane.docked-top[b-vm3z2vrpzs],.dm-pane.docked-bottom[b-vm3z2vrpzs]{transform:none!important;left:0!important;
    width:100%!important;height:38vh!important}
.dm-pane.docked-top[b-vm3z2vrpzs]{top:0!important;bottom:auto!important}
.dm-pane.docked-bottom[b-vm3z2vrpzs]{top:auto!important;bottom:0!important}

/* ⚠️ THE SMALL-SCREEN FALLBACK for the window itself; the host hides its own moving chrome to match.
   Below this width there is no room to arrange anything, so a pane stops pretending to be movable. */
@media(max-width:1100px){
    .dm-pane[b-vm3z2vrpzs]{position:static!important;transform:none!important;width:auto!important;height:auto!important;
        max-height:none;margin-bottom:.8rem}
    .dm-pane-grip[b-vm3z2vrpzs]{cursor:default}
    .dm-pane-resize[b-vm3z2vrpzs]{display:none}
}
/* /Components/Play/EquipmentList.razor.rz.scp.css */
/* The equipment list's own chrome (issues #949, #955).

   ⛔ IT IS IN THIS FILE BECAUSE THE MARKUP IS. These rules were `.ooc-equipment` in
   ActionsPane.razor.css, where Blazor scoped them to that one component. #955 mounts the same list on
   CombatActionsPane as well, and a scoped rule never reaches a sibling — so left where they were, every
   one of them would have matched nothing on the Combat window: an unstyled list, silently, with the
   build green and the markup correct. Moved in the same commit as the markup, which is the only way
   that mistake stays impossible rather than merely unlikely. See DmPane.razor.css for the time this
   repo paid for it.

   ⚠️ The fold chrome (.more-actions) and the button (.strike-button.action) are NOT here: they are the
   play surface's shared vocabulary and live unscoped under `.game-shell` in wwwroot/play.css, which is
   what lets both hosts draw them. Only the list itself is this component's own. */

/* Three columns rather than the action row's wrapped buttons: what it is, whether it is on, and the one
   control — a list where the middle column is the answer the player came to read. Class-scoped
   throughout, never a bare `li`: this sheet is scoped to the component, and being scoped is not a reason
   to be careless about which list a rule means. */
.equipment-rows[b-58c88whs4a]{margin:.4rem 0 0;padding:0;list-style:none;display:grid;gap:.25rem}
.equipment-rows li[b-58c88whs4a]{display:grid;grid-template-columns:1fr auto auto;align-items:center;gap:.5rem;
    border:1px solid #2c3559;border-radius:8px;background:#151b2e;padding:.25rem .3rem .25rem .55rem}
.equipment-rows li.on[b-58c88whs4a]{border-color:#4a5c96;background:#1b2440}
.equipment-rows li strong[b-58c88whs4a]{font-size:.82rem;color:#dbe2f5;font-weight:700}
.equipment-rows li span[b-58c88whs4a]{font-size:.72rem;color:#8e9ac2;letter-spacing:.04em;text-transform:uppercase}
.equipment-rows li.on span[b-58c88whs4a]{color:#a8bbf0}

/* What the button costs, and why armour is missing in a fight. Advisory rather than a control, so it is
   quieter than the rows it explains. */
.equipment-note[b-58c88whs4a]{margin:.35rem 0 0;font-size:.72rem;line-height:1.5;color:#8e9ab8;font-style:italic}
/* /Components/Play/FloatHost.razor.rz.scp.css */
/* The floating layout. Scoped, like TabHost's — nothing else draws a window.

   ⭐ ONLY WHAT THIS HOST RENDERS ITSELF. The window chrome (.dm-pane and its parts) moved to
   DmPane.razor.css, where the elements actually are: written here it compiled to `.dm-pane[b-<FloatHost>]`
   and matched nothing, because a child component's markup never carries its parent's scope attribute. The
   `.dm-panes*` rules below are safe for the opposite reason — this file's own markup draws every one. */

/* ⭐ THE HOST FILLS THE WINDOW, and that is the whole answer to "the entire screen should be usable".
   Panes are positioned ABSOLUTELY inside this element, so its top edge is the highest a pane can ever be
   dragged — the module clamps to `y >= 0` and y is measured from here. While the shell stayed in the page
   the nav and campaign header stood 244px above this box in a 1000px window, and a quarter of the display
   was unreachable by any gesture. Floating now turns on `.game-shell.expanded` (position:fixed;inset:0),
   so this box starts at the top of the window and `y = 0` means what it says.

   `flex:1` because the expanded shell is a flex COLUMN: without it the host takes its content height, and
   its content is absolutely positioned, which is to say no height at all. The `min-height` is the belt to
   that braces — it keeps the host from collapsing to nothing in any arrangement where it is NOT a flex
   item, which would make every pane vanish at once. It cannot fight `flex:1` here: expanded, the space
   available is taller than this floor. */
.dm-panes[b-4zeuigs4m3]{position:relative;flex:1;min-height:calc(100vh - 10rem)}

/* ⭐ WHERE A DOCK WOULD PUT IT, shown BEFORE the drop rather than after. Dave, on the old behaviour:
   "something keeps snapping the window all the way to the right when I try to drag it down… when I
   looked away something snapped that window all the way to the right… Not sure how or if its something
   I did." That is the whole complaint — not that docking exists, but that it happened silently, on
   release, to a window he was merely moving. A drop target you can see is a drop target you can avoid.

   One pseudo-element on the host rather than a new node: it is a hint that exists for the length of a
   gesture, and the host is already `position:relative`. */
.dm-panes[b-4zeuigs4m3]::after{content:"";position:absolute;display:none;pointer-events:none;z-index:500;
    border:2px dashed #dcb763;background:#dcb7631a;border-radius:12px}
.dm-panes.docking-left[b-4zeuigs4m3]::after{display:block;left:0;top:0;width:34%;height:100%}
.dm-panes.docking-right[b-4zeuigs4m3]::after{display:block;right:0;top:0;width:34%;height:100%}
.dm-panes.docking-top[b-4zeuigs4m3]::after{display:block;left:0;top:0;width:100%;height:38vh}
.dm-panes.docking-bottom[b-4zeuigs4m3]::after{display:block;left:0;bottom:0;width:100%;height:38vh}

/* The one strip this layout keeps. Fixed, because with every pane floating there is no document flow to
   sit at the top of. */
.dm-panes-bar[b-4zeuigs4m3]{position:sticky;top:0;z-index:9;display:flex;align-items:center;gap:.6rem;
    padding:.5rem .7rem;border:1px solid #27334f;border-radius:12px;background:#0e1320e6;
    backdrop-filter:blur(2px)}
.dm-panes-hint[b-4zeuigs4m3]{flex:1;font-size:.72rem;color:#8e9ab8}
.dm-panes-reset[b-4zeuigs4m3],.dm-panes-leave[b-4zeuigs4m3]{border:1px solid #303b58;background:#151b2b;color:#bec9e7;
    border-radius:8px;padding:.3rem .6rem;font-size:.74rem;font-weight:700;cursor:pointer}
.dm-panes-reset:hover[b-4zeuigs4m3],.dm-panes-leave:hover[b-4zeuigs4m3]{background:#1d2540;border-color:#4a5c86}

/* The record's own mode switcher, drawn by this host because the four-button strip it rides on in the tab
   layout belongs to that layout. The buttons wear `.chat-tab`, which is why that class had to be promoted
   to wwwroot/play.css — two hosts draw it now, and a rule scoped to TabHost could only reach one. */
.dm-record-modes[b-4zeuigs4m3]{flex:0 0 auto;display:flex;flex-wrap:wrap;gap:.35rem;padding:0 0 .5rem}

/* ⭐⭐ THE RECORD WINDOW'S ROOT, and the reason it exists is the whole of issue #400: the switcher and the
   record are SIBLINGS, so the record cannot be given a height by anything except a box that holds both.
   `.dm-pane-body` is a plain block, so `.journal{flex:1;min-height:0}` — written for the tab layout's
   flex column — was inert here, the log took its full content height, and the scroll range moved out onto
   the pane body. Measured before it was built: `.journal` range 0, pane body range 1830.

   ⚠️ `height:100%` AND `flex:1;min-height:0`, the same pair `.map-pane` and `.narration-pane` carry: the
   percentage answers a block host, the flex pair answers a flex host, and neither alone answers both.
   ⚠️ And the switcher takes `flex:0 0 auto` above rather than the shrinkable default, so the row of
   buttons is never the thing that gives way — the god-mode readout's lesson, one file over. */
.dm-record[b-4zeuigs4m3]{display:flex;flex-direction:column;flex:1;min-height:0;height:100%}

/* ⤳ The MENU VOCABULARY moved to wwwroot/play.css. Two components draw a right-click menu now — this
   host for its windows, WorldClock for the clock format — and a scoped rule reaches only the one that
   rendered the markup. The window menu's own rows (.dm-menu-opacity, .dm-menu-close) went with it
   because they belong to the same family; splitting a family across two sheets is how one half stops
   matching without anybody noticing. Only what THIS host alone draws is left below. */

/* ⚠️ THE SMALL-SCREEN FALLBACK, and it refuses politely rather than trying. Below this width there is no
   room to arrange anything: every pane would be full width and stacked, which is a worse tab layout with
   no tabs. The panes stack in document order (DmPane's own sheet does that half) and the chrome that
   implies moving them is hidden, so the surface stays usable and stops advertising a gesture it cannot
   honour. */
@media(max-width:1100px){
    .dm-panes[b-4zeuigs4m3]{min-height:0}
    .dm-panes-hint[b-4zeuigs4m3]{display:none}
}
/* /Components/Play/MapPane.razor.rz.scp.css */
/* The Map pane's own vocabulary, moved with its markup in #261 phase 3.

   Scoped, not promoted: the shell's full-screen layer draws none of this — it has no tools panel, no
   map strip, no debug readout and no scene picture. What the two DO share went to wwwroot/play.css
   instead, and the split is exactly "does anything else render this markup". */

/* ⭐⭐ THE PANE OWNS ITS OWN HEIGHT (issue #386). This wrapper exists so the column below it — tools,
   board, turn strip — divides a height this component can point at, in WHATEVER box the layout drops it
   into. Before it, the pane's children were direct children of their host and inherited that host's
   layout: `.map-stage` is a flex column and everything worked; `.dm-pane-body` is a plain BLOCK, so
   `.map-frame{flex:1}` was inert there, `.map-viewport` took the board's full natural height, and the
   board's vertical range moved OUT of the viewport and onto the pane body.

   That is the whole regression Dave reported, and it is exactly what it looked like from the outside:
   the drag scrolls the viewport, so left-and-right worked and up-and-down did nothing, while the wheel —
   which scrolls whatever ancestor can consume the delta — still moved the pane body. Measured in a
   browser against these very rules: viewport range 254×0, pane-body range 0×659.

   ⚠️ `height:100%` AND `flex:1;min-height:0`, because it must work in both kinds of host: the percentage
   is what a block container gives it, the flex pair is what a flex column gives it, and neither one
   alone covers both. The gap moved here with the children it separates. */
.map-pane[b-pg2iqk7gbq]{display:flex;flex-direction:column;gap:.6rem;flex:1;min-height:0;height:100%}
/* The board scrolls in here rather than growing the panel. Zoomed in, a forty-square dungeon is wider
   than any column will ever be, and that is what zoom is FOR — the alternative is drawing every map at
   whatever size makes the largest one fit, which is how we got 8px cells.

   ⭐ NO SCROLLBARS, and `overflow:auto` all the same. Dave: "Remove the scroll bar completely and allow
   full pan with mouse (the scroll bar just removes from usable space the map could be showing)." The bars
   are what is hidden; the SCROLLABILITY is what pans, and `overflow:hidden` here would delete the pan
   outright. MiniMap has shipped the same pairing since #264 and pans perfectly well without a bar.

   ⭐ AND THE WHEEL IS DECIDED, not inherited: `overscroll-behavior:contain` keeps a wheel over the board
   scrolling the BOARD and stops the leftover delta being handed to whatever is behind it once an edge is
   reached. Turning the wheel over a map should never move the page out from under it. */
.map-viewport[b-pg2iqk7gbq]{flex:1;min-height:0;overflow:auto;border-radius:12px;
    overscroll-behavior:contain;scrollbar-width:none}
.map-viewport[b-pg2iqk7gbq]::-webkit-scrollbar{display:none}
.map-strip[b-pg2iqk7gbq]{flex:0 0 auto;display:grid;gap:.4rem;padding:.7rem .85rem;border:1px solid #303b57;border-radius:12px;background:#0c111bd9}
.map-strip .turn-status[b-pg2iqk7gbq]{margin:0;font-size:.75rem;font-weight:700;color:#d9c287}
/* "Map and party tools" (Dave, 2026-08-10). Everything above the board was a setting stacked on a
   setting — six rows of chrome standing on the map's height for the whole session. One fold now holds
   the lot; the board gets the space back the moment it is shut.

   Folded with `display:none` rather than by not rendering the body, so the panel keeps ONE DOM identity:
   `aria-controls` points at something that exists in both states, and `display:none` takes the controls
   out of the tab order anyway — a hidden control that can still be tabbed into is the usual way this
   pattern goes wrong. */
.map-tools[b-pg2iqk7gbq]{flex:0 0 auto;display:flex;flex-direction:column;
    border:1px solid #2a3450;border-radius:12px;background:#0c111bd9;padding:.4rem .6rem}
.map-tools-head[b-pg2iqk7gbq]{display:flex;align-items:center;gap:.5rem}
/* A button, not a `summary`: the header carries a SECOND control (⤢), and a nested interactive element
   inside a `summary` fights the disclosure for the same click. */
.map-tools-toggle[b-pg2iqk7gbq]{display:flex;align-items:center;gap:.4rem;border:0;background:transparent;
    color:#9fb0dc;font-size:.72rem;font-weight:700;letter-spacing:.02em;cursor:pointer;padding:.15rem 0}
.map-tools-toggle:hover[b-pg2iqk7gbq]{color:#dfe6f6}
/* Fixed width so the label does not step sideways as the chevron turns. */
.map-tools-chevron[b-pg2iqk7gbq]{display:inline-block;width:.7rem;font-size:.62rem;color:#dcb763}
/* ⤢ pinned to the right of the header, and it NEVER folds away: seeing the whole map is an action taken
   in the middle of play, not a setting arranged beforehand. */
.map-tools-head .map-expand[b-pg2iqk7gbq]{margin-left:auto}
.map-tools-body[b-pg2iqk7gbq]{display:flex;flex-direction:column;gap:.5rem;
    margin-top:.4rem;padding-top:.5rem;border-top:1px solid #222b42}
.map-tools.shut .map-tools-body[b-pg2iqk7gbq]{display:none}
/* Party-follows (issue #144). Above the screen lift because it changes what a drag DOES, which is a rule
   and not a comfort setting — and it reads as a sentence about the party rather than as a control. */
.march-toggle[b-pg2iqk7gbq]{flex:0 0 auto;display:flex;align-items:center;gap:.4rem;font-size:.72rem;font-weight:700;
    color:#9fb0dc;cursor:pointer}
.march-toggle input[b-pg2iqk7gbq]{accent-color:#dcb763;margin:0}
.march-toggle:hover[b-pg2iqk7gbq]{color:#dfe6f6}
/* Who is Searching (issue #244). Beside the party-follows toggle because they are the two rules about
   what a walk MEANS; one chip per character because searching is a per-character posture — the rogue
   checks the flagstones while the fighter just walks. */
.search-toggles[b-pg2iqk7gbq]{flex:0 0 auto;display:flex;align-items:center;flex-wrap:wrap;gap:.3rem;
    font-size:.7rem;color:#8e9ab8}
.search-toggles-label[b-pg2iqk7gbq]{font-weight:700;color:#9fb0dc;cursor:help}
.search-chip[b-pg2iqk7gbq]{padding:.1rem .5rem;font-size:.68rem;border-radius:999px;cursor:pointer;
    background:#141d33;color:#8e9ab8;border:1px solid #43537c}
.search-chip:hover[b-pg2iqk7gbq]{color:#dfe6f6;border-color:#5570a0}
.search-chip.searching[b-pg2iqk7gbq]{background:#2b2413;color:#e8c36a;border-color:#dcb763;font-weight:700}
/* Sneaking (issue #229 layer 3). Deliberately a COOL lit colour against Searching's warm one: the two
   are mutually exclusive per character, so a glance down the two rows should never leave any doubt
   which posture somebody is in. */
.search-chip.sneaking[b-pg2iqk7gbq]{background:#122a2a;color:#79d6c8;border-color:#4f9e94;font-weight:700}
/* How loud the party actually is — the point of both rows above, and the rule players are most often
   surprised by, so it gets a line of its own rather than living in a tooltip. */
.party-noise[b-pg2iqk7gbq]{margin:.15rem 0 0;font-size:.68rem;color:#8e9ab8;cursor:help;font-style:italic}
/* Said in the tab rather than leaving it blank, so the panel does not read as a board that failed to
   load — the one place a missing map and a moved map look alike. */
.map-elsewhere[b-pg2iqk7gbq]{margin:.6rem .2rem;font-size:.75rem;color:#8e9ab8;font-style:italic}
/* The strip above the board is GONE as of 2026-08-10. It carried the admin's debug switch and the wind;
   the switch is a setting and folded into the tools panel, and the wind moved onto the board itself. Two
   rows of chrome above a map, for one checkbox and one arrow.

   ⭐ The frame does not scroll and the viewport inside it does, which is the whole reason the wind can sit
   still in the board's corner while the board slides under it. Position an element absolutely inside the
   SCROLLER and it scrolls away with the content — the containing block is the scrolled box. One element
   out, and it is pinned to what you can actually see, with nothing listening for a pan or a zoom. */
.map-frame[b-pg2iqk7gbq]{position:relative;flex:1;min-height:0;display:flex}
.map-frame .map-viewport[b-pg2iqk7gbq]{flex:1;min-height:0}
/* Above the board, in its corner — and since #386 that corner is the board's own, because there is no
   scrollbar left for it to keep clear of. `pointer-events:none` because it sits over live squares and
   must never swallow a click meant for the one underneath. */
.wind-floating[b-pg2iqk7gbq]{position:absolute;top:.45rem;right:.75rem;z-index:3;pointer-events:none;
    padding:.15rem .5rem;border-radius:999px;border:1px solid #2f3c5c;background:#0a0f1ae6;
    backdrop-filter:blur(2px)}
/* "scene map + any photos appear top and bottom" — Dave, on #226. Orientation, not decoration, so it
   is capped hard: the board is what this tab is for. */
.map-scene-picture[b-pg2iqk7gbq]{flex:0 0 auto;margin:0;line-height:0}
.map-scene-picture img[b-pg2iqk7gbq]{display:block;width:100%;max-height:7rem;object-fit:cover;
    border-radius:10px;border:1px solid #43537c}
.map-scene-picture figcaption[b-pg2iqk7gbq]{margin-top:.3rem;font-size:.7rem;line-height:1.3;color:#8e9ab8}

/* The card for whoever the party clicked on (Dave, 2026-09-03).

   ⚠️ Positioned against .map-frame, which is the element that does NOT pan or zoom — the same anchor
   the wind reading uses. Anchored to the top-left because the zoom and full-screen controls own the
   top-right, and a card that covered them would make the board unusable while somebody is talking.

   ⛔ max-height with its own scroll: an innkeeper handing out six quests must not grow a card taller
   than the board it is drawn over. */
/* ⭐ 2026-09-03: the person card became the shared EncounterCard, which draws its own veil over the
   whole window and centres itself. This host must therefore be NO BOX AT ALL — a positioned, clipped
   wrapper here would pin a modal into the top-left corner of the board and cut its bottom off. */
.map-person-card[b-pg2iqk7gbq] {
    display: contents;
}
/* /Components/Play/MinimapPane.razor.rz.scp.css */
/* The Minimap window's own height (issue #414).

   ⭐⭐ THE PANE OWNS ITS OWN HEIGHT — the third time this exact wrapper has been needed, after `.map-pane`
   (#386) and `.narration-pane` / `.dm-record` (#400). `MiniMap` spends its host's leftover room via
   `.mini-map.grows{flex:1 1 var(--mini-map-height,12rem)}`, and `flex` on a child of a plain BLOCK is
   inert. `.dm-pane-body` is a plain block, so the board kept the definite fallback height and the rest of
   the window was dead space that nothing could scroll into either — the pane body's scroll range was zero
   because there was nothing overflowing it.

   Measured in the running app before this existed, with the window stretched to 900px tall (body client
   height 821px): `.mini-map.grows` rendered 449.75px, pane-body scroll range 0 — 371px of the window doing
   nothing. The same class has always worked correctly in the tab layout, whose `.combat-stack` IS a flex
   column; that is the whole difference.

   ⚠️ `height:100%` AND `flex:1;min-height:0`, exactly as `.map-pane` and `.narration-pane` carry both, and
   for the same reason: the percentage is what a block container gives it, the flex pair is what a flex
   column gives it, and neither one alone covers both hosts. `min-height:0` because a flex item's default
   `min-height:auto` would floor this at the board's content height and put the overflow back on the pane
   body.

   ⚠️ `display:flex;flex-direction:column` is the half that actually repairs the child: `.mini-map.grows`
   needs a flex COLUMN parent for its `flex` shorthand to resolve, and its own `min-height` keeps the floor
   at `--mini-map-height` so a short window is byte-for-byte what it was. */
.minimap-pane[b-bk99dauric]{display:flex;flex-direction:column;flex:1;min-height:0;height:100%}
/* /Components/Play/NarrationPane.razor.rz.scp.css */
/* The player surface's own vocabulary, moved with its markup in #261 phase 3.

   Scoped rather than promoted: nothing outside this pane draws a composer, a thinking indicator or the
   unattended-run controls. The bubble family it sits among IS promoted (wwwroot/play.css) because three
   surfaces draw that — the distinction is "does anything else render this markup", not "does it look
   like it belongs together".

   ⛔ The ::deep on the action box below is load-bearing and moved verbatim. Read the comment. */

/* ⭐⭐ THE ROOT THAT OWNS THIS PANE'S HEIGHT (issue #400). The log and the composer are siblings, and the
   log is the one that scrolls — which needs SOMETHING above it with a height to divide. In the tab
   layout `.story-panel` was that something; in a floating window `.dm-pane-body` is a plain block and
   there was nothing at all, so `.game-shell .journal{flex:1;min-height:0}` meant nothing, the log grew to
   its full content height and the whole window scrolled instead of the log.

   ⚠️ `height:100%` AND `flex:1;min-height:0`, exactly as `.map-pane` carries both and for the same reason:
   the percentage is what a BLOCK host gives it, the flex pair is what a FLEX host gives it, and neither
   one alone covers both. In a flex column the `flex-basis:0` from `flex:1` supplies the used height and
   the percentage is simply never consulted, so the pair cannot fight. Under the small-screen fallback the
   pane body has no definite height either, and `height:100%` degrades to `auto` — the stacked layout it
   had before. */
.narration-pane[b-vmx9m1ilq5]{display:flex;flex-direction:column;flex:1;min-height:0;height:100%}

/* ⚠️ THE ACTION BOX IS A CHILD COMPONENT, SO ITS <input> HAS NO SCOPE ATTRIBUTE.
   ================================================================================================
   `.composer-row input` compiles to `.composer-row[b-x] input[b-x]`, and the element is rendered by
   <InputText>, whose root does NOT inherit this component's scope. That rule has therefore matched
   nothing since the day it was written: the box a player types every action into was an UNSTYLED
   browser default — white background, grey border — and only looked deliberate because a stray
   `input { width:70% }` in the POC stylesheet was giving it a size.

   Deleting that POC rule is what exposed it: the box shrank to its intrinsic 169px and turned white.
   `::deep` is the mechanism that crosses the component boundary, and it is the only thing here that
   does. Scoped CSS never reaches a child component — this is the third time that has cost us. */
.composer-row[b-vmx9m1ilq5]  input{min-width:0;flex:1;background:#141b2b;color:#eef2fb;
    border:1px solid #34405c;border-radius:10px;padding:.85rem;font:inherit}
.composer-row[b-vmx9m1ilq5]  input:focus-visible{outline:2px solid #7aa2d8;outline-offset:-1px;border-color:#5a76a8}
.composer-row[b-vmx9m1ilq5]  input::placeholder{color:#7c88a8}

.composer-foot[b-vmx9m1ilq5]{display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap}
.composer-foot p[b-vmx9m1ilq5]{flex:1;min-width:14rem}

/* Nobody at the table is a person, so these are the only things that will start the next turn. Cool
   slate rather than the End Turn button's firelight: firelight means "you are owed a decision", and here
   nobody is. Three widths of the same move, narrowest first. */
.unattended-run[b-vmx9m1ilq5]{display:flex;align-items:center;gap:.4rem}
.unattended-run button[b-vmx9m1ilq5]{border:1px solid #4a5a6e;background:#161d26;color:#cfe0ef;border-radius:10px;
    padding:.5rem .8rem;font-weight:700;cursor:pointer;white-space:nowrap;font-size:.8rem}
.unattended-run button:hover:not(:disabled)[b-vmx9m1ilq5]{background:#1f2a37;border-color:#7ea3c4}
.unattended-run button:disabled[b-vmx9m1ilq5]{opacity:.5;cursor:default}
/* Red, because it is the one control that interrupts something already happening — and NOT the End Turn
   pulse, which means "you are owed a decision". This means "make it stop". */
.unattended-run .run-stop[b-vmx9m1ilq5]{border-color:#c2453a;background:#3a1512;color:#ffd9d2}
.unattended-run .run-stop:hover:not(:disabled)[b-vmx9m1ilq5]{background:#4d1a16;border-color:#e0655a}

/* The one you reach for when you are testing rather than watching, so it carries the emphasis. */
.unattended-run .run-fight[b-vmx9m1ilq5]{background:#1b2736;border-color:#6d8fb0;color:#e6f1fa}
.unattended-run .run-fight:hover:not(:disabled)[b-vmx9m1ilq5]{background:#26374b;border-color:#9dc3e0}

/* Night colours rather than the turn button's firelight, so the two are never confused mid-fight. */
.rest-night[b-vmx9m1ilq5]{display:flex;align-items:center;gap:.5rem;border:1px solid #46557f;background:#151b2c;color:#c3cbe6;border-radius:10px;padding:.5rem .85rem;font-weight:700;cursor:pointer;white-space:nowrap}
.rest-night:hover:not(:disabled)[b-vmx9m1ilq5]{background:#1d2540;border-color:#6a7cae}
.rest-night:disabled[b-vmx9m1ilq5]{opacity:.5;cursor:default}
.rest-night .left[b-vmx9m1ilq5]{font-weight:600;font-size:.8rem;color:#9fb0dc;padding:.1rem .4rem;border-radius:999px;background:#232c46}

/* The thinking indicator: a bubble that is not a participant. Lifted out of the page's minified base
   block, where it had lived since the POC.

   ⚠️ @keyframes is NOT scoped by Blazor — the name stays global whichever sheet declares it. `pulse` is
   unique across this application (checked), and it is declared here beside its only user rather than
   left behind in a sheet that no longer styles anything that animates. */
.thinking[b-vmx9m1ilq5]{opacity:.85}
.pulse[b-vmx9m1ilq5]{display:inline-block;width:.45rem;height:.45rem;margin-right:.35rem;border-radius:50%;background:#d1a552;animation:pulse-b-vmx9m1ilq5 1s infinite}
@keyframes pulse-b-vmx9m1ilq5{50%{opacity:.25;transform:scale(1.5)}}

/* The composer box and its send button.

   ⛔ `flex:0 0 auto` and NOT the default, which is `flex:0 1 auto` — it can shrink. The box a player
   types every action into is the last child of a flex column, which is precisely the position the
   god-mode readout was in when it was squeezed to "1.5 lines of display" (see DebugPane.razor.css).
   The log above it is `flex:1;min-height:0` and yields to nothing; this is what says the composer does
   not. */
.action-composer[b-vmx9m1ilq5]{flex:0 0 auto;margin:0 1.1rem 1.1rem;padding:1rem;border:1px solid #303c59;background:#0c111cd9;border-radius:14px}
.action-composer label[b-vmx9m1ilq5]{display:block;font-size:.75rem;font-weight:700;color:#b7c2df;margin-bottom:.5rem}
.action-composer p[b-vmx9m1ilq5]{font-size:.72rem;color:#8e9ab8;margin:.55rem 0 0}
.composer-row[b-vmx9m1ilq5]{display:flex;align-items:center;gap:.6rem}
.mic-button[b-vmx9m1ilq5],.act-button[b-vmx9m1ilq5]{border:0;border-radius:10px;padding:.8rem .95rem;font-weight:800}
.mic-button[b-vmx9m1ilq5]{background:#252d42;color:#aeb8d4;cursor:pointer;line-height:1}
.act-button[b-vmx9m1ilq5]{background:linear-gradient(135deg,#c69446,#e8c36a);color:#16110a}
.act-button:disabled[b-vmx9m1ilq5],.mic-button:disabled[b-vmx9m1ilq5]{opacity:.55}
.mic-button:disabled[b-vmx9m1ilq5]{cursor:default}
.mic-button:hover:not(:disabled)[b-vmx9m1ilq5]{background:#303a54}
.mic-button:focus-visible[b-vmx9m1ilq5]{outline:2px solid #7aa2d8;outline-offset:2px}

/* Recording has to be unmistakable and it has to be visible without motion: a live microphone that
   only announces itself by pulsing is invisible to anyone running reduced motion, and "am I still
   being recorded" is exactly the question they must never have to guess at. Red is the state; the
   pulse is decoration on top of it. */
.mic-button.recording[b-vmx9m1ilq5]{background:#8d2033;color:#ffe8ec;animation:mic-pulse-b-vmx9m1ilq5 1.4s ease-in-out infinite}
.mic-button.recording:hover:not(:disabled)[b-vmx9m1ilq5]{background:#a3283d}
@keyframes mic-pulse-b-vmx9m1ilq5{50%{box-shadow:0 0 0 .35rem rgba(190,50,75,.28)}}
@media (prefers-reduced-motion:reduce){.mic-button.recording[b-vmx9m1ilq5]{animation:none}}

.mic-error[b-vmx9m1ilq5]{margin:.5rem 0 0;color:#f0b7c0;font-size:.85rem}


/* Temporary hit points (issue #169). Its own colour because it is not hit points: spent first,
   and never healed back. Reading it as part of the maximum is the misreading to prevent. */
.temp-hp[b-vmx9m1ilq5] { margin-left: .3rem; padding: 0 .28rem; border-radius: 4px; font-size: .78em;
    color: #8fd4ff; background: rgba(90, 209, 255, .14); border: 1px solid rgba(90, 209, 255, .35); }
/* /Components/Play/OpenWorldPane.razor.rz.scp.css */
/* The open world (OpenWorld v1 Phase 2).

   ⚠️ Scoped CSS cannot reach inside `OverlandView` — a child component's markup carries its own scope
   attribute — so nothing here tries to style the map. The map styles itself; this styles the furniture
   around it. */

.open-world[b-247sgxcbpi] {
    position: relative;
    display: flex;
    flex-direction: column;
    /* The canvas fills whatever the pane is given, and the status strip sits under it. `min-height: 0`
       because a flex child's default `min-height: auto` refuses to shrink below its content, which on a
       canvas that resizes to its host is a slow outward creep every time the pane is laid out. */
    min-height: 0;
    height: 100%;
    gap: 0.4rem;
}

.open-world[b-247sgxcbpi]  .world-view {
    /* ::deep, and it is the one place it is warranted: the viewer was written to fill a page and this
       hosts it inside a pane. Nothing else about how it draws is touched. */
    flex: 1 1 auto;
    min-height: 0;
}

.open-world-status[b-247sgxcbpi] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: baseline;
    padding: 0.35rem 0.6rem;
    border-radius: 6px;
    background: rgba(16, 22, 34, 0.72);
    font-size: 0.85rem;
    color: #d7e0f0;
}

.open-world-status .where[b-247sgxcbpi] { font-weight: 600; }
/* The going. Its own colour so "33% slower" reads as a mechanic rather than as more scenery, and dimmer
   than .where because it answers a second question about the same spot. */
.open-world-status .going[b-247sgxcbpi] { color: #b6c9a8; }
.open-world-status .weather[b-247sgxcbpi] { color: #9fb0c8; font-style: italic; }
/* ⭐ What the party is hauling (Dave, 2026-09-05). Its own colour so it reads as a separate fact from
   the ground and the sky, matching how `.going` and `.weather` already sit beside each other. */
.open-world-status .load[b-247sgxcbpi] { color: #c8bda0; }
/* ⚠️ Amber rather than red. Nothing is WRONG when somebody is encumbered -- the party is carrying more
   than is comfortable, which is a decision they made and may well want to keep. Red would read as an
   error, and until the speed penalty is armed it is not even a cost yet. */
.open-world-status .load.strained[b-247sgxcbpi] { color: #ffc247; font-weight: 600; }

.open-world-status .journey[b-247sgxcbpi] { margin-left: auto; color: #ffc247; }

.open-world-message[b-247sgxcbpi] {
    margin: 0;
    padding: 0.35rem 0.6rem;
    font-size: 0.85rem;
    color: #9fb0c8;
}

/* ── The travel dialog ─────────────────────────────────────────────────────────────────────────── */

.travel-backdrop[b-247sgxcbpi] {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    /* Above the canvas, which Pixi paints at the pane's own stacking level. A dialog that lost this race
       would be a dialog drawn underneath the map it is asking about. */
    z-index: 20;
    background: rgba(6, 9, 16, 0.55);
}

.travel-dialog[b-247sgxcbpi] {
    min-width: min(26rem, 90%);
    max-width: 32rem;
    padding: 1rem 1.15rem;
    border: 1px solid rgba(159, 176, 200, 0.35);
    border-radius: 10px;
    background: #121826;
    color: #e8edf7;
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.5);
}

.travel-dialog h3[b-247sgxcbpi] {
    margin: 0 0 0.75rem;
    font-size: 1.05rem;
}

.travel-dialog p[b-247sgxcbpi] { margin: 0 0 0.9rem; color: #c3cddd; }

.travel-facts[b-247sgxcbpi] {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.35rem 1rem;
    margin: 0 0 1rem;
}

.travel-facts dt[b-247sgxcbpi] { color: #9fb0c8; }
.travel-facts dd[b-247sgxcbpi] { margin: 0; }

.travel-refusal[b-247sgxcbpi] {
    margin: 0 0 1rem;
    color: #ffb4a2;
}

/* ⚠️ Not the refusal's alarm colour: "there is a way in once you get there" is an INVITATION, and
   painting it the same as a refusal reads as a warning about the journey. */
.travel-hint[b-247sgxcbpi] {
    margin: 0 0 1rem;
    color: #9fb0c8;
    font-size: 0.9em;
}

/* ── The night in camp (Dave, 2026-09-02) ──────────────────────────────────────────────────────────
   ⚠️ The quiet night is NOT the refusal's alarm colour. "Nothing happened and you are rested" is the
   good outcome, and painting it like a warning is how a player learns to dread a window that is usually
   telling them they are fine. */
.camp-progress[b-247sgxcbpi] {
    margin: 0 0 1rem;
    color: #9fb0c8;
    font-style: italic;
}

.camp-quiet[b-247sgxcbpi] {
    margin: 0 0 0.6rem;
    color: #a9d8b0;
}

.camp-detail[b-247sgxcbpi] {
    margin: 0 0 0.6rem;
    color: #c8d2e4;
    font-size: 0.92em;
    line-height: 1.45;
}

.camp-resumed[b-247sgxcbpi] {
    margin: 0 0 1rem;
    color: #e8c36a;
}

.travel-actions[b-247sgxcbpi] {
    display: flex;
    gap: 0.6rem;
    justify-content: flex-end;
}

.open-world-actions[b-247sgxcbpi] {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
    padding: 0 0.6rem;
}

/* The travel-pace dial — debug only, and it looks it. A control that changes how the game FEELS while
   somebody is playing has to be obviously not part of the game, or it reads as a setting every player is
   expected to have an opinion about. ⚠️ Wraps, because the rail this sits above taught that lesson: a
   flex row in a narrow pane does not shrink, it clips. */
.open-world-pace[b-247sgxcbpi] {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.4rem 0.8rem;
    margin: 0.4rem 0.6rem 0;
    padding: 0.4rem 0.6rem;
    border: 1px dashed #4a5570;
    border-radius: 4px;
    font-size: 0.85rem;
    color: #9fb0c8;
}

.open-world-pace label[b-247sgxcbpi] {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
}

.open-world-pace input[b-247sgxcbpi] {
    width: 5.5rem;
}

/* The ratio read back both ways. Dimmer than the control: it is what the number MEANS, not a second
   thing to set. */
.open-world-pace-reading[b-247sgxcbpi] {
    color: #7c8aa5;
    font-variant-numeric: tabular-nums;
}

/* The opening brief. Above the map's own status strip, because it is the first thing to read and the
   last thing to want out of the way. */
.open-world-arrival[b-247sgxcbpi] {
    display: flex;
    gap: 0.75rem;
    align-items: flex-start;
    padding: 0.5rem 0.7rem;
    border-left: 3px solid #ffc247;
    border-radius: 4px;
    background: rgba(30, 24, 12, 0.65);
    color: #e8edf7;
    font-size: 0.9rem;
}

.open-world-arrival p[b-247sgxcbpi] { margin: 0; flex: 1 1 auto; }

/* ── The testing dials (Dave, 2026-08-25) ───────────────────────────────────────────────────────────
   ⛔ A <details> card, shut by default. The ask was that they "not take up space unless im actually
   using them", so the closed state must cost one line and nothing else. */
.open-world-dials[b-247sgxcbpi] {
    margin-top: .5rem;
    border: 1px solid #2c3448;
    border-radius: .4rem;
    background: #131a27;
}

.open-world-dials > summary[b-247sgxcbpi] {
    padding: .35rem .6rem;
    color: #9aa6bd;
    font-size: .78rem;
    cursor: pointer;
    user-select: none;
}

.open-world-dials > summary:hover[b-247sgxcbpi] {
    color: #d7deea;
}

.open-world-dials-body[b-247sgxcbpi] {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    padding: .1rem .6rem .6rem;
    border-top: 1px solid #2c3448;
}

/* ⚠️ The caption carries the units and they are OPPOSITE kinds of time — real seconds above, in-game
   minutes below — so the word that distinguishes them is emphasised rather than left to be read. */
.open-world-dials-body strong[b-247sgxcbpi] {
    color: #d7deea;
}

.open-world-dials-note[b-247sgxcbpi] {
    margin: 0;
    color: #9aa6bd;
    font-size: .72rem;
    line-height: 1.35;
}

/* Issue #883. The travel dialog's risk line, once there is something honest to say.

   ⚠️ COLOUR ONLY, and deliberately so: the sentence beside it already states the region's authored level
   band and the party's own level, so a reader who cannot see the colour has lost nothing. This is
   emphasis on a number that is already written down, never the number itself. */
.risk-plain[b-247sgxcbpi] {
    color: #c8d1e0;
}

.risk-dangerous[b-247sgxcbpi] {
    color: #e8c37a;
}

.risk-deadly[b-247sgxcbpi] {
    color: #e88a7a;
    font-weight: 600;
}

/* What the place card says when there is nothing to do about it (Dave, 2026-09-03).

   ⚠️ Inside EncounterCard's Note, so it inherits the card's own type and colour — these two lines are
   the only thing the place card adds to the shared one. */
.place-refusal[b-247sgxcbpi],
.place-hint[b-247sgxcbpi] {
    margin: 0.4rem 0 0;
    opacity: 0.75;
    font-style: italic;
}

/* ── The fleeting encounter card (Dave, 2026-09-06) ────────────────────────────────────────────────
   The picture, and a Risk line that cannot be skimmed past. Dave: "make the Risk line something
   obvious like 'Extremely Deadly'. Let the idiots attack if they want. Its their funeral." */
.encounter-portrait[b-247sgxcbpi] {
    display: block;
    width: 148px;
    height: 148px;
    /* ⚠️ `cover`, not `contain`: the vaulted portraits are square-ish but not exactly, and letterboxing
       one inside a card reads as a broken image rather than as a picture. */
    object-fit: cover;
    border-radius: 8px;
    margin: 0 auto 0.9rem;
    border: 1px solid #2b3550;
}

.encounter-risk[b-247sgxcbpi] {
    margin: 0 0 0.9rem;
    font-weight: 600;
    color: #c3cddd;
}

/* ⛔ The one line on this card that must survive being skimmed. A party is about to choose whether to
   attack something four levels above them, and the whole point of showing the band is that the answer
   is obvious before the button is pressed rather than after. */
.encounter-risk.dire[b-247sgxcbpi] {
    color: #ffb4a2;
    background: rgba(140, 38, 30, 0.28);
    border: 1px solid rgba(255, 120, 100, 0.45);
    border-radius: 6px;
    padding: 0.4rem 0.6rem;
    letter-spacing: 0.02em;
}


/* ⛔⛔ The hand-in, on the card that has always SAID "Ready to hand in" and never offered a way to do
   it. Dave, 2026-09-10: the reward should "come from the system instead" of from a token nobody can
   find. Styled here because a class name with no rule draws nothing and errors nowhere. */
.quest-handin[b-247sgxcbpi] {
    display: flex;
    justify-content: flex-end;
    margin-top: 0.5rem;
}

.quest-handin-act[b-247sgxcbpi] {
    border: 1px solid #8a6f37;
    background: #241d10;
    color: #f4d58d;
    border-radius: 8px;
    padding: 0.4rem 0.9rem;
    font-weight: 700;
    cursor: pointer;
}

.quest-handin-act:hover:not(:disabled)[b-247sgxcbpi] {
    background: #33280f;
    border-color: #c69446;
}

.quest-handin-act:disabled[b-247sgxcbpi] {
    opacity: 0.5;
    cursor: default;
}
/* /Components/Play/PaneSection.razor.rz.scp.css */
/* ── A collapsible group of cards inside a play pane (Dave, 2026-09-07) ─────────────────────────────
   The Party tab was twelve sibling blocks, all always open, 2,361px tall in a 768px pane. The closed
   state has to cost ONE ROW and nothing else, or the panels have not bought anything. */

.pane-section[b-0hqd72payl] {
    border: 1px solid #2c3448;
    border-radius: .45rem;
    background: #131a27;
    margin-bottom: .5rem;
}

.pane-section > summary[b-0hqd72payl] {
    display: flex;
    align-items: baseline;
    gap: .5rem;
    padding: .4rem .6rem;
    cursor: pointer;
    user-select: none;
    /* ⚠️ The marker is drawn by the browser and sits in the padding box; without list-style the
       triangle and the flex row fight over the first line. */
    list-style-position: outside;
}

.pane-section > summary:hover .pane-section-title[b-0hqd72payl] { color: #f0e6cc; }

.pane-section > summary:focus-visible[b-0hqd72payl] {
    outline: 2px solid #d9c68f;
    outline-offset: -2px;
    border-radius: .4rem;
}

.pane-section-title[b-0hqd72payl] {
    font-size: .74rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: #d9c68f;
}

/* The answer you get without opening it. Pushed right so the titles stay a readable column. */
.pane-section-note[b-0hqd72payl] {
    margin-left: auto;
    font-size: .72rem;
    color: #8b97ad;
    text-align: right;
}

.pane-section-body[b-0hqd72payl] {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    padding: .1rem .6rem .6rem;
}

/* ⛔ The cards inside have their own margins, written when they were siblings in a scrolling column.
   Inside a section the gap above owns the spacing, so a doubled margin would put a ragged hole
   between every pair. */
.pane-section-body > :is(section, article, nav, div, p)[b-0hqd72payl] { margin-top: 0; margin-bottom: 0; }
/* /Components/Play/PartyPane.razor.rz.scp.css */
/* The Party pane's own vocabulary, moved with its markup in #261 phase 3 — the last pane.

   Scoped: nothing else renders a member row, a march control, a watch rota, a control card or the
   content-upgrade block. The two rules this pane shares with other surfaces (.warning on a condition
   chip, and .who) went to wwwroot/play.css instead, on the same test as every pane before it: does
   anything else render this markup. */

.member-tabs[b-gej6fi2wbt]{display:flex;flex-direction:column;gap:.25rem}
.member-tab[b-gej6fi2wbt]{display:grid;grid-template-columns:1.5rem minmax(0,1fr) auto;align-items:center;gap:.5rem;text-align:left;border:1px solid #2b3550;border-left-width:3px;border-left-color:#64b5f6;background:#111827b3;color:#dbe3f5;border-radius:10px;padding:.4rem .6rem;cursor:pointer}
.member-tab:hover[b-gej6fi2wbt]{background:#1a2235}
.member-tab.active[b-gej6fi2wbt]{background:#1b2740;border-color:#5570a0;border-left-color:#e8c36a}
.member-tab .rank[b-gej6fi2wbt]{font-family:Georgia,serif;font-weight:700;color:#d9c287;text-align:center}
.member-tab .name[b-gej6fi2wbt]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.85rem}
/* The marching order is edited where it is shown. The row holds the select-button and the two arrows
   side by side; the button keeps its own grid so the rank/name/who columns are unchanged. `active` moved
   from the button to the row so the whole row lights up, arrows included. */
.member-row[b-gej6fi2wbt]{display:flex;align-items:stretch;gap:.25rem}
.member-row .member-tab[b-gej6fi2wbt]{flex:1;min-width:0}
.member-row.active .member-tab[b-gej6fi2wbt]{background:#1b2740;border-color:#5570a0;border-left-color:#e8c36a}
.member-row .march-controls[b-gej6fi2wbt]{margin-left:0}
/* Bigger than the 25×17px pair they replace. These are the control for a thing the player is expected to
   arrange before every journey, not a nudge on a settings card. */
.member-row .march-controls button[b-gej6fi2wbt]{padding:.1rem .45rem;font-size:.7rem}
/* Night watches (issue #52). Folded shut by default: the rota matters on the night you camp somewhere
   dangerous, not every minute of the Party tab. Rows borrow the marching list's grammar. */
.watch-order[b-gej6fi2wbt]{margin:.4rem 0;font-size:.75rem;color:#9fb0dc}
.watch-order summary[b-gej6fi2wbt]{cursor:pointer;font-weight:700}
.watch-order .watch-hint[b-gej6fi2wbt]{margin:.3rem 0;font-size:.7rem;color:#8e9ab8}
.watch-order .member-row[b-gej6fi2wbt]{align-items:center;padding:.15rem 0}
.watch-order .watch-turn[b-gej6fi2wbt]{min-width:1.4rem;text-align:center;font-variant-numeric:tabular-nums;color:#dcb763}
.watch-order .watch-name[b-gej6fi2wbt]{flex:1;min-width:0}
/* ⤳ .end-turn and its spent-turn alarm moved to wwwroot/play.css (#261). It is rendered under the
   composer AND on the combat surface, and those two are now in different components — a scoped rule
   could only ever have reached one of them. */
/* A sandbox instrument, and dressed as one. Dashed and cool-toned so it never reads as part of the game
   the way the level-up panel above it does — this is scaffolding, and it should look like scaffolding. */
.grant-level[b-gej6fi2wbt]{width:100%;margin-top:.5rem;border:1px dashed #5a6b80;border-radius:9px;padding:.45rem .7rem;
    background:#141b24;color:#b9cbdd;font-size:.75rem;font-weight:700;cursor:pointer}
.grant-level:hover:not(:disabled)[b-gej6fi2wbt]{background:#1c2632;border-color:#87a8c6;color:#dcebf8}
.grant-level:disabled[b-gej6fi2wbt]{opacity:.45;cursor:not-allowed}
/* ⤳ The out-of-combat panel's own vocabulary — .out-of-combat, the fold, the character chips and the
   target picker — moved to NarrationPane.razor.css (#261 phase 3) with the markup it styles. */
.march-hint[b-gej6fi2wbt]{margin:1rem 1rem 0;font-size:.72rem;line-height:1.5;color:#8e9ab8}.march-hint strong[b-gej6fi2wbt]{color:#f4d58d}.march-feedback[b-gej6fi2wbt]{margin:.5rem 1rem 0;padding:.5rem .7rem;border:1px solid #8d4046;border-radius:10px;background:#42232699;font-size:.72rem;color:#ffb8be}.token.march-rank[b-gej6fi2wbt]{font-family:Georgia,serif;font-weight:700;color:#d9c287;background:#1d2740;border:1px solid #3a4a6e}.march-controls[b-gej6fi2wbt]{display:flex;flex-direction:column;gap:.15rem;margin-left:auto}.march-controls button[b-gej6fi2wbt]{border:1px solid #34405c;background:#1a2235;color:#bfcaf0;border-radius:6px;line-height:1;padding:.15rem .4rem;font-size:.65rem;cursor:pointer}.march-controls button:hover:not(:disabled)[b-gej6fi2wbt]{background:#243050;border-color:#4a5c86}.march-controls button:disabled[b-gej6fi2wbt]{opacity:.3;cursor:default}.combatant-top .ac[b-gej6fi2wbt]{margin-left:.6rem}
/* ⤳ The scene card, its picture, the read-aloud, the DM notes and the objectives card moved to
   ScenePane.razor.css (#261 phase 3), along with .end-adventure, the shop card, .ask-hint and
   .engine-note from further down and from the base block. They style that pane's own markup now, and a
   scoped rule cannot reach a child component. */
.condition.ai-badge[b-gej6fi2wbt]{color:#9fd6ff;border-color:#3b6394}
/* Advancement (M18-e). Earning is the point of the system, so the bar is as loud as the health one. */
.hp-row.xp[b-gej6fi2wbt]{margin-top:.55rem;color:#d9c287}
.hp-track.xp[b-gej6fi2wbt]{margin:.35rem 0 .2rem}
.hero .hp-track.xp span[b-gej6fi2wbt]{background:linear-gradient(90deg,#c69446,#f0cb75)}
/* The way into the character sheet. ActorSheet is its own modal with its own stylesheet, so only the
   button that opens it lives here. */
.open-sheet[b-gej6fi2wbt]{width:100%;margin-top:.55rem;padding:.45rem .8rem;border:1px solid #4a5570;border-radius:10px;
    background:#161c2a;color:#b8c6e2;font-size:.76rem;cursor:pointer}
.open-sheet:hover:not(:disabled)[b-gej6fi2wbt]{background:#1d2536;border-color:#6f7fa4;color:#dbe6f8}
.open-sheet:disabled[b-gej6fi2wbt]{opacity:.5;cursor:default}
/* ⤳ The action rows — .strike-button.action, .action-row, .skill-pick, .cost-pick, .more-actions and
   .glyph — moved to wwwroot/play.css (#261). These are the ones shared by the combat surface and the
   out-of-combat panel, which now sit in different components; one home or they diverge. */

/* Who is driving whom. Sits under the character card rather than behind a settings page, because
   deciding a companion should run herself is a thing you work out three rounds into a fight. */
.control-card[b-gej6fi2wbt]{margin-top:.6rem;padding:.5rem .6rem;border:1px solid #3a3020;border-radius:10px;
    background:#1b1610}
.control-card.enemies[b-gej6fi2wbt]{margin-top:.8rem;border-color:#4a2f22}
.control-card h4[b-gej6fi2wbt]{margin:0 0 .4rem;font-size:.72rem;font-weight:700;letter-spacing:.06em;
    text-transform:uppercase;color:#9a8b6a}
.control-row[b-gej6fi2wbt]{display:flex;flex-wrap:wrap;gap:.6rem;align-items:flex-end}
.control-row label[b-gej6fi2wbt]{display:flex;flex-direction:column;gap:.2rem;font-size:.7rem;font-weight:700;
    letter-spacing:.05em;text-transform:uppercase;color:#9a8b6a}
.control-row select[b-gej6fi2wbt]{padding:.25rem .4rem;border:1px solid #3a3020;border-radius:6px;background:#241d10;
    color:#e8dcc0;font-size:.8rem;text-transform:none;letter-spacing:0}
.control-row label.control-check[b-gej6fi2wbt]{flex-direction:row;align-items:center;gap:.35rem}
.control-hint[b-gej6fi2wbt]{margin:.4rem 0 0;font-size:.72rem;line-height:1.35;color:#8b7c5e}
.control-feedback[b-gej6fi2wbt]{margin:.35rem 0 0;font-size:.72rem;color:#d99a6c}
/* Distinct from the AI chip on purpose: at a glance the question is which turns cost money. */
.condition.auto-badge[b-gej6fi2wbt]{background:#1e2a1c;color:#9dc98a;border-color:#33452e}
/* A follower (Dave, 2026-09-13): a party member nobody drives. Gold beside the green AUTO chip, because the question
   it answers is not "what does this turn cost" but "can I take this seat" — and the answer is no. */
.condition.auto-badge.follower-badge[b-gej6fi2wbt]{background:#2a2414;color:#e0c47a;border-color:#5a4a24}
.control-card.follower-card[b-gej6fi2wbt]{border-left:3px solid #5a4a24}
/* ⤳ .strike-pair and .strike-repeat moved to wwwroot/play.css (#261). */

/* ⤳ .end-turn.combat-end moved to wwwroot/play.css (#261), with the rest of the End Turn family. */

/* A companion copied before their template had a kit (issue #20). A note on their card, not an alarm:
   nothing is wrong, and handing it over is the player's choice to make. */
.reequip[b-gej6fi2wbt]{margin:.5rem 0 0;padding:.55rem .7rem;border:1px solid #4a4130;border-radius:9px;
    background:#1b1810;display:grid;gap:.45rem}
.reequip p[b-gej6fi2wbt]{margin:0;font-size:.74rem;line-height:1.45;color:#b6a888}
.reequip strong[b-gej6fi2wbt]{color:#e8c36a;font-weight:700}
.reequip button[b-gej6fi2wbt]{justify-self:start;border:1px solid #6f5c33;border-radius:8px;padding:.35rem .8rem;
    background:linear-gradient(135deg,#3a2f16,#5a4720);color:#f4d58d;font-size:.74rem;font-weight:800;
    cursor:pointer}
.reequip button:hover:not(:disabled)[b-gej6fi2wbt]{border-color:#c09a45}
.reequip button:disabled[b-gej6fi2wbt]{opacity:.45;cursor:not-allowed}
/* ⛔ The content-upgrade offer's rules are GONE with its markup (Dave, 2026-09-10). Rules for markup
   nobody renders draw nothing and hide nothing; they just make the next reader go looking for the
   control. Upgrades apply on activation now — nobody is offered one.
   ⚠️ Removed as a whole block, comment and all: an orphaned comment left behind eats the rule after it.
   → memory `an-orphaned-comment-eats-the-next-rule` */

/* The character card, lifted out of the page’s minified base block where it had lived since the
   POC. Only this pane draws a combatant card now — the combat tracker is its own component with
   its own stylesheet, which is why these could come here scoped rather than be promoted. */
.combatant[b-gej6fi2wbt]{margin:1rem;padding:1rem;border:1px solid #303b57;border-radius:14px;background:#111827d9}
.combatant.hero[b-gej6fi2wbt]{border-left:3px solid #64b5f6}
.combatant.enemy[b-gej6fi2wbt]{border-left:3px solid #d87272}
.combatant.defeated[b-gej6fi2wbt]{opacity:.45;filter:grayscale(1)}
.combatant-top[b-gej6fi2wbt]{gap:.7rem}
.combatant-top h3[b-gej6fi2wbt]{font-size:.95rem;margin:0}
.combatant-top p[b-gej6fi2wbt]{margin:.1rem 0}
.ac[b-gej6fi2wbt]{margin-left:auto;font-size:.75rem;color:#d9c287}
.hp-row[b-gej6fi2wbt]{display:flex;justify-content:space-between;margin-top:1rem;font-size:.75rem;color:#aab5cf}
.hp-track[b-gej6fi2wbt]{height:.45rem;background:#2a3144;border-radius:10px;overflow:hidden;margin:.4rem 0 .65rem}
.hp-track span[b-gej6fi2wbt]{display:block;height:100%;background:linear-gradient(90deg,#e96e6e,#f1a05c);border-radius:10px}
.hero .hp-track span[b-gej6fi2wbt]{background:linear-gradient(90deg,#52c98d,#80e3ae)}
.condition[b-gej6fi2wbt]{padding:.25rem .5rem}
.condition.warning[b-gej6fi2wbt]{color:#f4d58d;border-color:#8a6f37}
.condition.danger[b-gej6fi2wbt]{color:#ff9fa3;border-color:#8f3c47}
/* /Components/Play/PartyRosterCard.razor.rz.scp.css */

/* ── The recruit list, behind a fold (Dave, 2026-09-07) ─────────────────────────────────────────────
   Twelve free characters on Dave's own account made this card 647px — most of the Party tab's
   scrolling, for a list used a handful of times a campaign. */
.roster-recruit[b-mmftb5m0ym] {
    border-top: 1px solid #2c3448;
    margin-top: .5rem;
    padding-top: .35rem;
}

.roster-recruit > summary[b-mmftb5m0ym] {
    display: flex;
    align-items: baseline;
    gap: .5rem;
    cursor: pointer;
    user-select: none;
    font-size: .78rem;
    font-weight: 700;
    color: #d9c68f;
}

.roster-recruit > summary:hover[b-mmftb5m0ym] { color: #f0e6cc; }
.roster-recruit > summary .muted[b-mmftb5m0ym] { margin-left: auto; font-weight: 400; }
/* /Components/Play/RulesEngineLogView.razor.rz.scp.css */
/* The rules engine's window (Dave, 2026-08-25). A debugging surface, and it is allowed to look like one:
   the filters are chrome, the log underneath is the table's own rendering unchanged. */
.rules-log[b-t9jx1075r0] {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}

/* ⚠️ WRAPS. This sits in a floating pane the reader drags to any width, and the lesson from the map's
   rail is one session old: a flex row that cannot wrap does not shrink, it clips. */
.rules-log-filters[b-t9jx1075r0] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem 0.7rem;
    padding: 0.4rem 0.6rem;
    border-bottom: 1px solid #2a3348;
    font-size: 0.85rem;
    color: #c9d3e4;
}

.rules-log-check[b-t9jx1075r0] {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    white-space: nowrap;
}

/* Takes the slack, so the box is the thing that grows when the pane is widened. */
.rules-log-search[b-t9jx1075r0] {
    flex: 1 1 10rem;
    min-width: 7rem;
}

/* ⛔ Never hidden, even unfiltered. A short list and a quiet engine look identical, and telling those two
   apart is the entire reason this window exists. */
.rules-log-count[b-t9jx1075r0] {
    margin: 0;
    padding: 0.25rem 0.6rem;
    font-size: 0.78rem;
    color: #7c8aa5;
    font-variant-numeric: tabular-nums;
}

.rules-log-empty[b-t9jx1075r0] {
    margin: 0;
    padding: 0.6rem;
    color: #7c8aa5;
    font-size: 0.85rem;
}

/* ⭐ The lines scroll, and the filters above them do not. Without an overflow of its own the whole pane
   scrolled as one, so the checkboxes and the search box slid off the top exactly when somebody was using
   them to chase a roll — and there was no element here to pin to the bottom. */
.rules-log-lines[b-t9jx1075r0] {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}
/* /Components/Play/ScenePane.razor.rz.scp.css */
/* The Scene pane's own vocabulary, moved out of Play.razor.css in the same commit as its markup (#261
   phase 3). A scoped rule never reaches a child component, so leaving these behind would have unstyled
   every one of them silently — see the two failure modes recorded on that phase-1 lesson.

   Scoped rather than promoted to wwwroot/play.css: nothing outside this pane uses any of it. Promotion
   is for shared vocabulary, and a rule promoted without need is a rule that can reach a page it was
   never meant to. */

.scene-card[b-jfcgfioi7o]{padding:1rem;border:1px solid #43537c;border-radius:14px;background:#151d33d9}
.scene-title h2[b-jfcgfioi7o]{margin:0;font-family:Georgia,serif;font-size:1.05rem;color:#faf7ef}
/* Issue #3. The picture sits between the scene's name and its read-aloud, so a player reads the words
   under the thing they describe. Height-capped: a tall portrait-shaped picture would otherwise push the
   objectives and the hint button off the panel entirely. */
.scene-picture[b-jfcgfioi7o]{margin:.7rem 0 0;line-height:0}
.scene-picture img[b-jfcgfioi7o]{display:block;width:100%;max-height:15rem;object-fit:cover;border-radius:10px;border:1px solid #43537c}
/* The written-once opening (issue #223). Upright and a shade warmer than the boxed text below it, and
   the two must not look alike: one is the author's sentences and one is a retelling of them, and a
   player is entitled to know which is which. The rule that says so is the left edge — the same device
   the DM notes use to mean "this line has a different author". */
.scene-prologue[b-jfcgfioi7o]{margin:.7rem 0 0;padding:0 0 0 .65rem;border-left:2px solid #6f7ea8;
    font-size:.82rem;line-height:1.6;color:#e2dcc9}
.scene-read-aloud[b-jfcgfioi7o]{margin:.6rem 0 0;font-size:.82rem;line-height:1.55;color:#d5c9a8;font-style:italic}
.scene-dm-notes[b-jfcgfioi7o]{margin:.6rem 0 0;padding:.6rem;border-radius:8px;font-size:.75rem;line-height:1.5;color:#d7c5ff;background:#221b36;border:1px solid #55447d}
.objectives-card[b-jfcgfioi7o]{padding:1rem;border:1px solid #303b57;border-radius:14px;background:#111827d9}
.objectives-card ul[b-jfcgfioi7o]{list-style:none;margin:.4rem 0 0;padding:0;display:grid;gap:.55rem}
.objectives-card li[b-jfcgfioi7o]{font-size:.78rem;color:#c3cbe6;display:grid;gap:.15rem}
.objectives-card li span[b-jfcgfioi7o]{color:#8e9ab8;font-size:.72rem;line-height:1.45}
.objectives-card li.dm-only[b-jfcgfioi7o]{border-left:2px solid #55447d;padding-left:.5rem;color:#d7c5ff}

/* The way back to the summary, for a player who chose to stay and tie off loose ends. Deliberately the
   most prominent thing in the Scene panel once it appears: the adventure IS over, and the only reason
   this campaign is still open is that they asked for a minute. */
.end-adventure[b-jfcgfioi7o]{width:100%;padding:.7rem .9rem;border:1px solid #6b8f5f;border-radius:12px;
    background:#16220f;color:#bfe0a8;font-size:.85rem;font-weight:700;cursor:pointer;text-align:left}
.end-adventure:hover[b-jfcgfioi7o]{background:#1d2f14;border-color:#8ab97a}

/* Just the way in. The shop itself is MerchantShop, which carries its own stylesheet because scoped CSS
   does not cross components — so nothing about the shelf, the prices or the buttons belongs here. */
.shop-card[b-jfcgfioi7o]{padding:.7rem .9rem;border:1px solid #4a4230;border-radius:14px;background:#1c180fd9}
.shop-card .eyebrow[b-jfcgfioi7o]{margin:0 0 .5rem}
.shop-note[b-jfcgfioi7o]{margin:.5rem 0 0;font-size:.74rem;color:#c8b48a}

/* Asking the adventure what the party remembers. Quiet on purpose: it is always there, and a hint that
   shouts is a hint that makes people feel they were meant to need it. */
.ask-hint[b-jfcgfioi7o]{width:100%;padding:.55rem .9rem;border:1px dashed #4a5570;border-radius:12px;
    background:#151a26;color:#9fb0d0;font-size:.78rem;cursor:pointer;text-align:left}
.ask-hint:hover:not(:disabled)[b-jfcgfioi7o]{background:#1b2233;border-color:#6f7fa4;color:#cddaf0}
.ask-hint:disabled[b-jfcgfioi7o]{opacity:.45;cursor:default}

/* Lifted out of the page's minified base block, where it had lived since the POC. Only this pane has
   ever rendered an .engine-note, so it comes with the markup rather than being promoted. */
.engine-note[b-jfcgfioi7o]{margin:1rem;padding:1rem;display:flex;gap:.65rem;background:#0c111bd9;border-radius:12px;color:#9ba7c4}
.engine-note p[b-jfcgfioi7o]{margin:0;font-size:.75rem;line-height:1.45}
/* /Components/Play/TabHost.razor.rz.scp.css */
/* The tab layout's own stylesheet, moved with its markup in #261 phase 4.

   ⭐ Scoped, and that is the strongest statement this file makes: NOTHING outside this host draws a
   play-grid, a story panel, an encounter panel, a tab strip or a tool stack. FloatHost will draw none of
   it — it has no columns and no tabs — which is exactly why the layout could come out whole, and why
   both layouts can ship without forking the play surface.

   The panes' own vocabulary went to their own sheets, and whatever more than one surface draws went to
   wwwroot/play.css. Only ARRANGEMENT is here. */

/* Each pane is sized to the VIEWPORT, never to the other pane. As a grid row both columns share a
   height taken from whichever is taller, and .story-panel stretched to fill it — so opening a full
   Party tab silently grew the chat window past the fold, and the screen changed shape depending on
   which tool happened to be selected. Fixed heights with internal scrolling make the layout stable
   whatever any tab contains. */
.play-grid[b-agj2uokpkb]{align-items:start}
.story-panel[b-agj2uokpkb]{height:calc(100vh - 12rem);min-height:26rem}
.encounter-panel[b-agj2uokpkb]{display:flex;flex-direction:column;max-height:calc(100vh - 12rem);align-self:start}
.encounter-panel .tool-tabs[b-agj2uokpkb]{flex:0 0 auto}
.encounter-panel .tool-stack[b-agj2uokpkb]{flex:1;min-height:0}
/* ⭐ The Combat tab takes the whole column, and only it. `max-height` alone leaves the panel sized by its
   CONTENT and parked at the top of its grid cell by `align-self:start` — so a fight with few combatants
   ended below a third of empty column, which is what Dave measured. A definite height is what gives the
   tool stack room to distribute and lets the minimap grow into it.

   Sized to the VIEWPORT and not to the other pane, deliberately: that is the same rule the note above
   states, and it is why .story-panel is `calc(100vh - 12rem)` rather than `100%`. Two panes that size to
   each other change shape depending on which tab happens to be open.

   ⚠️ Beaten on purpose by `.game-shell.expanded .encounter-panel` (three classes to this rule's two),
   which sets height:auto because expanded the shell IS the window and the panes take their size from the
   flex row instead. */
.encounter-panel.fills[b-agj2uokpkb]{height:calc(100vh - 12rem)}
.game-shell.expanded .play-grid[b-agj2uokpkb]{flex:1;min-height:0;margin:0}
/* height:auto matters — expanded, the shell is already the window, so the panes take their size from
   the flex row rather than from the viewport calc that sizes them in the normal layout. */
.game-shell.expanded .story-panel[b-agj2uokpkb],.game-shell.expanded .encounter-panel[b-agj2uokpkb]{height:auto;min-height:0;max-height:100%;overflow:hidden;display:flex;flex-direction:column}
.game-shell.expanded .encounter-panel[b-agj2uokpkb]{overflow-y:auto}
/* Right pane tabs. The tool stack scrolls inside its own pane rather than the whole page. */
.tool-tabs[b-agj2uokpkb]{padding:.75rem .9rem 0;flex-wrap:wrap;gap:.35rem}
.encounter-panel .tool-stack[b-agj2uokpkb]{padding:.9rem;display:grid;gap:.75rem;overflow-y:auto}
/* The Combat tab only (Dave, 2026-08-10: "about 30% of the left column unused"). A flex column so the
   minimap can be told to take the slack — a grid hands every row its content height and leaves the
   remainder as dead space at the bottom, which is exactly what he was looking at.

   ⚠️ `min-height:0` on the children that scroll, and the minimap opts IN to growing via its own `grows`
   class — this stylesheet cannot reach `.mini-map`, which carries MiniMap's scope attribute and not this
   page's. Styling another component's element from here is the mistake that has cost this repo three
   separate afternoons; the component owns its own sizing and takes a parameter. */
.encounter-panel .tool-stack.combat-stack[b-agj2uokpkb]{display:flex;flex-direction:column}
/* Everything except the minimap keeps its natural height and is allowed to be as tall as it needs.

   ⚠️ ::deep IS LOAD-BEARING. Written plainly this compiles to `.combat-stack > *[b-…]`, and since #261
   split the combat surface into panes the direct children of this stack are elements rendered by OTHER
   components, which carry their own scope attribute and never this page's. The rule would have matched
   nothing and the stack would have silently gone back to its content-height layout — the exact 30% of
   dead column Dave reported. ::deep moves the attribute to the ancestor instead. */
.combat-stack[b-agj2uokpkb]  > *{flex:0 0 auto}
/* ⤳ The 90-character measure moved to wwwroot/play.css (#261 phase 3), with the rest of the bubble
   family. Same reason and same discovery route as the companion bubble: scoped here it had become
   `.message[b-fdu3vypdan]` and could not reach a line TableLogView draws. */

/* Map tab: the board takes the pane, with the turn status pinned beneath it. */
.map-stage[b-agj2uokpkb]{flex:1;min-height:0;display:flex;flex-direction:column;gap:.6rem;padding:.9rem;overflow:hidden}
.play-grid[b-agj2uokpkb]{grid-template-columns:minmax(0,1.45fr) minmax(360px,1fr)}
.tool-stack[b-agj2uokpkb]{display:grid;gap:.8rem;padding:1rem}
/* M16: tabbed session log (sandbox-proven pattern), authored scene surface, party badges.
   The STRIP stays here — this host is the only thing that draws one. */
.chat-tabs[b-agj2uokpkb]{display:flex;gap:.4rem;flex-wrap:wrap}
/* ⤳ .chat-tab and .chat-tab.active moved to wwwroot/play.css (#261). FloatHost draws the same button for
   the record's mode switcher, so scoped here the rule reached the tab strip and left the floating one as
   an unstyled browser button. Two drawers, one home. */
/* Lifted out of the page's minified base block, where the two-column layout had lived since the
   POC. The shell above this host is a header and a set of fixed layers; none of it is a column. */
.play-grid[b-agj2uokpkb]{display:grid;grid-template-columns:minmax(0,1.8fr) minmax(290px,.8fr);gap:1.25rem}
.story-panel[b-agj2uokpkb],.encounter-panel[b-agj2uokpkb]{background:linear-gradient(145deg,#141a2ae6,#0e1320e6);border:1px solid #27334f;border-radius:18px;box-shadow:0 22px 70px #03060d70;backdrop-filter:blur(2px)}
.story-panel[b-agj2uokpkb]{min-height:72vh;display:flex;flex-direction:column}
.panel-heading[b-agj2uokpkb]{justify-content:space-between;padding:1.25rem 1.4rem;border-bottom:1px solid #27334f}
.panel-heading h2[b-agj2uokpkb]{font-size:1.2rem}
.ai-note[b-agj2uokpkb]{color:#d7c5ff;border-color:#55447d}
.encounter-panel[b-agj2uokpkb]{align-self:start}

/* ⚠️ The narrow-window rules, and they are the easiest thing in an extraction to leave behind: a
   responsive rule orphaned in the wrong sheet is correct at every width anyone tests at and wrong
   on a laptop. Both blocks style THIS host's columns — the tool column goes first because on one
   column the board and the tracker matter more than the scrollback above them. */
@media(max-width:900px){.play-grid[b-agj2uokpkb]{grid-template-columns:1fr}.encounter-panel[b-agj2uokpkb]{order:-1}.story-panel[b-agj2uokpkb]{min-height:60vh}}
@media(max-width:900px){.play-grid[b-agj2uokpkb]{grid-template-columns:1fr}}
/* /Components/Play/TableLogView.razor.rz.scp.css */
/* The live mechanical log: a pulse running underneath the conversation, not a participant in it.
   No bubble, no avatar, no header — one quiet line per thing that happened, smaller than the prose
   so it reads as machinery rather than as somebody speaking. */
.pulse-line[b-orfdkcwmjk]{margin:.15rem 0 .15rem .35rem;padding:0 0 0 .6rem;border-left:2px solid #2f2a1c;
    font-size:.74rem;line-height:1.45;color:#9a917f;letter-spacing:.01em}

/* The outcome words, shared by the quiet pulse under the prose and the Rolls log's own bubbles. A miss
   is red rather than the muted grey it started as: grey reads as "nothing happened here", and a missed
   attack is a thing that happened.

   ⛔ SCOPED, AND ::deep IS STILL LOAD-BEARING — which is exactly why these did NOT go to play.css with
   the bubble rules beside them. The <b> is not in this file's markup: it arrives inside a MarkupString
   from MechanicalLine.Decorate, and Blazor stamps its scope attribute only on elements it can see at
   compile time. Written plainly, these compile to `.pulse-line b.hit[b-…]`, which asks a
   runtime-injected element for an attribute it can never have, so the rule matched nothing on EITHER
   surface and the words rendered in the inherited grey. ::deep moves the attribute to the ancestor —
   `.pulse-line[b-…] b.hit` — which is the thing that was actually wanted.

   Worth saying plainly because the previous round of this got it wrong: the colouring was reported as
   working on the pulse and broken on the Rolls tab. It had never worked on either.

   ⚠️ They are scoped HERE, to the component that renders the MarkupString, which is what makes the
   ancestor selector land. Promoting them to the global sheet would work too — but only by dropping
   ::deep, and a rule whose comment explains at length why ::deep is required is the last rule anyone
   should be editing while moving it. Moved verbatim instead. */
.pulse-line[b-orfdkcwmjk]  b,.message.engine[b-orfdkcwmjk]  b{font-weight:800;letter-spacing:.04em}
.pulse-line[b-orfdkcwmjk]  b.hit,.message.engine[b-orfdkcwmjk]  b.hit{color:#8fd3a2}
.pulse-line[b-orfdkcwmjk]  b.crit,.message.engine[b-orfdkcwmjk]  b.crit{color:#ffd479;text-shadow:0 0 6px rgba(255,212,121,.35)}
.pulse-line[b-orfdkcwmjk]  b.miss,.message.engine[b-orfdkcwmjk]  b.miss{color:#e2736f}
.pulse-line[b-orfdkcwmjk]  b.fumble,.message.engine[b-orfdkcwmjk]  b.fumble{color:#ff5f56;text-shadow:0 0 6px rgba(255,95,86,.3)}

/* The verb, lifted out of the sentence. Every line in the live log opens with a name, and a column of
   names all looks the same until the thing that happened is given its own weight. */
.pulse-line[b-orfdkcwmjk]  .verb{display:inline-block;padding:0 .3rem;border-radius:4px;background:#2a2620;color:#cbbfa6;
    font-size:.72rem;font-weight:800;letter-spacing:.07em;vertical-align:baseline}
/* The same chip in the Rolls log, where the bubble is darker and the text is full size. */
.message.engine[b-orfdkcwmjk]  .verb{display:inline-block;padding:0 .35rem;border-radius:4px;background:#39414f;
    color:#dfe5f2;font-size:.78rem;font-weight:800;letter-spacing:.07em;vertical-align:baseline}
/* /Components/Play/WorldClock.razor.rz.scp.css */
/* The clock's own vocabulary — ALL of it, which it did not used to be.

   ⛔⛔ #1495. Two families of rule for this component's markup used to live elsewhere, and neither could
   ever match it:

   1. The LOOK (.world-clock, .clock-date, .clock-part, .expanded-clock) sat in Play.razor.css, where it
      compiled to `.world-clock[b-fdu3vypdan]` — Play's scope attribute, against markup this component
      renders. So the clock has been drawing as bare text on /play since it was extracted here. That file
      already carried the same post-mortem twice, for LevelUpPanel and for TableLogView's bubbles.
   2. The MENU BOX was `.game-shell .clock-menu` in wwwroot/play.css. Global, so it loads everywhere, but
      it needs a `.game-shell` ancestor — and FenReach's shell is `.fen-shell`, so the format menu would
      have opened as unstyled markup over the table.

   Both are below now, scoped to the component that renders them, so the clock looks the same on every
   surface that mounts it. ⚠️ play.css keeps the `.game-shell .dm-menu` family untouched: FloatHost draws
   its own menus with those classes and a scoped rule here could never reach them. The duplication is
   deliberate and is the smaller of the two evils — the alternative is a global selector that has to be
   widened by hand for every shell that ever mounts this clock.
   → memory `a-feature-authored-but-unreachable`, `a-mark-drawn-under-what-it-belongs-inside` */

/* --- the reading itself -------------------------------------------------------------------------- */

/* Deliberately prominent: the time estimate is new and needs watching, and a number nobody can see is a
   number nobody can tell is wrong. */
.world-clock[b-vqrk57as6u]{display:flex;flex-direction:column;align-items:flex-end;gap:.05rem;padding:.35rem .7rem;
    border:1px solid #46557f;border-radius:12px;background:#141a2ac9;cursor:help;white-space:nowrap}
.clock-date[b-vqrk57as6u]{font-size:.62rem;letter-spacing:.08em;text-transform:uppercase;color:#8e9ab8}
.clock-part[b-vqrk57as6u]{font-size:.62rem;letter-spacing:.05em;color:#9fb0dc;font-style:italic}
/* The one-line form, for a strip or a HUD bar. ⚠️ `max-width` and truncation because FenReach's top bar is
   authored at 390px and shares that row with the campaign name and two buttons. */
.expanded-clock[b-vqrk57as6u]{display:inline-block;max-width:11rem;overflow:hidden;text-overflow:ellipsis;
    padding:.2rem .5rem;border:1px solid #46557f;border-radius:999px;background:#141a2a;color:#f4d58d;
    font-size:.72rem;font-weight:700;white-space:nowrap;cursor:help}

/* `relative` so the menu can be positioned against the clock rather than against the viewport — this one
   opens from a fixed piece of chrome, unlike a window's menu which opens at a pointer that could be
   anywhere. */
.world-clock-host[b-vqrk57as6u]{position:relative;display:inline-flex;align-items:center;cursor:context-menu}

/* ⚠️ Anchored to the RIGHT edge. The clock sits mid-strip and the sample column makes this menu wide;
   opening it leftward keeps it on screen at the narrow end of the strip, where a left-anchored menu ran
   off the edge. */
.clock-menu[b-vqrk57as6u]{position:absolute;top:calc(100% + .4rem);right:0;min-width:15rem}

.clock-menu-name[b-vqrk57as6u]{flex:0 0 auto;font-weight:700}
/* The live sample, pushed to the right and dimmed — it is evidence for the choice, not the choice. */
.clock-menu-sample[b-vqrk57as6u]{flex:1;text-align:right;color:#8e9ab8;font-size:.7rem;
    font-variant-numeric:tabular-nums;white-space:nowrap}

/* --- the menu's own box, so it is drawn under any shell (#1495) --------------------------------- */

/* ⚠️ The BOX is here; the POSITIONING is the rule above. A window's menu opens at a pointer and is
   `fixed`; this one opens from a fixed piece of chrome and is `absolute` against the clock — given
   `fixed`, `top:100%` would resolve against the viewport and drop the menu off the bottom of the screen. */
/* ⛔ The tokens, not a hand-typed number: this backdrop covers the whole window, so it has to relate to
   the pane bands in dm-panes.js rather than guess above them. It is a dismiss-catcher — over the board
   and the panes, under any dialog — which is exactly --dm-layer-scrim. The menu sits one above its own
   backdrop; leaving it at a bare 701 would have put the menu UNDER the scrim it opened with. (#1500) */
.dm-menu-backdrop[b-vqrk57as6u]{position:fixed;inset:0;z-index:var(--dm-layer-scrim);background:transparent}
.clock-menu[b-vqrk57as6u]{z-index:calc(var(--dm-layer-scrim) + 1);padding:.3rem;display:flex;flex-direction:column;gap:.1rem;
    border:1px solid #303b58;border-radius:10px;background:#0e1320f7;
    box-shadow:0 22px 70px #03060d90;backdrop-filter:blur(2px)}
.dm-menu-head[b-vqrk57as6u]{margin:0;padding:.25rem .5rem .35rem;border-bottom:1px solid #27334f;
    font-size:.66rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:#8b96b6}
.clock-menu button[b-vqrk57as6u]{display:flex;align-items:center;gap:.4rem;width:100%;text-align:left;border:0;
    border-radius:6px;background:transparent;color:#bec9e7;padding:.35rem .5rem;font-size:.75rem;
    cursor:pointer}
.clock-menu button:hover[b-vqrk57as6u]{background:#1d2540;color:#dfe6f6}
/* Fixed width so a label does not step sideways as its tick appears and disappears. */
.dm-menu-tick[b-vqrk57as6u]{display:inline-block;width:.8rem;color:#e8c36a;font-weight:800}
/* /Components/Shared/ActorDerivationPreview.razor.rz.scp.css */
.derivation-preview[b-0ua4wuwf0s] { padding: .9rem; border: 1px solid #51442f; background: #15120e; }
.derivation-preview.valid[b-0ua4wuwf0s] { border-color: #507c48; }
.derivation-preview.invalid[b-0ua4wuwf0s] { border-color: #815044; }
.derivation-preview h3[b-0ua4wuwf0s], .derivation-preview h4[b-0ua4wuwf0s] { margin: 0 0 .5rem; color: #e6b85c; }
.derivation-preview dl[b-0ua4wuwf0s] { display: grid; grid-template-columns: repeat(5, 1fr); gap: .5rem; }
.derivation-preview dl div[b-0ua4wuwf0s] { display: grid; gap: .2rem; }
.derivation-preview dt[b-0ua4wuwf0s] { color: #9f927f; font-size: .78rem; }
.derivation-preview dd[b-0ua4wuwf0s] { margin: 0; font-weight: 700; }
@media (max-width: 800px) { .derivation-preview dl[b-0ua4wuwf0s] { grid-template-columns: 1fr 1fr; } }
/* /Components/Shared/ActorSheet.razor.rz.scp.css */
.actor-sheet-backdrop[b-bg054j216w]{position:fixed;inset:0;z-index:var(--dm-layer-dialog,1000);display:grid;place-items:center;padding:1.5rem;background:#050403bf;backdrop-filter:blur(5px)}
.actor-sheet[b-bg054j216w]{width:min(58rem,100%);max-height:min(52rem,calc(100vh - 3rem));overflow:auto;color:#eee8dc;border:1px solid #796241;border-radius:.75rem;background:#17130f;box-shadow:0 30px 90px #000c}
/* FOUR columns, not three: avatar · name · optional toolbar · close. With three, a header that HAD a
   toolbar pushed the close button onto a second row at the far LEFT — the whole of Dave's play note 6.
   The fourth track collapses to zero when no toolbar is supplied, so the close stays top-right either way. */
.sheet-header[b-bg054j216w]{position:sticky;top:0;z-index:1;display:grid;grid-template-columns:auto 1fr auto auto;align-items:center;gap:1rem;padding:1.2rem 1.35rem;border-bottom:1px solid #4c3d2a;background:#17130ff5;backdrop-filter:blur(8px)}
.sheet-header h2[b-bg054j216w]{margin:.08rem 0;color:#f2d38e;font:600 1.65rem/1.1 Georgia,serif}.sheet-header p[b-bg054j216w]{margin:.2rem 0;color:#a99c87}.sheet-header .eyebrow[b-bg054j216w]{color:#d7aa55;font:700 .68rem/1.2 ui-monospace,monospace;letter-spacing:.13em}.sheet-avatar[b-bg054j216w]{display:grid;place-items:center;width:3.1rem;height:3.1rem;border:1px solid #9b7b45;border-radius:50%;color:#17130f;background:#ddb766;font-weight:900}
/* ⭐ #592: the same disc, holding a picture instead of initials. `overflow:hidden` plus a round element
   is what crops a rectangular portrait to the circle — `border-radius` alone does not clip a child img.
   `object-fit:cover` so a tall or wide portrait fills the disc instead of squashing to fit it. */
.sheet-avatar.has-portrait[b-bg054j216w]{padding:0;background:#17130f}
.sheet-avatar.has-portrait[b-bg054j216w]{overflow:hidden}
.sheet-avatar.has-portrait img[b-bg054j216w]{width:100%;height:100%;object-fit:cover;display:block}

/* Where the body came from. Faint and set apart: it is history rather than a stat, and it must not
   compete with the numbers a player is reading to decide something. */
.sheet-section.fell h3[b-bg054j216w]{color:#b9a07a}
.sheet-section.fell .sheet-list strong[b-bg054j216w]{color:#cbb68f}

/* Placement only — the look comes from the shared .dm-close in app.css. */.sheet-close[b-bg054j216w]{align-self:start;justify-self:end}
.sheet-tabs[b-bg054j216w]{display:flex;gap:.35rem;padding:.7rem 1.35rem 0}
.sheet-tabs button[b-bg054j216w]{padding:.45rem .95rem;border:1px solid #4c3d2a;border-bottom:0;border-radius:.4rem .4rem 0 0;background:#1d1914;color:#a99c87;font:700 .74rem/1 inherit;cursor:pointer}
.sheet-tabs button:hover[b-bg054j216w]{color:#e3bd70}
.sheet-tabs button.on[b-bg054j216w]{background:#f5efe1;border-color:#796241;color:#241d16}
/* WHICH character (Dave, 2026-09-13). Wraps rather than scrolls, so a party of six never hides a tab. */
.sheet-members[b-bg054j216w]{flex-wrap:wrap;border-bottom:1px solid #4c3d2a}
.sheet-members + .sheet-tabs[b-bg054j216w]{padding-top:.45rem}
.sheet-vitals[b-bg054j216w]{display:grid;grid-template-columns:repeat(4,1fr);gap:.65rem;padding:1rem 1.35rem}.sheet-vitals span[b-bg054j216w]{display:grid;gap:.15rem;padding:.75rem;border:1px solid #3e3428;background:#201b15}.sheet-vitals small[b-bg054j216w]{color:#a99c87;text-transform:uppercase;font:700 .66rem/1 ui-monospace,monospace}.sheet-vitals strong[b-bg054j216w]{font-size:1.1rem;color:#f0d18d}
.sheet-columns[b-bg054j216w]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem;padding:0 1.35rem 1.35rem}.sheet-section[b-bg054j216w]{border:1px solid #40362a;background:#1d1914;padding:.9rem}.sheet-section h3[b-bg054j216w]{margin:0 0 .7rem;color:#e3bd70;font-size:.88rem;text-transform:uppercase;letter-spacing:.06em}.action-budget[b-bg054j216w]{margin:0 1.35rem 1rem}.action-budget div[b-bg054j216w]{display:flex;gap:.5rem;flex-wrap:wrap}.action-budget span[b-bg054j216w],.sheet-actions .sheet-action[b-bg054j216w]{padding:.3rem .5rem;border-radius:999px;background:#302719;color:#ddc28d;font-size:.78rem}.sheet-actions .sheet-action.own[b-bg054j216w]{background:#3d2f15;color:#f0d38f;font-weight:600}.sheet-actions .sheet-actions-group[b-bg054j216w]{margin:.55rem 0 .35rem;color:#9c8d76;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase}.sheet-list[b-bg054j216w]{display:grid;gap:.5rem;list-style:none;margin:0;padding:0}.sheet-list li[b-bg054j216w]{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:.25rem .75rem;padding:.55rem;border-bottom:1px solid #352d24}.sheet-list li:last-child[b-bg054j216w]{border:0}.sheet-list span[b-bg054j216w]{color:#b9aa94}.sheet-list code[b-bg054j216w]{grid-column:1/-1;color:#827763;font-size:.68rem}.sheet-list.compact li[b-bg054j216w]{padding:.4rem .2rem}.modifier-grid[b-bg054j216w]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.4rem;margin:0}.modifier-grid div[b-bg054j216w]{display:flex;justify-content:space-between;gap:.5rem;padding:.45rem .55rem;background:#15120f}.modifier-grid dt[b-bg054j216w]{color:#b9aa94}.modifier-grid dd[b-bg054j216w]{margin:0;color:#f0d18d;font-weight:700}.empty-sheet-row[b-bg054j216w]{margin:0;color:#8f8371;font-style:italic}.sheet-actions[b-bg054j216w]{margin:0 1.35rem 1.35rem}.sheet-actions div[b-bg054j216w]{display:flex;gap:.4rem;flex-wrap:wrap}
@media(max-width:650px){.actor-sheet-backdrop[b-bg054j216w]{padding:0}.actor-sheet[b-bg054j216w]{max-height:100vh;height:100vh;border-radius:0}.sheet-vitals[b-bg054j216w],.sheet-columns[b-bg054j216w]{grid-template-columns:repeat(2,minmax(0,1fr))}.sheet-columns[b-bg054j216w]{grid-template-columns:1fr}.sheet-header[b-bg054j216w]{padding:1rem}.sheet-vitals[b-bg054j216w],.sheet-columns[b-bg054j216w]{padding-left:1rem;padding-right:1rem}.action-budget[b-bg054j216w],.sheet-actions[b-bg054j216w]{margin-left:1rem;margin-right:1rem}}

/* The purse sits above the goods, because "can I afford it" is asked more often than "what am I carrying". */
.sheet-coins[b-bg054j216w]{display:flex;justify-content:space-between;gap:.5rem;margin:0 0 .6rem;padding:.45rem .55rem;
    background:#15120f;font-size:.8rem}
.sheet-coins span[b-bg054j216w]{color:#b9aa94}
.sheet-coins strong[b-bg054j216w]{color:#e3bd70;font-variant-numeric:tabular-nums}

/* Spells read as a sheet section: the numbers first, then what is actually in the slots today. */
.sheet-section.spells[b-bg054j216w]{grid-column:1/-1}
.spell-numbers[b-bg054j216w]{display:flex;gap:.5rem;flex-wrap:wrap;margin:0 0 .7rem}
.spell-numbers span[b-bg054j216w]{display:flex;flex-direction:column;gap:.1rem;padding:.35rem .6rem;background:#15120f;min-width:6rem}
.spell-numbers small[b-bg054j216w]{color:#8f8371;font-size:.66rem;text-transform:uppercase;letter-spacing:.05em}
.spell-numbers strong[b-bg054j216w]{color:#f0d18d;font-size:.9rem}
.spell-note[b-bg054j216w]{margin:.6rem 0 .4rem;color:#8f8371;font-size:.72rem;font-style:italic}
.sheet-list li.spent strong[b-bg054j216w]{color:#8f8371;text-decoration:line-through}
.sheet-list li em[b-bg054j216w]{font-style:normal;color:#827763}

/* An unidentified creature (issues #115/#116). Three vitals rather than four, because a sheet that
   cannot quote a number should not leave a box where the number used to be. */
.sheet-vitals.plain[b-bg054j216w]{grid-template-columns:repeat(3,1fr)}
.sheet-unknown[b-bg054j216w]{margin:0 1.35rem .6rem;color:#a99c87;font-size:.78rem;line-height:1.5;font-style:italic}

/* Trying to place the thing. One button per body of knowledge that might explain it. */
.sheet-identify[b-bg054j216w]{display:flex;flex-wrap:wrap;gap:.45rem;padding:0 1.35rem 1rem}
.identify-button[b-bg054j216w]{border:1px solid #4a5c88;border-radius:9px;padding:.5rem .85rem;background:#161d2c;
    color:#b9caea;font-size:.76rem;font-weight:700;cursor:pointer}
.identify-button:hover:not(:disabled)[b-bg054j216w]{border-color:#6e86bd;background:#1d2739}
.identify-button:disabled[b-bg054j216w]{opacity:.45;cursor:not-allowed}

/* A durable item's condition (issue #283). Three states, because "17/20" alone does not tell a player
   they are one blow from wreckage — the colour is what makes it readable at a glance, and the broken
   state has to be unmissable since a broken shield grants and blocks nothing. Overrides the neutral
   `.sheet-list li em` above it, which sets its own colour. */
.sheet-list .item-condition[b-bg054j216w]{color:#8a9d7e;font-style:normal;white-space:nowrap}
.sheet-list .item-battered[b-bg054j216w]{color:#d9a24a;font-style:normal;white-space:nowrap;font-weight:700}
.sheet-list .item-broken[b-bg054j216w]{color:#d97a6c;font-style:normal;white-space:nowrap;font-weight:800}
/* Taking what is on the body (issue #118). */
.sheet-list.loot li[b-bg054j216w]{align-items:center}
.take-button[b-bg054j216w]{margin-left:auto;border:1px solid #6f5c33;border-radius:8px;padding:.22rem .6rem;
    background:#1d1a12;color:#e0c88a;font-size:.7rem;font-weight:700;cursor:pointer}
.take-button:hover:not(:disabled)[b-bg054j216w]{border-color:#a5893f;background:#272113}
.take-button:disabled[b-bg054j216w]{opacity:.45;cursor:not-allowed}
.take-all[b-bg054j216w]{margin-top:.6rem;width:100%;border:1px solid #6f5c33;border-radius:9px;padding:.55rem;
    background:linear-gradient(135deg,#3a2f16,#5a4720);color:#f4d58d;font-size:.78rem;font-weight:800;
    cursor:pointer}
.take-all:hover:not(:disabled)[b-bg054j216w]{border-color:#c09a45}
.take-all:disabled[b-bg054j216w]{opacity:.45;cursor:not-allowed}

/* What a Recall Knowledge check actually bought (#116). Reads as recalled knowledge rather than as a
   stat block: the heading is the shape of the fact, the detail is the fact. */
.sheet-section.lore .sheet-list li strong[b-bg054j216w]{color:#b9caea}

/* ⛔ ONE COLUMN HERE, AND IT IS NOT A TIDINESS RULE (Dave, on a dead hunting spider).
   ================================================================================================
   `.sheet-list li` is a two-column grid, `minmax(0,1fr) auto`: the first track MAY COLLAPSE TO ZERO and
   the second is sized to its content. Everywhere else on this sheet the second column is short — "Reach
   5", "×2" — so the heading keeps the rest of the row. The lore list is the only place it holds a
   PARAGRAPH, so the description claimed the whole width, the heading's track computed to nothing, and
   the <strong> overflowed its own column and printed ON TOP of the description. Measured in the browser
   rather than guessed: the row reported a first track of zero pixels, and "Web Sense" was rendering over
   "The hunting spider has imprecise tremorsense..." — hiding that sentence's first word entirely.

   A fact is a name and its explanation, so they stack: one track, two rows, heading at full width.

   ⚠️ Scoped to `.lore` deliberately. The two-column rule is RIGHT for attacks, items and spells, where
   the second value is a short figure that belongs beside the name. Widening this to `.sheet-list li`
   would stack every one of them and double the length of the sheet. */
.sheet-section.lore .sheet-list li[b-bg054j216w]{grid-template-columns:minmax(0,1fr)}
.sheet-section.lore .sheet-list li span[b-bg054j216w]{overflow-wrap:anywhere}
.lore-note[b-bg054j216w]{margin:.5rem 0 0;font-size:.7rem;font-style:italic;color:#8a7f6b}

/* Temporary hit points (issue #169). Its own colour because it is not hit points: spent first,
   and never healed back. Reading it as part of the maximum is the misreading to prevent. */
.temp-hp[b-bg054j216w] { margin-left: .3rem; padding: 0 .28rem; border-radius: 4px; font-size: .78em;
    color: #8fd4ff; background: rgba(90, 209, 255, .14); border: 1px solid rgba(90, 209, 255, .35); }

/* Host-supplied controls in the sheet header — level up, outfit. Sits between the name block and the
   close button, so the close button stays exactly where a player's eye already expects it. */
.sheet-toolbar[b-bg054j216w] {
    display: flex;
    align-items: center;
    gap: .4rem;
    flex: none;
    align-self: center;
}

/* ── The equip toggle (Dave, 2026-09-07) ────────────────────────────────────────────────────────────
   Sits where Take sits on a corpse's sheet: same column, same weight, so the eye learns one place for
   "the thing I can do to this line". The equipped state is the LABEL, not a tick — the slot is the
   useful word, and "ARMOR" answers a question a tick does not. */
.equip-button[b-bg054j216w] {
    font-size: .68rem;
    letter-spacing: .04em;
    text-transform: uppercase;
    padding: .12rem .4rem;
    border: 1px solid #3a4258;
    border-radius: .3rem;
    background: #1b2231;
    color: #9aa6bd;
    cursor: pointer;
}

.equip-button:hover:not(:disabled)[b-bg054j216w] { color: #f0e6cc; border-color: #d9c68f; }
.equip-button:disabled[b-bg054j216w] { opacity: .5; cursor: default; }

.equip-button.on[b-bg054j216w] {
    background: #2b2415;
    border-color: #d9c68f;
    color: #f0e6cc;
    font-weight: 700;
}

/* ⭐⭐ THE EXPANDED SHEET, in the two shapes Dave picked from four studies on 2026-09-09.
   ⚠️ The rows themselves live in SheetStatRow.razor.css. What is here is only what THIS component's
   markup writes — the grids around the rows, the attribute tiles, and the contents of a popover, which
   is passed in as child content and therefore carries THIS component's scope, not the row's.
   → memory `a-scoped-rule-cannot-reach-a-child` */

/* SIX TILES. A fixed set of short figures, so they may sit in a tight row instead of a list. */
.stat-tiles[b-bg054j216w]{display:grid;grid-template-columns:repeat(auto-fit,minmax(5.4rem,1fr));gap:.4rem}
.stat-tile[b-bg054j216w]{display:grid;justify-items:center;gap:.1rem;padding:.5rem .3rem .42rem;
    border:1px solid #40362a;border-radius:.4rem;background:#15120f;color:inherit;font:inherit;cursor:pointer}
.stat-tile:hover[b-bg054j216w]{border-color:#796241;background:#201b15}
.stat-tile:focus-visible[b-bg054j216w]{outline:none;border-color:#d7aa55;box-shadow:0 0 0 1px #d7aa55}
.stat-tile.open[b-bg054j216w]{background:#2b2415;border-color:#d9c68f}
/* The class's key attribute is the one number a new player is told to care about, so it is marked. */
.stat-tile.key[b-bg054j216w]{border-color:#6b5327}
.stat-tile.key .tile-abbr[b-bg054j216w]{color:#e3bd70}
.tile-abbr[b-bg054j216w]{font:700 .66rem/1 ui-monospace,monospace;letter-spacing:.1em;color:#a99c87}
.tile-mod[b-bg054j216w]{font:700 1.25rem/1.1 Georgia,serif;color:#f0d18d;font-variant-numeric:tabular-nums}
.tile-score[b-bg054j216w]{font-size:.66rem;color:#8f8371;font-variant-numeric:tabular-nums}

/* ⛔ ONE drawer, under the whole row of tiles. It is NOT a popover: with six tiles across, a card
   hanging off tile four would sit half outside the sheet, and there is room under the row for free. */
.stat-drawer[b-bg054j216w]{margin-top:.5rem;padding:.65rem .8rem .7rem;border:1px solid #796241;border-radius:.5rem;
    background:#241d16}
.drawer-head[b-bg054j216w]{display:flex;align-items:baseline;justify-content:space-between;gap:.8rem}
.drawer-name[b-bg054j216w]{margin:0;font:1.02rem/1.2 Georgia,serif;color:#f2d38e}
.drawer-close[b-bg054j216w]{font:inherit;font-size:.8rem;line-height:1;color:#a99c87;background:none;border:0;
    cursor:pointer;padding:.15rem .25rem}
.drawer-close:hover[b-bg054j216w]{color:#e3bd70}
.drawer-sub[b-bg054j216w]{margin:.1rem 0 .55rem;font-size:.72rem;color:#a99c87}

/* THE ROWS. Two columns on a wide sheet, one on a narrow one — a skill is a name and a number, and
   reads badly stretched across the full width. */
.stat-list[b-bg054j216w]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 1.1rem}
.stat-list.one-column[b-bg054j216w]{grid-template-columns:1fr}
@media(max-width:650px){.stat-list[b-bg054j216w]{grid-template-columns:1fr}}

/* ⭐ The working, left to right, the way it is said out loud: "dex +4, trained +3, so +7". The
   <dl> this replaced stacked term over value down the card and read as a form, not as arithmetic. */
.stat-maths[b-bg054j216w]{display:flex;flex-wrap:wrap;align-items:baseline;gap:.3rem .38rem;font-size:.78rem}
.stat-maths .term[b-bg054j216w]{padding:.15rem .4rem;border-radius:.3rem;background:#15120f;color:#b9aa94}
.stat-maths .term.penalty[b-bg054j216w]{color:#d8a58f}
.stat-maths .op[b-bg054j216w]{color:#8f8371}
.stat-maths .sum[b-bg054j216w]{padding:.15rem .45rem;border-radius:.3rem;background:#2b2415;color:#f0d18d;
    font-weight:700;font-variant-numeric:tabular-nums}
.stat-note[b-bg054j216w]{margin:.5rem 0 0;font-size:.78rem;line-height:1.5;color:#b9aa94}
.stat-note:first-child[b-bg054j216w]{margin-top:0}

.sheet-lineage[b-bg054j216w]{margin:.2rem 0 1rem;font-size:.82rem;color:#b9a07a}
.sheet-plain-list[b-bg054j216w]{margin:0;font-size:.82rem;line-height:1.6;color:#c7bda8}

/* People remember, Phase 5 (#1291): the chatter choice on a companion's tab, placed like the actions above it. */
.companion-chatter[b-bg054j216w]{margin:0 1.35rem 1.35rem}

/* ── Character sheet parity (Dave, 2026-09-22: "Lets do it all.") ─────────────────────────────────────
   Only what the new markup writes. Every figure is the ruleset view's; these rules only place it, in the
   sheet's own colours, so nothing that was already here changes look. */

/* Experience beside the level, and a thin bar to the next: the header's own muted tone, the gold of the numbers. */
.sheet-xp[b-bg054j216w]{display:flex;align-items:center;gap:.5rem;margin:.25rem 0 0;font-size:.76rem;color:#a99c87}
.sheet-xp-figure[b-bg054j216w]{color:#f0d18d;font-weight:700;font-variant-numeric:tabular-nums}
.sheet-xp-bar[b-bg054j216w]{position:relative;width:7rem;height:.35rem;border:1px solid #4c3d2a;border-radius:999px;background:#15120f;overflow:hidden}
.sheet-xp-bar span[b-bg054j216w]{position:absolute;inset:0 auto 0 0;background:#d7aa55}
.sheet-xp-next[b-bg054j216w]{font-size:.7rem}

/* Dying is the one vital that must catch the eye; wounded and hero points read as ordinary boxes. */
.sheet-vitals .vital-dying[b-bg054j216w]{border-color:#8a3b30}
.sheet-vitals .vital-dying strong[b-bg054j216w]{color:#e38a7a}

/* The Traits box: small tags, the way a stat block prints them. */
.sheet-traits[b-bg054j216w]{display:flex;flex-wrap:wrap;gap:.3rem;margin:-.6rem 0 1rem}
.sheet-trait[b-bg054j216w]{padding:.12rem .45rem;border:1px solid #4c3d2a;border-radius:.3rem;background:#15120f;color:#c7bda8;font:700 .66rem/1.3 ui-monospace,monospace;letter-spacing:.06em;text-transform:uppercase}

/* A group heading inside a section — the feat slots, the valuables — in the same voice as "Your own" above the actions. */
.sheet-feat-group[b-bg054j216w]{margin:.7rem 0 .3rem;color:#9c8d76;font-size:.7rem;letter-spacing:.08em;text-transform:uppercase}
.sheet-feat-group:first-of-type[b-bg054j216w]{margin-top:0}

/* The load, set like the purse above it; amber once the ruleset says it is too much. */
.sheet-bulk.strained strong[b-bg054j216w]{color:#d9a24a}

/* What the host says about something just done from the sheet. */
.sheet-notice[b-bg054j216w]{margin:.8rem 1.35rem 0;padding:.5rem .7rem;border:1px solid #4c3d2a;border-radius:.4rem;background:#201b15;color:#ddc28d;font-size:.8rem}
/* /Components/Shared/AdventureSummaryDialog.razor.rz.scp.css */
/* The close-out. Matches the loading veil deliberately — the two moments that take the whole screen
   are the one before an adventure and the one after it, and they should feel like a pair. */
.summary-veil[b-2zt5j74jue]{position:fixed;inset:0;z-index:var(--dm-layer-dialog,1000);display:flex;align-items:center;justify-content:center;
    padding:1.5rem;background:#070b12ee;backdrop-filter:blur(5px);overflow-y:auto}
/* The card itself is bounded too, and scrolls as a whole. The loot cap below stops one long list doing
   it; this stops a long party, a long objective list and a long loot list doing it together. */
.summary-card[b-2zt5j74jue]{max-width:34rem;width:100%;max-height:calc(100vh - 3rem);overflow-y:auto;
    padding:1.8rem 2rem;border:1px solid #303b57;border-radius:18px;
    background:#0e1320f7;box-shadow:0 24px 80px #03060da0}

.summary-eyebrow[b-2zt5j74jue]{margin:0;font-size:.68rem;letter-spacing:.16em;text-transform:uppercase;color:#e8c36a}
.summary-card h2[b-2zt5j74jue]{margin:.35rem 0 1.2rem;font-family:Georgia,serif;font-size:1.5rem;color:#faf7ef;font-weight:600}
.summary-section[b-2zt5j74jue]{margin:0 0 1.2rem}
.summary-section h3[b-2zt5j74jue]{margin:0 0 .5rem;font-size:.72rem;letter-spacing:.12em;text-transform:uppercase;color:#8e9ab8}

.summary-objectives[b-2zt5j74jue]{margin:0;padding-left:1.1rem;font-size:.85rem;line-height:1.6;color:#cbd4e6}

.summary-party[b-2zt5j74jue]{list-style:none;margin:0;padding:0;display:grid;gap:.35rem}
.summary-party li[b-2zt5j74jue]{display:grid;grid-template-columns:1fr auto auto;gap:.7rem;align-items:baseline;
    padding:.4rem .6rem;border:1px solid #27334f;border-radius:10px;background:#131a2b99;font-size:.85rem}
/* Your own character reads first and looks it — the rest go back on the shelf when this closes. */
.summary-party li.yours[b-2zt5j74jue]{border-color:#4d5f8d;background:#182338cc}
.summary-party .who[b-2zt5j74jue]{color:#f0ece2}
.summary-party .level[b-2zt5j74jue]{color:#8e9ab8;font-size:.76rem}
.summary-party .xp[b-2zt5j74jue]{color:#e8c36a;font-weight:700;font-size:.8rem}

.summary-coins[b-2zt5j74jue]{margin:0 0 .5rem;font-size:.85rem;color:#e8c36a}
/* Capped and scrolled. A party that emptied a dungeon pushed the buttons off the bottom of the screen,
   which turns the one dialog you cannot skip into one you cannot use. */
.summary-loot[b-2zt5j74jue]{list-style:none;margin:0;padding:0;display:grid;gap:.25rem;
    max-height:11rem;overflow-y:auto;overscroll-behavior:contain}
.summary-loot li[b-2zt5j74jue]{display:flex;justify-content:space-between;gap:.7rem;font-size:.82rem;color:#cbd4e6;
    padding:.25rem .5rem;border-radius:8px}
.summary-loot li.valuable[b-2zt5j74jue]{background:#221c0f99;color:#f4d58d}
.summary-loot .who[b-2zt5j74jue]{color:#7f8aa6;font-size:.74rem}
.summary-empty[b-2zt5j74jue]{margin:0;font-size:.85rem;font-style:italic;color:#7f8aa6}
.summary-note[b-2zt5j74jue]{margin:.7rem 0 0;font-size:.74rem;line-height:1.5;color:#8e9ab8;font-style:italic}
.summary-error[b-2zt5j74jue]{margin:0 0 .8rem;padding:.5rem .7rem;border:1px solid #8d4046;border-radius:10px;
    background:#2a1418cc;font-size:.8rem;color:#ffb8be}

.summary-actions[b-2zt5j74jue]{display:flex;flex-wrap:wrap;gap:.6rem;margin-top:1.4rem}
.summary-actions button[b-2zt5j74jue]{flex:1 1 12rem;padding:.6rem .9rem;border-radius:11px;border:1px solid #3a4a6e;
    background:#161e30;color:#dfe5f2;font-size:.85rem;cursor:pointer}
.summary-actions button:hover:not(:disabled)[b-2zt5j74jue]{background:#1e293f;border-color:#4d5f8d}
.summary-actions button:disabled[b-2zt5j74jue]{opacity:.5;cursor:default}
.summary-actions .primary[b-2zt5j74jue]{border-color:#6b8f5f;background:#16220f;color:#bfe0a8;font-weight:700}
.summary-actions .primary:hover:not(:disabled)[b-2zt5j74jue]{background:#1d2f14;border-color:#8ab97a}
.summary-hint[b-2zt5j74jue]{margin:.8rem 0 0;font-size:.72rem;line-height:1.55;color:#7f8aa6}
.summary-hint strong[b-2zt5j74jue]{color:#bfe0a8;font-weight:600}
/* /Components/Shared/BattleMap.razor.rz.scp.css */
.battle-map-shell[b-avn663v8g5]{display:block}.battle-map[b-avn663v8g5]{display:grid;position:relative;width:100%;min-height:15rem;border:1px solid #34405c;border-radius:12px;overflow:hidden;background:#0b111d}.map-cell[b-avn663v8g5]{min-width:0;min-height:0;border:0;border-right:1px solid #202b40;border-bottom:1px solid #202b40;background:#111a2a;color:#7e8aa8;display:grid;place-items:center;padding:0}.map-cell:hover[b-avn663v8g5],.map-cell:focus-visible[b-avn663v8g5]{background:#1a2940;outline:2px solid #7aa2d8;outline-offset:-2px}.map-cell.blocked[b-avn663v8g5]{background:repeating-linear-gradient(135deg,#242b38,#242b38 5px,#171d29 5px,#171d29 10px);color:#687187}.map-cell.difficult[b-avn663v8g5]{background:repeating-linear-gradient(45deg,#1d3a29,#1d3a29 4px,#142419 4px,#142419 8px)}.map-cell.difficult:hover[b-avn663v8g5],.map-cell.difficult:focus-visible[b-avn663v8g5]{background:repeating-linear-gradient(45deg,#255034,#255034 4px,#1a3122 4px,#1a3122 8px)}.map-cell.cover[b-avn663v8g5]{box-shadow:inset 0 0 0 2px #4a72a8aa;border-bottom-right-radius:5px}.map-token[b-avn663v8g5]{z-index:2;margin:3px;border:2px solid #7d8bad;border-radius:9px;background:#283653e8;color:#f2f5fb;font-size:.7rem;font-weight:900;box-shadow:0 5px 14px #02050a99;cursor:grab}.map-token:active[b-avn663v8g5]{cursor:grabbing}.map-token.controlled[b-avn663v8g5]{border-color:#63b3ed;background:#244a70e8}.map-token.restricted[b-avn663v8g5]{border-style:dashed}.map-token.active[b-avn663v8g5]{border-color:#f4cb68;box-shadow:0 0 0 2px #f4cb6845,0 5px 14px #02050a99}.map-token.footprint-2[b-avn663v8g5]{font-size:.9rem}.map-empty[b-avn663v8g5],.map-legend[b-avn663v8g5]{color:#8e9ab8;font-size:.72rem}.map-legend[b-avn663v8g5]{margin:.6rem 0 0}.legend-active[b-avn663v8g5],.legend-blocked[b-avn663v8g5],.legend-difficult[b-avn663v8g5],.legend-cover[b-avn663v8g5]{display:inline-block;width:.65rem;height:.65rem;border-radius:2px;margin:0 .25rem 0 .6rem}.legend-active[b-avn663v8g5]{border:2px solid #f4cb68}.legend-blocked[b-avn663v8g5]{background:#343b49}.legend-difficult[b-avn663v8g5]{background:repeating-linear-gradient(45deg,#255034,#255034 3px,#142419 3px,#142419 6px)}.legend-cover[b-avn663v8g5]{box-shadow:inset 0 0 0 2px #4a72a8}.map-cell.unseen[b-avn663v8g5]{filter:grayscale(.85) brightness(.45)}.map-cell.unseen:hover[b-avn663v8g5],.map-cell.unseen:focus-visible[b-avn663v8g5]{filter:none}.battle-map.has-terrain .map-cell.unseen[b-avn663v8g5]{background:#05070c40;backdrop-filter:grayscale(1) brightness(.4);filter:none}.battle-map.has-terrain .map-cell.unseen:hover[b-avn663v8g5],.battle-map.has-terrain .map-cell.unseen:focus-visible[b-avn663v8g5]{background:#7aa2d833}.legend-unseen[b-avn663v8g5]{display:inline-block;width:.65rem;height:.65rem;border-radius:2px;margin:0 .25rem 0 .6rem;background:#05070c;border:1px solid #2a3242}.terrain-canvas[b-avn663v8g5]{position:absolute;inset:0;width:100%;height:100%;z-index:0;image-rendering:pixelated;pointer-events:none}.battle-map.has-terrain .map-cell[b-avn663v8g5]{position:relative;z-index:1;background:transparent;border-color:#ffffff12}.battle-map.has-terrain .map-cell:hover[b-avn663v8g5],.battle-map.has-terrain .map-cell:focus-visible[b-avn663v8g5]{background:#7aa2d833;outline:2px solid #cfe0ff}.battle-map.has-terrain .map-cell.blocked[b-avn663v8g5]{background:#0a0e16bb}.battle-map.has-terrain .map-cell.difficult[b-avn663v8g5]{background:repeating-linear-gradient(45deg,#1d3a2988,#1d3a2988 4px,#14241988 4px,#14241988 8px)}.battle-map.has-terrain .map-token[b-avn663v8g5]{position:relative;z-index:2}

/* The route preview drawn while a token is dragged (#238). Deliberately the same yellow as the
   post-drop move thread: they are the same journey, before and after the engine has agreed to it. */
.route-preview[b-avn663v8g5]{position:absolute;inset:0;width:100%;height:100%;z-index:3;pointer-events:none;overflow:visible}
.route-preview polyline[b-avn663v8g5]{fill:none;stroke:#f4cb68;stroke-width:2.5;stroke-dasharray:6 4;opacity:.85;
    stroke-linejoin:round;stroke-linecap:round}

/* ⚠️ A CELL FILLS ITS TRACK. Do not delete this because it "looks redundant".
   ================================================================================================
   A map cell is a <button>, and a global `button { margin-left:.5rem }` in app.css put an 8px gap
   between every column of every battle map since the POC — a 32px cell in a 40px track, vertical
   gaps only, because the rule set no vertical margin. That rule is gone, but the component must not
   depend on its absence: the next global button rule is one stylesheet away, and the failure is
   silent, cosmetic and reads as a picture problem rather than a layout one. It cost three wrong
   diagnoses before anyone measured a cell against its track.

   `getBoundingClientRect().width` against the computed track width is the ten-second probe. */
.map-cell[b-avn663v8g5]{margin:0;width:auto}

/* ⚠️ NEVER EXPLORED IS OPAQUE, NOT DIM (issue #230).
   ================================================================================================
   Three states, and the middle one is the only one that is a haze:

     never explored           -> nothing. You have not been here.
     explored, not visible    -> the picture, dimmed. You remember the shape, not what is in it.
     visible now              -> the picture.

   The first partly reverses `7bf15eb` ("fog of war is a haze, not a blackout"), which was right about
   the middle row and wrong about this one. At 40% brightness a player can read an entire hedge maze
   through the fog and plan a route through it without setting foot inside — which is exactly what Dave
   objected to. A haze is memory; a blackout is ignorance, and they are different claims.

   `background` rather than an overlay so it works identically over artwork, over the tile canvas and
   over a bare grid — the one thing the last three fog bugs all had in common was a rule that only held
   in the case it was written for. Hover does NOT lift it: hovering an unexplored square must not be a
   way to peek, and `.unseen:hover` is only defensible because you have already been there. */
.map-cell.unknown[b-avn663v8g5]{background:#05070c;border-color:#0d1119;color:transparent}
.battle-map.has-terrain .map-cell.unknown[b-avn663v8g5]{background:#05070c;border-color:#0d1119}
/* Focusable, because it is still a square and keyboard users still tab across the board — but it says
   nothing, and lighting it up would draw the outline of the dark. */
.map-cell.unknown:hover[b-avn663v8g5],.map-cell.unknown:focus-visible[b-avn663v8g5],
.battle-map.has-terrain .map-cell.unknown:hover[b-avn663v8g5],
.battle-map.has-terrain .map-cell.unknown:focus-visible[b-avn663v8g5]{background:#0a0f18;outline:2px solid #2a3346;outline-offset:-2px}
.legend-unknown[b-avn663v8g5]{display:inline-block;width:.65rem;height:.65rem;border-radius:2px;margin:0 .25rem 0 .6rem;
    background:#05070c;border:1px solid #0d1119}

/* ⚠️ FOG MUST DIM THE GRID TOO (issue #227) — the vertical lines Dave kept reporting.
   ================================================================================================
   The two fog rules are not the same rule, and the difference was never deliberate:

     bare grid:      .map-cell.unseen { filter: grayscale(.85) brightness(.45) }
     over artwork:   .map-cell.unseen { backdrop-filter: grayscale(1) brightness(.4); filter: none }

   `filter` dims the ELEMENT, borders included. `backdrop-filter` dims only what is BEHIND it. So over a
   published battlemap the picture drops to 40% brightness and our own `#ffffff12` cell border stays at
   100% — and a 7%-white line is invisible over lit cobblestone and the brightest thing on screen over a
   fogged hedge. On the Wickbourne grounds fog covers 78% of the map, so almost the whole board was grid
   lines over near-black. They read as VERTICAL because a hedge maze is full of horizontal runs that
   camouflage the horizontal lines and nothing that camouflages the vertical ones.

   Dimmed by the same factor the fog dims everything else: 0x12 × 0.4 ≈ 0x07. Hover restores it, because
   hover already lifts the fog off that square.

   This is the third bug of exactly this shape — see the two-rules-for-one-thing note. When one case has
   its own rule, say why, or the cases quietly stop agreeing. */
.battle-map.has-terrain .map-cell.unseen[b-avn663v8g5]{border-color:#ffffff07}
.battle-map.has-terrain .map-cell.unseen:hover[b-avn663v8g5],
.battle-map.has-terrain .map-cell.unseen:focus-visible[b-avn663v8g5]{border-color:#ffffff12}

/* Drag the raw map to pan it (issue #228). The rule the gesture implements: a pointer that lands on a
   token or a door handle belongs to that thing; anywhere else is the map, and the map moves.

   `cursor:inherit` on the cells is load-bearing. Every square is a <button>, and a button draws its own
   cursor, so without this the grab hand would appear only in the hairline gaps between cells — which is
   to say never. The token keeps its own grab because a token IS grabbable; the two gestures look alike
   on purpose, since both are "take hold of this", and what differs is only what moves. */
.battle-map.pannable[b-avn663v8g5]{cursor:grab}
.battle-map.pannable .map-cell[b-avn663v8g5]{cursor:inherit}
.battle-map.pannable.panning[b-avn663v8g5]{cursor:grabbing}
/* While panning, nothing on the board may start a text selection or a native image drag under the
   pointer — both leave a ghost outline trailing the hand for the length of the gesture. */
.battle-map.panning[b-avn663v8g5],.battle-map.panning *[b-avn663v8g5]{user-select:none}

/* #531. The table is held while the Game Master is being asked for a line, and the board says so in the
   one place a pointer is already looking. `wait` outranks the pan grab and the token grab because a
   gesture that will be refused should not advertise itself as available.

   ⚠️ The cursor is a courtesy, NOT the gate. What actually refuses a move is `Busy` on the drop handler
   and `draggable="false"` on the token — a rule that lived only in a cursor would be a rule the first
   person with a synthetic pointer walks straight through. */
.battle-map.held[b-avn663v8g5],.battle-map.held .map-cell[b-avn663v8g5],.battle-map.held .map-token[b-avn663v8g5]{cursor:wait}
/* Not `pointer-events:none`: the labels, the hover titles and plain selection all still have to work,
   because dimming the table must not stop the player reading it. Only the affordance dims. */
.battle-map.held .map-token[b-avn663v8g5]{opacity:.75}

/* Two Boggard Scouts render the same initials and were indistinguishable on the board. The numeral is
   taken from the creature's own name where it has one, so the token agrees with what the narration and
   the target buttons call it. */
.token-ordinal[b-avn663v8g5]{position:absolute;right:1px;bottom:0;font-size:.55rem;font-weight:800;line-height:1;
    padding:0 2px;border-radius:4px;background:rgba(0,0,0,.6);color:#f2e2b8}
.map-token[b-avn663v8g5]{position:relative}

/* ⭐ WHOSE SIDE, AND STILL STANDING? (Dave, 2026-08-10.) Every token used to be the same slate lozenge:
   allegiance was invisible, and a dead wolf was indistinguishable from a live one on a board you are
   supposed to read at a glance. Blue for us, warm amber for them.

   ⚠️ SPECIFICITY ARITHMETIC, and it is the whole reason for the `:not()`s. These rules sit LATER in the
   file than `.map-token.controlled` and `.map-token.active`, so at equal weight (0,2,0) they would win on
   source order and quietly erase both cues — the blue ring that says "this one is yours to move" and the
   gold that says "this one is up". Excluding those states is what lets allegiance be the DEFAULT dress
   and leaves the two states that matter more on top of it, rather than fighting them.

   The colours are backgrounds, not borders, precisely because the borders are already spoken for. */
.map-token.ally:not(.controlled)[b-avn663v8g5]{background:#1d3a5ce8}
.map-token.ally:not(.controlled):not(.active)[b-avn663v8g5]{border-color:#5b9bd5}
.map-token.foe[b-avn663v8g5]{background:#4d2a1ce8}
.map-token.foe:not(.active)[b-avn663v8g5]{border-color:#d9944a}
/* ⛔⛔ Neutral: everybody who is not a player and not hostile — innkeepers, quest givers, the timid,
   the non-attackable. Before this every one of them was drawn in the foe's orange for the sole crime of
   not being a player character, and Mabel Crust stood behind her own bar looking like a fight.
   Dave, 2026-09-10: "she should not be rendered as hostile."
   ⚠️ Deliberately its own colour rather than the ally blue: they are not on the party's side either,
   and a quest giver drawn as an ally would be the same mistake in the other direction. */
.map-token.neutral[b-avn663v8g5]{background:#2f2a1ce8}
.map-token.neutral:not(.active)[b-avn663v8g5]{border-color:#b4a06a}

/* Down, and it KEEPS its side's colour — "even when dead/down", which is the point: who the body belonged
   to is exactly what you want to know when reading a finished fight. Dimmed and desaturated so the living
   read first, but never so far that the colour stops being legible. */
.map-token.down[b-avn663v8g5]{opacity:.75;filter:saturate(.6)}
/* The X, drawn over the initials rather than replacing them, so the token still says WHO is down.
   `::after` rather than markup: the token already carries an ordinal child, and a defeated actor's label
   is built by TokenLabel — a screen reader is told about the state there, so this glyph is decoration and
   is hidden from the accessibility tree by being generated content with pointer events off. */
.map-token.down[b-avn663v8g5]::after{content:"✕";position:absolute;inset:0;display:grid;place-items:center;
    font-size:1.15rem;font-weight:900;line-height:1;color:#ffdede;pointer-events:none;
    text-shadow:0 0 3px #000,0 0 6px #000}

/* A move that has been asked for and not yet answered. The token itself cannot move until the engine
   says so — it is drawn from authoritative state and must stay that way — so the intent is drawn beside
   it instead: a hollow copy on the destination and a thread back to where the real one still stands.
   Dropping a token used to look like a rejected move for the whole round trip. */
.map-token-ghost[b-avn663v8g5]{z-index:3;pointer-events:none;display:grid;place-items:center;
    border-style:dashed;border-color:#f4cb68;background:#f4cb6818;color:#f4cb68cc;
    box-shadow:none;opacity:.9;animation:ghost-breathe-b-avn663v8g5 1.1s ease-in-out infinite}
@keyframes ghost-breathe-b-avn663v8g5{50%{opacity:.45}}
.move-thread[b-avn663v8g5]{position:absolute;inset:0;width:100%;height:100%;z-index:3;pointer-events:none;overflow:visible}
.move-thread line[b-avn663v8g5]{stroke:#f4cb68;stroke-width:2;stroke-dasharray:5 4;opacity:.85}
@media(prefers-reduced-motion:reduce){.map-token-ghost[b-avn663v8g5]{animation:none}}

/* ⚠️ WHY THE WALK ENDED HERE (issue #289). A floating caption at the square the token actually stopped
   on, carrying the engine's own reason. Until this, an interrupted walk was a token that stopped early
   for no visible reason and an explanation two panes away in Rolls & checks.

   ⚠️ IT REMOVES ITSELF IN CSS, NOT ON A TIMER. A timer on a Blazor circuit is a render, and a render per
   caption to un-draw something the browser can un-draw itself is a cost paid on the wire. The animation
   ends at opacity:0 and `forwards` holds it there. The element is `@key`ed on the walk batch in the
   markup, which is what makes a second walk a NEW element with an animation that has not run yet — a
   reused element would sit at its finished frame, permanently invisible.

   Above everything the board draws (tokens 2/3, debug 4) because a caption behind a token explains
   nothing, and `pointer-events:none` so it never eats a click on the squares underneath it. Positioned
   by the same CellStyle as the object markers, then told which way to spill by WalkNoteStyle —
   `.battle-map` is overflow:hidden, so a caption growing towards the near edge would be cut off. */
.map-walk-note[b-avn663v8g5]{position:relative;z-index:6;pointer-events:none;
    width:max-content;max-width:11rem;padding:.15rem .4rem;
    border:1px solid #f4cb68aa;border-radius:.35rem;background:#141a26f2;color:#f6e6c4;
    font-size:.62rem;line-height:1.3;font-weight:600;text-align:left;
    box-shadow:0 2px 8px #02050acc;
    animation:walk-note-fade-b-avn663v8g5 4.5s ease-out forwards}
@keyframes walk-note-fade-b-avn663v8g5{
    0%{opacity:0;transform:translateY(.35rem)}
    7%{opacity:1;transform:translateY(0)}
    72%{opacity:1;transform:translateY(0)}
    100%{opacity:0;transform:translateY(-.3rem)}}
/* Still goes away on its own — it simply stops sliding while it does it. */
@media(prefers-reduced-motion:reduce){
    .map-walk-note[b-avn663v8g5]{animation:walk-note-hold-b-avn663v8g5 4.5s steps(1,end) forwards}}
@keyframes walk-note-hold-b-avn663v8g5{0%{opacity:1}99%{opacity:1}100%{opacity:0}}

/* Walls and doors drawn on the EDGES between squares rather than in them (issue #90). The element is a
   grid item sitting in the square the edge is named from; the bar inside it straddles the boundary, so a
   wall is drawn where the wall is and consumes none of the floor on either side of it. */
.map-boundary[b-avn663v8g5]{position:relative;z-index:2;pointer-events:none;min-width:0;min-height:0}
.map-boundary .boundary-mark[b-avn663v8g5],.map-boundary .boundary-handle[b-avn663v8g5]{position:absolute;display:block;border:0;padding:0}
.map-boundary.east .boundary-mark[b-avn663v8g5],.map-boundary.east .boundary-handle[b-avn663v8g5]{top:0;bottom:0;right:-3px;width:6px}
.map-boundary.south .boundary-mark[b-avn663v8g5],.map-boundary.south .boundary-handle[b-avn663v8g5]{left:0;right:0;bottom:-3px;height:6px}
.map-boundary.wall .boundary-mark[b-avn663v8g5]{background:#8a93a8;box-shadow:0 0 0 1px #05070c99}
.map-boundary.window .boundary-mark[b-avn663v8g5]{background:repeating-linear-gradient(90deg,#6fa8d8,#6fa8d8 3px,transparent 3px,transparent 6px)}
/* A shut door is solid and warm so it reads as a thing rather than as more wall; an open one is the
   ghost of itself, because the doorway still matters even when nothing is standing in it. */
.map-boundary.door .boundary-mark[b-avn663v8g5],.map-boundary.door .boundary-handle[b-avn663v8g5]{background:#c08a3e;border-radius:2px}
.map-boundary.door.open .boundary-mark[b-avn663v8g5],.map-boundary.door.open .boundary-handle[b-avn663v8g5]{background:#c08a3e33;
    box-shadow:inset 0 0 0 1px #c08a3e88}
/* Only the HANDLE takes the click, never the element holding it. The holder is a grid item filling the
   whole square, so making that clickable would put an invisible lid over the square a door stands in and
   the one square you most need to click is the one you are walking through. */
.map-boundary.operable .boundary-handle[b-avn663v8g5]{pointer-events:auto;cursor:pointer}
.map-boundary.east.operable .boundary-handle[b-avn663v8g5]{right:-5px;width:10px}
.map-boundary.south.operable .boundary-handle[b-avn663v8g5]{bottom:-5px;height:10px}
.map-boundary.operable .boundary-handle:hover[b-avn663v8g5],.map-boundary.operable .boundary-handle:focus-visible[b-avn663v8g5]{
    outline:2px solid #f4cb68;outline-offset:1px;background:#f4cb68}

/* ⚠️ A WALL IS NOT BEHIND THE FOG (issue #227) — the half left open when the cells were fixed.
   ================================================================================================
   `.battle-map.has-terrain .map-cell.unseen` dims with `backdrop-filter`, and a backdrop is what is
   painted BEHIND the element. Boundaries are painted after the cells — deliberately, so they sit over the
   cell borders — so every wall bar and door mark on the board stayed at 100% while the picture under it
   dropped to 40%. A `#8a93a8` bar over a fogged hedge is the brightest thing on screen, which is the same
   shape of bug as the #ffffff12 grid lines two rules up, on a surface nobody thought to check.

   `filter` and not `backdrop-filter` here, and that is the whole distinction this file has paid for twice:
   the mark paints its OWN opaque bar, so dimming the element dims the thing you can see. The cell paints
   nothing and needs its backdrop dimmed instead. Same numbers as the fog — grayscale(1) brightness(.4) —
   so a remembered wall and the remembered picture behind it fade together rather than by two recipes.

   ⛔ There is no rule for the UNEXPLORED case on purpose. That edge is never rendered (see BattleMap.razor):
   #230 made a never-seen square opaque so the dark would have no outline to read in it, and a wall drawn
   dark is still an outline — one a colour picker turns back up. 596 walls and 31 doors are authored on
   Talhundereth, which is the entire floor plan. Not sending the line is the only version that holds. */
.map-boundary.remembered .boundary-mark[b-avn663v8g5],
.map-boundary.remembered .boundary-handle[b-avn663v8g5]{filter:grayscale(1) brightness(.4)}

/* The published picture of an authored map (#177). Positioned exactly like the tile canvas, because the
   cells above it already know how to be transparent over one. Stretched rather than contained: the server
   crops on cell boundaries, so the image and the grid cover the same squares by construction, and `fill`
   only ever absorbs sub-pixel rounding. `contain` would letterbox and slide the picture off the grid. */
/* Since #232 this is a crop of the EXPLORED part of the board, so it is a grid item placed on the
   squares it is a picture of rather than an overlay stretched across everything. `inset:0` would put
   a picture of three rooms over the whole map. */
.map-render[b-avn663v8g5]{position:relative;width:100%;height:100%;object-fit:fill;z-index:0;pointer-events:none;user-select:none;min-width:0;min-height:0}

/* Locks, traps, chests and named scenery (#146/#199). Under the tokens and out of the way of clicks:
   the marker exists to make the square worth Ctrl-clicking, and the cell button underneath is what
   answers the click. Without these the aiming built in #146 was undiscoverable — a trap on two squares
   of a forty-by-twenty-four grid could only be found by guessing where to point. */
.map-object[b-avn663v8g5]{z-index:1;pointer-events:none;display:grid;place-items:center;
    font-size:.8rem;line-height:1;color:#e2c98b;text-shadow:0 1px 3px #000a,0 0 6px #000c}

/* ⭐ #588: A CONTAINER IS THE ONE MARKER YOU CAN CLICK, and until now it was not.
   ================================================================================================
   The rule above is right for a trap and a bloodstain — the marker exists to make the square worth
   Ctrl-clicking and the cell button underneath answers. A chest is the exception: it has an INSIDE, the
   engine has been able to open it since #254, and there was no way to ask. Dave found `hedge-cache`
   behind the hedge, saw this glyph, and was told nothing about it.

   ⚠️ `z-index:2` — above its siblings and level with the doorway, because a chest sharing a square with
   scenery has to be the thing you hit. Still below the tokens, which come later in the DOM: a character
   standing on the chest is the more important click.

   The button resets the agent stylesheet's own chrome. Everything visual — the colour per kind, the
   `unfound` pink, the fog dimming — is inherited from `.map-object` above, so the two markers can never
   drift apart into "the clickable ones look different". */
.map-object.addressable[b-avn663v8g5]{z-index:2;pointer-events:auto;cursor:pointer;
    border:0;padding:0;background:none;font:inherit;color:inherit;-webkit-appearance:none;appearance:none}
.map-object.addressable:hover[b-avn663v8g5],.map-object.addressable:focus-visible[b-avn663v8g5]{
    outline:2px solid #f4cb68;outline-offset:-1px;border-radius:3px;
    background:#f4cb6822;text-shadow:0 0 6px #f4cb68,0 1px 3px #000a}
/* An emptied chest stays on the board and says so — Dave's ruling, and it reuses `dealt-with`, which a
   disarmed trap has worn since #201. Still clickable: the party can put things BACK into it. */
.map-object.addressable.dealt-with[b-avn663v8g5]{cursor:pointer}

/* ⭐ THE WAY ON (Dave, 2026-08-10): "add an icon/token on the map for the house and/or door… so that the
   players know where to go." The Drowned Vault's terrace narrates a house and a front door and the board
   drew neither, so a party that solved the sundial had nowhere to walk.

   ⚠️ `pointer-events:auto`, unlike the object glyphs beside it — this one has a title worth reaching, and
   it is the only thing on the board that answers "where now?". Above the objects (z-index 2) so a doorway
   sharing a square with scenery is still the thing you see; below the tokens (2 as well, but tokens come
   later in the DOM) so a character standing in the doorway is not hidden by it. */
.map-doorway[b-avn663v8g5]{z-index:2;pointer-events:auto;display:grid;place-items:center;cursor:help;
    font-size:.95rem;line-height:1;text-shadow:0 1px 3px #000c,0 0 8px #000e}
/* Open reads as an invitation and is deliberately the brighter of the two. */
.map-doorway.open[b-avn663v8g5]{color:#8fd6ff;
    animation:doorway-breathe-b-avn663v8g5 2.4s ease-in-out infinite}
/* Shut is stone, not an error: a barred door is a fact about the world, not a refusal by the app. */
.map-doorway.shut[b-avn663v8g5]{color:#9aa3ad;opacity:.85}
@keyframes doorway-breathe-b-avn663v8g5{50%{opacity:.55}}
@media(prefers-reduced-motion:reduce){.map-doorway.open[b-avn663v8g5]{animation:none}}

/* ⭐ THE ONE OVERLAY THE FOG TREATS DIFFERENTLY, AND IT IS ON PURPOSE (issue #402).
   ================================================================================================
   #227's sweep dimmed the walls, the door marks, the object glyphs and the grid lines, and left this
   one alone; #402 asked which it should be. The answer is neither of the two easy ones:

     remembered square (.unseen)  -> dimmed, by the fog's own grayscale(1) brightness(.4)
     never-explored square        -> FULL STRENGTH. The only overlay on the board that is.

   Dave's sentence is the whole reason: "add an icon/token on the map for the house and/or door… so
   that the players know where to go." A marker you can only see once you have been there is the
   version he played and could not find. And there is nothing to leak: a doorway is a signpost the
   author placed to be seen from across the map, not a secret — unlike a chest, which #227 stopped
   drawing on the blackout precisely because it says "something is here" about a room nobody entered.

   `filter`, not `backdrop-filter`, for the reason this file has now paid for three times: the glyph
   paints its own ink, so dimming the element dims the thing you can see. The breathing animation is
   left running, which is what keeps a dimmed marker findable on a board that has all faded together. */
.map-doorway.remembered[b-avn663v8g5]{filter:grayscale(1) brightness(.4)}
.battle-map.has-terrain .map-doorway[b-avn663v8g5]{position:relative}
.battle-map.has-terrain .map-object[b-avn663v8g5]{position:relative}
.map-object-trap[b-avn663v8g5]{color:#f0a58a}
.map-object-lock[b-avn663v8g5]{color:#e2c98b}
.map-object-container[b-avn663v8g5]{color:#c9b48a}
.map-object-feature[b-avn663v8g5]{color:#9fc0e8}
/* Picked, disarmed, jammed open — still there, no longer a problem. */
.map-object.dealt-with[b-avn663v8g5]{opacity:.45;filter:grayscale(.6)}
/* A square the party remembers rather than sees. Matches how the cell itself is drawn. */
.map-object.remembered[b-avn663v8g5]{opacity:.5;filter:grayscale(.7)}

/* NOT YET FOUND, and on screen only because this viewer's state legitimately contains it — the Game
   Master, or a tester on ?viewer=dm. A player's map has these removed server-side before it is sent
   (MapState.Concealing), so this style can never appear in ordinary play.

   Loud on purpose. Dave's reason for wanting it: "otherwise he may never know that a trap failed to
   trigger, and thus cannot report a problem." A reveal that looked like ordinary play would get
   reported as the bug instead of the bug. */
.map-object.unfound[b-avn663v8g5]{color:#ff8fd0;opacity:.95;
    outline:1px dashed #ff8fd0aa;outline-offset:-2px;border-radius:4px;
    background:repeating-linear-gradient(45deg,#ff8fd014,#ff8fd014 3px,transparent 3px,transparent 6px)}
.map-object.unfound.dealt-with[b-avn663v8g5]{opacity:.4}

.legend-object[b-avn663v8g5]{display:inline-block;width:.65rem;height:.65rem;border-radius:2px;
    margin:0 .25rem 0 .6rem;background:#e2c98b}
.legend-unfound[b-avn663v8g5]{display:inline-block;width:.65rem;height:.65rem;border-radius:2px;
    margin:0 .25rem 0 .6rem;border:1px dashed #ff8fd0}

/* ⚠️ GOD MODE (issue #250) — the admin debug layer, drawn over the player's own view of the board.
   z-index 4 puts it above the tokens (3), the boundaries (2) and the objects (1), and — the part that
   matters — above the fog. `.map-cell.unseen` dims with `backdrop-filter`, which only touches what is
   painted BEHIND it, so anything stacked above stays at full strength. An instrument dimmed by the very
   fog it exists to see through would be no instrument at all.

   Deliberately hideous. This is a measuring tool, not a feature: it must never be mistakeable for the
   game, or a screenshot of a debugging session gets reported as a rendering bug. */
.map-debug[b-avn663v8g5]{z-index:4;pointer-events:none;display:grid;place-items:center;
    font-size:.7rem;line-height:1;font-weight:700;border-radius:3px;
    text-shadow:0 1px 2px #000c}
.map-debug-creature[b-avn663v8g5]{color:#0b0f16;background:#5ce1a6e0;outline:2px solid #5ce1a6;outline-offset:-2px}
/* It has noticed the party. The single most useful thing on the whole overlay: it is the difference
   between "the scent rule did nothing" and "the scent rule fired and the consequence is missing". */
.map-debug-creature.alerted[b-avn663v8g5]{background:#ffd166f0;outline-color:#ff9f1c;
    animation:debug-alert-b-avn663v8g5 1s ease-in-out infinite}
@keyframes debug-alert-b-avn663v8g5{50%{outline-color:#ff5d5d}}
@media(prefers-reduced-motion:reduce){.map-debug-creature.alerted[b-avn663v8g5]{animation:none}}
.map-debug-object[b-avn663v8g5]{color:#0b0f16;background:#ff8fd0e0;outline:2px dashed #ff8fd0;outline-offset:-2px}
.map-debug-wanderer[b-avn663v8g5]{color:#0b0f16;background:#8fd0ffe0;outline:2px dotted #8fd0ff;outline-offset:-2px}
.legend-debug[b-avn663v8g5]{display:inline-block;width:.65rem;height:.65rem;border-radius:2px;
    margin:0 .25rem 0 .6rem;background:#5ce1a6}

/* ⚠️ FOG OF WAR OVER ARTWORK IS A HAZE, NOT A BLACKOUT. The rule above used to be a flat #05070ccc —
   near-black at 80% — which is right for a bare grid, where an unseen cell has nothing behind it worth
   preserving, and catastrophic over a traced battlemap. Dave, playing the hedge maze: "Many squares still
   have black overlays, ruining the look of the map."

   Note which way round it had gone: the plain-grid path was already the GENTLE one (grayscale + dim), and
   the has-terrain override replaced that filter with a solid fill. The harsher treatment had been chosen
   for the one case where there is art to protect. Now both desaturate, and the picture stays readable
   underneath — you can still see you have been somewhere without being shown a black square.

   ⚠️⚠️ AND THEN I BROKE IT, WHICH IS THE PART TO REMEMBER. The first attempt was
   `background:#05070c33; filter:grayscale(.85) brightness(.55)` — copied from the plain-grid rule above
   without noticing WHY that rule works there. On a bare grid the cell paints its own opaque background,
   so a filter on the cell dims the only thing you can see. Over artwork the picture is a SEPARATE <img>
   sitting BEHIND transparent cells, and `filter` only ever touches the element's own rendering — so it
   dimmed nothing at all and fog of war silently stopped existing. Dave, immediately: "the fog of war is
   not working at all now - I can see where I should not be able to see."

   A filter cannot dim a sibling behind you. `backdrop-filter` can — that is exactly what it is for — and
   the original 80% black "worked" only because an opaque fill is the other way to obscure a backdrop.

   ⚠️ Presentation only. Whether an outdoor map at ambient 15 with four lamps burning should have this
   much unseen area AT ALL is a separate question, upstream in KnowledgeScope, and is open in #227. */

/* ⭐ WHO IS TALKING (#869). A companion speaking aloud lights up, so the voice in the room has a face on
   the board — Dave: "highlight the character's token - maybe flash it yellow - while that character's
   voice is actively speaking."

   ⚠️ SPECIFICITY, deliberately, and the arithmetic above is why this is stated rather than assumed. The
   allegiance rules land at (0,2,0) and sit later in the file than `.active` and `.controlled`; this needs
   to beat ALL of them for as long as the line lasts, so it is (0,3,0) via a doubled class rather than
   `!important`. Speaking is the most transient state a token has and the most useful to see, so it wins
   while it is true and gives the board straight back afterwards.

   ⛔ Driven by a class, never by a script poking the DOM: the board rebuilds this attribute on every
   render, so a class added behind Blazor's back would vanish at the next unrelated re-render and the
   token would stop flashing for no visible reason. */
.map-token.speaking.speaking[b-avn663v8g5]{
    border-color:#ffd34d;
    background:#4a3a10e8;
    /* ⭐ Dave, 2026-09-03: "flash yellow or have a flashing outline of yellow". A proper outline that
       goes from off to full, not a glow that breathes — a token across a busy board has to be findable
       in one glance, and keyed to the audio's own `playing` and `ended` events so it is lit exactly
       while the voice is heard. */
    outline:3px solid #ffd34d;
    outline-offset:2px;
    box-shadow:0 0 0 3px #ffd34d55,0 0 14px #ffd34d66,0 5px 14px #02050a99;
    animation:token-speaking-b-avn663v8g5 .7s steps(2,jump-none) infinite
}
@keyframes token-speaking-b-avn663v8g5{
    0%,100%{outline-color:#ffd34d;box-shadow:0 0 0 4px #ffd34daa,0 0 22px #ffd34dcc,0 5px 14px #02050a99}
    50%{outline-color:#ffd34d22;box-shadow:0 0 0 3px #ffd34d22,0 0 8px #ffd34d33,0 5px 14px #02050a99}
}
/* ⛔ Respected, not optional: a pulsing element is a documented migraine and vestibular trigger, and the
   information here is "this one is talking", which the colour alone carries perfectly well. */
@media(prefers-reduced-motion:reduce){.map-token.speaking.speaking[b-avn663v8g5]{animation:none}}
/* /Components/Shared/BugReportDialog.razor.rz.scp.css */
/* The bug report window (Dave, 2026-09-15). ⚠️ Colours borrowed from FenReach's chrome; nobody has chosen this look.
   → memory `a-look-is-not-a-number` */
.bug-veil[b-iczrpccnxh] {
    position: fixed;
    inset: 0;
    z-index: var(--dm-layer-dialog);
    display: grid;
    place-items: center;
    padding: 1rem;
    background: #05080fcc;
}

.bug-report[b-iczrpccnxh] {
    width: min(56rem, 100%);
    max-height: calc(100vh - 2rem);
    overflow: auto;
    box-sizing: border-box;
    padding: 1rem 1.25rem;
    border: 1px solid #dcb76359;
    border-radius: .5rem;
    background: #0d1220;
    color: #e6e1d3;
    box-shadow: 0 1rem 3rem #000a;
}

.bug-head[b-iczrpccnxh] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}

.bug-head h2[b-iczrpccnxh] { margin: 0; font-size: 1.15rem; color: #f0e2bd; }

.bug-close[b-iczrpccnxh] {
    border: 0;
    background: none;
    color: #8e97a8;
    font-size: 1.1rem;
    cursor: pointer;
}

.bug-body[b-iczrpccnxh] {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    gap: 1rem;
    margin: .75rem 0;
}

@media (max-width: 40rem) {
    .bug-body[b-iczrpccnxh] { grid-template-columns: 1fr; }
}

.bug-shot[b-iczrpccnxh] { margin: 0; }

.bug-shot img[b-iczrpccnxh] {
    display: block;
    width: 100%;
    border: 1px solid #dcb76333;
    border-radius: .25rem;
}

.bug-shot figcaption[b-iczrpccnxh] { margin-top: .25rem; font-size: .75rem; color: #8e97a8; }

.bug-stats[b-iczrpccnxh] {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    gap: .25rem .75rem;
    margin: 0;
    font-size: .82rem;
    align-content: start;
}

.bug-stats dt[b-iczrpccnxh] { color: #8e97a8; }
.bug-stats dd[b-iczrpccnxh] { margin: 0; overflow-wrap: anywhere; }

.bug-words[b-iczrpccnxh] { display: grid; gap: .35rem; font-size: .9rem; }

.bug-words textarea[b-iczrpccnxh] {
    width: 100%;
    box-sizing: border-box;
    resize: vertical;
    padding: .5rem;
    border: 1px solid #dcb76359;
    border-radius: .25rem;
    background: #070b14;
    color: #e6e1d3;
    font: inherit;
}

.bug-quiet[b-iczrpccnxh] { margin: .5rem 0 0; font-size: .78rem; color: #8e97a8; }
.bug-error[b-iczrpccnxh] { margin: .5rem 0 0; font-size: .85rem; color: #e0a3a3; }

.bug-actions[b-iczrpccnxh] {
    display: flex;
    justify-content: flex-end;
    gap: .5rem;
    margin-top: .75rem;
}

.bug-actions button[b-iczrpccnxh] {
    padding: .4rem .9rem;
    border: 1px solid #dcb76359;
    border-radius: .25rem;
    background: #151b2c;
    color: #e6e1d3;
    cursor: pointer;
}

.bug-actions button.bug-send[b-iczrpccnxh] { background: #dcb763; color: #1a1406; border-color: #dcb763; }
.bug-actions button:disabled[b-iczrpccnxh] { opacity: .5; cursor: default; }
/* /Components/Shared/CharacterBenchPanel.razor.rz.scp.css */
/* The workbench's own stylesheet. Scoped CSS does not cross a component boundary — a lesson this project
   has paid for more than once — so nothing here leans on the page that opens it, and nothing the shop
   window styles is borrowed by reference. The two windows deliberately LOOK alike: the same counter, in
   the same game, and a player who has used one should recognise the other. */

/* ⚠ ABOVE THE CHARACTER SHEET, which sits at z-index 1000.
   This opened at 40 and was therefore drawn UNDERNEATH the sheet that opens it — the button appeared to
   do nothing, and the window was only visible after closing the sheet. Dave found it in the first
   minute. The shop window's 40/41 was the wrong thing to copy: it opens over /play's ordinary page, and
   this opens over a modal. Anything layered here must clear 1000. */
.bench-scrim[b-zn22z7fvhb] {
    position: fixed;
    inset: 0;
    background: #05070bcc;
    z-index: var(--dm-layer-dialog-over-dialog, 1100);
}

.bench-window[b-zn22z7fvhb] {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: var(--dm-layer-dialog-over-dialog, 1100);
    width: min(64rem, calc(100vw - 2.5rem));
    /* Taller than the shop window because the advancement wizard is a taller thing than a shelf. */
    max-height: min(54rem, calc(100vh - 2.5rem));
    display: flex;
    flex-direction: column;
    gap: .7rem;
    padding: 1.1rem 1.2rem 1.2rem;
    border: 1px solid #6b5a33;
    border-radius: 18px;
    background: #14110a;
    box-shadow: 0 1.6rem 3rem #000000a6;
    color: #e4d9bf;
    overflow: hidden;
}

.bench-window header[b-zn22z7fvhb] {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
}

.bench-window header > div[b-zn22z7fvhb] { flex: 1; }
.bench-window h2[b-zn22z7fvhb] { margin: .1rem 0 0; font-size: 1.15rem; color: #f0e2be; }

.eyebrow[b-zn22z7fvhb] {
    margin: 0;
    font-size: .62rem;
    letter-spacing: .14em;
    color: #9a844c;
}

.bench-blurb[b-zn22z7fvhb] {
    margin: .45rem 0 0;
    font-size: .78rem;
    line-height: 1.45;
    color: #bdae8d;
}

.bench-blurb strong[b-zn22z7fvhb] { color: #d8bf72; font-variant-numeric: tabular-nums; }

/* Layout only — the look comes from the shared .dm-close in app.css. */
.bench-close[b-zn22z7fvhb] { flex: none; }

.bench-tabs[b-zn22z7fvhb] {
    display: flex;
    gap: .4rem;
    border-bottom: 1px solid #332d1c;
    padding-bottom: .5rem;
}

.bench-tabs button[b-zn22z7fvhb] {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .3rem .8rem;
    border: 1px solid #3a3423;
    border-radius: 999px;
    background: #1b1710;
    color: #bdae8d;
    font-size: .76rem;
    cursor: pointer;
}

.bench-tabs button:hover[b-zn22z7fvhb] { border-color: #9a844c; color: #e8dcc0; }
.bench-tabs button.on[b-zn22z7fvhb] { background: #2a2413; border-color: #c2a961; color: #f0e2be; }

/* A level waiting to be taken is the one thing on this screen somebody could miss entirely. */
.bench-badge[b-zn22z7fvhb] {
    min-width: 1.15rem;
    padding: 0 .3rem;
    border-radius: 999px;
    background: #6d5a1f;
    color: #fdf3d4;
    font-size: .64rem;
    line-height: 1.15rem;
    text-align: center;
}

.bench-message[b-zn22z7fvhb] {
    margin: 0;
    padding: .45rem .6rem;
    border-radius: 10px;
    background: #1d2a17;
    border: 1px solid #43613a;
    font-size: .76rem;
    color: #cfe3bf;
}

.bench-message.error[b-zn22z7fvhb] {
    background: #2a1716;
    border-color: #6d3a35;
    color: #eec4bf;
}

/* The advancement wizard is as tall as it is — steps, a feat list, a review — and the window is not.
   Nothing between the two scrolled, so the Back/Next buttons were simply clipped off the bottom and the
   level-up could not be completed at all. The header, the tabs and the testing footer stay put; this is
   the part that moves. */
.bench-scroll[b-zn22z7fvhb] {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    /* Room for the scrollbar so it does not sit on top of the wizard's own buttons. */
    padding-right: .3rem;
}

.bench-columns[b-zn22z7fvhb] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
    gap: 1rem;
    /* The shelf and the belongings scroll independently inside their own columns, so this fills the
       window rather than scrolling as one long page — a search result that pushed "carrying" off the
       bottom would make selling something to afford it a scroll away. */
    flex: 1;
    overflow: hidden;
    min-height: 0;
}

.bench-column[b-zn22z7fvhb] {
    display: flex;
    flex-direction: column;
    gap: .45rem;
    min-height: 0;
}

.bench-empty[b-zn22z7fvhb] { margin: .3rem 0 0; font-size: .76rem; color: #8e8261; font-style: italic; }

.store-search[b-zn22z7fvhb] {
    display: flex;
    gap: .4rem;
}

.store-search input[b-zn22z7fvhb],
.store-search select[b-zn22z7fvhb] {
    padding: .32rem .55rem;
    border: 1px solid #4a4230;
    border-radius: 10px;
    background: #1b1710;
    color: #e8dcc0;
    font-size: .78rem;
    font-family: inherit;
}

.store-search input[b-zn22z7fvhb] { flex: 1; min-width: 0; }
.store-search input:focus[b-zn22z7fvhb],
.store-search select:focus[b-zn22z7fvhb] { outline: none; border-color: #c2a961; }

/* Always shown, never optional. A capped list with no total reads as the whole shop, and the library is
   thousands of items deep — see the standing note on lists that scale with the library. */
.store-count[b-zn22z7fvhb] {
    margin: 0;
    font-size: .7rem;
    color: #8e8261;
}

.store-count strong[b-zn22z7fvhb] { color: #c2a961; font-variant-numeric: tabular-nums; }

.store-list[b-zn22z7fvhb] {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .5rem;
    overflow-y: auto;
    min-height: 0;
}

.store-list li[b-zn22z7fvhb] {
    padding: .5rem .6rem;
    border: 1px solid #3a3423;
    border-radius: 12px;
    background: #1b1710;
}

.store-line[b-zn22z7fvhb] {
    display: flex;
    align-items: baseline;
    gap: .5rem;
    font-size: .78rem;
}

.store-name[b-zn22z7fvhb] {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: #e8dcc0;
}

.store-tags[b-zn22z7fvhb] { display: inline-flex; gap: .25rem; flex: none; }

.tag[b-zn22z7fvhb] {
    padding: 0 .35rem;
    border: 1px solid #3f3826;
    border-radius: 999px;
    background: #221d11;
    font-size: .6rem;
    color: #9a8b61;
    white-space: nowrap;
}

.tag.rare[b-zn22z7fvhb] { border-color: #6d5535; color: #d0a86a; }
.tag.worn[b-zn22z7fvhb] { border-color: #4a6136; color: #a7c78c; }

.store-unit[b-zn22z7fvhb] { font-size: .7rem; color: #c2a961; font-variant-numeric: tabular-nums; white-space: nowrap; }

.store-controls[b-zn22z7fvhb] {
    display: flex;
    align-items: center;
    gap: .35rem;
    margin-top: .45rem;
}

.step[b-zn22z7fvhb] {
    width: 1.6rem;
    height: 1.6rem;
    border: 1px solid #4a4230;
    border-radius: 8px;
    background: #241f13;
    color: #ddcb9d;
    font-size: .85rem;
    line-height: 1;
    cursor: pointer;
}

.step:hover:not(:disabled)[b-zn22z7fvhb] { background: #332c1a; border-color: #9a844c; }
.step:disabled[b-zn22z7fvhb] { opacity: .35; cursor: default; }

.count[b-zn22z7fvhb] {
    min-width: 1.9rem;
    text-align: center;
    font-size: .8rem;
    font-variant-numeric: tabular-nums;
    color: #f0e2be;
}

.trade[b-zn22z7fvhb] {
    flex: 1;
    padding: .3rem .6rem;
    border: 1px solid #6b5a33;
    border-radius: 999px;
    background: #2a2413;
    color: #e8d9a8;
    font-size: .74rem;
    cursor: pointer;
    white-space: nowrap;
}

.trade:hover:not(:disabled)[b-zn22z7fvhb] { background: #3a3119; border-color: #c2a961; }
.trade:disabled[b-zn22z7fvhb] { opacity: .4; cursor: default; }

/* The key to the "In use" refusal beside it: a counter that will not buy what you are wearing needs a
   way to take it off. Deliberately quieter than .trade — putting a sword on is not a transaction, and
   the eye should land on the sale first. */
.wear[b-zn22z7fvhb] {
    padding: .3rem .55rem;
    border: 1px solid #4a5560;
    border-radius: 999px;
    background: #1e242a;
    color: #b8c8d6;
    font-size: .72rem;
    cursor: pointer;
    white-space: nowrap;
}

.wear:hover:not(:disabled)[b-zn22z7fvhb] { background: #27303a; border-color: #7d95aa; }
.wear:disabled[b-zn22z7fvhb] { opacity: .4; cursor: default; }

/* Beyond what the purse holds. The button still works and the service refuses with a reason, but saying
   so before the click is kinder than saying so after. */
.trade.unaffordable[b-zn22z7fvhb] {
    border-color: #6d5535;
    border-style: dashed;
    color: #c2a06a;
}

/* ⚠ Deliberately ugly. This block is not part of the game and must never be mistaken for it — if it is
   still here the day somebody screenshots this screen, it should be obvious in the screenshot. */
.bench-testing[b-zn22z7fvhb] {
    margin-top: auto;
    padding-top: .6rem;
    border-top: 1px dashed #7a4a2a;
    display: flex;
    flex-direction: column;
    gap: .35rem;
}

.testing-label[b-zn22z7fvhb] {
    margin: 0;
    font-size: .62rem;
    letter-spacing: .1em;
    color: #c07b45;
}

.testing-row[b-zn22z7fvhb] {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .35rem;
    font-size: .72rem;
    color: #8e8261;
}

.testing-row span[b-zn22z7fvhb] { min-width: 7rem; }

.testing-row button[b-zn22z7fvhb] {
    padding: .2rem .55rem;
    border: 1px dashed #7a4a2a;
    border-radius: 999px;
    background: #201509;
    color: #d59f6d;
    font-size: .7rem;
    cursor: pointer;
}

.testing-row button:hover:not(:disabled)[b-zn22z7fvhb] { background: #2d1d0c; border-color: #c07b45; }
.testing-row button:disabled[b-zn22z7fvhb] { opacity: .4; cursor: default; }
.testing-row button.to-next[b-zn22z7fvhb] { border-style: solid; }

/* Something is happening. Without this the level-up looked like a button that did nothing: the level
   landed server-side and the screen sat unchanged for several seconds while three library reads
   finished. The reads are cached now and it is fast — this is what stops the NEXT slow thing reading as
   a broken one. */
.bench-message.working[b-zn22z7fvhb] {
    display: flex;
    align-items: center;
    gap: .5rem;
    background: #1a2030;
    border-color: #3c4a68;
    color: #c3cbe6;
}

.bench-spinner[b-zn22z7fvhb] {
    width: .8rem;
    height: .8rem;
    flex: none;
    border: 2px solid #3c4a68;
    border-top-color: #c2a961;
    border-radius: 50%;
    animation: bench-spin-b-zn22z7fvhb .7s linear infinite;
}

@keyframes bench-spin-b-zn22z7fvhb { to { transform: rotate(360deg); } }

/* Reduced motion: keep the signal, drop the movement. */
@media (prefers-reduced-motion: reduce) {
    .bench-spinner[b-zn22z7fvhb] { animation: none; border-top-color: #c2a961; }
}

/* What the model chose and why. Shown after the fact rather than as a dialog to approve: the level is
   already taken and reversible by rebuilding, and a wall of text between a button and its result is how
   people learn to stop reading walls of text. */
.ai-reasoning[b-zn22z7fvhb] {
    margin: .6rem 0 0;
    padding: .6rem .7rem;
    border: 1px solid #3c4a68;
    border-left-width: 3px;
    border-radius: 10px;
    background: #161d2c;
}

.ai-reasoning p[b-zn22z7fvhb] { margin: 0; font-size: .74rem; line-height: 1.5; color: #c3cbe6; }
.ai-reasoning .eyebrow[b-zn22z7fvhb] { margin-bottom: .3rem; color: #7d8db0; }

/* Not an error, but not nothing either: a model that keeps naming feats it was not offered is a prompt
   that needs looking at, and burying that would be losing the only place it shows. */
.ai-dropped[b-zn22z7fvhb] {
    margin-top: .45rem !important;
    padding-top: .45rem;
    border-top: 1px dashed #4a4230;
    color: #c2a06a !important;
    font-size: .68rem !important;
}
/* /Components/Shared/CharacterOptionsEditor.razor.rz.scp.css */
/* ⭐ Issue #685. The player's own settings for one character.

   Colours are the character sheet's, not the party pane's, because this is drawn INSIDE a
   .sheet-section: #b9aa94 for a label, #f0d18d for a value, #8f8371 for a quiet line. Taken from
   ActorSheet.razor.css rather than written from memory. */

.character-options[b-30c1ozivcv] { display: grid; gap: .25rem; }

.option-row[b-30c1ozivcv] { display: flex; flex-wrap: wrap; gap: .6rem; align-items: flex-end; }

.option-row label[b-30c1ozivcv] {
    display: flex; flex-direction: column; gap: .25rem;
    font-size: .68rem; font-weight: 700; letter-spacing: .05em;
    text-transform: uppercase; color: #b9aa94;
}

.option-row select[b-30c1ozivcv] {
    padding: .3rem .45rem;
    border: 1px solid #40362a; border-radius: 6px;
    background: #15120f; color: #f0d18d;
    font-size: .8rem; text-transform: none; letter-spacing: 0;
}

/* A checkbox reads left-to-right; only the dropdown wants its label stacked above it. */
.option-row label.option-check[b-30c1ozivcv] {
    flex-direction: row; align-items: center; gap: .4rem;
    text-transform: none; letter-spacing: 0; font-size: .8rem; font-weight: 400;
    color: #f0d18d;
}

.option-hint[b-30c1ozivcv] { margin: .1rem 0 .8rem; font-size: .72rem; line-height: 1.4; color: #8f8371; }

.option-hint strong[b-30c1ozivcv] { color: #b9aa94; }

/* ⚠️ Deliberately dimmer and italic: this line says the setting is recorded and acted on by nothing
   yet, and it should read as a caveat rather than as an explanation of what will happen. */
.option-hint.pending[b-30c1ozivcv] { margin-bottom: 0; font-style: italic; color: #7d7263; }
/* /Components/Shared/CharacterPersonalityPanel.razor.rz.scp.css */
/* ⭐ Issue #862. Who a character IS, on the tab beside Simple View and the ruleset's own sheet.

   Colours are the character sheet's, taken from ActorSheet.razor.css rather than written from memory —
   the same palette CharacterOptionsEditor.razor.css cites and for the same reason: this is drawn INSIDE
   the sheet dialog, and a panel that invents its own greys reads as a foreign body.
     #b9aa94 a label · #f0d18d a value · #8f8371 a quiet line · #40362a a border · #15120f an input. */

.personality[b-7rv2t77533] { display: grid; gap: .9rem; padding: .2rem 0 .4rem; }

/* --- sanctification: read-only, and it has to LOOK read-only ------------------------------------- */

/* ⚠️ Deliberately not styled as a field. It is derived from the character's traits, and anything with a
   box around it invites somebody to try to type in it. */
.creed[b-7rv2t77533] {
    display: grid; gap: .25rem;
    padding: .5rem .6rem;
    border: 1px solid #40362a; border-left: 3px solid #6b5a3e; border-radius: 6px;
    background: #17130f;
}

.creed-label[b-7rv2t77533] {
    font-size: .68rem; font-weight: 700; letter-spacing: .05em;
    text-transform: uppercase; color: #b9aa94;
}

.creed-value[b-7rv2t77533] { font-size: .95rem; color: #f0d18d; }

/* Holy and unholy are a declared allegiance with real mechanical weight, so they get a colour; "None"
   is the ordinary case for four characters in five and must not look like a warning. */
.creed-value.Holy[b-7rv2t77533] { color: #ffe9a8; }
.creed-value.Unholy[b-7rv2t77533] { color: #d99a9a; }
.creed-value.none[b-7rv2t77533] { color: #8f8371; font-style: italic; }

.creed-note[b-7rv2t77533] { margin: 0; font-size: .72rem; line-height: 1.45; color: #8f8371; }
.creed-note.bad[b-7rv2t77533] { color: #d99a9a; }

/* #1524. The deity is chosen from a list, so it is the one line in a creed box that is a control. Borrowed from
   .field select below; nobody has chosen this look (Dave reviews UI himself). */
.creed.deity select[b-7rv2t77533] {
    width: 100%;
    padding: .35rem .5rem;
    border: 1px solid #40362a; border-radius: 6px;
    background: #15120f; color: #f0d18d;
    font-family: inherit; font-size: .82rem;
}
.creed.deity select:disabled[b-7rv2t77533] { opacity: .55; cursor: not-allowed; }

/* --- the authored fields -------------------------------------------------------------------------- */

.field[b-7rv2t77533] { display: grid; gap: .3rem; }

.field > span[b-7rv2t77533] {
    font-size: .68rem; font-weight: 700; letter-spacing: .05em;
    text-transform: uppercase; color: #b9aa94;
}

.field > small[b-7rv2t77533] { font-size: .72rem; line-height: 1.45; color: #8f8371; }

.field textarea[b-7rv2t77533],
.field select[b-7rv2t77533] {
    width: 100%;
    padding: .4rem .5rem;
    border: 1px solid #40362a; border-radius: 6px;
    background: #15120f; color: #f0d18d;
    font-family: inherit; font-size: .82rem; line-height: 1.5;
    resize: vertical;
}

.field textarea:focus[b-7rv2t77533],
.field select:focus[b-7rv2t77533] { outline: none; border-color: #6b5a3e; }

.field textarea:disabled[b-7rv2t77533],
.field select:disabled[b-7rv2t77533] { opacity: .55; cursor: not-allowed; }

.field.voice select[b-7rv2t77533] { resize: none; }

/* --- the caveat and the actions -------------------------------------------------------------------- */

/* ⚠️ Italic and dim, exactly as CharacterOptionsEditor's "pending" line is: this says what the engine
   does and does not enforce of what the boxes above describe, and it must read as a caveat rather than
   a promise. There are TWO of these since #871 — what is enforced, then the exact wording that reaches
   it — so they need a gap between them; the previous single one could keep margin: 0. */
.caveat[b-7rv2t77533] {
    margin: 0; padding: .45rem .6rem;
    border-left: 3px solid #5a4a34; border-radius: 4px;
    background: #17130f;
    font-size: .72rem; font-style: italic; line-height: 1.45; color: #7d7263;
}

.caveat + .caveat[b-7rv2t77533] { margin-top: .4rem; }

/* The one non-italic thing in a caveat, because it is the literal text an author has to type: a phrase
   shown in the same slant as the prose around it invites paraphrase, which is the one thing that
   breaks the match. */
.caveat code[b-7rv2t77533] {
    font-style: normal; font-size: .72rem;
    padding: 0 .25rem; margin-right: .3rem;
    border-radius: 3px; background: #241d15; color: #b9a882;
}

.caveat strong[b-7rv2t77533] { color: #a1937c; }

.actions[b-7rv2t77533] { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; }

.save[b-7rv2t77533] {
    padding: .4rem .9rem;
    border: 1px solid #6b5a3e; border-radius: 6px;
    background: #2a2318; color: #f0d18d;
    font-size: .8rem; font-weight: 600; cursor: pointer;
}

.save:hover:not(:disabled)[b-7rv2t77533] { background: #362c1e; }
.save:disabled[b-7rv2t77533] { opacity: .55; cursor: progress; }

.message[b-7rv2t77533] { font-size: .75rem; }
.message.good[b-7rv2t77533] { color: #9fbf8f; }
.message.bad[b-7rv2t77533] { color: #d99a9a; }

.read-only[b-7rv2t77533] { margin: 0; font-size: .75rem; font-style: italic; color: #8f8371; }

/* A fact about where this write lands, not a warning about the write failing — so it takes the label
   colour rather than the caveat's dimmed italic. */
.notice[b-7rv2t77533] {
    margin: 0; padding: .45rem .6rem;
    border-left: 3px solid #6b5a3e; border-radius: 4px;
    background: #1b1610;
    font-size: .73rem; line-height: 1.45; color: #b9aa94;
}
/* /Components/Shared/ChoiceSlot.razor.rz.scp.css */
.slot[b-jzwpzznuzi]{display:flex;align-items:center;gap:.7rem;padding:.55rem .7rem .55rem .9rem;border:1px solid #2b364f;border-radius:11px;background:#111827a8;flex-wrap:wrap}
/* ⭐ A slot says whether it still wants an answer, in colour. Dave, 2026-09-08: "I had to hunt for the
   one I missed... it just blended in with the background." He missed a slot that APPEARED after a later
   choice (heritage opening an ancestry feat), which is correct behaviour that nothing announced.

   ⚠️ Deliberately quiet: a wash on the border and a hairline glow, not a red box. This is a wizard a
   player walks down once, and eight loud red rows on first paint would read as eight errors rather than
   as eight things still to do. */
.slot.empty[b-jzwpzznuzi]{border-style:dashed;border-color:#7d4a52;background:#1a1116a8;box-shadow:inset 0 0 0 1px #7d4a5233}
.slot.empty:hover[b-jzwpzznuzi]{border-color:#a8646f}
.slot.filled[b-jzwpzznuzi]{border-color:#3f6b52;background:#101c16a8}
.slot:hover[b-jzwpzznuzi]{border-color:#4a5c88}

/* ⛔ A locked slot is NOT unfinished. An earlier choice dictates it, so there is nothing to do and red
   would be a lie -- it goes back to neutral and stays out of the count. */
.slot.locked[b-jzwpzznuzi],.slot.locked.empty[b-jzwpzznuzi],.slot.locked.filled[b-jzwpzznuzi]{border-color:#2b364f;border-style:solid;background:#111827a8;box-shadow:none;opacity:.85}
.slot.locked:hover[b-jzwpzznuzi]{border-color:#2b364f}
.slot-label[b-jzwpzznuzi]{flex:0 0 8.5rem;font-size:.67rem;font-weight:800;letter-spacing:.11em;text-transform:uppercase;color:#8b96b6}
.slot-body[b-jzwpzznuzi]{flex:1;min-width:11rem;display:flex;align-items:baseline;gap:.55rem;flex-wrap:wrap;text-align:left;background:transparent;border:0;padding:.15rem 0;color:#e7eaf2;cursor:pointer;font:inherit}
.slot-body:disabled[b-jzwpzznuzi]{cursor:default}
.slot-body strong[b-jzwpzznuzi]{font-size:.95rem;color:#faf7ef}
.slot-summary[b-jzwpzznuzi]{font-size:.72rem;color:#9fb2d8}
.slot-plus[b-jzwpzznuzi]{font-size:1.05rem;line-height:1;color:#e8c36a}
.slot-placeholder[b-jzwpzznuzi]{font-size:.82rem;color:#78849f}
.slot-body:hover .slot-placeholder[b-jzwpzznuzi]{color:#b7c2df}
.slot-actions[b-jzwpzznuzi]{display:flex;align-items:center;gap:.35rem;flex-shrink:0}
.slot-chip[b-jzwpzznuzi]{border:1px solid #34405c;border-radius:999px;background:transparent;color:#9da8c5;padding:.24rem .6rem;font-size:.66rem;font-weight:700;cursor:pointer;white-space:nowrap}
.slot-chip:hover[b-jzwpzznuzi]{border-color:#e8c36a;color:#f5e3b0}
/* Every clear/remove control in the wizard is red at rest, and only these are — so "this undoes
   something" is readable without hovering, and never confused with an adjacent adjust control. */
.slot-x[b-jzwpzznuzi]{width:1.5rem;height:1.5rem;border-radius:999px;border:1px solid #6d3f46;background:#3a1e22;color:#e08b93;cursor:pointer;font-size:.65rem;line-height:1;display:grid;place-items:center}
.slot-x:hover[b-jzwpzznuzi]{border-color:#c9545c;background:#7c2a30;color:#ffe6e7}
.slot-lock-note[b-jzwpzznuzi]{flex-basis:100%;padding-left:9.2rem;font-size:.68rem;color:#8b96b6}
.slot-detail[b-jzwpzznuzi]{flex-basis:100%;margin:.35rem 0 .1rem;padding-left:9.2rem;font-size:.76rem;line-height:1.5;color:#9aa6c2;white-space:pre-line}
@media (max-width:560px){
    .slot-label[b-jzwpzznuzi]{flex-basis:100%}
    .slot-lock-note[b-jzwpzznuzi],.slot-detail[b-jzwpzznuzi]{padding-left:0}
}
/* /Components/Shared/CoachMark.razor.rz.scp.css */
/* ⚠️ HIGHLIGHTER YELLOW ON PURPOSE, and it is meant to be judged rather than admired. Dave, 2026-09-08:
   "more obviously a kick... maybe its a bad idea, but need to see to know." The previous treatment was a
   dark card with a gold border, which sat politely in the page and read as decoration. This does not.
   If it turns out to be too much, the whole decision is these few declarations. */
.coach-mark[b-fyuen4gtu8] {
    display: flex;
    gap: .75rem;
    align-items: flex-start;
    margin: .9rem 0;
    padding: .9rem 1.1rem;
    border-radius: 12px;
    /* A real highlighter yellow rather than a gold. Black text is not a preference here -- it is the only
       thing that stays readable on this. */
    background: linear-gradient(180deg, #ffe14d, #f5c518);
    border: 2px solid #b8860b;
    color: #14100a;
    box-shadow: 0 10px 30px #03060d80;
}

.coach-body h3[b-fyuen4gtu8] {
    margin: 0 0 .35rem;
    font-family: Georgia, 'Times New Roman', serif;
    font-size: 1rem;
    color: #14100a;
}

.coach-body p[b-fyuen4gtu8] {
    margin: 0;
    font-size: .86rem;
    line-height: 1.6;
    color: #241d0d;
}

/* ⭐ The reassurance after "intimidating" is the half that has to land. On yellow it cannot be a colour
   change, so it is weight plus an underline that reads as a marker stroke. */
.coach-body strong[b-fyuen4gtu8] {
    font-weight: 800;
    color: #000;
    text-decoration: underline;
    text-decoration-thickness: .14em;
    text-underline-offset: .15em;
    text-decoration-color: #b8860b99;
}

/* The arrow is drawn in the card's own flex row, so it needs no knowledge of where its target is. */
.coach-arrow[b-fyuen4gtu8] {
    flex: 0 0 auto;
    width: 0;
    height: 0;
    margin-top: .25rem;
    border-left: .55rem solid transparent;
    border-right: .55rem solid transparent;
}

.points-up .coach-arrow[b-fyuen4gtu8] {
    border-bottom: .8rem solid #14100a;
}

.points-down .coach-arrow[b-fyuen4gtu8] {
    border-top: .8rem solid #14100a;
}
/* /Components/Shared/CombatTracker.razor.rz.scp.css */
.combat-tracker[b-bhz3efnd1i] { display: grid; gap: .6rem; }
.tracker-idle[b-bhz3efnd1i] { display: grid; gap: .5rem; padding: 1rem; border: 1px solid #303b57; border-radius: 14px; background: #111827d9; }
.tracker-idle .empty-note[b-bhz3efnd1i] { margin: 0; color: #8e9ab8; font-size: .8rem; }
.thin-action[b-bhz3efnd1i] { justify-self: start; border: 1px solid #34405c; background: #1a2235; color: #dbe3f5; border-radius: 10px; padding: .45rem .8rem; cursor: pointer; }
.thin-action:hover[b-bhz3efnd1i] { background: #243050; }

.tracker-head[b-bhz3efnd1i] { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; padding: .8rem 1rem; border: 1px solid #3f5c8a; border-radius: 14px; background: #16203099; }
.tracker-head .eyebrow[b-bhz3efnd1i] { margin: 0 0 .25rem; font-size: .68rem; font-weight: 800; letter-spacing: .14em; color: #8b96b6; }
.tracker-head h3[b-bhz3efnd1i] { margin: 0; font-family: Georgia, serif; font-size: 1.05rem; color: #faf7ef; display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; }
.tracker-head h3 span[b-bhz3efnd1i] { font-family: inherit; font-size: .68rem; letter-spacing: .1em; text-transform: uppercase; color: #8e9ab8; }

.budget[b-bhz3efnd1i] { display: flex; gap: .35rem; }
.pip[b-bhz3efnd1i] { display: grid; place-items: center; min-width: 2.9rem; padding: .3rem .45rem; border-radius: 10px; border: 1px solid #34405c; background: #141b2b; font-size: 1rem; font-weight: 700; font-variant-numeric: tabular-nums; color: #e7eaf2; }
.pip em[b-bhz3efnd1i] { display: block; font-style: normal; font-size: .6rem; letter-spacing: .08em; text-transform: uppercase; color: #8e9ab8; }
.pip.actions[b-bhz3efnd1i] { border-color: #5570a0; }
.pip.reactions[b-bhz3efnd1i] { border-color: #4a5c86; }
.pip.movement[b-bhz3efnd1i] { border-color: #5c6f45; }

.tracker-list[b-bhz3efnd1i] { list-style: none; margin: 0; padding: 0; display: grid; gap: .3rem; }
/* Dave, 2026-08-21: "We have a lot of wasted space there, even during combat... so that the whole card
   shrinks by 1-2 lines."

   The name now spans the card and the vitals sit UNDER it rather than beside it, which is what buys the
   height back: the name no longer competes with AC, the bar and the drive badge for one line, so it stops
   wrapping on anything longer than "Ember". `init` spans both rows on the left, and disappears entirely
   out of combat - the markup does not render it. */
.tracker-list li[b-bhz3efnd1i] { display: grid; grid-template-columns: 2.2rem minmax(0, 1fr); grid-template-areas: "init who" "init vitals" "marks marks"; align-items: center; gap: .15rem .55rem; padding: .4rem .6rem; border: 1px solid #2b3550; border-left-width: 3px; border-radius: 10px; background: #111827b3; }
/* No initiative column at all once the fight is over, so the card does not carry a 2.2rem gutter for a
   number that is not there. */
.tracker-list li:not(:has(.init))[b-bhz3efnd1i] { grid-template-columns: minmax(0, 1fr); grid-template-areas: "who" "vitals" "marks"; }
.tracker-list li.party[b-bhz3efnd1i] { border-left-color: #64b5f6; }
.tracker-list li.foe[b-bhz3efnd1i] { border-left-color: #d87272; }
.tracker-list li.active[b-bhz3efnd1i] { background: #1b2740; border-color: #5570a0; border-left-color: #e8c36a; box-shadow: 0 0 0 1px #5570a055; }
.tracker-list li.defeated[b-bhz3efnd1i] { opacity: .45; filter: grayscale(1); }

.init[b-bhz3efnd1i] { grid-area: init; display: grid; place-items: center; align-self: center; font-family: Georgia, serif; font-weight: 700; color: #d9c287; font-variant-numeric: tabular-nums; }
.who[b-bhz3efnd1i] { grid-area: who; min-width: 0; display: flex; align-items: baseline; gap: .45rem; flex-wrap: wrap; }
/* The name gets the whole line now. Ellipsis rather than wrap: a second line here is the height this
   change exists to remove, and the full name is on the sheet a click away. */
.who strong[b-bhz3efnd1i], .who .row-sheet[b-bhz3efnd1i] { font-size: .88rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.side[b-bhz3efnd1i] { font-size: .62rem; letter-spacing: .1em; text-transform: uppercase; color: #8e9ab8; }
.vitals[b-bhz3efnd1i] { grid-area: vitals; display: flex; align-items: center; flex-wrap: wrap; gap: .1rem .5rem; }
.vitals .ac[b-bhz3efnd1i] { font-size: .7rem; color: #d9c287; white-space: nowrap; }
.vitals .hp[b-bhz3efnd1i] { font-size: .72rem; color: #aab5cf; font-variant-numeric: tabular-nums; white-space: nowrap; }
.hp-bar[b-bhz3efnd1i] { display: block; width: 3.5rem; height: .4rem; border-radius: 10px; background: #2a3144; overflow: hidden; }
/* Coloured by HOW HURT, not by which side. Dave: "the HP bar should be colored based on total vs current
   HP with green, yellow, red and flashing red, just to bring attention to badly hurt characters."

   The side-based colours these replace said something the border already said, and said nothing at all
   about the one fact worth shouting. Bands rather than a gradient so it reads at a glance across a room.

   The flash is ONLY the bottom band. A bar that flashes at half health is one a player learns to ignore,
   which would cost exactly the attention it is spent to buy. */
.hp-bar span[b-bhz3efnd1i] { display: block; height: 100%; border-radius: 10px; background: linear-gradient(90deg, #52c98d, #80e3ae); }
.hp-bar.hale span[b-bhz3efnd1i] { background: linear-gradient(90deg, #52c98d, #80e3ae); }
.hp-bar.hurt span[b-bhz3efnd1i] { background: linear-gradient(90deg, #d8bb4a, #ecd57a); }
.hp-bar.bloodied span[b-bhz3efnd1i] { background: linear-gradient(90deg, #e08a4a, #f0a86a); }
.hp-bar.dire span[b-bhz3efnd1i] { background: linear-gradient(90deg, #e04a4a, #ff7a7a); }

@media (prefers-reduced-motion: no-preference) {
    .hp-bar.dire span[b-bhz3efnd1i] { animation: hp-alarm-b-bhz3efnd1i 1s ease-in-out infinite; }
}

/* Never on a defeated actor: the alarm is for somebody still standing, and a corpse pulsing at the
   bottom of the list is noise competing with the character who can still be saved. */
.tracker-list li.defeated .hp-bar.dire span[b-bhz3efnd1i] { animation: none; }

@keyframes hp-alarm-b-bhz3efnd1i {
    0%, 100% { opacity: 1; }
    50% { opacity: .35; }
}

/* Ammunition remaining, beside the vitals it belongs with. Quiet until it is nearly the whole story:
   an empty quiver turns red, because that is the number that changes what you do next (issue #117). */
.vitals .ammo[b-bhz3efnd1i] { font-size: .7rem; color: #9db4a0; white-space: nowrap; font-variant-numeric: tabular-nums; }
.vitals .ammo.empty[b-bhz3efnd1i] { color: #ff9fa3; font-weight: 600; }

/* A combatant's name, when it opens their sheet. Styled as the text it replaces rather than as a
   button: the row is already busy, and the affordance is the underline on hover (issue #115). */
.row-sheet[b-bhz3efnd1i]{padding:0;border:0;background:none;font:inherit;font-weight:700;color:inherit;
    cursor:pointer;text-align:left}
.row-sheet:hover[b-bhz3efnd1i]{text-decoration:underline}

.marks[b-bhz3efnd1i] { grid-area: marks; display: flex; flex-wrap: wrap; gap: .25rem; }
.mark[b-bhz3efnd1i] { padding: .12rem .45rem; border-radius: 999px; border: 1px solid #8a6f37; background: #151b2b; font-size: .66rem; color: #f4d58d; }
.mark.down[b-bhz3efnd1i] { border-color: #8f3c47; color: #ff9fa3; }
/* A posture is something the character CHOSE, and a condition is something that happened to them.
   Different colour so the two do not read as one list. */
.mark.posture[b-bhz3efnd1i] { border-color: #4a6b8a; background: #131c2b; color: #9fc6e8; }

.tracker-foot[b-bhz3efnd1i] { margin: 0; font-size: .7rem; color: #8e9ab8; }

@media (max-width: 560px) {
    .tracker-list li[b-bhz3efnd1i] { grid-template-columns: 2.2rem minmax(0, 1fr); grid-template-areas: "init who" "init vitals" "marks marks"; }
    .vitals[b-bhz3efnd1i] { justify-content: flex-start; }
}

/* What the active actor is doing, on their own row. Muted: it is a status, not a headline. */
.who .acting[b-bhz3efnd1i]{display:block;margin-top:.15rem;font-size:.66rem;font-style:italic;color:#c9b47a;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* Who is taking this turn, on the row where the turn happens. The distinction that matters at a glance
   is which turns cost money, so AUTO and AI are deliberately different colours rather than shades. */
.drive[b-bhz3efnd1i]{margin-left:.3rem;padding:.02rem .3rem;border-radius:5px;border:1px solid transparent;
    font-size:.62rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase}
.drive.auto[b-bhz3efnd1i]{background:#1e2a1c;color:#9dc98a;border-color:#33452e}
.drive.ai[b-bhz3efnd1i]{background:#241d10;color:#c9b072;border-color:#3a3020}
.drive.you[b-bhz3efnd1i]{background:#1a2430;color:#8fb6d9;border-color:#2c3b4c}

/* Temporary hit points (issue #169). Its own colour because it is not hit points: spent first,
   and never healed back. Reading it as part of the maximum is the misreading to prevent. */
.temp-hp[b-bhz3efnd1i] { margin-left: .3rem; padding: 0 .28rem; border-radius: 4px; font-size: .78em;
    color: #8fd4ff; background: rgba(90, 209, 255, .14); border: 1px solid rgba(90, 209, 255, .35); }
/* /Components/Shared/CompanionChatterControl.razor.rz.scp.css */
/* People remember, Phase 5 (#1291). Drawn inside a .sheet-section, so the colours are the character sheet's, taken from
   CharacterOptionsEditor.razor.css: #b9aa94 for a label, #f0d18d for a value, #8f8371 for a quiet line. */

.chatter-choice[b-yazvnotd1o] { display: grid; gap: .45rem; }

.chatter-why[b-yazvnotd1o] { margin: 0 0 .2rem; font-size: .72rem; line-height: 1.4; color: #8f8371; font-style: italic; }

.chatter-option[b-yazvnotd1o] { display: flex; gap: .5rem; align-items: flex-start; cursor: pointer; }

.chatter-option input[b-yazvnotd1o] { margin-top: .2rem; accent-color: #e3bd70; }

.chatter-option span[b-yazvnotd1o] { display: grid; gap: .1rem; }

.chatter-option strong[b-yazvnotd1o] { color: #f0d18d; font-size: .82rem; }

.chatter-option small[b-yazvnotd1o] { color: #8f8371; font-size: .72rem; line-height: 1.4; }

.chatter-option em[b-yazvnotd1o] { color: #b9aa94; font-style: normal; font-weight: 700; }

.chatter-failed[b-yazvnotd1o] { margin: 0; color: #d8a58f; font-size: .72rem; }
/* /Components/Shared/ConditionBuilder.razor.rz.scp.css */
.condition-builder[b-s6m8d1x495] { display: grid; gap: .75rem; min-width: 0; padding: .8rem; border: 1px solid #55462f; background: #17140f; }
.condition-heading[b-s6m8d1x495], .condition-row[b-s6m8d1x495] { display: flex; flex-wrap: wrap; align-items: end; gap: .75rem; }
.condition-builder label[b-s6m8d1x495] { display: grid; gap: .3rem; color: #cdbfaa; }
.condition-builder input[b-s6m8d1x495], .condition-builder select[b-s6m8d1x495], .condition-builder textarea[b-s6m8d1x495] { color: #eee8dc; background: #242019; border: 1px solid #66563e; padding: .45rem; }
.condition-builder button[b-s6m8d1x495] { width: fit-content; color: #eee8dc; background: #31291e; border: 1px solid #766247; padding: .4rem .65rem; cursor: pointer; }
.condition-children[b-s6m8d1x495] { display: grid; gap: .55rem; padding-left: .75rem; border-left: 2px solid #4c7f7a; }
.condition-raw[b-s6m8d1x495] { display: grid; gap: .55rem; }
.condition-raw textarea[b-s6m8d1x495] { width: 100%; font-family: Consolas, monospace; }
.condition-error[b-s6m8d1x495] { color: #ff9c87; }
/* /Components/Shared/ContainerCard.razor.rz.scp.css */
/* The container card (#588) — the encounter card's younger sibling.

   ⭐⭐ EVERY GRAPHIC IS A SLOT, the same contract #578 set and for the same reason Dave gave then:
   "make the design easy to enhance with graphics so we can tweak it." Each variable is a complete
   `background` shorthand with a hand-drawn CSS fallback in it. To hang real art on this card, set the
   variable — no markup moves, and one slot can be replaced without touching the others.

     --container-frame     the outer iron-banded border
     --container-corner    the bracket at each of the four corners
     --container-banner    the parchment strip the state line sits on
     --container-plate     the plate behind the heading
     --container-button    the action buttons
     --container-texture   the card's inner ground, behind everything
     --container-art       ⭐ THE TYPED-ART SLOT. Overridden per STATE below, and the hook a per-KIND
                           lid (barrel, burial niche, sarcophagus, satchel) hangs on the day the art
                           exists. Dave: "we can fill that gap later, but for now we need something."

   ⛔ NOT PROMOTED TO forge.css OR play.css, and that is deliberate. A shared sheet is linked BEFORE the
   scoped bundle, so a shared rule loses every tie to a page's own — these rules would become defaults
   the first page to name `.container-card` could silently beat. → promoting-shared-css

   Sized in rem throughout so a future art pass can scale the whole card by changing font-size alone. */

.container-veil[b-wbxo58rzap] {
    position: fixed;
    inset: 0;
    /* ⛔ THE TOKEN, NEVER A HAND-TYPED NUMBER — and I typed one, and `DialogLayeringTests` caught it.
       A bare z-index relates to nothing: the floating pane bands are assigned in dm-panes.js, and a
       literal 60 sits BELOW a pinned window, so the card would open behind one. The encounter card's
       stylesheet already carries this exact warning, in these words, from #532. */
    z-index: var(--dm-layer-dialog, 1000);
    display: grid;
    /* `safe center` rather than plain centring, for the encounter card's reason: this card's ledger is as
       long as somebody's pack, so it can easily be taller than the window — and a grid-centred box that
       overflows has its TOP pushed off-screen, taking the title and the close button with it. */
    place-items: safe center;
    padding: 1rem;
    overflow-y: auto;
    background: #05070cc4;
    backdrop-filter: blur(2px);
}

.container-card[b-wbxo58rzap] {
    --container-ink: #f3e7cf;
    --container-ink-dim: #c9b48e;
    --container-gold: #d8b96a;
    --container-iron: #1a1613;

    --container-texture:
        radial-gradient(120% 80% at 50% 0%, #3a322a 0%, #221d18 55%, #14100d 100%);

    --container-frame:
        linear-gradient(160deg, #6b5a3f 0%, #2b241c 18%, #40362a 50%, #241e17 82%, #6b5a3f 100%);

    --container-corner:
        radial-gradient(circle at 68% 68%, #d8bd7d 0 12%, #8a7448 13% 20%, transparent 21%),
        linear-gradient(135deg, #b39a63 0 38%, #7d6a48 38% 62%, #4a3d28 62% 100%);

    --container-banner:
        linear-gradient(180deg, #e5d6b4 0%, #d3c09a 48%, #c0aa80 100%);

    --container-plate:
        linear-gradient(180deg, #3b2f22 0%, #4a3b2a 45%, #2a2118 100%);

    --container-button:
        linear-gradient(180deg, #5a4a30 0%, #47391f 55%, #2f2615 100%);

    /* The default lid: a banded wooden chest, shut. Redefined per state below. */
    --container-art:
        linear-gradient(180deg, transparent 0 38%, #2a1f14 38% 46%, transparent 46%),
        linear-gradient(180deg, #7d5f3a 0%, #5f4629 55%, #3d2c19 100%);

    position: relative;
    display: grid;
    justify-items: stretch;
    gap: .7rem;
    /* ⛔ BORDER-BOX, AND IT IS NOT A TIDINESS RULE. This app has no global reset — box-sizing is set per
       element where it is needed — so `width: min(34rem, 94vw)` was the CONTENT width, and the .55rem
       border plus 1.5rem of side padding were added ON TOP. In a 473px pane the card computed to 492px
       and its right edge landed 35px off-screen, which took the whole "Put in" column with it: every
       button in the ledger read "Put" and was unclickable past the edge.
       ⚠️ Caught by asking the browser for the computed box, not by looking — the card LOOKED fine,
       because the only thing overflowing was the one column that was flush right. */
    box-sizing: border-box;
    /* ⚠️ `100%`, NOT `94vw` — ObjectCard's correction, arrived at by measurement after this card's own
       border-box fix landed, and brought back here so the three cards say one thing. The veil is a fixed
       grid with 1rem of padding, so 100% is the content box the card has to fit inside; a
       viewport-relative width does not know about that padding and adds it straight back. 94vw of a
       470px viewport is 441.8 against a 438px veil content box: under four pixels, nothing visibly cut,
       and a horizontal scrollbar on a modal. Invisible to the eye, which is this whole family's shape. */
    width: min(34rem, 100%);
    padding: 1.4rem 1.5rem 1.5rem;
    background: var(--container-texture);
    border: .55rem solid transparent;
    border-image: var(--container-frame) 1;
    box-shadow: 0 1.2rem 3rem #000a, inset 0 0 3rem #0007;
    color: var(--container-ink);
    font-size: .95rem;
}

/* The four brackets, hung outside the padding box as real elements so a future sprite can replace one
   corner at a time. ⚠️ The card must never become a scroll container while these hang off it — the veil
   does the scrolling, which is the lesson the encounter card paid for. */
.container-corner[b-wbxo58rzap] {
    position: absolute;
    width: 1.15rem;
    height: 1.15rem;
    background: var(--container-corner);
}
.container-corner.top-left[b-wbxo58rzap] { top: -.75rem; left: -.75rem; }
.container-corner.top-right[b-wbxo58rzap] { top: -.75rem; right: -.75rem; transform: rotate(90deg); }
.container-corner.bottom-right[b-wbxo58rzap] { bottom: -.75rem; right: -.75rem; transform: rotate(180deg); }
.container-corner.bottom-left[b-wbxo58rzap] { bottom: -.75rem; left: -.75rem; transform: rotate(270deg); }

.container-close[b-wbxo58rzap] {
    position: absolute;
    top: .3rem;
    right: .45rem;
    border: 0;
    background: none;
    color: var(--container-ink-dim);
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
}
.container-close:hover[b-wbxo58rzap], .container-close:focus-visible[b-wbxo58rzap] { color: var(--container-ink); }

.container-title[b-wbxo58rzap] {
    margin: 0;
    padding: .35rem .8rem;
    background: var(--container-plate);
    border: 1px solid #00000055;
    box-shadow: inset 0 0 .8rem #0006;
    text-align: center;
    font-size: 1.15rem;
    letter-spacing: .02em;
    color: var(--container-gold);
}

.container-banner[b-wbxo58rzap] {
    margin: 0;
    padding: .2rem .7rem;
    background: var(--container-banner);
    color: #3a2d1a;
    text-align: center;
    font-style: italic;
    font-size: .88rem;
}

/* ⭐ The typed-art slot, drawn. A flat strip rather than a square: it is a LID, and the card is wide. */
.container-art[b-wbxo58rzap] {
    height: 3.2rem;
    background: var(--container-art);
    border: 1px solid #00000066;
    box-shadow: inset 0 0 1rem #0008;
}

/* ⭐ #597 Phase 4: a real picture, when the host resolved one. The typed lid above stays the empty
   state — it is the ordinary case on a fresh database — so this only has to hide the gradient behind an
   image that is already covering it, and give the taller crop somewhere to go. `object-fit: cover`
   rather than `contain`: the slot is a wide strip and a letterboxed square in it reads as a bug.
   ⚠️ ObjectCard.razor.css carries the same three rules for .object-art. Deliberately not promoted — the
   two sheets stay separately re-skinnable, which is ContainerCard's own recorded decision. */
.container-art.has-art[b-wbxo58rzap] {
    height: 8rem;
    padding: 0;
    background: #0b0906;
}

.container-art.has-art img[b-wbxo58rzap] {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* One override per state, so the four faces already look different with no asset loaded. */
.container-card.locked[b-wbxo58rzap] {
    --container-art:
        radial-gradient(circle at 50% 52%, #d8bd7d 0 9%, #6b5732 10% 14%, transparent 15%),
        linear-gradient(180deg, transparent 0 38%, #2a1f14 38% 46%, transparent 46%),
        linear-gradient(180deg, #7d5f3a 0%, #5f4629 55%, #3d2c19 100%);
}
/* ⚠️ The open lid needs WALLS, not just a dark inside. The first version was a single dark gradient and
   it read as a broken image rather than as a placeholder — which is precisely the failure the encounter
   card's empty portrait frame exists to avoid. A lit rim across the top and a band down each side is
   enough to say "you are looking into a box" with no asset at all. */
.container-card.open[b-wbxo58rzap] {
    --container-art:
        linear-gradient(90deg, #6f5638 0 4%, transparent 4% 96%, #6f5638 96% 100%),
        linear-gradient(180deg, #8a6c45 0 14%, #3a2b1b 14% 22%, #171009 22% 100%);
}
/* Staved in: the seam is broken and out of true, and the inside shows through the gap. */
.container-card.broken[b-wbxo58rzap] {
    --container-art:
        linear-gradient(100deg, transparent 0 44%, #120d09 44% 56%, transparent 56%),
        linear-gradient(180deg, #5e4830 0 30%, #17110d 30% 100%);
}

.container-prompt[b-wbxo58rzap] {
    margin: .2rem 0 0;
    font-size: .82rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--container-ink-dim);
}

.container-hands[b-wbxo58rzap] { display: flex; flex-wrap: wrap; gap: .35rem; }

.container-hand[b-wbxo58rzap] {
    padding: .25rem .6rem;
    border: 1px solid #6b5a3f;
    background: #1d1812;
    color: var(--container-ink-dim);
    font: inherit;
    font-size: .85rem;
    cursor: pointer;
}
.container-hand:hover[b-wbxo58rzap], .container-hand:focus-visible[b-wbxo58rzap] { border-color: var(--container-gold); color: var(--container-ink); }
.container-hand.chosen[b-wbxo58rzap] { background: var(--container-plate); border-color: var(--container-gold); color: var(--container-gold); }

.container-actions[b-wbxo58rzap] { display: flex; flex-wrap: wrap; gap: .5rem; }

.container-action[b-wbxo58rzap] {
    flex: 1 1 12rem;
    display: grid;
    gap: .15rem;
    padding: .5rem .8rem;
    border: 1px solid #00000066;
    background: var(--container-button);
    box-shadow: inset 0 1px 0 #ffffff18, 0 .2rem .5rem #0006;
    color: var(--container-ink);
    font: inherit;
    cursor: pointer;
}
.container-action:hover:not(:disabled)[b-wbxo58rzap], .container-action:focus-visible[b-wbxo58rzap] { filter: brightness(1.18); }
.container-action:disabled[b-wbxo58rzap] { opacity: .5; cursor: default; }
.container-action small[b-wbxo58rzap] { font-size: .72rem; color: var(--container-ink-dim); }
/* The destructive route reads as destructive before it is pressed, not after. */
.container-action.ruinous[b-wbxo58rzap] { --container-button: linear-gradient(180deg, #6d2a1e 0%, #552016 55%, #391510 100%); }

.container-ledger[b-wbxo58rzap] { display: grid; gap: .3rem; }

.container-heading[b-wbxo58rzap] {
    margin: .3rem 0 0;
    font-size: .8rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--container-gold);
    border-bottom: 1px solid #6b5a3f55;
    padding-bottom: .15rem;
}

.container-nothing[b-wbxo58rzap] { margin: 0; font-size: .85rem; font-style: italic; color: var(--container-ink-dim); }

.container-items[b-wbxo58rzap] { list-style: none; margin: 0; padding: 0; display: grid; gap: .2rem; }
.container-items li[b-wbxo58rzap] { display: flex; align-items: center; gap: .5rem; }
.container-items .item-name[b-wbxo58rzap] { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.container-items .item-name.coin[b-wbxo58rzap] { color: var(--container-gold); }
.container-items .item-count[b-wbxo58rzap] { color: var(--container-ink-dim); font-size: .85rem; }

.item-move[b-wbxo58rzap] {
    flex: 0 0 auto;
    padding: .15rem .55rem;
    border: 1px solid #6b5a3f;
    background: #1d1812;
    color: var(--container-ink-dim);
    font: inherit;
    font-size: .8rem;
    cursor: pointer;
}
.item-move:hover:not(:disabled)[b-wbxo58rzap], .item-move:focus-visible[b-wbxo58rzap] { border-color: var(--container-gold); color: var(--container-ink); }
.item-move:disabled[b-wbxo58rzap] { opacity: .45; cursor: default; }

.container-working[b-wbxo58rzap] { margin: 0; font-size: .85rem; color: var(--container-ink-dim); }

.container-feedback[b-wbxo58rzap] { margin: .2rem 0 0; font-size: .85rem; }
.container-feedback.yes[b-wbxo58rzap] { color: #9fd6a0; }
.container-feedback.no[b-wbxo58rzap] { color: #f0a58a; }

@media (max-width: 30rem) {
    .container-card[b-wbxo58rzap] { font-size: .9rem; padding: 1.1rem 1rem 1.2rem; }
    .container-action[b-wbxo58rzap] { flex-basis: 100%; }
}
/* /Components/Shared/CreatureFootprintPreview.razor.rz.scp.css */
.footprint-preview[b-4a58e63n8d] { display: grid; gap: .25rem; }
.footprint-preview h4[b-4a58e63n8d], .footprint-preview p[b-4a58e63n8d] { margin: 0; }
/* /Components/Shared/CreatureModifierEditor.razor.rz.scp.css */
/* The unrecognised-key advisory (issue #130).

   ⚠️ Scoped to THIS component even though the markup renders inside DraftListEditor's dialog and rows.
   That is correct and it is the trap this repo has hit four times in the other direction: a RenderFragment
   carries the scope id of the component that AUTHORED it, not the one that renders it — which is exactly
   why DraftListEditor needs ::deep to reach these same elements and why a rule written over there would
   match nothing here.

   ⛔ AMBER, NEVER RED, and the colours are lifted from ForgeAutomation's .action-warning on purpose. An
   unknown key is allowed: another pack or another ruleset may define it, nothing refuses to save, and a
   red field would say the opposite of what the note says. */
.modifier-unknown[b-nxwu3v4cbo]{margin-left:.4rem;padding:.05rem .35rem;border-radius:999px;
    border:1px solid #5a4a2e;background:#241d10;color:#d8bd7c;
    font-size:.62rem;font-weight:700;letter-spacing:.02em;text-transform:uppercase;cursor:help}

.modifier-unknown-note[b-nxwu3v4cbo]{margin:.15rem 0 0;padding:.35rem .5rem;border:1px solid #5a4a2e;border-radius:6px;
    background:#241d10;color:#d8bd7c;font-size:.74rem}
.modifier-unknown-note code[b-nxwu3v4cbo]{color:#e8dcc0}
/* /Components/Shared/CreaturePicker.razor.rz.scp.css */
/* The creature/pack pair, and the two things it says about what it found. */

.creature-picker[b-ty8te8etzs] {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.creature-picker-pack[b-ty8te8etzs] {
    margin: 0;
    color: var(--forge-text-muted);
    font-size: 0.8rem;
}

.creature-picker-note[b-ty8te8etzs] {
    margin: 0;
    color: var(--forge-text-dim);
    font-size: 0.78rem;
}

/* A note, not an error: the id may well be real in a pack this Forge has not loaded. Styled as a
   remark rather than a refusal so it reads as information, which is what it is. */
.creature-picker-unknown[b-ty8te8etzs] {
    margin: 0;
    padding: 0.35rem 0.55rem;
    background: var(--forge-surface-notice);
    border: 1px solid var(--forge-border-quiet);
    border-radius: 4px;
    color: var(--forge-text-muted);
    font-size: 0.78rem;
}
/* /Components/Shared/DamageTypePicker.razor.rz.scp.css */
/*
    The damage-type picker inside the FenReach Actions popup (#1471). The popup's own language, copied: Georgia on
    marsh-dusk glass, #dcb763 gold. Authored at 390px first; the choices wrap rather than scroll sideways.
*/

.damage-picker[b-3tunool4nz] {
    margin: 0 0 .75rem;
    padding: .6rem .65rem;
    border-radius: 8px;
    border: 1px solid #dcb763;
    background: #dcb7630f;
    font-family: Georgia, 'Times New Roman', serif;
    color: #e6ebf5;
}

.damage-picker-ask[b-3tunool4nz] {
    margin: 0 0 .45rem;
    font-size: .85rem;
    font-weight: 700;
}

.damage-picker-choices[b-3tunool4nz] {
    display: flex;
    flex-wrap: wrap;
    gap: .35rem;
}

.damage-type[b-3tunool4nz],
.damage-cancel[b-3tunool4nz] {
    font: inherit;
    font-size: .78rem;
    font-weight: 700;
    min-height: 2.1rem;
    padding: .3rem .7rem;
    border-radius: 6px;
    border: 1px solid #dcb763;
    background: transparent;
    color: #dcb763;
    cursor: pointer;
    white-space: nowrap;
}

.damage-cancel[b-3tunool4nz] {
    margin-top: .5rem;
    border-color: #ffffff33;
    color: #b8c0cf;
}

.damage-type:disabled[b-3tunool4nz],
.damage-cancel:disabled[b-3tunool4nz] { opacity: .5; cursor: not-allowed; }
/* /Components/Shared/DesignerToolkitDialog.razor.rz.scp.css */
/* The author's instruments. Deliberately kin to the adventure summary and the loading veil — the three
   things that take the screen should feel like they came from the same table — but marked out in a colder
   colour, because this one MUTATES the campaign rather than reporting on it. */
.designer-veil[b-td42ekcjwi]{position:fixed;inset:0;z-index:var(--dm-layer-dialog,1000);display:flex;align-items:center;justify-content:center;
    padding:1.5rem;background:#070b12ee;backdrop-filter:blur(5px);overflow-y:auto}
.designer-card[b-td42ekcjwi]{max-width:38rem;width:100%;max-height:calc(100vh - 3rem);overflow-y:auto;
    padding:1.5rem 1.8rem 1.6rem;border:1px solid #3a4a6e;border-radius:18px;
    background:#0e1320f7;box-shadow:0 24px 80px #03060da0}

.designer-head[b-td42ekcjwi]{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem}
.designer-eyebrow[b-td42ekcjwi]{margin:0;font-size:.68rem;letter-spacing:.16em;text-transform:uppercase;color:#7fb3d5}
.designer-card h2[b-td42ekcjwi]{margin:.35rem 0 0;font-family:Georgia,serif;font-size:1.4rem;color:#faf7ef;font-weight:600}
/* Layout only — the look comes from the shared .dm-close in app.css. */
.designer-close[b-td42ekcjwi]{flex:0 0 auto}

/* One tab today. The strip is here so the second one does not move everything (issue #256). */
.designer-tabs[b-td42ekcjwi]{display:flex;gap:.35rem;margin:1.1rem 0 0;border-bottom:1px solid #27334f}
.designer-tab[b-td42ekcjwi]{padding:.45rem .85rem;border:none;background:none;cursor:pointer;
    font-size:.78rem;letter-spacing:.06em;color:#8e9ab8;border-bottom:2px solid transparent}
.designer-tab.active[b-td42ekcjwi]{color:#cfe3f5;border-bottom-color:#7fb3d5}

.designer-panel[b-td42ekcjwi]{padding-top:1.1rem}
.designer-lede[b-td42ekcjwi]{margin:0 0 1.1rem;font-size:.8rem;line-height:1.6;color:#8e9ab8}
.designer-lede strong[b-td42ekcjwi]{color:#cfe3f5;font-weight:600}

.designer-tool[b-td42ekcjwi]{display:flex;gap:1rem;align-items:flex-start;justify-content:space-between;
    padding:.85rem 1rem;margin-bottom:.7rem;border:1px solid #27334f;border-radius:12px;background:#131a2b99}
.designer-tool-text[b-td42ekcjwi]{min-width:0}
.designer-tool h3[b-td42ekcjwi]{margin:0 0 .3rem;font-size:.9rem;color:#f0ece2;font-weight:600}
.designer-tool p[b-td42ekcjwi]{margin:0;font-size:.78rem;line-height:1.55;color:#9aa6c2}
/* The reason a control is dead, in the place the control is. */
.designer-blocked[b-td42ekcjwi]{margin:.45rem 0 0 !important;color:#e0b070 !important;font-style:italic}

.designer-go[b-td42ekcjwi]{flex:0 0 auto;padding:.5rem .95rem;border-radius:11px;border:1px solid #4d6f8d;
    background:#12202c;color:#bcdcf0;font-size:.82rem;font-weight:600;cursor:pointer}
.designer-go:hover:not(:disabled)[b-td42ekcjwi]{background:#183043;border-color:#7fb3d5}
.designer-go:disabled[b-td42ekcjwi]{opacity:.4;cursor:default}

.designer-result[b-td42ekcjwi]{margin:1rem 0 0;padding:.55rem .75rem;border:1px solid #3a4a6e;border-radius:10px;
    background:#131a2bcc;font-size:.82rem;line-height:1.5;color:#cfe3f5}
.designer-result.bad[b-td42ekcjwi]{border-color:#8d4046;background:#2a1418cc;color:#ffb8be}

.designer-hint[b-td42ekcjwi]{margin:1.2rem 0 0;font-size:.72rem;line-height:1.55;color:#7f8aa6}
/* /Components/Shared/DiceTheatre.razor.rz.scp.css */
/*
   The veil behind the dice, and nothing else.

   ⛔⛔ THE POPUP ITSELF IS NOT STYLED HERE AND CANNOT BE — but NOT because of a shadow root, which is
   what this comment used to claim and what the island has never actually used. Blazor's scoped CSS is an
   attribute selector stamped onto elements the SERVER rendered, and every node inside `.dice-stage` is
   built by the island in the browser, so no selector written here can name one. The popup's own chrome is
   `ClientApp/src/dice/host-styles.ts`, put in the document as a plain <style>.
   → memory `scoped-css-cannot-reach-a-built-element`

   ⚠️ dice-theatre 0.3.0 DOES offer `mount(element, { shadow: true })` and the island deliberately does
   not take it: a document stylesheet stops at a shadow boundary, and one rule in host-styles.ts corrects
   the package's own sheet. host-styles.ts states that in full.

   ⛔ `--dm-layer-dialog-over-dialog`, not a hand-typed number: DialogLayeringTests refuses a
   full-window overlay that picks its own z-index, and it is right to — six of eight dialogs once opened
   BEHIND Dave's pinned panes. The over-a-dialog band rather than the plain dialog one because a d20 can
   be rolled while the actor sheet or a merchant's shop is open, and those are at `--dm-layer-dialog`;
   this one is holding up the turn they are looking at.
*/
/* ⛔⛔ `display: flex` below BEATS the browser's own `[hidden] { display: none }`, so the idle veil
   covered the whole page in black — Dave, 2026-09-21: "my page is just completely black now". The host
   is rendered always (see the component), so the hidden state has to be stated here, with the attribute
   selector's higher specificity, or `hidden` is decoration. */
.dice-veil[hidden][b-edsbgcujcr] {
    display: none;
}

.dice-veil[b-edsbgcujcr] {
    position: fixed;
    inset: 0;
    z-index: var(--dm-layer-dialog-over-dialog, 1100);
    display: flex;
    align-items: safe center;
    justify-content: center;
    padding: 1rem;
    /* ⚠️ Scrolling belongs to the veil, never to the stage: the arena is `min(680px, 92vw)` square and
       on a short window the close button would otherwise be off the bottom of the screen — which on
       this dialog means unreachable, and this dialog is holding a turn. → DialogLayeringTests' encounter
       card, the same lesson. */
    overflow-y: auto;
    overscroll-behavior: contain;
    background: #04060ceb;
    backdrop-filter: blur(5px);
}

.dice-stage[b-edsbgcujcr] {
    width: 100%;
    max-width: 46rem;
    /* ⛔ The island's node goes INSIDE this one, and Blazor never touches its children. It needs a box;
       everything visible inside it is the island's. → the component's own ⛔⛔ about rendering always. */
    display: block;
}
/* /Components/Shared/DraftListEditor.razor.rz.scp.css */
/* The list's own chrome. It travels with the component, because scoped CSS does not cross a component
   boundary and a page that styled these rows from outside would unstyle them the moment anyone reused
   the editor somewhere else. */

.draft-list[b-5wvkd7nqzs] {
    display: grid;
    gap: .45rem;
    padding: .8rem;
    border: 1px solid #2a3450;
    border-radius: 10px;
    background: #131a2c;
}

.draft-list-heading[b-5wvkd7nqzs] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}

.draft-list-heading h4[b-5wvkd7nqzs] {
    margin: 0;
    font-family: Georgia, serif;
    font-size: .92rem;
    color: #faf7ef;
}

.draft-list-empty[b-5wvkd7nqzs] {
    margin: 0;
    font-size: .76rem;
    font-style: italic;
    color: #8e9ab8;
}

.draft-list-rows[b-5wvkd7nqzs] {
    display: grid;
    gap: .3rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Wraps so a row can carry a validator's diagnostic under its sentence. Nothing else in a row can wrap
   — the sentence is `flex: 1; min-width: 0` and the buttons are `flex: none` — so this changes no
   existing list's layout; it only gives the full-width item below somewhere to go. */
.draft-list-rows li[b-5wvkd7nqzs] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
    padding: .4rem .55rem;
    border: 1px solid #2a3450;
    border-radius: 8px;
    background: #0f1524;
}

/* ::deep is LOAD-BEARING: the errors list is drawn by ForgeFieldErrors and so carries THAT component's
   scope id, never this one's. Written plainly the rule would compile to `.forge-field-errors[b-thislist]`
   and match nothing — leaving a publish-time error squeezed between the sentence and the buttons, which
   is where it sat before it had a line of its own. */
.draft-list-rows li[b-5wvkd7nqzs]  .forge-field-errors {
    flex-basis: 100%;
    margin: 0;
}

/* The sentence takes the slack so the two buttons stay hard right however long the line runs, and
   min-width:0 lets it actually shrink — a flex item defaults to min-content and would otherwise push
   the buttons off the edge of a narrow pane. */
.draft-list-sentence[b-5wvkd7nqzs] {
    flex: 1;
    min-width: 0;
    font-size: .8rem;
    color: #d7deee;
}

.draft-list-edit[b-5wvkd7nqzs] {
    flex: none;
}

/* --- Inside the dialog ------------------------------------------------------------------------------
   ::deep is LOAD-BEARING. Everything below styles markup authored by whatever component supplied the
   Form fragment, so it carries that component's scope id and never this one's. Written plainly these
   rules compile to `label[b-thiscomponent]` and match absolutely nothing, silently. */

.draft-form[b-5wvkd7nqzs] {
    display: grid;
    gap: .6rem;
}

.draft-form[b-5wvkd7nqzs]  label {
    display: grid;
    gap: .25rem;
    font-size: .74rem;
    color: #8e9ab8;
}

.draft-form[b-5wvkd7nqzs]  input,
.draft-form[b-5wvkd7nqzs]  select,
.draft-form[b-5wvkd7nqzs]  textarea {
    width: 100%;
    box-sizing: border-box;
    padding: .45rem .55rem;
    border: 1px solid #34405c;
    border-radius: 8px;
    background: #0f1524;
    color: #eef2fb;
    font-size: .82rem;
}

/* Two or three short fields sit on one line rather than each taking a row of their own. */
.draft-form[b-5wvkd7nqzs]  .field-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
    gap: .6rem;
}

/* A list nested inside the dialog — an attack's extra damage components. Deliberately NOT a second
   dialog: a dialog opened from a dialog is a stack the author has to unwind, for three fields. */
.draft-form[b-5wvkd7nqzs]  .nested-list {
    display: grid;
    gap: .35rem;
    padding: .6rem;
    border: 1px solid #2a3450;
    border-radius: 8px;
    background: #0f1524;
}

.draft-form[b-5wvkd7nqzs]  .nested-list .nested-heading {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    font-size: .74rem;
    color: #8e9ab8;
}

.draft-form[b-5wvkd7nqzs]  .nested-list .nested-empty {
    font-size: .74rem;
    font-style: italic;
    color: #8e9ab8;
}

.draft-form[b-5wvkd7nqzs]  .nested-list .nested-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr)) auto;
    align-items: end;
    gap: .4rem;
}
/* /Components/Shared/EncounterCard.razor.rz.scp.css */
/* The encounter card (#578, #579) — Dave's reference image, in CSS.

   ⭐⭐ EVERY GRAPHIC IS A SLOT, because Dave asked for exactly that: "make the design easy to enhance
   with graphics so we can tweak it." Each variable below is a complete `background` shorthand with a
   hand-drawn CSS fallback in it. To hang real art on this card, set the variable — nothing else changes,
   no markup moves, and one slot can be replaced without touching the other five.

     --encounter-frame     the outer stone-and-iron border
     --encounter-corner    the spiked bracket at each of the four corners
     --encounter-banner    the parchment strip the subtitle sits on
     --encounter-plate     the red title plate behind the heading
     --encounter-button    the big action button
     --encounter-texture   the card's inner ground, behind everything

   Sized in rem throughout so a future art pass can scale the whole card by changing font-size alone. */

.encounter-card[b-d5w5hy6bkr] {
    --encounter-ink: #f3e7cf;
    --encounter-ink-dim: #c9b48e;
    --encounter-gold: #d8b96a;
    --encounter-blood: #7d1f18;
    --encounter-iron: #1a1613;

    /* The stone ground: a dark slab with a faint grain, lit slightly from above. */
    --encounter-texture:
        radial-gradient(120% 80% at 50% 0%, #3a322a 0%, #221d18 55%, #14100d 100%);

    /* Iron banding with a bevelled highlight, which is what reads as "metal" without an image. */
    --encounter-frame:
        linear-gradient(160deg, #6b5a3f 0%, #2b241c 18%, #40362a 50%, #241e17 82%, #6b5a3f 100%);

    /* An iron bracket: a solid plate with a bevel and a rivet, rotated per corner by the rules below.
       Chunky on purpose — the reference's corners are hardware holding the frame together, and two thin
       crossed lines read as a scratch rather than as a fitting. */
    --encounter-corner:
        radial-gradient(circle at 68% 68%, #d8bd7d 0 12%, #8a7448 13% 20%, transparent 21%),
        linear-gradient(135deg, #b39a63 0 38%, #7d6a48 38% 62%, #4a3d28 62% 100%);

    --encounter-banner:
        linear-gradient(180deg, #e5d6b4 0%, #d3c09a 48%, #c0aa80 100%);

    --encounter-plate:
        linear-gradient(180deg, #5c1a16 0%, #7d1f18 45%, #4a1310 100%);

    --encounter-button:
        linear-gradient(180deg, #96271d 0%, #7d1f18 55%, #571512 100%);

    position: relative;
    display: grid;
    justify-items: center;
    gap: .75rem;
    /* ⛔ BORDER-BOX, AND IT IS NOT A TIDINESS RULE (#590). This app has no global reset — box-sizing is
       set per element, where somebody remembered — so `width: min(30rem, …)` was the CONTENT width and
       the .8rem frame plus 1.5rem of side padding were added ON TOP: 4.6rem, ~74px, of card that no
       stated width accounted for. The container card had the identical fault and it bit at once, because
       its ledger has a flush-right button column that went off screen with it; this card degraded
       quietly instead, its content being centred text and one full-width button, so the overflow cost
       symmetry rather than function and nobody reported it for two releases.
       ⚠️ The cure is the one ContainerCard and ObjectCard already carry, deliberately — a third recipe
       for one fault is how this file's own comments say it acquired three kinds of fog. */
    box-sizing: border-box;
    /* ⚠️ `100%`, NOT `92vw`, which is ObjectCard's correction and it is the half that survives a narrow
       pane. The veil is a fixed grid with 1rem of padding, so 100% is the content box the card actually
       has to fit inside; a viewport-relative width does not know about that padding and hands it back.
       At 92vw the card cleared the viewport and still overflowed its own veil by 2rem, which shows up as
       a horizontal scrollbar on a modal with nothing visibly cut — invisible to the eye, exactly as the
       ~74px above was. */
    width: min(30rem, 100%);
    /* ⛔ NOT A SCROLL CONTAINER, and it was one. `max-height` + `overflow-y: auto` lived here, and the
       four corner brackets hang 0.75rem OUTSIDE the card by design — absolutely-positioned children past
       the padding box are scrollable overflow, so the card permanently overflowed itself by exactly the
       12px the brackets stick out, on both axes.
       ⚠️ It looked like a hover bug because Chrome only PAINTS overlay scrollbars while the pointer is
       over the scroll container: hovering Begin Encounter revealed bars that had been there all along.
       The scrolling belongs to the veil, which has no decoration hanging off it. */
    padding: 1.4rem 1.5rem 1.6rem;
    border: .8rem solid transparent;
    border-radius: .5rem;
    /* Two layers: the inner ground paints the padding box, the frame paints the border box. That is what
       lets one variable be the border art and another be the card's ground. */
    background:
        var(--encounter-texture) padding-box,
        var(--encounter-frame) border-box;
    box-shadow: 0 1.5rem 3rem #000000cc, inset 0 0 3rem #00000099;
    color: var(--encounter-ink);
    font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
}

/* The end card is the same frame in ash rather than blood — it is an aftermath, not an alarm. */
.encounter-card.ending[b-d5w5hy6bkr] {
    --encounter-blood: #3f4a34;
    --encounter-plate: linear-gradient(180deg, #2c3626 0%, #3f4a34 45%, #232b1e 100%);
    --encounter-button: linear-gradient(180deg, #4c5a3e 0%, #3f4a34 55%, #2a331f 100%);
}

/* The tombstone (#1439): the same frame in grave stone. Not blood, which is the alarm of a fight about
   to start, and not the ending card's ash, which is an aftermath the party walks away from.
   ⚠️ UNTUNED. Three variables picked to read as cut stone and nothing more — the palette wants Dave's
   eye before it goes anywhere near a release. → memory `a-look-is-not-a-number` */
.encounter-card.tombstone[b-d5w5hy6bkr] {
    --encounter-blood: #4a4f57;
    --encounter-plate: linear-gradient(180deg, #33383f 0%, #4a4f57 45%, #23262b 100%);
    --encounter-button: linear-gradient(180deg, #565c66 0%, #4a4f57 55%, #2f333a 100%);
}

/* ⛔ AND NOTHING MAY COVER IT. --dm-layer-blocking is reserved in app.css for "a hold that must not be
   dismissed by accident", which is precisely a death: FenReach's own death notice already sat there
   before this card replaced it (#1030), and moving it down to the ordinary dialog band would have been a
   silent demotion nobody would see until a pinned window opened over a tombstone.
   ⚠️ A TOKEN, never a number — DialogLayeringTests refuses a hand-typed z-index on a full-window veil. */
.encounter-veil.tombstone[b-d5w5hy6bkr] {
    position: fixed;
    inset: 0;
    z-index: var(--dm-layer-blocking, 1200);
}

/* ── The corners ─────────────────────────────────────────────────────────────────────────────────── */

.encounter-corner[b-d5w5hy6bkr] {
    position: absolute;
    width: 2.6rem;
    height: 2.6rem;
    /* Clipped to an L so the plate reads as a bracket wrapping the corner rather than a square tile
       sitting on it. Both arms are the same width, which is what makes it look forged rather than drawn. */
    clip-path: polygon(0 0, 100% 0, 100% 34%, 34% 34%, 34% 100%, 0 100%);
    background: var(--encounter-corner);
    background-repeat: no-repeat;
    background-size: 100% 100%;
    filter: drop-shadow(0 .1rem .15rem #000000aa);
    pointer-events: none;
}

.encounter-corner.top-left[b-d5w5hy6bkr] { top: -.75rem; left: -.75rem; }
.encounter-corner.top-right[b-d5w5hy6bkr] { top: -.75rem; right: -.75rem; transform: rotate(90deg); }
.encounter-corner.bottom-right[b-d5w5hy6bkr] { bottom: -.75rem; right: -.75rem; transform: rotate(180deg); }
.encounter-corner.bottom-left[b-d5w5hy6bkr] { bottom: -.75rem; left: -.75rem; transform: rotate(270deg); }

/* ── Heading, banner, picture ────────────────────────────────────────────────────────────────────── */

.encounter-title[b-d5w5hy6bkr] {
    width: 100%;
    margin: 0;
    padding: .45rem .8rem;
    border: 1px solid #00000066;
    border-radius: .25rem;
    background: var(--encounter-plate);
    box-shadow: inset 0 0 1.2rem #00000088;
    color: var(--encounter-ink);
    font-size: clamp(1.5rem, 5vw, 2.1rem);
    font-variant: small-caps;
    letter-spacing: .06em;
    line-height: 1.1;
    text-align: center;
    text-shadow: 0 .12rem .2rem #000000cc;
}

.encounter-banner[b-d5w5hy6bkr] {
    width: 100%;
    margin: 0;
    padding: .35rem .9rem;
    border: 1px solid #8a7551;
    border-radius: .15rem;
    background: var(--encounter-banner);
    color: #2b2117;
    font-size: 1.02rem;
    font-weight: 700;
    text-align: center;
    text-shadow: 0 .05rem 0 #ffffff88;
}

.encounter-portrait[b-d5w5hy6bkr] {
    width: 100%;
    margin: 0;
    border: .18rem solid #5a4a33;
    border-radius: .2rem;
    background: #0d0b09;
    box-shadow: inset 0 0 2rem #000000cc;
    overflow: hidden;
}

/* ⛔ `contain`, never `cover`. Generated creature portraits are SQUARE (gpt-image-2 renders
   1024x1024) and the frame is landscape, so `cover` scaled to the width and then cropped the
   overflow off the top and bottom -- which on a centred, full-body portrait takes the creature's
   head off. Dave caught it on an Orc Veteran. `contain` fits the whole image inside the frame
   whatever its aspect, so a square portrait pillarboxes and a landscape scene picture letterboxes;
   neither loses anything. The cap is 22rem rather than 16rem so a square portrait is not reduced to
   a narrow strip between two wide bars -- at the card's ~400px width, 16rem left the picture using
   barely half the frame. */
.encounter-portrait img[b-d5w5hy6bkr] {
    display: block;
    width: 100%;
    max-height: 22rem;
    object-fit: contain;
}

/* An unillustrated card, which is a real state rather than a broken image. */
.encounter-portrait.empty[b-d5w5hy6bkr] {
    min-height: 6rem;
    background:
        repeating-linear-gradient(135deg, #17130f 0 .6rem, #131009 .6rem 1.2rem);
}

/* ── The rows ────────────────────────────────────────────────────────────────────────────────────── */

.encounter-rows[b-d5w5hy6bkr] {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: baseline;
    gap: .3rem .7rem;
    width: 100%;
    margin: 0;
    padding: .7rem .9rem;
    border: 1px solid #7d6a48;
    border-radius: .2rem;
    background: var(--encounter-banner);
    color: #2b2117;
}

.encounter-note[b-d5w5hy6bkr] {
    width: 100%;
    color: var(--encounter-ink-dim);
    font-size: .88rem;
    line-height: 1.45;
}

/* ── The button ──────────────────────────────────────────────────────────────────────────────────── */

.encounter-action[b-d5w5hy6bkr] {
    width: 100%;
    margin-top: .2rem;
    padding: .7rem 1rem;
    border: 1px solid #00000077;
    border-radius: .2rem;
    background: var(--encounter-button);
    box-shadow: inset 0 .1rem 0 #ffffff22, 0 .3rem .8rem #00000088;
    color: var(--encounter-ink);
    cursor: pointer;
    font-family: inherit;
    font-size: 1.15rem;
    font-variant: small-caps;
    letter-spacing: .08em;
    text-shadow: 0 .1rem .18rem #000000aa;
}

.encounter-action:hover:not(:disabled)[b-d5w5hy6bkr] { filter: brightness(1.15); }
.encounter-action:disabled[b-d5w5hy6bkr] { cursor: default; filter: grayscale(.5) brightness(.8); }

.encounter-close[b-d5w5hy6bkr] {
    position: absolute;
    top: .35rem;
    right: .55rem;
    padding: 0 .35rem;
    border: 1px solid #7d6a48;
    border-radius: 999px;
    background: #00000055;
    color: var(--encounter-gold);
    cursor: pointer;
    font-size: 1rem;
    line-height: 1.4;
}

/* ── The veil ────────────────────────────────────────────────────────────────────────────────────── */

.encounter-veil[b-d5w5hy6bkr] {
    position: fixed;
    inset: 0;
    /* The scroll lives HERE now (see the card). `safe center` rather than plain centring: a card taller
       than the window would otherwise be centred with its top pushed off-screen and unreachable, which is
       the classic grid-centring trap and would hide the title of the very card that cannot be dismissed. */
    overflow-y: auto;
    /* ⛔ THE TOKEN, NEVER A HAND-TYPED NUMBER. A bare z-index here relates to nothing: the floating pane
       bands are assigned in dm-panes.js, and a literal 60 sat *below* a pinned window — so an
       un-ignorable card could open behind one and be neither ignorable nor visible. That is #532's exact
       question about the prompt this card replaced, and DialogLayeringTests asks it automatically. */
    z-index: var(--dm-layer-dialog, 1000);
    display: grid;
    place-items: safe center;
    padding: 1rem;
    background: #05040399;
    backdrop-filter: blur(3px);
}

@media (prefers-reduced-motion: no-preference) {
    .encounter-card[b-d5w5hy6bkr] { animation: encounter-card-in-b-d5w5hy6bkr .18s ease-out; }
    @keyframes encounter-card-in-b-d5w5hy6bkr {
        from { opacity: 0; transform: scale(.97); }
        to { opacity: 1; transform: none; }
    }
}
/* /Components/Shared/EncounterCardRow.razor.rz.scp.css */
/* Rows inside the encounter card. The card owns the parchment they sit on; this owns their rhythm. */

.encounter-row-label[b-7i7vlw0g7e] {
    display: flex;
    align-items: center;
    gap: .45rem;
    margin: 0;
    color: #4a3a26;
    font-weight: 700;
    white-space: nowrap;
}

/* The round glyph from Dave's reference. A text character rather than an asset, so it costs nothing and
   scales with the card — and it is the most obvious thing here to swap for real art later. */
.encounter-row-icon[b-7i7vlw0g7e] {
    display: grid;
    place-items: center;
    width: 1.35rem;
    height: 1.35rem;
    border-radius: 999px;
    background: #2b2117;
    color: #d8b96a;
    font-size: .8rem;
    line-height: 1;
}

.encounter-row-value[b-7i7vlw0g7e] {
    margin: 0;
    border-bottom: 1px solid #00000022;
    color: #2b2117;
}

/* ⚠️ Inherits the CARD's accent rather than naming a colour. The ending card recolours itself to ash,
   and a hardcoded blood red here put a bright red number on it — the one row meant to draw the eye,
   drawing it to the wrong palette. Custom properties cross a component boundary even though scoped CSS
   does not, which is what makes this work from a separate stylesheet. */
.encounter-row-value.emphasis[b-7i7vlw0g7e] {
    color: var(--encounter-blood, #7d1f18);
    font-weight: 700;
}
/* /Components/Shared/FightActions.razor.rz.scp.css */
/*
    The FenReach Actions popup (#1368).

    ⭐⭐ AUTHORED AT 390px FIRST, in the fight band's own language — Georgia on marsh-dusk glass, #dcb763 gold, the
    party's green and the foe's rust as left borders — because Dave, 2026-09-17: "I like the way ours looks right now
    and I do not want to destroy that aesthetic." On a phone it is its own full screen; from 40rem up it is a card
    centred over the page. Nobody has chosen this look beyond the band's, which it copies. → memory `a-look-is-not-a-number`
*/

.actions-veil[b-t3k4njx2xl] {
    position: fixed;
    inset: 0;
    z-index: var(--dm-layer-dialog, 1000);
    display: flex;
    align-items: stretch;
    justify-content: center;
    background: #05070dcc;
}

.actions-sheet[b-t3k4njx2xl] {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    width: 100%;
    height: 100%;
    background: #0d1220f2;
    color: #d9deea;
    font-family: Georgia, 'Times New Roman', serif;
}

/* --- head ----------------------------------------------------------------------------------- */

.actions-head[b-t3k4njx2xl] {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .6rem .75rem;
    border-bottom: 1px solid #dcb76340;
}

.actions-title[b-t3k4njx2xl] {
    flex: 1 1 auto;
    min-width: 0;
    font-size: .95rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.actions-budget[b-t3k4njx2xl] {
    flex: 0 0 auto;
    padding: .1rem .35rem;
    border-radius: 5px;
    border: 1px solid #dcb76355;
    color: #dcb763;
    font-size: .75rem;
    font-weight: 700;
}

.actions-budget em[b-t3k4njx2xl] {
    font-style: normal;
    font-size: .6rem;
    font-weight: 400;
    color: #8d97a8;
    margin-left: .1rem;
}

.actions-back[b-t3k4njx2xl],
.actions-close[b-t3k4njx2xl] {
    flex: 0 0 auto;
    width: 2rem;
    height: 2rem;
    padding: 0;
    border-radius: 6px;
    border: 1px solid #ffffff22;
    background: transparent;
    color: #dcb763;
    font: inherit;
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
}

/* --- body ----------------------------------------------------------------------------------- */

.actions-body[b-t3k4njx2xl] {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: .6rem .75rem;
}

.actions-hint[b-t3k4njx2xl] {
    margin: 0 0 .5rem;
    font-size: .78rem;
    color: #7d879a;
}

/* #1407: a condition the actor can act on, first. Set off from what follows by the band's gold, nothing new. */
.actions-recovery[b-t3k4njx2xl] {
    margin: 0 0 .75rem;
    padding: 0 0 .6rem;
    border-bottom: 1px solid #dcb76340;
}

.actions-people[b-t3k4njx2xl],
.actions-list[b-t3k4njx2xl] {
    list-style: none;
    margin: 0;
    padding: 0;
}

.actions-people > li + li[b-t3k4njx2xl],
.actions-list > li + li[b-t3k4njx2xl] { margin-top: .3rem; }

.person[b-t3k4njx2xl] {
    box-sizing: border-box;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .5rem;
    width: 100%;
    padding: .55rem .6rem;
    border-radius: 7px;
    border: 1px solid #ffffff14;
    border-left: 3px solid #6f7a8d;
    background: #ffffff08;
    color: #e6ebf5;
    font: inherit;
    font-size: .85rem;
    text-align: left;
    cursor: pointer;
}

/* The side is the border, exactly as on the band. */
.person.party[b-t3k4njx2xl] { border-left-color: #6fae7a; }
.person.foe[b-t3k4njx2xl] { border-left-color: #c2705f; }
.person.you[b-t3k4njx2xl] { border-left-color: #dcb763; background: #dcb7630f; }
.person.down[b-t3k4njx2xl] { opacity: .6; }

.person-name[b-t3k4njx2xl] { font-weight: 700; }

.person-where[b-t3k4njx2xl] { color: #8d97a8; font-size: .72rem; }

.actions-target[b-t3k4njx2xl] {
    display: flex;
    align-items: baseline;
    gap: .45rem;
    margin: 0 0 .4rem;
    font-size: .78rem;
    color: #8d97a8;
}

.side[b-t3k4njx2xl] {
    font-size: .62rem;
    letter-spacing: .06em;
    text-transform: uppercase;
}

.side.party[b-t3k4njx2xl] { color: #6fae7a; }
.side.foe[b-t3k4njx2xl] { color: #c2705f; }

.actions-group[b-t3k4njx2xl] {
    margin: .7rem 0 .35rem;
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: #dcb763;
}

.action-row[b-t3k4njx2xl] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .3rem .5rem;
    padding: .45rem .55rem;
    border-radius: 7px;
    border: 1px solid #ffffff14;
    background: #ffffff08;
    font-size: .82rem;
}

/* ⛔ Greyed, never hidden (#1368 point 9): a row the engine refuses stays on screen with the engine's sentence. */
.action-row.greyed[b-t3k4njx2xl] { opacity: .55; }

.row-name[b-t3k4njx2xl] {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: .35rem;
    min-width: 0;
}

.glyph[b-t3k4njx2xl] { color: #dcb763; }

.row-note[b-t3k4njx2xl] {
    font-style: normal;
    font-size: .68rem;
    color: #8d97a8;
}

.why[b-t3k4njx2xl] {
    flex: 1 1 100%;
    font-size: .7rem;
    color: #b8c0cf;
}

.provoke[b-t3k4njx2xl] {
    margin: 0 0 .35rem;
    padding: .3rem .5rem;
    border-radius: 6px;
    border: 1px solid #d98f5a66;
    background: #d98f5a14;
    color: #f0c9a8;
    font-size: .74rem;
}

.repeat-row[b-t3k4njx2xl] { display: flex; flex-wrap: wrap; gap: .3rem; }

.repeat[b-t3k4njx2xl],
.go[b-t3k4njx2xl],
.more[b-t3k4njx2xl],
.toggle[b-t3k4njx2xl],
.pager button[b-t3k4njx2xl],
.end[b-t3k4njx2xl] {
    font: inherit;
    font-size: .78rem;
    font-weight: 700;
    min-height: 2.1rem;
    padding: .3rem .7rem;
    border-radius: 6px;
    border: 1px solid #dcb763;
    background: transparent;
    color: #dcb763;
    cursor: pointer;
    white-space: nowrap;
}

.repeat:disabled[b-t3k4njx2xl],
.go:disabled[b-t3k4njx2xl],
.more:disabled[b-t3k4njx2xl],
.pager button:disabled[b-t3k4njx2xl],
.end:disabled[b-t3k4njx2xl] { opacity: .5; cursor: not-allowed; }

.actions-more[b-t3k4njx2xl] {
    display: flex;
    gap: .5rem;
    margin-top: .8rem;
}

.more[b-t3k4njx2xl] { flex: 1 1 0; }

.more em[b-t3k4njx2xl] {
    font-style: normal;
    font-size: .68rem;
    color: #8d97a8;
    margin-left: .2rem;
}

/* --- the searchable lists ------------------------------------------------------------------- */

/* ⭐ The search is PINNED on a phone: the list scrolls under it, so a long spellbook never scrolls the box away. */
.actions-search[b-t3k4njx2xl] {
    position: sticky;
    top: -.6rem;
    z-index: 1;
    display: flex;
    gap: .4rem;
    margin: -.6rem -.75rem .5rem;
    padding: .6rem .75rem;
    background: #0d1220;
}

.actions-search input[b-t3k4njx2xl] {
    flex: 1 1 auto;
    min-width: 0;
    padding: .45rem .6rem;
    border-radius: 6px;
    border: 1px solid #ffffff22;
    background: #ffffff0a;
    color: #e6ebf5;
    font: inherit;
    font-size: .85rem;
}

.toggle[aria-pressed="true"][b-t3k4njx2xl] { background: #dcb763; color: #1a1408; }

.last-used[b-t3k4njx2xl] { margin-bottom: .5rem; }

/* On a phone every row is on screen and the list scrolls; the pager is a desktop control. */
.pager[b-t3k4njx2xl] { display: none; }

/* --- foot ----------------------------------------------------------------------------------- */

.actions-foot[b-t3k4njx2xl] {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .6rem .75rem;
    border-top: 1px solid #dcb76340;
}

.feedback[b-t3k4njx2xl] {
    flex: 1 1 auto;
    margin: 0;
    font-size: .74rem;
    color: #f0b3ae;
}

.end[b-t3k4njx2xl] {
    margin-left: auto;
    background: #dcb763;
    color: #1a1408;
}

/* --- the desktop accommodation -------------------------------------------------------------- */

@media (min-width: 40rem) {
    .actions-veil[b-t3k4njx2xl] { align-items: center; padding: 1.5rem; }

    .actions-sheet[b-t3k4njx2xl] {
        width: min(38rem, 100%);
        height: auto;
        max-height: 85vh;
        border: 1px solid #dcb76340;
        border-radius: 10px;
    }

    /* Paged on a desktop: only the current page's rows are drawn, and the pager moves between them. */
    .action-row.off-page[b-t3k4njx2xl] { display: none; }

    .pager[b-t3k4njx2xl] {
        display: flex;
        align-items: center;
        justify-content: center;
        gap: .75rem;
        margin-top: .5rem;
        font-size: .75rem;
        color: #8d97a8;
    }
}
/* /Components/Shared/FightBand.razor.rz.scp.css */
/*
    The FenReach fight band (#1025).

    ⭐⭐ AUTHORED AT 390px FIRST, and that is not a preference. `FenReach.razor.css` opens by declaring
    "Mobile-first on purpose — the product is a phone game, so the narrow layout is the real one and the
    desktop view is the accommodation rather than the other way round." A fight display authored on a
    desktop and squeezed afterwards is a desktop panel somebody has to redesign for the native clients.

    ⚠️ The palette is FenReach's — Georgia serif on marsh-dusk, #dcb763 gold, #0d1220 glass — lifted from
    `.fen-offer`, which is the card this one replaces. Nobody has chosen this look.
    → memory `a-look-is-not-a-number`
*/

.fight-band[b-iqwwu07oh7] {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    gap: .45rem;
    flex: 0 0 auto;
    width: min(92rem, calc(100% - 5rem));
    margin-inline: auto;
    padding: .5rem .75rem;
    border: 1px solid #dcb76340;
    border-radius: 10px;
    background: #0d122099;
    color: #d9deea;
    font-family: Georgia, 'Times New Roman', serif;
}

/* --- the head row, present in all three states ------------------------------------------------ */

/* ⛔ ONE LINE AT 390px. Collapsed is meant to be SMALLER than the single-sentence card it replaces
   (~34px against ~78px) while saying more, so nothing in here may be allowed to wrap by default: the
   name is the only flexible part and it truncates. */
.band-head[b-iqwwu07oh7] {
    display: flex;
    align-items: center;
    gap: .4rem;
    min-width: 0;
}

.band-round[b-iqwwu07oh7] {
    flex: 0 0 auto;
    font-size: .7rem;
    font-weight: 700;
    letter-spacing: .06em;
    color: #dcb763;
}

.band-whose[b-iqwwu07oh7] {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: baseline;
    gap: .35rem;
    overflow: hidden;
}

.band-whose strong[b-iqwwu07oh7] {
    font-size: .85rem;
    font-weight: 700;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.band-whose em[b-iqwwu07oh7] {
    flex: 0 0 auto;
    font-style: normal;
    font-size: .68rem;
    color: #7d879a;
}

/* ⚠️ The pips are the ACTIVE actor's, never the viewer's — see the component. They stay in all three
   states because no state may leave a player unable to take a PF2e turn correctly. */
.band-budget[b-iqwwu07oh7] {
    flex: 0 0 auto;
    display: flex;
    gap: .25rem;
}

.band-budget .pip[b-iqwwu07oh7] {
    display: inline-flex;
    align-items: baseline;
    gap: .1rem;
    padding: .1rem .3rem;
    border-radius: 5px;
    border: 1px solid #ffffff1f;
    font-size: .72rem;
    font-weight: 700;
    line-height: 1.2;
}

.band-budget .pip em[b-iqwwu07oh7] {
    font-style: normal;
    font-size: .58rem;
    font-weight: 400;
    color: #8d97a8;
}

.band-budget .actions[b-iqwwu07oh7] { color: #dcb763; border-color: #dcb76355; }
/* ⛔ Its OWN pip. `ExtraActionsRemaining` may only be spent on a restricted list (#831), so folding it
   into the action count would contradict every gate in the engine. */
.band-budget .extra[b-iqwwu07oh7] { color: #8fd3c4; border-color: #8fd3c455; }
.band-budget .reactions[b-iqwwu07oh7] { color: #9fb6e8; border-color: #9fb6e855; }
.band-budget .movement[b-iqwwu07oh7] { color: #b8c0cf; }

.band-end[b-iqwwu07oh7] {
    flex: 0 0 auto;
    font: inherit;
    font-size: .72rem;
    font-weight: 700;
    padding: .25rem .5rem;
    border-radius: 6px;
    border: 1px solid #dcb763;
    background: #dcb763;
    color: #1a1408;
    cursor: pointer;
    white-space: nowrap;
}

.band-end:disabled[b-iqwwu07oh7] { opacity: .5; cursor: not-allowed; }

.band-toggle[b-iqwwu07oh7] {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.55rem;
    height: 1.55rem;
    padding: 0;
    border-radius: 6px;
    border: 1px solid #ffffff22;
    background: transparent;
    color: #dcb763;
    cursor: pointer;
}

.band-toggle svg[b-iqwwu07oh7] {
    width: .9rem;
    height: .9rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* --- the bodies ------------------------------------------------------------------------------- */

.band-hint[b-iqwwu07oh7] {
    margin: 0 0 .35rem;
    font-size: .74rem;
    color: #7d879a;
}

/* ⚠️ Colours matched by eye to the band's own amber accents; not measured. → memory `a-look-is-not-a-number` */
.band-notice[b-iqwwu07oh7] {
    margin: .35rem 0 .15rem;
    padding: .35rem .6rem;
    border-left: 3px solid #d6a84a;
    border-radius: .25rem;
    background: #d6a84a1f;
    color: #eadbb4;
    font-size: .8rem;
    line-height: 1.35;
}

.chip-row[b-iqwwu07oh7],
.roster-rows[b-iqwwu07oh7] {
    list-style: none;
    margin: 0;
    padding: 0;
}

.chip-row[b-iqwwu07oh7] {
    display: flex;
    flex-wrap: wrap;
    gap: .3rem;
}

.chip-row > li[b-iqwwu07oh7] {
    display: flex;
    align-items: center;
    gap: .3rem;
    padding: .2rem .4rem;
    border-radius: 7px;
    border: 1px solid #ffffff14;
    border-left: 3px solid #6f7a8d;
    background: #ffffff08;
    font-size: .74rem;
}

.roster-rows > li[b-iqwwu07oh7] {
    display: grid;
    grid-template-columns: 1.4rem minmax(0, 1fr) auto;
    align-items: center;
    gap: .2rem .45rem;
    padding: .3rem .4rem;
    border-radius: 7px;
    border-left: 3px solid #6f7a8d;
    background: #ffffff08;
    font-size: .78rem;
}

.roster-rows > li + li[b-iqwwu07oh7] { margin-top: .25rem; }

/* The side is the border, as it is on /play: a word repeating it is pure height. A FOE still says so in
   the roster, because that is the reading a player actually needs mid-fight. */
.chip-row > li.party[b-iqwwu07oh7],
.roster-rows > li.party[b-iqwwu07oh7] { border-left-color: #6fae7a; }

.chip-row > li.foe[b-iqwwu07oh7],
.roster-rows > li.foe[b-iqwwu07oh7] { border-left-color: #c2705f; }

.chip-row > li.you[b-iqwwu07oh7],
.roster-rows > li.you[b-iqwwu07oh7] { border-left-color: #dcb763; background: #dcb7630f; }

.chip-row > li.down[b-iqwwu07oh7],
.roster-rows > li.down[b-iqwwu07oh7] { opacity: .6; }

.chip-name[b-iqwwu07oh7] {
    font: inherit;
    font-weight: 700;
    padding: 0;
    border: 0;
    background: none;
    color: #e6ebf5;
    text-align: left;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

button.chip-name[b-iqwwu07oh7] { cursor: pointer; text-decoration: underline dotted #ffffff33; }

.chip-where[b-iqwwu07oh7],
.row-where[b-iqwwu07oh7] { color: #8d97a8; font-size: .7rem; }

.row-init[b-iqwwu07oh7] { color: #7d879a; font-size: .7rem; text-align: right; }

.row-who[b-iqwwu07oh7] { display: flex; align-items: baseline; gap: .3rem; min-width: 0; }

.row-side[b-iqwwu07oh7] {
    font-size: .62rem;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: #c2705f;
}

.row-where[b-iqwwu07oh7] { grid-column: 2; }

.row-vitals[b-iqwwu07oh7] { display: flex; align-items: center; gap: .35rem; }

.row-ac[b-iqwwu07oh7] { color: #8d97a8; font-size: .7rem; }

.vitals[b-iqwwu07oh7] { display: inline-flex; align-items: center; gap: .3rem; }

.hp[b-iqwwu07oh7] { font-size: .72rem; font-variant-numeric: tabular-nums; }

/* ⛔ A WORD, NOT A NUMBER, for a foe. /play prints 12 / 24 for everybody because it has a Dungeon
   Master's seat; this surface has none, so a foe's exact hit points here would be a leak. */
.hp.word[b-iqwwu07oh7] { color: #c9d0dd; font-size: .7rem; }

.temp-hp[b-iqwwu07oh7] { color: #8fd3c4; font-size: .66rem; }

.hp-bar[b-iqwwu07oh7] {
    display: inline-block;
    width: 2.4rem;
    height: .32rem;
    border-radius: 999px;
    background: #ffffff1a;
    overflow: hidden;
}

.hp-bar > span[b-iqwwu07oh7] { display: block; height: 100%; background: #6f7a8d; }

/* Coloured by how hurt they are rather than by which side they are on. The flash is reserved for the
   band where somebody is about to go down — a bar that flashes at half health is one a player learns to
   ignore. The bands are FightReadings'; nothing here invents a threshold. */
.hp-bar.hale > span[b-iqwwu07oh7] { background: #6fae7a; }
.hp-bar.hurt > span[b-iqwwu07oh7] { background: #d8c16a; }
.hp-bar.bloodied > span[b-iqwwu07oh7] { background: #d98f5a; }
.hp-bar.dire > span[b-iqwwu07oh7] { background: #d9615a; animation: band-dire-b-iqwwu07oh7 1s ease-in-out infinite alternate; }

@keyframes band-dire-b-iqwwu07oh7 {
    from { opacity: 1; }
    to { opacity: .45; }
}

@media (prefers-reduced-motion: reduce) {
    .hp-bar.dire > span[b-iqwwu07oh7] { animation: none; }
}

.marks[b-iqwwu07oh7] { display: inline-flex; flex-wrap: wrap; gap: .2rem; }

.roster-rows .marks[b-iqwwu07oh7] { grid-column: 2 / -1; }

.mark[b-iqwwu07oh7] {
    padding: .05rem .25rem;
    border-radius: 4px;
    background: #ffffff12;
    color: #b8c0cf;
    font-size: .62rem;
}

.mark.down[b-iqwwu07oh7] { background: #d9615a33; color: #f0b3ae; }

/* --- the three states, and the one the viewport picks ----------------------------------------- */

/* ⛔ COLLAPSED IS NOT "OFF". Only the BODY is switched here; the head row above — round, whose turn, the
   pips, the reaction, the movement, the Actions door (End turn until #1368) and the toggle — is outside every one of these rules and is
   present in all three states. Dave, 2026-09-16: "leave the new expand toggle button and the other info
   on its same plane still visible." */
.fight-band[data-view="band"] .band-roster[b-iqwwu07oh7],
.fight-band[data-view="roster"] .band-chips[b-iqwwu07oh7],
.fight-band[data-view="collapsed"] .band-chips[b-iqwwu07oh7],
.fight-band[data-view="collapsed"] .band-roster[b-iqwwu07oh7] { display: none; }

/* ⭐ `by-width` is the un-chosen case: band on a phone, roster on a desktop. Doing it HERE rather than by
   measuring the window on the server is what makes the default cost nothing and never flash — and once
   the player presses the toggle, their choice wins at every width. */
.fight-band[data-view="by-width"] .band-roster[b-iqwwu07oh7] { display: none; }

/* Arrowheads OUT when the next press shows more, IN when it shows less. Cycle: band → roster →
   collapsed → band. Both glyphs are in the markup so the un-chosen case can be answered by the same
   media query that answers the body. */
.fight-band .glyph-in[b-iqwwu07oh7] { display: none; }
.fight-band[data-view="roster"] .glyph-out[b-iqwwu07oh7] { display: none; }
.fight-band[data-view="roster"] .glyph-in[b-iqwwu07oh7] { display: inline; }

@media (min-width: 60rem) {
    .fight-band[data-view="by-width"] .band-chips[b-iqwwu07oh7] { display: none; }
    .fight-band[data-view="by-width"] .band-roster[b-iqwwu07oh7] { display: block; }
    .fight-band[data-view="by-width"] .glyph-out[b-iqwwu07oh7] { display: none; }
    .fight-band[data-view="by-width"] .glyph-in[b-iqwwu07oh7] { display: inline; }
}
/* /Components/Shared/ForgeDialog.razor.rz.scp.css */
/* Chromeless mode (ForgeDialogChrome.None). The wrapper must generate NO box: the play surfaces it hosts
   position themselves against the viewport, and a wrapper with a box would become their containing block
   and move them. display:contents leaves the caller's layout exactly as it was while keeping a real node
   to hang the keydown and the focus trap on. */
.forge-dialog-bare[b-xum0sl9xjs] {
    display: contents;
}

.forge-dialog-veil[b-xum0sl9xjs] {
    position: fixed;
    inset: 0;
    z-index: var(--dm-layer-dialog, 1000);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    background: rgba(6, 9, 18, .72);
}

.forge-dialog[b-xum0sl9xjs] {
    width: min(46rem, 100%);
    /* Capped rather than sized: a dialog with ten fields must scroll inside itself rather than run off
       the bottom of a laptop screen and take its confirm button with it. */
    max-height: min(85vh, 52rem);
    display: flex;
    flex-direction: column;
    border: 1px solid #43537c;
    border-radius: 14px;
    background: #131a2c;
    box-shadow: 0 18px 48px rgba(0, 0, 0, .55);
}

.forge-dialog:focus[b-xum0sl9xjs] {
    outline: none;
}

.forge-dialog-head[b-xum0sl9xjs] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: .9rem 1.1rem;
    border-bottom: 1px solid #2a3450;
}

.forge-dialog-head h2[b-xum0sl9xjs] {
    margin: 0;
    font-family: Georgia, serif;
    font-size: 1rem;
    color: #faf7ef;
}

.forge-dialog-close[b-xum0sl9xjs] {
    border: none;
    background: none;
    color: #8e9ab8;
    font-size: 1.3rem;
    line-height: 1;
    cursor: pointer;
}

.forge-dialog-close:hover[b-xum0sl9xjs] {
    color: #faf7ef;
}

.forge-dialog-description[b-xum0sl9xjs] {
    margin: 0;
    padding: .7rem 1.1rem 0;
    font-size: .78rem;
    line-height: 1.5;
    color: #8e9ab8;
}

.forge-dialog-body[b-xum0sl9xjs] {
    padding: 1rem 1.1rem;
    overflow-y: auto;
    display: grid;
    gap: .7rem;
}

.forge-dialog-foot[b-xum0sl9xjs] {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: .6rem;
    padding: .8rem 1.1rem;
    border-top: 1px solid #2a3450;
}

.forge-dialog-problem[b-xum0sl9xjs] {
    margin: 0 auto 0 0;
    font-size: .74rem;
    color: #e5877a;
}
/* /Components/Shared/ForgeFieldErrors.razor.rz.scp.css */
.forge-field-errors[b-3azklqi4h2] { margin: .25rem 0 0; padding-left: 1.15rem; color: #ff9c87; font-size: .85rem; }
/* /Components/Shared/HeroPointOffer.razor.rz.scp.css */
/* The Hero Point question (#1519). ⚠️ Colours borrowed from FenReach's chrome and the bug report window; nobody has
   chosen this look. → memory `a-look-is-not-a-number` */
.hero-veil[b-8u6dbs6w9y] {
    position: fixed;
    inset: 0;
    z-index: var(--dm-layer-dialog);
    display: grid;
    place-items: center;
    padding: 1rem;
    background: #05080fcc;
}

.hero-offer[b-8u6dbs6w9y] {
    width: min(30rem, 100%);
    box-sizing: border-box;
    padding: 1rem 1.25rem;
    border: 1px solid #dcb76359;
    border-radius: .5rem;
    background: #0d1220;
    color: #e6e1d3;
    box-shadow: 0 1rem 3rem #000a;
}

.hero-offer-death[b-8u6dbs6w9y] { border-color: #c0504d99; }

.hero-offer h2[b-8u6dbs6w9y] { margin: 0 0 .5rem; font-size: 1.15rem; color: #f0e2bd; }

.hero-what[b-8u6dbs6w9y] { margin: 0 0 .5rem; font-weight: 600; overflow-wrap: anywhere; }

.hero-rule[b-8u6dbs6w9y] { margin: 0 0 1rem; font-size: .9rem; color: #b9b3a3; }

.hero-actions[b-8u6dbs6w9y] {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: .5rem;
}

.hero-actions button[b-8u6dbs6w9y] {
    padding: .45rem .9rem;
    border: 1px solid #dcb76359;
    border-radius: .3rem;
    background: #151c2e;
    color: #e6e1d3;
    cursor: pointer;
}

.hero-actions .hero-spend[b-8u6dbs6w9y] { background: #6b5320; border-color: #dcb763; color: #fff6dc; }
/* /Components/Shared/HeroPointReroll.razor.rz.scp.css */
/* The "Use hero point" list (#1526). ⚠️ Colours borrowed from HeroPointOffer.razor.css (itself FenReach's chrome);
   nobody has chosen this look. → memory `a-look-is-not-a-number` */
.reroll-veil[b-vg5mbfhc8f] {
    position: fixed;
    inset: 0;
    z-index: var(--dm-layer-dialog);
    display: grid;
    place-items: center;
    padding: 1rem;
    background: #05080fcc;
}

.reroll-panel[b-vg5mbfhc8f] {
    width: min(34rem, 100%);
    max-height: calc(100vh - 2rem);
    overflow-y: auto;
    box-sizing: border-box;
    padding: 1rem 1.25rem;
    border: 1px solid #dcb76359;
    border-radius: .5rem;
    background: #0d1220;
    color: #e6e1d3;
    box-shadow: 0 1rem 3rem #000a;
}

.reroll-head[b-vg5mbfhc8f] { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }

.reroll-head h2[b-vg5mbfhc8f] { margin: 0; font-size: 1.15rem; color: #f0e2bd; }

.reroll-close[b-vg5mbfhc8f] {
    padding: .1rem .5rem;
    border: 1px solid #dcb76359;
    border-radius: .3rem;
    background: #151c2e;
    color: #e6e1d3;
    cursor: pointer;
}

.reroll-rule[b-vg5mbfhc8f] { margin: .5rem 0 1rem; font-size: .9rem; color: #b9b3a3; }

.reroll-hero h3[b-vg5mbfhc8f] { margin: 0 0 .4rem; font-size: 1rem; color: #f0e2bd; }

.reroll-points[b-vg5mbfhc8f] { margin-left: .4rem; font-size: .8rem; font-weight: 400; color: #dcb763; }

.reroll-list[b-vg5mbfhc8f] { margin: 0 0 1rem; padding: 0; list-style: none; display: grid; gap: .5rem; }

.reroll-pick[b-vg5mbfhc8f] {
    width: 100%;
    padding: .5rem .75rem;
    border: 1px solid #dcb76359;
    border-radius: .3rem;
    background: #151c2e;
    color: #e6e1d3;
    text-align: left;
    overflow-wrap: anywhere;
    cursor: pointer;
}

.reroll-open .reroll-pick[b-vg5mbfhc8f] { background: #6b5320; border-color: #dcb763; color: #fff6dc; }

.reroll-pick:disabled[b-vg5mbfhc8f] { cursor: not-allowed; opacity: .6; }

.reroll-why[b-vg5mbfhc8f] { margin: .25rem 0 0; font-size: .8rem; color: #b9b3a3; }

.reroll-none[b-vg5mbfhc8f] { margin: 0 0 1rem; font-size: .9rem; color: #b9b3a3; }

.reroll-message[b-vg5mbfhc8f] { margin: 0; font-size: .9rem; color: #e8a09e; }
/* /Components/Shared/HintListEditor.razor.rz.scp.css */
/* What a hint says on its row (issue #135). The list and the dialog belong to DraftListEditor and are
   styled there; only the three spans this editor writes into the row are left here.

   ⚠️ Scoped to THIS component even though the markup renders inside DraftListEditor's rows: a
   RenderFragment carries the scope id of the component that AUTHORED it, not the one that renders it. */

/* The tier reads as a label rather than as prose, so the eye can sort a list of hints by how much each
   one gives away without reading any of them. */
.hint-tier[b-y1sroew1zv] {
    flex: none;
    padding: .05rem .35rem;
    border-radius: 999px;
    border: 1px solid #443a2c;
    background: #15120e;
    color: #e6b85c;
    font-size: .66rem;
    white-space: nowrap;
}

/* The hint itself takes the slack and truncates: a hint is a sentence and the row is a line.
   min-width:0 is what actually lets it shrink — a flex item defaults to min-content. */
.hint-said[b-y1sroew1zv] {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* What it costs to hear it, hard right beside the buttons. */
.hint-earned[b-y1sroew1zv] {
    flex: none;
    color: #8f8372;
    font-size: .72rem;
}
/* /Components/Shared/ImageAttachEditor.razor.rz.scp.css */
.image-attach[b-7rjp6gxik3] {
    display: grid;
    gap: .45rem;
    padding: .7rem;
    border: 1px solid #303b57;
    border-radius: 10px;
}

.image-attach-head[b-7rjp6gxik3] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
}

.image-attach-label[b-7rjp6gxik3] {
    font-size: .72rem;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: #8e9ab8;
}

.image-attach-preview[b-7rjp6gxik3] {
    margin: 0;
    line-height: 0;
}

.image-attach-preview img[b-7rjp6gxik3] {
    display: block;
    width: 100%;
    max-height: 14rem;
    object-fit: cover;
    border-radius: 8px;
}

.image-attach-note[b-7rjp6gxik3] {
    margin: 0;
    font-size: .72rem;
    color: #8fbf8f;
}

.image-attach-note.failed[b-7rjp6gxik3] {
    color: #e5877a;
}

/* The generate affordance (Dave, 2026-09-03). Sits under the file input because drawing one is the
   second thing you reach for, after finding one you already have. */
.image-attach-generate[b-7rjp6gxik3] { margin-top: 0.5rem; display: flex; flex-direction: column; gap: 0.4rem; }
.image-attach-generate-actions[b-7rjp6gxik3] { display: flex; gap: 0.4rem; }
/* /Components/Shared/InventoryItemEditor.razor.rz.scp.css */
/* The inventory editor's own chrome (issue #135).

   The list and the dialog are DraftListEditor's and are styled over there. What is left here is the one
   thing this editor still adds: the id shown on the row.

   ⭐ The advisory and the "showing N of M" note left with the markup under #554 — they are ItemPicker's
   now, rules and elements together. Leaving the rules behind would have been the #233 shape (a rule
   whose target element the component never renders) AND would have handed the new component the fetch
   with none of the styling, because scoped CSS never crosses components.

   ⚠️ Scoped to THIS component even though the markup renders inside DraftListEditor's rows and dialog.
   That is correct, and it is the trap this repo has hit four times in the other direction: a
   RenderFragment carries the scope id of the component that AUTHORED it, not the one that renders it. */

.inventory-id[b-r3mj1pd3am] {
    flex: none;
    padding: .05rem .3rem;
    border-radius: 4px;
    background: #0b0f1a;
    color: #8e9ab8;
    font-size: .68rem;
}

/* /Components/Shared/ItemPicker.razor.rz.scp.css */
/* The item-id box and the two things it says about what it found (issue #554).

   ⚠️ Scoped to THIS component, which is the point of extracting it: the advisory and the "showing N of
   M" note used to live in InventoryItemEditor.razor.css, and a second host copying the markup would have
   got the fetch AND none of the styling — scoped CSS never crosses components. Moving the markup and the
   rules together is what stops that. */

.item-picker[b-titgyv581n] {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

/* ⛔ AMBER, NEVER RED, and these are CreatureModifierEditor's and CreaturePicker's colours on purpose —
   it is the same advisory about the same kind of value. An id this Forge cannot resolve is ALLOWED:
   another pack may define it, nothing refuses to save, and a red field would say the opposite of what
   the note itself says. */
.item-picker-unknown[b-titgyv581n] {
    margin: 0;
    padding: 0.35rem 0.55rem;
    border: 1px solid var(--forge-border-quiet);
    border-radius: 4px;
    background: var(--forge-surface-notice);
    color: var(--forge-text-muted);
    font-size: 0.78rem;
}

.item-picker-unknown code[b-titgyv581n] {
    color: var(--forge-text-input);
}

/* Which pack the item came from, once the id could only mean one (issue #629). CreaturePicker's own
   `.creature-picker-pack` values, because it is the same line saying the same thing. */
.item-picker-pack[b-titgyv581n] {
    margin: 0;
    color: var(--forge-text-muted);
    font-size: 0.8rem;
}

/* How much of the corpus is on screen. Quiet, because it is a fact rather than a problem. */
.item-picker-note[b-titgyv581n] {
    margin: 0;
    color: var(--forge-text-dim);
    font-size: 0.78rem;
}
/* /Components/Shared/LevelUpPanel.razor.rz.scp.css */
/* The level-up panel's own stylesheet, created 2026-07-31.

   IT HAD NONE. Every rule for this component lived in Play.razor.css without ::deep, so the build
   emitted them scoped to Play's hash — `.level-up-panel .xp-row[b-fdu3vypdan]` — where they could never
   match markup rendered inside this component. The XP bar, the chips, the fieldsets and the buttons had
   never been styled at any point. Confirmed by reading the built bundle rather than the source, which is
   the only way to be sure about scoped CSS.

   Fifth time this repo has been bitten by scoped CSS not crossing a component boundary. The rule that
   keeps coming out of it: a component that has markup owns a stylesheet, or its styles are somebody
   else's dead code. Colours match /play's cool palette, since this panel lives there. */

.level-up-panel[b-f8b5i38lhg] { display: grid; gap: .1rem; }

/* ── Experience ─────────────────────────────────────────────────────────────────────────────────── */

.xp-row[b-f8b5i38lhg] { display: flex; align-items: baseline; gap: .5rem; font-size: .78rem; color: #c3cbe6; }
.xp-row strong[b-f8b5i38lhg] { font-size: .95rem; color: #f0cb75; }
.xp-remaining[b-f8b5i38lhg] { margin-left: auto; font-size: .7rem; color: #8e9ab8; }
.xp-remaining.ready[b-f8b5i38lhg] { color: #80e3ae; }
.xp-track[b-f8b5i38lhg] { height: .5rem; background: #2a3144; border-radius: 10px; overflow: hidden; margin: .45rem 0 .6rem; }
.xp-track span[b-f8b5i38lhg] { display: block; height: 100%; border-radius: 10px; background: linear-gradient(90deg, #c69446, #f0cb75); }

.level-up-action[b-f8b5i38lhg] { background: linear-gradient(135deg, #c69446, #e8c36a); color: #16110a;
    border-color: #e8c36a; font-weight: 800; text-align: center; }
.level-up-action:disabled[b-f8b5i38lhg] { background: #2a3144; color: #7f8aa6; border-color: #3c4a68; }
.thin-action.secondary[b-f8b5i38lhg] { background: #1a2133; color: #aab5cf; }

/* ── The wizard ─────────────────────────────────────────────────────────────────────────────────── */

.level-up-wizard[b-f8b5i38lhg] { display: grid; gap: .7rem; margin-top: .8rem; padding-top: .8rem;
    border-top: 1px solid #303b57; }

/* A map of what is still coming. Long forms are bearable when you can see the end of them. */
.wizard-rail[b-f8b5i38lhg] { display: flex; flex-wrap: wrap; gap: .3rem; margin: 0; padding: 0; list-style: none; }
.rail-step button[b-f8b5i38lhg] { border: 1px solid #3c4a68; border-radius: 999px; background: #1a2133; color: #8e9ab8;
    padding: .18rem .55rem; font-size: .66rem; font-weight: 700; letter-spacing: .04em;
    text-transform: uppercase; cursor: pointer; }
.rail-step.done button[b-f8b5i38lhg] { border-color: #4d6a4f; background: #18241b; color: #9fd0a6; cursor: pointer; }
.rail-step.current button[b-f8b5i38lhg] { border-color: #e8c36a; background: #3a2f18; color: #f0cb75; }
.rail-step button:disabled[b-f8b5i38lhg] { cursor: default; opacity: .75; }

.wizard-step[b-f8b5i38lhg] { display: grid; gap: .4rem; }
.wizard-step h4[b-f8b5i38lhg] { margin: 0; font-family: Georgia, serif; font-size: .95rem; color: #f0e6d4; }
.wizard-hint[b-f8b5i38lhg] { margin: 0; font-size: .72rem; line-height: 1.5; color: #9ca8c5; }
.wizard-tally[b-f8b5i38lhg] { margin: .1rem 0 0; font-size: .68rem; color: #8e9ab8; }
.wizard-picked[b-f8b5i38lhg] { color: #f0cb75; }
.wizard-unqualified[b-f8b5i38lhg] { color: #e0b49c; }

.chip-row[b-f8b5i38lhg] { display: flex; flex-wrap: wrap; gap: .35rem; }
.choice-chip[b-f8b5i38lhg] { border: 1px solid #3c4a68; border-radius: 999px; background: #202b42; color: #e8edf8;
    padding: .3rem .6rem; font-size: .72rem; cursor: pointer; }
.choice-chip:hover[b-f8b5i38lhg] { border-color: #7394c2; }
.choice-chip.chosen[b-f8b5i38lhg] { border-color: #e8c36a; background: #3a2f18; color: #f0cb75; }

/* ── Feat cards: the whole point of the rebuild ──────────────────────────────────────────────────── */

.feat-filter[b-f8b5i38lhg] { width: 100%; box-sizing: border-box; background: #141b2b; color: #eef2fb;
    border: 1px solid #34405c; border-radius: 8px; padding: .45rem .6rem; font-size: .8rem; }

/* Capped and scrolled: a class-feat list is up to 150 entries and must not push the rest of the page
   off the bottom of the screen. */
.feat-list[b-f8b5i38lhg] { display: grid; gap: .4rem; max-height: 22rem; overflow-y: auto; padding-right: .2rem; }

.feat-card[b-f8b5i38lhg] { display: grid; gap: .2rem; width: 100%; text-align: left; cursor: pointer;
    border: 1px solid #303b57; border-radius: 10px; background: #141a2a; padding: .5rem .6rem; }
.feat-card:hover[b-f8b5i38lhg] { border-color: #7394c2; background: #182034; }
.feat-card.chosen[b-f8b5i38lhg] { border-color: #e8c36a; background: #2b2517; }

.feat-card-head[b-f8b5i38lhg] { display: flex; flex-wrap: wrap; align-items: baseline; gap: .4rem; }
.feat-card-head strong[b-f8b5i38lhg] { color: #f0e6d4; font-size: .82rem; }
.feat-card.chosen .feat-card-head strong[b-f8b5i38lhg] { color: #f0cb75; }
.feat-level[b-f8b5i38lhg] { font-size: .64rem; color: #8e9ab8; padding: .05rem .35rem; border-radius: 999px; background: #202b42; }
.feat-cost[b-f8b5i38lhg] { font-size: .64rem; color: #c9b47a; padding: .05rem .35rem; border-radius: 999px; background: #2f2a1a; }
.feat-traits[b-f8b5i38lhg] { font-size: .64rem; letter-spacing: .04em; text-transform: uppercase; color: #7f8aa6; }
.feat-prereq[b-f8b5i38lhg] { font-size: .68rem; color: #e0b49c; }
/* #1302: a prerequisite nothing could read, so the player has to. */
.feat-unconfirmed[b-f8b5i38lhg] { font-size: .68rem; color: #f0cb75; font-style: italic; }
/* The whole description (Dave, 2026-09-16), paragraph breaks kept. Bounded to ten lines like the character creator's
   picker rows, so a 150-card list stays scannable; the chosen card opens in full, and the review shows it in full. */
.feat-desc[b-f8b5i38lhg] { font-size: .72rem; line-height: 1.45; color: #b8c2da; white-space: pre-line;
    display: -webkit-box; -webkit-line-clamp: 10; line-clamp: 10; -webkit-box-orient: vertical; overflow: hidden; }
.feat-card.chosen .feat-desc[b-f8b5i38lhg] { display: block; -webkit-line-clamp: unset; line-clamp: none; overflow: visible; }

.wizard-feat-detail[b-f8b5i38lhg] { display: block; margin-top: .3rem; font-size: .72rem; line-height: 1.45; color: #b8c2da;
    white-space: pre-line; }

/* ── Review ─────────────────────────────────────────────────────────────────────────────────────── */

.wizard-summary[b-f8b5i38lhg] { display: grid; gap: .3rem; margin: 0; }
.wizard-summary div[b-f8b5i38lhg] { display: grid; grid-template-columns: minmax(7rem, auto) 1fr; gap: .6rem;
    padding: .3rem .5rem; border: 1px solid #303b57; border-radius: 8px; background: #141a2a; }
.wizard-summary dt[b-f8b5i38lhg] { font-size: .68rem; letter-spacing: .04em; text-transform: uppercase; color: #8e9ab8; }
.wizard-summary dd[b-f8b5i38lhg] { margin: 0; font-size: .78rem; color: #e8edf8; }

/* Says what is still owed rather than looking finished. A player who forfeits a feat should do it on
   purpose, not because a screen implied there was nothing left. */
.wizard-warning[b-f8b5i38lhg] { margin: .2rem 0 0; padding: .45rem .6rem; border-left: 3px solid #8a6f37;
    background: #241d10; color: #f4d58d; font-size: .72rem; line-height: 1.45; }

/* ── Navigation ─────────────────────────────────────────────────────────────────────────────────── */

.wizard-nav[b-f8b5i38lhg] { display: flex; align-items: center; gap: .5rem; }
.wizard-nav .thin-action[b-f8b5i38lhg] { margin: 0; width: auto; flex: 0 0 auto; }
.wizard-count[b-f8b5i38lhg] { flex: 1; text-align: center; font-size: .68rem; color: #8e9ab8; }
.wizard-cancel[b-f8b5i38lhg] { justify-self: center; border: 0; background: none; color: #8e9ab8;
    font-size: .7rem; text-decoration: underline; cursor: pointer; padding: 0; }
.wizard-cancel:hover[b-f8b5i38lhg] { color: #c3cbe6; }

@media (max-width: 600px) {
    .wizard-summary div[b-f8b5i38lhg] { grid-template-columns: 1fr; gap: .1rem; }
    .feat-list[b-f8b5i38lhg] { max-height: 16rem; }
}

/* "Level with AI" — deliberately the quieter of the two buttons. Choosing a character's feats yourself is
   the feature; this is the shortcut, and a shortcut that outshouts the real thing teaches people not to
   look at their own character. */
.level-with-ai[b-f8b5i38lhg] {
    background: #1b2436;
    border-color: #3c4a68;
    color: #cbd6ee;
    font-weight: 600;
}

.level-with-ai:hover:not(:disabled)[b-f8b5i38lhg] { border-color: #7394c2; background: #223050; }
.level-with-ai span[b-f8b5i38lhg] { display: block; margin-top: .15rem; font-size: .66rem; color: #8e9ab8; font-weight: 400; }
/* /Components/Shared/LootCard.razor.rz.scp.css */
/* The loot card (#968) — the container card's sibling, over bodies instead of a box.

   ⭐⭐ EVERY GRAPHIC IS A SLOT, the same contract #578 set and #588 carried, for the reason Dave gave
   then: "make the design easy to enhance with graphics so we can tweak it." Each variable is a complete
   `background` shorthand with a hand-drawn CSS fallback in it. To hang real art on this card, set the
   variable — no markup moves, and one slot can be replaced without touching the others.

     --loot-frame     the outer iron-banded border
     --loot-corner    the bracket at each of the four corners
     --loot-banner    the parchment strip the summary line sits on
     --loot-plate     the plate behind the heading
     --loot-button    the take-everything buttons
     --loot-texture   the card's inner ground, behind everything

   ⛔ NOT PROMOTED TO play.css, and that is deliberate and is ContainerCard's own recorded decision. A
   shared sheet is linked BEFORE the scoped bundle, so a shared rule loses every tie to a page's own —
   these rules would become defaults the first page to name `.loot-card` could silently beat.
   → promoting-shared-css

   ⛔ ITS OWN CLASS PREFIX rather than reusing `.container-*`. Scoped CSS is per-component, so sharing the
   NAMES across two components would share nothing and would only make each sheet look as if it applied
   to the other. → scoped-css-cannot-reach-a-built-element

   Sized in rem throughout so a future art pass can scale the whole card by changing font-size alone. */

.loot-veil[b-szepvkvir6] {
    position: fixed;
    inset: 0;
    /* ⛔ THE TOKEN, NEVER A HAND-TYPED NUMBER. A bare z-index relates to nothing: the floating pane bands
       are assigned in dm-panes.js, and a literal 60 sits BELOW a pinned window, so the card would open
       behind one. `DialogLayeringTests` enforces this and refuses a bare 1000 as readily as a bare 60. */
    z-index: var(--dm-layer-dialog, 1000);
    display: grid;
    /* `safe center` rather than plain centring: this card's ledger is as long as everything the party
       just killed was carrying, so it is routinely taller than the window — and a grid-centred box that
       overflows has its TOP pushed off-screen, taking the title and the close button with it. */
    place-items: safe center;
    padding: 1rem;
    overflow-y: auto;
    background: #05070cc4;
    backdrop-filter: blur(2px);
}

.loot-card[b-szepvkvir6] {
    --loot-ink: #f3e7cf;
    --loot-ink-dim: #c9b48e;
    --loot-gold: #d8b96a;

    --loot-texture:
        radial-gradient(120% 80% at 50% 0%, #3a322a 0%, #221d18 55%, #14100d 100%);

    --loot-frame:
        linear-gradient(160deg, #6b5a3f 0%, #2b241c 18%, #40362a 50%, #241e17 82%, #6b5a3f 100%);

    --loot-corner:
        radial-gradient(circle at 68% 68%, #d8bd7d 0 12%, #8a7448 13% 20%, transparent 21%),
        linear-gradient(135deg, #b39a63 0 38%, #7d6a48 38% 62%, #4a3d28 62% 100%);

    --loot-banner:
        linear-gradient(180deg, #e5d6b4 0%, #d3c09a 48%, #c0aa80 100%);

    --loot-plate:
        linear-gradient(180deg, #3b2f22 0%, #4a3b2a 45%, #2a2118 100%);

    --loot-button:
        linear-gradient(180deg, #5a4a30 0%, #47391f 55%, #2f2615 100%);

    position: relative;
    display: grid;
    justify-items: stretch;
    gap: .7rem;
    /* ⛔ BORDER-BOX, AND IT IS NOT A TIDINESS RULE — ContainerCard's scar, inherited rather than
       re-earned. This app has no global reset, so without it the .55rem border and 1.5rem of side
       padding are added ON TOP of the declared width, and in a narrow pane the right-hand column of
       buttons lands off-screen: every "Take" reads as clipped text and none of them is clickable. */
    box-sizing: border-box;
    /* ⚠️ `100%`, NOT `94vw`. The veil is a fixed grid with 1rem of padding, so 100% is the content box
       the card has to fit inside; a viewport-relative width does not know about that padding and adds it
       straight back, which is a few invisible pixels and a horizontal scrollbar on a modal. */
    width: min(34rem, 100%);
    padding: 1.4rem 1.5rem 1.5rem;
    background: var(--loot-texture);
    border: .55rem solid transparent;
    border-image: var(--loot-frame) 1;
    box-shadow: 0 1.2rem 3rem #000a, inset 0 0 3rem #0007;
    color: var(--loot-ink);
    font-size: .95rem;
}

/* The four brackets, hung outside the padding box as real elements so a future sprite can replace one
   corner at a time. ⚠️ The card must never become a scroll container while these hang off it — the veil
   does the scrolling, which is the lesson the encounter card paid for. */
.loot-corner[b-szepvkvir6] {
    position: absolute;
    width: 1.15rem;
    height: 1.15rem;
    background: var(--loot-corner);
}
.loot-corner.top-left[b-szepvkvir6] { top: -.75rem; left: -.75rem; }
.loot-corner.top-right[b-szepvkvir6] { top: -.75rem; right: -.75rem; transform: rotate(90deg); }
.loot-corner.bottom-right[b-szepvkvir6] { bottom: -.75rem; right: -.75rem; transform: rotate(180deg); }
.loot-corner.bottom-left[b-szepvkvir6] { bottom: -.75rem; left: -.75rem; transform: rotate(270deg); }

.loot-close[b-szepvkvir6] {
    position: absolute;
    top: .3rem;
    right: .45rem;
    border: 0;
    background: none;
    color: var(--loot-ink-dim);
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
}
.loot-close:hover[b-szepvkvir6], .loot-close:focus-visible[b-szepvkvir6] { color: var(--loot-ink); }

.loot-title[b-szepvkvir6] {
    margin: 0;
    padding: .35rem .8rem;
    background: var(--loot-plate);
    border: 1px solid #00000055;
    box-shadow: inset 0 0 .8rem #0006;
    text-align: center;
    font-size: 1.15rem;
    letter-spacing: .02em;
    color: var(--loot-gold);
}

.loot-banner[b-szepvkvir6] {
    margin: 0;
    padding: .2rem .7rem;
    background: var(--loot-banner);
    color: #3a2d1a;
    text-align: center;
    font-style: italic;
    font-size: .88rem;
}

.loot-prompt[b-szepvkvir6] {
    margin: .2rem 0 0;
    font-size: .82rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--loot-ink-dim);
}

.loot-hands[b-szepvkvir6] { display: flex; flex-wrap: wrap; gap: .35rem; }

.loot-hand[b-szepvkvir6] {
    padding: .25rem .6rem;
    border: 1px solid #6b5a3f;
    background: #1d1812;
    color: var(--loot-ink-dim);
    font: inherit;
    font-size: .85rem;
    cursor: pointer;
}
.loot-hand:hover[b-szepvkvir6], .loot-hand:focus-visible[b-szepvkvir6] { border-color: var(--loot-gold); color: var(--loot-ink); }
.loot-hand.chosen[b-szepvkvir6] { background: var(--loot-plate); border-color: var(--loot-gold); color: var(--loot-gold); }

/* ⭐⭐ THE METER. Dave asked for encumbrance specifically "to hopefully limit the amount of junk the
   party picks up", so it is given real estate directly under the picker — above the loot rather than
   below it, because it has to be read BEFORE the decision rather than found afterwards. */
.loot-load[b-szepvkvir6] {
    display: grid;
    gap: .25rem;
    padding: .5rem .7rem;
    border: 1px solid #6b5a3f66;
    background: #00000033;
}

.loot-load-line[b-szepvkvir6] { display: flex; align-items: baseline; gap: .5rem; }
.loot-load-name[b-szepvkvir6] { flex: 1 1 auto; font-size: .82rem; color: var(--loot-ink-dim); }
.loot-load-figure[b-szepvkvir6] { font-variant-numeric: tabular-nums; color: var(--loot-ink); }

.loot-load-bar[b-szepvkvir6] {
    height: .5rem;
    background: #0000005c;
    border: 1px solid #00000066;
    overflow: hidden;
}

.loot-load-bar > span[b-szepvkvir6] {
    display: block;
    height: 100%;
    /* Colour comes from the state class on the wrapper, so the bar and the note can never disagree about
       whether somebody is overloaded. */
    background: var(--loot-load-fill, #7fa86a);
    transition: width .15s ease-out;
}

.loot-load.fine[b-szepvkvir6] { --loot-load-fill: linear-gradient(90deg, #5f8a4e, #8fbf72); }
.loot-load.heavy[b-szepvkvir6] { --loot-load-fill: linear-gradient(90deg, #a8862f, #d8b96a); }
.loot-load.over[b-szepvkvir6] { --loot-load-fill: linear-gradient(90deg, #8a2f22, #d06a4a); }

.loot-load.heavy .loot-load-figure[b-szepvkvir6] { color: var(--loot-gold); }
.loot-load.over .loot-load-figure[b-szepvkvir6] { color: #f0a58a; }

.loot-load-note[b-szepvkvir6] { margin: 0; font-size: .82rem; }
.loot-load.heavy .loot-load-note[b-szepvkvir6] { color: var(--loot-gold); }
.loot-load.over .loot-load-note[b-szepvkvir6] { color: #f0a58a; }

/* The ruleset's own arithmetic, small and always present. Never hidden behind a tooltip: a number a
   player can argue with has to be arguable without hovering something. */
.loot-load-because[b-szepvkvir6] { margin: 0; font-size: .72rem; font-style: italic; color: var(--loot-ink-dim); }

.loot-ledger[b-szepvkvir6] { display: grid; gap: .3rem; }

.loot-heading[b-szepvkvir6] {
    margin: .3rem 0 0;
    font-size: .8rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--loot-gold);
    border-bottom: 1px solid #6b5a3f55;
    padding-bottom: .15rem;
}

.loot-nothing[b-szepvkvir6] { margin: 0; font-size: .85rem; font-style: italic; color: var(--loot-ink-dim); }

.loot-items[b-szepvkvir6] { list-style: none; margin: 0; padding: 0; display: grid; gap: .2rem; }
.loot-items li[b-szepvkvir6] { display: flex; align-items: center; gap: .5rem; }
.loot-items .item-name[b-szepvkvir6] { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.loot-items .item-name.coin[b-szepvkvir6] { color: var(--loot-gold); }
.loot-items .item-count[b-szepvkvir6] { color: var(--loot-ink-dim); font-size: .85rem; }

/* What the row weighs, held to a fixed column so a list of them reads as a column rather than as text
   that happens to be numbers. */
.loot-items .item-bulk[b-szepvkvir6] {
    flex: 0 0 auto;
    min-width: 2rem;
    text-align: right;
    font-size: .78rem;
    font-variant-numeric: tabular-nums;
    color: var(--loot-ink-dim);
}

.item-move[b-szepvkvir6] {
    flex: 0 0 auto;
    padding: .15rem .55rem;
    border: 1px solid #6b5a3f;
    background: #1d1812;
    color: var(--loot-ink-dim);
    font: inherit;
    font-size: .8rem;
    cursor: pointer;
}
.item-move:hover:not(:disabled)[b-szepvkvir6], .item-move:focus-visible[b-szepvkvir6] { border-color: var(--loot-gold); color: var(--loot-ink); }
.item-move:disabled[b-szepvkvir6] { opacity: .45; cursor: default; }

.loot-all[b-szepvkvir6] {
    justify-self: start;
    margin-top: .15rem;
    padding: .25rem .7rem;
    border: 1px solid #00000066;
    background: var(--loot-button);
    box-shadow: inset 0 1px 0 #ffffff18, 0 .15rem .4rem #0006;
    color: var(--loot-ink);
    font: inherit;
    font-size: .8rem;
    cursor: pointer;
}
.loot-all:hover:not(:disabled)[b-szepvkvir6], .loot-all:focus-visible[b-szepvkvir6] { filter: brightness(1.18); }
.loot-all:disabled[b-szepvkvir6] { opacity: .5; cursor: default; }

.loot-working[b-szepvkvir6] { margin: 0; font-size: .85rem; color: var(--loot-ink-dim); }

.loot-feedback[b-szepvkvir6] { margin: .2rem 0 0; font-size: .85rem; }
.loot-feedback.yes[b-szepvkvir6] { color: #9fd6a0; }
.loot-feedback.no[b-szepvkvir6] { color: #f0a58a; }

@media (max-width: 30rem) {
    .loot-card[b-szepvkvir6] { font-size: .9rem; padding: 1.1rem 1rem 1.2rem; }
    .loot-all[b-szepvkvir6] { justify-self: stretch; text-align: center; }
}
/* /Components/Shared/MapCellPicker.razor.rz.scp.css */
/* The board an author picks den squares on. Every colour here is either a real forge token or an
   explicit hsl() — never var(--something-plausible), which renders happily and silently wrong. */

.cell-picker[b-h86rl641sj] {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.cell-picker-toolbar[b-h86rl641sj] {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
}

.cell-picker-count[b-h86rl641sj] {
    margin: 0;
    color: var(--forge-text-dim);
    font-size: 0.85rem;
}

.cell-picker-clear[b-h86rl641sj] {
    background: var(--forge-surface-button);
    color: var(--forge-text);
    border: 1px solid var(--forge-border-input);
    border-radius: 4px;
    padding: 0.15rem 0.6rem;
    font-size: 0.8rem;
    cursor: pointer;
}

/* Bounded so a 35x44 map cannot push the dialog's buttons off the bottom of the screen — the board
   scrolls inside its own box rather than growing the page. */
.cell-picker-board[b-h86rl641sj] {
    width: 100%;
    max-height: 55vh;
    background: var(--forge-surface-1);
    border: 1px solid var(--forge-border-card);
    border-radius: 4px;
}

.cell-picker-cell[b-h86rl641sj] {
    /* The hairline grid. Drawn per cell rather than as a pattern so a single square can be restyled
       without the grid moving. */
    stroke: var(--forge-border-quiet);
    stroke-width: 0.03;
    cursor: pointer;
}

.cell-picker-free[b-h86rl641sj] { fill: transparent; }

.cell-picker-free:hover[b-h86rl641sj] { fill: hsl(43deg 60% 60% / 25%); }

.cell-picker-blocked[b-h86rl641sj] {
    fill: hsl(0deg 0% 100% / 14%);
    cursor: not-allowed;
}

/* Void reads as a hole rather than as a heavier wall: the map does not go here at all. */
.cell-picker-void[b-h86rl641sj] {
    fill: hsl(0deg 0% 0% / 45%);
    cursor: not-allowed;
}

.cell-picker-picked[b-h86rl641sj] {
    fill: var(--forge-gold);
    stroke: var(--forge-gold-bright);
    stroke-width: 0.06;
}

/* The den's other half — present, clearly not what is being edited. */
.cell-picker-companion[b-h86rl641sj] {
    fill: hsl(43deg 55% 55% / 30%);
    stroke-dasharray: 0.15 0.1;
}

.cell-picker-room-cell[b-h86rl641sj] {
    stroke: none;
    pointer-events: none;
}

/* ⚠️ Both properties on every tint, because the SAME class dresses two different kinds of element: an
   SVG <rect>, which takes `fill`, and the legend's <span>, which takes `background` and would ignore
   `fill` in silence — a legend of six identical empty boxes beside a board of six colours. Splitting
   them into two class families is the version of this that drifts. */
.cell-picker-room-0[b-h86rl641sj] { fill: hsl(205deg 70% 55% / 16%); background: hsl(205deg 70% 55% / 45%); }
.cell-picker-room-1[b-h86rl641sj] { fill: hsl(145deg 60% 50% / 16%); background: hsl(145deg 60% 50% / 45%); }
.cell-picker-room-2[b-h86rl641sj] { fill: hsl(275deg 60% 62% / 16%); background: hsl(275deg 60% 62% / 45%); }
.cell-picker-room-3[b-h86rl641sj] { fill: hsl(25deg 75% 55% / 16%); background: hsl(25deg 75% 55% / 45%); }
.cell-picker-room-4[b-h86rl641sj] { fill: hsl(340deg 65% 60% / 16%); background: hsl(340deg 65% 60% / 45%); }
.cell-picker-room-5[b-h86rl641sj] { fill: hsl(180deg 55% 48% / 16%); background: hsl(180deg 55% 48% / 45%); }

/* ⛔ The waypoint numbers are styled by PRESENTATION ATTRIBUTES in the component, not here, and this
   comment is what stops somebody moving them back. Those <text> elements are built with
   RenderTreeBuilder (Razor owns the <text> tag name), so CSS isolation never stamps its scope attribute
   on them and every rule written here would compile to `.cell-picker-order[b-xxxxx]` and match nothing.
   Only `pointer-events` survives here, and only because it is harmless either way. */
.cell-picker-order[b-h86rl641sj] {
    pointer-events: none;
}

.cell-picker-legend[b-h86rl641sj] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 0.9rem;
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: 0.78rem;
    color: var(--forge-text-muted);
}

.cell-picker-legend li[b-h86rl641sj] {
    display: flex;
    align-items: center;
    gap: 0.35rem;
}

.cell-picker-swatch[b-h86rl641sj] {
    width: 0.7rem;
    height: 0.7rem;
    border: 1px solid var(--forge-border-quiet);
    border-radius: 2px;
}

.cell-picker-refusal[b-h86rl641sj] {
    margin: 0;
    padding: 0.4rem 0.6rem;
    background: var(--forge-danger-surface);
    border: 1px solid var(--forge-danger-border);
    border-radius: 4px;
    color: var(--forge-danger);
    font-size: 0.82rem;
}
/* /Components/Shared/MerchantShop.razor.rz.scp.css */
/* The shop window's own stylesheet, because scoped CSS does not cross components — a lesson this
   project has paid for three times. Nothing here leans on the page that opens it. */

.shop-scrim[b-2x6x26bo2f] {
    position: fixed;
    inset: 0;
    background: #05070bcc;
    z-index: var(--dm-layer-dialog, 1000);
}

.shop-window[b-2x6x26bo2f] {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: var(--dm-layer-dialog, 1000);
    width: min(58rem, calc(100vw - 2.5rem));
    max-height: min(40rem, calc(100vh - 3rem));
    display: flex;
    flex-direction: column;
    gap: .7rem;
    padding: 1.1rem 1.2rem 1.2rem;
    border: 1px solid #6b5a33;
    border-radius: 18px;
    background: #14110a;
    box-shadow: 0 1.6rem 3rem #000000a6;
    color: #e4d9bf;
    overflow: hidden;
}

.shop-window header[b-2x6x26bo2f] {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
}

.shop-window header > div[b-2x6x26bo2f] { flex: 1; }
.shop-window h2[b-2x6x26bo2f] { margin: .1rem 0 0; font-size: 1.15rem; color: #f0e2be; }

.eyebrow[b-2x6x26bo2f] {
    margin: 0;
    font-size: .62rem;
    letter-spacing: .14em;
    color: #9a844c;
}

.shop-blurb[b-2x6x26bo2f] {
    margin: .45rem 0 0;
    font-size: .78rem;
    line-height: 1.45;
    color: #bdae8d;
}

/* Layout only — the look comes from the shared .dm-close in app.css. */
.shop-close[b-2x6x26bo2f] { flex: none; }

.shop-purses[b-2x6x26bo2f] {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem 1.4rem;
    font-size: .78rem;
    color: #bdae8d;
    padding-bottom: .55rem;
    border-bottom: 1px solid #332d1c;
}

.shop-purses strong[b-2x6x26bo2f] { color: #d8bf72; font-variant-numeric: tabular-nums; }

.shop-message[b-2x6x26bo2f] {
    margin: 0;
    padding: .45rem .6rem;
    border-radius: 10px;
    background: #1d2a17;
    border: 1px solid #43613a;
    font-size: .76rem;
    color: #cfe3bf;
}

.shop-message.error[b-2x6x26bo2f] {
    background: #2a1716;
    border-color: #6d3a35;
    color: #eec4bf;
}

/* Two counters side by side, stacking on a narrow window rather than shrinking into unreadability. */
.shop-columns[b-2x6x26bo2f] {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
    gap: 1rem;
    overflow: hidden;
}

.shop-column[b-2x6x26bo2f] {
    display: flex;
    flex-direction: column;
    gap: .45rem;
    min-height: 0;
}

.shop-column ul[b-2x6x26bo2f] {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .5rem;
    overflow-y: auto;
    min-height: 0;
}

.shop-column li[b-2x6x26bo2f] {
    padding: .5rem .6rem;
    border: 1px solid #3a3423;
    border-radius: 12px;
    background: #1b1710;
}

.shop-empty[b-2x6x26bo2f] { margin: .3rem 0 0; font-size: .76rem; color: #8e8261; font-style: italic; }

.shop-line[b-2x6x26bo2f] {
    display: flex;
    align-items: baseline;
    gap: .5rem;
    font-size: .78rem;
}

.shop-name[b-2x6x26bo2f] {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: #e8dcc0;
}

.shop-stock[b-2x6x26bo2f] { font-size: .68rem; color: #8e8261; font-variant-numeric: tabular-nums; }
.shop-unit[b-2x6x26bo2f] { font-size: .7rem; color: #c2a961; font-variant-numeric: tabular-nums; }

.shop-controls[b-2x6x26bo2f] {
    display: flex;
    align-items: center;
    gap: .35rem;
    margin-top: .45rem;
}

.step[b-2x6x26bo2f] {
    width: 1.6rem;
    height: 1.6rem;
    border: 1px solid #4a4230;
    border-radius: 8px;
    background: #241f13;
    color: #ddcb9d;
    font-size: .85rem;
    line-height: 1;
    cursor: pointer;
}

.step:hover:not(:disabled)[b-2x6x26bo2f] { background: #332c1a; border-color: #9a844c; }
.step:disabled[b-2x6x26bo2f] { opacity: .35; cursor: default; }

.count[b-2x6x26bo2f] {
    min-width: 1.9rem;
    text-align: center;
    font-size: .8rem;
    font-variant-numeric: tabular-nums;
    color: #f0e2be;
}

.trade[b-2x6x26bo2f] {
    flex: 1;
    padding: .3rem .6rem;
    border: 1px solid #6b5a33;
    border-radius: 999px;
    background: #2a2413;
    color: #e8d9a8;
    font-size: .74rem;
    cursor: pointer;
    white-space: nowrap;
}

.trade:hover:not(:disabled)[b-2x6x26bo2f] { background: #3a3119; border-color: #c2a961; }
.trade:disabled[b-2x6x26bo2f] { opacity: .4; cursor: default; }

/* Dialled past what the merchant can actually pay for. The button still works — the engine will refuse
   and say why — but saying so before the click is kinder than saying so after. */
.trade.unaffordable[b-2x6x26bo2f] {
    border-color: #6d5535;
    border-style: dashed;
    color: #c2a06a;
}

/* ── Who is at the counter, and what they cannot sell (Dave, 2026-09-07) ────────────────────────────── */
.shop-trader[b-2x6x26bo2f] {
    display: inline-flex;
    align-items: baseline;
    gap: .35rem;
    font-size: .78rem;
    color: #9aa6bd;
}

.shop-trader select[b-2x6x26bo2f] {
    font-size: .78rem;
    padding: .1rem .25rem;
}

/* Worn or wielded: named where the carried count would be, so the eye lands on the reason the
   Sell button is dead rather than on a dead button. */
.shop-equipped[b-2x6x26bo2f] {
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: #d9c68f;
}
/* /Components/Shared/MiniMap.razor.rz.scp.css */
/* Issue #44. A compact picture of the board for surfaces that are not the map tab.
   Issue #264 — it now zooms and pans, because at fit size on a 29x40 map a square is under five pixels. */
.mini-map[b-cqutknvmlz]{position:relative;border:1px solid #303b57;border-radius:12px;background:#0c111bd9;overflow:hidden}
.mini-map.empty[b-cqutknvmlz]{padding:.7rem .85rem}
.mini-empty[b-cqutknvmlz]{margin:0;font-size:.72rem;color:#8e9ab8}

/* ⚠️ POINTER EVENTS OFF ON EVERYTHING THE BOARD DRAWS, AND IT IS LOAD-BEARING. Underneath this is a real
   BattleMap — up to sixteen hundred <button> cells, each draggable-droppable, plus tokens that request a
   move when dragged. At minimap size a cell is a few pixels across, so every one of them is an unhittable
   click target sitting on top of the pan gesture. The board is hidden from a screen reader for the same
   reason and the section above carries the label instead.

   ⚠️ ON THE CHILDREN, NOT ON `.battle-map` ITSELF (#264). It used to be on the board, which also made the
   board unable to HEAR a pointer — and the pan gesture listens on the board. Moving it one level down
   keeps every drawn thing untouchable while leaving the empty board hit-testable, which is exactly the
   split we want: the squares are scenery, the board is a viewport. If a minimap ever needs a clickable
   CELL, that is a new parameter and a real decision, not the loosening of this rule. */
/* ⚠️ `::deep` ON EVERY ONE OF THESE, AND IT IS NOT OPTIONAL. `.battle-map` is BattleMap's element and
   carries BattleMap's scope attribute, so `.mini-board .battle-map` compiles to
   `.mini-board[b-mini] .battle-map[b-mini]` and matches nothing at all. Written without it first, and the
   result was a section two pixels tall with a 596px board hanging out of it and every cell still
   clickable — the styling silently absent rather than wrong. Third time today; see issue #233. */
.mini-board[b-cqutknvmlz]  .battle-map > *{pointer-events:none}
/* ⚠️ AND THE DOOR HANDLES SEPARATELY, because BattleMap's own stylesheet says
   `.map-boundary.operable .boundary-handle{pointer-events:auto}` — a descendant that sets `auto` is
   hit-testable again however emphatically an ancestor said `none`, so the rule above does NOT reach it.
   A door the driven character is standing against therefore had a live, three-pixel button on the
   minimap: harmless while nothing was bound to it, and a pan gesture that mysteriously refuses to start
   the moment #264 made the board pannable (`.boundary-handle` is one of map-pan.js's MANIPULABLE). */
.mini-board[b-cqutknvmlz]  .boundary-handle{pointer-events:none}

/* ⚠️ A DEFINITE HEIGHT, NOT A max-height. An element with `overflow:hidden` has a min-content height of
   ZERO, so inside the tool column — a grid inside a flex child with `min-height:0` — the row was free to
   shrink to nothing and did: the section measured TWO PIXELS with a 596px board hanging out of it, and
   the board itself measured perfectly all the way down. Only walking the parent chain found it.

   `--mini-map-height` so a host can say how much room it is giving without knowing anything about how
   this is built. That is the whole framework promise: a travel screen and a combat panel want different
   sizes and neither should have to reach inside. ZOOM DOES NOT TOUCH IT — zooming grows the board inside
   this box, never the box, so the minimap cannot shove the tracker off the bottom of the tool column. */
.mini-map[b-cqutknvmlz]{height:var(--mini-map-height,12rem)}
/* ...and `grows` spends the host's LEFTOVER room instead (Dave, 2026-08-10: "there is about 30% of the
   left column unused... but smaller if the list of combatants is larger — with a minimum set so the map
   never gets too awful small (the current value for the minimum)").

   `min-height` is that minimum, and it is the same variable, so the floor is byte-for-byte the box this
   had before. `flex-shrink:1` is what lets a long initiative list push it back down to that floor.

   ⚠️ The percentage chain below still needs something definite to resolve against, and a flex item's
   height IS definite once flex has resolved it — which is why this is `flex`, and why `height:auto` here
   is safe where the `max-height` in the note above was not. `min-height` also overrides the flex default
   of `min-height:auto`, which on an `overflow:hidden` item computes to zero: that is the exact route by
   which this box once measured two pixels. Requires the host to be a flex column; a grid host will
   ignore all of it and keep the definite height, which is a safe way to be wrong. */
.mini-map.grows[b-cqutknvmlz]{height:auto;flex:1 1 var(--mini-map-height,12rem);
    min-height:var(--mini-map-height,12rem)}
/* ⚠️ NOT `place-items:center`. Centring a grid item makes its height AUTO, and the board's height is a
   percentage of this box — so the percentage had nothing definite to resolve against, fell back to auto,
   and the board went back to fitting its WIDTH: 594px inside 192px. Centred with `margin:0 auto` on the
   board instead, which leaves the height chain definite the whole way down. */
/* Scrollbars hidden rather than thin: this box is 192px tall and a scrollbar on each axis would eat the
   legibility the zoom was added to buy. The gesture is the affordance — the board draws a grab cursor
   whenever there is anything to pan to (BattleMap's `.pannable`), and the zoom readout says why. */
.mini-board[b-cqutknvmlz]{height:100%;line-height:0;overflow:auto;overscroll-behavior:contain;scrollbar-width:none}
.mini-board[b-cqutknvmlz]::-webkit-scrollbar{display:none}

/* Letterboxed rather than cropped. The board carries its own aspect-ratio, so constraining the HEIGHT
   and letting the width follow keeps the whole map on screen — a minimap that shows the top-left quarter
   of a tall dungeon is worse than no minimap, because it looks like the whole thing. */
/* `height:100%` on the board resolves against THIS, so this needs a definite height of its own or the
   percentage falls back to auto and the board fits to width again — 594px in a 192px box. */
.mini-board[b-cqutknvmlz]  .battle-map-shell{display:block;height:100%}
/* ⚠️ BOTH DIMENSIONS SCALED BY THE SAME VARIABLE, and the `max-width` is not decoration. At step zero
   this is `height:100%; max-width:100%` — byte for byte the letterboxing above, so an untouched minimap
   renders exactly as it did before it could zoom. At 3× it is `height:300%; max-width:300%`, which grows
   the picture and leaves WHICH axis letterboxes to the map's own shape: a tall dungeon still fits to
   height and a wide one still fits to width, three times bigger, with the overflow scrollable.
   Scaling only the height would crop a wide map the moment anybody zoomed. */
.mini-board[b-cqutknvmlz]  .battle-map{border-radius:0;border:0;min-height:0;
    height:calc(100% * var(--mini-zoom, 1));width:auto;
    max-width:calc(100% * var(--mini-zoom, 1));margin:0 auto}

/* The two overlays. Both float on the board rather than taking a row of their own, because the whole
   complaint behind #264 was that there is not enough room here — spending 1.5rem of a 12rem box on a
   toolbar would undo most of what the zoom just bought. */
.mini-zoom[b-cqutknvmlz]{position:absolute;left:.4rem;top:.4rem;display:flex;align-items:center;gap:.15rem;
    border:1px solid #43537c;border-radius:8px;background:#101828e0;padding:.1rem .15rem;
    backdrop-filter:blur(2px)}
.mini-zoom button[b-cqutknvmlz]{border:0;border-radius:6px;background:transparent;color:#c3cbe6;
    padding:.05rem .3rem;font-size:.72rem;font-weight:700;line-height:1.4;cursor:pointer}
.mini-zoom button:hover:not(:disabled)[b-cqutknvmlz]{background:#1c2740;color:#eef2fb}
.mini-zoom button:disabled[b-cqutknvmlz]{opacity:.4;cursor:default}
.mini-zoom button:focus-visible[b-cqutknvmlz]{outline:2px solid #7aa2d8;outline-offset:1px}
.mini-zoom-level[b-cqutknvmlz]{min-width:1.9rem;text-align:center;color:#8e9ab8;font-size:.62rem;font-weight:700;
    letter-spacing:.02em}
.mini-follow[b-cqutknvmlz]{font-size:.8rem}

.mini-open[b-cqutknvmlz]{position:absolute;right:.4rem;bottom:.4rem;display:flex;align-items:center;gap:.3rem;
    border:1px solid #43537c;border-radius:8px;background:#101828e0;color:#c3cbe6;
    padding:.2rem .5rem;font-size:.68rem;font-weight:700;cursor:pointer;backdrop-filter:blur(2px)}
.mini-open:hover[b-cqutknvmlz]{background:#1c2740;border-color:#5a6a94;color:#eef2fb}
.mini-open:focus-visible[b-cqutknvmlz]{outline:2px solid #7aa2d8;outline-offset:1px}
/* /Components/Shared/ObjectCard.razor.rz.scp.css */
/* The card behind every non-container object on the board (issue #593).

   ⭐ THE CONTAINER CARD'S FAMILY, deliberately: same veil, same gothic frame, same four corner brackets,
   same banner plate. A player who has opened a chest should recognise this instantly as the same kind of
   thing, and the two cards sitting side by side in the same fight is exactly when a mismatch would show.

   ⛔ NOT PROMOTED TO A SHARED STYLESHEET, and that is ContainerCard's own recorded decision. Two cards
   that look alike are not two cards that are the same: this one has a step list and no ledger, and the
   moment either grows its own art the shared base becomes a set of overrides fighting each other. Six
   graphic slots hang off --object-*, mirroring --container-*, and either card can be re-skinned alone.

   --object-art is THE TYPED-ART SLOT, keyed by KIND below rather than by state — a statue, a lock and a
   trap already read differently with no asset, and dropping in real sprites later is one variable each. */

.object-veil[b-zvlt1w4zyy] {
    position: fixed;
    inset: 0;
    /* ⛔ THE TOKEN, NEVER A HAND-TYPED NUMBER. A bare z-index relates to nothing: the floating pane bands
       are assigned in dm-panes.js, and a literal 60 sits BELOW a pinned window, so the card would open
       behind one. The container card's stylesheet carries this same warning in these same words, from
       #532, and `DialogLayeringTests` is what catches it when somebody types the number anyway. */
    z-index: var(--dm-layer-dialog, 1000);
    display: grid;
    /* `safe center` rather than plain centring: an object with a long step list can be taller than the
       window, and a grid-centred box that overflows has its TOP pushed off-screen — taking the title and
       the close button with it. */
    place-items: safe center;
    padding: 1rem;
    overflow-y: auto;
    background: #05070cc4;
    backdrop-filter: blur(2px);
}

.object-card[b-zvlt1w4zyy] {
    --object-ink: #f3e7cf;
    --object-ink-dim: #c9b48e;
    --object-gold: #d8b96a;

    --object-texture:
        radial-gradient(120% 80% at 50% 0%, #3a322a 0%, #221d18 55%, #14100d 100%);

    --object-frame:
        linear-gradient(160deg, #6b5a3f 0%, #2b241c 18%, #40362a 50%, #241e17 82%, #6b5a3f 100%);

    --object-corner:
        radial-gradient(circle at 68% 68%, #d8bd7d 0 12%, #8a7448 13% 20%, transparent 21%),
        linear-gradient(135deg, #b39a63 0 38%, #7d6a48 38% 62%, #4a3d28 62% 100%);

    --object-plate:
        linear-gradient(180deg, #3b2f22 0%, #4a3b2a 45%, #2a2118 100%);

    --object-button:
        linear-gradient(180deg, #5a4a30 0%, #47391f 55%, #2f2615 100%);

    /* The default: a carved stone marker. Redefined per kind below. */
    --object-art:
        linear-gradient(180deg, #6d6459 0%, #4d463d 55%, #302b25 100%);

    position: relative;
    display: grid;
    justify-items: stretch;
    gap: .7rem;
    /* ⛔ BORDER-BOX, AND IT IS NOT A TIDINESS RULE. This app has no global reset, so a `width` without it
       is the CONTENT width and the .55rem border plus 1.5rem of side padding land ON TOP. That is #590:
       in a 473px pane the container card computed to 492px and put its whole right-hand column off
       screen, and it LOOKED fine, because only the flush-right column overflowed. */
    box-sizing: border-box;
    /* ⚠️ `100%`, NOT `94vw`. The veil is a grid with 1rem of padding, so 100% here is the content box
       it actually has to fit inside; a viewport-relative width does not know about that padding and
       adds it back on. Measured on a 313px-wide board: the card came to 294 and the veil's scrollWidth
       to 326 against a clientWidth of 298 — a horizontal scrollbar on a dialog, with nothing visibly
       cut, which is #590's shape exactly and just as invisible to the eye. Asked the browser. */
    width: min(32rem, 100%);
    padding: 1.4rem 1.5rem 1.5rem;
    background: var(--object-texture);
    border: .55rem solid transparent;
    border-image: var(--object-frame) 1;
    box-shadow: 0 1.2rem 3rem #000a, inset 0 0 3rem #0007;
    color: var(--object-ink);
    font-size: .95rem;
}

/* Per-kind art. The hook a real sprite hangs on, one kind at a time. */
.object-card.lock[b-zvlt1w4zyy] {
    --object-art:
        radial-gradient(circle at 50% 42%, #2a2118 0 9%, transparent 10%),
        linear-gradient(180deg, #b8974f 0%, #8a7139 55%, #57451f 100%);
}
.object-card.trap[b-zvlt1w4zyy] {
    --object-art:
        repeating-linear-gradient(135deg, #6b3230 0 12%, #2a1614 12% 24%);
}

/* The four brackets, hung outside the padding box as real elements so a future sprite can replace one
   corner at a time. ⚠️ The card must never become a scroll container while these hang off it — the veil
   does the scrolling, which is the lesson the encounter card paid for. */
.object-corner[b-zvlt1w4zyy] {
    position: absolute;
    width: 1.15rem;
    height: 1.15rem;
    background: var(--object-corner);
}
.object-corner.top-left[b-zvlt1w4zyy] { top: -.75rem; left: -.75rem; }
.object-corner.top-right[b-zvlt1w4zyy] { top: -.75rem; right: -.75rem; transform: rotate(90deg); }
.object-corner.bottom-right[b-zvlt1w4zyy] { bottom: -.75rem; right: -.75rem; transform: rotate(180deg); }
.object-corner.bottom-left[b-zvlt1w4zyy] { bottom: -.75rem; left: -.75rem; transform: rotate(270deg); }

.object-close[b-zvlt1w4zyy] {
    position: absolute;
    top: .3rem;
    right: .45rem;
    border: 0;
    background: none;
    color: var(--object-ink-dim);
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
}
.object-close:hover[b-zvlt1w4zyy], .object-close:focus-visible[b-zvlt1w4zyy] { color: var(--object-ink); }

.object-title[b-zvlt1w4zyy] {
    margin: 0;
    padding: .35rem .8rem;
    background: var(--object-plate);
    border: 1px solid #00000055;
    box-shadow: inset 0 0 .8rem #0006;
    text-align: center;
    font-size: 1.15rem;
    letter-spacing: .02em;
    color: var(--object-gold);
}

.object-banner[b-zvlt1w4zyy] {
    margin: 0;
    text-align: center;
    font-style: italic;
    color: var(--object-ink-dim);
}

.object-art[b-zvlt1w4zyy] {
    height: 4.5rem;
    background: var(--object-art);
    border: 1px solid #00000066;
    box-shadow: inset 0 0 1.2rem #0008;
}

/* ⭐ #597 Phase 4: a real picture, when the host resolved one. The typed gradient above stays the empty
   state, so this only hides it behind an image already covering it and gives the crop room. `cover`
   rather than `contain`: the slot is a wide strip and a letterboxed square in it reads as a bug.
   ⚠️ ContainerCard.razor.css carries the same three rules for .container-art, and they are deliberately
   not promoted to a shared sheet — either card stays re-skinnable alone, which is that card's own
   recorded decision. */
.object-art.has-art[b-zvlt1w4zyy] {
    height: 9rem;
    padding: 0;
    background: #0b0906;
}

.object-art.has-art img[b-zvlt1w4zyy] {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.object-description[b-zvlt1w4zyy] {
    margin: 0;
    line-height: 1.45;
}

.object-working[b-zvlt1w4zyy] {
    margin: 0;
    text-align: center;
    font-style: italic;
    color: var(--object-gold);
}

.object-prompt[b-zvlt1w4zyy] {
    margin: 0;
    color: var(--object-ink-dim);
    font-size: .88rem;
}

.object-hands[b-zvlt1w4zyy] {
    display: flex;
    flex-wrap: wrap;
    gap: .4rem;
}

.object-hand[b-zvlt1w4zyy] {
    padding: .25rem .7rem;
    background: var(--object-plate);
    border: 1px solid #00000066;
    color: var(--object-ink-dim);
    font: inherit;
    font-size: .85rem;
    cursor: pointer;
}
.object-hand.chosen[b-zvlt1w4zyy] {
    border-color: var(--object-gold);
    color: var(--object-ink);
    box-shadow: 0 0 .5rem #d8b96a55;
}

.object-actions[b-zvlt1w4zyy], .object-steps[b-zvlt1w4zyy] {
    display: grid;
    gap: .5rem;
}

.object-step[b-zvlt1w4zyy] { display: grid; gap: .25rem; }

.object-step-prompt[b-zvlt1w4zyy] {
    margin: 0;
    color: var(--object-ink-dim);
    font-size: .88rem;
    line-height: 1.4;
}

.object-action[b-zvlt1w4zyy] {
    padding: .5rem .8rem;
    background: var(--object-button);
    border: 1px solid #00000077;
    box-shadow: inset 0 1px 0 #ffffff18;
    color: var(--object-ink);
    font: inherit;
    text-align: left;
    cursor: pointer;
}
.object-action:hover:not(:disabled)[b-zvlt1w4zyy], .object-action:focus-visible:not(:disabled)[b-zvlt1w4zyy] {
    border-color: var(--object-gold);
    color: #fff6e2;
}
.object-action:disabled[b-zvlt1w4zyy] { opacity: .5; cursor: default; }
.object-action small[b-zvlt1w4zyy] {
    display: block;
    color: var(--object-ink-dim);
    font-size: .78rem;
}
.object-action.ruinous[b-zvlt1w4zyy] { border-color: #7c3a2c; }

/* A step already worked. Reads as a note the party wrote, not as a dead button. */
.object-step.done[b-zvlt1w4zyy] {
    padding: .5rem .8rem;
    background: #ffffff08;
    border-left: 3px solid var(--object-gold);
}
.object-step-label[b-zvlt1w4zyy] {
    margin: 0;
    color: var(--object-gold);
    font-size: .85rem;
    letter-spacing: .02em;
}
.object-step-reveal[b-zvlt1w4zyy] {
    margin: .2rem 0 0;
    line-height: 1.45;
}

.object-ask[b-zvlt1w4zyy] {
    margin: 0;
    padding-top: .2rem;
    border-top: 1px solid #ffffff14;
    color: var(--object-ink-dim);
    font-size: .85rem;
    font-style: italic;
    line-height: 1.4;
}

.object-feedback[b-zvlt1w4zyy] {
    margin: 0;
    padding: .4rem .6rem;
    background: #00000055;
    border-left: 3px solid var(--object-ink-dim);
    line-height: 1.4;
}
.object-feedback.yes[b-zvlt1w4zyy] { border-left-color: #6f9f5a; }
.object-feedback.no[b-zvlt1w4zyy] { border-left-color: #a35c4a; }
/* /Components/Shared/OptionPicker.razor.rz.scp.css */
.picker-backdrop[b-mxzdoa80jb]{position:fixed;inset:0;background:#04060cc4;backdrop-filter:blur(2px);display:grid;place-items:center;padding:1rem;z-index:var(--dm-layer-dialog,1000)}
.picker-panel[b-mxzdoa80jb]{display:flex;flex-direction:column;width:min(940px,100%);max-height:min(86vh,780px);background:linear-gradient(155deg,#151b2cf7,#0d1220f7);border:1px solid #3a4568;border-radius:16px;box-shadow:0 34px 100px #02040ac0;overflow:hidden;outline:none}
.picker-head[b-mxzdoa80jb]{flex:none;display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;padding:1.1rem 1.2rem .8rem}
.picker-head h2[b-mxzdoa80jb]{margin:0;font-family:Georgia,serif;font-size:1.12rem;color:#faf7ef}
.picker-count[b-mxzdoa80jb]{margin:.25rem 0 0;font-size:.72rem;color:#8b96b6}
.picker-head-actions[b-mxzdoa80jb]{display:flex;align-items:center;gap:.45rem;flex-shrink:0}
.picker-ghost[b-mxzdoa80jb]{border:1px solid #3a4568;border-radius:8px;background:transparent;color:#b7c2df;padding:.4rem .75rem;font-size:.74rem;font-weight:700;cursor:pointer}
.picker-ghost:hover[b-mxzdoa80jb]{border-color:#e8c36a;color:#f5e3b0}
/* The look comes from the shared .dm-close in app.css; this component adds no placement of its own. */
.picker-toolbar[b-mxzdoa80jb]{flex:none;display:flex;gap:.5rem;padding:0 1.2rem .7rem}
.picker-search[b-mxzdoa80jb]{flex:1;min-width:0;background:#141b2b;color:#eef2fb;border:1px solid #34405c;border-radius:9px;padding:.6rem .8rem;font:inherit;font-size:.85rem}
.picker-search:focus[b-mxzdoa80jb]{outline:none;border-color:#e8c36a}
.picker-sort[b-mxzdoa80jb]{background:#141b2b;color:#eef2fb;border:1px solid #34405c;border-radius:9px;padding:.6rem .5rem;font:inherit;font-size:.8rem}
.picker-dir[b-mxzdoa80jb]{width:2.2rem;border:1px solid #34405c;border-radius:9px;background:#141b2b;color:#b7c2df;cursor:pointer;font-size:.9rem}
.picker-dir:hover[b-mxzdoa80jb]{border-color:#4a5c88}
/* flex:none is load-bearing — a scrollable flex item's automatic minimum size is zero, so without it
   the column squeezes this row and clips the chips mid-height. It wraps to as many rows as it needs;
   the option list below is the part that gives up space. */
.picker-facets[b-mxzdoa80jb]{flex:none;display:flex;flex-wrap:wrap;gap:.35rem;padding:0 1.2rem .75rem}
.facet[b-mxzdoa80jb]{border:1px solid #303b57;border-radius:999px;background:#111827;color:#9da8c5;padding:.28rem .68rem;font-size:.7rem;cursor:pointer;white-space:nowrap}
.facet:hover[b-mxzdoa80jb]{border-color:#4a5c88;color:#cdd6ea}
.facet.on[b-mxzdoa80jb]{border-color:#e8c36a;color:#f5e3b0;background:#221b0e}
.picker-list[b-mxzdoa80jb]{flex:1 1 auto;min-height:0;overflow-y:auto;padding:0 1.2rem 1rem;display:grid;gap:.4rem;align-content:start}
.picker-row[b-mxzdoa80jb]{display:flex;align-items:flex-start;justify-content:space-between;gap:.8rem;width:100%;text-align:left;padding:.65rem .8rem;border:1px solid #283449;border-radius:10px;background:#111827b0;color:#e7eaf2;cursor:pointer}
.picker-row:hover[b-mxzdoa80jb]{border-color:#4a5c88;background:#16203699}
.picker-row.chosen[b-mxzdoa80jb]{border-color:#e8c36a;background:#1c1a12}
.picker-row.blocked[b-mxzdoa80jb]{opacity:.45;cursor:not-allowed}
.picker-row.blocked:hover[b-mxzdoa80jb]{border-color:#283449;background:#111827b0}
.picker-row-main[b-mxzdoa80jb]{display:flex;flex-direction:column;gap:.15rem;min-width:0}
.picker-row-main strong[b-mxzdoa80jb]{font-size:.88rem}
.picker-row-summary[b-mxzdoa80jb]{font-size:.74rem;color:#9fb2d8}
/* ⚠️ Ten lines rather than four. Four was set when the text was also cut at 400 characters, and
   between them a third of the heritages showed half a sentence. The clamp stays — a row still has
   to be scannable — but it is now the ONLY limit, and it is generous enough that most options
   read in full. */
.picker-row-flavor[b-mxzdoa80jb]{font-size:.72rem;line-height:1.45;color:#8895b2;white-space:pre-line;display:-webkit-box;-webkit-line-clamp:10;line-clamp:10;-webkit-box-orient:vertical;overflow:hidden}
.picker-row-side[b-mxzdoa80jb]{display:flex;align-items:center;gap:.4rem;flex-shrink:0}
.picker-badge[b-mxzdoa80jb]{font-style:normal;font-size:.6rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:#c9a2e8;border:1px solid #4b3a63;border-radius:999px;padding:.16rem .45rem}
/* ⭐ Same pill as the rarity badge, in yellow -- Dave's design cue, 2026-09-08: "the way you do the
   [rare] marker and the like are perfect, just use yellow". Yellow is this product's "we are helping
   you" colour, the same as the onboarding coach-marks, so the two read as one voice.
   ⚠️ Outlined rather than filled: these appear on EVERY class row, and a solid yellow chip repeated
   twenty-seven times down a list would out-shout the class names it is there to describe. */
.picker-guidance[b-mxzdoa80jb]{font-style:normal;font-size:.6rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:#e8c36a;border:1px solid #7a6428;background:#2a230f66;border-radius:999px;padding:.16rem .45rem;white-space:nowrap}
.picker-mark[b-mxzdoa80jb]{font-size:.7rem;font-weight:800;color:#e8c36a}
.picker-empty[b-mxzdoa80jb]{margin:.6rem 0;font-size:.78rem;color:#78849f;text-align:center}
.picker-foot[b-mxzdoa80jb]{flex:none;display:flex;justify-content:flex-end;padding:.8rem 1.2rem;border-top:1px solid #232d44}
.picker-done[b-mxzdoa80jb]{border:0;border-radius:9px;padding:.6rem 1.4rem;font-weight:800;font-size:.82rem;background:linear-gradient(135deg,#c69446,#e8c36a);color:#16110a;cursor:pointer}
@media (max-width:620px){
    .picker-toolbar[b-mxzdoa80jb]{flex-wrap:wrap}
    .picker-search[b-mxzdoa80jb]{flex-basis:100%}
}
/* /Components/Shared/OverlandView.razor.rz.scp.css */
/* The read-only world viewer (issue #438).

   The map is the content and the rail is a caption on it — the inverse of the studio, where the rails are
   where the work happens. So the canvas takes the room and the rail is a narrow strip that scrolls on its
   own; the page itself never does, because a map that slides away under the wheel cannot be read. */

.world-view[b-7v3t483jg7] {
    display: flex;
    flex-direction: column;
    gap: .6rem;
    height: calc(100dvh - 13rem);
    min-height: 28rem;
}

.world-view-bar[b-7v3t483jg7] {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: .3rem .7rem;
    flex: 0 0 auto;
    font-size: .8rem;
}

/* A world's name can be a copyright notice — Blackmarsh's is one — so it yields rather than pushing the
   scale and the hover caption off the strip. */
.world-view-bar strong[b-7v3t483jg7] {
    font-size: 1rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 26rem;
}

.world-view-gap[b-7v3t483jg7] {
    flex: 1 1 auto;
}

/* What the pointer is resting on. Reserved height, because a caption that appears and disappears reflows
   the bar under the reader's hand and makes the map twitch. */
.world-view-hover[b-7v3t483jg7] {
    min-height: 1.1em;
    min-width: 12rem;
    color: #e8a37f;
}

.world-view-scale[b-7v3t483jg7] {
    color: #9aa6bd;
    font-variant-numeric: tabular-nums;
}

.world-view-body[b-7v3t483jg7] {
    display: flex;
    /* ⛔ NO WRAP, and that is the second answer rather than the first. Wrapping the rail underneath was
       the obvious fix for the narrow case and it broke the tall one: a wrapped flex container stretches
       its LINES, so the canvas grew to 832px inside a 461px pane and half the map sat below the fold.
       Measured in a running browser, both times. The rail is narrower instead, and scrolls. */
    gap: .6rem;
    flex: 1 1 auto;
    min-height: 0;
}

/* Pixi's. ⛔ Nothing inside this element belongs to Blazor: a diff that removed the canvas would leave a
   running ticker drawing into nothing. */
.world-view-canvas[b-7v3t483jg7] {
    /* ⚠️ A real min-width, so the map can never be squeezed to the ribbon it was: measured at 110px in a
       floating pane, which reads as "the map did not load" rather than as a narrow map. */
    flex: 1 1 auto;
    min-width: 12rem;
    border: 1px solid #2c3448;
    border-radius: .4rem;
    overflow: hidden;
    background: #0b111d;
}

.world-view-rail[b-7v3t483jg7] {
    /* 11rem rather than 15: this pane is hosted in a floating window now, not only on a full Forge page,
       and the map is what the window is FOR. The rail scrolls, so nothing is lost by making it narrow. */
    flex: 0 0 11rem;
    overflow-y: auto;
    padding: .6rem;
    border: 1px solid #2c3448;
    border-radius: .4rem;
    background: #131a27;
    font-size: .78rem;
}

.world-view-rail h3[b-7v3t483jg7] {
    margin: .9rem 0 .35rem;
    font-size: .74rem;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: #9aa6bd;
}

.world-view-actions[b-7v3t483jg7] {
    display: flex;
    /* ⛔ WRAPS. The row held three short buttons and a fourth with a real sentence on it — "Centre on
       party" — pushed it past the rail's width, and a flex row that cannot wrap does not shrink, it
       CLIPS: Dave's screenshot showed a button reading "Ce … pa". The rail is a fixed narrow column by
       design, so anything added here has to be allowed a second line rather than assumed to fit. */
    flex-wrap: wrap;
    gap: .3rem;
    margin-bottom: .5rem;
}

/* The one that says where you are. Given the full width of the rail on its own line, because it is the
   button somebody reaches for when they are lost and a lost reader should not have to parse an
   abbreviation. */
.world-view-actions .world-view-find[b-7v3t483jg7] {
    flex: 1 0 100%;
}

.world-view-field[b-7v3t483jg7],
.world-view-check[b-7v3t483jg7] {
    display: block;
    margin: .4rem 0;
    color: #c9d3e4;
}

.world-view-field select[b-7v3t483jg7] {
    width: 100%;
    margin-top: .15rem;
}

.world-view-check[b-7v3t483jg7] {
    display: flex;
    align-items: center;
    gap: .35rem;
}

.world-view-hint[b-7v3t483jg7] {
    margin: .35rem 0 0;
    color: #8d98ad;
    font-size: .72rem;
    line-height: 1.35;
}

.world-view-list[b-7v3t483jg7] {
    list-style: none;
    margin: .3rem 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .2rem;
}

.world-view-list button[b-7v3t483jg7] {
    width: 100%;
    text-align: left;
    background: #1a2230;
    border: 1px solid #333d52;
    border-radius: .3rem;
    padding: .25rem .4rem;
    color: inherit;
    font: inherit;
    cursor: pointer;
}

.world-view-list button:hover[b-7v3t483jg7] {
    border-color: #4b5a76;
}

.world-view-list span[b-7v3t483jg7] {
    color: #8d98ad;
    font-size: .7rem;
    margin-left: .3rem;
}

.world-view-status[b-7v3t483jg7] {
    margin: 0;
    flex: 0 0 auto;
    font-size: .78rem;
    color: #9aa6bd;
}

.world-view-status.error[b-7v3t483jg7] {
    color: #f0a08a;
}

/* ── The rail's own handle (Dave, 2026-08-25: "give more room for the map"). ─────────────────────────
   ⛔ A sibling of the rail, never a child: a control inside the panel it collapses goes away with it.
   ⚠️ `align-self: flex-start` and a fixed width, so it is a tab on the edge of the map rather than a
   column that takes height from the flex row — a stretched button would be a 700px-tall arrow. */
.world-view-rail-toggle[b-7v3t483jg7] {
    flex: 0 0 auto;
    align-self: flex-start;
    width: 1.1rem;
    padding: .5rem 0;
    border: 1px solid #2c3448;
    border-radius: .4rem;
    background: #131a27;
    color: #9aa6bd;
    font-size: .8rem;
    line-height: 1;
    cursor: pointer;
}

.world-view-rail-toggle:hover[b-7v3t483jg7] {
    background: #1b2434;
    color: #d7dEEA;
}

/* ⛔ `hidden` is a presentation hint that `display: flex` on an ancestor does NOT override for the
   element itself — but a UA sheet's `[hidden] { display: none }` loses to any author rule setting
   display on the same element. Nothing here sets display on the rail, so this states the intent
   explicitly rather than relying on that ordering holding after the next edit. */
.world-view-rail[hidden][b-7v3t483jg7] {
    display: none;
}
/* /Components/Shared/Pager.razor.rz.scp.css */
/* The pager's own look, moved here from OptionPicker.razor.css when the control became a component (#1475). A
   scoped rule never reaches a child component, so OptionPicker's .picker-ghost does not style these buttons and
   this sheet carries its own copy of that look. */
.picker-pager[b-x7ih1g15p5]{flex:none;display:flex;align-items:center;justify-content:center;gap:.75rem;padding:.6rem 1.2rem;border-top:1px solid #232d44}
.picker-ghost[b-x7ih1g15p5]{border:1px solid #3a4568;border-radius:8px;background:transparent;color:#b7c2df;padding:.4rem .75rem;font-size:.74rem;font-weight:700;cursor:pointer}
.picker-ghost:hover[b-x7ih1g15p5]{border-color:#e8c36a;color:#f5e3b0}
.picker-ghost:disabled[b-x7ih1g15p5]{opacity:.4;cursor:default;border-color:#3a4568;color:#b7c2df}
.picker-page[b-x7ih1g15p5]{font-size:.74rem;color:#8b96b6}
/* /Components/Shared/PartyBand.razor.rz.scp.css */
/*
    The FenReach party band (#1496).

    ⭐⭐ AUTHORED AT 390px FIRST, like the fight band beside it. FenReach.razor.css opens by declaring "the product is
    a phone game, so the narrow layout is the real one and the desktop view is the accommodation".

    ⚠️ The palette is the fight band's — Georgia on marsh-dusk glass, #dcb763 gold, #0d1220 — and deliberately so:
    these two cards stack on the same screen during a fight, and two different dialects of the same idea read as one
    of them being unfinished. Nobody has chosen this look; it is the neighbour's. → memory `a-look-is-not-a-number`

    ⛔ The three-state switching at the bottom is the fight band's mechanism, class for class, because the two cards
    must collapse identically — they share one remembered choice (`KnownSettings.FenReachFightBand`).
*/

.party-band[b-k5qtfmermy] {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    gap: .45rem;
    flex: 0 0 auto;
    width: min(92rem, calc(100% - 5rem));
    margin-inline: auto;
    padding: .5rem .75rem;
    border: 1px solid #dcb76340;
    border-radius: 10px;
    background: #0d122099;
    color: #d9deea;
    font-family: Georgia, 'Times New Roman', serif;
}

/* --- the head row, present in all three states ------------------------------------------------ */

/* ⛔ ONE LINE AT 390px. Collapsed has to be genuinely small or it is not a collapse, so nothing in here may wrap by
   default: the tally is the only flexible part and it truncates. */
.party-head[b-k5qtfmermy] {
    display: flex;
    align-items: center;
    gap: .4rem;
    min-width: 0;
}

.party-mark[b-k5qtfmermy] {
    flex: 0 0 auto;
    font-size: .7rem;
    font-weight: 700;
    letter-spacing: .06em;
    color: #dcb763;
}

.party-tally[b-k5qtfmermy] {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: baseline;
    gap: .35rem;
    overflow: hidden;
}

.party-tally strong[b-k5qtfmermy] {
    font-size: .8rem;
    font-weight: 700;
    white-space: nowrap;
}

.party-tally em[b-k5qtfmermy] {
    flex: 0 0 auto;
    font-style: normal;
    font-size: .68rem;
    color: #7d879a;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The one door. Tinted like the band's Actions button, because it is the same kind of thing: the only control on a
   card that is otherwise a readout. */
.party-care[b-k5qtfmermy] {
    flex: 0 0 auto;
    border: 1px solid #6f9c72;
    border-radius: 7px;
    background: linear-gradient(135deg, #2c5c3a, #3d7a4e);
    color: #eafbee;
    padding: .2rem .55rem;
    font-family: inherit;
    font-size: .72rem;
    font-weight: 700;
    cursor: pointer;
}

.party-care:hover:not(:disabled)[b-k5qtfmermy] { filter: brightness(1.12); }
.party-care:disabled[b-k5qtfmermy] { opacity: .5; cursor: default; }

.party-toggle[b-k5qtfmermy] {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.55rem;
    height: 1.55rem;
    padding: 0;
    border: 1px solid #ffffff24;
    border-radius: 6px;
    background: transparent;
    color: #dcb763;
    cursor: pointer;
}

.party-toggle:hover[b-k5qtfmermy] { background: #ffffff12; }

.party-toggle svg[b-k5qtfmermy] {
    width: .82rem;
    height: .82rem;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
}

/* --- the rows, in both bodies ------------------------------------------------------------------ */

.chip-row[b-k5qtfmermy],
.roster-rows[b-k5qtfmermy] {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    gap: .35rem;
}

.chip-row[b-k5qtfmermy] { flex-wrap: wrap; }
.roster-rows[b-k5qtfmermy] { flex-direction: column; }

.chip-row > li[b-k5qtfmermy],
.roster-rows > li[b-k5qtfmermy] {
    display: flex;
    align-items: center;
    gap: .35rem;
    min-width: 0;
    padding: .22rem .4rem;
    border: 1px solid #ffffff14;
    border-radius: 7px;
    background: #ffffff08;
    font-size: .74rem;
}

.roster-rows > li[b-k5qtfmermy] { gap: .5rem; }

/* You, and whoever is acting, are findable without reading. ⚠️ Two different marks because they are two different
   facts and they are routinely true of different people. */
.chip-row > li.you[b-k5qtfmermy],
.roster-rows > li.you[b-k5qtfmermy] { border-color: #dcb76359; }

.chip-row > li.acting[b-k5qtfmermy],
.roster-rows > li.acting[b-k5qtfmermy] {
    border-color: #dcb763;
    background: #dcb7631f;
}

/* Down is dimmed rather than hidden — it is the row that matters most and the one nobody should have to hunt for. */
.chip-row > li.down[b-k5qtfmermy],
.roster-rows > li.down[b-k5qtfmermy] {
    border-color: #a5525259;
    background: #4a1e1e2e;
}

.chip-name[b-k5qtfmermy] {
    max-width: 8rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    border: 0;
    padding: 0;
    background: transparent;
    color: #f0e2bd;
    font-family: inherit;
    font-size: inherit;
    font-weight: 700;
    text-align: left;
    cursor: pointer;
}

.chip-name:hover[b-k5qtfmermy] { text-decoration: underline; }

.row-who[b-k5qtfmermy] {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: baseline;
    gap: .3rem;
}

.row-side[b-k5qtfmermy] {
    flex: 0 0 auto;
    font-size: .62rem;
    letter-spacing: .04em;
    color: #7d879a;
}

.row-vitals[b-k5qtfmermy] {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: .35rem;
}

.row-ac[b-k5qtfmermy] {
    font-size: .66rem;
    color: #8e9ab8;
    white-space: nowrap;
}

.vitals[b-k5qtfmermy] {
    display: inline-flex;
    align-items: center;
    gap: .25rem;
}

.hp[b-k5qtfmermy] {
    font-variant-numeric: tabular-nums;
    font-size: .72rem;
    white-space: nowrap;
}

.temp-hp[b-k5qtfmermy] {
    font-size: .66rem;
    font-weight: 700;
    color: #8fd0e8;
}

/* The bar is the glanceable half; the number is the exact one. Both, because Dave asked to play without opening a
   sheet and a bar alone cannot answer "can they take one more hit". */
.hp-bar[b-k5qtfmermy] {
    display: inline-block;
    width: 2.6rem;
    height: .3rem;
    border-radius: 999px;
    background: #ffffff1a;
    overflow: hidden;
}

.hp-bar > span[b-k5qtfmermy] {
    display: block;
    height: 100%;
    background: #7d879a;
}

.hp-bar.hale > span[b-k5qtfmermy] { background: #6ee7b7; }
.hp-bar.hurt > span[b-k5qtfmermy] { background: #e8c36a; }
.hp-bar.bloodied > span[b-k5qtfmermy] { background: #e89a5a; }
.hp-bar.dire > span[b-k5qtfmermy] { background: #e86a6a; }

.marks[b-k5qtfmermy] {
    flex: 0 0 auto;
    display: flex;
    flex-wrap: wrap;
    gap: .2rem;
}

.mark[b-k5qtfmermy] {
    padding: .05rem .3rem;
    border: 1px solid #ffffff1f;
    border-radius: 5px;
    font-size: .62rem;
    color: #c3cbdd;
}

.mark.down[b-k5qtfmermy] {
    border-color: #a55252;
    background: #4a1e1e66;
    color: #ffc9c9;
    font-weight: 700;
}

/* --- the three states ------------------------------------------------------------------------- */

/* ⛔ The same switching the fight band uses. A chosen state wins outright; `by-width` hands it to the media query,
   which is the one decision the server cannot make. */
.party-band[data-view="band"] .party-roster[b-k5qtfmermy],
.party-band[data-view="roster"] .party-chips[b-k5qtfmermy],
.party-band[data-view="collapsed"] .party-chips[b-k5qtfmermy],
.party-band[data-view="collapsed"] .party-roster[b-k5qtfmermy] { display: none; }

.party-band[data-view="by-width"] .party-roster[b-k5qtfmermy] { display: none; }

.party-band .glyph-in[b-k5qtfmermy] { display: none; }
.party-band[data-view="roster"] .glyph-out[b-k5qtfmermy] { display: none; }
.party-band[data-view="roster"] .glyph-in[b-k5qtfmermy] { display: inline; }

@media (min-width: 60rem) {
    .party-band[data-view="by-width"] .party-chips[b-k5qtfmermy] { display: none; }
    .party-band[data-view="by-width"] .party-roster[b-k5qtfmermy] { display: block; }
    .party-band[data-view="by-width"] .glyph-out[b-k5qtfmermy] { display: none; }
    .party-band[data-view="by-width"] .glyph-in[b-k5qtfmermy] { display: inline; }
}
/* /Components/Shared/PartyCare.razor.rz.scp.css */
/*
    The Care popup (#1496) — healing the rules engine resolves, opened from the party band's door.

    ⭐ AUTHORED AT 390px FIRST: a full-screen sheet on a phone, a centred card from 40rem up. The same shape
    `FightActions.razor.css` takes, and for the same reason — these are the two popups this surface has, and one of
    them behaving differently would read as a bug rather than as a choice.

    ⚠️ The palette is the bands' — Georgia on marsh-dusk glass, #dcb763 gold. Nobody has chosen this look.
    → memory `a-look-is-not-a-number`
*/

.care-veil[b-iyiax4y51w] {
    position: fixed;
    inset: 0;
    z-index: var(--dm-layer-dialog, 1000);
    display: flex;
    align-items: stretch;
    justify-content: center;
    background: #05070cd9;
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
}

.care-sheet[b-iyiax4y51w] {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    width: 100%;
    max-height: 100%;
    border: 1px solid #dcb76340;
    background: linear-gradient(160deg, #0d1220fa, #0a0e18fa);
    color: #d9deea;
    font-family: Georgia, 'Times New Roman', serif;
}

.care-head[b-iyiax4y51w] {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex: 0 0 auto;
    padding: .6rem .8rem;
    border-bottom: 1px solid #dcb7631f;
}

.care-title[b-iyiax4y51w] {
    flex: 1 1 auto;
    font-size: .95rem;
    color: #f0e2bd;
}

.care-close[b-iyiax4y51w] {
    flex: 0 0 auto;
    width: 1.8rem;
    height: 1.8rem;
    border: 1px solid #ffffff24;
    border-radius: 6px;
    background: transparent;
    color: #d9deea;
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
}

.care-close:hover[b-iyiax4y51w] { background: #ffffff12; }

/* ⚠️ The BODY scrolls, never the sheet: a phone with six party members and a caster's worth of verbs is taller than
   the viewport, and a scrolling sheet takes the close button off screen with it. */
.care-body[b-iyiax4y51w] {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: .7rem .8rem;
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.care-hint[b-iyiax4y51w] {
    margin: 0;
    font-size: .68rem;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: #8b96b6;
}

.care-none[b-iyiax4y51w] {
    margin: 0;
    font-size: .8rem;
    color: #a8b2c6;
    line-height: 1.5;
}

.care-people[b-iyiax4y51w],
.care-list[b-iyiax4y51w] {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: .3rem;
}

.care-people[b-iyiax4y51w] { gap: .25rem; }

.care-person[b-iyiax4y51w] {
    display: flex;
    align-items: baseline;
    gap: .45rem;
    width: 100%;
    text-align: left;
    padding: .4rem .55rem;
    border: 1px solid #ffffff1a;
    border-radius: 8px;
    background: #ffffff08;
    color: #d9deea;
    font-family: inherit;
    font-size: .8rem;
    cursor: pointer;
}

.care-person:hover:not(:disabled)[b-iyiax4y51w] { background: #ffffff14; }
.care-person:disabled[b-iyiax4y51w] { opacity: .55; cursor: default; }

.care-person.chosen[b-iyiax4y51w] {
    border-color: #dcb763;
    background: #dcb7631f;
}

.person-name[b-iyiax4y51w] { flex: 1 1 auto; font-weight: 700; color: #f0e2bd; }
.person-hp[b-iyiax4y51w] { flex: 0 0 auto; font-variant-numeric: tabular-nums; font-size: .74rem; }

.person-down[b-iyiax4y51w] {
    flex: 0 0 auto;
    width: 100%;
    font-size: .66rem;
    color: #ffc9c9;
}

.care-healer[b-iyiax4y51w] { display: flex; flex-direction: column; gap: .25rem; }

.care-group[b-iyiax4y51w] {
    margin: .2rem 0 0;
    font-size: .74rem;
    font-weight: 700;
    color: #dcb763;
}

.care-row[b-iyiax4y51w] {
    display: flex;
    flex-direction: column;
    gap: .15rem;
}

.care-row > button[b-iyiax4y51w] {
    display: flex;
    align-items: baseline;
    gap: .45rem;
    width: 100%;
    text-align: left;
    padding: .4rem .55rem;
    border: 1px solid #6f9c7259;
    border-radius: 8px;
    background: #2c5c3a33;
    color: #eafbee;
    font-family: inherit;
    font-size: .8rem;
    cursor: pointer;
}

.care-row > button:hover:not(:disabled)[b-iyiax4y51w] { background: #2c5c3a59; }

/* ⛔ A refused row stays legible. It is not decoration — its REASON is the useful part of it, and a row greyed to
   the point of being unreadable hides the only thing that tells a player what to do next. */
.care-row > button:disabled[b-iyiax4y51w] { opacity: .55; cursor: default; }

.care-row.refused > button[b-iyiax4y51w] {
    border-color: #ffffff1a;
    background: #ffffff08;
    color: #a8b2c6;
}

.row-name[b-iyiax4y51w] { flex: 1 1 auto; font-weight: 700; }
.row-roll[b-iyiax4y51w] { flex: 0 0 auto; font-size: .66rem; color: #9fb0dc; }

.row-why[b-iyiax4y51w] {
    padding: 0 .55rem;
    font-size: .68rem;
    color: #c8a06a;
    line-height: 1.45;
}

.care-feedback[b-iyiax4y51w] {
    margin: 0;
    padding: .4rem .55rem;
    border: 1px solid #c8a06a59;
    border-radius: 8px;
    background: #3a2a1233;
    font-size: .74rem;
    color: #e8d3a8;
    line-height: 1.45;
}

.care-foot[b-iyiax4y51w] {
    flex: 0 0 auto;
    padding: .5rem .8rem;
    border-top: 1px solid #dcb7631f;
}

/* ⭐ Said on the popup, permanently. It is the promise the ask was about — "without the DM" — and a player has no
   other way to know that pressing this does not wake the narrator and spend a turn. */
.care-note[b-iyiax4y51w] {
    font-size: .66rem;
    color: #7d879a;
}

@media (min-width: 40rem) {
    .care-veil[b-iyiax4y51w] { align-items: center; padding: 1.5rem; }
    .care-sheet[b-iyiax4y51w] {
        width: min(30rem, 100%);
        max-height: min(40rem, 100%);
        border-radius: 12px;
        box-shadow: 0 30px 90px #03060db0;
    }
}
/* /Components/Shared/PassedPlaceTravel.razor.rz.scp.css */
/* #1504. The Travel here beside an engine-written "You passed ..." line. Quiet, like the quest window's. */
.passed-travel[b-k51gkpkrrd] {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    margin-left: 0.5rem;
}

.passed-travel-go[b-k51gkpkrrd] {
    font: inherit;
    font-size: 0.82rem;
    padding: 0.1rem 0.55rem;
    border-radius: 4px;
    border: 1px solid rgba(232, 208, 138, 0.55);
    background: transparent;
    color: #e8d08a;
    cursor: pointer;
}

.passed-travel-go:hover:not(:disabled)[b-k51gkpkrrd] {
    background: rgba(232, 208, 138, 0.12);
}

.passed-travel-go:disabled[b-k51gkpkrrd] {
    opacity: 0.5;
    cursor: default;
}

.passed-travel-refused[b-k51gkpkrrd] {
    font-size: 0.8rem;
    opacity: 0.8;
}
/* /Components/Shared/Pf2eSheetPanel.razor.rz.scp.css */
/* The printed sheet's colours: cream paper, deep green rules, oxblood headings. Kept close on purpose
   — a player who knows the Player Core sheet should recognise this at a glance — but it is our own
   layout, drawn in CSS, with none of Paizo's artwork or logotype. */
.pf2e-sheet[b-7gm44uy52d]{--ink:#241d16;--paper:#f5efe1;--rule:#2c4636;--accent:#7a2617;--muted:#6b6154;color:var(--ink);background:var(--paper);padding:1.1rem 1.25rem 1.35rem;font-size:.82rem}
.pf2e-sheet h4[b-7gm44uy52d]{margin:0 0 .55rem;color:var(--rule);font:700 .72rem/1 ui-monospace,monospace;letter-spacing:.14em;text-transform:uppercase;border-bottom:2px solid var(--rule);padding-bottom:.3rem}

.pf2e-banner[b-7gm44uy52d]{display:flex;align-items:flex-end;gap:.9rem;flex-wrap:wrap;padding-bottom:.8rem;margin-bottom:.9rem;border-bottom:3px double var(--rule)}
.pf2e-identity[b-7gm44uy52d]{flex:1;min-width:14rem}
.pf2e-eyebrow[b-7gm44uy52d]{margin:0;color:var(--muted);font:700 .6rem/1 ui-monospace,monospace;letter-spacing:.16em;text-transform:uppercase}
.pf2e-identity h3[b-7gm44uy52d]{margin:.1rem 0 .2rem;color:var(--accent);font:700 1.6rem/1.05 Georgia,serif}
.pf2e-lineage[b-7gm44uy52d]{margin:0;color:var(--muted);font-size:.78rem}
.pf2e-badge[b-7gm44uy52d]{display:grid;justify-items:center;gap:.1rem;min-width:4.2rem;padding:.4rem .6rem;border:1px solid var(--rule);border-radius:.35rem;background:#ffffff8c}
.pf2e-badge small[b-7gm44uy52d]{color:var(--muted);font:700 .58rem/1 ui-monospace,monospace;letter-spacing:.12em;text-transform:uppercase}
.pf2e-badge strong[b-7gm44uy52d]{font:700 1.15rem/1 Georgia,serif;color:var(--rule)}

.pf2e-block[b-7gm44uy52d]{margin-bottom:1rem}
.pf2e-columns[b-7gm44uy52d]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem}
.pf2e-subhead[b-7gm44uy52d]{margin:.6rem 0 .3rem;color:var(--accent);font:700 .7rem/1 ui-monospace,monospace;letter-spacing:.1em;text-transform:uppercase}
.pf2e-note[b-7gm44uy52d]{margin:.45rem 0 0;color:var(--muted);font-size:.75rem;line-height:1.5}
.pf2e-note b[b-7gm44uy52d]{color:var(--rule)}
.pf2e-empty[b-7gm44uy52d]{margin:0;color:var(--muted);font-style:italic}

.pf2e-attributes[b-7gm44uy52d]{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:.45rem}
.pf2e-attribute[b-7gm44uy52d]{display:grid;justify-items:center;gap:.1rem;padding:.5rem .3rem;border:1px solid #c9bda4;border-radius:.35rem;background:#fffdf7}
.pf2e-attribute.key[b-7gm44uy52d]{border-color:var(--accent);box-shadow:inset 0 0 0 1px #7a261733}
.pf2e-attr-name[b-7gm44uy52d]{color:var(--muted);font:700 .56rem/1.1 ui-monospace,monospace;letter-spacing:.06em;text-transform:uppercase;text-align:center}
.pf2e-attribute strong[b-7gm44uy52d]{font:700 1.35rem/1 Georgia,serif;color:var(--rule)}
.pf2e-attr-score[b-7gm44uy52d]{color:var(--muted);font-size:.68rem}
.pf2e-attribute em[b-7gm44uy52d]{color:var(--accent);font-style:normal;font-size:.55rem;letter-spacing:.06em;text-transform:uppercase}

.pf2e-stat-row[b-7gm44uy52d]{display:flex;gap:.5rem;flex-wrap:wrap}
.pf2e-stat[b-7gm44uy52d]{flex:1;min-width:6rem;display:grid;gap:.1rem;padding:.5rem .6rem;border:1px solid #c9bda4;border-radius:.35rem;background:#fffdf7}
.pf2e-stat small[b-7gm44uy52d]{color:var(--muted);font:700 .58rem/1 ui-monospace,monospace;letter-spacing:.1em;text-transform:uppercase}
.pf2e-stat strong[b-7gm44uy52d]{font:700 1.3rem/1 Georgia,serif;color:var(--rule)}
.pf2e-stat.big strong[b-7gm44uy52d]{font-size:1.7rem}
.pf2e-stat span[b-7gm44uy52d]{color:var(--muted);font-size:.66rem}
/* A stat's headline number with its rank box beside it, on one line inside the box's grid. */
.pf2e-stat-figure[b-7gm44uy52d]{display:flex;align-items:center;gap:.5rem}

/* The T/E/M/L box the printed sheet puts beside every trained thing. */
.pf2e-rank[b-7gm44uy52d]{display:grid;place-items:center;width:1.15rem;height:1.15rem;border:1px solid var(--rule);border-radius:.2rem;color:var(--rule);font:700 .6rem/1 ui-monospace,monospace;flex-shrink:0}
/* .pf2e-stat span is more specific than .pf2e-rank, so inside a stat box the rank would otherwise
   come out muted and at the breakdown line's size. Restate it at the higher specificity. */
.pf2e-stat .pf2e-rank[b-7gm44uy52d]{color:var(--rule);font:700 .6rem/1 ui-monospace,monospace}
.pf2e-line-list[b-7gm44uy52d],.pf2e-skill-grid[b-7gm44uy52d],.pf2e-feats[b-7gm44uy52d]{list-style:none;margin:0;padding:0}
.pf2e-line-list li[b-7gm44uy52d],.pf2e-skill-grid li[b-7gm44uy52d]{display:flex;align-items:center;gap:.5rem;padding:.28rem .35rem;border-bottom:1px dotted #cabfa8}
.pf2e-line-list li:last-child[b-7gm44uy52d],.pf2e-skill-grid li:last-child[b-7gm44uy52d]{border-bottom:0}
.pf2e-line-list strong[b-7gm44uy52d],.pf2e-skill-grid strong[b-7gm44uy52d]{flex:1;font-weight:600}
.pf2e-line-list b[b-7gm44uy52d],.pf2e-skill-grid b[b-7gm44uy52d]{min-width:2.2rem;text-align:right;color:var(--rule);font:700 .92rem/1 Georgia,serif}
.pf2e-line-list span:last-child[b-7gm44uy52d],.pf2e-skill-grid span:last-child[b-7gm44uy52d]{min-width:8.5rem;text-align:right;color:var(--muted);font-size:.68rem}
.pf2e-line-list.plain li[b-7gm44uy52d]{gap:.4rem}
.pf2e-skill-grid[b-7gm44uy52d]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:1rem}
.pf2e-skill-grid li.untrained[b-7gm44uy52d]{opacity:.55}

.pf2e-table[b-7gm44uy52d]{width:100%;border-collapse:collapse;font-size:.76rem}
.pf2e-table th[b-7gm44uy52d]{padding:.3rem .4rem;border-bottom:1px solid var(--rule);color:var(--muted);font:700 .6rem/1 ui-monospace,monospace;letter-spacing:.08em;text-transform:uppercase;text-align:left}
.pf2e-table td[b-7gm44uy52d]{padding:.35rem .4rem;border-bottom:1px dotted #cabfa8;vertical-align:top}
.pf2e-table td b[b-7gm44uy52d]{color:var(--rule);font:700 .92rem/1 Georgia,serif}
.pf2e-table em[b-7gm44uy52d]{display:block;color:var(--muted);font-style:normal;font-size:.64rem}

.pf2e-feats li[b-7gm44uy52d]{padding:.4rem 0;border-bottom:1px dotted #cabfa8}
.pf2e-feats li:last-child[b-7gm44uy52d]{border-bottom:0}
.pf2e-feats strong[b-7gm44uy52d]{color:var(--rule)}
.pf2e-feats p[b-7gm44uy52d]{margin:.15rem 0 0;color:var(--muted);font-size:.72rem;line-height:1.5}
.pf2e-actions[b-7gm44uy52d]{margin-left:.35rem;color:var(--accent)}

.pf2e-legal[b-7gm44uy52d]{margin:1rem 0 0;padding-top:.6rem;border-top:1px solid #cabfa8;color:#8a8073;font-size:.62rem;line-height:1.5}

@media(max-width:760px){
    .pf2e-columns[b-7gm44uy52d],.pf2e-skill-grid[b-7gm44uy52d]{grid-template-columns:1fr}
    .pf2e-attributes[b-7gm44uy52d]{grid-template-columns:repeat(3,minmax(0,1fr))}
}

/* Coins get one box per denomination, always all four: an empty gold box is information. */
.pf2e-coins[b-7gm44uy52d]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.45rem}
.pf2e-coin[b-7gm44uy52d]{display:grid;justify-items:center;gap:.15rem;padding:.5rem .3rem;border:1px solid #c9bda4;border-radius:.35rem;background:#fffdf7}
.pf2e-coin small[b-7gm44uy52d]{color:var(--muted);font:700 .56rem/1 ui-monospace,monospace;letter-spacing:.1em;text-transform:uppercase}
.pf2e-coin strong[b-7gm44uy52d]{font:700 1.25rem/1 Georgia,serif}
.pf2e-coin.pp strong[b-7gm44uy52d]{color:#5c6b7a}
.pf2e-coin.gp strong[b-7gm44uy52d]{color:#8a6a1f}
.pf2e-coin.sp strong[b-7gm44uy52d]{color:#6d6e72}
.pf2e-coin.cp strong[b-7gm44uy52d]{color:#7a4a2a}
@media(max-width:760px){.pf2e-coins[b-7gm44uy52d]{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* ── The equip toggle, matching the Simple View's (Dave, 2026-09-07) ────────────────────────────────
   Deliberately the same shape in both views: a player who learns it on one sheet must not have to learn
   it again on the other. */
.equip-button[b-7gm44uy52d] {
    font-size: .66rem;
    letter-spacing: .04em;
    text-transform: uppercase;
    padding: .1rem .35rem;
    border: 1px solid #3a4258;
    border-radius: .3rem;
    background: #1b2231;
    color: #9aa6bd;
    cursor: pointer;
}

.equip-button:hover:not(:disabled)[b-7gm44uy52d] { color: #f0e6cc; border-color: #d9c68f; }
.equip-button:disabled[b-7gm44uy52d] { opacity: .5; cursor: default; }

.equip-button.on[b-7gm44uy52d] {
    background: #2b2415;
    border-color: #d9c68f;
    color: #f0e6cc;
    font-weight: 700;
}
/* /Components/Shared/PortraitPicker.razor.rz.scp.css */
/* Modelled on OptionPicker.razor.css, which is the house dialog. Own class names and own file because
   scoped CSS reaches only its own component's markup — a rule written over there cannot style this. */
.portrait-picker-backdrop[b-ir0vqyvst8]{position:fixed;inset:0;background:#04060cc4;backdrop-filter:blur(2px);display:grid;place-items:center;padding:1rem;z-index:var(--dm-layer-dialog,1000)}
.portrait-picker[b-ir0vqyvst8]{display:flex;flex-direction:column;width:min(860px,100%);max-height:min(86vh,760px);background:linear-gradient(155deg,#151b2cf7,#0d1220f7);border:1px solid #3a4568;border-radius:16px;box-shadow:0 34px 100px #02040ac0;overflow:hidden;outline:none}
.portrait-picker-head[b-ir0vqyvst8]{flex:none;display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;padding:1.1rem 1.2rem .8rem}
.portrait-picker-head h2[b-ir0vqyvst8]{margin:0;font-family:Georgia,serif;font-size:1.12rem;color:#faf7ef}
.portrait-picker-count[b-ir0vqyvst8]{margin:.25rem 0 0;font-size:.72rem;color:#8b96b6}
.portrait-picker-toolbar[b-ir0vqyvst8]{flex:none;display:flex;gap:.5rem;padding:0 1.2rem .8rem}
.portrait-picker-search[b-ir0vqyvst8]{flex:1;min-width:0;background:#141b2b;color:#eef2fb;border:1px solid #34405c;border-radius:9px;padding:.6rem .8rem;font:inherit;font-size:.85rem}
.portrait-picker-search:focus[b-ir0vqyvst8]{outline:none;border-color:#e8c36a}

/* min-height:0 is load-bearing on a scrolling flex child: without it the automatic minimum size keeps
   the grid at its full content height and the panel grows past max-height instead of scrolling. */
.portrait-picker-grid[b-ir0vqyvst8]{flex:1 1 auto;min-height:0;overflow-y:auto;padding:0 1.2rem 1rem;display:grid;grid-template-columns:repeat(auto-fill,minmax(128px,1fr));gap:.6rem;align-content:start}
.portrait-card[b-ir0vqyvst8]{position:relative;display:flex;flex-direction:column;gap:.35rem;padding:.4rem;border:1px solid #283449;border-radius:10px;background:#111827b0;color:#e7eaf2;cursor:pointer;text-align:left}
.portrait-card:hover[b-ir0vqyvst8]{border-color:#4a5c88;background:#16203699}
.portrait-card.chosen[b-ir0vqyvst8]{border-color:#e8c36a;background:#1c1a12}
.portrait-card img[b-ir0vqyvst8]{width:100%;aspect-ratio:1;object-fit:cover;border-radius:7px;background:#0b1120}
.portrait-card-name[b-ir0vqyvst8]{font-size:.72rem;line-height:1.3;color:#cdd6ea;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.portrait-card-mark[b-ir0vqyvst8]{position:absolute;top:.55rem;right:.55rem;font-size:.72rem;font-weight:800;color:#16110a;background:#e8c36a;border-radius:999px;padding:.05rem .35rem}

.portrait-picker-empty[b-ir0vqyvst8]{margin:.4rem 1.2rem 1.3rem;font-size:.78rem;line-height:1.55;color:#8b96b6;text-align:center}
.portrait-picker-note[b-ir0vqyvst8]{flex:none;margin:0;padding:0 1.2rem 1rem;font-size:.72rem;color:#78849f;text-align:center}
/* /Components/Shared/PropLayer.razor.rz.scp.css */
.map-prop[b-xl996s0rqn]{z-index:1;margin:0;padding:0;border:0;background:transparent;display:grid;place-items:center;cursor:pointer}
.map-prop canvas[b-xl996s0rqn]{width:100%;height:100%;image-rendering:pixelated;pointer-events:none;filter:drop-shadow(0 2px 2px #0006)}
.map-prop:hover[b-xl996s0rqn]{outline:2px solid #f4cb68aa;outline-offset:-2px}
/* /Components/Shared/ReactionOffer.razor.rz.scp.css */
/*
    #1380. The reaction offer's own three lines inside EncounterCard's note. The frame, the palette and
    the buttons are the card's; nothing here restyles them.

    ⚠️ Nobody has chosen this look — it borrows the card's own spacing and its quiet text colour. Dave
    reviews UI himself. → memory `dave-reviews-ui-himself`, `a-look-is-not-a-number`
*/

.reaction-trigger[b-qslq40skeh] {
    margin: 0 0 0.35rem;
    font-style: italic;
    opacity: 0.85;
}

.reaction-ask[b-qslq40skeh] {
    margin: 0 0 0.6rem;
}

.reaction-standing[b-qslq40skeh] {
    margin: 0.6rem 0 0;
    font-size: 0.85em;
    opacity: 0.7;
}
/* /Components/Shared/ReactionStandingChoices.razor.rz.scp.css */
/*
    #1380. The sheet's standing-choice rows. Borrowed from the sheet's own settings spacing; nobody has
    chosen this look. Dave reviews UI himself. → memory `dave-reviews-ui-himself`, `a-look-is-not-a-number`
*/

.reaction-standings[b-2lxfhbx5be] {
    margin-top: 0.9rem;
}

.reaction-standings h4[b-2lxfhbx5be] {
    margin: 0 0 0.2rem;
    font-size: 0.95rem;
}

.reaction-standings-hint[b-2lxfhbx5be] {
    margin: 0 0 0.5rem;
    font-size: 0.82rem;
    opacity: 0.75;
}

.reaction-standing-row[b-2lxfhbx5be] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 0.75rem;
    align-items: center;
    justify-content: space-between;
    padding: 0.35rem 0;
    border-top: 1px solid rgba(128, 128, 128, 0.25);
}

.reaction-standing-what[b-2lxfhbx5be] {
    display: flex;
    flex-direction: column;
    min-width: 8rem;
}

.reaction-standing-name[b-2lxfhbx5be] {
    font-weight: 600;
}

.reaction-standing-trigger[b-2lxfhbx5be] {
    font-size: 0.78rem;
    font-style: italic;
    opacity: 0.7;
}

.reaction-standing-choice[b-2lxfhbx5be] {
    display: flex;
    gap: 0.25rem;
}

.reaction-standing-choice button[b-2lxfhbx5be] {
    padding: 0.15rem 0.5rem;
    font-size: 0.8rem;
    cursor: pointer;
}

.reaction-standing-choice button.chosen[b-2lxfhbx5be] {
    font-weight: 700;
    text-decoration: underline;
}

.reaction-standing-choice button:disabled[b-2lxfhbx5be] {
    cursor: default;
    opacity: 0.6;
}

.reaction-standings-notice[b-2lxfhbx5be] {
    margin: 0.5rem 0 0;
    font-size: 0.82rem;
}
/* /Components/Shared/RegionMapPanel.razor.rz.scp.css */
.region-map-stage[b-jlp3d4kp8l] {
    position: relative;
    line-height: 0;
    border-radius: 6px;
    overflow: hidden;
}

.region-map-stage img[b-jlp3d4kp8l] {
    display: block;
    width: 100%;
    height: auto;
}

/* Centred on its own coordinate rather than hung from its top-left corner: the pin marks a point, and an
   offset marker is a marker that lies by half its own size. */
.region-pin[b-jlp3d4kp8l] {
    position: absolute;
    width: 14px;
    height: 14px;
    margin: -7px 0 0 -7px;
    border-radius: 50%;
    background: #e8503a;
    border: 2px solid #fff;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, .55), 0 1px 4px rgba(0, 0, 0, .5);
    pointer-events: none;
}
/* /Components/Shared/ReleaseNotesList.razor.rz.scp.css */
.running[b-km5ohqml6m],
.empty[b-km5ohqml6m] {
    margin: 0 0 2rem;
    color: #6d7688;
}

.release[b-km5ohqml6m] {
    border-top: 1px solid #00000018;
    padding: 1.5rem 0 .5rem;
}

.release.current[b-km5ohqml6m] {
    border-top-color: #dcb76366;
}

.release h2[b-km5ohqml6m] {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: .6rem;
    margin: 0;
    font-family: Georgia, 'Times New Roman', serif;
    font-size: 1.35rem;
}

.badge[b-km5ohqml6m] {
    font-family: inherit;
    font-size: .68rem;
    font-weight: 800;
    letter-spacing: .1em;
    text-transform: uppercase;
    padding: .18rem .5rem;
    border-radius: 999px;
    background: #dcb763;
    color: #16110a;
}

.released[b-km5ohqml6m] {
    margin: .15rem 0 .8rem;
    font-size: .85rem;
    color: #6d7688;
}

.summary[b-km5ohqml6m] {
    margin: 0 0 1rem;
}

.release h3[b-km5ohqml6m] {
    margin: 1.1rem 0 .35rem;
    font-size: .78rem;
    font-weight: 800;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: #6d7688;
}

.release ul[b-km5ohqml6m] {
    margin: 0;
    padding-left: 1.2rem;
}

.release li[b-km5ohqml6m] {
    margin: .3rem 0;
    line-height: 1.55;
}

/* ⭐ A past release is a closed <details>: its heading is the thing clicked, with a turning marker. */
.release > summary[b-km5ohqml6m] {
    cursor: pointer;
    list-style: none;
}

.release > summary[b-km5ohqml6m]::-webkit-details-marker {
    display: none;
}

.release > summary h2[b-km5ohqml6m]::before {
    content: "B8";
    display: inline-block;
    width: 1rem;
    font-size: .9rem;
    color: #6d7688;
    transition: transform .15s;
}

.release[open] > summary h2[b-km5ohqml6m]::before {
    transform: rotate(90deg);
}

.release:not([open]) > summary .released[b-km5ohqml6m] {
    margin-bottom: .2rem;
}

/* The `####` sub-groups inside a section: quieter than the section heading, louder than a bullet. */
.release h4[b-km5ohqml6m] {
    margin: .8rem 0 .2rem;
    font-size: .95rem;
    font-weight: 700;
}

/* ⚠️ The in-game popup is dark; the page's greys and hairlines disappear on it. */
.release-notes.dark .running[b-km5ohqml6m],
.release-notes.dark .empty[b-km5ohqml6m],
.release-notes.dark .released[b-km5ohqml6m],
.release-notes.dark .release h3[b-km5ohqml6m],
.release-notes.dark .release > summary h2[b-km5ohqml6m]::before {
    color: #aeb5c2;
}

.release-notes.dark .release[b-km5ohqml6m] {
    border-top-color: #ffffff1a;
}

.release-notes.dark .release.current[b-km5ohqml6m] {
    border-top-color: #dcb76366;
}
/* /Components/Shared/ResourcePoolEditor.razor.rz.scp.css */
/* The resource editor's own chrome (issue #135).

   The list and the dialog belong to DraftListEditor and are styled over there. What is left here is the
   unrecognised-key advisory this editor adds.

   ⚠️ Scoped to THIS component even though the markup renders inside DraftListEditor's rows and dialog.
   That is correct: a RenderFragment carries the scope id of the component that AUTHORED it, not the one
   that renders it — which is why a rule written over there would match none of this. */

/* ⛔ AMBER, NEVER RED, the same palette as CreatureModifierEditor's advisory because it is the same
   advisory about the same kind of value. An unknown key is allowed: another pack or another ruleset may
   define it, nothing refuses to save, and a red field would say the opposite of what the note says. */
.resource-unknown[b-yk6w9qi6ke] {
    flex: none;
    margin-left: .4rem;
    padding: .05rem .35rem;
    border-radius: 999px;
    border: 1px solid #5a4a2e;
    background: #241d10;
    color: #d8bd7c;
    font-size: .62rem;
    font-weight: 700;
    letter-spacing: .02em;
    text-transform: uppercase;
    cursor: help;
}

.resource-unknown-note[b-yk6w9qi6ke] {
    margin: .15rem 0 0;
    padding: .35rem .5rem;
    border: 1px solid #5a4a2e;
    border-radius: 6px;
    background: #241d10;
    color: #d8bd7c;
    font-size: .74rem;
}

.resource-unknown-note code[b-yk6w9qi6ke] {
    color: #e8dcc0;
}
/* /Components/Shared/RuleEnvelopeCollectionEditor.razor.rz.scp.css */
/* The collection's heading, and what a definition says on its row (issue #135). The list and the dialog
   belong to DraftListEditor and are styled there.

   ⚠️ The row spans below are scoped to THIS component even though DraftListEditor renders them: a
   RenderFragment carries the scope id of the component that AUTHORED it, not the one that renders it. */

.rule-envelope-collection[b-gq276pn6rl] {
    display: grid;
    gap: .55rem;
}

.rule-collection-heading h3[b-gq276pn6rl],
.rule-collection-heading p[b-gq276pn6rl] {
    margin: 0;
}

.rule-collection-heading p[b-gq276pn6rl] {
    color: #a99c88;
    font-size: .8rem;
}

.envelope-id[b-gq276pn6rl] {
    flex: none;
    padding: .05rem .3rem;
    border-radius: 4px;
    background: #0b0f1a;
    color: #8e9ab8;
    font-size: .68rem;
}

/* The top-level keys of the rules block. It takes the slack and truncates: a definition can have a dozen
   and the row is a line. min-width:0 is what actually lets a flex item shrink below its content. */
.envelope-shape[b-gq276pn6rl] {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: #8e9ab8;
    font-size: .72rem;
}

/* An envelope whose rules are `{}` reads as empty rather than as blank, because those are different: the
   ruleset reads nothing off it, and that is worth seeing without opening it. */
.envelope-empty[b-gq276pn6rl] {
    flex: 1;
    min-width: 0;
    color: #8f8372;
    font-size: .72rem;
    font-style: italic;
}

.envelope-action[b-gq276pn6rl] {
    flex: none;
}
/* /Components/Shared/SceneGraphCanvas.razor.rz.scp.css */
.scene-graph-shell[b-zljcusravz] { display: grid; gap: .75rem; }

.scene-graph-toolbar[b-zljcusravz] { display: flex; flex-wrap: wrap; align-items: center; gap: .65rem; }
.scene-graph-toolbar button[b-zljcusravz] { border: 1px solid #756244; background: #211d17; color: #e9dcc4; padding: .45rem .7rem; cursor: pointer; border-radius: .3rem; }
.scene-graph-toolbar button:hover[b-zljcusravz] { background: #2c261d; }
.scene-graph-toolbar button.active[b-zljcusravz] { background: #4a3a1e; border-color: #e6b85c; color: #f3dfae; }
.scene-graph-toolbar .spacer[b-zljcusravz] { flex: 1; }
.scene-graph-toolbar .hint[b-zljcusravz] { color: #e6b85c; }
.scene-graph-toolbar .hint.problem[b-zljcusravz] { color: #d98c6a; }
.scene-graph-toolbar .legend[b-zljcusravz] { display: inline-flex; flex-wrap: wrap; gap: .75rem; color: #bdb09c; font-size: .85em; }
.scene-graph-toolbar .legend span[b-zljcusravz] { display: inline-flex; align-items: center; gap: .35rem; }
.scene-graph-toolbar .legend i[b-zljcusravz] { width: .8rem; height: .8rem; border-radius: .2rem; }

/* Scene kinds carry a colour so the shape of an adventure — how much talking, how much fighting —
   is legible at a glance without reading a single card. */
.legend i.kind-social[b-zljcusravz] { background: #68a7a1; }
.legend i.kind-exploration[b-zljcusravz] { background: #7aa35c; }
.legend i.kind-combat[b-zljcusravz] { background: #b66f6a; }
.legend i.kind-boss[b-zljcusravz] { background: #b9484a; }

/* The viewport scrolls, the surface inside it is as big as the story needs. Keeping the pixel size on
   an inner element is what stops the canvas pushing the whole page wide and making the WINDOW grow a
   scrollbar. min-width:0 is required because the parent is a grid: without it a grid item refuses to
   shrink below its content and the overflow never engages. */
.scene-graph-viewport[b-zljcusravz] { position: relative; min-width: 0; max-width: 100%; max-height: min(70vh, 46rem); overflow: auto; border: 1px solid #55462f; border-radius: .35rem; background-color: #16130f; resize: vertical; }
.scene-graph-surface[b-zljcusravz] { position: relative; background-image: radial-gradient(#453b2c 1px, transparent 1px); background-size: 22px 22px; }

.scene-edge-layer[b-zljcusravz] { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.scene-edge-layer marker path[b-zljcusravz] { fill: #82725a; }
.scene-edge[b-zljcusravz] { stroke: #82725a; stroke-width: 2.5; }
/* A connector the author routed by hand is drawn a shade brighter, so it is obvious which lines are
   deliberate and which are still following the automatic route. */
.scene-edge.bent[b-zljcusravz] { stroke: #9d8a6b; }
/* A gated way through is a dashed one: the party may not get to take it. */
.scene-edge.gated[b-zljcusravz] { stroke-dasharray: 7 5; stroke: #c2a05e; }
/* A route back the way the story came is drawn faint, so a loop does not read as a mistake. */
.scene-edge.backward[b-zljcusravz] { stroke: #5d5142; stroke-width: 2; opacity: .75; }

.scene-node[b-zljcusravz] { position: absolute; width: 184px; display: grid; border: 2px solid #6b5a3f; border-radius: .5rem; background: #29231b; box-shadow: 0 4px 12px #0008; cursor: grab; }
.scene-node:active[b-zljcusravz] { cursor: grabbing; }
.scene-node.kind-social[b-zljcusravz] { border-color: #68a7a1; }
.scene-node.kind-exploration[b-zljcusravz] { border-color: #7aa35c; }
.scene-node.kind-combat[b-zljcusravz] { border-color: #b66f6a; }
.scene-node.kind-boss[b-zljcusravz] { border-color: #b9484a; box-shadow: 0 4px 18px #b9484a44; }
.scene-node.selected[b-zljcusravz] { outline: 3px solid #f0ce88; }
.scene-node.link-start[b-zljcusravz] { box-shadow: 0 0 0 4px #68a7a188; }
/* A scene nothing leads to is the failure this whole view exists to make visible. */
.scene-node.unreachable[b-zljcusravz] { border-style: dashed; opacity: .82; }
.scene-node.unreachable[b-zljcusravz]::after { content: "unreachable"; position: absolute; top: -.65rem; right: .4rem; padding: 0 .35rem; font-size: .7em; color: #1a1610; background: #d98c6a; border-radius: .2rem; }

.scene-node-main[b-zljcusravz] { display: grid; gap: .25rem; width: 100%; padding: .55rem .6rem; text-align: left; color: #f3ead9; background: transparent; border: 0; cursor: pointer; }
.scene-node-title[b-zljcusravz] { display: flex; align-items: center; gap: .35rem; }
.scene-node-title strong[b-zljcusravz] { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.entry-pip[b-zljcusravz] { color: #e6b85c; }
.scene-node-main small[b-zljcusravz] { color: #b8aa91; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.scene-node-badges[b-zljcusravz] { display: flex; flex-wrap: wrap; gap: .3rem; }
.scene-node-badges .badge[b-zljcusravz] { padding: 0 .3rem; font-size: .72em; border-radius: .2rem; background: #1b1814; color: #cdbb99; border: 1px solid #4b4031; }
.scene-node-badges .badge.fight[b-zljcusravz] { color: #e2a09a; }
.scene-node-badges .badge.map[b-zljcusravz] { color: #a9bcd8; }
.scene-node-badges .badge.effect[b-zljcusravz] { color: #d8c48a; }
.scene-node-badges .badge.anchor[b-zljcusravz] { color: #9fc0bd; max-width: 9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.scene-node-actions[b-zljcusravz] { display: grid; grid-template-columns: repeat(3, 1fr); border-top: 1px solid #4b4031; }
.scene-node-actions button[b-zljcusravz] { color: #d8c49f; background: #1b1814; border: 0; border-right: 1px solid #3b3328; cursor: pointer; padding: .2rem 0; }
.scene-node-actions button:last-child[b-zljcusravz] { border-right: 0; }
.scene-node-actions button:hover:not(:disabled)[b-zljcusravz] { background: #2c261d; }
.scene-node-actions button:disabled[b-zljcusravz] { opacity: .35; cursor: default; }
.scene-node-actions button.danger:hover[b-zljcusravz] { background: #4a2020; color: #f0c0c0; }

.scene-graph-empty[b-zljcusravz] { padding: 2rem; border: 1px dashed #55462f; border-radius: .35rem; color: #bdb09c; }
.scene-graph-problems[b-zljcusravz] { display: grid; gap: .3rem; margin: 0; padding: .6rem .9rem .6rem 1.9rem; border: 1px solid #6d4a35; border-radius: .35rem; background: #241a15; color: #e0b49c; }

/* The grab handle on a connector's corner. Small and quiet until the pointer finds it. */
.edge-handle[b-zljcusravz] { position: absolute; width: 14px; height: 14px; border-radius: 50%; background: #2a241c; border: 2px solid #82725a; cursor: grab; opacity: .55; }
.edge-handle:hover[b-zljcusravz] { opacity: 1; border-color: #e6b85c; background: #3a3123; }
.edge-handle.bent[b-zljcusravz] { opacity: .9; border-color: #c2a05e; }
.edge-handle:active[b-zljcusravz] { cursor: grabbing; }

.scene-graph-toolbar .hint.quiet[b-zljcusravz] { color: #8f8372; }
.scene-graph-toolbar button:disabled[b-zljcusravz] { opacity: .4; cursor: default; }
/* /Components/Shared/ScenePicturePanel.razor.rz.scp.css */
/* A picture for an encounter or a den. The fallback strip and the real image are deliberately
   different shapes: one is "what this is", the other is "what is in it". */

.scene-picture[b-2cz2oqrsvk] {
    display: grid;
    gap: 0.45rem;
    margin: 0.6rem 0;
    padding: 0.6rem;
    border: 1px solid var(--forge-border-nested);
    border-radius: 8px;
    background: var(--forge-surface-1);
}

.scene-picture-head[b-2cz2oqrsvk] {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
}

.scene-picture-head strong[b-2cz2oqrsvk] { color: var(--forge-text); }

.scene-picture p[b-2cz2oqrsvk] { margin: 0; }

.scene-picture-current[b-2cz2oqrsvk] {
    width: 100%;
    max-height: 22rem;
    object-fit: contain;
    border-radius: 6px;
    background: var(--forge-surface-well);
}

/* The zero-cost default: portraits of what is in it, read as a row of tokens rather than as one
   picture, so nobody mistakes it for scene art somebody paid for. */
.scene-picture-fallback[b-2cz2oqrsvk] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.scene-picture-fallback img[b-2cz2oqrsvk] {
    width: 4.5rem;
    height: 4.5rem;
    object-fit: cover;
    border-radius: 6px;
    border: 1px solid var(--forge-border-quiet);
}

.scene-picture-shared[b-2cz2oqrsvk] {
    color: var(--forge-text-muted);
    font-size: 0.78rem;
}

.scene-picture-actions[b-2cz2oqrsvk] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

.scene-picture-actions input[b-2cz2oqrsvk] { flex: 1; min-width: 10rem; }

.scene-picture-compose[b-2cz2oqrsvk] { display: grid; gap: 0.4rem; }
.scene-picture-compose textarea[b-2cz2oqrsvk] { width: 100%; font-family: inherit; }

.scene-picture-browse[b-2cz2oqrsvk] { display: grid; gap: 0.4rem; }

.scene-picture-grid[b-2cz2oqrsvk] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
    gap: 0.4rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.scene-picture-grid button[b-2cz2oqrsvk] {
    display: grid;
    gap: 0.2rem;
    width: 100%;
    padding: 0.25rem;
    background: var(--forge-surface-2);
    border: 1px solid var(--forge-border-quiet);
    border-radius: 6px;
    color: var(--forge-text-muted);
    font-size: 0.72rem;
    cursor: pointer;
}

.scene-picture-grid button:hover[b-2cz2oqrsvk] { border-color: var(--forge-gold); }

.scene-picture-grid img[b-2cz2oqrsvk] {
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: 4px;
}

.scene-picture-error[b-2cz2oqrsvk] {
    padding: 0.35rem 0.55rem;
    border: 1px solid var(--forge-danger-border);
    border-radius: 6px;
    background: var(--forge-danger-surface);
    color: var(--forge-danger);
    font-size: 0.8rem;
}

/* The brief moved after the prompt was written and the words in the box are the author's own. Not an
   error — nothing is wrong and nothing is refused — so it wears the gold edge rather than the danger
   one, and sits inside the compose box where the words it is about are. */
.scene-picture-drift[b-2cz2oqrsvk] {
    padding: 0.35rem 0.55rem;
    border: 1px solid var(--forge-border-gold);
    border-radius: 6px;
    background: var(--forge-surface-notice);
    color: var(--forge-text-muted);
    font-size: 0.8rem;
}

.scene-picture-drift button[b-2cz2oqrsvk] {
    margin-left: 0.35rem;
}
/* /Components/Shared/SheetStatRow.razor.rz.scp.css */
/* ⚠️ THE SHEET IS WARM — parchment, leather and gold (#17130f / #1d1914 / #e3bd70). Every colour here
   comes from that set. A cold-blue row on a brown sheet reads as a control borrowed from another screen,
   which is half of what made the first attempt at this look wrong. */

/* The row is the resting state: a name, a figure, a rank letter. It never grows. */
.stat-row-host[b-fr8cyk2lxw] { position: relative; }

.stat-row[b-fr8cyk2lxw] {
    display: flex;
    align-items: baseline;
    gap: .5rem;
    width: 100%;
    font: inherit;
    color: inherit;
    text-align: left;
    background: transparent;
    border: 0;
    border-bottom: 1px solid #352d24;
    padding: .42rem .35rem;
    cursor: pointer;
}

.stat-row:hover[b-fr8cyk2lxw] { background: #262019; }

/* ⚠️ Focus needs its own mark: the hover fill is too close to the ground to serve as one, and this
   list is entirely keyboard-navigable. */
.stat-row:focus-visible[b-fr8cyk2lxw] { background: #262019; outline: none; box-shadow: inset 0 0 0 1px #d7aa55; }

.stat-row.open[b-fr8cyk2lxw] { background: #2b2415; }

.stat-name[b-fr8cyk2lxw] { flex: 1; font-size: .84rem; color: #d8ceba; }

.stat-value[b-fr8cyk2lxw] {
    font-family: Georgia, 'Times New Roman', serif;
    font-size: .95rem;
    color: #f0d18d;
    font-variant-numeric: tabular-nums;
}

/* Fixed width, so a column of rank letters lines up whichever letter it is. */
.stat-rank[b-fr8cyk2lxw] {
    flex: 0 0 auto;
    width: 1.25rem;
    text-align: center;
    font: 800 .58rem/1.5 ui-monospace, monospace;
    color: #8f8371;
    border: 1px solid #40362a;
    border-radius: 4px;
    padding: .1rem 0;
}

/* ⭐ Rank is carried by colour as well as letter, so "am I any good at this" reads without decoding.
   Untrained stays plain on purpose — it is the absence of training, not a grade. */
.stat-rank.rank-T[b-fr8cyk2lxw] { color: #a9c99a; border-color: #46543a; }
.stat-rank.rank-E[b-fr8cyk2lxw] { color: #9fc0d8; border-color: #3a4a56; }
.stat-rank.rank-M[b-fr8cyk2lxw] { color: #e3bd70; border-color: #6b5327; }
.stat-rank.rank-L[b-fr8cyk2lxw] { color: #d3a6dd; border-color: #56406a; }

/* ⛔ Absolute, so opening it moves nothing on the sheet. Sits under its own row, over everything else. */
.stat-pop[b-fr8cyk2lxw] {
    position: absolute;
    z-index: 20;
    top: 100%;
    left: 0;
    /* Never wider than the sheet it floats over, and never so narrow that the working wraps mid-equation. */
    width: max-content;
    min-width: 15rem;
    max-width: min(22rem, 88vw);
    margin-top: .2rem;
    background: #241d16;
    border: 1px solid #796241;
    border-radius: .5rem;
    box-shadow: 0 18px 46px #000000b0;
    padding: .7rem .85rem .75rem;
}

/* A row in the right-hand column would push its popover off the sheet, so that column opens leftward.
   ⚠️ Guarded twice, because "even child" only means "right-hand column" in a TWO-column list: below
   the sheet's own breakpoint, and in the one-column feats list, every row spans the full width and
   flipping alternate ones would just make the list look unsteady.
   ⚠️ The ancestor is the parent component's element, matched by class name — which scoped CSS allows,
   since only the LAST element in a selector carries the scope attribute. */
@media (min-width: 651px) {
    .stat-list:not(.one-column) > .stat-row-host:nth-child(even) .stat-pop[b-fr8cyk2lxw] { left: auto; right: 0; }
}

.stat-pop-head[b-fr8cyk2lxw] { display: flex; align-items: baseline; justify-content: space-between; gap: .8rem; }

.stat-pop-name[b-fr8cyk2lxw] {
    margin: 0;
    font-family: Georgia, 'Times New Roman', serif;
    font-size: 1.02rem;
    color: #f2d38e;
}

.stat-pop-close[b-fr8cyk2lxw] {
    font: inherit;
    font-size: .8rem;
    line-height: 1;
    color: #a99c87;
    background: none;
    border: 0;
    cursor: pointer;
    padding: .15rem .25rem;
}

.stat-pop-close:hover[b-fr8cyk2lxw] { color: #e3bd70; }

.stat-pop-sub[b-fr8cyk2lxw] {
    margin: .1rem 0 .55rem;
    font-size: .72rem;
    color: #a99c87;
}
/* /Components/Shared/SpellPreparationEditor.razor.rz.scp.css */
/* Scoped to this component only; both hosts inherit the same look because they host the same thing. */
.prep[b-wark6r0yv7]{display:flex;flex-direction:column;gap:.6rem}
.prep-empty[b-wark6r0yv7]{margin:0;font-size:.75rem;color:#8e9ab8;font-style:italic}

.prep-head[b-wark6r0yv7]{display:flex;align-items:flex-start;justify-content:space-between;gap:.6rem}
.prep-eyebrow[b-wark6r0yv7]{margin:0;font-size:.62rem;letter-spacing:.09em;text-transform:uppercase;color:#8e9ab8}
.prep-head h4[b-wark6r0yv7]{margin:.15rem 0 0;font-family:Georgia,serif;font-size:.95rem;color:#f4d58d;font-weight:600}

.prep-badge[b-wark6r0yv7]{padding:.15rem .45rem;border-radius:999px;font-size:.62rem;font-weight:700;white-space:nowrap}
.prep-badge.pending[b-wark6r0yv7]{background:#2a2340;border:1px solid #5c4a8d;color:#cbb8f0}

.prep-note[b-wark6r0yv7]{margin:0;padding:.45rem .6rem;border-left:2px solid #46557f;background:#151b2c99;border-radius:0 8px 8px 0;font-size:.7rem;line-height:1.5;color:#a9b5d4}
.prep-note strong[b-wark6r0yv7]{color:#f4d58d}

.prep-slots[b-wark6r0yv7]{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.35rem}
.prep-slot[b-wark6r0yv7]{display:flex;align-items:center;gap:.45rem;padding:.35rem .5rem;border:1px solid #2b3550;border-radius:9px;background:#111827aa}
.prep-slot.restricted[b-wark6r0yv7]{border-style:dashed;border-color:#5c6a44;background:#151c14aa}

.prep-rank[b-wark6r0yv7]{flex:none;width:1.4rem;height:1.4rem;display:grid;place-items:center;border-radius:6px;background:#1d2740;border:1px solid #3a4a6e;font-family:Georgia,serif;font-size:.7rem;font-weight:700;color:#d9c287}
.prep-name[b-wark6r0yv7]{flex:1;font-size:.8rem;color:#e3e9fb}
.prep-pick[b-wark6r0yv7]{flex:1;min-width:0;padding:.25rem .4rem;border:1px solid #34405c;border-radius:7px;background:#0f1524;color:#e3e9fb;font-size:.78rem}
.prep-pick:disabled[b-wark6r0yv7]{opacity:.5}

.prep-lock[b-wark6r0yv7]{flex:none;font-size:.6rem;letter-spacing:.05em;text-transform:uppercase;color:#9db06f}
.prep-spent[b-wark6r0yv7]{flex:none;padding:.1rem .35rem;border-radius:999px;background:#3a2226;border:1px solid #7d4048;font-size:.6rem;color:#ffb8be}

.prep-actions[b-wark6r0yv7]{display:flex;gap:.4rem}
.prep-save[b-wark6r0yv7]{flex:1;padding:.4rem .7rem;border:1px solid #8a6f37;border-radius:9px;background:#241d10;color:#f4d58d;font-weight:700;font-size:.75rem;cursor:pointer}
.prep-save:hover:not(:disabled)[b-wark6r0yv7]{background:#33280f;border-color:#c69446}
.prep-reset[b-wark6r0yv7]{padding:.4rem .7rem;border:1px solid #34405c;border-radius:9px;background:#151b2c;color:#bfcaf0;font-size:.75rem;cursor:pointer}
.prep-reset:hover:not(:disabled)[b-wark6r0yv7]{background:#1d2540}
.prep-save:disabled[b-wark6r0yv7],.prep-reset:disabled[b-wark6r0yv7]{opacity:.45;cursor:default}

.prep-feedback[b-wark6r0yv7]{margin:0;font-size:.7rem;color:#9db06f}
.prep-feedback.bad[b-wark6r0yv7]{color:#ffb8be}
/* /Components/Shared/StructuredRulesEditor.razor.rz.scp.css */
.structured-rules-editor[b-qh56it7nzz] { display: grid; gap: .6rem; }
.rules-hints[b-qh56it7nzz] { display: flex; flex-wrap: wrap; align-items: center; gap: .45rem; color: #ad9f8b; }
.rules-hints code[b-qh56it7nzz] { padding: .2rem .4rem; color: #e9c878; border: 1px solid #51442f; background: #201b14; }
.structured-rules-editor textarea[b-qh56it7nzz] { width: 100%; box-sizing: border-box; font: .84rem/1.45 Consolas, monospace; tab-size: 2; }
.json-highlight[b-qh56it7nzz] { overflow: auto; max-height: 18rem; padding: .75rem; color: #ddd4c5; background: #100e0b; border: 1px solid #453a2b; white-space: pre-wrap; font: .82rem/1.45 Consolas, monospace; }
.json-property[b-qh56it7nzz] { color: #82b8df; }
.json-string[b-qh56it7nzz] { color: #a8cf8a; }
.json-number[b-qh56it7nzz] { color: #e6b85c; }
.json-literal[b-qh56it7nzz] { color: #d38fc5; }
.json-punctuation[b-qh56it7nzz] { color: #cfc4b3; }
/* /Components/Shared/StudioPanel.razor.rz.scp.css */
/* The card chrome moved here with the markup. It used to sit in ForgeMapStudio.razor.css, and had to
   move the moment the <section> became this component's element rather than that page's: a scoped rule
   for `.tool-block` written there matches nothing here.
   The page's own `.tool-block label` / `.tool-block input` rules still work, because Blazor appends the
   scope to the FINAL selector only — the label is the page's markup even when the card around it is not. */

.tool-block[b-1fr47z2hrm] {
    background: #141c2c;
    border: 1px solid #26314a;
    border-radius: 10px;
    padding: .6rem .65rem;
    display: flex;
    flex-direction: column;
    gap: .45rem;
}

/* Shut, the card is just its own header: no content gap to leave a stripe of padding behind. */
.tool-block.shut[b-1fr47z2hrm] {
    gap: 0;
}

.tool-block h3[b-1fr47z2hrm] {
    margin: 0;
}

.panel-toggle[b-1fr47z2hrm] {
    display: flex;
    align-items: baseline;
    gap: .4rem;
    width: 100%;
    padding: 0;
    background: none;
    border: 0;
    cursor: pointer;
    text-align: left;
    font: inherit;
    color: #8593b3;
}

.panel-toggle:hover[b-1fr47z2hrm] {
    color: #c3cbe6;
}

.panel-toggle:focus-visible[b-1fr47z2hrm] {
    outline: 2px solid #6f9ede;
    outline-offset: 2px;
    border-radius: 4px;
}

.panel-caret[b-1fr47z2hrm] {
    font-size: .7rem;
    line-height: 1;
}

.panel-title[b-1fr47z2hrm] {
    font-size: .64rem;
    letter-spacing: .16em;
    text-transform: uppercase;
}

/* Pushed to the right so the titles stay in one column and stay scannable. */
.panel-summary[b-1fr47z2hrm] {
    margin-left: auto;
    font-size: .62rem;
    letter-spacing: .04em;
    text-transform: none;
    color: #5f6c88;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 55%;
}
/* /Components/Shared/TokenChipLayer.razor.rz.scp.css */
.map-chip[b-jw7zn7qgmf]{z-index:2;margin:8%;border:2px solid #7d8bad;border-radius:9px;background:#283653e8;color:#f2f5fb;font-size:.7rem;font-weight:900;box-shadow:0 4px 10px #02050a99;cursor:grab;display:grid;place-items:center;min-width:0;padding:0}
.map-chip:active[b-jw7zn7qgmf]{cursor:grabbing}
.map-chip.party[b-jw7zn7qgmf]{border-color:#63b3ed;background:#244a70e8}
.map-chip.opposition[b-jw7zn7qgmf]{border-color:#c47b6e;background:#5a2f28e8}
.map-chip:hover[b-jw7zn7qgmf]{outline:2px solid #f4cb68aa;outline-offset:1px}
/* /Components/Shared/TokenTray.razor.rz.scp.css */
.token-tray[b-xom9mewyy0]{min-width:11rem;max-width:15rem;color:#d5c9b6}
.token-tray h4[b-xom9mewyy0]{margin:0 0 .35rem;color:#cbb98f;font-size:.72rem;text-transform:uppercase;letter-spacing:.09em}
.tray-hint[b-xom9mewyy0]{margin:0 0 .55rem;color:#948a7c;font-size:.66rem;line-height:1.35}
.token-tray ul[b-xom9mewyy0]{list-style:none;margin:0;padding:0;display:grid;gap:.4rem}
.tray-chip[b-xom9mewyy0]{display:grid;grid-template-columns:1.9rem minmax(0,1fr) auto;gap:.5rem;align-items:center;width:100%;padding:.35rem;text-align:left;border:1px solid #3a332a;background:#161310;color:#e6dccb;cursor:grab}
.tray-chip:active[b-xom9mewyy0]{cursor:grabbing}
.tray-chip:hover[b-xom9mewyy0]{border-color:#80663d;background:#211b13}
.tray-chip.placed[b-xom9mewyy0]{opacity:.6}
.tray-chip.party[b-xom9mewyy0]{border-left:3px solid #63b3ed}
.tray-chip.opposition[b-xom9mewyy0]{border-left:3px solid #c47b6e}
.tray-avatar[b-xom9mewyy0]{display:grid;place-items:center;width:1.85rem;height:1.85rem;border-radius:50%;color:#e8c983;background:#342916;font-size:.62rem;font-weight:900}
.tray-name[b-xom9mewyy0]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.74rem}
.tray-placed[b-xom9mewyy0]{color:#8f8679;font-size:.58rem;text-transform:uppercase;letter-spacing:.04em}
/* /Components/Shared/WorldShell.razor.rz.scp.css */
/* The world pages share the landing page's look: the same painted background, gold eyebrow and serif
   headings, so a visitor moving between them never feels they have left the site.
   ⚠️ Everything a PAGE renders is styled through ::deep — see the note in WorldShell.razor. */

.world-shell[b-cm576mtng9] {
    min-height: 100vh;
    box-sizing: border-box;
    color: #e7eaf2;
    background: linear-gradient(rgba(8, 12, 20, .55), rgba(8, 12, 20, .95)), url('/images/bg-landing.jpg');
    background-size: cover;
    background-position: center;
    background-attachment: fixed;
}

.world-nav[b-cm576mtng9] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .75rem 1.5rem;
    max-width: 1080px;
    margin: 0 auto;
    padding: 1.25rem clamp(1.25rem, 5vw, 2rem);
}

.world-brand[b-cm576mtng9] {
    font-family: Georgia, 'Times New Roman', serif;
    font-size: 1.35rem;
    color: #faf7ef;
    text-decoration: none;
}

.world-nav-links[b-cm576mtng9] {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem 1.25rem;
    flex: 1 1 auto;
}

.world-nav-links a[b-cm576mtng9] {
    color: #d9deea;
    text-decoration: none;
    font-size: .92rem;
}

.world-nav-links a:hover[b-cm576mtng9],
.world-nav-links a:focus-visible[b-cm576mtng9],
.world-brand:hover[b-cm576mtng9] { color: #e8c36a; }

.world-play[b-cm576mtng9] {
    border-radius: 10px;
    padding: .5rem 1.2rem;
    font-weight: 800;
    font-size: .85rem;
    letter-spacing: .05em;
    background: linear-gradient(135deg, #c69446, #e8c36a);
    color: #16110a;
    text-decoration: none;
}

.world-inner[b-cm576mtng9] {
    max-width: 1080px;
    margin: 0 auto;
    padding: clamp(1rem, 4vw, 3rem) clamp(1.25rem, 5vw, 2rem) 3rem;
}

.world-foot[b-cm576mtng9] {
    padding: 2rem 1.25rem 2.5rem;
    text-align: center;
    font-size: .85rem;
    color: #8e97a8;
}

.world-foot p[b-cm576mtng9] { margin: .2rem 0; }
.world-foot a[b-cm576mtng9] { color: #b9c1d1; }

/* ---- page content ------------------------------------------------------------------------------- */

.world-shell[b-cm576mtng9]  .world-hero {
    max-width: 46rem;
    padding: clamp(1rem, 6vh, 4rem) 0 clamp(1.5rem, 4vh, 2.5rem);
}

.world-shell[b-cm576mtng9]  .eyebrow {
    font-size: .72rem;
    font-weight: 800;
    letter-spacing: .16em;
    color: #dcb763;
    margin: 0 0 .6rem;
    text-transform: uppercase;
}

.world-shell[b-cm576mtng9]  h1 {
    margin: 0 0 1rem;
    font-family: Georgia, 'Times New Roman', serif;
    font-size: clamp(2.3rem, 7vw, 4rem);
    line-height: 1.05;
    color: #faf7ef;
    text-shadow: 0 4px 24px #000a;
}

.world-shell[b-cm576mtng9]  .lead {
    margin: 0;
    font-size: clamp(1rem, 2.2vw, 1.2rem);
    line-height: 1.65;
    color: #d9deea;
    text-shadow: 0 2px 12px #000a;
}

.world-shell[b-cm576mtng9]  .world-section {
    margin: 0 0 2.5rem;
    padding: clamp(1.25rem, 3vw, 2rem);
    border: 1px solid #ffffff14;
    border-radius: 14px;
    background: #0c111ce6;
}

.world-shell[b-cm576mtng9]  .world-section h2 {
    margin: 0 0 .9rem;
    font-family: Georgia, 'Times New Roman', serif;
    font-size: clamp(1.35rem, 3vw, 1.7rem);
    color: #f0e2bd;
}

.world-shell[b-cm576mtng9]  .world-section h3 {
    margin: 1.4rem 0 .5rem;
    font-family: Georgia, 'Times New Roman', serif;
    font-size: 1.12rem;
    color: #f0e2bd;
}

.world-shell[b-cm576mtng9]  .world-section p {
    margin: 0 0 .9rem;
    font-size: 1rem;
    line-height: 1.7;
    color: #c7cddb;
    max-width: 48rem;
}

.world-shell[b-cm576mtng9]  .world-section p:last-child { margin-bottom: 0; }

.world-shell[b-cm576mtng9]  .world-section a { color: #e8c36a; }

.world-shell[b-cm576mtng9]  .saying {
    margin: 1rem 0 0;
    font-family: Georgia, 'Times New Roman', serif;
    font-style: italic;
    font-size: 1.15rem;
    color: #f0e2bd;
}

.world-shell[b-cm576mtng9]  .facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: .75rem 1.5rem;
    margin: 0 0 2.5rem;
    padding: 1.1rem 1.4rem;
    border: 1px solid #ffffff14;
    border-radius: 14px;
    background: #0c111cd9;
}

.world-shell[b-cm576mtng9]  .facts div { min-width: 0; }

.world-shell[b-cm576mtng9]  .facts dt {
    font-size: .7rem;
    font-weight: 800;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: #dcb763;
}

.world-shell[b-cm576mtng9]  .facts dd {
    margin: .2rem 0 0;
    color: #e7eaf2;
    line-height: 1.5;
}

.world-shell[b-cm576mtng9]  .facts dd a { color: #e8c36a; }

/* Five cards on the overview. A 20rem floor lays them out 3 + 2, then 2 + 2 + 1, then one column. */
.world-shell[b-cm576mtng9]  .card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
    gap: 1px;
    background: #ffffff14;
    border: 1px solid #ffffff14;
    border-radius: 14px;
    overflow: hidden;
}

.world-shell[b-cm576mtng9]  .card {
    display: block;
    background: #0c111ce6;
    padding: 1.5rem 1.4rem;
    color: inherit;
    text-decoration: none;
    transition: background .15s ease;
}

.world-shell[b-cm576mtng9]  a.card:hover,
.world-shell[b-cm576mtng9]  a.card:focus-visible { background: #141b2be6; }

.world-shell[b-cm576mtng9]  .card h3 {
    margin: 0 0 .35rem;
    font-family: Georgia, 'Times New Roman', serif;
    font-size: 1.15rem;
    color: #f0e2bd;
}

.world-shell[b-cm576mtng9]  .card .card-members {
    margin: 0 0 .6rem;
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .06em;
    color: #dcb763;
}

.world-shell[b-cm576mtng9]  .card p {
    margin: 0;
    font-size: .95rem;
    line-height: 1.6;
    color: #c7cddb;
}

.world-shell[b-cm576mtng9]  .customs {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
    gap: 1rem 1.5rem;
}

.world-shell[b-cm576mtng9]  .customs li { line-height: 1.6; color: #c7cddb; }

.world-shell[b-cm576mtng9]  .customs strong {
    display: block;
    font-family: Georgia, 'Times New Roman', serif;
    font-weight: normal;
    font-size: 1.05rem;
    color: #f0e2bd;
}

.world-shell[b-cm576mtng9]  .relations {
    list-style: none;
    margin: 0;
    padding: 0;
}

.world-shell[b-cm576mtng9]  .relations li {
    padding: .7rem 0;
    border-top: 1px solid #ffffff12;
    line-height: 1.6;
    color: #c7cddb;
}

.world-shell[b-cm576mtng9]  .relations li:first-child { border-top: 0; padding-top: 0; }

.world-shell[b-cm576mtng9]  .relations a {
    font-family: Georgia, 'Times New Roman', serif;
    color: #e8c36a;
    margin-right: .35rem;
}

.world-shell[b-cm576mtng9]  .earldom {
    scroll-margin-top: 1.5rem;
}

.world-shell[b-cm576mtng9]  .earldom-meta {
    margin: -.4rem 0 .9rem;
    font-size: .85rem;
    color: #aeb6c6;
}

.world-shell[b-cm576mtng9]  .earldom-character {
    font-style: italic;
}

.world-shell[b-cm576mtng9]  .world-map {
    margin: 0 0 2.5rem;
}

.world-shell[b-cm576mtng9]  .world-map img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: 14px;
    border: 1px solid #ffffff1f;
    box-shadow: 0 18px 50px #00000080;
}

.world-shell[b-cm576mtng9]  .world-map figcaption {
    margin-top: .6rem;
    text-align: center;
    font-size: .85rem;
    color: #aeb6c6;
}

/* #1535. The gods page: the at-a-glance table, which scrolls on its own rather than widening the page. */
.world-shell[b-cm576mtng9]  .gods-table-wrap {
    overflow-x: auto;
    margin: 0 0 1.2rem;
    border: 1px solid #ffffff14;
    border-radius: 14px;
    background: #0c111cd9;
}

.world-shell[b-cm576mtng9]  .gods-table {
    width: 100%;
    min-width: 44rem;
    border-collapse: collapse;
    font-size: .9rem;
    color: #c7cddb;
}

.world-shell[b-cm576mtng9]  .gods-table th {
    text-align: left;
    padding: .7rem .9rem;
    font-size: .7rem;
    font-weight: 800;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: #dcb763;
    border-bottom: 1px solid #ffffff1f;
}

.world-shell[b-cm576mtng9]  .gods-table td {
    padding: .55rem .9rem;
    border-top: 1px solid #ffffff0d;
    vertical-align: top;
    line-height: 1.45;
}

.world-shell[b-cm576mtng9]  .gods-table td a {
    font-family: Georgia, 'Times New Roman', serif;
    color: #e8c36a;
}

.world-shell[b-cm576mtng9]  .gods-table tr.feared td { color: #aeb6c6; }

.world-shell[b-cm576mtng9]  .deity {
    margin-top: 2rem;
}

@media (max-width: 560px) {
    .world-shell[b-cm576mtng9] { background-attachment: scroll; }
    .world-play[b-cm576mtng9] { margin-left: auto; }
}
