/* /Components/Assessment/AskEverythingSwitchOffListing.razor.rz.scp.css */
/*
    Where the answers that are about to go are drawn, and nothing about what they say.

    The dialog's explanation and the list of answers used to run together as one block of body text,
    so the clinician read a reason and a list of removals in the same voice. The list is set apart
    instead: a lightly tinted, bordered panel the body prints above, outside it.

    The tint and the border are palette variables rather than colours — --mud-palette-background-gray
    is a step off --mud-palette-surface in both of MudBlazor's own palettes, and
    --mud-palette-lines-default is the same divider colour the rest of the dialog's rules use — so
    dark mode follows without a second rule here.

    The height is capped and the panel scrolls within itself, because a session under Ask Everything
    can list dozens of answers and the one thing the clinician must not lose while they read them is
    the dialog's title, its body copy and its two buttons: a panel that grows pushes the press off
    the screen. 50vh leaves all three in view down to a phone's height.

    That any of this *takes effect* — that the panel scrolls, that the answers align, that a Comment
    indents — is the computed-style tier's fact against a real viewport (#1019, ADR 0037). Nothing
    reads this file to prove it.
*/
.switch-off-panel[b-uzx1fqy8ek] {
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: var(--mud-default-borderradius);
    background-color: var(--mud-palette-background-gray);
    padding: 12px 16px;
    max-height: 50vh;
    overflow-y: auto;
}

/*
    A line's question and its Initial answer on one row, the answer pushed to the right edge so the
    answers read down one column and the clinician can scan what goes without reading every question
    again. The question takes the rest of the row and is the one that wraps when the row is short of
    room, so a short answer stays on one line however long its question. The answer never shrinks
    below its one-line width until it reaches half the row; a free-text answer longer than that wraps
    inside that half, its lines set right so they still end hard against the column's edge.

    ::deep on both because MudText renders those elements, not this component — the row div is this
    component's markup and carries the scope; its children are MudBlazor's.
*/
.switch-off-question-row[b-uzx1fqy8ek] {
    display: flex;
    align-items: baseline;
    gap: 16px;
}

.switch-off-question-row[b-uzx1fqy8ek]  .switch-off-question {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: break-word;
}

.switch-off-question-row[b-uzx1fqy8ek]  .switch-off-answer {
    flex-shrink: 0;
    max-width: 50%;
    overflow-wrap: break-word;
    text-align: right;
}

/*
    The clinician's own words, set apart from the answer above them: indented past the question so
    the quote reads as hanging off its line rather than as another line of its own, and italic so a
    sentence they typed is never mistaken for an answer the service worded. Both Occasions' Comments
    take the same treatment, because both are the clinician's.

    ::deep for the same reason as above: the Comment is a MudText.
*/
.switch-off-line[b-uzx1fqy8ek]  .switch-off-comment {
    font-style: italic;
    margin-left: 24px;
}
/* /Components/Assessment/AssessmentFormComponent.razor.rz.scp.css */
/* The option is a button, so the keyboard gets a real control; this block is what keeps it drawn
   as the card it has always been. A button arrives with chrome from two directions. The browser pins
   its own settings on a form control — a background and a padding, a border, the control font, a
   centred `text-align`, and `letter-spacing` / `word-spacing: normal`, so none of the page's
   typography reaches it — and MudBlazor 8.15.0's bare `button` reset puts `display: inline-flex`
   with `justify-content: center` on top of that, which draws a button's content in the middle of it.

   Three of these lines are the only thing holding that chrome off today. `width: 100%` fills the
   card, so the whole card is still what a clinician clicks. `justify-content: flex-start` undoes
   MudBlazor's `justify-content: center`: without it the tick box and the chip drift into the centre
   of a control that now spans the card. And `letter-spacing: inherit` carries the page's own
   spacing across the form-control boundary — the one text setting MudBlazor's
   `input, button, select, optgroup, textarea` rule does not re-inherit, since that rule re-inherits
   the family, the size and the line height and stops there. It matters as much as the geometry: the
   chip is sized to its content, and at the theme's `.01071em` on the body's 14px — 0.14994px a
   character — a label falling back to the pinned `normal` draws a visibly narrower chip than the
   identical one elsewhere on the page.

   The rest resolve to what the cascade already gives this element, and are kept as this component's
   own guard rather than as working overrides. MudBlazor 8.15.0's bare `button` reset itself declares
   `padding: 0`, `border: 0`, `background-color: rgba(0,0,0,0)`, `color: inherit` and
   `cursor: pointer`; its font rule covers the family, the size and the line height;
   `button, select { text-transform: none }` and a body whose own `text-transform` and `word-spacing`
   resolve to `none` and `0` leave those two nothing to change; and `text-align` moves nothing here,
   because the tick box and the chip are flex items that `justify-content` places. Each is one
   MudBlazor release away from being load-bearing, and what lies under all of them is the browser's
   own button.

   What holds the resolved cascade to the facts a clinician reads is `BodyPartOptionCardTests` in the
   computed-style tier (ADR 0037): the control drawn at the card's full width and painting no surface
   of its own, the theme's letter-spacing reaching the chip through it, and its content starting at
   the card's leading edge. */
button[role="checkbox"][b-tv7va78cq5] {
    width: 100%;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    letter-spacing: inherit;
    word-spacing: inherit;
    text-align: inherit;
    text-transform: inherit;
    justify-content: flex-start;
    cursor: pointer;
}

/* The option a clinician is about to tick has to be visible while they are still on it, and the
   cascade this button sits in takes the browser's own ring off it twice over: MudBlazor 8.15.0's
   bare `button` reset declares `outline: 0`, and its `button:focus { outline: none }` declares it
   again the moment the option is focused. So the ring is declared here, for the option alone, in the
   palette's primary so it reads as this app's rather than the browser's — and the scoped
   `button[role="checkbox"]:focus-visible` selector outranks both of those on specificity. An outline
   is painted outside the box and takes part in no layout, so the card is drawn exactly where it was.

   The ring on an option a clinician is actually on is the manual BROWSER-PASS's fact, and only
   because focus is interaction: the computed-style tier loads a frozen static render and presses
   nothing, so there is no focused option in it to measure. That is the whole of what is left to the
   manual pass here — the geometry this button is drawn at is held automatically, by
   `BodyPartOptionCardTests`. */
button[role="checkbox"]:focus-visible[b-tv7va78cq5] {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

/*
    Where a Follow-up checklist row's checkbox sits against its own diagnosis name, and nothing
    else about the start form.

    MudBlazor's own .mud-checkbox is `align-items: center`, and the box and the <p> carrying
    "<rank>. <name>" are both children of it. On this card - half of a MaxWidth.Small dialog -
    most names wrap to two or three lines, so centring put the box beside the middle line and
    left the rank number on the line above it, reading as the tail of the row before (#1031).
    Top-aligning puts the box beside the label's *first* line, which is the line the rank number
    is on.

    ::deep because the element is MudCheckBox's, not this component's: the row div carries the
    scope and the <label class="mud-checkbox"> inside it does not. QuestionnaireComponent.razor.css
    overrides .mud-divider the same way. No !important is wanted - the compiled selector carries
    this component's scope attribute beside the row's own, so it outweighs MudBlazor's bare class
    whichever sheet is linked first. Whether it actually takes effect is a browser's answer and
    not a stylesheet read's (ADR 0037): FollowUpChecklistRowLayoutTests is where it is asked.
*/
[data-follow-up-row][b-tv7va78cq5]  .mud-checkbox {
    align-items: flex-start;
}

/*
    How much of the clinician's screen one body part's Follow-up checklist may take before it
    scrolls within itself.

    Every rated diagnosis of the Previous assessment is a row, because a diagnosis that visit
    selected is a Follow-up diagnosis whatever it now rates (ADR 0046) - and a shipped pack rates
    twenty-odd of them. Inlined whole, the card ran 1280-1600px tall against the 48px of an ordinary
    body-part card, the dialog's content stood at nearly five screens, and the one control the
    clinician has to reach - Create Assessment Session - sat thousands of pixels below the fold
    (#1011's browser pass). The rows are all still there and all still tickable; what is bounded is
    how much of the screen they may take at once.

    A share of the viewport rather than a row count or a rem height, because the quantity at issue
    is exactly that: how much of the screen one body part may claim while the form is still a form.
    Under a third, so that a second body part's card and the submit are on the screen with it.

    Whether the cap is in effect, and whether the diagnoses below it are still reachable, is a
    browser's answer and not a stylesheet read's (ADR 0037): StartFormFollowUpLayoutTests is where both are
    asked.
*/
[data-follow-up-checklist][b-tv7va78cq5] {
    max-height: 30vh;
    overflow-y: auto;
}

/*
    Where the form's two buttons sit while the body-part cards scroll past them.

    The picker offers every body part that has a Content Pack, two sides each, and a card carrying a
    Follow-up is a block rather than a chip - so this form is taller than the MaxWidth.Small dialog
    either of its hosts opens it in, whatever the clinician has ticked. Left in flow, Create
    Assessment Session was simply off the bottom of the screen until the clinician found the
    scrollbar (#1011's browser pass).

    Sticky rather than fixed, so the row scrolls with the form on a screen the form does fit, and so
    it never leaves the dialog it belongs to. The background is the surface the dialog paints,
    because a transparent pinned row would have the cards scrolling through the buttons; the divider
    above it is what says the row is pinned rather than floating over a card by accident.

    What the browser does with it - the row on screen while the cards are not, and nothing showing
    through it - is StartFormFollowUpLayoutTests' subject (ADR 0037).
*/
[data-start-form-actions][b-tv7va78cq5] {
    position: sticky;
    bottom: 0;
    z-index: 1;
    padding-block: 8px;
    border-top: 1px solid var(--mud-palette-divider);
    background-color: var(--mud-palette-surface);
}

/*
    And the strip under it. A sticky row is held inside the scrollport less the scroll container's
    own padding, so the dialog's bottom padding is a band below the pinned row where a card still
    scrolls past in view - half a body part's chip under the buttons, which reads as a rendering
    fault rather than as a form. This paints that band the same surface, clipped by the scroller
    itself at whatever the padding turns out to be, rather than pinning the row to a padding figure
    this stylesheet would then have to keep in step with MudBlazor's.
*/
[data-start-form-actions][b-tv7va78cq5]::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    top: 100%;
    height: 1rem;
    background-color: inherit;
}
/* /Components/Assessment/ExercisesComponent.razor.rz.scp.css */
/*
    What the disclosure on an exercise row looks like, and nothing about what it says or does.

    The chevron and the name are one button, so the button has to read as the name it is: no border,
    no background, the table's own font and colour, and the chevron beside the text rather than above
    it. Nothing here decides whether a row is open — aria-expanded and the rendered region are that —
    so no test reads this file as source (ADR 0037). What the phone-width block at the foot of it
    *does* is another matter, and is asserted through the running cascade by
    PhoneWidthExerciseInstructionsTests.
*/
.exercise-instructions-toggle[b-s898exacj8] {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0;
    border: none;
    background: none;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

/*
    The revealed text is a paragraph of clinical content the clinician reads, not a row of figures, so
    it is given the body wrapping rather than the table's dense cell treatment.
*/
.exercise-instructions[b-s898exacj8] {
    white-space: normal;
}

/*
    Phone width, where MudBlazor takes the row apart.

    The query mirrors MudBlazor's own Xs block — @media (max-width:600px) in MudBlazor.min.css — so
    these rules come into effect exactly when the ones they answer do. Under it
    .mud-xs-table .mud-table-cell goes display:flex, which takes each of the row's five labelled cells
    out of table layout and stacks them. The disclosure cell is a raw <td> carrying none of
    MudBlazor's classes, so it inherits none of the stacked cells' inset; and the name button, being
    the table's own font and nothing else, is a line of text tall.

    Only the component's own literal markup can be reached from here: the td, the toggle and the
    paragraph carry the Razor scope attribute, while the MudTd-rendered .mud-table-cells do not, so a
    rule naming those would be silently dead and none is written.

    What these two declarations do is asserted by PhoneWidthExerciseInstructionsTests, in the
    computed-style tier and through the running cascade (ADR 0037) — which is also why there is no
    third declaration here: a display:block on the cell, to take the one remaining table-cell out of
    table layout as well, measured identically in Chromium at this width with the longest text the
    pack ships, so it was left out rather than shipped inert.
*/
@media (max-width: 600px) {
    .exercise-instructions-cell[b-s898exacj8] {
        /* The dense stacked cells' own inset, which a raw td gets none of, so the revealed text
           lines up with the figures it belongs to instead of running to the card's edge. */
        padding: 6px 16px;
    }

    .exercise-instructions-toggle[b-s898exacj8] {
        /* The name is a thumb's target here, not a line of text. The minimum is declared as a custom
           property so the tier reads it back out of the cascade rather than restating it, which is
           what keeps a pixel number out of the test (ADR 0037). */
        --physiodx-exercise-press-target: 44px;

        min-height: var(--physiodx-exercise-press-target);
    }
}
/* /Components/Assessment/QuestionnaireComponent.razor.rz.scp.css */
/*
    The Category list's placement, and nothing about what it says.

    Below md the list is full-width above the rows and scrolls away with the page, so a tablet in
    portrait spends its width on a row's question, answer, N/A box and Comment — no rule here
    applies at that width, which is what makes the plain grid the whole story there.

    At md and up (MudBlazor's md, 960px — the same breakpoint as the MudItem md attributes beside
    it) the list is a pinned left column: it stays on screen while a long Category such as Range of
    motion scrolls, so any Category is one click away from the last row. Where it pins is the
    page's answer, not this component's: --physiodx-under-banner is set by the session page, which
    is what knows whether a Terminated banner sits in the sticky stack above (ADR 0036).

    Back and Next are not styled here at all: they sit after the last row in normal flow and scroll
    with it, at every width.
*/
@media (min-width: 960px) {
    .category-list-pane[b-z14fkyq0tv] {
        position: sticky;
        top: var(--physiodx-under-banner, var(--mud-appbar-height));

        /* A short viewport must still reach the last Category, so the pane scrolls within itself
           rather than running off the bottom of the screen. */
        max-height: calc(100vh - var(--physiodx-under-banner, var(--mud-appbar-height)) - 16px);
        overflow-y: auto;

        /* Below the nav header and the Terminated banner, which stack at 10: the pinned list must
           never paint over the notice that says why the session is read-only. */
        z-index: 1;
    }
}

/*
    One Category entry's two lines: the name and its count across the first, and — on a Follow-up —
    the to-confirm count under them (ADR 0046). A column rather than a row, filling the entry's
    width so the count still sits at the far end of the first line, and with min-width 0 so a long
    Category name shrinks the first line rather than widening the whole entry past its pane.

    It says nothing about whether the second line is there: that is CategoryList.ToConfirmLabelOf's
    answer, and a full assessment's entry renders exactly the one line it always did.
*/
.category-entry-lines[b-z14fkyq0tv] {
    display: flex;
    flex-direction: column;
    flex-grow: 1;
    min-width: 0;
}

/*
    The to-confirm line, secondary to the count above it: the clinician reads how far through the
    Category they are first, and what is left to confirm under it. Nowrap, because the clause is one
    phrase and a line break inside "2 to confirm" would read as two facts.
*/
.category-entry-to-confirm[b-z14fkyq0tv] {
    font-size: var(--mud-typography-caption-size, 0.75rem);
    line-height: 1.3;
    opacity: 0.75;
    white-space: nowrap;
}

/*
    Where opening a Category lands. The rows' top is scrolled to by the browser seam, and this is
    what keeps that landing clear of the sticky stack above it at every width — the fallback covers
    a rendering of the questionnaire outside the session page's context.
*/
.category-top[b-z14fkyq0tv] {
    scroll-margin-top: calc(var(--physiodx-under-banner, var(--mud-appbar-height)) + 8px);
}

/*
    The Asked-anyway divider's two rules, and why they need a declaration at all.

    MudBlazor's own divider is `width: 100%; flex-grow: 1; flex-shrink: 0`, which is a sound default
    for a divider that is the only thing on its line and a broken one for two of them either side of
    a label: each rule's base size is the whole row, neither may shrink, and the row overflows to
    twice its container. The label is then pushed clean past the card's edge and clipped off the
    screen, leaving a gap where the divider should read (#930's markup, found in #932's browser pass).

    The shared rule below is written against both dividers, but only the trailing one keeps what it
    says: its base size is nothing and it is free to grow, and `width: auto` is the load-bearing half
    of that — a flex item's base size comes from its width before flex-basis: 0 can mean anything, and
    MudBlazor's 100% is what has to go.

    The two rules do not share the row evenly. The label reads at the left, where a clinician
    scanning the row titles down the left of the questionnaire already is, so the lead rule is a
    fixed 16px stub that neither grows nor shrinks — just enough to read as a divider's lead rather
    than as clutter — and the trailing rule bases at nothing and grows into everything the stub and
    the label leave. :first-child adds a pseudo-class, so the stub wins over the shared rule above on
    specificity whichever order they are written in, overriding its flex and leaving its `width: auto`
    behind unused: the stub's flex-basis of 16px settles its size outright, so width plays no part in
    it.

    Both sizes live here and not in a utility class on the markup, because MudBlazor's own
    `flex-grow-1` is `flex-grow: 1 !important` and no scoped rule can beat it. The markup carries no
    flex utility on either divider; this stylesheet is the single owner of the row's flex story.

    ::deep because the rules are a child component's elements — the wrapper is this component's
    markup and carries the scope, the <hr>s inside it do not.
*/
[data-asked-anyway-divider][b-z14fkyq0tv]  .mud-divider {
    flex: 1 1 0;
    width: auto;
}

[data-asked-anyway-divider][b-z14fkyq0tv]  .mud-divider:first-child {
    flex: 0 0 16px;
}
/* /Components/Assessment/StepBar.razor.rz.scp.css */
/* One row, always: the Steps are a sequence, and a sequence broken over two lines stops reading as
   one. Every Step takes an equal share of the row with its number above its name, and a long name
   wraps inside its own share, so all ten fit the row the drawer leaves and the Step the clinician
   is on is never the one scrolled out of sight. Only below the tight tier's minimum share does the
   row scroll sideways. */
.step-bar[b-tc76nw4j81] {
    display: flex;
    overflow-x: auto;

    /* The row is what the shares are measured against, so the tight tier below can ask how much
       room the row actually has rather than how wide the window is. */
    container-type: inline-size;
}

.step-bar__step[b-tc76nw4j81] {
    flex: 1 1 0;
    min-width: 88px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    gap: 6px;
    padding: 12px 6px;
    border: 0;
    border-bottom: 3px solid transparent;
    background: transparent;
    font: inherit;
    font-size: 0.875rem;
    font-weight: 500;
    line-height: 1.25;
    text-align: center;
    color: var(--mud-palette-text-secondary);
    cursor: pointer;
}

.step-bar__step:hover:not(:disabled)[b-tc76nw4j81] {
    background: var(--mud-palette-action-default-hover);
}

.step-bar__step:focus-visible[b-tc76nw4j81] {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: -2px;
}

.step-bar__number[b-tc76nw4j81] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 24px;
    height: 24px;
    border-radius: 12px;
    background: var(--mud-palette-text-secondary);
    color: var(--mud-palette-surface);
    font-size: 0.75rem;
}

/* Ten shares at the floor above need 880px, and the row does not always have it: at 960px and
   wider the docked navigation drawer takes 240px of the page, so there is *less* room for the row
   at 960px than at 959px — a viewport query would promise a fit the drawer had already taken away.
   The threshold is the base floor taken once per Step, so it rose from 792px to 880px when Other
   Recommendations became the ninth of ten Steps (#795): without that, a row between 793px and 879px
   would fall back to the base floor and scroll.
   Measured again in a browser on a Shoulder v1 session for #795, with ten Steps on the bar: the row
   is 1582px at a 1920px viewport, 1102px at 1440px, 942px at 1280px, 882px at 1220px, 812px at
   1150px, 686px at 1024px, 622px at 960px, 861px at 959px and 670px at 768px — and none of the nine
   scrolled. The 1220px and 1150px widths are the gap the raised threshold closes: at 882px the base
   floor still fits ten, and at 812px the tight tier is in force. Where the row is under ten floors
   wide the share, its type and its padding step down together, so ten still fit; a row that scrolls
   hides the Step the clinician is on, which is the one thing this file exists to prevent. */
@container (max-width: 880px) {
    .step-bar__step[b-tc76nw4j81] {
        min-width: 60px;
        padding: 10px 2px;
        gap: 4px;
        font-size: 0.75rem;

        /* A name with no space in it, such as Questionnaire, wraps inside its own share rather than
           spilling it and pushing the row back into a scroll. */
        overflow-wrap: anywhere;
    }

    .step-bar__number[b-tc76nw4j81] {
        width: 20px;
        height: 20px;
        border-radius: 10px;
        font-size: 0.6875rem;
    }
}

.step-bar__step--current[b-tc76nw4j81] {
    color: var(--mud-palette-primary);
    border-bottom-color: var(--mud-palette-primary);
}

.step-bar__step--current .step-bar__number[b-tc76nw4j81] {
    background: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text);
}

/* A Step that may not open yet. Its tooltip says what it waits on. */
.step-bar__step:disabled[b-tc76nw4j81] {
    color: var(--mud-palette-text-disabled);
    cursor: not-allowed;
}

.step-bar__step:disabled .step-bar__number[b-tc76nw4j81] {
    background: var(--mud-palette-action-disabled-background);
    color: var(--mud-palette-text-disabled);
}
/* /Components/Layout/MainLayout.razor.rz.scp.css */
.page[b-2noye0uila] {
    position: relative;
    display: flex;
    flex-direction: column;
}

main[b-2noye0uila] {
    flex: 1;
}

.sidebar[b-2noye0uila] {
    background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}

.top-row[b-2noye0uila] {
    background-color: #f7f7f7;
    border-bottom: 1px solid #d6d5d5;
    justify-content: flex-end;
    height: 3.5rem;
    display: flex;
    align-items: center;
}

    .top-row[b-2noye0uila]  a, .top-row[b-2noye0uila]  .btn-link {
        white-space: nowrap;
        margin-left: 1.5rem;
        text-decoration: none;
    }

    .top-row[b-2noye0uila]  a:hover, .top-row[b-2noye0uila]  .btn-link:hover {
        text-decoration: underline;
    }

    .top-row[b-2noye0uila]  a:first-child {
        overflow: hidden;
        text-overflow: ellipsis;
    }

@media (max-width: 640.98px) {
    .top-row[b-2noye0uila] {
        justify-content: space-between;
    }

    .top-row[b-2noye0uila]  a, .top-row[b-2noye0uila]  .btn-link {
        margin-left: 0;
    }
}

@media (min-width: 641px) {
    .page[b-2noye0uila] {
        flex-direction: row;
    }

    .sidebar[b-2noye0uila] {
        width: 250px;
        height: 100vh;
        position: sticky;
        top: 0;
    }

    .top-row[b-2noye0uila] {
        position: sticky;
        top: 0;
        z-index: 1;
    }

    .top-row.auth[b-2noye0uila]  a:first-child {
        flex: 1;
        text-align: right;
        width: 0;
    }

    .top-row[b-2noye0uila], article[b-2noye0uila] {
        padding-left: 2rem !important;
        padding-right: 1.5rem !important;
    }
}

#blazor-error-ui[b-2noye0uila] {
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

    #blazor-error-ui .dismiss[b-2noye0uila] {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
    }
/* /Components/Pages/AssessmentSession.razor.rz.scp.css */
/*
    The sticky stack under the app bar, top to bottom: the nav header, the Terminated banner when
    there is one, then the questionnaire's pinned Category list. Each layer pins under the one
    above it through these two custom properties, so the offsets are stated here — on the page that
    knows what is in the stack — and merely consumed by the components inside it.

    The clearances are the height of the layer above at 960px and wider, measured in devtools on
    this page, plus an 8px gap. Fixed constants rather than a JS measurement seam on purpose: if
    wording ever grows a layer past its clearance the failure is a few overlapped pixels, never a
    broken layout and never an observer outliving its circuit.
*/
.session-sticky-context[b-erla45w4m4] {
    /* The nav header measures 38.75px tall; 48px clears it with 8px of air under it. */
    --physiodx-under-nav: calc(var(--mud-appbar-height) + 48px);
    --physiodx-under-banner: var(--physiodx-under-nav);
}

/* A Terminated session carries one more layer, so everything below it pins that much lower: the
   banner's two lines measure 92px, and 100px clears them with the same 8px of air. */
.session-sticky-context.session-terminated[b-erla45w4m4] {
    --physiodx-under-banner: calc(var(--physiodx-under-nav) + 100px);
}
