/* ============================================================================
   The narrow-screen layout — the app shell, the chat page, and the signed-in
   pages behind it.

   Everything here is scoped two ways at once, and both matter:

     1. Inside a width media query, always. The desktop layout at >=960px is
        meant to be byte-identical to what it was before this file existed, so
        nothing here may apply unconditionally. The handful of rules that do sit
        outside a query only ever hide something (.stn-narrow-*), and they are
        the default state that the query then undoes.

     2. Nested under something specific enough to win. This is a global
        stylesheet like site.css, and Chat.razor's own <style> block is still
        unscoped and renders in the body — after this file in the head — so it
        takes any tie on equal specificity. Chat rules nest under
        .stn-chat-shell for that reason; the popover rules further down use
        .mud-popover for the same reason and can't use the shell, because
        MudBlazor renders menus outside it.

   960px is not a taste call: MudBlazor puts .mud-grid-item-md-* inside
   @media (min-width:960px), so max-width:959px means exactly "MudGrid has
   stacked its items" with no dead band in between.
   ============================================================================ */

:root {
    /* The bottom tabs' own height, named once because two very different things
       have to agree on it: the bar sets it, and every page behind the bar has to
       keep that much clear so its last row isn't sitting underneath. */
    --stn-tabbar-h: calc(52px + env(safe-area-inset-bottom));
}

/* --- Shared with desktop: the pane heights that used to be inline ----------
   These two were style="height:calc(100dvh - 140px)" attributes on the markup.
   An inline style can't be overridden by a class without !important, and the
   mobile shell needs to own its own height, so they moved here unchanged and
   guarded to the width they were always meant for. 140px is the app bar, the
   "chatting as" row and the footer link row. */
@media (min-width: 960px) {
    /* vh first, dvh second — this is the rule a landscape tablet lands on, and
       a browser without dvh would otherwise drop it and leave both panes with no
       height at all. */
    .stn-side-col,
    .stn-main-paper { height: calc(100vh - 140px);
                      height: calc(100dvh - 140px); }
}

/* The app bar and the footer links, on the chat route only. The bar carries eight controls that
   need about 500px and it is the sole reason the page scrolled sideways on a phone; the footer is
   three links that belong on a page you can scroll, not under a composer. Both come back at 960px
   and on every other route. */
@media (max-width: 959px) {
    .mud-appbar.stn-appbar-chat { display: none; }
    /* !important only because the element also carries MudBlazor's .d-flex
       utility, and that is itself !important — specificity can't outrank it. */
    .stn-footer-chat { display: none !important; }
}

/* ---------------------------------------------------------------------------
   The app shell.

   The document does not scroll on a phone; a region inside it does. That is the
   difference between a page and an app, and it is not only cosmetic — it is the
   fix for two real defects.

   A mobile browser has two viewports. The *layout* viewport is the tall one,
   measured as if the browser's own toolbar were hidden; the *visual* viewport is
   what you can actually see right now. `position: fixed; inset: 0` anchors to
   the layout viewport, so while the toolbar is on screen the bottom of a
   full-height fixed element sits behind it — which is why the composer's picture
   and emote buttons were cut off. And a bar pinned with `bottom: 0` gets shoved
   around as that toolbar slides in and out, which is the drift you see when
   scrolling.

   Pinning the shell to 100dvh instead — the dynamic viewport height, which
   tracks the toolbar — makes both go away, and the bottom bar becomes an
   ordinary flex row that has nothing left to slide against.
   --------------------------------------------------------------------------- */
@media (max-width: 959px) {
    /* No `overscroll-behavior: none` here, and that omission is the rule.

       The document not scrolling (overflow:hidden) does not stop a phone's
       pull-to-refresh — suppressing that takes overscroll-behavior, which is
       what used to be on this line. It cost more than it bought. Reloading is
       the one recovery an app like this cannot script away: a circuit that has
       gone for good leaves a page that only a fresh load fixes, and dragging
       down is the gesture everyone reaches for first. Taking it away left
       people with a screen they could not revive.

       Chaining is kept out of the places where an accidental reload would cost
       something — see overscroll-behavior-y on the transcript and the sheets —
       so a pull only ever reaches the browser from the app's own chrome. */
    html, body { height: 100%; overflow: hidden; }

    /* 100vh first, 100dvh second. dvh needs Chrome 108 and is the whole point
       here — it tracks the browser's toolbar — but a browser without it drops
       that line and keeps 100vh, which is the same number minus the toolbar
       tracking. Without the fallback it would drop *both* and the shell would
       have no height at all, taking the entire app with it. */
    .stn-mobile-shell { position: fixed; top: 0; left: 0; right: 0;
                        height: 100vh;
                        height: 100dvh; max-width: none;
                        display: flex; flex-direction: column;
                        padding: 0; margin: 0; }

    .stn-mobile-shell > .mud-appbar { flex: 0 0 auto; }

    /* The one thing that scrolls, and it scrolls one way. min-height:0 is what
       allows a flex child to be shorter than its content instead of stretching
       the column.

       overflow-x is set explicitly, and that is not tidying: asking for
       overflow-y:auto on its own makes the *other* axis compute to auto too
       (CSS won't pair a scrolling axis with a visible one), so a single stray
       pixel anywhere on any page turns into a sideways scrollbar for the whole
       app. A column of text and cards has no business scrolling horizontally;
       anything that genuinely needs to — a wide table, a code block — should
       carry its own scroller rather than moving the entire page under the
       reader's thumb. */
    .stn-mobile-shell > .mud-main-content { flex: 1 1 auto; min-height: 0;
                                            overflow-x: hidden; overflow-y: auto;
                                            -webkit-overflow-scrolling: touch;
                                            padding: 0 12px; }

    /* Pages keep their side padding from the rule above, so the container's own
       is dropped — otherwise everything is indented twice. */
    .stn-mobile-shell > .d-flex { padding-left: 12px; padding-right: 12px; }

    /* --- The reconnection overlay ----------------------------------------
       The last fixed element still anchored to the layout viewport, and the
       worst one to leave there. site.css gives it `inset: 0`, so on a phone it
       is as tall as the page would be with the browser's toolbar hidden;
       centring the panel in that box puts it low, and its button — the only
       way out of a dead circuit — sits behind the toolbar. Exactly the trap
       the composer fell into further up, on the one screen where there is
       nothing else left to press.

       Pinned to the dynamic viewport height instead, as the shell is. `bottom`
       is released explicitly rather than relying on the over-constrained-box
       rule to drop it.

       It also scrolls now: on a short phone the taller states (the panel plus
       the log-out link) can still outgrow the screen, and a panel that cannot
       be scrolled to is a panel whose button does not exist. `safe center`
       keeps the top reachable when that happens instead of centring it out of
       the scrollable area — browsers without it fall back to the plain
       `center` in site.css, which is the behaviour we have today.

       Deliberately not `overscroll-behavior: contain`: when this overlay is up,
       the page underneath is dead, and a pull-to-refresh is a recovery rather
       than an accident. */
    #components-reconnect-modal {
        bottom: auto;
        height: 100vh;
        height: 100dvh;
        overflow-y: auto;
        align-items: safe center;
    }
}

@media (max-width: 959px) {
    /* One screen, not two. Before this, both panes kept their own 100dvh-140px
       height and MudGrid simply stacked them, so the document came out roughly
       two viewports tall: a full-screen roster with the conversation below the
       fold.

       Fixed to the viewport now that the app bar is gone from this route: there
       is no longer a z-index 1300 element to paint over us, the document behind
       has nothing left to scroll, and the shell escapes MudContainer's padding
       to go full-bleed. z-index stays low so MudBlazor's popovers (1200) — every
       menu and tooltip on the page — still land above it. */
    /* Fills the shell's scrolling region rather than pinning itself to the
       viewport. It used to be position:fixed with inset:0, which anchored it to
       the *layout* viewport — taller than what you can see whenever the browser's
       toolbar is up — and that is what pushed the composer's picture and emote
       buttons off the bottom of the screen.

       The keyboard is subtracted here rather than at the shell, because only this
       screen has something that must stay above it. The fallback is what a
       prerendered first paint and a browser without visualViewport get, and both
       are correct.

       The home indicator is handled further down instead, by whichever element
       actually reaches the bottom edge — put here it would leave a band of bare
       page below the tabs rather than letting their own background fill it. */
    /* Grows into the region rather than claiming a percentage of it. This was
       height:calc(100% - …), and a percentage height needs a parent whose height
       is *definite*. Modern browsers treat a flex-resolved height as definite, so
       it worked — but that was a clarification to the spec, and an older engine
       resolves the 100% to auto instead: the shell shrinks to its content, the
       panes inside get nothing to divide, and the bottom tabs ride up to sit just
       under the header. Growing as a flex child asks no such question.

       The keyboard comes off as a bottom margin, which flex subtracts from the
       height it hands out — same result as the old calc(), no percentages. */
    .stn-chat-shell { flex: 1 1 auto; min-height: 0;
                      margin-bottom: var(--stn-kb, 0px);
                      display: flex; flex-direction: column; overflow: hidden;
                      /* The containing block for the overlays further down. */
                      position: relative; }

    /* Chat owns its region outright: no page padding, and nothing here scrolls
       except the transcript and the two lists inside it. A flex column so the
       shell above has something to grow into. */
    .stn-chat-page > .mud-main-content { padding: 0; overflow: hidden;
                                         display: flex; flex-direction: column; }

    /* With a keyboard up there is barely a conversation left to look at, and the
       tabs are exactly where it lands. They come back when it goes. */
    .stn-kb-open .stn-mobile-tabs { display: none; }

    /* MudGrid's negative gutter margins buy nothing when there is one item on
       screen, and they cost horizontal overflow on a narrow viewport.

       flex-wrap: nowrap is the load-bearing one, and it is what makes the
       transcript scroll at all.

       .mud-grid is flex-wrap: wrap, and a wrapping flex container sizes each
       line from its items' content — the container's own height only decides
       how leftover space is *handed out* between lines, never how a line is cut
       down. So the pane grew to the height of the whole conversation, the line
       grew with it, and the grid overflowed the shell; overflow:hidden up there
       then clipped it, and there was nothing left with a scrollbar. Every
       min-height:0 below this point was correct and none of them could help,
       because the chain never reached them with a height to divide.

       With one line the container's cross size is definite and align-items
       stretch clamps the item to it, which is what the transcript needs to
       resolve its own height and scroll inside it. Nothing is given up: below
       960px exactly one of the two items is on screen (see data-view), so there
       has never been anything here to wrap. */
    .stn-chat-shell .stn-chat-grid { flex: 1 1 auto; min-height: 0;
                                     flex-wrap: nowrap;
                                     margin: 0; width: 100%; }
    /* min-width:0 is load-bearing, not tidying. A flex item defaults to
       min-width:auto, which refuses to shrink below its content's min-content
       width — and the peer header (six controls) and composer (five) are wider
       than a 390px phone. Without it the pane lays out wider than the viewport
       and the shell's overflow:hidden clips the Send button off the screen
       instead of fitting it. Letting the item shrink pushes the decision down to
       the content, which is where the later stages deal with it. */
    .stn-chat-shell .stn-side-item,
    .stn-chat-shell .stn-main-item { flex: 1 1 100%; min-width: 0; max-width: 100%;
                                     min-height: 0; display: flex; padding: 0; }
    .stn-chat-shell .stn-side-col,
    .stn-chat-shell .stn-main-paper { flex: 1 1 auto; min-width: 0; min-height: 0; }

    /* One pane at a time. The attribute mirrors a field the page already keeps,
       so the switch costs no viewport detection and no circuit round-trip on
       resize — the browser re-evaluates the query and that is the whole of it. */
    .stn-chat-shell[data-view="conversation"] .stn-side-item { display: none; }
    .stn-chat-shell[data-view="list"] .stn-main-item { display: none; }

    /* And one list at a time within it. On a desktop these two sit above and
       below each other with a splitter between; on a phone that split gives each
       of them half of not very much, so the tabs choose instead. */
    .stn-chat-shell[data-pane="people"] .stn-side-convos { display: none; }
    .stn-chat-shell[data-pane="chats"] .stn-side-roster { display: none; }

    /* Which persona you're speaking as belongs to the list screen. Over a
       conversation it's a line of chrome above a header that already names both
       sides, and height is the scarce thing here. */
    .stn-chat-shell[data-view="conversation"] .stn-chatting-as { display: none; }

    /* The way back out. Desktop has no equivalent because it never leaves the
       roster on screen to begin with. */
    .stn-chat-shell .stn-mobile-back { display: inline-flex; }

    /* The back arrow is a seventh control in a header that already ran to the
       edge, and every icon button is effectively unshrinkable — so without
       somewhere to give, the last item (the ⋮ menu, holding block, report and
       the note editor) lays out past the pane and gets clipped away entirely.
       The nickname is the one child that can absorb it, and truncating a long
       name beats losing a menu. Stage 4 gives the name its room back by moving
       watch and star into that same ⋮. */
    .stn-chat-shell .stn-peer-head { min-width: 0; }
    /* flex:1 1 auto, not just shrinkable: the name should take whatever the
       fixed-width controls leave rather than be the first thing squeezed. Left
       to shrink alone it collapsed to nothing and the header stopped saying who
       you were talking to at all. */
    .stn-chat-shell .stn-peer-head > .mud-typography-h6 { flex: 1 1 auto; min-width: 0;
                                                          overflow: hidden; text-overflow: ellipsis;
                                                          white-space: nowrap; }

    /* The other thing that reaches the bottom: with a conversation open the tabs
       are gone and the composer is the last row. MudBlazor's pa-2 is the 8px. */
    .stn-chat-shell #stn-composer { padding-bottom: calc(8px + env(safe-area-inset-bottom)); }

    /* --- iOS zoom --------------------------------------------------------
       Safari zooms the page whenever a focused field is under 16px, and then
       leaves it zoomed — on a fixed full-screen layout that strands you looking
       at part of a conversation with no obvious way back. MudBlazor hard-codes
       .875rem on its inputs as a literal, not from the theme, so the theme can't
       fix this; and touching the theme would change every page at every width.
       Scoped to the shell and to this width, desktop typography is untouched. */
    .stn-chat-shell .mud-input-control > div.mud-input.mud-input-text,
    .stn-chat-shell .mud-input-control input.mud-input-slot,
    .stn-chat-shell .mud-input-control textarea.mud-input-slot,
    .stn-chat-shell .mud-select .mud-input-slot { font-size: 16px; }

    /* --- Undo desktop splitter state -------------------------------------
       Both of these were authored outside any media query, so they have been
       reaching phones all along.

       .stn-side-convos reads --stn-convo-basis, which stnChat.applySplitters
       writes to :root from localStorage. That value is per-device, so a height
       dragged in a desktop session was constraining the same device's phone
       layout; and the 50% ceiling is a desktop instinct that leaves a phone
       roster and conversation list fighting over one short column.

       .stn-split-h is a 10px row-resize strip. Only .stn-split-v was hidden
       below 960px — this one has been live on touch, sitting between the two
       lists with nothing sensible to do. */
    .stn-chat-shell .stn-side-convos { flex: 1 1 auto; max-height: none; }
    .stn-chat-shell .stn-split-h { display: none; }

    /* --- Menu contents ---------------------------------------------------
       Prefixed with .mud-popover, not .stn-chat-shell, and the distinction is
       load-bearing twice over.

       MudBlazor renders every menu into its popover provider at the end of the
       layout, so this markup is a sibling of the shell rather than a descendant
       — a .stn-chat-shell prefix would match nothing at all.

       And it does need *a* prefix: the page's own <style> block is emitted in
       the body, after this file in the head, so a bare .stn-emoji here would
       tie on specificity and lose on source order. .mud-popover is the real
       parent, so it buys the specificity honestly rather than by repeating a
       class name to inflate the count.

       8 fixed columns of 34px is 286px of grid inside a popover capped at 80vw:
       a crush on a 360px phone, and each cell narrower than a thumb. Let the
       column count follow the width instead. */
    .mud-popover .stn-emoji { width: 40px; height: 40px; }
    /* The explicit width is required, not decorative: auto-fill has nothing to
       fill unless the track's container is a definite size, and a popover sizes
       to its content. The old repeat(8, 34px) supplied that width by accident of
       being fixed; drop to auto-fill without replacing it and the grid collapses
       to two columns. */
    .mud-popover .stn-emoji-grid { width: min(320px, calc(100vw - 32px));
                                   grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)); }

    /* A flat 320px panel holding a row of text, a risk chip and a delete button
       has nothing left for the text at this width. */
    .mud-popover .stn-canned { width: min(320px, calc(100vw - 32px)); }
    .mud-popover .stn-canned-risk-note { padding: 6px 10px 2px; font-size: .68rem;
                                         line-height: 1.35;
                                         color: var(--mud-palette-text-secondary); }

}

/* --- Bottom tabs ---------------------------------------------------------
   Hidden by default rather than shown-then-hidden: on a desktop these never
   exist, and the app bar above does the same job at a width that fits it. */
.stn-mobile-tabs { display: none; }

@media (max-width: 959px) {
    /* Part of the shell's flex column rather than fixed to the bottom, so the
       panes above simply get the height that is left and nothing has to be told
       how tall the tabs are. */
    /* The last row of the app column, not a pinned bar. Nothing to slide against:
       the column is exactly 100dvh, so the bar sits at the bottom of what you can
       see rather than at the bottom of a taller viewport hidden behind the
       browser's toolbar.

       Reaches the bottom of the screen, so it carries the home-indicator inset
       and lets its own background fill it rather than leaving a bare strip. */
    .stn-mobile-tabs { flex: 0 0 auto; display: flex; align-items: stretch;
                       border-top: 1px solid var(--mud-palette-lines-default);
                       background: var(--mud-palette-surface);
                       padding-bottom: env(safe-area-inset-bottom); }

    /* A conversation is a screen in its own right: it has a back arrow of its
       own, and tabs beneath a composer are exactly where the keyboard lands. */
    .stn-chat-shell[data-view="conversation"] .stn-mobile-tabs { display: none; }

    /* --- Dialog buttons --------------------------------------------------
       MudBlazor lays these out as one nowrap row. Several of ours put a
       "Don't show this again" checkbox in there alongside the buttons, held to
       the left with mr-auto — and on a phone the row is far too narrow for all
       three, so the checkbox is pushed out past the panel edge and simply cannot
       be reached. Which means the one control whose entire purpose is "stop
       asking me" is the one you can't press.

       Given a row of its own it fits, and the buttons keep theirs. */
    .mud-dialog-actions { flex-wrap: wrap; }
    .mud-dialog-actions > .mud-checkbox { flex: 1 1 100%; margin-left: 0; }

    .stn-tab { flex: 1 1 0; display: flex; flex-direction: column; align-items: center;
               justify-content: center; gap: 2px; min-height: 52px; padding: 6px 4px;
               border: 0; background: transparent; cursor: pointer;
               font-size: .68rem; letter-spacing: var(--stn-track-label);
               color: var(--mud-palette-text-secondary); }
    .stn-tab-on { color: var(--mud-palette-primary); }
    /* The selected tab is named by colour and by a rule under it, not colour
       alone — the two lists differ by one word and a tint is a thin signal. */
    .stn-tab-on::after { content: ''; position: absolute; margin-top: 46px;
                         width: 22px; height: 2px; border-radius: 999px;
                         background: var(--mud-palette-primary); }

    .stn-tab-icon { position: relative; display: inline-flex; }
    .stn-tab-badge { position: absolute; top: -5px; left: 11px; min-width: 15px; height: 15px;
                     padding: 0 4px; border-radius: 999px; font-size: .6rem; line-height: 15px;
                     text-align: center; background: var(--mud-palette-error);
                     color: var(--mud-palette-error-text); }
}

/* ===========================================================================
   Narrow screens only — the composer's second row.

   Five controls abreast is fine on a desktop and hopeless on a narrow phone: the
   picture, canned-reply and emote buttons are 48px each and Send is another 64,
   so at 390px the box you actually type in was left with 189px, or about 133
   with VIP+ canned replies in the row. Giving the field its own row and letting
   the buttons wrap beneath fixes that, and keeps every control.

   But it must be keyed on *narrow*, not on *mobile*. The same phone turned
   sideways is 844px wide and only 390 tall, and there height is the scarce axis:
   the second row costs 65px of it, and with the keyboard up that arithmetic goes
   negative — 390 minus a ~226px landscape keyboard, a 68px peer header and a
   121px two-row composer leaves -25px, so the composer cannot be shown at all.
   In landscape there is width to spare and one row fits comfortably.

   560px is where the single row stops being cramped: 241px of buttons and gaps
   leaves the text field around 300px.
   =========================================================================== */
@media (max-width: 559px) {
    /* order:-1 rather than reordering the markup, so the tab order and the DOM
       still read the way the desktop lays them out. */
    .stn-chat-shell #stn-composer { flex-wrap: wrap; }
    .stn-chat-shell #stn-composer > .mud-input-control { flex: 1 1 100%; order: -1; }
    /* The only direct-child <button> is Send — the icon buttons each sit inside a
       tooltip or menu wrapper. Pushed to the far end of the second row. */
    .stn-chat-shell #stn-composer > button { margin-left: auto; }
}

/* ===========================================================================
   Short screens — a phone held sideways, where height is what runs out.
   =========================================================================== */
@media (max-width: 959px) and (max-height: 480px) {
    /* Which persona you are speaking as is worth a row when there are 800px of
       them and not when there are 390. It is still named in the app bar. */
    .stn-chat-shell .stn-chatting-as { display: none; }

    /* Same five tabs, less of them: the label drops to a size that still reads
       and the row gives back a dozen pixels to the list above it. */
    .stn-mobile-tabs .stn-tab { min-height: 42px; padding: 4px; font-size: .62rem; }
    .stn-mobile-tabs .stn-tab-on::after { margin-top: 36px; }
}

/* ===========================================================================
   Touch targets — keyed on the pointer, not on the width.

   These began life inside the max-width:959px block, and that was a mistake: it
   treats "narrow" and "touched" as the same question. They are not. A tablet in
   landscape is 1280 CSS px, so it correctly gets the two-pane desktop layout —
   and then got 26px controls and a 12px drag splitter, on a screen operated with
   a finger.

   `pointer: coarse` describes the primary input device instead, which is the
   thing that actually decides how big a target has to be. A mouse or trackpad
   reports `fine`, so none of this reaches a desktop — including a laptop with a
   touchscreen, whose *primary* pointer is still the trackpad.

   Layout stays keyed on width, where it belongs. The two questions are
   independent, and the CSS now says so.
   =========================================================================== */
@media (pointer: coarse) {
    /* Nearly every control in the app is Size.Small, which lands around 26px:
       a comfortable mouse target and a poor thumb one. Raised in one place
       rather than per control, so a button added later inherits it.

       Scoped to page content, menus and the chat shell rather than set globally,
       so it cannot reach the input adornments MudBlazor builds its text fields
       out of — forcing those to 44px would inflate every input on the site. */
    .mud-main-content .mud-icon-button,
    .mud-popover .mud-icon-button,
    .stn-chat-shell .mud-icon-button { min-width: 44px; min-height: 44px; }
    .mud-main-content .mud-button { min-height: 44px; }

    /* Sized rather than padded where the control has its own box: growing
       .stn-convo-close by padding alone would push the unread badge off the row
       it sits on. */
    .stn-chat-shell .stn-row { padding: 10px 8px; }
    .stn-chat-shell .stn-convo-close { padding: 10px; }
    .stn-chat-shell .stn-convo-close-placeholder { width: 44px; }

    /* Cells narrower than a thumb, in a grid of 8 fixed 34px columns. */
    .mud-popover .stn-emoji { width: 40px; height: 40px; }

    /* The numeric field's up/down arrows are 20x20 and a mouse affordance
       besides — a touch device raises a number pad for the field itself, so the
       arrows are a hard target for something you can already do more easily.
       Hidden rather than enlarged: at 44px each they would make the field taller
       than the rows around it.

       Matching MudBlazor's own selector rather than the short one: it declares
       this at .mud-input-control.mud-input-number-control
       .mud-input-numeric-spin, and a bare .mud-input-numeric-spin loses to it
       three specificity points to one however late it loads. */
    .mud-input-control.mud-input-number-control .mud-input-numeric-spin { display: none; }

    /* The footer links are 18px tall. Read rarely, and at the very end of a
       page — but that is no reason for them to be hard to hit. */
    .mud-main-content ~ div .mud-link,
    .stn-footer-chat .mud-link { display: inline-block; padding: 12px 4px; }

    /* Per-message actions sit absolutely over the top-right of the message body,
       revealed on hover. Chat.razor's (hover:none) block already pins them
       visible on a touch screen — which means three now-44px buttons permanently
       covering the first line of every message. Put them under it instead, where
       they cover nothing and read as belonging to it. */
    .stn-chat-shell .stn-msg-actions { position: static; opacity: 1;
                                       display: flex; justify-content: flex-end;
                                       background: transparent; }
    .stn-chat-shell .stn-msg { flex-wrap: wrap; }

    /* Both splitters are a drag with no touch equivalent, and the one between
       the two lists is a 10px strip. On a tablet the two-pane layout is still
       right; resizing it by dragging a hairline is not. */
    .stn-split-v, .stn-split-h { display: none; }
}

/* --- Controls that exist at one width only --------------------------------
   Hidden by default so the button never appears on desktop, where the roster is
   always on screen and there is nothing to go back to. */
.stn-mobile-back { display: none; }

/* The peer's presence in words. Desktop reads it off the dot's tooltip. */
.stn-peer-presence { display: none; }

/* Content that exists at one width only. Two classes rather than one because
   what "shown" means differs: a menu item has to come back as a flex row, a
   paragraph as a block, and there is no single value that is right for both.
   Anything a desktop only says on hover has a narrow-width twin here — a
   tooltip needs a hover a touch screen hasn't got, and while MudBlazor's do
   open on a long press, nothing on screen suggests trying it. */
.stn-narrow-only,
.stn-narrow-block,
.stn-narrow-inline { display: none !important; }

@media (max-width: 959px) {
    .stn-wide-only { display: none !important; }
    .stn-narrow-only { display: flex !important; }
    .stn-narrow-block { display: block !important; }
    .stn-narrow-inline { display: inline !important; }

    .stn-peer-presence { display: inline; font-size: .72rem; white-space: nowrap;
                         color: var(--mud-palette-text-secondary); }

    /* --- Overlays become sheets ------------------------------------------
       All three are position:absolute inside .stn-pane on a desktop, which is
       right there: the pane is most of the window. On a phone the pane is one
       region of the shell, so "cover the pane" leaves a lightbox with the header
       still above it. Re-anchored to the shell instead, which now covers exactly
       what you can see — deliberately not position:fixed, because that would
       resolve against the layout viewport and hang behind the browser's toolbar,
       the same trap the composer fell into.

       They stay well under MudBlazor's popover layer (1200): the gallery holds
       PictureThumb's ⋮ menu, and a gallery that outranked its own menu would
       bury it. */
       Longhands rather than `inset: 0`, which needs Chrome 87 — on an older
       browser the shorthand is dropped entirely and the sheet collapses to
       nothing rather than covering the screen. */
    .stn-chat-shell .stn-profile-panel,
    .stn-chat-shell .stn-gallery,
    .stn-chat-shell .stn-lightbox { position: absolute;
                                    top: 0; right: 0; bottom: 0; left: 0;
                                    max-height: none;
                                    padding-bottom: env(safe-area-inset-bottom); }
    /* Contained for the same reason as the transcript: these cover the screen, so a scroll
       that runs past their end would otherwise chain to the document and reload the page
       out from under an open sheet. */
    .stn-chat-shell .stn-profile-panel { z-index: 20; overflow-y: auto;
                                         overscroll-behavior-y: contain; }
    .stn-chat-shell .stn-gallery { z-index: 21; }
    .stn-chat-shell .stn-lightbox { z-index: 22; }

    /* --- The roster filter, as a sheet -----------------------------------
       Eight facets inline in a short scroller push the list they are filtering
       clean off the screen, and the Apply button ends up somewhere below the
       fold. Over the whole screen instead, with the buttons pinned where a
       thumb already is. */
    .stn-chat-shell .stn-filter-form { position: absolute;
                                       top: 0; right: 0; bottom: 0; left: 0; z-index: 19;
                                       margin: 0; border-radius: 0; overflow-y: auto;
                                       overscroll-behavior-y: contain;
                                       background: var(--mud-palette-surface);
                                       padding-bottom: calc(64px + env(safe-area-inset-bottom)); }
    .stn-chat-shell .stn-filter-actions { position: absolute; left: 0; right: 0;
                                          bottom: env(safe-area-inset-bottom);
                                          margin: 0; padding: 10px 12px; z-index: 1;
                                          background: var(--mud-palette-surface);
                                          border-top: 1px solid var(--mud-palette-lines-default); }
}
