/* /Components/Layout/DesktopLayout.razor.rz.scp.css */
/* /Components/Layout/ReactiveLayout.razor.rz.scp.css */
/* /Components/Layout/ReconnectModal.razor.rz.scp.css */
.components-reconnect-first-attempt-visible[b-1f42o0xh0h],
.components-reconnect-repeated-attempt-visible[b-1f42o0xh0h],
.components-reconnect-failed-visible[b-1f42o0xh0h],
.components-pause-visible[b-1f42o0xh0h],
.components-resume-failed-visible[b-1f42o0xh0h],
.components-rejoining-animation[b-1f42o0xh0h] {
    display: none;
}

#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible[b-1f42o0xh0h],
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation[b-1f42o0xh0h],
#components-reconnect-modal.components-reconnect-paused .components-pause-visible[b-1f42o0xh0h],
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible[b-1f42o0xh0h],
#components-reconnect-modal.components-reconnect-retrying[b-1f42o0xh0h],
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible[b-1f42o0xh0h],
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation[b-1f42o0xh0h],
#components-reconnect-modal.components-reconnect-failed[b-1f42o0xh0h],
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible[b-1f42o0xh0h] {
    display: block;
}


#components-reconnect-modal[b-1f42o0xh0h] {
    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-1f42o0xh0h 0.5s both;
    color: black;
    &[open]

{
    animation: components-reconnect-modal-slideUp-b-1f42o0xh0h 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity-b-1f42o0xh0h 0.5s ease-in-out 0.3s;
    animation-fill-mode: both;
}

}

#components-reconnect-modal[b-1f42o0xh0h]::backdrop {
    background-color: rgba(0, 0, 0, 0.4);
    animation: components-reconnect-modal-fadeInOpacity-b-1f42o0xh0h 0.5s ease-in-out;
    opacity: 1;
}

@keyframes components-reconnect-modal-slideUp-b-1f42o0xh0h {
    0% {
        transform: translateY(30px) scale(0.95);
    }

    100% {
        transform: translateY(0);
    }
}

@keyframes components-reconnect-modal-fadeInOpacity-b-1f42o0xh0h {
    0% {
        opacity: 0;
    }

    100% {
        opacity: 1;
    }
}

@keyframes components-reconnect-modal-fadeOutOpacity-b-1f42o0xh0h {
    0% {
        opacity: 1;
    }

    100% {
        opacity: 0;
    }
}

.components-reconnect-container[b-1f42o0xh0h] {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1rem;
}

#components-reconnect-modal p[b-1f42o0xh0h] {
    margin: 0;
    text-align: center;
}

#components-reconnect-modal button[b-1f42o0xh0h] {
    border: 0;
    background-color: #6b9ed2;
    color: white;
    padding: 4px 24px;
    border-radius: 4px;
}

    #components-reconnect-modal button:hover[b-1f42o0xh0h] {
        background-color: #3b6ea2;
    }

    #components-reconnect-modal button:active[b-1f42o0xh0h] {
        background-color: #6b9ed2;
    }

.components-rejoining-animation[b-1f42o0xh0h] {
    position: relative;
    width: 80px;
    height: 80px;
}

    .components-rejoining-animation div[b-1f42o0xh0h] {
        position: absolute;
        border: 3px solid #0087ff;
        opacity: 1;
        border-radius: 50%;
        animation: components-rejoining-animation-b-1f42o0xh0h 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
    }

        .components-rejoining-animation div:nth-child(2)[b-1f42o0xh0h] {
            animation-delay: -0.5s;
        }

@keyframes components-rejoining-animation-b-1f42o0xh0h {
    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/Home.razor.rz.scp.css */
/*
   The page shell: a full-height flex column holding the window area above
   the drawer.

   Moved here from app.css, where it was the only global rule with exactly
   one consumer. It used to sit beside four Bootstrap utilities on the same
   element - `d-flex flex-column h-100 w-100` - which say the same four
   things with `!important`, so every declaration here except the two
   overflows was being overridden by a copy of itself. Those utilities are
   off the markup now and this rule is the single owner, which is the point:
   three mechanisms setting one layout is how they come to disagree.

   The vignette this class used to carry lives on <body> alongside the colour
   wash it belongs with - see the note there for why splitting them across
   two elements made the backdrop arrive in two visible pieces.
*/
.radial_background[b-96sbtjsgef] {
    display: flex;
    flex-direction: column;
    overflow-x: hidden;
    overflow-y: hidden;
    height: 100%;
    width: 100%;
}

/*
   min-height: 0 is the load-bearing one and is not tidiness: a flex item's
   automatic minimum size is its content, so without it a tall window refuses
   to shrink and pushes the drawer off the bottom of the screen.

   It was also spelled twice more on the element itself, as Bootstrap's
   `flex-grow-1` and a hand-rolled `min-h-0` utility in app.css that Bootstrap
   does not ship. Both were redundant - this rule is an element selector plus
   a scope attribute, so it outranks a bare class either way - and both are
   gone.
*/
main[b-96sbtjsgef] {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    overflow: visible;
    margin: 0px;
}
/* /Components/UI/CheckBox/CheckBox.razor.rz.scp.css */
/*
   A button drawn as a checkbox.

   It is a button because a real <input type="checkbox"> cannot be driven from
   the model once it has been clicked - see the note in the markup for the
   dirty-checkedness flag that causes it. Everything here is therefore about
   making a button read as the checkbox it stands in for, without inheriting
   any of Bootstrap's button styling.
*/
.checkbox[b-31zotzeqp0] {
    display: flex;
    align-items: center;
    gap: 8px;
    /* Strip the <button> back to text; this is a control the user reads, not
       a call to action competing with Submit. */
    padding: 0;
    border: none;
    background: none;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

    /*
       The box itself. Sized well above the surrounding text so it is a
       hittable target on a phone - 20px is the same compromise the titlebar
       buttons make, which are as large as a 36px titlebar allows.
    */
    .checkbox > i[b-31zotzeqp0] {
        flex: 0 0 auto;
        font-size: 22px;
        line-height: 1;
        opacity: 0.9;
        transition: opacity 0.15s ease, transform 0.15s ease;
    }

    /*
       min-width: 0 so the sentence can wrap on a narrow window instead of
       pushing the box out of the row - the same automatic-minimum-size rule
       .window_title_icon records in the titlebar.
    */
    .checkbox > span[b-31zotzeqp0] {
        min-width: 0;
    }

    .checkbox:hover > i[b-31zotzeqp0] {
        opacity: 1;
        transform: scale(1.05);
    }

    /*
       A visible focus ring, and it must stay. This is the only control in the
       form that cannot be reached by typing, so a keyboard user arrives here
       by tabbing and has nothing else to tell them where they are.
    */
    .checkbox:focus-visible[b-31zotzeqp0] {
        outline: 2px solid rgba(255, 204, 102, 0.9);
        outline-offset: 3px;
        border-radius: 4px;
    }

@media (prefers-reduced-motion: reduce) {
    .checkbox > i[b-31zotzeqp0] {
        transition: none;
    }

    .checkbox:hover > i[b-31zotzeqp0] {
        transform: none;
    }
}
/* /Components/UI/Cropper/Cropper.razor.rz.scp.css */
/*!
 * Cropper.js v1.6.1
 * https://fengyuanchen.github.io/cropperjs
 *
 * Copyright 2015-present Chen Fengyuan
 * Released under the MIT license
 *
 * Date: 2023-09-17T03:44:17.565Z
 */

.cropper-container[b-1a3twxwd0k] {
    direction: ltr;
    font-size: 0;
    line-height: 0;
    position: relative;
    -ms-touch-action: none;
    touch-action: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

    .cropper-container img[b-1a3twxwd0k] {
        backface-visibility: hidden;
        display: block;
        height: 100%;
        image-orientation: 0deg;
        max-height: none !important;
        max-width: none !important;
        min-height: 0 !important;
        min-width: 0 !important;
        width: 100%;
    }

.cropper-wrap-box[b-1a3twxwd0k],
.cropper-canvas[b-1a3twxwd0k],
.cropper-drag-box[b-1a3twxwd0k],
.cropper-crop-box[b-1a3twxwd0k],
.cropper-modal[b-1a3twxwd0k] {
    bottom: 0;
    left: 0;
    position: absolute;
    right: 0;
    top: 0;
}

.cropper-wrap-box[b-1a3twxwd0k],
.cropper-canvas[b-1a3twxwd0k] {
    overflow: hidden;
}

.cropper-drag-box[b-1a3twxwd0k] {
    background-color: #fff;
    opacity: 0;
}

.cropper-modal[b-1a3twxwd0k] {
    background-color: #000;
    opacity: 0.5;
}

.cropper-view-box[b-1a3twxwd0k] {
    display: block;
    height: 100%;
    outline: 1px solid #39f;
    outline-color: rgba(51, 153, 255, 0.75);
    overflow: hidden;
    width: 100%;
}

.cropper-dashed[b-1a3twxwd0k] {
    border: 0 dashed #eee;
    display: block;
    opacity: 0.5;
    position: absolute;
}

    .cropper-dashed.dashed-h[b-1a3twxwd0k] {
        border-bottom-width: 1px;
        border-top-width: 1px;
        height: calc(100% / 3);
        left: 0;
        top: calc(100% / 3);
        width: 100%;
    }

    .cropper-dashed.dashed-v[b-1a3twxwd0k] {
        border-left-width: 1px;
        border-right-width: 1px;
        height: 100%;
        left: calc(100% / 3);
        top: 0;
        width: calc(100% / 3);
    }

.cropper-center[b-1a3twxwd0k] {
    display: block;
    height: 0;
    left: 50%;
    opacity: 0.75;
    position: absolute;
    top: 50%;
    width: 0;
}

    .cropper-center[b-1a3twxwd0k]::before,
    .cropper-center[b-1a3twxwd0k]::after {
        background-color: #eee;
        content: ' ';
        display: block;
        position: absolute;
    }

    .cropper-center[b-1a3twxwd0k]::before {
        height: 1px;
        left: -3px;
        top: 0;
        width: 7px;
    }

    .cropper-center[b-1a3twxwd0k]::after {
        height: 7px;
        left: 0;
        top: -3px;
        width: 1px;
    }

.cropper-face[b-1a3twxwd0k],
.cropper-line[b-1a3twxwd0k],
.cropper-point[b-1a3twxwd0k] {
    display: block;
    height: 100%;
    opacity: 0.1;
    position: absolute;
    width: 100%;
}

.cropper-face[b-1a3twxwd0k] {
    background-color: #fff;
    left: 0;
    top: 0;
}

.cropper-line[b-1a3twxwd0k] {
    background-color: #39f;
}

    .cropper-line.line-e[b-1a3twxwd0k] {
        cursor: ew-resize;
        right: -3px;
        top: 0;
        width: 5px;
    }

    .cropper-line.line-n[b-1a3twxwd0k] {
        cursor: ns-resize;
        height: 5px;
        left: 0;
        top: -3px;
    }

    .cropper-line.line-w[b-1a3twxwd0k] {
        cursor: ew-resize;
        left: -3px;
        top: 0;
        width: 5px;
    }

    .cropper-line.line-s[b-1a3twxwd0k] {
        bottom: -3px;
        cursor: ns-resize;
        height: 5px;
        left: 0;
    }

.cropper-point[b-1a3twxwd0k] {
    background-color: #39f;
    height: 5px;
    opacity: 0.75;
    width: 5px;
}

    .cropper-point.point-e[b-1a3twxwd0k] {
        cursor: ew-resize;
        margin-top: -3px;
        right: -3px;
        top: 50%;
    }

    .cropper-point.point-n[b-1a3twxwd0k] {
        cursor: ns-resize;
        left: 50%;
        margin-left: -3px;
        top: -3px;
    }

    .cropper-point.point-w[b-1a3twxwd0k] {
        cursor: ew-resize;
        left: -3px;
        margin-top: -3px;
        top: 50%;
    }

    .cropper-point.point-s[b-1a3twxwd0k] {
        bottom: -3px;
        cursor: s-resize;
        left: 50%;
        margin-left: -3px;
    }

    .cropper-point.point-ne[b-1a3twxwd0k] {
        cursor: nesw-resize;
        right: -3px;
        top: -3px;
    }

    .cropper-point.point-nw[b-1a3twxwd0k] {
        cursor: nwse-resize;
        left: -3px;
        top: -3px;
    }

    .cropper-point.point-sw[b-1a3twxwd0k] {
        bottom: -3px;
        cursor: nesw-resize;
        left: -3px;
    }

    .cropper-point.point-se[b-1a3twxwd0k] {
        bottom: -3px;
        cursor: nwse-resize;
        height: 20px;
        opacity: 1;
        right: -3px;
        width: 20px;
    }

@media (min-width: 768px) {

    .cropper-point.point-se[b-1a3twxwd0k] {
        height: 15px;
        width: 15px;
    }
}

@media (min-width: 992px) {

    .cropper-point.point-se[b-1a3twxwd0k] {
        height: 10px;
        width: 10px;
    }
}

@media (min-width: 1200px) {

    .cropper-point.point-se[b-1a3twxwd0k] {
        height: 5px;
        opacity: 0.75;
        width: 5px;
    }
}

.cropper-point.point-se[b-1a3twxwd0k]::before {
    background-color: #39f;
    bottom: -50%;
    content: ' ';
    display: block;
    height: 200%;
    opacity: 0;
    position: absolute;
    right: -50%;
    width: 200%;
}

.cropper-invisible[b-1a3twxwd0k] {
    opacity: 0;
}

.cropper-bg[b-1a3twxwd0k] {
    background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
}

.cropper-hide[b-1a3twxwd0k] {
    display: block;
    height: 0;
    position: absolute;
    width: 0;
}

.cropper-hidden[b-1a3twxwd0k] {
    display: none !important;
}

.cropper-move[b-1a3twxwd0k] {
    cursor: move;
}

.cropper-crop[b-1a3twxwd0k] {
    cursor: crosshair;
}

.cropper-disabled .cropper-drag-box[b-1a3twxwd0k],
.cropper-disabled .cropper-face[b-1a3twxwd0k],
.cropper-disabled .cropper-line[b-1a3twxwd0k],
.cropper-disabled .cropper-point[b-1a3twxwd0k] {
    cursor: not-allowed;
}
/* /Components/UI/DataGrid/DataGrid.razor.rz.scp.css */
/*
   .small_scroll IS GONE, and it is worth saying why rather than just deleting
   it (2026-08-25).

   It carried `max-height: 50%; overflow: auto` and was worn by the filter
   dropdown - which DataGridFilter renders, not this component. Isolation stamps
   the scope attribute on the last element of a selector, so it compiled to
   `.small_scroll[b-...]` and matched nothing at all. The filter panel therefore
   ran off the bottom of the window with no scrollbar, which is exactly how it
   was reported.

   The percentage would not have worked either: it resolves against the
   containing block, and a Popper-positioned dropdown's is auto-height, so 50%
   computes to no limit. Both halves now live inline on the element, in
   DataGridFilter.MenuStyle, alongside the width - see the remarks there.
*/

.paginator[b-s8v0ydlg1m] {
    width: 100%;
}

/*
   The row area: the middle of the grid, between the toolbar and the divider.

   It replaced Bootstrap's `flex-grow-1 h-100`, which was two rules fighting.
   h-100 asks for the full height of the parent, and flex-grow-1 then grows from
   that - so the toolbar, divider and paginator were all ADDITIONAL to a child
   already claiming everything, and the paginator was pushed below the bottom of
   the window. Measured: 2px past .window_content at one size, more at others,
   which is why it reads as "the buttons land below the window".

   min-height: 0 is the half that is easy to leave out. A flex item will not
   shrink below its content by default, so without it the row area could never
   give height back to its siblings however little room there was.
*/
.datagrid_body[b-s8v0ydlg1m] {
    /*
       NO GROW. The height is set inline from the measurement, and flex-grow
       overrides it - the element takes its basis from the inline height and then
       grows past it to fill the row, which un-bounds the very thing the inline
       height exists to bound. Measured: a body told to be 331px sitting at 623px
       with a third of it empty, because the page size was still the one computed
       for 331.

       Shrink is left on so a container smaller than the measurement still fits
       rather than overflowing while the next measurement is in flight.
    */
    flex: 0 1 auto;
    min-height: 0;
    /*
       The horizontal twin of min-height above, and it became load-bearing when
       columns became resizable.

       A flex item defaults to `min-width: auto`, which is its CONTENT's minimum -
       and a `table-layout: fixed` table with stated column widths has a large
       one. So dragging the columns wider than the window made this element grow
       to match rather than scroll, and the grid pushed out past the window edge
       taking the toolbar and paginator's alignment with it. Measured: 2054px of
       columns in a 1254px window, with the element itself reporting 2054.
    */
    min-width: 0;
    /*
       CLIPS, and this is the guarantee rather than the tidiness. Everything
       else here is arithmetic - how many rows fit - and arithmetic can be a row
       out while a measurement settles. Without a clip, being one row out does
       not show as a slightly full grid, it shoves the divider and paginator
       down and out of the window. Measured: the row content ran 96px past the
       height this element had been given, and all 96 came out of the bottom.
    */
    overflow-y: hidden;
    /*
       ACROSS it does not clip, because a column can now be dragged wider than
       the grid and clipping one would put it out of reach with no way back. The
       two axes genuinely want different answers: vertically the row count is
       arithmetic that must never be allowed to shove the paginator out of the
       window, horizontally it is the user's own choice and they have to be able
       to follow it.

       In practice the scrolling is done by Bootstrap's `.table-responsive`
       INSIDE this element, which was already in the markup and is already
       `overflow-x: auto` - measured at 2053px of columns scrolling inside
       1239px. So this never engages today. It is here as the honest statement
       of intent for the axis, and as what would catch the overflow if that
       wrapper were ever removed.

       A horizontal scrollbar does eat into the measured height, so the last row
       can be clipped by the rule above. That is the right way round: a row cut
       off at the bottom of a scroll area is visibly a row, where a paginator
       pushed off the window is simply gone.
    */
    overflow-x: auto;
}

/*
   The hairline between the table and the paginator, drawn as a gradient that
   fades out at both ends. Moved here from app.css, where this component was
   its only consumer.

   The element carried a second class, `div-transparent`, which was defined
   nowhere - not here, not in app.css, not in Bootstrap - so it has been
   dropped from the markup rather than left looking load-bearing.
*/
.divider[b-s8v0ydlg1m] {
    position: relative;
    margin: 16px;
    height: 1px;
}

    .divider[b-s8v0ydlg1m]:before {
        content: "";
        position: absolute;
        top: 0;
        left: 0%;
        right: 0%;
        width: 100%;
        height: 1px;
        background-image: linear-gradient(to right, rgba(255,255,255,0.0) 0%, rgba(255,255,255, 0.50) 20%, rgba(255,255,255, 0.70) 50%, rgba(255,255,255, 0.50) 80%, rgba(255,255,255, 0.0) 100%);
    }

/*
   ::deep IS LOAD-BEARING IN EVERY RULE BELOW THAT HAS IT, and its absence is
   why this grid had no striping, no hover, no header styling and no selection
   highlight (fixed 2026-08-25, when the first window to actually render one was
   written).

   CSS isolation stamps the scope attribute on the LAST element of a selector,
   and that element has to be one THIS component rendered. DataGrid renders the
   table, thead, tbody and the header tr - and nothing else. Every other tr, th
   and td comes from DataGridRow, DataGridColumn, DataGridCell or
   DataGridFilter, none of which has a stylesheet and therefore none of which
   has a scope attribute at all. So `.datagrid tbody tr` compiled to
   `.datagrid tbody tr[b-xxxxx]` and matched nothing, silently.

   ::deep moves the attribute onto the compound before it, so
   `.datagrid tbody ::deep tr` becomes `.datagrid tbody[b-xxxxx] tr` - anchored
   on the tbody we do render, reaching the rows we do not. The rule of thumb:
   put ::deep immediately after the last element in the selector that this
   component's own markup creates.
*/
.datagrid[b-s8v0ydlg1m] {
    /*
       One row's height, declared once. The rows use it, and the JS measures a
       real row rather than reading this - so the two cannot disagree, and a
       consumer that overrides it is measured correctly without telling anyone.
    */
    --datagrid-row-height: 32px;
    width: 100%;
    table-layout: fixed;
}

    .datagrid thead[b-s8v0ydlg1m] {
        border-bottom: 1px solid black;
    }

        /* th comes from DataGridFilter and DataGridColumn. */
        .datagrid thead[b-s8v0ydlg1m]  th {
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        /*
           Every body row is a DataGridRow.

           A FIXED height, not max-height, and that is what makes the page size
           computable at all. max-height does not constrain a table row, so rows
           stretched to fill whatever space the table had - which meant the row
           height depended on the ROW COUNT, and the row count is derived from
           the row height. Measured going round that loop: 18.5px with one page
           of rows, 20.5px at another size, then 26px once seventeen were asked
           for in a box sized for a shorter row. It never settled.

           With a fixed height the measurement is the same answer every time and
           the arithmetic converges on the first pass.
        */
        .datagrid tbody[b-s8v0ydlg1m]  tr {
            height: var(--datagrid-row-height);
        }

            .datagrid tbody[b-s8v0ydlg1m]  tr:hover {
                background-image: linear-gradient(to right, rgba(248,249,251,0.30), rgba(248,249,251, 0.10), rgba(248,249,251, 0.30));
            }

            .datagrid tbody[b-s8v0ydlg1m]  tr:nth-child(even) {
                background-color: rgba(0,0,0,0.10);
            }

            .datagrid tbody[b-s8v0ydlg1m]  tr td {
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
            }

                .datagrid tbody[b-s8v0ydlg1m]  tr td input {
                    width: 100%;
                    height: 100%;
                    overflow: hidden;
                    text-overflow: ellipsis;
                    white-space: nowrap;
                    margin: 0px;
                    padding: 0px;
                }

/*
   The header row IS ours - DataGrid renders <tr class="datagrid_header"> - so
   this one needs no ::deep. Left empty deliberately: thead already carries the
   rule underneath it, and a second border here drew two.
*/
.datagrid_header[b-s8v0ydlg1m] {
}

/*
   Applied by DataGridRow and DataGridCell to their own markup, so there is no
   element of ours at the end of it - the anchor has to be the table.
*/
.datagrid[b-s8v0ydlg1m]  .datagrid_selected {
    background-image: linear-gradient(to bottom, rgba(248,249,251,0.15), rgba(248,249,251, 0.10), rgba(248,249,251, 0.15));
}

/*
   COLUMN RESIZING.

   ::deep on every rule here, and for the usual reason: the th, and the handle
   inside it, are rendered by DataGridColumn - which has no stylesheet and
   therefore no scope attribute. The anchor is the thead, which this component
   does render.
*/
.datagrid thead[b-s8v0ydlg1m]  th.datagrid_column {
    /*
       The containing block for the handle below.

       Safe on THIS cell and deliberately not applied to the filter's, which
       holds an absolutely-positioned dropdown: positioning that cell would make
       it the dropdown's containing block, and the grid's row area clips - so the
       filter panel would be cut off at the edge of the grid instead of floating
       over the window. That is the same trap the dock menu documents from the
       other end.
    */
    position: relative;
}

/*
   The grab area, on the column's right edge.

   THE SAME SHAPE AS A WINDOW SPLITTER, deliberately: a finger-sized transparent
   handle with a hairline drawn by ::after, thin enough to read as a boundary
   rather than a gutter. Same idiom, same colours, so a column edge and a dock
   edge look like the same affordance - which they are.

   Inside the cell rather than straddling the join, which is where the splitters
   sit. An absolutely-positioned child contributes to its scroll container's
   overflow, so a handle hanging 6px past the LAST column would add 6px of
   horizontal scroll to a table that otherwise fits exactly. Ten pixels of the
   column's own trailing edge is a large enough target without that.
*/
.datagrid thead[b-s8v0ydlg1m]  th.datagrid_column .datagrid_column_resize {
    position: absolute;
    top: 0;
    right: 0;
    z-index: 1;
    display: flex;
    width: 10px;
    height: 100%;
    cursor: col-resize;
    /*
       The pointer has to be able to grab this, and the cell above carries a
       Bootstrap tooltip attribute - so the handle sits above the cell's own
       content rather than beside it.
    */
    touch-action: none;
    user-select: none;
}

    /*
       The line, ALWAYS VISIBLE. It was `border-right: 1px solid transparent`
       lit only on hover, which is no affordance at all: nothing on screen said
       the column could be resized, so there was nothing to aim at and no reason
       to try. The splitters have never done that - .window_splitter::after is
       painted at rest and merely brightens - and this now matches them value
       for value.
    */
    .datagrid thead[b-s8v0ydlg1m]  th.datagrid_column .datagrid_column_resize::after {
        content: "";
        width: 1px;
        height: 100%;
        margin-left: auto;
        background: var(--window-splitter-color, rgba(255,255,255,0.14));
        transition: background 120ms ease;
    }

    /*
       Lit while hovered, and while THIS handle is being dragged.

       The second half is not decoration and the splitters record why: a drag
       holds pointer capture, so the moment the pointer outruns the handle
       nothing is hovered any more and the line being dragged would go dark
       mid-drag. DataGrid.razor.js puts the active class on the handle for
       exactly the span of the drag.
    */
    .datagrid thead[b-s8v0ydlg1m]  th.datagrid_column .datagrid_column_resize:hover::after,
    .datagrid thead[b-s8v0ydlg1m]  th.datagrid_column .datagrid_column_resize_active::after {
        background: var(--window-splitter-color-active, rgba(255,255,255,0.4));
    }

/*
   While a drag is in flight.

   The cursor is put on the whole grid because the pointer is captured by the
   handle and will very often be outside it - without this it reverts to the
   default arrow the moment it leaves the 6px strip, which reads as the drag
   having ended. Selection is killed for the same reason: a drag across a table
   otherwise highlights every cell it crosses.
*/
.datagrid_resizing[b-s8v0ydlg1m] {
    cursor: col-resize;
    user-select: none;
}

    .datagrid_resizing[b-s8v0ydlg1m]  * {
        cursor: col-resize;
    }
/* /Components/UI/Section/Section.razor.rz.scp.css */
/*
   The one element the content always sits in, whatever state the section is
   in. Two jobs, and the second is why it is a real box.

   1. Section.razor.js roots its "which field gets the caret" query here.
      Section renders the form but not the fields inside it, so this wrapper
      is the only handle it has on them.
   2. It carries the blocked state below.

   It was display: contents, chosen so a wrapper added for job 1 could not
   move anything. Job 2 rules that out: display: contents generates no box,
   and opacity has nothing to apply to, so the dimming simply would not
   paint. Measured against the real Login window before changing it - the
   form's geometry is identical either way, x/y/width/height to the pixel -
   so the box costs nothing here. Worth re-measuring if a section is ever
   dropped straight into a flex or grid parent, where a block child is not
   the same thing as no child.
*/
.section_form[b-yekh2crnk9] {
    display: block;

    /*
       Two durations, because the two states they serve last for very different
       lengths of time.

       OPACITY IS FAST (160ms) because it is the save, and a save against a
       local API is over in a few hundred milliseconds. Anything slower than
       this spends the whole round trip mid-transition and the section never
       settles anywhere - which is exactly what went wrong with the blur this
       replaced: 220ms out and 220ms back meant the form was blurring or
       unblurring for essentially the entire operation, and reading a thing
       that never holds still is noise rather than feedback.

       FILTER IS SLOWER (220ms) because it serves the locked state, which lasts
       as long as a modal is open. That is .window_lock's own figure, and this
       is the same gesture at a smaller scale.

       Idle is `filter: none` rather than `blur(0px)`, which is the reason
       .window_lock gives and it holds here too: blur(0) is not free, it
       promotes the element to its own composited layer permanently for no
       visual gain. Interpolating from `none` is well defined - the browser
       substitutes the identity value - so the transition still animates.
    */
    transition: opacity 160ms ease, filter 220ms ease;
}

/*
   Stacks the form and the saving indicator in one grid cell, so the indicator
   is drawn OVER the content rather than in place of it.

   Grid rather than position:absolute, and that is the load-bearing part. An
   absolutely-positioned overlay needs a positioned wrapper, and a positioned
   wrapper becomes the containing block for every absolutely-positioned thing
   a window renders inside its form - a dropdown, a popover, a picker - which
   silently clips or re-anchors them. Two children assigned the same grid area
   overlap and nothing's containing block changes.

   Both subjects below are elements THIS component renders, which is why
   neither needs ::deep. SectionBusy's own root is left alone: it is given a
   wrapper here rather than being positioned from a distance, so it stays
   usable on its own in the Loading branch.
*/
.section_busy_host[b-yekh2crnk9] {
    display: grid;
}

/*
   Stretches the section to the height the window gives it.

   min-height rather than height, so content taller than the window still
   grows the box and .window_content scrolls it, instead of being clipped at
   exactly one screenful.

   IT IS SELF-LIMITING TO MAXIMIZED WINDOWS, which is the neat part and is
   worth not "fixing" later. .window_content is a flex item that grows; in a
   maximized window the chain above it has a definite height so this resolves
   and the section fills. In a NORMAL window the whole frame is sized BY its
   content, so that height is indefinite, a percentage min-height resolves
   against nothing, and the section shrink-wraps exactly as before. One rule,
   both behaviours, no breakpoint and no class toggling on maximize.

   What it fixes is the indicator jumping. The overlay is sized to this box, so
   while the box shrink-wraps the form, anything that changes the form's height
   during a save - a validation message arriving is the obvious one - drags the
   hourglass up or down with it. A constant box means a constant centre.
*/
.section_fill[b-yekh2crnk9] {
    min-height: 100%;
}

    .section_busy_host > .section_form[b-yekh2crnk9],
    .section_busy_host > .section_busy_overlay[b-yekh2crnk9] {
        grid-area: 1 / 1;
        /*
           STAY INSIDE THE TRACK.

           A grid item defaults to `min-width: auto`, which is its CONTENT's
           min-content width - so an item whose content is genuinely wide grows
           past its own grid area instead of letting that content overflow.
           Nothing in a section is normally wide enough to notice, which is why
           this went unnoticed until a DataGrid's columns became draggable:
           widening them pushed this element to 2053px inside a 1254px track,
           and the grid's own `overflow-x: auto` never engaged because it was
           being stretched to the full width rather than constrained.

           min-width: 0 hands the decision back to whatever is inside. It has no
           effect at all until content would overflow, so a form that fits is
           laid out exactly as before.
        */
        min-width: 0;
    }

/*
   The overlay's own box. A soft scrim so the indicator stays legible over
   whatever text it happens to land on, and pointer-events off so it is never
   the thing a click hits - the form beneath is already inert, and one
   statement about interaction is better than two that can disagree.

   --section-busy-min-height is SectionBusy's floor, overridden to nothing
   here. Left at its default the indicator would force the grid row to 180px,
   so a short form would grow while it saved and shrink back afterwards. Custom
   properties inherit across component boundaries, so setting it on this
   element reaches SectionBusy without a selector that has to reach into it.
*/
.section_busy_overlay[b-yekh2crnk9] {
    --section-busy-min-height: 0;

    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;

    /*
       NO BACKGROUND. This element positions the hourglass and nothing else.

       It had a scrim - rgba(0,0,0,0.45), copied from .window_locked - and it
       was wrong here for a reason worth keeping: a scrim is a sheet of dark
       laid OVER something, and over a whole window that is invisible as a
       shape, because it covers everything the eye can see. Over a section it
       is a dark RECTANGLE sitting on part of a window, with four visible
       edges, and it reads as a box that has appeared rather than as content
       going away.

       What replaced it is .section_saving fading the content itself right out,
       which needs nothing laid over anything.

       THE DELAY IS THE IMPORTANT PART OF THIS RULE. The content takes 160ms to
       fade out, so the hourglass waits until that is nearly done before it
       starts - it is not a cross-fade, it is a handover. `both` holds it at
       opacity 0 through the delay rather than letting it show early.

       IT COMES FROM C#, as --section-busy-appear-delay, and the fallback here
       is only for an overlay rendered without it. Submit adds the same figure
       to MinimumIndicatorMilliseconds so that parameter can mean the
       indicator's own time on screen rather than the saving state's, which
       means the number is needed in both places - and a duration living
       independently in a stylesheet and a code file is one that disagrees with
       itself the first time either is tuned.

       With the minimum hold at its default the delay is invisible, because
       every save now lasts longer than it. It still matters at
       MinimumIndicatorMilliseconds = 0, where it goes back to its original
       job: a save quicker than the delay shows no hourglass at all, only the
       content dipping and recovering, which is the right amount of feedback at
       that speed and far better than a spinner appearing and vanishing inside
       a fifth of a second.

       An animation rather than a transition because this element is added to
       the DOM rather than being always present, so it has no previous state to
       transition FROM - the trick .window_shadow::after uses to avoid popping
       is not available here.

       There is deliberately no exit. The element is removed the instant the
       state changes, and the content fading back over 160ms covers that end.
    */
    animation: section_busy_overlay_in-b-yekh2crnk9 160ms ease var(--section-busy-appear-delay, 140ms) both;
}

@keyframes section_busy_overlay_in-b-yekh2crnk9 {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

/*
   The button row: Submit, plus whatever the window put in ActionContent.

   flex-wrap is the load-bearing part rather than a nicety. This app is
   mobile-first and a window is about 360px wide on a phone, while Login's
   row alone is "Login" + "Forgot Username" + "Forgot Password" - measured
   at roughly 340px of buttons before gaps. Without wrapping, the last one
   is simply off the side of the window: .window_content does not scroll
   sideways for it, and nothing reports a problem.

   gap rather than margins on the buttons, because the buttons belong to the
   window rather than to this component - a margin rule here would need
   ::deep to reach them, and gap needs to reach nothing. Same reason the
   margin-top lives here instead of the `mt-2` the Submit button used to
   carry: one owner for the row's spacing.
*/
.section_actions[b-yekh2crnk9] {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
}

/*
   Content that is on screen but must not be touched - saving, or locked
   behind a modal. One class for both, because it is one statement: "this is
   still here, and it is not yours right now".

   APPEARANCE ONLY. Blocking interaction is the `inert` attribute the markup
   puts on the same element, not CSS, and the difference matters: this used to
   be a set of `pointer-events: none` rules, which stop a mouse and nothing
   else. The caret is very often already in a field when a save starts, and
   from there the keyboard reaches everything - including submitting the form
   again with Enter. `inert` takes the subtree out of hit-testing, out of the
   tab order and out of the accessibility tree.

   Those rules were also broken in their own right: their subjects were
   controls the WINDOW rendered, so isolation stamped the scope attribute
   where it matches nothing and pointer-events resolved to `auto`. The content
   dimmed to 45% and stayed fully editable, which is worse than not dimming
   it. Both faults were latent - nothing had ever put a form section into
   Locked, and Saving rendered an empty fragment rather than this.

   BLUR AT THE SOURCE, NOT A backdrop-filter ON THE INDICATOR, and the numbers
   are .window_locked's rather than new ones - this is the modal lock's
   gesture applied to one section, so it should not look like a second idea.

   The temptation is to put backdrop-filter on .section_busy_overlay, and
   WindowFrame.razor.css records why that was tried on the overlay windows and
   removed: an element with opacity < 1 is its own backdrop root, so during
   any fade the filter samples nothing at all and then snaps into focus the
   instant opacity reaches exactly 1. It also costs a permanent composited
   layer on the element being read. Blurring the source has neither problem
   and is what actually reads as "something is on top of this".

   SAVING AND LOCKED ARE TWO DIFFERENT PICTURES, and they were one class for a
   while because both happened to be a dim. They are not the same statement:

     locked  "still here, just not yours right now" - so the content stays
             visible and goes soft. Lasts as long as a modal is open.
     saving  "gone for a moment" - so it fades out entirely and the hourglass
             takes its place. Lasts one round trip.

   Collapsing them again makes one of the two wrong: a shared opacity: 0 leaves
   a locked form invisible with nothing explaining why, and a shared dim leaves
   a saving form half-showing behind the indicator.

   NOTHING IS LAID ON TOP in either case. A scrim was tried and is wrong at
   this scale - a sheet of dark over a whole window has no perceptible shape
   because it covers everything the eye can see, while the same sheet over one
   section is a rectangle with four visible edges and reads as a box appearing
   rather than as content going away.
*/

/*
   Saving. Straight to nothing, no blur.

   The blur that used to be here is gone because of how LONG a save is: the
   round trip is a few hundred milliseconds, so the form spent essentially all
   of it either blurring or unblurring and never held a settled state. A thing
   that is always mid-change reads as noise. Fading right out is a single
   legible event - content goes, hourglass arrives, content returns - and it
   has the side benefit that there is nothing left behind the indicator to
   compete with it.

   opacity: 0 and not `display: none` or `visibility: hidden`: the box has to
   keep its size or the section would collapse mid-save and the hourglass would
   move. `inert` in the markup is what actually stops it being touched -
   invisible content is still perfectly focusable and clickable.
*/
.section_saving[b-yekh2crnk9] {
    opacity: 0;
}

/*
   Locked, and ONLY when the window is not already locked - see IsSelfLocked.
   These are .window_locked's own numbers, because this is that gesture applied
   to one section and two sets of figures for one idea is how they drift.
*/
.section_locked[b-yekh2crnk9] {
    filter: blur(6px);
    opacity: 0.8;
}
/* /Components/UI/Section/SectionBusy.razor.rz.scp.css */
/*
   The busy indicator: a still hourglass, centred, with a shadow.

   NOTHING HERE ANIMATES, and nothing here is coloured. What says "something is
   happening" is the handover - see .section_saving in Section.razor.css, which
   fades the content out so this can arrive in the space it left, then fades it
   back. That is already one legible movement; an indicator that moves as well
   competes with it, and a coloured one pulls the eye towards the part of the
   window that has just been made unavailable.
*/
.section_busy[b-w9n36tzoi8] {
    display: flex;
    align-items: center;
    justify-content: center;

    /*
       Centred in the space available, which is what the floor buys: the
       section sits in .window_content, a flex column only as tall as its
       content, so height:100% would resolve against an auto height and
       collapse to the size of the hourglass itself. A floor plus centring
       gives the same result in a short window and a tall one.

       --section-busy-min-height is part of this component's contract, read as
       a var() FALLBACK rather than declared here. A declaration on this
       element would beat the inherited value and the override would silently
       do nothing. Section sets it to 0 when the indicator is laid over a form,
       where the floor would otherwise stretch the form's own row and a short
       form would visibly grow while saving and shrink back afterwards.
    */
    min-height: var(--section-busy-min-height, 180px);
    width: 100%;
    padding: 16px;
    box-sizing: border-box;

    /*
       Decorative, and laid over live content while a section is saving. It
       must never be the thing a click lands on - the content underneath is
       already inert, and making this transparent to the pointer keeps that
       one statement about interaction rather than two.
    */
    pointer-events: none;
    user-select: none;

    /*
       The only colour in the component, and the SVG's shapes all draw with
       currentColor at varying opacity - so the whole hourglass is retinted by
       changing this one line. Plain white, deliberately: the backdrop is a
       dark navy with blue and violet washes, and anything tinted reads as
       belonging to one of them.
    */
    color: rgba(255, 255, 255, 0.92);
}

.section_busy_hourglass[b-w9n36tzoi8] {
    display: block;
    width: 48px;
    height: 64px;

    /*
       drop-shadow, not box-shadow. A filter runs on what is actually painted,
       so this traces the hourglass; box-shadow is cast by the element's border
       box and would put a soft grey rectangle behind it.

       Two layers: a tight dark one that separates the glyph from whatever it
       happens to be sitting on, and a wide soft one that lifts it off the
       blur. Both are black rather than tinted, for the reason `color` above is
       white.
    */
    filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.9)) drop-shadow(0 8px 18px rgba(0, 0, 0, 0.65));
}
/* /Components/UI/UserServiceGate.razor.rz.scp.css */
/*
   The blocking layer.

   ABOVE EVERYTHING. The drawer menus sit at 1600 and the drawer scrim at 1500,
   which are the highest things this application draws - so this has to clear
   them or an open launcher menu would float over a modal that is supposed to be
   blocking the whole app. Blazor's own reconnect dialog uses the top layer and
   is above this by construction, which is the right way round: if the circuit
   itself has dropped, that is the more fundamental problem and its message
   should win.

   backdrop-filter is safe here where it was not on the overlay windows: this
   element never animates its opacity, and an element with opacity < 1 is its
   own backdrop root, which is what made the filter sample nothing there.
*/
.user_service_gate[b-okn6hndbxo] {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    background-color: rgba(5, 8, 22, 0.55);
    backdrop-filter: blur(6px);
}

/*
   The panel. Deliberately close to Blazor's reconnect dialog in weight and
   shape - same situation, same promise - while wearing this application's own
   amber accent rather than the framework default.
*/
.user_service_gate_panel[b-okn6hndbxo] {
    max-width: 420px;
    padding: 24px 28px;
    border: 1px solid rgba(255, 204, 102, 0.55);
    border-radius: 10px;
    background-color: rgba(32, 32, 40, 0.96);
    box-shadow: 0 40px 120px rgba(0,0,0,0.55), 0 0 8px 4px rgba(0,0,0,0.5);
    color: white;
    text-align: center;
}

    .user_service_gate_panel p[b-okn6hndbxo] {
        margin: 8px 0px 0px 0px;
    }

.user_service_gate_heading[b-okn6hndbxo] {
    margin: 12px 0px 0px 0px;
    font-size: 1.15rem;
    font-weight: 600;
}

/*
   The one moving thing. bx-spin comes from Boxicons' animations.min.css, which
   App.razor loads beside the glyph font - the same stylesheet that carries the
   size scale. Checking only fonts/basic/boxicons.min.css says these do not
   exist, because that file is glyph names and nothing else.
*/
.user_service_gate_spinner i[b-okn6hndbxo] {
    font-size: 2.25rem;
    color: rgba(255, 204, 102, 0.9);
    animation: user_service_gate_spin-b-okn6hndbxo 1.4s linear infinite;
}

@keyframes user_service_gate_spin-b-okn6hndbxo {
    from {
        transform: rotate(0deg);
    }

    to {
        transform: rotate(360deg);
    }
}

/*
   The consequence, said plainly and quietly. It is the part that tells somebody
   whether to wait or to go and start the service, so it is here rather than in
   a log nobody reads - but it is secondary to "this is being retried".
*/
.user_service_gate_detail[b-okn6hndbxo] {
    color: rgba(255, 255, 255, 0.65);
    font-size: 0.85rem;
}
/* /Components/UI/Window/WindowFrame.razor.rz.scp.css */
.window_shadow[b-k73fy81xfz] {
    position: relative;
    display: flex;
    flex-direction: column;
    min-height: 0;
    width: 100%;
    border-radius: 8px;
    align-self: flex-start;
    padding: 5px;
    overflow: visible;
    /* Fills .window_lock - see the note on .window_lock's flex rule. */
    flex: 1 1 auto;
    min-height: 0;
}


.window[b-k73fy81xfz] {
    /* Single source of truth for the titlebar's height - used by
       .window_titlebar to size itself and by .window_dock_menu to sit just
       below it from outside the clipping container. */
    --window-titlebar-height: 36px;
    /*
       The titlebar's own metrics, declared once here and consumed in three
       places: by .window_button itself, by .window_title_icon, and by the
       reserve arithmetic that keeps the title centred on the window.

       They are variables rather than literals BECAUSE of that third
       consumer. The title centres by reserving the same width on both sides
       of the row, and the reserve has to be at least as wide as the button
       strip really is - so a button that grew a pixel while the reserve did
       not would push the title quietly off centre, with nothing on screen to
       say why. Written once, the two cannot disagree.
    */
    --window-titlebar-button-glyph: 20px;
    --window-titlebar-button-padding: 2px;
    --window-titlebar-button-border: 1px;
    --window-titlebar-button-gap: 12px;
    /* The inset between the last button and the window's rounded corner. */
    --window-titlebar-button-edge: 4px;
    /* A Boxicon is a 1em square, so a button is its glyph plus its own box. */
    --window-titlebar-button-size: calc(var(--window-titlebar-button-glyph) + 2 * (var(--window-titlebar-button-padding) + var(--window-titlebar-button-border)));
    --window-titlebar-icon-margin: 10px;
    --window-titlebar-icon-size: 20px;
    /* The breathing room the title keeps between itself and whichever of
       the two side groups it is closest to. */
    --window-titlebar-title-gap: 10px;
    position: relative;
    display: flex;
    flex-direction: column;
    width: 100%;
    min-width: 0;
    min-height: 0;
    max-height: 100%;
    border-radius: 8px;
    border: 1px solid rgba(0,0,0,.25);
    background-color: rgba(64,64,64,0.5);
    /* Fills .window_shadow - see the note on .window_lock's flex rule. */
    flex: 1 1 auto;
    min-height: 0;
    /*
       The drop shadow lives on .window itself rather than on a separate
       element painted behind it.

       An outer box-shadow is never drawn inside the element's own border
       box, so it physically cannot show through the window. That matters
       here because .window is only 50% opaque - the previous
       .window_shadow::before sat behind the glass at inset: 10px, so its
       outer shadow radiated from a box 10px inside the window's edges and
       read straight through the translucent background as a dark ring just
       inside the border.

       The white hairline is an INSET shadow, which paints above the
       background and below content - which is where it was always meant to
       read, and it now follows the window's actual top edge rather than a
       line 10px in.
    */
    box-shadow: 0 40px 120px rgba(0,0,0,0.55), 0 0 8px 4px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.12);
}


    /*
   A DOCKED window gets a much tighter shadow than a floating one, and this
   is the actual fix for "the splitter is right up against the window".

   The default `0 40px 120px` is a floating-window shadow: its core sits 40px
   below the frame and it reaches 160px. That reads beautifully for a window
   hovering over a backdrop, and badly for a panel that is flush against
   other panels - every dock boundary is only ~11px from the frame to the
   splitter line, so the shadow was still at nearly full strength when the
   line crossed it, and it washed across whatever panel sat beyond. It was
   the reason the top dock looked cramped, and the reason a left dock sitting
   above a bottom dock looked cramped in exactly the same way.

   Reserving enough room for the big shadow was tried first and rejected: it
   cost ~20% of a 200px panel's height to buy nothing but appearance. Fitting
   the shadow to the space instead costs nothing at all.

   `0 6px 16px` puts the core at about the 5px the frame is already inset by,
   so it resolves inside its own gap. The ambient ring and the inset
   highlight are unchanged, so a docked panel still reads as a distinct
   surface with a lit top edge - it just stops throwing a floating window's
   shadow across its neighbours.
*/
    .window.window_docked[b-k73fy81xfz] {
        box-shadow: 0 6px 16px rgba(0,0,0,0.5), 0 0 8px 4px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.12);
    }

    .window:not(.window_maximized)[b-k73fy81xfz] {
        max-height: 100%;
    }

        .window:not(.window_maximized) .window_content_padding[b-k73fy81xfz] {
            min-height: 0;
            overflow: hidden;
        }

        .window:not(.window_maximized) .window_content[b-k73fy81xfz] {
            overflow-y: auto;
        }


.window_lock[b-k73fy81xfz] {
    position: relative;
    display: flex;
    flex-direction: column;
    min-height: 0;
    width: 100%;
    align-self: flex-start;
    overflow: visible;
    /*
       Unlocked is `filter: none`, not `blur(0px)`. blur(0px) is not free -
       it still promotes this element to its own composited layer for every
       unlocked window, permanently, for no visual gain. Interpolating from
       `none` is well defined: the browser substitutes the identity value
       (blur(0)), so the transition still animates.
    */
    /*
       The whole frame fills .window_component_host UNCONDITIONALLY, in both
       normal and maximized states, using flex rather than height: 100%.
       Two reasons, both found by measuring mid-animation:

       1. While the host's height is mid-interpolation FROM `auto` (which is
          how maximize/restore animates), the host stays "indefinite" for
          percentage resolution - so `height: 100%` on a child falls back to
          content height for the whole animation and only snaps to the right
          size on the final frame. Flex reads the container's used height
          each layout pass instead, so it tracks frame by frame.

       2. Making the fill conditional on .window_maximized broke RESTORE:
          that class is removed instantly, so the frame collapsed to content
          height on frame 0 while the host was still shrinking around it.
          Filling unconditionally keeps both directions symmetrical.

       At rest in normal mode this is a no-op - an auto-height flex
       container has no free space to distribute, so the frame still sizes
       to its content.
    */
    flex: 1 1 auto;
    min-height: 0;
    /*
       Deliberately NO height transition here, and none on .window_shadow or
       .window either. Maximize/restore is animated on exactly ONE element,
       .window_component_host.

       Transitioning height on these nested elements too looks reasonable
       and is badly broken: each interpolates from its resolved `auto`
       toward `100%` of a parent that is ITSELF mid-transition, so the
       percentage collapses while the parent is in flight. Measured
       mid-animation, .window_lock went 85px -> 2px -> 0px -> 0px -> 734px
       while the host grew smoothly - on screen, the window implodes to its
       titlebar and then snaps open.

       Only ever animate height at a single level of a nested layout chain.
    */
    transition: filter 220ms ease, opacity 220ms ease;
}

/*
   ---------------------------------------------------------------------
   Locked windows: blurred and dimmed, as a cue that something is above.
   ---------------------------------------------------------------------

   `filter`, not `backdrop-filter` - this blurs the window's OWN content,
   which is what reads as depth ("something sits on top of me"). Blurring
   the backdrop instead would blur whatever is BEHIND this window, which
   says nothing about what is above it.

   Keyed off lock state rather than stacking position, deliberately. A
   minimized modal still locks its owner (see
   WindowManager.RefreshOwnershipStates), so when the user minimizes a
   Confirm dialog its owner stays locked - and therefore stays blurred,
   with nothing on screen above it to explain why it is unusable. The blur
   IS that explanation: "there is still a popup up here, go bring it back."

   The happy side effect is that nothing flickers. Minimizing the modal on
   top does not change the owner's lock state, so the owner does not change
   appearance at that moment. Blur appears when the modal opens and clears
   when it actually closes - both moments where a visible change is the
   correct signal rather than a distraction.

   This is ALSO how legibility is handled for the window on top. An overlay
   is translucent, so without help the text beneath it shows through and
   makes it hard to read. Blurring and fading the source is what fixes that
   - and it is strictly better than the backdrop-filter that used to sit on
   the active overlay, which cost a permanent composited layer on the one
   window the user is actually reading and scrolling.

   All three numbers below are pure taste: blur radius and opacity here,
   scrim alpha in the rule beneath. Raise the blur first if text still
   reads through an overlay - blur destroys legibility far faster than
   dimming does.
*/
.window_locked[b-k73fy81xfz] {
    filter: blur(6px);
    opacity: 0.8;
}

/*
   The scrim darkens on top of the blur; between them they stop text
   beneath an overlay reading through it.

   It is always present and merely transparent when unlocked, rather than
   being created by the .window_locked rule. A pseudo-element that blinks
   into existence via `content` has no previous state to animate from, so it
   would pop; one that already exists can transition its colour.

   Note it lives INSIDE the .window_locked opacity group, so its effective
   alpha is this value multiplied by that opacity.
*/
.window_shadow[b-k73fy81xfz]::after {
    content: "";
    position: absolute;
    /* Matches .window_shadow's 5px padding, so the scrim covers .window exactly. */
    inset: 5px;
    border-radius: 8px;
    background-color: transparent;
    pointer-events: none;
    z-index: 1;
    transition: background-color 220ms ease;
}

.window_locked > .window_shadow[b-k73fy81xfz]::after {
    background-color: rgba(0,0,0,0.45);
}

/*
   The lock blur, fade and scrim deliberately still cross-fade under
   reduced motion - those are changes in appearance, not movement. The
   maximize/restore resize is suppressed in WindowComponentHost.razor.css,
   which is the only place size is animated.
*/

/*
   OFF, deliberately - kept as a documented decision rather than deleted,
   because the selector is the non-obvious part if it is ever reconsidered.

   This used to give the active overlay a frosted backdrop. Removed for two
   reasons:

   1. It popped. The entry animation fades opacity 0 -> 1, and opacity < 1
      makes an element a backdrop root - so for the whole fade the overlay's
      backdrop-filter could only sample inside itself (i.e. nothing), then
      snapped into focus the instant opacity hit exactly 1.

   2. It became redundant. Locked windows are now blurred and scrimmed at
      the SOURCE (see .window_locked above), which solves the same
      text-bleed problem before the overlay ever composites over it -
      without a permanent composited layer on the one window being read
      and scrolled.

   The gap it leaves: an INDEPENDENT (non-modal) overlay does not lock its
   owner, so nothing beneath it is blurred. No such overlay exists today -
   every overlay in the app is Modal. When chat windows arrive, prefer
   extending the source-blur to "anything that is not the topmost
   interactive window" over switching this back on.
*/
.window_popup_item_active .window_lock:not(.window_locked) .window[b-k73fy81xfz] {
    /* backdrop-filter: blur(10px); */
}

.window_container[b-k73fy81xfz] {
    /*
       This is where the window's own content stops.

       The titlebar never shrinks (flex: 0 0 auto - see its note), which is
       right, but it means a window squeezed shorter than its own titlebar
       has a titlebar sticking out of the bottom of it, with the content
       chain trailing below that. Measured on a left dock dragged down to
       40px: the container sized correctly at 29px while the titlebar ran 7px
       past the frame and the content 16px past.

       Clipping here rather than on .window keeps .window's overflow visible,
       which the dock menu needs - it is positioned against .window precisely
       so it can leave this box.

       An element's own box-shadow is not affected by its own overflow, so
       clipping here would have been safe on .window too; the menu is the
       reason it is one level in.
    */
    overflow: hidden;
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    border-radius: 8px;
    border: 1px solid rgba(255,255,255,0.05);
}

/*
   .window_maximized no longer needs to change how the frame is sized -
   the frame fills its host unconditionally (see the flex rules on
   .window_lock / .window_shadow / .window). Only .window_component_host
   changes size, and everything inside follows it.
*/

.window_maximized.window[b-k73fy81xfz] {
    display: flex;
    flex-direction: column;
}

.window_maximized .window_container[b-k73fy81xfz] {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

/*
   A narrow window - a shrunk dock, mostly - has to give up something, and
   the order it gives things up in is the whole point of these three rules.

   1. The title truncates first, ALL THE WAY TO ZERO, before the buttons
      give up anything. That is now structural rather than a shrink weight:
      .window_title is absolutely positioned, so it is not a flex item at
      all and contributes nothing to the row's sizing. It can only ever be
      squeezed by its own reserve, which is derived from the very things it
      must not overlap.

   2. Then the buttons start to clip. The button strip is allowed to shrink
      (flex: 0 1 auto) and clips its own overflow.

   3. Nothing ever escapes the window. .window itself keeps overflow:
      visible on purpose - it has to, or it would clip its own drop shadow -
      so the clipping has to happen here, on the titlebar row.

   Before this, every one of those was min-width: auto all the way down, so
   a titlebar simply painted at its content width and the close button ended
   up outside the window's rounded box entirely.
*/
.window_titlebar[b-k73fy81xfz] {
    min-width: 0;
    /*
       The containing block for .window_title, which is stretched across this
       row rather than laid out in it.

       Safe to position, unlike .window_drawer: the only absolutely-positioned
       descendant this row has is the title, and this row is already clipping
       it either way. The dock menu is deliberately rendered OUTSIDE
       .window_container precisely so that neither this element nor that one
       can ever become its containing block - see the note beside it in the
       markup.
    */
    position: relative;
    /*
       `clip` with a margin rather than `hidden`, so the buttons' hover glow
       is not cut off at the titlebar's edge.

       The clipping itself has to stay - it is what rule 3 above is about.
       What was wrong is that it clipped at exactly the border box, and
       .window_button's hover shadow (blur 5px, spread 2px) reaches about 7px
       past the button. A button sits 4px inside this row, so the top and
       bottom of its glow were being sliced off.

       overflow-clip-margin moves the clip edge outwards by that much without
       letting anything else out: a button that genuinely overflows is still
       clipped, just 8px later, which is invisible against a glow that fades
       to nothing over the same distance.

       `clip` rather than `hidden` because this element never wants to be a
       scroll container - `hidden` quietly is one, and can be scrolled
       programmatically, which would slide the titlebar's contents out of
       alignment with nothing on screen to say why. Both values are
       non-visible, so the flex automatic-minimum-size note below still
       applies exactly as it did.
    */
    overflow: clip;
    overflow-clip-margin: 8px;
    /*
       Fixed height rather than whatever the button metrics happen to add up
       to. Two things depend on knowing it: the dock menu positions itself
       just below the titlebar from outside the clipping container, and a
       fixed height is what lets flex-shrink stay at 0 below without the
       titlebar's size being a moving target.
    */
    height: var(--window-titlebar-height);
    /*
       flex-shrink: 0 is required BECAUSE of the overflow: hidden above, and
       the connection is easy to miss.

       A flex item's automatic minimum size is only `auto` while its overflow
       is visible; setting overflow to hidden silently changes it to zero. So
       the moment this row started clipping, it also became free to be
       squashed to nothing by the column flex around it - and a window whose
       content wants more room than it has, like Terms, promptly did exactly
       that, crushing a 40px titlebar to about 16px and cutting the buttons
       in half.

       The titlebar is fixed-height chrome. It gives up width (see the title
       and button rules) and never height.
    */
    flex: 0 0 auto;
}

/*
   justify-content: flex-end is load-bearing, not cosmetic. An overflowing
   flex container spills away from its justification, so aligning to the end
   makes the strip overflow to the LEFT - which means the leftmost button
   (Dock) is clipped first and Close, the one you always need, is clipped
   last. Aligning to the start would clip Close first and leave a window
   that cannot be closed.
*/
.window_titlebar_buttons[b-k73fy81xfz] {
    flex: 0 1 auto;
    min-width: 0;
    /*
       What pushes the strip to the right-hand end of the row.

       It used to be pushed there by .window_title's flex-grow-1 eating the
       free space between them. The title is out of flow now, so on a window
       with no icon this strip is the row's only item and would otherwise sit
       at the start. An auto margin absorbs the free space instead, and
       collapses to zero the moment there is none - which is exactly when the
       strip has to start shrinking and clipping.
    */
    margin-left: auto;
    /*
       Same clip-with-a-margin as .window_titlebar, and it has to be done in
       BOTH places: this strip clips before the titlebar does, so leaving
       this one at `hidden` would cut the glow off regardless of what its
       parent allows. See the note there for why 8px.
    */
    overflow: clip;
    overflow-clip-margin: 8px;
    justify-content: flex-end;
    /*
       The space BETWEEN buttons, and the only thing that sets it.

       It used to come from .window_button's own horizontal margin, which
       made the spacing half of a value that was also doing two other jobs -
       the vertical inset inside a 36px titlebar, and the gap between the
       last button and the window's edge. Nudging any one of the three moved
       all three. The button now carries vertical margin only; this sets the
       spacing, and the padding below sets the edge inset.

       Sized for a thumb rather than a cursor. The buttons are ~26px square,
       which is as large as a 36px titlebar allows, so they are already well
       under the 44px a touch target wants - spacing is the only room left to
       give, and 8px was leaving Maximize and Close close enough to hit the
       wrong one on a phone.
    */
    gap: var(--window-titlebar-button-gap);
    /* The inset .window_button's horizontal margin used to provide, so the
       Close button does not sit against the window's rounded corner. */
    padding-right: var(--window-titlebar-button-edge);
}

/*
   Only present when the window declared an icon, so this rule can never
   change the metrics of a window that did not.

   flex: 0 0 auto is the same lesson .window_titlebar_buttons learned: the
   titlebar clips, and a flex item inside an overflow:hidden container has an
   automatic minimum size of 0, so without this the icon would be squeezed to
   nothing by a long title rather than the title being ellipsised.
*/
.window_title_icon[b-k73fy81xfz] {
    flex: 0 0 auto;
    margin-left: var(--window-titlebar-icon-margin);
    font-size: var(--window-titlebar-icon-size);
    text-shadow: 1px 1px 2px black;
    opacity: 0.85;
}

/*
   THE RESERVE. This is what makes the title centre on the window rather than
   on the space left over between the icon and the buttons.

   The problem it solves: a flex title between two groups of unequal width is
   centred on the GAP, so it sits off to one side by half the difference -
   and the difference moves as an icon appears, as the dock control comes and
   goes, and from window to window. Measured on a 1280px window with an icon
   and four buttons, the text sat 57px left of the window's true centre.

   The fix is symmetry rather than measurement. .window_title is stretched
   across the whole titlebar and given the SAME padding on both sides, so its
   content box is centred by construction whatever that padding is; the only
   job left for the padding is to be wide enough that the text cannot reach
   the icon or the buttons. So the reserve is the WIDER of the two sides, and
   an over-estimate is harmless - it costs the title a little width and
   cannot move it off centre.

   The button sum deliberately over-reserves by one gap (it counts a gap for
   every button rather than for the spaces between them), and that spare gap
   is what keeps the title from touching the Dock button.

   Both sides are declared on .window_titlebar rather than on the title, so
   the :has() below - which is about what this ROW contains - has something
   of its own to set.
*/
.window_titlebar[b-k73fy81xfz] {
    --window-titlebar-icon-reserve: 0px;
    --window-titlebar-buttons-reserve: calc(var(--window-titlebar-button-count, 0) * (var(--window-titlebar-button-size) + var(--window-titlebar-button-gap)) + var(--window-titlebar-button-edge));
}

/*
   An icon widens the left-hand side, so it widens the reserve on BOTH sides.
   :has() rather than a flag on the state object, because the icon's presence
   is already expressed by whether the element is rendered and a second
   statement of it could only drift from the first.
*/
.window_titlebar:has(.window_title_icon)[b-k73fy81xfz] {
    --window-titlebar-icon-reserve: calc(var(--window-titlebar-icon-margin) + var(--window-titlebar-icon-size) + var(--window-titlebar-title-gap));
}

/*
   OUT OF FLOW, ON PURPOSE, and it is the two consequences of that rather
   than the positioning itself that matter.

   1. The text is centred on the WINDOW. inset: 0 stretches this box across
      the whole titlebar and the padding either side of it is the same value,
      so its content box shares the row's centre line no matter what the icon
      and the buttons are doing. See the reserve above for how that padding
      is arrived at.

      This was a flex item with .flex-grow-1 and flex-basis: 0, which centres
      the text on the space BETWEEN the two groups - off centre by half the
      difference between them, which changes with the icon and with the dock
      control. No amount of flex tuning fixes that: a flex item cannot know
      about a sibling on the other side of it.

   2. "The title truncates first" stops being a shrink weight and becomes a
      structural fact. Contributing nothing to the row's sizing is stronger
      than contributing zero shrink: the buttons cannot be squeezed by a long
      title at all, at any width, and the strip only ever starts clipping
      when the window itself is narrower than the strip. The previous
      arrangement got there by way of flex-basis: 0 and had been wrong before
      that - measured on a 375px phone with "Terms & Conditions" open, the
      title held 196px while the Dock button was cut to 9px of its 26.

   pointer-events: none is REQUIRED, not defensive. This box is painted over
   the icon and the whole button strip, so without it the title would swallow
   every click aimed at Dock, Minimize, Maximize and Close - the buttons would
   simply stop working, with nothing to see. It has no handlers of its own and
   nothing here is selectable, so there is nothing to give up.
*/
.window_title[b-k73fy81xfz] {
    position: absolute;
    inset: 0;
    padding-inline: max(var(--window-titlebar-icon-reserve), var(--window-titlebar-buttons-reserve));
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 0;
    overflow: hidden;
    pointer-events: none;
    text-shadow: 1px 1px 2px black;
    font-size: 24px;
    font-weight: bold;
}

    .window_title > span[b-k73fy81xfz] {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

/*
   The dock menu is anchored to .window_container (see the position: relative
   there) rather than to the dock button, and sits outside the titlebar's
   clipping.

   It stays a descendant of the window rather than being portalled to a
   top-level layer on purpose: it therefore moves with the window through its
   open/minimize/close transforms, because it is part of what is being
   transformed. A fixed-position menu would detach and slide on its own -
   and, with a transformed ancestor, `fixed` stops meaning "relative to the
   viewport" anyway.
*/
.window_dock_menu[b-k73fy81xfz] {
    position: absolute;
    /* Both offsets are +1px for .window_container's border, which sits
       between .window's padding box (this element's containing block) and
       the titlebar the menu is aligning to. */
    top: calc(var(--window-titlebar-height) + 1px);
    right: 1px;
    z-index: 20;
    min-width: 10rem;
    padding: 4px;
    border-radius: 6px;
    background: var(--window-menu-background, #1d2028);
    border: 1px solid var(--window-menu-border, rgba(255, 255, 255, 0.12));
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
}

.window_dock_option[b-k73fy81xfz] {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 6px 10px;
    border: 0;
    border-radius: 4px;
    background: transparent;
    color: inherit;
    text-align: left;
    white-space: nowrap;
}

    .window_dock_option:hover[b-k73fy81xfz] {
        background: var(--window-menu-hover, rgba(255, 255, 255, 0.08));
    }

.window_dock_option_current[b-k73fy81xfz] {
    background: var(--window-menu-current, rgba(255, 255, 255, 0.14));
}

.window_content_padding[b-k73fy81xfz] {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
    padding: 3px;
}

.window_content[b-k73fy81xfz] {
    flex: 1 1 auto;
    min-height: 0;
    border-radius: 5px;
    overflow-x: hidden;
    overflow-y: auto;
    margin: 0;
    padding: 3px;
}

.window_maximized .window_content_padding[b-k73fy81xfz] {
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}

.window_maximized .window_content[b-k73fy81xfz] {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
}

.window_content[b-k73fy81xfz]::-webkit-scrollbar {
    width: 8px;
}

.window_content[b-k73fy81xfz]::-webkit-scrollbar-track {
    background: transparent;
}

.window_content[b-k73fy81xfz]::-webkit-scrollbar-thumb {
    background-color: rgba(255,255,255,0.3);
    border-radius: 10px;
    border: 1px solid rgba(255,255,255,0.5);
}

    .window_content[b-k73fy81xfz]::-webkit-scrollbar-thumb:hover {
        background-color: rgba(255,255,255,0.5);
    }

.top_gradient[b-k73fy81xfz] {
    content: "";
    position: absolute;
    top: 0px;
    left: 5%;
    right: 5%;
    width: 90%;
    height: 1px;
    background-image: linear-gradient(to right, rgba(255,255,255,0.0) 0%, rgba(255,255,255, 0.10) 20%, rgba(255,255,255, 0.30) 50%, rgba(255,255,255, 0.10) 80%, rgba(255,255,255, 0.0) 100%);
}

.bottom_gradient[b-k73fy81xfz] {
    content: "";
    position: absolute;
    bottom: -1px;
    left: 0%;
    right: 0%;
    width: 100%;
    height: 1px;
    background-image: linear-gradient(to right, rgba(255,255,255,0.0) 0%, rgba(255,255,255, 0.10) 20%, rgba(255,255,255, 0.30) 50%, rgba(255,255,255, 0.10) 80%, rgba(255,255,255, 0.0) 100%);
}

/*.bottom_star {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    bottom: -32px;
    width: 64px;
    height: 64px;
    overflow: visible;
    background-image: url("star4.png");
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
}*/

.window_button[b-k73fy81xfz] {
    position: relative;
    display: flex;
    /* Never squash. A button that shrinks to fit becomes an unreadable
       sliver of an icon; one that keeps its size and gets clipped by the
       strip above is at least honestly gone. */
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    border-radius: 10px;
    /*
       These three come from .window's variables because the title's reserve
       is computed from the same three. A literal here would be a second,
       silent statement of the button's width, and the title would drift off
       centre the first time the two disagreed.
    */
    border: var(--window-titlebar-button-border) solid black;
    box-shadow: rgba(255,204,102,0.5) 0px 0px 2px 2px;
    background-color: rgba(64,64,64,0.5);
    /*    backdrop-filter: blur(5px);*/
    transition: all 0.2s ease;
    /*
       Vertical only. The horizontal half moved to .window_titlebar_buttons
       as `gap` (spacing) and `padding-right` (edge inset) - see the note
       there for why one value doing three jobs was the problem.

       4px top and bottom is what centres a ~26px button in the 36px
       titlebar; it is not free space and should not be traded away.
    */
    margin: 4px 0;
    padding: var(--window-titlebar-button-padding);
    font-size: var(--window-titlebar-button-glyph);
}

.close_button[b-k73fy81xfz] {
    background-color: rgba(255,0,0,0.5);
}


.window_button:hover[b-k73fy81xfz] {
    border: var(--window-titlebar-button-border) solid black;
    box-shadow: rgba(255,204,102,1) 0px 0px 5px 2px;
}

    .window_button:hover:active[b-k73fy81xfz] {
        border: var(--window-titlebar-button-border) solid rgba(255,204,102,1);
        box-shadow: rgba(255,204,102,1) 0px 0px 5px 2px;
    }

.window_button[b-k73fy81xfz],
.window_button *[b-k73fy81xfz] {
    cursor: pointer;
}
/* /Components/UI/Window/WindowLauncher.razor.rz.scp.css */
.launch_button[b-7syw44fqbi] {
    position: relative;
    border-radius: 45px;
    border: 1px solid black;
    box-shadow: rgba(255,204,102,0.5) 0px 0px 3px 3px;
    background-color: transparent;
    /*backdrop-filter: blur(5px) saturate(50%);*/
    transition: all 0.2s ease;
    /*
       Vertical 2px, horizontal 12px - the horizontal half is the launcher's
       breathing room, not a nudge.

       This button is the "auto" middle column of .window_drawer's grid, with
       a minimized list packed hard against it on either side (see the auto
       margin in WindowDrawer.razor.css, which is what makes the left-hand row
       hug the launcher). At 2px the nearest entry sat almost against the
       button's glow, so a row of minimized windows read as continuing
       straight through the launcher rather than stopping at it.

       Margin rather than padding: the button's own padding would grow the
       lit border with it, and it is the ring that marks where the launcher
       ends. Because the column is sized to its content, this is simply
       column width and cannot push anything off the row - the two lists
       divide what is left equally, so they each give up half of it.
    */
    margin: 2px 12px;
    padding: 0px;
}

    .launch_button:hover[b-7syw44fqbi] {
        border: 1px solid black;
        box-shadow: rgba(255,204,102,1) 0px 0px 3px 3px;
    }

        .launch_button:hover:active[b-7syw44fqbi] {
            border: 1px solid rgba(255,204,102,1);
            box-shadow: rgba(255,204,102,1) 0px 0px 3px 3px;
        }

    .launch_button[b-7syw44fqbi],
    .launch_button *[b-7syw44fqbi] {
        cursor: pointer;
    }

.launch_inner[b-7syw44fqbi] {
    display: inline-block;
    width: 64px;
    height: 64px;
    background-image: url('/logo64.png');
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
    vertical-align: middle;
}

/*
   The menu gets a ceiling and a floor once it can hold a grid or a table.

   Content is one row per program and stays short; Small icons and Details are
   both far wider and taller for the same set, and the menu is anchored to a
   button at the bottom of the viewport - so without a max-height it grows off
   the top of the screen, exactly as an overlay window did before it was clamped
   on both axes. min-width stops the four picker buttons being wider than the
   menu they sit in when only one or two programs are launchable.

   overflow-y here rather than on a child: .menu is already overflow: hidden so
   the rounded corners clip, and a scroll container has to be the element that
   is actually too tall.
*/
.launcher_menu[b-7syw44fqbi] {
    max-height: min(70dvh, 560px);
    min-width: 232px;
    max-width: min(92vw, 560px);
    overflow-y: auto;
    overflow-x: hidden;
}

/*
   A wider floor for the two tile views.

   Nothing inside a wrapped flex row makes its container wider - the tiles just
   wrap harder - so at the 232px floor above, 84px tiles came out two to a row
   and the grid read as a narrow column. This is a floor, not a width: with few
   enough programs to fit, the menu is still sized by its content.

   SELECTED WITH :has(), NOT WITH A CONDITIONAL CLASS, and that is load-bearing.
   Bootstrap toggles `.show` on this element at run time; Blazor rewrites the
   whole class attribute whenever its own expression for it changes, taking
   `.show` with it. A conditional class here therefore made the menu disappear
   on the first view change that altered the string, with no hide.bs.dropdown
   firing - it read as a menu that had never opened. :has() moves the decision
   into CSS, where the markup never changes. app.css already leans on it for the
   drawer scrim.
*/
.launcher_menu:has(.launcher_tiles)[b-7syw44fqbi] {
    min-width: 320px;
}

/*
   The view picker, across the top of the menu.

   Sticky rather than scrolling away with the list: the whole point of it is to
   change a list that may be taller than the menu, and having to scroll back up
   to reach it would be worse than not having it. Its own background is opaque
   enough to hide the rows passing underneath - the menu's own is translucent,
   so a transparent bar here would show the list through it.
*/
.launcher_views[b-7syw44fqbi] {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    justify-content: flex-end;
    gap: 2px;
    padding: 4px 6px;
    background-color: rgba(48,48,48,0.92);
    border-bottom: 1px solid rgba(255,255,255,0.10);
}

.launcher_view_button[b-7syw44fqbi] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0px;
    border: 1px solid transparent;
    border-radius: 6px;
    background-color: transparent;
    color: rgba(255,255,255,0.65);
    font-size: 16px;
    cursor: pointer;
    transition: all 0.15s ease;
}

    .launcher_view_button:hover[b-7syw44fqbi] {
        border-color: rgba(255,204,102,0.5);
        background-color: rgba(255,255,255,0.08);
        color: white;
    }

    /*
       The selected view. Amber, the same accent the launcher ring and the focus
       rings already use, so "this is the one you are looking at" reads as part
       of the same interface.
    */
    .launcher_view_active[b-7syw44fqbi] {
        border-color: rgba(255,204,102,0.85);
        background-color: rgba(255,204,102,0.16);
        color: white;
    }

/*
   Small and Large icons: one wrapped flex row per category.

   THE ICON SIZE IS NOT SET HERE. bx-sm and bx-md come from Boxicons' own
   transformations.min.css, which App.razor loads beside the glyph font, and
   they are declared !important - so a font-size on the same element would lose
   anyway. Only the tile WIDTH is ours, because that is what decides how many
   fit on a row.

   The trap this walked into first: the glyph file
   (fonts/basic/boxicons.min.css) is names only and contains not a single
   font-size rule, so checking it alone reads as "the size classes do not
   exist". Check transformations.min.css too - it also carries the rotate and
   flip helpers, bx-fw, bx-border and the rest of the bx-xs..bx-5xl scale.
*/
.launcher_tiles[b-7syw44fqbi] {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    padding: 6px;
}

/*
   The tile width, which is the only thing separating Small icons from Large.

   On the TILE rather than inherited from the row through a custom property,
   which is what it used to be. Equivalent either way; this is one less
   indirection, and it puts the declaration on the element whose box it
   describes.

   Safe to make this class conditional because nothing else writes to a tile's
   class list - unlike the menu <ul>, which Bootstrap toggles `.show` on and
   where a conditional class made the menu vanish.

   THE CENTRING BUG WAS NOT HERE - see the transition note on .launcher_tile
   below. Moving the class was tried first as a fix and changed nothing, because
   the width was being ANIMATED and the menu simply had not finished growing
   when Popper measured it.
*/

/* bx-sm is 1.55rem, so 25px at this app's 16px root. */
.launcher_tile_small[b-7syw44fqbi] {
    width: 44px;
}

/* bx-md is 2.25rem, so 36px. */
.launcher_tile_large[b-7syw44fqbi] {
    width: 84px;
}

.launcher_tile[b-7syw44fqbi] {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    padding: 6px 2px;
    border: 1px solid transparent;
    border-radius: 8px;
    background-color: transparent;
    color: inherit;
    cursor: pointer;
    /*
       NAMED PROPERTIES, NEVER `all`, AND THAT IS THE WHOLE OF THE CENTRING BUG
       (2026-08-25).

       `transition: all` included WIDTH, and width is what separates Small icons
       from Large icons - so switching view ANIMATED every tile from 44px to 84px
       over 150ms, and the menu only reached its final size at the end of that.
       Popper is told to re-measure from OnAfterRenderAsync, which is the moment
       the DOM is patched and therefore the moment the animation STARTS: it read
       the old width, positioned for it, and was never asked again. The menu
       ended up off-centre by roughly the width the tiles had yet to gain, worst
       between the two tile views because that is the only pair whose widths
       differ. Content -> Large looked fine because those tiles are built fresh
       and start at their final size.

       Only the hover feedback should animate. A property that encodes a MODE -
       which view you are in - has no business easing, and `all` is what quietly
       swept it in.
    */
    transition: border-color 0.15s ease, background-color 0.15s ease;
}

    .launcher_tile:hover[b-7syw44fqbi] {
        border-color: rgba(255,204,102,0.5);
        background-color: rgba(255,255,255,0.10);
    }

    /*
       line-height only. The font-size belongs to bx-sm / bx-md, which carry
       !important and would win regardless; stating one here would be a rule
       that looks like it sets the size and does nothing.
    */
    .launcher_tile i[b-7syw44fqbi] {
        line-height: 1;
    }

/*
   The title under a large tile. Two lines at most, then clipped - a tile grid
   whose rows are different heights because one program has a long name stops
   reading as a grid.
*/
.launcher_tile_title[b-7syw44fqbi] {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    /*
       Always two lines tall, even for a one-word name. Clamping alone is not
       enough: it caps the tall ones but leaves the short ones short, and the
       grid then has rows of two different heights depending on which programs
       happened to land in them. Measured before this: 63px for a row of Login
       and Register against 76px for a row of Cookie Policy and Privacy Policy.

       1.2 line-height x 2 lines, stated as a min so a title that somehow
       renders taller is not clipped by this as well as by the clamp.
    */
    min-height: 2.4em;
    font-size: 11px;
    line-height: 1.2;
    text-align: center;
    overflow-wrap: anywhere;
}

/*
   Details.

   A plain table rather than the DataGrid component, which is a full-window
   control: it renders a toolbar and paging arrows in FontAwesome - a font this
   application does not load at all, so those six buttons draw nothing - and it
   sizes itself h-100 inside a dropdown that has no height to give it.
*/
.launcher_details[b-7syw44fqbi] {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

    .launcher_details th[b-7syw44fqbi] {
        position: sticky;
        top: 36px;
        padding: 4px 8px;
        background-color: rgba(255,255,255,0.10);
        font-weight: 600;
        text-align: left;
        white-space: nowrap;
    }

    .launcher_details td[b-7syw44fqbi] {
        padding: 4px 8px;
        border-top: 1px solid rgba(255,255,255,0.06);
        vertical-align: middle;
    }

    .launcher_details tbody tr[b-7syw44fqbi] {
        cursor: pointer;
        transition: background-color 0.15s ease;
    }

        .launcher_details tbody tr:hover[b-7syw44fqbi] {
            background-color: rgba(255,255,255,0.10);
        }

/*
   The Description column, given real room rather than being left to collapse.

   It measured 83px, which was the width of the word "Description" in the
   header and nothing else - because NO PROGRAM HAS A DESCRIPTION SEEDED.
   `def_program` carries an optional second `def_string` pointer beside its
   NOT NULL header, and it is null for all sixty-eight, so every cell in this
   column is empty and the column shrink-wrapped the heading. Reserving the
   width is what makes the column look like somewhere text will go, and stops
   the table reflowing the day the first description is written.

   A FLOOR, NOT A WIDTH. The table is `width: 100%` of the menu and the menu is
   sized by its content between its own min-width and max-width, so this pushes
   the menu wider until it hits `max-width: min(92vw, 560px)` and no further.

   RELAXED ON A NARROW SCREEN, and that is the half that is easy to leave out.
   The menu caps at 92vw, and `.launcher_menu` is `overflow-x: hidden` so a
   table wider than the menu is CLIPPED rather than scrollable - the last
   column would simply be cut off with nothing to reach it with. Below the same
   767px breakpoint the workspace grid uses, the floor goes away and the column
   collapses back to its heading, which is the right trade when there is no
   width to give it and no text in it yet.
*/
.launcher_details_description[b-7syw44fqbi] {
    min-width: 190px;
}

/*
   The Name column.

   Given a floor because "Data Grid Sample" was wrapping onto a second line at
   128px, which makes that row 48px tall against 28px for its neighbours - a
   ragged list, and the program name is the one thing in this table somebody is
   actually scanning for.

   NOT `white-space: nowrap`, deliberately. A floor lets a long name still wrap
   rather than forcing the table wider than the menu can show, and the menu is
   `overflow-x: hidden` so anything past its edge is cut off with no way to
   reach it.
*/
.launcher_details_name[b-7syw44fqbi] {
    min-width: 170px;
}

/*
   The Last Accessed column.

   `white-space: nowrap` so a timestamp is never broken across lines - it was
   wrapping its AM/PM onto a second line - plus a floor wide enough for the
   longest date this renders (a two-digit month, day and hour, e.g.
   "12/31/2026 12:00 PM") so the column does not resize as the clock passes
   noon or the month rolls over.

   THIS RULE WAS DEAD FOR PART OF 2026-08-26 and the failure is worth knowing.
   A scripted edit inserting the block above it used a Perl replacement of
   `$add . $old` WITHOUT /e, so the replacement string interpolated a literal
   " . " and the selector became `. .launcher_details_when` - a parse error, so
   the browser dropped the whole rule. Nothing errored, the stylesheet still
   loaded, and the only symptom was this column quietly starting to wrap. Check
   the SEAM after a scripted insert, not just the inserted text.
*/
.launcher_details_when[b-7syw44fqbi] {
    min-width: 140px;
    white-space: nowrap;
    color: rgba(255,255,255,0.70);
}

/*
   EVERY column floor goes away on a narrow screen, and leaving one behind
   would undo the others.

   The menu caps at 92vw and `.launcher_menu` is `overflow-x: hidden`, so a
   table wider than the menu is CLIPPED rather than scrollable - the last
   column would simply be cut off with nothing to reach it with. The three
   floors add up to 500px, which does not fit a 375px phone, so below the same
   767px breakpoint the workspace grid uses they are all released and the
   columns go back to being sized by their content.

   `.launcher_details_when` keeps its `nowrap` here on purpose: a timestamp
   broken across two lines is unreadable at any width, and at ~110px it still
   leaves room for the other two.

   THIS BLOCK MUST STAY BELOW ALL THREE FLOORS. A media query adds NO
   specificity - it only gates when its rules apply - so `min-width: 0` here
   and `min-width: 170px` up there are the same weight and SOURCE ORDER
   decides. It sat above two of them for a while and lost to both: the query
   matched, the description relaxed because its floor was declared earlier, and
   the other two stayed at their desktop widths and clipped the table off the
   side of a phone. Add a fourth column and its floor goes ABOVE this block.
*/
@media (max-width: 767px) {
    .launcher_details_name[b-7syw44fqbi],
    .launcher_details_description[b-7syw44fqbi],
    .launcher_details_when[b-7syw44fqbi] {
        min-width: 0;
    }
}

/*
   One row of the launcher menu: the program's icon beside its name.

   Moved here from app.css when the drawer's minimized entries became
   icon-only and stopped using it, which left this component as the only
   consumer. Note that the menu's own frame - `.menu` and
   `.window_drawer_menu` - deliberately stays global, because the minimized
   groups wear those two as well.
*/
.menu_item[b-7syw44fqbi] {
    display: flex;
    align-items: center;
    gap: 6px;
}

/*
   A category heading inside the launcher menu. Also moved from app.css,
   where this component was its only consumer.

   `color: transparent` is inert as things stand and is kept as the
   fallback it reads as: both call sites pair this class with Bootstrap's
   `.text-light`, which sets the colour with `!important` and wins. A
   heading that ever appears without it would be invisible rather than
   wrongly coloured, which is the safer way round for a band whose whole job
   is to be a separator.
*/
.seperator_header[b-7syw44fqbi] {
    display: block;
    width: 100%;
    clear: both;
    padding: 3px;
    margin: 0px;
    color: transparent;
    text-align: inherit;
    text-decoration: none;
    white-space: nowrap;
    background-color: rgba(255,255,255,0.10);
    overflow: hidden;
}
/* /Components/WindowFramework/Drawer/MinimizedWindowGroupList.razor.rz.scp.css */
/*
   ---------------------------------------------------------------------
   A drawer entry: one minimized window, or one collapsed group of them.

   ICON ONLY, with the window's name on `title`/`aria-label` and nowhere on
   screen. A labelled entry was as wide as its window's name - "Terms &
   Conditions" measured 113px, and two lines tall once it wrapped - against
   a column that is about 150px on a phone. Four windows wanted 444px of a
   149px row, so the drawer was full at two and unusable at four.

   A square entry makes the cost per window fixed and known, which is what
   lets the row scroll sensibly (see WindowDrawer.razor.css) instead of
   simply being overfull.

   The size is a touch target first: 44px is the smallest square a thumb
   hits reliably, and it fits the drawer without changing its height, which
   the 64px launcher sets.

   NOTE: `title` is the browser's native tooltip, which appears on hover and
   so reaches a mouse but not a finger. That is a real gap on the phone this
   change is for, and the answer is a press-and-hold label rather than
   anything here - Bootstrap's tooltip is not an option, since nothing in
   this app ever calls new bootstrap.Tooltip and every data-bs-toggle=
   "tooltip" in the codebase is inert decoration on top of `title`.
   ---------------------------------------------------------------------
*/
.window_drawer_entry[b-ialpz2dghn] {
    position: relative;
    /* Never shrink. A row that squashes its entries to fit is a row that
       silently stops being scrollable, which is the failure this replaced:
       the buttons compressed and then overflowed past the START edge, where
       overflow-x cannot reach them. */
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    /* Matches .window_button and .launch_button - the same dark translucent
       pill with a gold glow, so the drawer reads as part of the same chrome
       rather than as browser default buttons, which is what these were. */
    border: 1px solid black;
    border-radius: 10px;
    background-color: rgba(64, 64, 64, 0.5);
    box-shadow: rgba(255, 204, 102, 0.5) 0 0 2px 2px;
    color: inherit;
    font-size: 24px;
    line-height: 1;
    transition: all 0.2s ease;
    cursor: pointer;
}

.window_drawer_entry:hover[b-ialpz2dghn] {
    box-shadow: rgba(255, 204, 102, 1) 0 0 5px 2px;
}

.window_drawer_entry:hover:active[b-ialpz2dghn] {
    border: 1px solid rgba(255, 204, 102, 1);
    box-shadow: rgba(255, 204, 102, 1) 0 0 5px 2px;
}

/*
   The member count, so a collapsed group still says how much is behind it.
   Without it a group of eight conversations looks exactly like a group of
   two, and the drawer's whole job is telling the user what is open.

   A corner badge rather than a chip beside the name, because there is no
   name any more. Positioned against the entry (position: relative there)
   and allowed to sit slightly outside it, so it never crowds the icon.
*/
.window_group_count[b-ialpz2dghn] {
    position: absolute;
    top: -4px;
    right: -4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 4px;
    border-radius: 999px;
    /* Opaque, not the translucent chip it was: it now overlaps the entry's
       own edge and the icon behind it, and a see-through badge over a glyph
       is unreadable. */
    background: rgba(255, 204, 102, 0.95);
    color: #201a0c;
    font-size: 11px;
    font-weight: bold;
    line-height: 1;
}

/*
   ---------------------------------------------------------------------
   The group menu.

   Positioning is left entirely to Bootstrap/Popper, exactly as
   WindowLauncher's menu is - which is why nothing here knows or cares how
   tall the drawer is. Popper measures the toggle and flips the menu above
   it when there is no room below, and there never is: the drawer is the
   last row of the page.

   The one rule that matters is a NEGATIVE one, and it lives in the markup
   rather than here: there must be no positioned wrapper around the toggle
   and menu. Bootstrap's own .dropdown class sets position: relative, and
   that is enough to hand the menu to .window_workspacelist (overflow-x:
   auto) and .window_drawer (overflow: hidden) to clip, because an
   absolutely-positioned element is only clipped by overflow ancestors in
   its containing-block chain. Unwrapped, the menu's containing block is
   the fixed <body>, which is outside both.
   ---------------------------------------------------------------------
*/
.window_group_menu[b-ialpz2dghn] {
    /* Never taller than the space above the drawer; past that the list
       scrolls rather than the menu running off the top of the screen.
       Popper writes the max-height itself when it has to, but only after a
       flip - this keeps a long list sane from the first frame. */
    max-height: min(60vh, 520px);
    overflow-y: auto;
    min-width: 260px;
    max-width: min(420px, calc(100vw - 24px));
    padding: 6px;
    /*
       Deeper than the ambient .menu shadow: this one is asking for a
       decision, so it should read as sitting well above the drawer.

       ON TOP of that shadow rather than instead of it, which is what
       --menu-shadow is for. box-shadow is one property, so naming it here
       used to discard all three of .menu's layers - and while .menu's were
       broken that went unnoticed. Now that they paint, replacing them would
       leave the menu that most wants lift with less of it than the launcher
       beside it. Earlier layers paint over later ones, so this close dark
       shadow sits in front of the ambient one and the two darken together.
    */
    box-shadow: rgba(0, 0, 0, 0.7) 0 8px 24px 4px, var(--menu-shadow);
}

/*
   Sized for a finger rather than a cursor. The picture alone is a 64px
   target and the row is taller still, so the whole row - not just the
   text - is what gets pressed.
*/
.window_group_item[b-ialpz2dghn] {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 6px 10px;
    border-radius: 6px;
}

.window_group_title[b-ialpz2dghn] {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 1.1rem;
}

/*
   Full 64x64. Pictures are supplied at whatever size the application likes,
   so the size is pinned here rather than trusted - but pinned to the size
   the source images actually are, so nothing is being scaled up.

   object-fit keeps a non-square image from stretching into a distorted
   face; a plain width/height would squash it.
*/
.window_group_picture[b-ialpz2dghn] {
    flex: 0 0 auto;
    width: 64px;
    height: 64px;
    border-radius: 8px;
    object-fit: cover;
    /* A transparent or slow-loading image would otherwise collapse the row
       as it swaps in. */
    background: rgba(255, 255, 255, 0.08);
}
/* /Components/WindowFramework/Drawer/WindowDrawer.razor.rz.scp.css */
/* Grid (not flex) is what keeps the launcher fixed dead-center: the two
   side columns are both "1fr", so they always get equal, content-
   independent widths and the "auto" middle column never shifts no matter
   how many buttons pile up on either side. A flex row with
   justify-content:center would center the row's total width instead,
   which is exactly what let the launcher drift as the workspace list grew. */
/*
   Dims and blurs everything behind an open group menu, so picking a
   conversation reads as a decision rather than as a tooltip.

   Entirely CSS-driven: Bootstrap puts .show on the menu it opened, and
   :has() picks that up from here. Nothing has to be told when a menu opens
   or closes, and no state is duplicated between Bootstrap and Blazor.

   pointer-events stays none throughout, which is the important part. The
   scrim must not intercept the click that dismisses the menu - Bootstrap's
   own click-outside handling is what closes it, and it should also reach
   whatever was clicked, rather than being eaten by a backdrop the way a
   modal's would be.

   Nothing renders it inert when no menu is open beyond opacity, so it costs
   a composited layer and no hit-testing.
*/
/*
   1500 sits above .window_popup_layer (1000), so an open group menu blurs
   overlay windows too rather than leaving a modal sitting sharply on top of
   the scrim. The menu itself is lifted past this in
   MinimizedWindowGroupList.razor.css, since Bootstrap's own .dropdown-menu
   z-index is 1000 and would otherwise end up underneath.
*/
.window_drawer_scrim[b-dyv98koi4h] {
    position: fixed;
    inset: 0;
    z-index: 1500;
    pointer-events: none;
    opacity: 0;
    background: rgba(0, 0, 0, 0.35);
    transition: opacity 160ms ease;
}

/*
   Any drawer menu raises it - the launcher's as well as a group's. They are
   the same gesture from the user's side (a menu is up, everything else is
   waiting), so they should look the same.
*/
body:has(.window_drawer_menu.show) .window_drawer_scrim[b-dyv98koi4h] {
    opacity: 1;
    backdrop-filter: blur(6px) saturate(80%);
}

@media (prefers-reduced-motion: reduce) {
    .window_drawer_scrim[b-dyv98koi4h] {
        transition: none;
    }
}

/*
   NOTE: this element must stay position: static. It has overflow: hidden,
   and positioning it would make it the containing block for WindowLauncher's
   dropdown - which is a direct child - handing that menu to this overflow to
   clip. Unpositioned, the menu's containing block is the fixed <body>,
   outside this element entirely, and the menu is free to open upwards over
   the page. The same rule is what makes the group menus work; see
   MinimizedWindowGroupList.razor's header note.

   That does mean the drawer blurs along with everything else behind an open
   menu, which is the intent - "blur everything while they select".
*/
.window_drawer[b-dyv98koi4h] {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    width: 100%;
    /* flex-shrink:0 here - the drawer sits as an item in the *outer* page's
       flex column alongside the window content area; without this, a tall
       window (e.g. Terms) growing that sibling squeezes the drawer down to
       almost nothing, pushing the launcher off the bottom of the screen. */
    flex: 0 0 auto;
    overflow: hidden;
    padding: 4px;
}

/*
   Both minimized lists. They are one column each side of the launcher, and
   the grid above gives each of them exactly half of what is left - about
   150px on a phone - so overflowing is the normal case rather than the edge
   case, and scrolling has to actually work.
*/
.window_workspacelist[b-dyv98koi4h],
.window_windowlist[b-dyv98koi4h] {
    display: flex;
    flex-direction: row;
    /* Centred against the 64px launcher. These were flex-end on one list
       and flex-start on the other, which reads as a pair of opposites but
       is the CROSS axis in a row - it bottom-aligned one row of buttons and
       top-aligned the other. The horizontal packing those comments were
       about is justify-content, below. */
    align-items: center;
    gap: 6px;
    overflow-x: auto;
    overflow-y: hidden;
    /* Don't let a swipe that runs off the end of the row start scrolling
       the page behind it. */
    overscroll-behavior-x: contain;
    min-width: 0;
    padding: 4px;
    /* Thin rather than hidden: a row that scrolls with nothing to say so is
       a row whose extra entries do not exist as far as the user knows. On
       touch these are overlay scrollbars and cost no height; on a mouse the
       ~11px they reserve still fits under the launcher. */
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 204, 102, 0.5) transparent;
}

    .window_workspacelist[b-dyv98koi4h]::-webkit-scrollbar,
    .window_windowlist[b-dyv98koi4h]::-webkit-scrollbar {
        height: 6px;
    }

    .window_workspacelist[b-dyv98koi4h]::-webkit-scrollbar-thumb,
    .window_windowlist[b-dyv98koi4h]::-webkit-scrollbar-thumb {
        border-radius: 999px;
        background: rgba(255, 204, 102, 0.5);
    }

/*
   Packing the left-hand row against the launcher WITHOUT breaking its
   scrolling, which is the whole reason this is an auto margin on the first
   entry rather than justify-content: flex-end.

   An overflowing flex container spills away from its justification, so
   flex-end made this row overflow past its START edge - and a start-edge
   overflow is unreachable, because scrollLeft cannot go below zero. That
   was not a theoretical limit: with four windows open the row's content was
   444px inside a 149px box, the first entry sat at x = -295, and
   scrollWidth equalled clientWidth, so the browser reported nothing to
   scroll while three of the four entries were off the side of the screen.

   An auto margin on the first item does the same job and collapses to zero
   the moment the content no longer fits, at which point the row starts at
   its start edge and overflows to the END - which scrolls.

   ::deep because the entries are rendered by MinimizedWindowGroupList; this
   component owns the row's layout but not the elements in it.
*/
.window_workspacelist[b-dyv98koi4h]  > :first-child {
    margin-inline-start: auto;
}

/*
   The right-hand row needs no such trick: flex-start already overflows to
   the end edge, which is the scrollable direction.
*/
.window_windowlist[b-dyv98koi4h] {
    justify-content: flex-start;
}
/* /Components/WindowFramework/Host/WindowHost.razor.rz.scp.css */
/*
   The main window layer is a 3x3 grid:

       ┌───────────────────────────────┐
       │           TopDock             │   row 1, spanning all columns
       ├──────┬─────────────────┬──────┤
       │ Left │      Main       │ Right│   row 2
       ├──────┴─────────────────┴──────┤
       │          BottomDock           │   row 3, spanning all columns
       └───────────────────────────────┘

   Track sizes come from the four --dock-* custom properties, written
   inline by WindowHost.razor from each docked window's DockSize. All four
   are always emitted, including zeroes, because a track can only be
   transitioned if it is present in both the old and new declarations -
   dropping one would make a dock appear and disappear instantly while the
   others animate.

   The grid areas themselves are claimed by WindowComponentHost, from its
   own Placement - see the region rules in WindowComponentHost.razor.css.
   That split is forced by CSS isolation (this file's selectors only match
   elements WindowHost itself renders) but it also lands in the right
   place: "how big are the regions" is this component's business, "which
   region am I in" is the window's.
*/
.window_workspace[b-i4u0m7ueeg] {
    position: relative;
    width: 100%;
    height: 100%;
    overflow: visible;
    display: grid;
    grid-template-columns: minmax(0, var(--dock-left, 0px)) minmax(12px, 1fr) minmax(0, var(--dock-right, 0px));
    grid-template-rows: minmax(0, var(--dock-top, 0px)) minmax(12px, 1fr) minmax(0, var(--dock-bottom, 0px));
    transition: grid-template-columns 260ms cubic-bezier(0.22, 1, 0.36, 1), grid-template-rows 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

/*
   Every track is a minmax(), and both halves are doing a job.

   The MAIN track is minmax(12px, 1fr) rather than a bare 1fr, because 1fr
   is really minmax(auto, 1fr) and that auto minimum refuses to shrink below
   the content's intrinsic size - a long unbroken line in the main window
   would push the grid wider than the viewport and take the docks off screen
   with it. Any definite minimum fixes that; 12px is chosen to match
   MIN_MAIN_SIZE in WindowSplitter.razor.js, which sizes it to two splitter
   grab areas rather than to content. When someone deliberately squeezes the
   docks together, all the main area still owes them is a way to take hold
   of the splitters again.

   The DOCK tracks are minmax(0, <size>) rather than a fixed <size>, which
   makes the whole layout self-limiting. A fixed track is never reduced to
   fit its container, so two docks that between them asked for more than the
   workspace simply overflowed it - the main track hit zero and everything
   past it slid off screen. With a zero base and the requested size only as a
   growth limit, the grid distributes what space actually exists: below
   capacity nothing changes and each dock gets exactly what it asked for;
   over capacity they share equally and the main area keeps its 120px.

   The drag clamps in WindowSplitter.razor.js keep it from getting there
   interactively. This is the guarantee underneath that, for the cases JS
   never sees - a viewport that shrinks after the fact, a restored dock size
   from a wider screen, a font-size change.
*/

/*
   While a splitter is being dragged the track sizes are written straight
   to the element by JS at pointer speed. Transitioning them then would
   make the panel lag a finger it is supposed to be following, so the
   transition is suspended for the duration of the drag and restored on
   release, where it smooths the single authoritative update coming back
   from the server.
*/
.window_workspace_resizing[b-i4u0m7ueeg] {
    transition: none;
    user-select: none;
    cursor: inherit;
}

@media (prefers-reduced-motion: reduce) {
    .window_workspace[b-i4u0m7ueeg] {
        transition: none;
    }
}

/*
   Mobile-first fallback: below the tablet breakpoint there is no room to
   put two panels side by side, so the side docks stop being side docks and
   stack as full-width rows instead - member list above, conversation
   below. The columns collapse to a single track and the left/right dock
   sizes are reinterpreted as row heights.

   Their splitters turn horizontal at the same breakpoint (see
   WindowSplitter.razor.css) rather than being withdrawn: with all four docks
   now competing on one axis, giving height back to the main area is the only
   way to keep it on screen, and that needs a handle on every one of them.

   This block and WindowSplitter.razor.js are the two things that have to
   agree about the shape of the stacked grid, and they agree by DERIVATION
   rather than by both being told: the drag maths reads the used track lists
   off this grid and recognises the layout from one column and five rows, so
   the breakpoint itself is written down only here.
*/
@media (max-width: 767px) {
    .window_workspace[b-i4u0m7ueeg] {
        grid-template-columns: minmax(0, 1fr);
        /* Same minmax() reasoning as the desktop template above - more so
           here, since four dock tracks share one axis instead of two. */
        grid-template-rows:
            minmax(0, var(--dock-top, 0px))
            minmax(0, var(--dock-left, 0px))
            minmax(12px, 1fr)
            minmax(0, var(--dock-right, 0px))
            minmax(0, var(--dock-bottom, 0px));
        transition: grid-template-rows 260ms cubic-bezier(0.22, 1, 0.36, 1);
    }
}

/*
   One wrapper per region, and the wrapper - not the window - is what claims
   a grid cell.

   The windows used to be grid items themselves, each placing itself into its
   own cell. That works right up to the moment two of them want the same
   cell: grid items in one cell overlap, so two normal windows in the main
   area rendered on top of each other. A wrapper turns the cell into a
   formatting context the windows can FLOW in, which is what lets them tile.

   position: relative makes each region the containing block for the windows
   inside it. That is what the exit animation pins to (see the leaving rules
   in WindowComponentHost.razor.css) - previously it relied on an
   absolutely-positioned grid item being contained by its grid AREA, which
   only worked while the window was a grid item.

   min-width/min-height: 0 for the usual reason - the automatic minimum size
   of a flex item refuses to shrink below its content, and without this a
   long line inside a window would push its region wider than its track.
*/
.window_region[b-i4u0m7ueeg] {
    position: relative;
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
}

.window_region_top[b-i4u0m7ueeg] {
    grid-column: 1 / -1;
    grid-row: 1;
}

.window_region_bottom[b-i4u0m7ueeg] {
    grid-column: 1 / -1;
    grid-row: 3;
}

.window_region_left[b-i4u0m7ueeg] {
    grid-column: 1;
    grid-row: 2;
}

.window_region_right[b-i4u0m7ueeg] {
    grid-column: 3;
    grid-row: 2;
}

.window_region_main[b-i4u0m7ueeg] {
    grid-column: 2;
    grid-row: 2;
}

/*
   Stacked layout below the tablet breakpoint - one column, five rows, in
   visual order. Mirrors the template swap above; both have to change
   together.
*/
@media (max-width: 767px) {
    .window_region_top[b-i4u0m7ueeg] {
        grid-column: 1;
        grid-row: 1;
    }

    .window_region_left[b-i4u0m7ueeg] {
        grid-column: 1;
        grid-row: 2;
    }

    .window_region_main[b-i4u0m7ueeg] {
        grid-column: 1;
        grid-row: 3;
    }

    .window_region_right[b-i4u0m7ueeg] {
        grid-column: 1;
        grid-row: 4;
    }

    .window_region_bottom[b-i4u0m7ueeg] {
        grid-column: 1;
        grid-row: 5;
    }
}

.window_popup_layer[b-i4u0m7ueeg] {
    position: absolute;
    inset: 0;
    z-index: 1000;
    pointer-events: none;
}

/* z-index is set inline per item from WindowInstance.ZIndex - see WindowHost.razor. */
.window_popup_item[b-i4u0m7ueeg] {
    position: absolute;
    inset: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    pointer-events: auto;
}

    .window_popup_item:has(.window_component_host_hidden)[b-i4u0m7ueeg],
    .window_popup_item:has(.window_component_host_minimized)[b-i4u0m7ueeg] {
        pointer-events: none;
    }
/* /Components/WindowFramework/Host/WindowSplitter.razor.rz.scp.css */
/*
   A splitter is a grid item placed in the track NEXT TO the dock it guards,
   then pushed against the shared edge. Living in the grid rather than being
   absolutely positioned means it follows the track automatically - no second
   copy of the layout maths to keep in sync with WindowHost.razor.css.

   The vertical pair (left/right) straddle their boundary with a -6px
   margin, so the line sits on it with 6px of grab area either side.

   The horizontal pair are tuned differently, and deliberately: both sit
   ENTIRELY BELOW their boundary, putting the line about 6px down. Top takes
   margin-top: 0 (flush with the row start), bottom takes margin-bottom:
   -12px (its whole band past the row end). Two things fall out of that, and
   both are why it is done this way:

     The frame's drop shadow throws downward (`0 6px 16px` when docked), so
     the window that needs clearance at a horizontal boundary is always the
     one ABOVE it. Pushing the line down gives that window room and costs
     the window below almost nothing, since its top edge only ever meets the
     ambient ring.

     Nothing overlaps. A vertical splitter runs the full height of the main
     row and stops exactly at its end edge; because the bottom splitter now
     starts there rather than 6px earlier, the two abut instead of fighting
     over a 12x12 square at the corner. That is what makes the vertical pair
     need no vertical inset at all.
*/
.window_splitter[b-1syyv8sftp] {
    z-index: 5;
    background: transparent;
    touch-action: none;
    /* Matches .window_lock's lock transition exactly, so a splitter blurs in
       step with the windows either side of it rather than a beat apart. */
    transition: filter 220ms ease, opacity 220ms ease;
}

/*
   Splitters blur with the layer they belong to.

   A locked window blurs itself at the source (.window_locked), but splitters
   are siblings of the windows rather than children, so nothing was reaching
   them - a modal came up, the whole main layer went soft, and the splitter
   lines stayed razor sharp on top of it, which read as though they were part
   of the modal.

   pointer-events is dropped at the same time, and that part is correctness
   rather than polish: a modal blocks the layer beneath it, so resizing a
   dock behind one must be impossible, not merely discouraged. Without it the
   splitter still sat above the blurred windows and stayed perfectly
   draggable.

   Unlocked is `filter: none` rather than `blur(0)`, for the same reason
   .window_lock is - blur(0) is not free, it promotes every splitter to its
   own composited layer permanently. Interpolating from `none` still
   animates, because the browser substitutes the identity value.
*/
.window_workspace_locked .window_splitter[b-1syyv8sftp] {
    filter: blur(6px);
    opacity: 0.8;
    pointer-events: none;
}

/*
   The visible line is drawn by ::after and is thinner than the element
   itself. The element stays finger-sized (Fitts's law applies harder on a
   phone than a mouse); the line stays hairline-thin so it reads as a
   boundary rather than as a gutter.
*/
.window_splitter[b-1syyv8sftp]::after {
    content: "";
    display: block;
    background: var(--window-splitter-color, rgba(255, 255, 255, 0.14));
    transition: background 120ms ease;
}

/*
   Lit while hovered, and while THIS splitter is being dragged.

   The drag half used to read `.window_workspace_resizing .window_splitter`,
   and that class lives on the WORKSPACE - so grabbing one splitter lit every
   splitter on screen, which with four docks open is four lines announcing a
   drag that only one of them is doing.

   The hover half alone is not enough to replace it, which is why the drag
   needs a state of its own at all: a drag holds pointer capture, so once the
   pointer outruns the handle nothing is hovered any more and the line the
   user is actually dragging would go dark mid-drag. WindowSplitter.razor.js
   puts .window_splitter_active on the handle itself for exactly the span of
   the drag.
*/
.window_splitter:hover[b-1syyv8sftp]::after,
.window_splitter_active[b-1syyv8sftp]::after {
    background: var(--window-splitter-color-active, rgba(255, 255, 255, 0.4));
}

/*
   No vertical inset is needed - margin-block stays 0, and that is load
   bearing rather than merely default.

   align-self: stretch runs these the full height of the main row, ending
   exactly on its end edge. The horizontal splitters are positioned to start
   there rather than straddle it (see the header note), so the two abut and
   never contest the same pixels. An earlier revision inset these by 6px to
   dodge a horizontal splitter that DID straddle; moving the horizontal one
   instead solved it at the source and this inset became unnecessary.

   Where the two still cross, the horizontal splitter takes the higher
   z-index below, so the junction reads as a clean T and the pointer lands on
   one of them rather than on whichever came last in the DOM.
*/
.window_splitter_left[b-1syyv8sftp],
.window_splitter_right[b-1syyv8sftp] {
    grid-column: 2;
    grid-row: 2;
    width: 12px;
    cursor: col-resize;
    align-self: stretch;
    margin-block: 0px;
}

    .window_splitter_left[b-1syyv8sftp]::after,
    .window_splitter_right[b-1syyv8sftp]::after {
        width: 1px;
        height: 100%;
        margin-inline: auto;
    }

.window_splitter_left[b-1syyv8sftp] {
    justify-self: start;
    margin-left: -6px;
}

.window_splitter_right[b-1syyv8sftp] {
    justify-self: end;
    margin-right: -6px;
}

.window_splitter_top[b-1syyv8sftp],
.window_splitter_bottom[b-1syyv8sftp] {
    grid-column: 1 / -1;
    grid-row: 2;
    height: 12px;
    cursor: row-resize;
    justify-self: stretch;
    /* One above .window_splitter's 5, so a horizontal splitter wins any
       crossing with a vertical one - both the paint order and the hit test.
       They span the full width, so they are the ones that read as
       continuous. */
    z-index: 6;
}

    .window_splitter_top[b-1syyv8sftp]::after,
    .window_splitter_bottom[b-1syyv8sftp]::after {
        height: 1px;
        width: 100%;
        margin-block: auto;
    }

.window_splitter_top[b-1syyv8sftp] {
    align-self: start;
    margin-top: 0px;
}

.window_splitter_bottom[b-1syyv8sftp] {
    align-self: end;
    margin-bottom: -12px;
}

/*
   Below the stacking breakpoint the side docks become full-width rows (see
   WindowHost.razor.css), so their splitters stop being vertical lines and
   become horizontal ones - the same shape, and the same maths, as the
   top/bottom pair, just guarding a different pair of rows.

   They used to be WITHDRAWN here instead, on the grounds that a phone has no
   width to trade between two panels. That reasoning was about the wrong axis:
   stacked, these rows are competing for HEIGHT with everything else, and with
   no handle four docks at their default thickness squeeze the main area down
   to its 12px minimum with no way to give any of it back. All four docks need
   a splitter precisely because they now share one axis.

   The row a splitter sits in has to be remapped, because the row COUNT
   changes. Desktop is three rows:

       1 top dock | 2 main | 3 bottom dock

   and stacked is five:

       1 top dock | 2 left dock | 3 main | 4 right dock | 5 bottom dock

   Each splitter belongs in the row ADJACENT to the dock it guards, on the
   side the boundary actually falls: start-aligned in the row after a leading
   dock, end-aligned in the row before a trailing one. So what is fixed is the
   relationship, not the number. The top splitter is start-aligned in the row
   BELOW the top dock - row 2 in both layouts, which is why it needs no
   override here and kept working by luck. The bottom splitter is end-aligned
   in the row ABOVE the bottom dock, which moves from row 2 to row 4; left at
   row 2 it guarded the boundary above the MAIN area instead, appearing in the
   middle of the screen with nothing to resize. Left and right both land in
   row 3, the main area, start- and end-aligned respectively.

   The drag axis follows on its own: WindowSplitter.razor.js reads the grid's
   USED track lists, and a one-column five-row workspace tells it everything
   it needs. The breakpoint stays written down exactly once - here.
*/
@media (max-width: 767px) {
    .window_splitter_left[b-1syyv8sftp],
    .window_splitter_right[b-1syyv8sftp] {
        grid-column: 1 / -1;
        grid-row: 3;
        width: auto;
        height: 12px;
        cursor: row-resize;
        justify-self: stretch;
        /* Undoes the -6px straddle of the vertical layout; stacked, these
           sit entirely below their boundary like the horizontal pair. */
        margin-inline: 0;
    }

        .window_splitter_left[b-1syyv8sftp]::after,
        .window_splitter_right[b-1syyv8sftp]::after {
            width: 100%;
            height: 1px;
            margin-inline: 0;
            margin-block: auto;
        }

    .window_splitter_left[b-1syyv8sftp] {
        align-self: start;
        margin-top: 0px;
    }

    .window_splitter_right[b-1syyv8sftp] {
        align-self: end;
        margin-bottom: -12px;
    }

    .window_splitter_bottom[b-1syyv8sftp] {
        grid-row: 4;
    }
}
/* /Components/WindowFramework/Rendering/WindowComponentHost.razor.rz.scp.css */
/*
   Open / minimize / restore animation.

   Windows stay in the DOM for their whole lifetime and only toggle
   `display`, which historically could not be transitioned at all. Two
   modern additions make it work:

     `display ... allow-discrete` defers the flip to `display: none` until
     the end of the transition, so a window animates OUT before it stops
     being laid out.

     `@starting-style` supplies the "from" state on the way back IN. Going
     display:none -> display:flex, `display` flips at the START and the
     element has no previous style to interpolate from, so without this it
     would simply appear.

   Both degrade cleanly: a browser that supports neither just shows and
   hides instantly, which is exactly the old behaviour.

   Deliberately NO `will-change` here. will-change of transform, opacity or
   filter promotes the element to its own composited layer permanently,
   which is exactly the cost this file works to avoid - see the note on
   .window_component_host_visible about why the resting transform is `none`.
*/
.window_component_host[b-mtxjgbouud] {
    display: flex;
    flex-direction: column;
    width: 100%;
    min-height: 0;
    min-width: 0;
    overflow: visible;
    /* Shrink toward the drawer at the bottom of the screen. */
    transform-origin: bottom center;
    /*
       How far a leaving window travels downward. A variable rather than a
       literal because it has to differ for windows whose HEIGHT is animating
       at the same time - see the override below the away state.

       14% is a percentage of the element's OWN height, which is what makes
       the distinction necessary.

       The floor is what stops a SHORT window fading in place. Percentage
       travel is proportional to height, and so is the scale about
       bottom-center, so the whole gesture shrinks with the window: measured
       on the top edge, a 952px side dock falls 364px and reads as a drop,
       while a 200px bottom dock falls 58px and reads as hovering while it
       fades. Same declaration, two different animations, purely because one
       panel is short.

       max() raises the short cases to a distance that reads as deliberate
       while leaving every window already taller than ~457px exactly as it
       was - so the side docks and maximized windows keep the fall that
       already looked right, and nothing regresses to fix the bottom dock.

       This pair is the DEFAULT - "fall toward the drawer at the bottom of
       the screen" - and it is right for a main-area window and an overlay.
       A DOCKED panel overrides it to retract into its own edge instead; see
       the four region rules below the away state.
    */
    --host-exit-x: 0px;
    --host-exit-travel: max(14%, 64px);
    /*
       Maximize/restore animates height between `auto` (content-sized) and
       100%. Interpolating to or from an intrinsic keyword like `auto` is
       normally impossible - `interpolate-size: allow-keywords` is what
       makes it legal. It inherits, so setting it here also covers
       .window_lock / .window_shadow / .window inside WindowFrame, which
       resize in lockstep with this element.

       Unlike opacity and transform, animating height is NOT a
       compositor-only effect: it forces layout on every frame. This is by
       far the most expensive animation in the window system, and the first
       one to drop if maximize feels sluggish on a real device.

       Where interpolate-size is unsupported this degrades to the instant
       snap you had before, so it is safe to ship as-is.
    */
    interpolate-size: allow-keywords;
    /*
       `width` is in here for maximizing OVERLAYS, which are content-width
       rather than 100% and so grow on both axes. Root windows are always
       width: 100%, so the width leg never fires for them.
    */
    /*
       flex-grow is in the list for windows SHARING a dock: they split it by
       grow rather than by content, so giving that share back smoothly is the
       only way a neighbour can take it over without snapping. It is
       deliberately never used for main-area sizing - see the maximized rule
       further down for why grow must stay out of that.
    */
    transition: opacity 200ms ease, transform 260ms cubic-bezier(0.22, 1, 0.36, 1), width 260ms cubic-bezier(0.22, 1, 0.36, 1), height 260ms cubic-bezier(0.22, 1, 0.36, 1), flex-grow 260ms cubic-bezier(0.22, 1, 0.36, 1), display 260ms allow-discrete;
}

.window_component_host_maximized[b-mtxjgbouud] {
    /*
       This flex is for the OVERLAY layer, where the host is a flex item of
       .window_popup_item. Main-layer hosts are flex items of their region
       wrapper and override it further down - grow there would fill the
       region in one frame and swallow the maximize animation.
    */
    flex: 1 1 auto;
    height: 100%;
}

/*
   The viewport clamp is deliberately NOT keyed on _visible.

   It used to be, and that broke the exit animation badly. `allow-discrete`
   keeps a minimizing window laid out for the whole transition, but the
   class has already flipped to _minimized by then - so a _visible-only
   clamp is dropped on the first frame of the animation and the window
   balloons to its full intrinsic content height while it is fading and
   folding. Measured on the Cookie window: 734px -> 2581px, an 1847px jump
   mid-flight, which reads on screen as the titlebar scrambling before the
   window vanishes.

   Clamping in every visibility state keeps the geometry stable for the
   entire animation.
*/
.window_component_host:not(.window_component_host_overlay)[b-mtxjgbouud] {
    max-height: 100%;
}

/*
   Overlay popup windows size to their content, but never past the
   viewport ON EITHER AXIS: they grow until they are effectively as large as
   a maximized window and then their content scrolls or wraps instead.
   Without the clamp a long overlay (a chat thread, a terms body) runs off
   the edges of the screen with no way to reach the rest.

   BOTH clamps are load-bearing, and the width one is the easier of the two
   to leave out, because nothing looks wrong until a window has genuinely
   wide content in it. `width: auto` on a flex item resolves to its
   MAX-CONTENT width - which for a paragraph is that whole paragraph set on
   one line. The Terms & Conditions dialog is what found this: its body is
   several hundred lines of prose, so the window sized itself to the longest
   sentence and, being centred by .window_popup_item, overflowed off BOTH
   sides at once, taking the titlebar buttons and the Agree/Disagree row
   with it. There was no way to reach them and no scrollbar to get there,
   because the overflow was on the layer rather than inside the window.

   max-width: 100% caps the flex item at the popup layer - which is exactly
   the box a maximized overlay fills - and the paragraphs then wrap inside
   it like any other block. So a dialog is never wider than a maximized
   window, whatever it contains.

   min-height / min-width: 0 are what actually let the inner .window_content
   scroll and wrap - a flex item's automatic minimum size is its content, so
   without them the element refuses to shrink below it and pushes the
   overflow straight back out past the clamps.
*/
.window_component_host_overlay[b-mtxjgbouud] {
    width: auto;
    height: auto;
    flex: 0 0 auto;
    max-height: 100%;
    min-height: 0;
    max-width: 100%;
    min-width: 0;
}

/*
   A maximized OVERLAY fills the popup layer, the same way a maximized root
   fills the workspace.

   This has to be a COMPOUND selector, not just .window_component_host_maximized.
   That rule and .window_component_host_overlay carry identical specificity
   (one class plus the scope attribute each) and _overlay is declared later,
   so _overlay silently won and reset flex/height straight back to content
   sizing. The framework was doing its part the whole time - SizeMode flipped
   to Maximized and the titlebar swapped Maximize for Normal - but the window
   never changed size, so the button looked dead.

   Note it keeps `flex: 0 0 auto` from the rule above rather than switching
   to `flex: 1 1 auto`. With flex-grow the widening is done by flex
   distribution, which is not interpolable - width snapped to full on the
   first frame while height eased over 260ms, so the overlay lurched wide
   and then unfolded. Leaving grow off makes the explicit width/height the
   things that change, and both axes then animate together (measured
   237->343->370->375 wide against 109->587->710->734 tall).
*/
.window_component_host_overlay.window_component_host_maximized[b-mtxjgbouud] {
    width: 100%;
    height: 100%;
    min-height: 0;
}

/*
   The "away" state: the window drops toward the drawer and folds away from
   the viewer. perspective() must come first in the list for the rotate to
   have any depth.

   The sign of rotateX matters and is easy to get backwards. CSS's +Y axis
   points DOWN, so rotateX(positive) swings the TOP edge away from the
   viewer and rotateX(negative) brings it toward them. Folding down into a
   drawer at the bottom of the screen is the former - like a lid closing
   away from you.

   translateY does the heavy lifting rather than the rotation. A small tilt
   on a tall window is a genuinely ambiguous depth cue: the eye can read it
   either as the top coming forward or the bottom receding, and it can flip
   between viewings of the very same animation. Leading with clear downward
   travel removes the ambiguity and leaves the fold as seasoning.

   All three states below list the SAME transform functions in the SAME
   order - that is what lets the browser interpolate componentwise instead
   of falling back to a matrix blend, which is what makes a fold look like
   a fold rather than a smear.
*/
.window_component_host_hidden[b-mtxjgbouud],
.window_component_host_minimized[b-mtxjgbouud] {
    display: none;
    opacity: 0;
    transform: perspective(1000px) translate(var(--host-exit-x), var(--host-exit-travel)) scale(0.9) rotateX(12deg);
}

/*
   A DOCKED panel retracts into its OWN edge, rather than falling toward the
   drawer like everything else.

   The default gesture is "fall toward the drawer at the bottom of the
   screen", which is right for a main-area window: the drawer is where it is
   going, and there is nothing between it and the bottom of the screen. For a
   docked panel it is wrong twice over, and the bottom dock shows both.

   It goes the WRONG WAY for three of the four edges. Minimizing a top dock
   sends it down into the content rather than up out of the way, and a side
   dock travels perpendicular to the direction its own space is closing.

   And on the bottom it does not travel FAR ENOUGH to leave. A bottom panel
   sits directly above the drawer, so 64px of fall lands it on top of the
   launcher button and it fades out there, over the chrome, instead of
   leaving the screen. With the main region simultaneously growing 242 -> 442
   into the space behind it, the panel reads as being shoved down by the row
   above rather than retracting under its own steam.

   100% is the panel's own size along the axis it docks on, which is exactly
   far enough to clear the edge: a 200px bottom dock at y 442-642 lands at
   642-842, past the bottom of a 642px workspace. Percentages are safe here
   because these are the panels that KEEP their box - the ones whose height
   collapses take a fixed pixel travel instead, from the rule below, which
   wins on specificity (two classes to one).

   `translate()` takes both axes in one function, so all three states still
   list the same functions in the same order - which is what keeps the
   browser interpolating componentwise instead of blending matrices.
*/
.window_component_host_region_top[b-mtxjgbouud] {
    --host-exit-travel: -100%;
}

.window_component_host_region_bottom[b-mtxjgbouud] {
    --host-exit-travel: 100%;
}

.window_component_host_region_left[b-mtxjgbouud] {
    --host-exit-x: -100%;
    --host-exit-travel: 0px;
}

.window_component_host_region_right[b-mtxjgbouud] {
    --host-exit-x: 100%;
    --host-exit-travel: 0px;
}

/*
   A window whose HEIGHT is animating cannot express its travel as a
   percentage, because translateY(%) resolves against the element's own
   height - so the distance it moves is the PRODUCT of two curves running in
   opposite directions, and a product of that shape peaks in the middle and
   returns to where it started.

   That is a window falling down and then rising back up before it vanishes,
   which is what it looked like. Measured on a 734px window, in pixels of
   travel across the exit:

     progress  0     0.15   0.3    0.5    0.7    0.85   1
     travel    0     13.1   21.6   25.7   21.6   13.1   0

   Both halves are individually correct and neither is the bug on its own.
   The collapse to `height: 0` is what lets the neighbours below slide up
   smoothly (see the tiled rule further down), and the percentage is what
   makes travel proportional for windows that keep their height. They simply
   cannot both apply to the same window.

   So the windows that collapse - tiled main windows, and windows sharing a
   dock - travel a fixed distance instead. Every other window keeps the
   percentage, because its height is stable for the whole animation and the
   proportional feel is the intended one.

   Set on the plain selectors rather than on the exit states, because the
   ENTRY animation has the same shape in reverse: @starting-style takes
   these windows in from `height: 0`, so a percentage dips them downward
   mid-entry and lifts them back. One override covers both directions.

   48px rather than something derived: it is comfortably more than the 25.7px
   the broken curve peaked at, so the fall reads as deliberate, and well
   under the ~90px a full-height window travels, so a short tiled window does
   not sail down over whatever is beneath it.

   The main-region test has to be the SAME one that decides whether the height
   collapses at all - a window with nobody below it keeps its box, so its
   height is stable and the percentage is correct for it, exactly as it is for
   a maximized window. If these two selectors ever drift apart, one of them is
   wrong: the fixed travel exists only to compensate for a collapsing height.
*/
.window_component_host_region_main:not(.window_component_host_maximized):has(~ .window_component_host_visible)[b-mtxjgbouud],
.window_component_host_dock_x:has(~ .window_component_host_visible)[b-mtxjgbouud],
.window_component_host_visible ~ .window_component_host_dock_x[b-mtxjgbouud],
.window_component_host_dock_y:has(~ .window_component_host_visible)[b-mtxjgbouud],
.window_component_host_visible ~ .window_component_host_dock_y[b-mtxjgbouud] {
    --host-exit-travel: 48px;
}

/*
   Which cell of the workspace grid this window occupies, taken straight
   from its framework Placement. The track sizes are WindowHost's business
   (see WindowHost.razor.css); this is only the claim on a cell.

   Two windows in the same region are stacked in the same cell, which is
   correct: the framework guarantees only one of them is visible at a time
   (activating one minimizes the other - see WindowManager.ActivateCore's
   sharesRegion check), and the loser is display:none.
*/
/*
   How a window sizes itself WITHIN its region. Which region that is, and
   where the region sits, belongs to the wrapper - see .window_region in
   WindowHost.razor.css.

   A DOCKED window fills its region: the dock's track is what defines its
   size along the docking axis, and a sidebar that only came down as far as
   its content would leave a gap under itself. Two windows docked to the
   same edge share it.
*/
.window_component_host_region_top[b-mtxjgbouud],
.window_component_host_region_bottom[b-mtxjgbouud],
.window_component_host_region_left[b-mtxjgbouud],
.window_component_host_region_right[b-mtxjgbouud] {
    flex: 1 1 auto;
}

/*
   A window in the MAIN area sizes to its own content and does not grow, so
   several normal windows tile down the region instead of one of them
   swallowing the space. `1` for flex-shrink lets them give way
   proportionally when their combined content exceeds the region rather than
   overflowing it.

   Note this is flex-GROW zero even when maximized - see the rule below.
*/
.window_component_host_region_main[b-mtxjgbouud] {
    flex: 0 1 auto;
}

/*
   A maximized main window fills its region through `height`, never through
   flex-grow, and the distinction is the whole maximize animation.

   flex-grow interpolates perfectly well - measured 100/100 -> 66.7/133.3 ->
   0/200 across a two-item column - but it only does so if it is in the
   transition list, and for a main window it deliberately is not. Letting
   grow do the filling would resize the window in a single frame, leaving
   the height transition underneath nothing to animate. Keeping grow at zero
   makes `height: auto -> 100%` the only thing changing.

   (An earlier version of this note claimed flex-grow was not interpolable
   at all. It is; the property that genuinely cannot be interpolated, and
   which broke this animation before, is align-self.)

   This overrides the generic .window_component_host_maximized rule further
   up, whose `flex: 1 1 auto` is for the OVERLAY layer, where the host is a
   flex item of .window_popup_item and there is no height animation to
   protect.
*/
.window_component_host_region_main.window_component_host_maximized[b-mtxjgbouud] {
    flex: 0 1 auto;
    height: 100%;
}

/*
   A main-layer window that is LEAVING - minimized, hidden by a workspace
   switch, or closing - is taken out of flow for the duration of its exit.

   Two things that are individually correct combine badly without this.
   `display ... allow-discrete` deliberately keeps a leaving window laid out
   for the whole animation so it has something to animate, and a laid-out
   flex item still claims space. Together they meant the outgoing window
   held its slot while the incoming one was pushed below it, then snapped up
   when the exit finished: opening Profile 2 over Profile showed the new
   window 108px down for ~260ms before it jumped into place.

   Which is why this applies only to windows that FILL their region - see the
   rule that exempts tiled windows below.

   The offsets are pinned explicitly rather than left to the static
   position. That distinction matters and is easy to get wrong: an
   absolutely positioned box with no offsets sits where it WOULD have been
   in flow, and that location still moves when the elements around it
   change. It only looked correct while the leaving window happened to be
   first in DOM order; closing one that was later, with an incoming window
   re-entering flow above it, dropped the leaving window by exactly the
   incoming one's height - it visibly fell before fading out.

   What it pins TO is its region wrapper, which carries position: relative
   for exactly this (see .window_region in WindowHost.razor.css). So inset 0
   here means "the top-left of my own region", and a docked panel animates
   out of its own dock rather than leaping to the corner of the workspace.

   This used to lean on a subtler rule - an absolutely positioned child of a
   grid container takes its grid AREA as containing block, provided it
   carries grid placement properties - which worked only while each window
   was itself a grid item. Once regions gained wrappers so their windows
   could tile, the windows stopped being grid items and that rule stopped
   applying; an ordinary positioned ancestor is both sturdier and easier to
   reason about.

   Scoped away from overlays deliberately: an overlay host is centred by
   .window_popup_item's flexbox, and taking it out of that flow would snap
   it to the corner mid-animation. Overlays are already positioned by their
   wrapper, so they never had this problem.
*/
.window_component_host_hidden:not(.window_component_host_overlay)[b-mtxjgbouud],
.window_component_host_minimized:not(.window_component_host_overlay)[b-mtxjgbouud],
.window_component_host_closing:not(.window_component_host_overlay)[b-mtxjgbouud] {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
}

/*
   A TILED window leaves from where it is standing, so it stays in flow.

   Taking a window out of flow and pinning it to the top of its region is
   right for one that FILLS that region - a docked panel, or a maximized main
   window - because the top of the region is where it already was, so the pin
   is invisible, and releasing the space immediately is what stops the window
   replacing it from being shoved down for the length of an animation.

   For a tiled window it is neither of those things. The lower of two tiled
   windows would jump to the top of the region and only then fade, which
   reads as the wrong window minimizing. Left in flow it folds away exactly
   where it sits.

   Staying in flow is also what makes the space it leaves behind closeable
   smoothly, which is what `height: 0` below is for. Because the window is
   still in flow, its box is what the windows after it are standing on, so
   collapsing that box slides them up - no measuring, no JS, and it rides
   the same 260ms curve as everything else. Out of flow there would be
   nothing left to animate: the space would already be gone on frame one and
   the survivors would jump.

   Order matters in the pairing. Opacity finishes at 200ms and height at
   260ms, so the window has faded out before the last of the collapse, and
   the squeeze is never seen against a still-visible frame. The content does
   not squash as it goes, because .window_container clips (see
   WindowFrame.razor.css) - the frame closes over its content like a blind
   rather than crushing it.

   Interpolating `auto -> 0` needs `interpolate-size: allow-keywords`, which
   is set at the top of this file.

   Only main-region NORMAL windows qualify, which is exactly the tiling case:
   docked windows fill their dock, and a maximized main window fills the main
   area, so for those the top of the region IS where they already are.

   ...and only when something is actually STANDING BELOW to take the space.
   That qualifier is what `:has(~ .window_component_host_visible)` is doing,
   and without it the collapse runs the animation backwards.

   Collapsing height keeps the top edge pinned by flow and pulls the BOTTOM
   edge up to meet it. When a window below is waiting for that room, the rise
   is the point - it is the survivor sliding up. When nothing is below, the
   window simply folds upward into its own top edge, and for a tall window
   that swamps everything else: measured on the Cookie window, the bottom
   edge travels 642px up against 48px of downward translate, so the window
   visibly leaves UPWARDS. Maximizing it first made it correct again, which
   is the tell - a maximized window takes the pinned branch above and keeps
   its box, so only the transform moves it.

   A window that fills its region is in exactly the position a maximized one
   is in, whether or not the framework calls it maximized. The Cookie window
   gets there through `max-height: 100%` clamping content that wants more
   room: 642.4px of content in a 642.4px region, tiled by class and
   region-filling in fact.

   `:only-child` is the obvious test and it is the WRONG one. Windows stay in
   the DOM for their whole lifetime and only toggle `display` (see the note at
   the top of this file), so a minimized window is still a sibling: the last
   visible window in a region reports :only-child = false and would go on
   collapsing. Measured with two windows open - the visible one had
   :only-child false and no later visible sibling, which is the distinction
   that matters. The subsequent-sibling test asks the question the collapse
   actually depends on: is there anyone after me who would move up.
*/
.window_component_host_region_main:not(.window_component_host_maximized):has(~ .window_component_host_visible).window_component_host_hidden[b-mtxjgbouud],
.window_component_host_region_main:not(.window_component_host_maximized):has(~ .window_component_host_visible).window_component_host_minimized[b-mtxjgbouud],
.window_component_host_region_main:not(.window_component_host_maximized):has(~ .window_component_host_visible).window_component_host_closing[b-mtxjgbouud] {
    position: static;
    height: 0;
    min-height: 0;
}

/*
   ...and the same collapse as the ENTRY state, so a tiled window coming back
   pushes its neighbours down as smoothly as leaving let them slide up.

   This does not come for free, which is easy to assume and wrong. Going from
   display: none to displayed, a property transitions only if
   @starting-style gives it a before-change value; anything omitted starts at
   its after-change value and therefore has no delta to animate. The block
   further up lists opacity and transform, so height was snapping from 0 to
   auto in a single frame and shoving the window below it down - the leaving
   half looked animated and the returning half did not.

   Restoring from the drawer is the obvious way to see it, but it is the same
   for any route back: un-maximizing a window returns the neighbours it
   evicted, and those were arriving the same way.

   Deliberately narrower than the block above, which applies to every
   visible host. A docked window or a maximized one growing from zero height
   on entry would be a different animation from the fold they have now, and
   the tiling case is the only one where anything is standing underneath
   waiting for the room.

   Carries the same "is anyone below me" test as the exit, and for the same
   reason: growing from zero height only means something when there is a
   neighbour to push down. A window arriving on its own would instead unfold
   from its own top edge - the entry equivalent of leaving upwards - so it
   arrives at full height with just the fold, exactly as a maximized window
   does. Both directions have to agree, or a window would leave one way and
   come back another.
*/
@starting-style {
    .window_component_host_region_main:not(.window_component_host_maximized):has(~ .window_component_host_visible).window_component_host_visible[b-mtxjgbouud] {
        height: 0;
    }

    /* Windows sharing a dock arrive the same way they leave - claiming their
       grow share gradually, so the neighbour already in the dock gives ground
       smoothly instead of halving in one frame.

       Same both-directions test as the exit, and for the same reason: a dock
       whose other window is merely MINIMIZED is not being shared, and a window
       arriving into it alone would otherwise unfold from zero height with
       nothing to give ground. Entry and exit have to agree, or a panel leaves
       one way and comes back another. */
    .window_component_host_dock_x:has(~ .window_component_host_visible).window_component_host_visible[b-mtxjgbouud],
    .window_component_host_visible ~ .window_component_host_dock_x.window_component_host_visible[b-mtxjgbouud],
    .window_component_host_dock_y:has(~ .window_component_host_visible).window_component_host_visible[b-mtxjgbouud],
    .window_component_host_visible ~ .window_component_host_dock_y.window_component_host_visible[b-mtxjgbouud] {
        flex-grow: 0;
        height: 0;
    }
}

/*
   ...except that a leaving DOCKED window cannot use "100% of my region",
   because its region is disappearing at the same moment. WindowHost drops a
   dock from the track sizes as soon as its window stops being visible, so
   the track is already animating to 0px on the first frame of the exit.

   A percentage of a collapsing track would be bad enough; what actually
   happened was worse. The frame keeps overflow: visible for its drop
   shadow, so a zero-width containing block does not clip it - it paints at
   its intrinsic content width. The panel appeared to snap out to full size
   and fold away from there, reading as "it undocked itself, then closed".

   --host-dock-size is written inline by WindowComponentHost from the same
   value WindowHost used for the track, so the panel keeps its real size on
   the way out while the space closes up underneath it. The two happen
   together rather than one after the other.

   Same specificity as the rule above (two classes either way), so these
   must stay AFTER it to win.
*/
/*
   max-height is released here, and that is not incidental.

   The viewport clamp further up is what stops a leaving window ballooning
   to its intrinsic content height mid-exit. But it is `max-height: 100%`,
   and 100% now resolves against a grid area that is collapsing to zero at
   exactly the same moment - so a leaving bottom dock was clamped to
   max-height: 0 and vanished on the first frame instead of folding away
   (measured: height 170px -> 0px the instant it started closing).

   Setting an explicit height from --host-dock-size does the clamp's job
   already, and does it better: the size is known rather than inherited from
   a track that is disappearing.
*/
/*
   "Does this dock go away with me?" - and holding its own box is the DEFAULT
   answer, with sharing as the exception below.

   A dock whose last visible window is leaving IS that window: the track
   collapses underneath it, so it has to carry its own size out. A dock that
   still holds a visible window survives, and a leaver hands its share over
   instead.

   This used to ask `:only-child`, and that was wrong for the same reason it
   was wrong in the main region: windows stay in the DOM for their whole
   lifetime and only toggle `display`, so a MINIMIZED window is still a child.
   A dock holding one visible window plus one minimized one therefore failed
   :only-child and took the sharing branch - collapsing height to 0 with
   nothing to hand the space to, which folds the panel upward into its own top
   edge. Measured on a synthetic bottom dock: sole leaver kept height 170px,
   the same leaver with one minimized sibling collapsed to 0px.

   It also lost the two declarations below, which is why the failure was worse
   here than in the main region. Releasing `max-height` and pinning the size
   from --host-dock-size are what stop a leaving panel being clamped by a
   track that is collapsing to zero at the same moment - see the note above.

   The test is inverted rather than negated because "is there another VISIBLE
   window in this dock" has to look BOTH WAYS, and CSS can only look forward:
   `:has(~ .visible)` finds one after, and `.visible ~ ...` finds one before.
   Writing the negation would need both of those inside :not(). Making the
   safe answer the default and letting the two positive cases override it says
   the same thing without the gymnastics - and it fails safe, since a window
   that matches neither keeps its box rather than collapsing into nothing.

   Note the main region asks a NARROWER question - only later siblings, since
   those are the ones that slide up into the space. Here any surviving window
   keeps the track alive, wherever it sits in DOM order.
*/
/*
   The cap is REPLACED here, never released, and that distinction is the
   whole of a bug that made a docked panel leave from off the top of the
   screen.

   A docked window rests at `height: auto` held down by `max-height: 100%` -
   the content is taller than the dock and the clamp is what hides that. The
   exit sets an explicit height, so the height transition runs
   `auto -> <length>`, and `interpolate-size: allow-keywords` (set at the top
   of this file) resolves that `auto` to the INTRINSIC CONTENT height rather
   than to the clamped height it was displaying. Release the clamp in the
   same frame and there is nothing left to hide it.

   Measured on the Cookie window docked to the bottom, one frame after
   pressing minimize: height 200px -> 1309.2px, top 752 -> -357. Anchored
   `bottom: 0`, the panel grows UPWARDS off the top of the viewport and then
   shrinks back down over 260ms - which reads exactly as "it minimized from
   the top of the screen".

   `max-height: none` was right about what it was fixing and wrong about how.
   The clamp could not stay `100%`, because 100% resolves against a grid
   track that is collapsing to zero at the same moment, which clamped a
   leaving bottom dock to zero on the first frame (measured 170px -> 0px).
   But the answer to "this percentage resolves against the wrong thing" is a
   value that resolves against the right thing, not no value at all. Capping
   at the same size the height is heading for makes the bogus start value
   unobservable: the used height is that size on every frame.

   For dock_x the cap stays `100%` deliberately - a left or right dock
   collapses along WIDTH, so its region's height is not going anywhere and
   100% still means what it says.
*/
.window_component_host_dock_x.window_component_host_hidden[b-mtxjgbouud],
.window_component_host_dock_x.window_component_host_minimized[b-mtxjgbouud],
.window_component_host_dock_x.window_component_host_closing[b-mtxjgbouud] {
    width: var(--host-dock-size);
    height: 100%;
    max-height: 100%;
}

.window_component_host_dock_y.window_component_host_hidden[b-mtxjgbouud],
.window_component_host_dock_y.window_component_host_minimized[b-mtxjgbouud],
.window_component_host_dock_y.window_component_host_closing[b-mtxjgbouud] {
    width: 100%;
    height: var(--host-dock-size);
    max-height: var(--host-dock-size);
}

/*
   Windows SHARING a dock leave the way tiled main windows do: in flow,
   giving their space back as they go, so whatever is left grows into it
   rather than snapping.

   The mechanism differs from the main region though, and it has to. Tiled
   main windows are content-sized, so collapsing `height` is enough. Docked
   windows split their dock with flex-grow, and a grow share is not something
   `height` can give back - set height to 0 alone and grow: 1 simply claims
   the space again. So the share itself is animated, grow 1 -> 0, with height
   collapsing alongside it to take the basis down too.

   That works because flex-grow interpolates (measured), and it is why
   flex-grow is in the transition list at the top of this file.

   "Sharing" means another window in this dock is still VISIBLE - not merely
   still in the DOM, which a minimized one always is. Each axis therefore
   appears twice: once finding a visible sibling after it, once finding one
   before it. Both carry three classes against the base rule's two, so they
   win on specificity as well as on source order.
*/
.window_component_host_dock_x:has(~ .window_component_host_visible).window_component_host_hidden[b-mtxjgbouud],
.window_component_host_dock_x:has(~ .window_component_host_visible).window_component_host_minimized[b-mtxjgbouud],
.window_component_host_dock_x:has(~ .window_component_host_visible).window_component_host_closing[b-mtxjgbouud],
.window_component_host_visible ~ .window_component_host_dock_x.window_component_host_hidden[b-mtxjgbouud],
.window_component_host_visible ~ .window_component_host_dock_x.window_component_host_minimized[b-mtxjgbouud],
.window_component_host_visible ~ .window_component_host_dock_x.window_component_host_closing[b-mtxjgbouud],
.window_component_host_dock_y:has(~ .window_component_host_visible).window_component_host_hidden[b-mtxjgbouud],
.window_component_host_dock_y:has(~ .window_component_host_visible).window_component_host_minimized[b-mtxjgbouud],
.window_component_host_dock_y:has(~ .window_component_host_visible).window_component_host_closing[b-mtxjgbouud],
.window_component_host_visible ~ .window_component_host_dock_y.window_component_host_hidden[b-mtxjgbouud],
.window_component_host_visible ~ .window_component_host_dock_y.window_component_host_minimized[b-mtxjgbouud],
.window_component_host_visible ~ .window_component_host_dock_y.window_component_host_closing[b-mtxjgbouud] {
    position: static;
    flex-grow: 0;
    height: 0;
    min-height: 0;
    max-height: none;
}

/*
   Which EDGE a leaving panel is pinned to has to match the edge it docks
   on, because its grid area does not merely shrink - it moves.

   Pinning everything to top/left is right for the left and top docks: their
   tracks collapse toward the origin, so the area stays put at x=0 (or y=0)
   and only loses size. The right and bottom docks collapse the other way.
   Their zero-width area ends up flush against the far edge of the
   workspace, so a panel pinned to its left would start where the track
   ended and slide out past the viewport - measured at x=1293 on a 1280px
   screen, the panel visibly shooting off to the right as it folded.

   Anchoring to the far edge instead keeps the panel exactly where it was
   while the space closes up behind it.
*/
.window_component_host_region_right.window_component_host_hidden[b-mtxjgbouud],
.window_component_host_region_right.window_component_host_minimized[b-mtxjgbouud],
.window_component_host_region_right.window_component_host_closing[b-mtxjgbouud] {
    left: auto;
    right: 0;
}

.window_component_host_region_bottom.window_component_host_hidden[b-mtxjgbouud],
.window_component_host_region_bottom.window_component_host_minimized[b-mtxjgbouud],
.window_component_host_region_bottom.window_component_host_closing[b-mtxjgbouud] {
    top: auto;
    bottom: 0;
}

/*
   Stacked mobile layout: a left/right dock is a full-width row there, so its
   size is a height rather than a width and the axis rules swap over. Mirrors
   the template swap in WindowHost.razor.css.
*/
@media (max-width: 767px) {
    .window_component_host_dock_x.window_component_host_hidden[b-mtxjgbouud],
    .window_component_host_dock_x.window_component_host_minimized[b-mtxjgbouud],
    .window_component_host_dock_x.window_component_host_closing[b-mtxjgbouud] {
        width: 100%;
        height: var(--host-dock-size);
    }
}

/*
   `transform: none` at rest, NOT an identity 3D transform.

   Writing the resting state as perspective(1000px) translateY(0) scale(1)
   rotateX(0deg) looks harmless - it is visually identity - but it computes
   to matrix3d(...) rather than none, which means every idle window
   permanently owns a 3D-transformed composited layer. Descendant scrollers
   inside a 3D-transformed subtree lose fast composited scrolling, and text
   in such a layer loses subpixel antialiasing and has to be re-rasterized.
   On the Terms window (5990px of text in a 679px viewport) that is the
   difference between smooth and gritty scrolling.

   Interpolating none -> the away transform still animates: the browser
   substitutes identity values. Measured at 50%, scale/rotate/translate are
   bit-identical to the old explicit-identity version; only the perspective
   component differs, because the identity for perspective() is
   perspective(infinity), so depth ramps in rather than being constant. The
   fold reads a touch flatter early and lands in exactly the same place.
*/
.window_component_host_visible[b-mtxjgbouud] {
    display: flex;
    opacity: 1;
    transform: none;
}

/* Entry state, used when a window opens or is restored. */
@starting-style {
    .window_component_host_visible[b-mtxjgbouud] {
        opacity: 0;
        transform: perspective(1000px) translate(var(--host-exit-x), var(--host-exit-travel)) scale(0.9) rotateX(12deg);
    }
}

/*
   Closing reuses the same away state as minimizing, so a window leaves the
   same way whether it is being put down or thrown out.

   Unlike minimize, this cannot be done with `display` alone: closing
   destroys the element, and a destroyed element has nothing to transition.
   The window is kept alive for exactly this animation by the two-phase
   close in WindowManager - it sits in WindowLifecycleState.Closing until
   WindowComponentHost reports WindowUiPhase.ExitComplete, which is fired
   from JS when these animations settle.

   Needs to beat .window_component_host_visible, which is still present on a
   closing window (it is still on screen), hence the compound selector.
*/
.window_component_host_visible.window_component_host_closing[b-mtxjgbouud] {
    opacity: 0;
    transform: perspective(1000px) translate(var(--host-exit-x), var(--host-exit-travel)) scale(0.9) rotateX(12deg);
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    .window_component_host_visible.window_component_host_closing[b-mtxjgbouud] {
        transform: none;
    }
}

/*
   Respect the OS "reduce motion" setting. The window still appears and
   disappears, it just stops moving - which matters more than usual here,
   since scale+rotate animations are a common migraine and vestibular
   trigger.
*/
@media (prefers-reduced-motion: reduce) {
    .window_component_host[b-mtxjgbouud] {
        transition: opacity 120ms ease, display 120ms allow-discrete;
    }

    .window_component_host_hidden[b-mtxjgbouud],
    .window_component_host_minimized[b-mtxjgbouud],
    .window_component_host_visible[b-mtxjgbouud] {
        transform: none;
    }

    @starting-style {
        .window_component_host_visible[b-mtxjgbouud] {
            transform: none;
        }
    }
}
/* /Components/Windows/TermsAgreement.razor.rz.scp.css */
/*
   The dialog's own layout. Everything here is about ONE thing: the buttons
   staying put while the terms scroll.

   A consent dialog whose Agree button is below several hundred lines of legal
   text is a consent control the user has to hunt for, and on a phone it is one
   they may never find at all - Section's action row sits after the content, so
   without a cap on the content the row is simply off the bottom of the window.
*/

.terms_agreement_intro[b-lpofu073kh] {
    margin-bottom: 10px;
    font-weight: bold;
}

/*
   max-height rather than height, so a short document does not leave a box of
   empty space under it. The terms are long enough today that this is always
   the cap; that is not a reason to hardcode the height, because the component
   inside is shared with the Terms window and may legitimately be edited.

   The unit is `vh` rather than a percentage: this is an overlay window, sized
   by its content rather than by a definite parent height, so a percentage
   would resolve against nothing and the cap would silently do nothing. Same
   trap Section's `Fill` documents from the other side.
*/
.terms_agreement_scroller[b-lpofu073kh] {
    max-height: 45vh;
    overflow-y: auto;
    overflow-x: hidden;
    /* Room for the scrollbar, so the text does not sit under it. */
    padding-right: 12px;
    border: 1px solid rgba(0, 0, 0, 0.25);
    border-radius: 6px;
    background-color: rgba(0, 0, 0, 0.15);
    /* Matches the drawer's minimized rows - thin, and visible enough to say
       there is more to read, which on a consent dialog is the point. */
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 204, 102, 0.5) transparent;
}

    /*
       tabindex="0" in the markup makes this focusable, which is what lets a
       keyboard user scroll it with the arrow keys - a scroll container is not
       reachable by keyboard otherwise, so the terms would be readable only
       with a mouse. The outline is the visible half of that and must not be
       removed.
    */
    .terms_agreement_scroller:focus-visible[b-lpofu073kh] {
        outline: 2px solid rgba(255, 204, 102, 0.9);
        outline-offset: 2px;
    }

    .terms_agreement_scroller[b-lpofu073kh]::-webkit-scrollbar {
        width: 8px;
    }

    .terms_agreement_scroller[b-lpofu073kh]::-webkit-scrollbar-thumb {
        border-radius: 999px;
        background: rgba(255, 204, 102, 0.5);
    }
