/* /Components/DiceTray.razor.rz.scp.css */
/*
    Scoped to DiceTray.razor by Blazor's CSS isolation — the build rewrites these selectors with a
    generated attribute so none of it can leak onto the rest of the page, and nothing on the rest of
    the page can reach in. That is what makes this component safe to move, scale and restyle later.
    https://learn.microsoft.com/aspnet/core/blazor/components/css-isolation

    Everything here is expressed against the theme's custom properties (--pgm-*) so the tray follows
    light and dark without a second stylesheet.
*/

.tray[b-tfii38465z] {
    --tray-gap: .4rem;

    display: flex;
    flex-direction: column;
    gap: var(--tray-gap);
    padding: .6rem .7rem;
    border: 1px solid var(--pgm-line);
    border-radius: .5rem;
    background: color-mix(in srgb, var(--pgm-paper) 92%, var(--pgm-ink));
    box-shadow: 0 1px 3px var(--pgm-shadow);
}

.tray.compact[b-tfii38465z] {
    --tray-gap: .25rem;
    padding: .4rem .5rem;
}

/* ── header: what was thrown, and by whom ─────────────────────────────────── */

.head[b-tfii38465z] {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: .4rem;
    font-size: .8rem;
    color: color-mix(in srgb, var(--pgm-ink) 60%, transparent);
}

.notation[b-tfii38465z] {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    letter-spacing: .01em;
}

.label[b-tfii38465z]::before { content: "· "; }

.who[b-tfii38465z] {
    margin-left: auto;
    font-family: var(--pgm-display);
    font-style: italic;
    color: var(--pgm-ink);
}

/* ── one row per group ────────────────────────────────────────────────────── */

.group[b-tfii38465z] {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .3rem;
}

.tag[b-tfii38465z] {
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    padding: .05rem .35rem;
    border-radius: .25rem;
    /* --tag-hue is set inline per group, derived from the tag text (see DiceTray.razor). */
    color: hsl(var(--tag-hue, 270) 55% 35%);
    background: hsl(var(--tag-hue, 270) 60% 92%);
}

.dice[b-tfii38465z] {
    display: flex;
    flex-wrap: wrap;
    gap: .25rem;
}

.subtotal[b-tfii38465z] {
    margin-left: auto;
    font-variant-numeric: tabular-nums;
    font-size: .85rem;
    color: color-mix(in srgb, var(--pgm-ink) 70%, transparent);
}

/* ── a die ────────────────────────────────────────────────────────────────── */

.die[b-tfii38465z] {
    display: inline-grid;
    place-items: center;
    min-width: 1.75rem;
    height: 1.75rem;
    padding: 0 .2rem;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    line-height: 1;
    color: hsl(var(--tag-hue, 270) 45% 28%);
    background: hsl(var(--tag-hue, 270) 55% 95%);
    border: 1px solid hsl(var(--tag-hue, 270) 40% 78%);
}

.compact .die[b-tfii38465z] {
    min-width: 1.45rem;
    height: 1.45rem;
    font-size: .8rem;
}

/*
    Shape by die type. A d6 is square, a d20 is round-ish, a d4 points up — enough of a silhouette
    to tell two groups apart at a glance without pretending to be a real polyhedron. The 3D layer,
    when it lands, is what draws actual dice; this only has to be legible.
*/
.die.d4[b-tfii38465z]  { border-radius: .2rem .6rem .2rem .2rem; }
.die.d6[b-tfii38465z]  { border-radius: .2rem; }
.die.d8[b-tfii38465z]  { border-radius: .6rem .2rem .6rem .2rem; }
.die.d10[b-tfii38465z] { border-radius: .7rem .25rem .7rem .25rem; }
.die.d12[b-tfii38465z] { border-radius: .5rem; }
.die.d20[b-tfii38465z] { border-radius: 50%; }
.die.d100[b-tfii38465z] { border-radius: 50%; }

/*
    A dropped die is still SHOWN — that is the whole point of rolling it (ADR-069). It is dimmed and
    struck through rather than removed, so "you rolled 6, 5, 3, 1 and dropped the 1" is something the
    player can see rather than something they have to be told.

    Opacity alone would not be enough: dimming is invisible to anyone who cannot perceive the
    contrast difference, so the strike-through carries the same meaning independently. The title
    attribute in the markup carries it a third time, for a screen reader.
*/
.die.dropped[b-tfii38465z] {
    opacity: .45;
    text-decoration: line-through;
    background: transparent;
    border-style: dashed;
}

/* ── the total ────────────────────────────────────────────────────────────── */

.foot[b-tfii38465z] {
    display: flex;
    align-items: baseline;
    gap: .35rem;
    padding-top: .3rem;
    border-top: 1px solid var(--pgm-line);
}

.modifier[b-tfii38465z] {
    font-variant-numeric: tabular-nums;
    font-size: .85rem;
    color: color-mix(in srgb, var(--pgm-ink) 60%, transparent);
}

.total-label[b-tfii38465z] {
    margin-left: auto;
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: color-mix(in srgb, var(--pgm-ink) 55%, transparent);
}

.total[b-tfii38465z] {
    font-family: var(--pgm-display);
    font-size: 1.5rem;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    color: var(--pgm-ink);
}

/*
    Dark theme (ADR-033). The component chooses each tag as a HUE only, so lightness is all that
    changes here — "radiant" keeps its colour identity in both themes.

    The [data-bs-theme] ancestor lives on <html>, outside this component, and that is fine: CSS
    isolation appends the generated scope attribute to the LAST selector only, so this compiles to
    `[data-bs-theme="dark"] .tag[b-xxxxx]` — an outside ancestor reaching an inside element, which
    is exactly what a theme switch has to do.
*/
[data-bs-theme="dark"] .tag[b-tfii38465z] {
    color: hsl(var(--tag-hue, 270) 70% 78%);
    background: hsl(var(--tag-hue, 270) 45% 22%);
}

[data-bs-theme="dark"] .die[b-tfii38465z] {
    color: hsl(var(--tag-hue, 270) 75% 85%);
    background: hsl(var(--tag-hue, 270) 40% 20%);
    border-color: hsl(var(--tag-hue, 270) 35% 35%);
}
/* /Components/Layout/MainLayout.razor.rz.scp.css */
.page[b-ignd54in2z] {
    position: relative;
    display: flex;
    flex-direction: column;
}

main[b-ignd54in2z] {
    flex: 1;
}

/* The .sidebar rules are gone with the sidebar itself. The nav lives in .top-row now (TopNav),
   so `main` is the full width of the viewport on every page — which is the point: the play page
   was paying 250px for a column it never used. */

.top-row[b-ignd54in2z] {
    background-color: #f7f7f7;
    border-bottom: 1px solid #d6d5d5;
    /* No justify-content: the .pgm-top-spacer below does the pushing, so the bar can hold a
       left group and a right group instead of only a right one. */
    height: 3.5rem;
    display: flex;
    align-items: center;
}

/* Eats the slack between the brand and the account cluster. */
.pgm-top-spacer[b-ignd54in2z] { flex: 1 1 auto; }

/* The brand. Deliberately quiet: it is a landmark and a way home, not a call to action, and it
   is on screen on every page including during a shared window. */
.top-row[b-ignd54in2z]  a.pgm-brand,
.pgm-brand[b-ignd54in2z] {
    margin-left: 0;
    font-weight: 600;
    font-size: 1.05rem;
    letter-spacing: .01em;
    color: var(--pgm-accent, #6f5cff);
    text-decoration: none;
    white-space: nowrap;
}

.pgm-brand:hover[b-ignd54in2z] { color: var(--pgm-accent, #6f5cff); opacity: .82; }

/* Dark mode (ADR-033): the sidebar is dark in both themes, but this top bar is hardcoded light —
   darken it so it doesn't read as a bright strip. The [data-bs-theme="dark"] ancestor is on <html>,
   outside this component's CSS-isolation scope, so it wins over the rule above in dark mode. */
[data-bs-theme="dark"] .top-row[b-ignd54in2z] {
    background-color: #1c1e26;
    border-bottom-color: rgba(255, 255, 255, .10);
}

    .top-row[b-ignd54in2z]  a, .top-row[b-ignd54in2z]  .btn-link {
        white-space: nowrap;
        margin-left: 1.5rem;
        text-decoration: none;
    }

/* The account cluster — help, settings, the name menu — is a tight group, so it
   opts out of the 1.5rem the rule above puts between top-bar links. That spacing
   is right for separate links and wrong for a toolbar.

   ⚠️ ::deep is required: LoginDisplay renders these, not this component, so
   CSS isolation would otherwise scope the selector to markup that never appears
   here. https://learn.microsoft.com/aspnet/core/blazor/components/css-isolation */
.top-row[b-ignd54in2z] { gap: .45rem; padding-right: .35rem; }

    .top-row[b-ignd54in2z]  .pgm-icon-btn,
    .top-row[b-ignd54in2z]  .pgm-menu,
    .top-row[b-ignd54in2z]  .pgm-menu-panel a {
        margin-left: 0;
    }

    /* Underlining a menu row on hover reads as a link inside a menu inside a bar.
       The row already highlights; that is the affordance. */
    .top-row[b-ignd54in2z]  .pgm-menu-panel a:hover {
        text-decoration: none;
    }

    .top-row[b-ignd54in2z]  a:hover, .top-row[b-ignd54in2z]  .btn-link:hover {
        text-decoration: underline;
    }

    .top-row[b-ignd54in2z]  a:first-child {
        overflow: hidden;
        text-overflow: ellipsis;
    }

@media (max-width: 640.98px) {
    .top-row[b-ignd54in2z]  a, .top-row[b-ignd54in2z]  .btn-link {
        margin-left: 0;
    }
}

@media (min-width: 641px) {
    .top-row[b-ignd54in2z] {
        position: sticky;
        top: 0;

        /* ⚠️ 1030, not 1. The header is sticky, so it creates a stacking context, and at z-index 1
           ANY positioned element further down the page with z-index >= 1 draws straight through the
           open Admin menu — reported 2026-07-31 as an "Add" button shining through the dropdown.
           1030 is Bootstrap's own $zindex-fixed, which is the level a site header is expected to
           occupy, so page content cannot out-stack it by accident.
           https://getbootstrap.com/docs/5.3/layout/z-index/ */
        z-index: 1030;
    }

    .top-row.auth[b-ignd54in2z]  a:first-child {
        flex: 1;
        text-align: right;
        width: 0;
    }

    .top-row[b-ignd54in2z], article[b-ignd54in2z] {
        padding-left: 2rem !important;
        padding-right: 1.5rem !important;
    }
}

#blazor-error-ui[b-ignd54in2z] {
    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-ignd54in2z] {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
    }


/* ── The top navigation ──────────────────────────────────────────────────────────────────────
   ⚠️ Only the rules that must FIGHT this file's own selectors live here. The nav's own layout is in
   wwwroot/css/pgm-theme.css, and it has to be, because CSS isolation scopes this file to MainLayout's
   OWN markup: `.pgm-topnav` is rendered by TopNav.razor, a child component, so a bare `.pgm-topnav`
   rule here never matches it. That is what shipped broken — the nav stacked vertically and spilled
   out of the 3.5rem bar, because `display: flex` was written here and silently applied to nothing.
   The ::deep rules below did work, which is exactly what made it confusing.
   https://learn.microsoft.com/aspnet/core/blazor/components/css-isolation#child-component-support */

/* `.top-row ::deep a { margin-left: 1.5rem }` above is right for separate top-bar links and wrong
   for a nav group, so the group opts out. This one belongs here: it exists only to override a rule
   in this file. */
.top-row[b-ignd54in2z]  .pgm-topnav a,
.top-row[b-ignd54in2z]  .pgm-topnav .pgm-menu {
    margin-left: 0;
}

.top-row[b-ignd54in2z]  .pgm-topnav a:hover {
    text-decoration: none;
}
/* /Components/Layout/ReconnectModal.razor.rz.scp.css */
/*
    The reconnect modal, themed (ADR-033).

    Blazor toggles ONE of these state classes on the dialog; every rule below keys off them. The
    default is "hide everything", so a state we have not written markup for shows an empty box
    rather than the wrong message.
*/

.components-reconnect-first-attempt-visible[b-1wsamliet6],
.components-reconnect-repeated-attempt-visible[b-1wsamliet6],
.components-reconnect-trying-visible[b-1wsamliet6],
.components-reconnect-failed-visible[b-1wsamliet6],
.components-pause-visible[b-1wsamliet6],
.components-resume-failed-visible[b-1wsamliet6],
.components-rejoining-animation[b-1wsamliet6] {
    display: none;
}

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

/* ── the panel ───────────────────────────────────────────────────────────── */

#components-reconnect-modal[b-1wsamliet6] {
    /* Theme tokens rather than a hardcoded white: the stock modal was a white card with a blue
       button, which in dark mode is a flashbang at the exact moment someone is already annoyed.
       Custom properties inherit through the DOM, so :root's --pgm-* reach in here even though the
       dialog renders in the browser's top layer. */
    background: var(--pgm-paper);
    color: var(--pgm-ink);
    width: min(24rem, calc(100vw - 2rem));
    margin: 18vh auto;
    padding: 2rem 1.75rem 1.75rem;
    border: 1px solid var(--pgm-line);
    border-radius: .75rem;
    box-shadow: 0 18px 48px rgba(0, 0, 0, .35);
    opacity: 0;
    transition: display .5s allow-discrete, overlay .5s allow-discrete;
    animation: pgm-reconnect-fade-out-b-1wsamliet6 .5s both;
}

#components-reconnect-modal[open][b-1wsamliet6] {
    animation:
        pgm-reconnect-rise-b-1wsamliet6 1.2s cubic-bezier(.05, .89, .25, 1.02) .3s,
        pgm-reconnect-fade-in-b-1wsamliet6 .5s ease-in-out .3s;
    animation-fill-mode: both;
}

#components-reconnect-modal[b-1wsamliet6]::backdrop {
    /* Deliberately heavy. The table behind this is not interactive while the circuit is down, and a
       faint scrim invites clicking on controls that will not respond. */
    background-color: rgba(10, 8, 18, .62);
    backdrop-filter: blur(2px);
    animation: pgm-reconnect-fade-in-b-1wsamliet6 .5s ease-in-out;
    opacity: 1;
}

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

#components-reconnect-modal p[b-1wsamliet6] {
    margin: 0;
    text-align: center;
    font-size: .9rem;
    color: color-mix(in srgb, var(--pgm-ink) 78%, transparent);
}

/* The one line that names what happened. Display face, because this is the app speaking. */
#components-reconnect-modal p.pgm-headline[b-1wsamliet6] {
    font-family: var(--pgm-display);
    font-size: 1.15rem;
    line-height: 1.25;
    color: var(--pgm-ink);
    margin-bottom: .1rem;
}

/* The reassurance. Quieter than the explanation, and set off by a rule, because it answers a
   different question — "is my game gone?" rather than "what is happening?". */
#components-reconnect-modal p.pgm-note[b-1wsamliet6] {
    margin-top: .5rem;
    padding-top: .7rem;
    border-top: 1px solid var(--pgm-line);
    width: 100%;
    font-size: .8rem;
    font-style: italic;
    color: color-mix(in srgb, var(--pgm-ink) 55%, transparent);
}

#components-reconnect-modal button[b-1wsamliet6] {
    margin-top: .4rem;
    border: 0;
    background-color: var(--pgm-accent);
    color: #fff;
    font: inherit;
    padding: .4rem 1.5rem;
    border-radius: .35rem;
    cursor: pointer;
    transition: background-color .12s ease-in-out;
}

#components-reconnect-modal button:hover[b-1wsamliet6] { background-color: var(--pgm-accent-dark); }
#components-reconnect-modal button:focus-visible[b-1wsamliet6] {
    outline: 2px solid var(--pgm-accent);
    outline-offset: 2px;
}

/* ── the animation ───────────────────────────────────────────────────────── */

/*
    Rings travelling outward: a signal being sent, over and over, which is exactly what the client
    is doing. Three of them, staggered, so there is always one mid-flight — a single ring has a dead
    beat between cycles that reads as "stopped" rather than "trying".
*/
.components-rejoining-animation[b-1wsamliet6] {
    position: relative;
    width: 74px;
    height: 74px;
    margin-bottom: .3rem;
}

.components-rejoining-animation div[b-1wsamliet6] {
    position: absolute;
    inset: 50% auto auto 50%;
    width: 0;
    height: 0;
    border: 2px solid var(--pgm-accent);
    border-radius: 50%;
    opacity: 0;
    animation: pgm-reconnect-ring-b-1wsamliet6 1.8s cubic-bezier(0, .2, .8, 1) infinite;
}

.components-rejoining-animation div:nth-child(2)[b-1wsamliet6] { animation-delay: -.6s; }
.components-rejoining-animation div:nth-child(3)[b-1wsamliet6] { animation-delay: -1.2s; }

@keyframes pgm-reconnect-ring-b-1wsamliet6 {
    0%   { width: 0;    height: 0;    margin: 0 0 0 0;       opacity: 0; }
    6%   { width: 0;    height: 0;    margin: 0 0 0 0;       opacity: .9; }
    100% { width: 74px; height: 74px; margin: -37px 0 0 -37px; opacity: 0; }
}

@keyframes pgm-reconnect-rise-b-1wsamliet6 {
    0%   { transform: translateY(24px) scale(.96); }
    100% { transform: translateY(0) scale(1); }
}

@keyframes pgm-reconnect-fade-in-b-1wsamliet6  { 0% { opacity: 0; } 100% { opacity: 1; } }
@keyframes pgm-reconnect-fade-out-b-1wsamliet6 { 0% { opacity: 1; } 100% { opacity: 0; } }

/*
    Motion is decoration here, never information — every state is stated in words. So anyone who has
    asked their system for less of it gets a still panel and loses nothing.
    https://developer.mozilla.org/docs/Web/CSS/@media/prefers-reduced-motion
*/
@media (prefers-reduced-motion: reduce) {
    #components-reconnect-modal[b-1wsamliet6],
    #components-reconnect-modal[open][b-1wsamliet6],
    #components-reconnect-modal[b-1wsamliet6]::backdrop {
        animation: none;
        opacity: 1;
    }

    .components-rejoining-animation div[b-1wsamliet6] {
        animation: none;
        width: 74px;
        height: 74px;
        margin: -37px 0 0 -37px;
        opacity: .25;
    }
}
/* /Components/Pages/Play.razor.rz.scp.css */
/*
    The play surface — sized to the screen, not to its contents.

    ── The problem this solves ──────────────────────────────────────────────────
    The page was an ordinary document. Header, voice controls, the GM strip, a row of party cards,
    a two-column room/story region, a transcript pinned at 22rem, and the action box — each block
    sized independently and stacked. Added up it comfortably exceeded 1440 pixels of screen, so the
    thing a player reads all session was below the fold, and scrolling to it pushed the party out of
    view. Every individual block was reasonable; the sum was not.

    ── The shape ────────────────────────────────────────────────────────────────
    A column that is exactly as tall as the window. Inside it:

      * fixed furniture keeps its natural height (header, controls, GM strip, action box);
      * the party gallery keeps its natural height but is capped, and scrolls inside that cap;
      * the two BIG regions — the room/story panes and the transcript — share whatever is left.

    The result is that the page itself never scrolls on a desktop. Adding a party member, or a long
    room description, now consumes space from a scroll region instead of pushing the transcript off
    the bottom of the screen.

    ⚠️ `min-height: 0` on the flexible children is load-bearing, not tidying. A flex item's default
    `min-height: auto` means it refuses to shrink below its content, so a long transcript would grow
    the shell and push the action box off screen — the exact bug this file exists to fix, wearing a
    different hat.
    https://developer.mozilla.org/docs/Web/CSS/CSS_flexible_box_layout/Mastering_wrapping_of_flex_items

    ── Desktop only, on purpose ─────────────────────────────────────────────────
    Below Bootstrap's `lg` the page already works: the two panes become tabs (`.pgm-pane-tabs`) and
    a phone scrolls the whole document, which is correct on a phone. The shell engages at 992px and
    up, where there is a window to fit things into.
*/

/* Height of the app chrome above this page: the sticky top row (3.5rem) plus `.content`'s padding
   (1.1rem), with a little slack so a hairline rounding difference does not produce a permanent
   1px scrollbar. One value, one place to correct it if the chrome changes. */
.pgm-play[b-bu6eiqrurf] {
    --pgm-play-chrome: 5.2rem;
}

@media (min-width: 992px) {
    .pgm-play[b-bu6eiqrurf] {
        display: flex;
        flex-direction: column;
        /* dvh, not vh: on mobile browsers vh is the tallest the viewport ever gets, so a shell sized
           in vh hides its own bottom behind the URL bar.
           https://developer.mozilla.org/docs/Web/CSS/length#viewport-percentage_lengths */
        height: calc(100dvh - var(--pgm-play-chrome));
        min-height: 26rem;
        /* `auto`, not `hidden`. If the fixed furniture alone ever exceeds the window — a tiny
           window, a huge font size, a browser zoomed to 200% — the shell degrades to a scrolling
           page, which is what it used to be. `hidden` would instead clip the action box out of
           existence, and a table you cannot type into is a worse failure than one you scroll. */
        overflow: auto;
        gap: 0;
    }

    /* Furniture: natural height, never squeezed. These are already compact, and shaving pixels off
       a header buys less than the two scroll regions give up willingly. */
    .pgm-play > .pgm-play-fixed[b-bu6eiqrurf] {
        flex: 0 0 auto;
    }

    /*
        The party gallery. Natural height up to a ceiling, then it scrolls.

        A cap rather than a share of the leftover space because the cards are the one region whose
        height is driven by how many PEOPLE are at the table — a six-player party must not silently
        take the transcript's room. 34vh is about two rows of cards at 1440p and one row on a
        laptop, which is the right trade in both cases.
    */
    .pgm-play-party[b-bu6eiqrurf] {
        flex: 0 1 auto;
        min-height: 0;
        max-height: 34vh;
        overflow-y: auto;
        overscroll-behavior: contain;   /* stop a flick here from scrolling the page behind it */
    }

    /*
        The two regions that share what is left.

        40/60 in favour of the transcript. The panes hold reference material you look at when you
        need it — exits, inventory, who is here — while the transcript is the game itself, arriving
        continuously and read constantly. `min-height: 0` lets both actually shrink; the basis of 0
        makes the ratio govern the split rather than the content length.
    */
    /* ⚠️ `flex-basis: auto`, not 0. With a basis of 0 the 4/6 ratio is enforced no matter what is
       in either box, which produced the reported fault: the transcript sat with 200px of empty
       space below its last line while the room pane was crushed to two lines and the exits scrolled
       out of sight. Content the reader can see beats a ratio the reader cannot.

       With `auto` the panes take what their content needs first, `flex-grow: 4` still hands them
       the larger share of any SURPLUS, and `max-height` keeps a very long room from taking the
       whole window. The transcript below keeps a basis of 0 deliberately: it is the one region that
       should soak up whatever is left, because it is where the game arrives. */
    .pgm-play-panes[b-bu6eiqrurf] {
        flex: 4 1 auto;
        max-height: 55%;
        min-height: 0;
        margin-bottom: .5rem;
    }

    /* Each column scrolls on its own, so a long room description does not drag the inventory beside
       it out of reach. */
    .pgm-play-panes > [class*="col-"][b-bu6eiqrurf] {
        max-height: 100%;
        overflow-y: auto;
        overscroll-behavior: contain;
    }

    .pgm-play-transcript[b-bu6eiqrurf] {
        /* The tall-viewport layout gives the transcript the lion's share of what is left. It was
           6, against a location pane that rarely needs its full allowance once the art has drawn. */
        flex: 8 1 0;
        /* ⚠️ AUTO, and this is load-bearing. The base rule sets an explicit clamp() height, which on
           a flex item WINS over flex-basis — so the transcript kept a fixed height inside a shell
           that is trying to fit the window, and everything below it went off the bottom of a 1440
           screen. The clamp is for the stacked phone layout; here the shell does the sizing. */
        height: auto;
        min-height: 7rem;
        overflow-y: auto;
        overscroll-behavior: contain;
        margin-bottom: .5rem !important;
    }
}

/*
    The game master's seat: last on the row, hard against the right edge.

    Its own rule rather than Bootstrap's `ms-auto order-last`, because those two only produce the
    intended result when there happens to be space left on the line. `margin-left: auto` right-aligns
    an item by eating the FREE space on its flex line — so with a full row of players there is none
    to eat, the seat wraps, and it lands alone on a second row. Which is the layout this replaced.

    `order` is what actually guarantees "last"; the auto margin is what pins it right when the row is
    not full. Stated here so the behaviour does not depend on how many people are at the table.
*/
.pgm-gm-seat[b-bu6eiqrurf] {
    order: 99;
    margin-left: auto;
}

/*
    Outside the shell — and on phones, where the shell is off — the transcript still needs a height,
    because it is a scroll container and a scroll container with no height is just a very long page.
    Viewport-relative with a floor and a ceiling: 22rem was fine on the screen it was written on and
    nowhere else.
*/
.pgm-play-transcript[b-bu6eiqrurf] {
    /* ⚠️ Raised from clamp(10rem, 30vh, 22rem). The transcript is the CANONICAL channel (ADR-012)
       and it was the smallest element on the page: on a laptop it showed two and a half lines
       while a row of chrome had a storey of its own above it. Merging the header row paid for
       most of this; the rest comes from the ceiling, because on a tall monitor there is no reason
       for the log to stop at 22rem while the window keeps going. */
    height: clamp(14rem, 42vh, 34rem);
    overflow-y: auto;
}

/* The toolbar carries the campaign name and every table control on one line. Wrapping is expected
   on a narrow window and is why it is a flex row rather than a fixed strip. */
.pgm-play-toolbar[b-bu6eiqrurf] {
    row-gap: .35rem;
}

/*
    The room map. A flat 340px was the single largest fixed block on the page. Proportional to the
    window, with a ceiling so it does not become a poster on a tall monitor.
*/
.pgm-play-map[b-bu6eiqrurf] {
    max-height: clamp(120px, 22vh, 340px);
    width: auto;
    object-fit: contain;
}

/*
    ── Short screens ────────────────────────────────────────────────────────────
    A 1440p monitor is not the tight case; a 13" laptop at 768 logical pixels is, and so is a
    1440p monitor with the browser at 125% or a docked window at half height. Below 900px of
    viewport the fixed furniture has to give something up too, so the two scroll regions are still
    usable rather than technically present at four lines each.

    These shrink the two camera strips, which are the tallest furniture on the page and the least
    information-dense — a smaller face is still a face.

    `.pgm-cam-slot` is defined globally in pgm-theme.css but the ELEMENTS carrying it are rendered
    by this component, so isolation stamps them with this file's scope and these overrides reach
    them. (`::deep` would be needed only for markup a CHILD component renders.)
    https://learn.microsoft.com/aspnet/core/blazor/components/css-isolation#css-isolation-bundling
*/
@media (max-height: 900px) {
    .pgm-play .pgm-cam-slot[b-bu6eiqrurf] { height: clamp(64px, 9vh, 108px); }
    .pgm-play-party[b-bu6eiqrurf] { max-height: 30vh; }
}

@media (max-height: 740px) {
    .pgm-play .pgm-cam-slot[b-bu6eiqrurf] { height: clamp(56px, 8vh, 84px); }
    .pgm-play-party[b-bu6eiqrurf] { max-height: 26vh; }
}


/* ── Nothing here may make the page scroll sideways ──────────────────────────────────────────
   Reported at a narrow window: a horizontal scrollbar appeared along the bottom and the left edge
   of every heading was clipped. The shell carries `overflow: auto` on purpose (see the note above
   — clipping the action box out of existence is a worse failure than scrolling), but that same
   `auto` applies to the horizontal axis, so anything inside that refuses to shrink turns into a
   sideways scroll of the whole table.

   ⚠️ The cause is a default that catches everybody: a flex or grid CHILD is `min-width: auto`, not
   zero, so it will not shrink below the width of its own content no matter what you set on the
   parent. One long unbroken word, one wide button row, and the column pushes the shell wider than
   the window. Setting `min-width: 0` restores the ability to shrink, and then the inner scroll
   regions do their job.
   https://developer.mozilla.org/docs/Web/CSS/min-width#auto */
.pgm-play[b-bu6eiqrurf],
.pgm-play > *[b-bu6eiqrurf],
.pgm-play-panes > [class*="col-"][b-bu6eiqrurf] {
    min-width: 0;
}

/* Vertical only. The shell should never offer a sideways scrollbar: anything genuinely too wide
   scrolls inside its own box below, which keeps the headings and the action box where they are. */
@media (min-width: 992px) {
    .pgm-play[b-bu6eiqrurf] { overflow-x: hidden; }
}

/* The party row is the one thing that legitimately runs out of room, because a card has a minimum
   readable width and the number of them is set by who turned up. It scrolls ITSELF rather than
   widening the page. */
.pgm-play-party[b-bu6eiqrurf] {
    overflow-x: auto;
}

/* Long words with no break opportunity — a pasted URL, a very long character name — are the other
   way a column silently sets a floor under its own width. */
.pgm-play-transcript[b-bu6eiqrurf],
.pgm-play-panes[b-bu6eiqrurf] {
    overflow-wrap: anywhere;
}


/* ── Exits: one compact strip ────────────────────────────────────────────────────────────────
   The heading and bulleted list around two one-line exits cost more vertical space than the exits
   did. This is the affordance that says what you can do next, so it gets to stay on screen, which
   means it has to be cheap. */
.pgm-exits[b-bu6eiqrurf] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .35rem;
}

.pgm-exits-label[b-bu6eiqrurf] {
    font-size: .8rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    opacity: .55;
    margin-right: .15rem;
}

/* A player's exits are not buttons, because a player cannot move the party: only the game master
   can. Styled as quiet chips so they read as information rather than as controls that do nothing. */
.pgm-exit[b-bu6eiqrurf] {
    padding: .1rem .45rem;
    border: 1px solid var(--pgm-line);
    border-radius: 999px;
    font-size: .85rem;
    opacity: .85;
}

/* The fallback drawer for a feature the narration has not mentioned. Closed by default: the point
   of removing the standing list was that an always-visible inventory of interactable nouns tells
   you which things matter before you have looked at any of them. */
.pgm-examine > summary[b-bu6eiqrurf] {
    cursor: pointer;
    user-select: none;
}

/* Interact buttons under the narration that named the thing. */
.pgm-interact-row[b-bu6eiqrurf] {
    display: flex;
    flex-wrap: wrap;
    gap: .3rem;
    padding-top: .15rem;
}

/* The voice/camera failure notice. Collapsed to one line: it is chrome about a service, and the
   page underneath it is somebody's game. */
.pgm-play-averror > summary[b-bu6eiqrurf] {
    cursor: pointer;
    list-style: revert;
}
.pgm-play-averror > div[b-bu6eiqrurf] {
    max-height: 8rem;
    overflow-y: auto;
    overflow-wrap: anywhere;
}

/* ── The party gallery: one surface, edge to edge ─────────────────────────────────────────────
   The cards used to sit loose on the page background, so the row read as several objects rather
   than as one thing: the table. */
.pgm-play-gallery[b-bu6eiqrurf] {
    background: rgba(255, 255, 255, .025);
    border: 1px solid rgba(255, 255, 255, .08);
    border-radius: .4rem;
    padding: .5rem;
}

/* ⚠️ No horizontal scrollbar on the gallery. On a narrow window the seats wrap onto a second line
   rather than sliding sideways, because a scrollbar hides people and the whole point of this strip
   is that you can see everybody at once. */
.pgm-play-gallery > .row[b-bu6eiqrurf] {
    overflow-x: visible;
}

/* ── Short windows: the furniture gives way before the reading does ──────────────────────────
   Reported on a 1440-wide window at laptop height: the location art was cut off, the inventory had
   its own scrollbar and the transcript was pushed past the bottom. The shell fits the viewport, so
   anything that does not shrink pushes something else out — and the things that should yield are
   the decorative ones. */
@media (max-height: 950px) {
    /* The gallery is faces and hit points. It reads fine smaller, and it is the largest fixed block
       above the fold. */
    .pgm-play-gallery[b-bu6eiqrurf] { padding: .3rem; }
    .pgm-play-party[b-bu6eiqrurf] { max-height: 24vh; }

    /* The location image is the single biggest discretionary block on the page. */
    .pgm-play-map[b-bu6eiqrurf] { max-height: 22vh; object-fit: cover; }
}

@media (max-height: 800px) {
    .pgm-play-party[b-bu6eiqrurf] { max-height: 20vh; }
    .pgm-play-map[b-bu6eiqrurf] { max-height: 16vh; object-fit: cover; }
}
/* /Components/SetupHelp.razor.rz.scp.css */
/* ⚠️ Wider, and sized by its content rather than fixed.

   .pgm-overlay-panel is deliberately a FIXED height, because HelpOverlay has tabs and a box that
   resized per tab jumped under the cursor. That reasoning does not apply here: this dialog has no
   tabs, so a fixed height only means a short guide sits in a tall box and a long one scrolls when
   it did not have to. A max-height gives each guide the room it actually needs and keeps the cap
   for the one that would overflow.

   Overriding rather than changing the shared class, so the tabbed panel keeps the behaviour it was
   given for a reason. Scoped CSS wins on specificity: the generated [b-xxxxx] attribute makes this
   .pgm-setup-panel[b-xxxxx] against a bare .pgm-overlay-panel. */
.pgm-setup-panel[b-y8oz8gyd8j] {
    width: min(56rem, 100%);
    height: auto;
    max-height: min(90dvh, 54rem);
    min-height: 0;
}

/* ⚠️ The dialog is sized to match the page behind it, explicitly.

   The overlay sets no font-size of its own, so this is not inherited from anywhere sensible: it has
   to be stated. Two passes to get it right, both worth recording because they are the same mistake
   in opposite directions.

   First version left it at the browser default and read as "massive text" beside a settings page
   built almost entirely from .form-text and .small. Second version dropped to .875rem and read as
   too small. The answer is neither preference: it is to MATCH THE PAGE. Body text here is the size
   of the page's labels and inputs (1rem), and the section headings are the size of its fieldset
   legends (.8rem, uppercase), so the dialog looks like part of the same screen rather than a
   component from somewhere else. */
.pgm-setup-body[b-y8oz8gyd8j] {
    font-size: 1rem;      /* same as .form-label and the inputs on the page behind */
    line-height: 1.5;
}

/* The console link is the one thing you may want to hit before reading anything, but at body size:
   it was rendering as the largest text in the dialog, which made it look like the heading. */
.pgm-setup-url[b-y8oz8gyd8j] {
    font-size: 1rem;
    word-break: break-all;   /* a long console path must not widen the panel */
}

/* Section headings read as labels, matching the fieldset legends on the page behind. */
/* Deliberately the same rule as .pgm-field-group > legend, so a heading in here and a heading on
   the card behind it are the same thing rendered twice, not two sizes of label. */
.pgm-setup-body h6[b-y8oz8gyd8j] {
    font-size: .8rem;
    font-weight: 600;
    letter-spacing: .02em;
    text-transform: uppercase;
    color: var(--bs-secondary-color);
    margin: .9rem 0 .3rem;
}

.pgm-setup-body h6:first-of-type[b-y8oz8gyd8j] { margin-top: 0; }

.pgm-setup-body ol[b-y8oz8gyd8j] {
    margin: 0;
    padding-left: 1.15rem;
}

.pgm-setup-body li[b-y8oz8gyd8j] { margin-bottom: .3rem; }

.pgm-setup-body .alert[b-y8oz8gyd8j] {
    font-size: .95rem;
    padding: .55rem .7rem;
}

.pgm-setup-foot-note[b-y8oz8gyd8j] {
    font-size: .85rem;
}
