/* /Components/Access/AgentOrganisationSwitcher.razor.rz.scp.css */
/* W8-05 · F10.2 — which agency an agent is acting for. TOKENS ONLY: R3.1's sweep in
   PortalStyleSheetTests reads every *.razor.css in this project and refuses a hex or an rgb() literal.

   🔴 A SCOPED SHEET, AND THE CLASSES ARE ON THIS COMPONENT'S OWN ELEMENTS. Blazor stamps the isolation
   attribute onto markup THIS file renders and not onto a child component's, so a class handed to
   UiSelect through its Class parameter could not be styled from here - it would render nothing, with no
   error, which is the failure UtilityClassTests was written for. Hence the wrapper div in the .razor. */

/* 🔴 W8-22 · NO MARGIN OF ITS OWN ANY MORE, AND THAT IS A CONSEQUENCE OF WHERE THIS NOW RENDERS.
   It read `margin-block-end: 16px` - "separation from PortalGrid below it" - which was right while this
   component was a block between the welcome card and the grid. It is handed to
   PortalWelcomeCard.Aside now, so it sits INSIDE the heading's row and the distance to anything below is
   that row's own (I18: the container owns the gap, and a margin here would be added to it).

   What is left is a floor for the CONTROL arm, which is the arm that still has to be usable: a labelled
   select beside a heading, whose intrinsic width is its longest option, would otherwise be as narrow as
   the shortest agency name somebody happens to broker for. 12rem is 192px - a whole multiple of the 4px
   base unit, so R5.4's sweep accepts it - and it is a minimum rather than a width, so a long name still
   widens it. */
.biztech-agent-organisation[b-51er7501jj] {
    min-inline-size: 12rem;
}

/* 🔴 W8-22 · ONE AGENCY IS A STATEMENT, AND IT IS DRAWN AS ONE LINE. R8.8 forbids a control that does
   nothing, so this arm has no select - but "no control" was rendering as two lines of loose text
   between two bordered cards, which reads as something unfinished rather than as the answer to *who am
   I acting for*.

   A PILL: inline-flex so it is as wide as its own content rather than as wide as the page (a full-width
   bar would read as a third card), the pill radius UiChip already uses for the same shape, the surface
   token for its ground and the subtle outline for its edge. Every colour is a --biztech-* token - R3.1's
   sweep in PortalStyleSheetTests refuses a hex or an rgb() literal, and W0-25's per-tenant palette is
   what these resolve through in both modes.

   🔴 AND IT IS COMPACT, WHICH THE FIRST SHAPE WAS NOT. That one was a two-line column at 8px/16px
   padding with a 12px gap, and beside the portal's heading it read as heavy as the welcome card - asked
   for as "one liner, more compact". 4px block, 12px inline and an 8px gap: as tall as its own caption
   and no taller. The padding is asymmetric, which is why it is written here rather than taken from a
   utility - R5.1 asks for a utility class when one exists and biztech-pa-* is symmetric - and every
   number is a whole multiple of the 4px base unit (R5.4).

   NO flex-direction and NO margin: the three children sit on one line, and the distance to anything
   around them belongs to the heading row this pill is handed to. */
.biztech-agent-organisation--fixed[b-51er7501jj] {
    display: inline-flex;
    align-items: center;
    gap: 8px;

    padding-block: 4px;
    padding-inline: 12px;

    border: 1px solid var(--biztech-palette-outline-subtle);
    border-radius: var(--biztech-radius-pill);
    background: var(--biztech-palette-surface);
}

/* 🔴 ::deep, BECAUSE THE SVG IS UiIcon'S OWN ELEMENT. A bare `.biztech-icon` here would carry this
   component's scope attribute and match nothing - I19, and PortalTileChip.razor.css records the same
   trap costing a whole launcher its chips. The tint is a token, and R11.3 holds: UiIcon sets no colour
   of its own and inherits this one. */
.biztech-agent-organisation--fixed[b-51er7501jj]  .biztech-icon {
    flex: 0 0 auto;
    color: var(--biztech-palette-primary);
}
/* /Components/Admin/FormBuilderCanvas.razor.rz.scp.css */
/* W3-21 · R14.1 layer 3 — the palette column, the <ul> and its rows are this component's own markup.

   Layout and one border colour. Everything else the rows wear comes from the seam's own components, which
   is why there is no type scale, no spacing override and no hover rule here: UiButton, UiIconButton and
   UiText already carry theirs.

   Every length is a multiple of the 4px base unit (R5.1, R5.4), every colour is a semantic role read
   through --biztech-* (R3.1, R7.4), and the radius is read from the variable rather than restated as 6px
   (R5.5) - MudThemeProvider is unmounted on this document, so --mud-default-borderradius resolves to
   nothing and would silently render square corners. */

.biztech-form-builder-palette[b-3y5fuv7ty1] {
    /* 12rem = 192px, 48 scale steps. Wide enough for "Signature" at the button's own padding without
       wrapping, which is what keeps the palette a scannable column rather than a ragged block. */
    flex: 0 0 12rem;
    display: flex;
    flex-direction: column;

    /* align-items rather than a width on the button: a stretched column makes every action look like the
       primary one, and R3.6 reserves that weight for one action per surface. */
    align-items: stretch;
    gap: 4px;
}

.biztech-form-builder-palette-button[b-3y5fuv7ty1] {
    /* The label left-aligns under the icon so twelve of them read as a list rather than as twelve centred
       captions of different widths. */
    justify-content: flex-start;
}

.biztech-form-builder-fields[b-3y5fuv7ty1] {
    /* min-width: 0 is load-bearing, not defensive: a flex item's default min-width is auto, so a long
       field label would push this column wider than its parent instead of wrapping inside it. */
    flex: 1 1 20rem;
    min-width: 0;
}

.biztech-form-builder-list[b-3y5fuv7ty1] {
    /* The default list indent puts every row a long way from the column's edge, which reads as an
       indented block rather than as the form's own field order. */
    margin: 0;
    padding-left: 0;
    list-style: none;

    display: flex;
    flex-direction: column;
    gap: 4px;
}

.biztech-form-builder-row[b-3y5fuv7ty1] {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 4px;

    border: 1px solid var(--biztech-palette-outline-subtle);
    border-radius: var(--biztech-radius-default);
}

.biztech-form-builder-row-selected[b-3y5fuv7ty1] {
    /* R12.7 asks for a second channel beside colour, and the row's own select button already carries
       aria-pressed - so this is the visual half of a state that is announced either way. */
    border-color: var(--biztech-palette-primary);
}

.biztech-form-builder-row-over[b-3y5fuv7ty1] {
    /* Where a drop would land. Dashed as well as coloured, for the reason above: the drag is a pointer
       gesture, but the marker must not be colour alone. */
    border-color: var(--biztech-palette-primary);
    border-style: dashed;
}

.biztech-form-builder-open[b-3y5fuv7ty1] {
    flex: 1 1 auto;
    min-width: 0;
}

.biztech-form-builder-field[b-3y5fuv7ty1] {
    /* A real <button> (R8.8) that has to look like a row rather than like a control: the browser's own
       button chrome is removed, and the focus ring comes from app.css's global :focus-visible rule, which
       is why nothing here writes an outline (R3.10, R3.11). */
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    width: 100%;
    padding: 4px;

    background: none;
    border: 0;
    border-radius: var(--biztech-radius-default);
    color: inherit;
    font: inherit;
    text-align: start;
    cursor: pointer;
}

.biztech-form-builder-field:disabled[b-3y5fuv7ty1] {
    cursor: default;
}

.biztech-form-builder-field-label[b-3y5fuv7ty1] {
    /* A long label truncates rather than wrapping the row to two lines - R4.7 permits it where the full
       value is reachable another way, and it is: this row's own editor shows the label in a text field. */
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.biztech-form-builder-field-meta[b-3y5fuv7ty1] {
    /* R4.4's floor is 12px and Caption is already there, so this reads at the body size in a dimmer role
       rather than being shrunk below the floor to fit. */
    color: var(--biztech-palette-text-secondary);
}

.biztech-form-builder-problem[b-3y5fuv7ty1] {
    color: var(--biztech-palette-error);
}
/* /Components/Admin/FormBuilderFieldEditor.razor.rz.scp.css */
/* W3-21 · R14.1 layer 3 — one rule, for this component's own wrapper around a problem message.

   The colour is NOT here: UiText carries it through Color="UiColor.Error", which is a semantic role
   (R3.1) rather than a value this sheet would have to restate and keep in step with the palette. What is
   here is the only thing the wrapper is for - putting each message on its own line under the control it
   concerns, rather than running several together as inline spans. */

.biztech-form-builder-problem-line[b-oumf2susz4] {
    display: block;

    /* One scale step, so a message sits closer to the control it belongs to than to the next field -
       R5.9's rule about a label and its value, applied to a control and its refusal. */
    margin-block-start: 4px;
}
/* /Components/Admin/FormBuilderPreview.razor.rz.scp.css */
/* W3-22 · R14.1 layer 3 — one rule, for this component's own row of gate chips.

   The colour is NOT here: UiChip carries it through Color="UiColor.Warning", which is a semantic role (R3.1)
   rather than a value this sheet would have to restate and keep in step with the palette. What is here is the
   only thing the row is for — letting four chips wrap instead of pushing the section's heading sideways.

   Every length is a multiple of the 4px base unit (R5.1, R5.4). */

.biztech-form-preview-gates[b-o5fnwc482h] {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;

    /* One scale step below the heading and two above the fields, so the marks read as belonging to the
       section they sit under rather than to the first field beneath them — R5.9's rule about a label and its
       value, applied to a section and its disclosure. */
    margin-block: 4px 8px;
}
/* /Components/Admin/FormBuilderSectionList.razor.rz.scp.css */
/* W3-22 · R14.1 layer 3 — the <ul> and its rows are this component's own markup.

   🔴 THESE RULES ARE NEAR-DUPLICATES OF FormBuilderCanvas.razor.css'S, AND THAT IS FORCED RATHER THAN
   CARELESS. Blazor scopes a component stylesheet to its own component — the build stamps a `b-<hash>`
   attribute on this file's elements and rewrites its selectors to match — so `.biztech-form-builder-row`
   declared next door cannot reach a row rendered here. The two ways out are both worse: promoting the rules
   to `app.css` is R14.1's layer 4, where `UtilityClassTests` classifies every class and the layer is
   reserved for what genuinely spans components; and rendering both lists from one component would put the
   fields pane and the sections pane in one file for the sake of forty lines of CSS.

   Layout and one border colour. Everything else the rows wear comes from the seam's own components, which is
   why there is no type scale and no hover rule here: UiButton, UiIconButton, UiIcon and UiChip carry theirs.

   Every length is a multiple of the 4px base unit (R5.1, R5.4), every colour is a semantic role read through
   --biztech-* (R3.1, R7.4), and the radius is read from the variable rather than restated as 6px (R5.5) —
   MudThemeProvider is unmounted on this document, so --mud-default-borderradius resolves to nothing and
   would silently render square corners. */

.biztech-form-section-pane[b-ubzze5qn2n] {
    /* min-width: 0 is load-bearing rather than defensive: this pane sits in the panel's flow beside a
       heading row, and a long section name would otherwise push it wider than its parent instead of
       wrapping inside it. R13's no-sideways-scroll rule is what that buys. */
    min-width: 0;
}

.biztech-form-section-list[b-ubzze5qn2n] {
    /* The default list indent puts every row a long way from the pane's edge, which reads as an indented
       block rather than as the form's own section order. */
    margin: 8px 0 0;
    padding-left: 0;
    list-style: none;

    display: flex;
    flex-direction: column;
    gap: 4px;
}

.biztech-form-section-row[b-ubzze5qn2n] {
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 4px;

    border: 1px solid var(--biztech-palette-outline-subtle);
    border-radius: var(--biztech-radius-default);
}

.biztech-form-section-row-selected[b-ubzze5qn2n] {
    /* R12.7 asks for a second channel beside colour, and the row's own select button already carries
       aria-pressed — so this is the visual half of a state that is announced either way. */
    border-color: var(--biztech-palette-primary);
}

.biztech-form-section-open[b-ubzze5qn2n] {
    flex: 1 1 auto;
    min-width: 0;
}

.biztech-form-section-row-button[b-ubzze5qn2n] {
    /* A real <button> (R8.8) that has to look like a row rather than like a control: the browser's own
       button chrome is removed, and the focus ring comes from app.css's global :focus-visible rule, which is
       why nothing here writes an outline (R3.10, R3.11). */
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    width: 100%;
    padding: 4px;

    background: none;
    border: 0;
    border-radius: var(--biztech-radius-default);
    color: inherit;
    font: inherit;
    text-align: start;
    cursor: pointer;
}

.biztech-form-section-row-button:disabled[b-ubzze5qn2n] {
    cursor: default;
}

.biztech-form-section-name[b-ubzze5qn2n] {
    /* A long heading truncates rather than wrapping the row to two lines — R4.7 permits it where the full
       value is reachable another way, and it is: this row's own editor shows the heading in a text field. */
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.biztech-form-section-meta[b-ubzze5qn2n] {
    /* R4.4's floor is 12px and the body size is already above it, so this reads at the body size in a dimmer
       role rather than being shrunk below the floor to fit. */
    color: var(--biztech-palette-text-secondary);
}

.biztech-form-section-gates[b-ubzze5qn2n] {
    /* Wraps, because a section can carry all four gates at once and a row that scrolled sideways to show the
       fourth would hide the very disclosure this chip row exists to surface. */
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-block-start: 4px;
}

.biztech-form-section-problem[b-ubzze5qn2n] {
    color: var(--biztech-palette-error);
}
/* /Components/Admin/FormJsonErrorList.razor.rz.scp.css */
/* T25 · R14.1 layer 3 — the <ul> and its rows are this component's own markup.

   Layout only. Every colour here would be a palette colour, and the alert this list sits inside already
   supplies the error colour through MudTheme (layer 1), so there is nothing to declare.

   No font-size either, deliberately: R5.4 puts every raw length on the 4px scale, and the size this
   wanted (13px) is not on it. Inheriting the alert's own size is the right answer rather than a named
   constant invented for one list - the monospace face is what distinguishes a pointer from prose here,
   not a smaller one. */

.biztech-form-json-errors[b-e5is8tydob] {
    /* The default list indent puts the pointers a long way from the alert's icon, which reads as a
       separate block rather than as its detail. */
    margin: 0;
    padding-left: 0;
    list-style: none;
}

.biztech-form-json-errors li[b-e5is8tydob] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;

    /* Ten of these stack, so the rhythm between them is what makes the list scannable rather than a
       wall of text. One scale step. */
    padding-block: 0.25rem;
}

.biztech-form-json-errors code[b-e5is8tydob] {
    /* The pointer is a path into the document the operator is editing, so it must not re-wrap or
       re-space: "fields[3].type" is a string they will search for verbatim.

       A stack rather than var(--mud-typography-monospace-family): MudBlazor 9.0.0 emits no such
       variable, and a var() that resolves to nothing would silently fall back to the proportional
       body face - which is the one thing this rule exists to prevent. */
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    white-space: nowrap;
    align-self: flex-start;
}

.biztech-form-json-errors span[b-e5is8tydob] {
    /* Takes the remaining width so a long sentence wraps under itself rather than under the pointer. */
    flex: 1 1 12rem;
    min-width: 0;
}
/* /Components/Cases/AnnotationOverlay.razor.rz.scp.css */
/* W6-10 · F8.8 · the annotation overlay's own styles.

   Scoped, so they arrive through BizTech.Portal.styles.css - already referenced from
   Pages/_Host.cshtml, which is why this file needs no host-page change. SignaturePad.razor.css makes
   the same note.

   🔴 NO @media (prefers-color-scheme) ANYWHERE (I16, R7.3). Every colour here reads a --biztech-*
   token, which BizTechCssVariables re-emits when the mode flips - so a dark-mode overlay is free and
   there is nothing to keep in step. That is the whole reason the ink is SVG rather than a canvas: a
   canvas would need getComputedStyle and a repaint on the flip. */

.biztech-annotation-host[b-p34n9vrh8w] {
    /* The positioning context the ink layer fills. */
    position: relative;

    /* 🔴 W6-53 · R13.1. THE STICKY BAND MUST NOT LAND ON TOP OF THE INK, AND SCROLLING IS WHEN IT
       DID.

       SessionStage pins the band at 600px and up. Nothing here changed that - what changed is the STAGE:
       W6-53 took an h1, a `SESSION: {ref}` badge and a "You are in this session as X" line off the top
       of the customer's pane, so the artefact now begins about 90px higher. Scroll it into view and its
       top edge arrives UNDER the band rather than below it, and a pointer aimed at the first 48px of the
       drawing surface reaches the band instead.

       Measured by SessionAnnotationTouchTests.A_stroke_normalises_to_the_same_fractions_at_every_width,
       which went red at 768 and at 1440 and stayed GREEN at 375 - the one width where the band is not
       sticky. That split is the whole diagnosis, and it is why this is a page defect rather than a test
       accommodation: a customer who scrolls has the top of the artefact hidden under the band, and
       drawing there hits the band.

       scroll-margin-block-start is the property browsers give for exactly this, and Chromium honours it
       in scrollIntoViewIfNeeded. 4rem = 64px, a scale value, against a band measured at 49px - the
       clearance is deliberate rather than exact, because the band's height is content-driven and a
       single wrapped control would grow it.

       Inside the same media query the pin is, so below sm - where nothing is pinned - nothing is
       reserved either. R13.3: "every desktop-only rule must be reversed there". */
}

@media (min-width: 600px) {
    .biztech-annotation-host[b-p34n9vrh8w] {
        scroll-margin-block-start: 4rem;
    }
}

.biztech-annotation-ink[b-p34n9vrh8w] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;

    /* 🔴 IDLE BY DEFAULT, AND THIS IS THE CARD'S "AN OVERLAY, NEVER A MUTATION" IN ONE DECLARATION.
       Two stacked surfaces cannot both take a pointer, so until the visitor turns annotating on every
       field underneath is reachable and nothing about the form has changed. */
    pointer-events: none;
}

.biztech-annotation-live[b-p34n9vrh8w] {
    pointer-events: auto;

    /* 🔴 BOTH NEEDED ON TOUCH, and R13.5 names this surface specifically. touch-action stops the
       browser claiming the gesture for a scroll or a double-tap zoom before pointermove ever fires;
       without it the overlay is unusable on a phone and works perfectly on the desktop it was tested
       on. The module calls preventDefault as well - belt and braces, because the two fail differently.
       SessionAnnotationTouchTests dispatches real CDP touch input to prove it. */
    touch-action: none;
    cursor: crosshair;
}

/* One stroke's width in the unit square would be invisible, so the ink is sized in absolute units and
   told not to scale with the viewBox. Without non-scaling-stroke, preserveAspectRatio="none" stretches
   the line THICKNESS along with the geometry, and one mark is a different weight on each pane. */
.biztech-annotation-ink polyline[b-p34n9vrh8w],
.biztech-annotation-sample line[b-p34n9vrh8w] {
    fill: none;

    /* 4px, not 3: §5's scale is multiples of the 4px base unit and PortalStyleSheetTests enforces it.
       Thicker is the right direction anyway - this is a mark meant to be read on a projector and on a
       375px phone. */
    stroke-width: 4px;
    stroke-linecap: round;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}

/* 🔴 THE FOUR PENS - D5. A COLOUR AND A DASH PATTERN EACH, never a colour alone: R11.5 and R12.2
   forbid colour as the only channel, and the legend beside the surface is what turns a dash into a
   name. Four roles from §3's 22, all of which BizTechPaletteDefaults sets in both modes. */
.biztech-annotation-pen-0[b-p34n9vrh8w] {
    stroke: var(--biztech-palette-primary);
}

.biztech-annotation-pen-1[b-p34n9vrh8w] {
    stroke: var(--biztech-palette-secondary);
    stroke-dasharray: 9 5;
}

.biztech-annotation-pen-2[b-p34n9vrh8w] {
    stroke: var(--biztech-palette-tertiary);
    stroke-dasharray: 2 5;
}

.biztech-annotation-pen-3[b-p34n9vrh8w] {
    stroke: var(--biztech-palette-info);
    stroke-dasharray: 12 4 2 4;
}

/* The in-progress stroke. Deliberately NEUTRAL ink rather than the drawer's own pen: the pen is
   assigned by AnnotationLayer from the room, which JavaScript does not have - and the real mark, in
   the real pen, replaces this a moment later when the pen lifts. */
.biztech-annotation-preview[b-p34n9vrh8w] {
    stroke: var(--biztech-palette-text-primary);
}

/* A <ul>, so the user agent gives it a block margin and a list indent, and both are noise here: this
   is a key to the ink, not a bulleted list in prose.

   🔴 margin-block AND padding-inline-start RATHER THAN THE SHORTHANDS, which is not cosmetic. A scoped
   selector is (0,2,0) against a utility class's (0,1,0), so `margin: 0` here would silently beat every
   biztech-mb-* a page passes this component - UtilitySpecificityTests' whole subject. The longhands
   remove exactly what the browser chose and leave the utility layer owning the rest, which is why this
   file needs no entry in that test's Declared set. */
.biztech-annotation-legend[b-p34n9vrh8w] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-block: 0;
    padding-inline-start: 0;
    list-style: none;
}

.biztech-annotation-legend li[b-p34n9vrh8w] {
    display: flex;
    align-items: center;
    gap: 0.25rem;
}

.biztech-annotation-sample[b-p34n9vrh8w] {
    width: 24px;
    height: 8px;
    flex: 0 0 auto;
}
/* /Components/Cases/CaseProgressStrip.razor.rz.scp.css */
/*
    W4-22, W4-24 · F7.2, F7.27. The case's progress as a STEPPER: a named node per stage, joined by a
    rule, a tick on each one the case has passed and a chip on the one it is at.

    🔴 IT REPLACED A ROW OF DOTS AND A LEGEND NOBODY HAD. Five markers of three fills, 8px and 12px,
    with no labels, needed the sentence beside them to decode - and that sentence named only the
    current stage, so the dots either side of it said nothing at all.

    🔴 HOLLOW, NEVER DASHED. A stage the case has not reached is an outlined marker with muted ink.
    The map learnt this the hard way: a dash reads as an unfinished drawing rather than as "not yet".

    Every colour is a --biztech-* token - PortalStyleSheetTests sweeps every scoped sheet for a
    hand-written hex - and there is no @media (prefers-color-scheme) anywhere (I16, R7.3). The two
    modes differ because the tokens do, and for no other reason.
*/
.biztech-progress-strip[b-xwp1cvo6bw] {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/*
    🔴 THE <ol> IS THE SEQUENCE. It is a list because the stages are ordered and that order is the fact
    being shown; the browser's block margin and its list marker would both fight the rail, so they come
    off here - the exemption UtilitySpecificityTests carries for this selector, with its reasoning.
*/
.biztech-progress-strip__markers[b-xwp1cvo6bw] {
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    margin: 0;
    padding-inline-start: 0;
    list-style: none;
}

/*
    Three cells per node: the state above, the marker on the rail, the name below.

    🔴 `order` HERE IS LOAD-BEARING, NOT COSMETIC. The MARKUP reads marker, name, state - the order a
    screen reader needs, "First thing, Done". The SCREEN needs state, marker, name. CSS order moves the
    boxes and leaves the accessibility tree alone, which is the only way to have both, and it is why
    the reflow below sm costs nothing in the reading.

    The tick sits ABOVE the marker rather than inside it, which is why no On* ink is needed for a glyph
    on a filled dot - and why the same three cells simply re-order on the other axis.

    flex: 1 1 0 with min-inline-size: 0 so nine nodes share the row evenly and a long tenant label wraps
    inside its own node instead of pushing the page sideways, which is R13.8's whole subject.
*/
.biztech-progress-strip__node[b-xwp1cvo6bw] {
    position: relative;
    display: flex;
    flex: 1 1 0;
    min-inline-size: 0;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    text-align: center;
}

.biztech-progress-strip__node > .biztech-progress-strip__state[b-xwp1cvo6bw] {
    order: 1;
}

.biztech-progress-strip__node > .biztech-progress-strip__marker[b-xwp1cvo6bw] {
    order: 2;
}

.biztech-progress-strip__node > .biztech-progress-strip__label[b-xwp1cvo6bw] {
    order: 3;
}

/*
    The connecting rule: a pseudo-element on every node but the first, running back to the one before
    it. A pseudo-element rather than an element, so it is outside the accessibility tree by
    construction rather than by an aria-hidden somebody has to remember to add.

    EMPHASISED into a node the case has reached, MUTED into one it has not - so the sequence reads as a
    sequence rather than as five unrelated marks. A rail drawn in one colour end to end is the "inert"
    version the card warns about: it renders as the row of dots this replaced and looks correct in
    every file sweep.
*/
.biztech-progress-strip__node + .biztech-progress-strip__node[b-xwp1cvo6bw]::before {
    content: "";
    position: absolute;
    inset-inline-end: 50%;
    inline-size: 100%;

    /* R5.6 fixes every rule and border in this application at 1px, and a rail is a rule. */
    block-size: 1px;

    /* 56px = the state cell's 44 + the node's 4px gap + half the marker's 16px border box, so the rail
       meets the marker's middle.

       🔴 THE MARKER DECLARES `box-sizing: border-box` SO THAT 16 IS ITS REAL WIDTH. Without it the 1px
       border sits outside and the marker renders 18px - MudBlazor's global reset left with W0-53, and
       app.css sets box-sizing on two selectors, neither of them this - which put an earlier draft's
       rail 1px above the marker's centre. Half of 18 is also not on the 4px scale (R5.4), so the box
       model and the guardrail wanted the same correction.

       The state cell's height is FIXED rather than minimum for the same reason this number exists: a
       chip is taller than a tick, so a minimum would push the current node's marker down and leave the
       rail pointing at nothing. */
    inset-block-start: 56px;
    background: var(--biztech-palette-outline);
}

/*
    🔴 THE SIBLING COMBINATOR IS REPEATED HERE, AND IT IS LOAD-BEARING. Written first as a bare
    `.node--done::before`, this rule NEVER APPLIED and the rail shipped one colour end to end - the
    "inert" stepper the card warns looks correct in every file sweep. Blazor stamps its scope attribute
    on the last compound selector, so the two rules compiled to:

        .node + .node[b-x]::before   2 classes + 1 attribute = (0,3,1)   <- won everywhere
        .node--done[b-x]::before     1 class   + 1 attribute = (0,2,1)

    Source order never entered into it. Matching the shape makes them tie, and then source order does
    decide. ProgressStripLayoutTests compares the two selectors the way a browser does, because a regex
    that only checks both declarations EXIST was green against the broken sheet.
*/
.biztech-progress-strip__node + .biztech-progress-strip__node--done[b-xwp1cvo6bw]::before,
.biztech-progress-strip__node + .biztech-progress-strip__node--current[b-xwp1cvo6bw]::before {
    background: var(--biztech-palette-success);
}

/*
    The state cell keeps a FIXED height whether or not it holds anything, so every marker sits on one
    line across the row instead of stepping up under the node that carries the chip. That fixed height
    is what the rail's offset is measured from.

    🔴 TWO LINES' WORTH, NOT ONE, AND A BROWSER AT 768 IS WHAT DECIDED IT. At one line the "In progress"
    chip - which is ~88px of nowrap text at caption size - overhung a ~59px node by half its width in
    each direction and sat across its neighbours' cells. Capping it at the node's width lets it wrap
    instead, and the cell has to have room for the second line or the wrap would be clipped.

    44px suits both: a one-line chip is centred in it, a two-line chip fits, and the markers stay on
    one line either way.
*/
.biztech-progress-strip__state[b-xwp1cvo6bw] {
    display: flex;
    align-items: center;
    justify-content: center;
    block-size: 44px;
    max-inline-size: 100%;
}

/*
    The chip may not be wider than the node it belongs to. Without this it is sized by its own text and
    overhangs into the columns either side, which is what 768 showed.

    🔴 W0-75 · AND IT SAID `> *`, WHICH HAD NEVER APPLIED. Blazor stamps the scope attribute on a
    selector's LAST compound, so this compiled to `> *[b-xxxxxxxxxx]` - and the only two things this span
    ever holds are UiIcon's <svg> and UiChip's <span>, both rendered by a child component and neither
    carrying this component's scope. The overhang 768 showed has been back ever since.

    ::deep and the chip by name, rather than `::deep *`: the chip is what is sized by its own text, an
    icon is a fixed 1em box, and a cap applied to every descendant of the state span would be a rule
    about elements this file cannot see. ScopedStyleReachTests refuses a universal last compound now, so
    the shape cannot return in silence.
*/
.biztech-progress-strip__state[b-xwp1cvo6bw]  .biztech-chip {
    max-inline-size: 100%;
}

.biztech-progress-strip__node--done .biztech-progress-strip__state[b-xwp1cvo6bw] {
    color: var(--biztech-palette-success);
}

/*
    🔴 --biztech-palette-outline, NOT --outline-subtle. The map's --ahead node is being changed to this
    same token by this card and for the same reason: subtle measures 1.25:1 against the paper behind it
    and the shape is simply not there. Outline measures 3.33:1 light and 3.51:1 dark, which clears
    R3.6's 3:1 for a non-text boundary.
*/
.biztech-progress-strip__marker[b-xwp1cvo6bw] {
    position: relative;
    z-index: 1;

    /* So 16 is the marker's REAL size and half of it is a scale step. Nothing else on this page sets
       box-sizing: the global reset went with MudBlazor in W0-53. */
    box-sizing: border-box;
    inline-size: 16px;
    block-size: 16px;
    border-radius: 999px;
    border: 1px solid var(--biztech-palette-outline);
    background: var(--biztech-palette-surface);
}

.biztech-progress-strip__node--done .biztech-progress-strip__marker[b-xwp1cvo6bw] {
    border-color: var(--biztech-palette-success);
    background: var(--biztech-palette-success);
}

/* Where the case is now: the primary colour, so the eye lands on it first. */
.biztech-progress-strip__node--current .biztech-progress-strip__marker[b-xwp1cvo6bw] {
    border-color: var(--biztech-palette-primary);
    background: var(--biztech-palette-primary);
}

/*
    A layout class on a span THIS component renders. UiText renders its own markup, so a class handed to
    it would compile to a selector matching nothing - the ::deep trap CasePage.razor.css records in
    full. Colour is INHERITED across that boundary, which is why muting works from out here.
*/
.biztech-progress-strip__label[b-xwp1cvo6bw] {
    min-inline-size: 0;
    overflow-wrap: anywhere;
}

.biztech-progress-strip__node--ahead .biztech-progress-strip__label[b-xwp1cvo6bw] {
    color: var(--biztech-palette-text-secondary);
}

/*
    The state as text for a reader who cannot see the tick, hidden by CLIPPING rather than display:none
    or visibility:hidden - the idiom .biztech-skip-link and .biztech-lifecycle-route both use, and for
    the same reason: removed is not the same as hidden. app.css publishes no shared utility for it
    (TenantChrome's header says so and DeckViewer.razor.css carries its own copy), so this sheet carries
    one too.
*/
.biztech-progress-strip__reading[b-xwp1cvo6bw] {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/*
    🔴 375 IS THE HARD PART AND IT IS THE POINT. Nine named nodes do not fit across a phone, so below sm
    the stepper turns vertical: nodes stacked, the rule running down the left, names to the right of
    each. R13.3 - every desktop-only rule must be reversed there - and it is the same information in
    the axis that has room, which is why the answer is not a smaller font.
*/
@media (max-width: 599.98px) {
    .biztech-progress-strip__markers[b-xwp1cvo6bw] {
        flex-direction: column;
        align-items: stretch;
    }

    .biztech-progress-strip__node[b-xwp1cvo6bw] {
        flex: 0 0 auto;
        flex-direction: row;
        align-items: center;
        gap: 8px;
        min-block-size: 40px;
        text-align: start;
    }

    /* The rail turns with the nodes: down the left edge, meeting each marker's middle.

       8px: half the marker's 16px border box, which it declares box-sizing: border-box to make real.

       🔴 A KNOWN LIMIT, WRITTEN DOWN RATHER THAN HALF-FIXED. `inset-block-end: 50%` measures from THIS
       node's centre and `block-size: 100%` spans this node's height, so the rail arrives exactly at the
       previous marker only while the two nodes are the SAME height. min-block-size holds that for a
       single-line label; a tenant label long enough to wrap at 375 makes its node taller and leaves a
       short gap in the rail above it.

       A fixed overshoot cannot fix it in both directions - too little still leaves the gap, too much
       paints a stub above the previous marker - and the exact fix needs two segments per node, where
       the lower half would have to be coloured by the NEXT node's state, which CSS here cannot reach.
       Left as it is deliberately: the failure is a small gap in one rail on a phone, and the cure is
       worse than the symptom. */
    .biztech-progress-strip__node + .biztech-progress-strip__node[b-xwp1cvo6bw]::before {
        inset-block-start: auto;
        inset-block-end: 50%;
        inset-inline-start: 8px;
        inset-inline-end: auto;
        inline-size: 1px;
        block-size: 100%;
    }

    /* 🔴 THE SAME SELECTOR SHAPE AS THE DEFAULTS ABOVE, and that is required rather than tidy. Those
       order rules use the child combinator, so they score (0,2,0); a bare class here would score
       (0,1,0) and LOSE - a media query does not raise specificity - leaving the vertical stepper laid
       out in the horizontal order, with no warning and a green suite. */
    .biztech-progress-strip__node > .biztech-progress-strip__marker[b-xwp1cvo6bw] {
        order: 1;
    }

    .biztech-progress-strip__node > .biztech-progress-strip__label[b-xwp1cvo6bw] {
        order: 2;
        flex: 1 1 auto;
    }

    .biztech-progress-strip__node > .biztech-progress-strip__state[b-xwp1cvo6bw] {
        order: 3;
        min-block-size: 0;
    }
}
/* /Components/Cases/MediaRoom.razor.rz.scp.css */
/* F8.11 · the video tiles.

   🔴 ::deep IS REQUIRED ON EVERY TILE RULE, AND THE REASON IS THE WHOLE POINT OF THIS FILE. Blazor's
   scoped CSS rewrites a selector to `.biztech-media-tile[b-xxxxx]`, and it stamps that attribute onto
   elements THIS COMPONENT RENDERS. The tiles are created by wwwroot/js/biztech-media.js, so they carry
   no attribute and a plain selector would never match one - the pane would render correctly in a test
   that inspects markup and show unstyled, full-bleed video in a browser. ::deep moves the attribute to
   the ancestor instead, which is the one form that reaches a child the module made.

   The three elements the COMPONENT renders - .biztech-media, .biztech-media-tiles and
   .biztech-media-controls - must NOT carry ::deep, for the mirror-image reason. MediaAssetTests asserts
   both halves, because either mistake is silent.

   🔴 EVERY COLOUR IS READ FROM A ROLE (R7.4), AND ONE PIECE OF THIS DESIGN CHANGED TO KEEP THAT TRUE.
   The participant's name was first drawn OVER the video on a translucent black scrim with white text -
   the standard conferencing treatment, and what every hand-built call UI does. PortalStyleSheetTests
   refused it, correctly: a scoped sheet may write no colour the palette does not control, and there is
   no exemption for one. A scrim is also a colour pair nothing measures - text-on-video is not in
   PaletteContrast.RequiredPairs, and it cannot be, because what is behind it is a camera.

   So the name sits BELOW the frame instead of on it. It costs a little tile height and it is the better
   design: the label takes the paper's own text colour by inheritance, so it follows dark mode and is
   readable whatever the camera is pointed at - which a scrim over a bright window is not. */

/* The pane's own vertical rhythm, so the tiles, the copy and the controls are evenly spaced without
   each of them carrying a margin utility. R5.1's 4px scale. */
.biztech-media[b-u6zxw9v36i] {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* 🔴 W6-43 · design §4. TWO GRIDS, FOR TWO JOBS, AND BOTH HALVES WERE MEASURED IN A BROWSER ON
   2026-09-06 RATHER THAN READ.

   One rule served both until this card: repeat(auto-fit, minmax(9rem, 1fr)).

     In a 20rem rail it computes ONE column - grid-template-columns returned a single 294px track. Two
     9rem tiles and an 8px gap need 296px, and a 20rem rail less a paper's 12px padding leaves exactly
     296px, so it rounds down. Two people were two full-width stacked tiles of ~190px, and four were
     760px of rail.

     On an 869px stage the same rule computed 195px 195px 195px 195px - four faces 110px tall, on the one
     surface whose whole job is those faces.

   auto-fit packs as MANY tiles per row as fit. A rail wants a fixed glance; a stage wants as FEW as
   possible, so each tile is as large as it can be. Neither is the question auto-fit answers. */

/* The rail: fixed two-up, whatever the rail's width does to it. */
.biztech-media-tiles[b-u6zxw9v36i] {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
}

/* 🔴 THE STAGE: FOUR RULES AND ONE ATTRIBUTE - no measuring, no ResizeObserver. biztech-media.js creates
   the tiles, so it already knows the count and writes it to data-tiles; `many` is what it writes beyond
   nine, because CSS cannot express a range and the column count stops changing there anyway. */
.biztech-media-on-stage .biztech-media-tiles[data-tiles="1"][b-u6zxw9v36i] {
    grid-template-columns: minmax(0, 1fr);
}

.biztech-media-on-stage .biztech-media-tiles[data-tiles="2"][b-u6zxw9v36i],
.biztech-media-on-stage .biztech-media-tiles[data-tiles="3"][b-u6zxw9v36i],
.biztech-media-on-stage .biztech-media-tiles[data-tiles="4"][b-u6zxw9v36i] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.biztech-media-on-stage .biztech-media-tiles[data-tiles="5"][b-u6zxw9v36i],
.biztech-media-on-stage .biztech-media-tiles[data-tiles="6"][b-u6zxw9v36i],
.biztech-media-on-stage .biztech-media-tiles[data-tiles="7"][b-u6zxw9v36i],
.biztech-media-on-stage .biztech-media-tiles[data-tiles="8"][b-u6zxw9v36i],
.biztech-media-on-stage .biztech-media-tiles[data-tiles="9"][b-u6zxw9v36i] {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

.biztech-media-on-stage .biztech-media-tiles[data-tiles="many"][b-u6zxw9v36i] {
    grid-template-columns: repeat(4, minmax(0, 1fr));
}

/* 🔴 R13.3 · AND THE REVERSAL, WHICH IS THE RULE THIS SHEET WOULD OTHERWISE BREAK: "every desktop-only
   rule must be reversed there (container height, per-column scroll, sticky headings)". Four columns at
   375px is an 85px face, which is below the point a tile is for - minmax's 9rem floor was the old rule's
   way of saying the same thing. Two is the most a phone can carry. */
@media (max-width: 599px) {
    .biztech-media-on-stage .biztech-media-tiles[data-tiles][b-u6zxw9v36i] {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

/* Empty until somebody joins, and it must take NO space then - the flex gap above would otherwise
   leave 8px of nothing between the paper's heading and the Join button. :empty is exact here: the
   module either put a tile in or it did not. */
.biztech-media-tiles:empty[b-u6zxw9v36i] {
    display: none;
}

[b-u6zxw9v36i] .biztech-media-tile {
    border: 1px solid var(--biztech-palette-outline);
    border-radius: var(--biztech-radius-default);
    overflow: hidden;
}

/* 16:9, declared as a ratio rather than a height, so a tile keeps its shape at every column width the
   auto-fit above produces.

   The ground behind a track that has not arrived yet, or a camera that is off. The BACKGROUND role
   rather than SURFACE, so an empty frame reads as a recess in the paper it sits on rather than as the
   same sheet - and both are declared roles held to R3's contrast pairs. */
[b-u6zxw9v36i] .biztech-media-frame {
    aspect-ratio: 16 / 9;
    display: block;
    background: var(--biztech-palette-background);
}

[b-u6zxw9v36i] .biztech-media-video {
    inline-size: 100%;
    block-size: 100%;
    /* cover, not contain: a letterboxed face in a 9rem tile is mostly empty ground. The crop is the
       lesser cost, and it is what every conferencing client does. */
    object-fit: cover;
    display: block;
}

/* 🔴 MIRRORED, AND ONLY THE LOCAL ONE. A self-view that is not mirrored reads as somebody else's
   picture - you raise your left hand and the person on screen raises their right. A remote tile must
   NOT be mirrored: that is what the other person actually looks like. */
[b-u6zxw9v36i] .biztech-media-self {
    transform: scaleX(-1);
}

/* 🔴 NO COLOUR DECLARED, DELIBERATELY - see this file's header. The name inherits the paper's text
   colour, so it follows the palette and dark mode with nothing here to keep in step. */
[b-u6zxw9v36i] .biztech-media-name {
    display: block;
    padding: 4px 8px;
    font-size: 0.75rem;
    /* A long name must not push the tile wider than its column. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 🔴 W6-45 · design §9, specimen 11a. THE ONE STATE ON THESE SURFACES THAT COLOUR IS ALLOWED TO CARRY.

   §9: "Muted and camera-off take the error role. It is the one place on these surfaces where colour
   carries a state rather than a hierarchy, and being muted without knowing is why it earns the
   exception." A mute button tells the person pressing it; everybody else learns it here.

   Both roles are already in PaletteContrast.RequiredPairs, so this costs no new colour and no
   unmeasured pair - which is the whole reason the error role is what carries it rather than a new one.

   🔴 AND IT IS NOT COLOUR ALONE (R12.7). The ::before writes a real mark into the name, so a reader who
   cannot tell the two inks apart still gets the fact. That is also why the rule is on the NAME and not
   on the frame: MediaRoom's own header rules out a scrim over a camera, because what is under it is a
   video frame and PaletteContrast cannot measure a pair against moving pictures. */
[b-u6zxw9v36i] .biztech-media-muted .biztech-media-name {
    color: var(--biztech-palette-error);
}

[b-u6zxw9v36i] .biztech-media-muted .biztech-media-name::before {
    /* U+1F507 SPEAKER WITH CANCELLATION STROKE, then a non-breaking space so the mark never wraps away
       from the name it belongs to. */
    content: "\1F507\00A0";
}

.biztech-media-controls[b-u6zxw9v36i] {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* 🔴 W6-43 · design §4. THE RAIL'S CAP, AND ::deep IS REQUIRED BECAUSE THE TILE IS THE MODULE'S - this
   file's header says why in full. The module marks every tile past the fourth with data-media-overflow;
   only the RAIL hides them, which is the whole difference between a monitor and the content.

   A MARKER RATHER THAN :nth-child(n+5), AND THE AUDIO ELEMENTS ARE WHY. attachRemote() appends a remote
   <audio> into this same container, so :nth-child would count audio and cap the wrong tiles - and
   display: none would not rescue it, because :nth-child counts hidden elements too.

   MediaRoom renders "N more on camera" beside this, so the hiding is stated rather than silent. */
.biztech-media-in-rail[b-u6zxw9v36i]  .biztech-media-tile[data-media-overflow] {
    display: none;
}

/* The empty stage. Centred, because the card's clause is "centred on the stage, never a blank box", and
   a 16:9 grid of nothing is exactly the blank box it means. No colour is declared: the sentence inherits
   the paper's ink, which is this file's own standing rule and the reason a tile's name sits below its
   frame rather than over it. */
.biztech-media-empty[b-u6zxw9v36i] {
    display: grid;
    justify-items: center;
    align-content: center;
    gap: 8px;
    padding: 24px;
    text-align: center;
}

/* The rail's overflow line: the count on the left, the way to see them on the right. It wraps, because a
   20rem rail less its paper's padding is 296px and "8 more on camera" beside a button does not fit. */
.biztech-media-more[b-u6zxw9v36i] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 8px;
}
/* /Components/Cases/PdfFormOverlay.razor.rz.scp.css */
/*
    W3-18 · F6.22. Only what is the same for every control lives here; each rect is inline, because it is
    computed per widget from coordinates in someone else's document and no stylesheet could hold it.

    Every length is 0, 1px, 2px or a multiple of the 4px base unit, which is what
    PortalStyleSheetTests.No_stylesheet_writes_a_length_that_is_neither_a_scale_step_nor_a_named_constant
    requires. Every colour is read from the palette rather than written, which its sibling requires.
*/

/*
    🔴 THE CONTAINER SCROLLS ITSELF. A page rendered at 150dpi is 1240px wide, and letting that widen the
    document would give a narrow viewport a horizontal scrollbar on the whole page - the invariant that wide
    content scrolls inside its own box.
*/
.biztech-pdf-overlay[b-djnerpiw8k] {
    overflow: auto;
    max-width: 100%;
}

/* The positioning context every control's inline left/top is measured from. */
.biztech-pdf-overlay-page[b-djnerpiw8k] {
    position: relative;
    margin: 0 auto;
}

/*
    display: block removes the inline-image baseline gap, which would otherwise offset every control by a
    few pixels against the page it is positioned over - the same silent misalignment the coordinate flip
    causes, arriving by a different route.
*/
.biztech-pdf-overlay-image[b-djnerpiw8k] {
    display: block;
    width: 100%;
    height: auto;
}

.biztech-pdf-overlay-field[b-djnerpiw8k] {
    position: absolute;
}

/*
    The control fills the box the document drew, so what a participant clicks is the box they can see.

    🔴 NO outline: none. R3.11's focus ring is what tells a keyboard user where they are, and on this page
    it is the ONLY such cue - every other visual signal is in an image that is aria-hidden. The ring is
    raised above its neighbours so it is not clipped by an adjacent control's box.
*/
.biztech-pdf-overlay-control[b-djnerpiw8k] {
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    padding: 0 2px;
    border: 1px solid var(--biztech-palette-outline);
    background-color: var(--biztech-palette-surface);
    color: var(--biztech-palette-text-primary);
    font: inherit;
}

.biztech-pdf-overlay-control:focus-visible[b-djnerpiw8k] {
    position: relative;
    z-index: 1;
}
/* /Components/Cases/SessionChat.razor.rz.scp.css */
/*
    W6-44 · F8.10 · the chat panel. Specimen 7 is the conversation, specimen 8 the other four states.

    Layout and hairlines only — no colour the palette does not control (R3.1) and no length that is not a
    multiple of the 4px base unit (R5.4). Everything visual comes from UiPaper, UiText and the tokens.

    🔴 THE SPECIMEN'S SUB-CAPTION TYPE SIZE IS NOT AVAILABLE HERE, AND THAT IS THE RIGHT ANSWER.
    Specimen 7 sets `.625rem` on the avatar's initials and on the gutter time; 10px is not on the 4px
    scale and no token below --biztech-font-caption-size exists, so both read the caption token instead.
    SessionParticipantRail.razor.css made the identical substitution for the identical mark. The
    specimen's `padding: 0 10px` and `gap: 6px` on the pill are 0.75rem and 0.5rem here for the same
    reason. Nothing was added to PortalStyleSheetTests.LayoutConstants: a registry entry is for a value
    that is load-bearing at exactly that number, and none of these is.

    🔴 THE PAPER IS FLUSH AND THESE ROWS CARRY THE PADDING. UiPaper declares none of its own, so this is
    not a reset — it is the only arrangement in which the log's scrollbar reaches the paper's border. A
    padded paper would inset the rail's one scroll container and put its scrollbar in the middle of the
    panel.
*/

/* min-block-size: 0 so the log can be the thing that scrolls rather than the column growing to fit it.
   A flex item's default minimum is its content, which is the same trap SessionStage's minmax(0, 1fr)
   answers one level up. */
.biztech-session-chat[b-ycnsnxkoev] {
    display: flex;
    flex-direction: column;
    min-block-size: 0;
}

.biztech-session-chat__head[b-ycnsnxkoev] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    border-block-end: 1px solid var(--biztech-palette-outline-subtle);
}

/*
    🔴 THE PILL IS THE ONE PLACE THIS PANEL SPENDS A FILL, AND IT IS NOT DECORATION.

    The card forbids tinted message backgrounds and right-alignment, because "the design direction spends
    colour on hierarchy and not on decoration". This is the other case: an interactive control that has
    just appeared, whose whole job is to be noticed by somebody who is reading something else. Primary
    over OnPrimary is a pair the palette already holds to contrast in both modes (R3.7), so it needs no
    new one — the same argument the card makes for "You" over a colour.

    The user-agent button defaults come off explicitly, the four the board card also undoes. The focus
    ring is NOT declared here: app.css has exactly one, shared, and this element is on its selector list.
*/
.biztech-session-chat__new[b-ycnsnxkoev] {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    flex: 0 0 auto;

    box-sizing: border-box;
    block-size: 1.5rem;
    padding: 0 0.75rem;
    border: 1px solid transparent;
    border-radius: var(--biztech-radius-pill);

    background: var(--biztech-palette-primary);
    color: var(--biztech-palette-on-primary);

    font: inherit;
    font-size: var(--biztech-font-caption-size);
    cursor: pointer;
}

/*
    🔴 block-size, NOT max-height, AND THAT IS THE CARD'S WHOLE POINT ONE LEVEL IN. W6-12 wrote
    `max-height: 14rem` so "a short conversation takes only the room it needs" — which means the panel
    grows by up to 224px as a conversation goes on, and W6-42 measured every presenter control moving
    120px when it did. "A box that cannot grow cannot push a button." 15rem is the specimen's 240px,
    which is 60 × 4, and it is fixed.

    🔴 15rem IS THE CONTENT BOX, WHICH IS WHAT THE CARD'S NUMBER NAMES. There is no universal
    `box-sizing` reset in this application (W0-53 deleted MudBlazor's `*` rule), so this box renders
    240 + 16 = 256px outer with the padding below. Specimen 7 renders the same 256 for the same reason:
    `padding: 8px 0` plus `block-size: 240px`, and no reset in its sheet either.
    AgentSessionViewportTests.The_chat_log_is_exactly_240px_whether_or_not_it_is_full measures
    `getComputedStyle().blockSize` for exactly this reason, and its own note records the first draft
    getting it wrong.

    This is the DIV that carries role="log" — see the markup's note on why the region wraps the list
    rather than being the list. So it needs no <ul> reset at all; those are on the rows below.
*/
.biztech-session-chat-log[b-ycnsnxkoev] {
    padding-block: 0.5rem;

    block-size: 15rem;
    overflow-y: auto;
    scrollbar-width: thin;
}

/*
    🔴 padding-inline-start, NOT padding: 0. UtilitySpecificityTests forbids the second outright, and its
    reason is real rather than stylistic: Blazor rewrites a scoped selector as `.x[b-xxxxxxxxxx]`, which
    is (0,2,0) against a utility class's (0,1,0), so `padding: 0` here would silently beat every
    biztech-pa-* anything ever passes this list. The inline-start property removes the BULLET INDENT,
    which is the only thing that actually needs removing, and it needs no exemption entry — the same
    choice the four <ul> resets already in that test's Declared set each made and wrote down.

    The block margin is a zero and DOES need one, which is the seventh <ul> entry in that register: the
    rows sit between the head's hairline and the composer's, and any block margin would open a gap
    between a hairline and the content it separates.
*/
.biztech-session-chat__rows[b-ycnsnxkoev] {
    list-style: none;
    margin: 0;
    padding-inline-start: 0;
}

.biztech-session-chat__empty[b-ycnsnxkoev] {
    padding: 1rem 0.75rem;
}

/* The failed-send alert's inset, which the flush paper does not supply and no `biztech-ma-*` utility
   exists to pass. Specimen 8's "send failed" card draws the same 8px 12px 0. */
.biztech-session-chat__notice[b-ycnsnxkoev] {
    padding: 0.5rem 0.75rem 0;
}

/* 🔴 THE COMPOSER'S OWN INSET AND HAIRLINE ARE NOT HERE, AND THE FIRST DRAFT PUT THEM HERE WRONGLY.
   Blazor's CSS isolation scopes this sheet to elements in THIS component's markup, so a class passed
   down as `<UiChatComposer Class="…">` lands on a div UiChatComposer renders and carries no [b-xxxxx]
   from here — the rule would compile, render, and match nothing. That is the ::deep trap
   CaseProgressStrip.razor.css records. They are declared in UiChatComposer.razor.css instead, on
   `.biztech-chat-composer__row`, which is also where specimen 7 draws them. */

/*
    A two-column grid rather than a flex row, because the avatar spans BOTH lines of a head row - the
    meta line and the body - and a follow-on's gutter has to line up under it exactly. 1.5rem is the
    specimen's 24px column and the avatar's own box, so the two cannot drift.
*/
/*
    🔴 THE INLINE PADDING IS THE ROW'S AND THE BLOCK PADDING IS THE HEAD ROW'S, WHICH IS THE INVERSE OF
    THE FIRST DRAFT AND DELIBERATELY SO. That draft put `padding: 0.25rem 0.75rem` here and then
    `padding-block: 0` on the follow-on - a box-model zero in a scoped sheet, at (0,2,1) with the scope
    attribute, which would outrank any utility. It passed UtilitySpecificityTests only because that
    sweep's regex matches the `padding:` shorthand and not the `padding-block` longhand.

    Harmless in fact - this <li> is rendered by this component, so no caller can pass it a utility - but
    sitting in a guard's blind spot is not a place to be when the same shape can be written with no zero
    at all. Widening that regex is a follow-up card's line, not this one's.
*/
.biztech-session-chat__msg[b-ycnsnxkoev] {
    display: grid;
    grid-template-columns: 1.5rem 1fr;
    column-gap: 0.5rem;
    padding-inline: 0.75rem;
}

/*
    A run head takes the vertical room; a follow-on does not, so a run reads as one block of text rather
    than as three rows that happen to be adjacent - the whole "three lines from one person is one act of
    speaking".
*/
.biztech-session-chat__msg[data-chat-row="message"][b-ycnsnxkoev] {
    padding-block: 0.25rem;
}

.biztech-session-chat__msg:hover[b-ycnsnxkoev] {
    background: var(--biztech-palette-background);
}

.biztech-session-chat__avatar[b-ycnsnxkoev] {
    grid-row: 1 / span 2;

    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    inline-size: 1.5rem;
    block-size: 1.5rem;

    border: 1px solid var(--biztech-palette-outline-subtle);
    border-radius: var(--biztech-radius-pill);
    background: var(--biztech-palette-background);
    color: var(--biztech-palette-text-secondary);
    font-size: var(--biztech-font-caption-size);
}

/* align-items: baseline so the name and the time sit on one line however their sizes differ, which is
   what makes "Sarah Chen  14:02" read as one caption rather than two stacked ones. */
.biztech-session-chat__meta[b-ycnsnxkoev] {
    grid-column: 2;
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
}

/*
    🔴 WEIGHT, NOT HUE. design-direction.html: "hierarchy from size and weight, never hue". A sender's
    name in a colour would need a contrast pair per mode and would say nothing a heavier weight does not
    — and it is the same argument the card makes for rendering "You" as a word.
*/
.biztech-session-chat__who[b-ycnsnxkoev] {
    font-weight: 600;
}

.biztech-session-chat__when[b-ycnsnxkoev] {
    font-size: var(--biztech-font-caption-size);
    color: var(--biztech-palette-text-secondary);
}

/* overflow-wrap so a pasted reference with no spaces in it wraps instead of widening the rail, which in
   a grid column would push the whole panel wider than the column it is in. */
.biztech-session-chat__body[b-ycnsnxkoev] {
    grid-column: 2;
    min-inline-size: 0;
    overflow-wrap: anywhere;
}

/*
    🔴 IN THE DOM AT ALL TIMES, HIDDEN BY OPACITY. "Hover-only information is not information", and the
    reason it is opacity rather than display: none or visibility: hidden is that both of those remove the
    text from the accessibility tree — so a screen-reader user would lose the one thing this rule exists
    to keep. opacity: 0 is read.

    grid-row: 1 so it sits beside the body rather than above it: this row has no meta line, so the body
    is on line 1 and an unplaced gutter would take line 1 and push the body to line 2.
*/
.biztech-session-chat__gutter[b-ycnsnxkoev] {
    grid-column: 1;
    grid-row: 1;
    align-self: center;
    text-align: end;

    font-size: var(--biztech-font-caption-size);
    color: var(--biztech-palette-text-secondary);

    opacity: 0;
    transition: opacity 120ms ease-in-out;
}

/*
    🔴 THE FOCUS SELECTOR IS ON THE LOG AND NOT ON THE ROW, and the two are different features rather
    than two spellings of one.

    :hover is per row — a pointer is on one message. :focus-within is on the LOG, because the log is the
    only tab stop this panel adds (R12.2 requires it: a fixed-height scroll container that only a mouse
    can move is unreachable on screen) and specimen 7's tabindex="0" per follow-on would put one stop per
    message on /i/{key}, ahead of the customer's own form fields, where AssigneeFormKeyboardTests walks
    the whole tab order. So one Tab reveals every time at once.
*/
.biztech-session-chat__msg[data-chat-row="follow-on"]:hover .biztech-session-chat__gutter[b-ycnsnxkoev],
.biztech-session-chat-log:focus-within .biztech-session-chat__gutter[b-ycnsnxkoev] {
    opacity: 1;
}

/*
    The two hairlines either side of the label are this row's own ::before and ::after, because a rule
    cannot be drawn through text. flex: 1 1 auto on both, so the label stays centred whatever its length
    — "Yesterday" and "Tue 18 Aug 2026" are the extremes and neither shifts the other's row.
*/
.biztech-session-chat__day[b-ycnsnxkoev] {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem 0.75rem 0.25rem;
}

.biztech-session-chat__day[b-ycnsnxkoev]::before,
.biztech-session-chat__day[b-ycnsnxkoev]::after {
    content: "";
    flex: 1 1 auto;
    block-size: 1px;
    background: var(--biztech-palette-outline-subtle);
}

.biztech-session-chat__day span[b-ycnsnxkoev] {
    font-size: var(--biztech-font-caption-size);
    color: var(--biztech-palette-text-secondary);
}

/* R12.8, R6.8, WCAG 2.3.3. The gutter's reveal is the one transition this sheet declares, and it is
   guarded here. The auto-scroll is the other half and cannot be reached from a media query at all —
   biztech-chat.js reads the same setting before its scrollTo, which is what the card means by "reduced
   motion is read in JavaScript as well as in CSS". */
@media (prefers-reduced-motion: reduce) {
    .biztech-session-chat__gutter[b-ycnsnxkoev] {
        transition: none;
    }
}
/* /Components/Cases/SessionContentPane.razor.rz.scp.css */
/*
    W6-11 · F8.9. Two rules, both §13.

    A pushed object is somebody ELSE'S file at somebody else's dimensions, arriving unannounced on a pane
    that is 375px wide on a phone. Neither rule is decoration: without them this component is the one that
    puts a horizontal scrollbar on the page.
*/

.biztech-session-content-image[b-3w29pabbb1] {
    /*
        Contained rather than covered, which is DeckViewer's own choice and its reasoning: a pushed
        document is something to read, so seeing all of it matters more than filling the frame. `cover`
        would crop whatever is at the edges - on a driving licence, the part the agent asked for.
    */
    max-width: 100%;
    height: auto;
    object-fit: contain;
}

.biztech-session-content-text[b-3w29pabbb1] {
    /*
        <pre> defaults to `white-space: pre`, so a document with long lines scrolls the PAGE sideways
        rather than itself - §13's own prohibition. `anywhere` rather than `break-word` because a stored
        document may carry an unbroken run of characters no dictionary would break.
    */
    white-space: pre-wrap;
    overflow-wrap: anywhere;

    /*
        🔴 AND NO `margin: 0`, WHICH THIS SHEET DECLARED UNTIL UtilitySpecificityTests CAUGHT IT. Blazor
        rewrites a scoped selector as `.x[b-xxxxxxxxxx]` - (0,2,0) against a utility class's (0,1,0) - so a
        reset here would silently beat any spacing utility a page passes this component, with
        the class still present in the markup. That test names three ways out; this is the third, and it
        was the honest one: the line was tidiness restating a `*` reset W0-53 removed, not a requirement.
        <pre>'s own block margin inside a UiPaper is ordinary spacing. If it ever needs changing, it needs
        a utility class in the markup rather than a reset in here.
    */
}
/* /Components/Cases/SessionDocumentRequests.razor.rz.scp.css */
/*
    W6-09 · F8.7. Layout only — no colour, no font, no border. R3.1: a hex in a component stylesheet is a
    bug, and everything visual here comes from UiPaper, UiText and UiChip.

    The bound is SessionChat's, for the same reason: an agent who asks for four documents on a long call
    must not have the form pushed off the screen by the record of it. A max-height rather than a fixed
    height, so a call with one ask takes only the room it needs.
*/
/*
    🔴 padding-inline-start, NOT padding: 0 — and this is SessionChat.razor.css's note, which applies
    identically here. UtilitySpecificityTests forbids the second outright: Blazor rewrites a scoped
    selector as `.x[b-xxxxxxxxxx]`, which is (0,2,0) against a utility class's (0,1,0), so `padding: 0`
    would silently beat every biztech-pa-* anything ever passed this element. The inline-start property
    removes the BULLET INDENT, which is the only thing that needs removing.

    The bottom margin is a real value rather than a zero, so it is not the reset that rule is about.
*/
.biztech-session-documents[b-1luibvo47r] {
    list-style: none;
    margin: 0 0 0.75rem;
    padding-inline-start: 0;
    max-height: 14rem;
    overflow-y: auto;
}

.biztech-session-documents > li + li[b-1luibvo47r] {
    margin-top: 0.5rem;
}
/* /Components/Cases/SessionFinishPanel.razor.rz.scp.css */
/*
    W6-35 · F8.21. Layout only — no colour, no font, no border. R3.1: a hex in a component stylesheet is a
    bug, and everything visual here comes from UiButton and UiText.

    No max-height, and the contrast with SessionDocumentRequests.razor.css is deliberate: that list is
    unbounded (an agent may ask for four documents on a long call), while this one is bounded by the case's
    own participants. A scroll box around three names would be chrome around nothing.
*/
/*
    🔴 padding-inline-start, NOT padding: 0 — SessionChat.razor.css's note, which applies identically here.
    UtilitySpecificityTests forbids the second outright: Blazor rewrites a scoped selector as
    `.x[b-xxxxxxxxxx]`, which is (0,2,0) against a utility class's (0,1,0), so `padding: 0` would silently
    beat every biztech-pa-* anything ever passed this element. The inline-start property removes the BULLET
    INDENT, which is the only thing that needs removing.

    The bottom margin is a real value rather than a zero, so it is not the reset that rule is about.
*/
.biztech-session-finish[b-23uq6pbckh] {
    list-style: none;
    margin: 0 0 0.75rem;
    padding-inline-start: 0;
}

/* 🔴 W6-53 · THE NAME AND ITS BUTTON WERE TOUCHING - MEASURED AT 0px IN CHROME ON 2026-09-09.

   The li stacked a block <p> on an inline-flex button with nothing between them, while every other
   pair in this card sits 8px or 12px apart. A ROW rather than a stack: at the rail's measured 294px
   the name and "Request signature" fit side by side, which also takes 20px off the card per signer -
   and flex-wrap drops the button under the name when a long name, or the "Asked" caption after it,
   needs the width. align-items: center puts the name's text box on the middle of the 32px control
   rather than on its top edge. 0.5rem is the 8px this card's other gaps already use (R5.4). */
.biztech-session-finish > li[b-23uq6pbckh] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

.biztech-session-finish > li + li[b-23uq6pbckh] {
    margin-top: 0.5rem;
}

/* The milestone row: the select and its button side by side, above "End session".

   Two controls that do different things, so they are grouped away from End rather than sitting in one
   undifferentiated column of buttons - the panel's own markup remark says why the two acts are separate,
   and a reader should be able to see that they are.

   align-items: end so the button's baseline meets the select's, which has a label above it and is
   therefore taller. flex-wrap so a narrow rail stacks them instead of squeezing the select. Both
   lengths are 4px-scale multiples (R5.4). */
.biztech-session-milestone[b-23uq6pbckh] {
    display: flex;
    flex-wrap: wrap;
    align-items: end;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
}

/* ::deep, and it is required rather than stylistic - CasePage.razor.css's rule for the same class,
   reproduced because the reasoning is the valuable half. Blazor stamps its scoped-CSS attribute on the
   elements THIS component renders; UiSelect renders its own markup, so a bare selector here would
   compile to `.biztech-milestone-control[b-xxxxxxxxxx]` and match nothing. `::deep` compiles to
   `[b-xxxxxxxxxx] .biztech-milestone-control`, which reaches the class Class= hands down and stays
   scoped to this component's subtree.

   12rem = 192px, so it stays a scale value. It exists so the select does not collapse to the width of
   its longest stage label in the flex row above. */
[b-23uq6pbckh] .biztech-milestone-control {
    min-width: 12rem;
}
/* /Components/Cases/SessionParticipantRail.razor.rz.scp.css */
/* 🔴 W6-40 · F8.1. What goes IN the rail column, never the column itself.

   This declares no width and no grid position on purpose: SessionStage owns the two-column split, and a
   component that placed itself could not be moved below the stage at sm, which is R13.3's reflow and the
   card's "at sm the rail moves below the stage rather than being clipped".

   Tokens only (R3.1). The card: "Where the mock's colours differ from the tokens, the tokens win." */

/* The block owns the gap between the roster panel and the signature slot, so neither child has to carry
   a trailing margin that would also apply when it is the last thing in the column. */
.biztech-session-rail[b-axqol7fle2] {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

/* 🔴 W6-53 · design §4. THE HEAD IS ONE ROW, MEASURED FROM THE SPECIMEN'S OWN RULE:

     .mk-paper-head { display:flex; align-items:baseline; justify-content:space-between;
                      gap:8px; margin-bottom:8px }

   `baseline`, not `center`, and it is the specimen's word: the title is .875rem and the count is
   .75rem, so centring two boxes of different heights leaves neither text sitting on the other's line.
   The margin is here rather than a biztech-mb-* on the element, because the element is a bare div this
   file owns rather than a UiText a page configures. */
.biztech-session-rail__head[b-axqol7fle2] {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    margin-block-end: 0.5rem;
}

/* 🔴 W6-53. 4px OF PADDING AND A RULE BETWEEN ROWS, WHICH IS .mk-people li EXACTLY:

     .mk-people li      { display:flex; align-items:center; gap:8px; padding:4px 0 }
     .mk-people li + li { border-top: 1px solid outline-subtle }

   This had 8px of padding and no separator. Both halves matter and they trade against each other: the
   rule is what makes four rows read as four rows, so the padding can halve and the panel gets 16px
   shorter per person - which is the rail budget specimen 3's own note is counting when it caps the
   video grid at four tiles.

   `li + li` and not `li:not(:last-child)`: the border belongs to the row BELOW the boundary, so a list
   of one draws no rule at all and there is never a line under the last name. */
.biztech-session-rail__row[b-axqol7fle2] {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding-block: 0.25rem;
}

.biztech-session-rail__row + .biztech-session-rail__row[b-axqol7fle2] {
    border-block-start: 1px solid var(--biztech-palette-outline-subtle);
}

/* 🔴 W6-53 · design §4. THE STATE IS A CHIP, WHICH IS .mk-pill:

     .mk-pill { display:inline-flex; align-items:center; gap:6px; block-size:24px; padding:0 10px;
                border-radius:pill; border:1px solid outline-subtle; background:background }

   The word and its glyph sat loose at the end of the row before this, so a row read as three unrelated
   things rather than as a name and its standing.

   🔴 THE COLOURS ARE THE APPLICATION'S TOKENS AND NOT THE SPECIMEN'S, BY THE SPECIMEN'S OWN RULE
   - "Where the mock's colours differ from the tokens, the tokens win", which this file's header already
   cites. It is not a preference here: .mk-pill-ok reads var(--mk-ok-bg), var(--mk-ok-bd) and
   var(--mk-ok-fg), and NONE OF THE THREE IS DEFINED ANYWHERE IN THAT DOCUMENT. An unresolved var() is
   invalid at computed-value time, so in a browser that chip paints a transparent background and a
   currentColor border. There is no colour there to copy, so the palette supplies it.

   flex: 0 0 auto so a chip never shrinks under a long name - the name has min-inline-size: 0 below and
   is the thing that gives. white-space: nowrap so "Not here yet" stays one line inside a 24px chip. */
.biztech-session-rail__pill[b-axqol7fle2] {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    gap: 0.25rem;
    block-size: 1.5rem;

    /* 8px and 4px, where the specimen says 10px and 6px — the avatar's own remark below carries why
       all three round rather than being registered as constants. */
    padding-inline: 0.5rem;
    border-radius: var(--biztech-radius-pill);
    border: 1px solid var(--biztech-palette-outline-subtle);
    background: var(--biztech-palette-background);
    color: var(--biztech-palette-text-secondary);
    font-size: var(--biztech-font-caption-size);
    line-height: 1;
    white-space: nowrap;
}

/* min-inline-size: 0 so a long name shrinks rather than pushing the state word out of the column. A
   grid or flex item's default minimum is its content, which is the same trap SessionStage's
   minmax(0, 1fr) answers one level up. */
.biztech-session-rail__name[b-axqol7fle2] {
    flex: 1 1 auto;
    min-inline-size: 0;
}

/* The mock draws photographs. There is no UiAvatar in the seam and a participant carries only a name and
   an email, so there is no image to show - initials in a circle say who the row is without inventing a
   component or a face. aria-hidden in the markup: the full name is beside it. */
.biztech-session-rail__mark[b-axqol7fle2] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    inline-size: 1.5rem;
    block-size: 1.5rem;
    border-radius: var(--biztech-radius-pill);
    background: var(--biztech-palette-background);
    border: 1px solid var(--biztech-palette-outline-subtle);
    color: var(--biztech-palette-text-secondary);

    /* 🔴 W6-53. 24px AND OUTLINED, WHICH IS .mk-avatar EXACTLY:

         .mk-avatar { inline-size:24px; block-size:24px; border-radius:pill;
                      background:background; border:1px solid outline-subtle;
                      font-size:.625rem; font-weight:600; color:text-secondary }

       This was a 28px FILLED circle on outline-subtle. Three differences: the size, the fill, and the
       type. The fill is the one that mattered - a solid chip beside a solid state chip made the row
       read as two badges with a name wedged between them, and the specimen draws the avatar as an
       outline so the only filled thing on the row is the state.

       🔴 THE SPECIMEN'S THREE OFF-SCALE LENGTHS ROUND, AND PortalStyleSheetTests IS WHY. It refused
       .625rem here and the chip's 10px padding and 6px gap below, in as many words: "round it to the
       scale, or - if it is genuinely load-bearing at that exact value - add it to LayoutConstants here
       AND to §5's registry in the guidelines, with the measurement." Two pixels of initial and two of
       chip padding are not load-bearing at an exact value, so they round rather than buy a registry
       entry and a name each. "DO" at caption size and weight 600 measures under 16px, so it still sits
       inside a 24px circle. */
    font-size: var(--biztech-font-caption-size);
    font-weight: 600;
}

/* 🔴 THE ABSENT ROW IS DIMMED AND NOT HIDDEN, which is I11's discipline applied to presence: somebody
   who is not in the room is still on the case, and a row that vanished would tell a presenter the
   person was never invited. Only the MARK dims - the name keeps its ordinary ink, because R3.5 makes
   text somebody has to read a meaningful thing to see. */
.biztech-session-rail__row[data-present="false"] .biztech-session-rail__mark[b-axqol7fle2] {
    color: var(--biztech-palette-text-disabled);
}

/* 🔴 W6-53. THE OK CHIP, AND ITS THREE STATES ARE THE THREE THE SPECIMENS DRAW.

   `Here` and `Signed` are .mk-pill-ok - something that has happened. `Signing…` is .mk-pill-warn -
   something in flight. `Waiting…`, `Reading only` and `Not here yet` keep the base chip, which is
   .mk-pill-wait: text-secondary on the plain surface, no colour at all. That is the specimen's own
   grading and it is also R3.5's, because a row nobody is waiting on should not draw the eye.

   Matched on data-present as well as data-signoff, so the ONE chip whose colour comes from presence
   rather than from a signature gets it - a NotExpected row reading `Here`. A row reading `Not here
   yet` is NotExpected too and must stay uncoloured, which is why this cannot key on the state alone. */
.biztech-session-rail__row[data-signoff="signed"] .biztech-session-rail__pill[b-axqol7fle2],
.biztech-session-rail__row[data-signoff="not-expected"][data-present="true"] .biztech-session-rail__pill[b-axqol7fle2] {
    border-color: var(--biztech-palette-success);
    color: var(--biztech-palette-success);
}

.biztech-session-rail__row[data-signoff="signing"] .biztech-session-rail__pill[b-axqol7fle2] {
    border-color: var(--biztech-palette-warning);
    color: var(--biztech-palette-warning);
}

/* 🔴 COLOUR IS THE SECOND SIGNAL, NEVER THE ONLY ONE (R3.6, WCAG 1.4.1). Each row already carries an
   icon and a word - "Signed", "Signing…", "Waiting…" - so these two rules add emphasis to something
   already legible without them, which is what makes them safe on a page nobody can reconfigure. */
.biztech-session-rail__row[data-signoff="signed"][b-axqol7fle2] {
    color: var(--biztech-palette-success);
}

.biztech-session-rail__row[data-signoff="signing"][b-axqol7fle2] {
    color: var(--biztech-palette-warning);
}

/* A native <progress> keeps the user agent's paint unless every one of these is set, and the two
   pseudo-elements are NOT interchangeable: Chrome and Safari fill through ::-webkit-progress-value
   inside ::-webkit-progress-bar, Firefox through ::-moz-progress-bar. Setting one leaves the other
   browser showing a stock bar in a colour no palette controls. */
.biztech-session-rail__progress[b-axqol7fle2] {
    inline-size: 100%;
    block-size: 0.25rem;
    margin-block-start: 0.5rem;
    appearance: none;
    border: 0;
    border-radius: var(--biztech-radius-pill);
    background: var(--biztech-palette-outline-subtle);
}

.biztech-session-rail__progress[b-axqol7fle2]::-webkit-progress-bar {
    border-radius: var(--biztech-radius-pill);
    background: var(--biztech-palette-outline-subtle);
}

.biztech-session-rail__progress[b-axqol7fle2]::-webkit-progress-value {
    border-radius: var(--biztech-radius-pill);
    background: var(--biztech-palette-primary);
}

.biztech-session-rail__progress[b-axqol7fle2]::-moz-progress-bar {
    border-radius: var(--biztech-radius-pill);
    background: var(--biztech-palette-primary);
}
/* /Components/Cases/SessionStage.razor.rz.scp.css */
/* 🔴 W6-40 · F8.1 · R13.1, R13.2, R13.3. One stage, one rail.

   R13.2 says structure responsively in MARKUP and use CSS only for what the grid cannot reach. This is
   that case: FormLayoutTests declares both mounting pages Surface.Workspace, and
   A_form_that_owns_its_fields_lays_them_out_on_a_grid asserts a workspace names zero UiGridItem - so the
   seam's grid is unavailable here. ResourceLibrary.razor.css:30,39 is the same shape for the same
   reason, the other hand round.

   🔴 minmax(0, 1fr) AND NOT 1fr, AND THE DIFFERENCE IS THE 375px CLAUSE. A grid track's default minimum
   is `auto`, which is its CONTENT - so a slide image wider than the column would push the track past the
   viewport and take the rail off the screen with it. `.biztech-main` declares overflow-x: hidden, so
   that would be CLIPPED rather than scrolled: the page would look fine and the rail would simply not be
   there. R13.8 records the same trap on the board. */

.biztech-session-stage__columns[b-a767le4z9p] {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1rem;
    padding: 1rem;
}

/* R13.1: md is 960, and MudBlazor's breakpoints are the application's so a page never disagrees with
   its grid. Above it the rail takes a fixed column and the stage takes what is left.

   20rem because the rail's content is a name, a state word and a signature pad - narrower than
   ResourceLibrary's 15rem list would suggest is enough, because a pad has to be drawable on. Below md
   the single-column rule above applies and the rail stacks under the stage, which is the card's "at sm
   the rail moves below the stage rather than being clipped".

   align-items: start so the rail does not stretch to the height of a tall deck and leave its panel
   floating in empty space. */
@media (min-width: 960px) {
    .biztech-session-stage__columns[b-a767le4z9p] {
        grid-template-columns: minmax(0, 1fr) 20rem;
        align-items: start;
    }
}

/* Both tracks repeat min-inline-size: 0 because minmax() governs the TRACK and this governs the ITEM -
   a flex or grid item's own automatic minimum is its content too, and one without the other still
   overflows. */
.biztech-session-stage__main[b-a767le4z9p] {
    min-inline-size: 0;
}

/* 🔴 W6-42 · A FLEX COLUMN, AND NO scrolling OF ITS OWN. The card: "The rail can hold a child that
   fills it, with ONE scroll container inside the rail and no nesting." The one is SessionChat's log,
   which caps itself at 14rem and scrolls there. A rail that scrolled too would put a scrollbar inside a
   scrollbar, which is R13.3's "nine nested scrollers" one column in - and neither is reachable by
   keyboard in a predictable order.

   The gap is on this element rather than on each child, so nothing has to carry a trailing margin that
   would also apply when it is last. SessionParticipantRail.razor.css makes the same argument one level
   in. */
.biztech-session-stage__rail[b-a767le4z9p] {
    min-inline-size: 0;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

/* 🔴 ::deep IS REQUIRED, NOT STYLISTIC. The rail's children come from the CALLING PAGE's render
   fragment and carry the PAGE's scope attribute, so a plain rule here would compile to
   `.biztech-session-stage-fill[b-xxxxxxxxxx]` and match nothing at all - the trap MediaRoom.razor.css
   and EducationItemFrame.razor.css each record, both found in a browser rather than by a test.

   flex: 1 1 auto with min-block-size: 0 is what lets a bounded rail hand its leftover height to one
   child, which is specimen 1's shape: chat takes what is left and only its log scrolls. In an
   auto-height column there is no leftover, so this costs nothing today - and it is what W6-44's fixed
   240px log lands into. */
.biztech-session-stage__rail[b-a767le4z9p]  .biztech-session-stage-fill {
    flex: 1 1 auto;
    min-block-size: 0;
}

/* 🔴 W6-53 · THE RAIL'S gap IS THE ONLY SPACING BETWEEN ITS CARDS, AND THIS RULE IS WHAT MAKES
   THE SENTENCE ABOVE TRUE.

   Measured in Chrome at 1264x615 on 2026-09-09: the finish card sat 32px above the participants card
   while the participants card sat 16px above chat, and the last card carried a trailing 16px INSIDE
   the rail. The rail's cards carry their own biztech-mb-4, which ADDS to gap rather than replacing it
   - the exact case the gap remark above says nothing should have to carry. One rail, two different
   gaps, and no reason a reader could see for either.

   🔴 THE MARGIN CANNOT COME OFF THE COMPONENTS. SessionChat also renders on AssigneeForm in
   normal block flow, where its own bottom margin is the only thing separating it from the signature
   prompt directly below it. So the RAIL neutralises the margin here and the cards keep one that is
   still correct everywhere else.

   ::deep for .biztech-session-stage-fill's reason directly above - the cards come from the calling
   page's fragment and carry the PAGE's scope attribute. Every rail card holds exactly ONE UiPaper and
   none nests a second, so this reaches each card and nothing inside one. */
.biztech-session-stage__rail[b-a767le4z9p]  .biztech-paper {
    margin-block-end: 0;
}

/* 🔴 W6-42 · R13.1, R13.3, R6.1. THE BAND PINS, AND MOBILE-FIRST IS WHY THE BASE RULE IS THE ONE THAT
   REVERSES IT.

   R13.3: "every desktop-only rule must be reversed there (container height, per-column scroll, sticky
   headings) or you get nine nested scrollers and nine headings pinned to the viewport." The measurement
   behind that sentence is the board's. Here the number is blunter: the demonstration machine is
   1264x615, and a band pinned on a 375px phone takes a fifth of the viewport and never gives it back.

   So the BASE rule is what a phone gets and the 600px block adds the pin. This file's existing 960px
   block is the same shape for the same reason.

   R6.1: never a raw z-index, use a layer. --biztech-layer-sticky is the layer
   .biztech-board-column-header takes (app.css:875) and for the same job.

   `top` rather than inset-block-start: that is the property the house sticky pattern uses, and this
   frame is not written for a vertical writing mode. */
.biztech-session-stage__bar[b-a767le4z9p] {
    position: static;
}

@media (min-width: 600px) {
    .biztech-session-stage__bar[b-a767le4z9p] {
        position: sticky;
        top: 0;
        z-index: var(--biztech-layer-sticky);
    }
}

/* 🔴 W6-42 · specimen 1. The toolbar row, and it is the FRAME's own element rather than a Ui*
   component - R14.1's "go one layer down only when the layer above cannot reach", and layer 3 reaches a
   flex row. It wraps, so a narrow stage stacks the controls instead of pushing the column wide. The
   margin is on this element rather than on each control, so a card adding a control adds no spacing
   decision with it. */
.biztech-session-stage__toolbar[b-a767le4z9p] {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-block-end: 1rem;
}

/* 🔴 W6-53 · design §4. THE ROW IS A SURFACE, AND A FLEX ITEM CANNOT BE ONE.

   The specimen's toolbar is a bordered card the full width of the stage:

     .mk-toolbar { display:flex; align-items:center; gap:8px; flex-wrap:wrap; padding:8px;
                   background:surface; border:1px solid outline-subtle;
                   border-radius:default }

   UiToolbar already declares exactly those four surface properties, so nothing needed drawing - what
   was wrong is that this element is a FLEX ROW and UiToolbar was one of its items, so it sized to its
   own content. Driven in Chrome on 2026-09-09: the agent's toolbar card stopped at 620px on an 1130px
   stage, with Leave video's auto margin pushing against the inside of a shrink-wrapped box instead of
   against the stage edge. Reported as "the toolbar still not up to standard".

   flex: 1 1 auto gives the card the line. min-inline-size: 0 lets it shrink below its content so its
   OWN flex-wrap has something to do - UiToolbar.razor.css makes that argument for itself one level in,
   and a flex item's automatic minimum is its content, which is the same trap SessionStage's
   minmax(0, 1fr) answers one level up.

   ::deep for .biztech-session-stage-fill's reason above: UiToolbar renders its own markup, so a plain
   rule here would compile to `.biztech-toolbar[b-xxxxxxxxxx]` and match nothing. */
.biztech-session-stage__toolbar[b-a767le4z9p]  .biztech-toolbar {
    flex: 1 1 auto;
    min-inline-size: 0;
}

/* 🔴 W6-43 · specimen 1's spacer, as a class the pages can name. Swap sits at the FAR END of the row,
   away from the controls that act on the artefact, because it acts on the FRAME - which of the two
   columns the artefact and the video are in.

   ::deep for .biztech-session-stage-fill's reason one slot along: the toolbar's children come from the
   CALLING PAGE's render fragment and carry the PAGE's scope attribute, so a plain rule here would
   compile to `.biztech-session-toolbar-end[b-xxxxxxxxxx]` and match nothing at all. */
.biztech-session-stage__toolbar[b-a767le4z9p]  .biztech-session-toolbar-end {
    margin-inline-start: auto;
}

/* 🔴 W6-53. THE SECOND ONE GETS NO AUTO MARGIN, AND THIS IS FLEXBOX ARITHMETIC RATHER THAN A
   PREFERENCE.

   Free space in a flex line is distributed EQUALLY among every auto margin on it. So the moment Leave
   video joined Swap in wearing this class, each took HALF the slack and Leave video landed in the middle
   of the row - measured in Chrome on 2026-09-09 at x=743 on a 1770px pane, which is nobody's idea of
   "the far end".

   The first one in source order opens the gap; every later one sits against it. Which is also the
   reading order the two specimens want: Leave video ends the call, Swap moves the frame, and Swap is
   last because it acts on the FRAME rather than on the call. */
.biztech-session-stage__toolbar[b-a767le4z9p]  .biztech-session-toolbar-end
    ~ .biztech-session-toolbar-end {
    margin-inline-start: 0;
}
/* /Components/Cases/SessionStageBar.razor.rz.scp.css */
/* 🔴 W6-40 · F8.1. The band the proposal draws across the top of a live session surface.

   R6.6 and §6's 10.0 note — "a border carries depth on an in-flow surface; a shadow is only for the top
   layer" (ADR 0026 D-E). This is in flow under the tenant header, so it takes an edge and no shadow.

   Tokens only. The card: "Where the mock's colours differ from the tokens, the tokens win", and R3.1
   makes a hex in a stylesheet a bug. The mock's live dot is green; the palette's green is Success, and
   that is the role rather than the value. */

.biztech-session-bar[b-sm0rkas1bw] {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.75rem;
    padding: 0.5rem 1rem;
    background: var(--biztech-palette-surface);

    /* `outline` and not `outline-subtle`: this edge is what separates the band from the stage below it,
       which makes it meaningful under R3.5 — and R3.7 leaves outline-subtle deliberately untested, so a
       rule that must be seen may not rest on it. */
    border-block-end: 1px solid var(--biztech-palette-outline);
}

/* Not animated. R6.7's reduced-motion guard would be a second rule for a pulse nobody asked for, and a
   blinking dot in front of an audience is the kind of ornament that reads as a fault. */
.biztech-session-bar__dot[b-sm0rkas1bw] {
    inline-size: 0.5rem;
    block-size: 0.5rem;
    flex: 0 0 auto;
    border-radius: var(--biztech-radius-pill);
    background: var(--biztech-palette-success);
}

/* The proposal's separators. Ornament, and nothing depends on seeing them — which is exactly what R3.7
   says outline-subtle is for. */
.biztech-session-bar__rule[b-sm0rkas1bw] {
    inline-size: 1px;
    block-size: 1rem;
    flex: 0 0 auto;
    background: var(--biztech-palette-outline-subtle);
}

/* 🔴 W6-45 · specimen 1. The reference is a fact you look up, not the name you read from the back of
   the room, so it takes the secondary ink the count beside it already takes. ADR 0026 D-B: hierarchy
   from size and weight, and the one step down here is the type scale's, never a hue. */
.biztech-session-bar__ref[b-sm0rkas1bw] {
    color: var(--biztech-palette-text-secondary);
}

/* 🔴 W6-45 · specimen 1's spacer. The exits sit at the FAR end of the band, which is what makes them
   read as a pair rather than as two more items in a row of facts.

   `margin-inline-start: auto` and NOT a UiSpacer, because this row wraps: a flex spacer that wrapped
   would become an empty element on a line of its own and stop pushing anything, while an auto margin
   simply has no leftover space to take and behaves. That is design §8's 375px band, where the row
   splits in two and the exits sit under the title rather than beside it.

   NO ::deep, and that is worth saying because the neighbouring frame needs one. The children come from
   the calling page and carry the PAGE's scope attribute, so a rule aimed AT a child would match nothing
   here - which is what SessionStage.razor.css records for .biztech-session-toolbar-end. Everything this
   rule sets is on the wrapper, which is this component's own element, and `gap` reaches its children
   from the parent without naming them. */
.biztech-session-bar__exits[b-sm0rkas1bw] {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-inline-start: auto;
}
/* /Components/Cases/SignaturePad.razor.rz.scp.css */
/* T12 · the signature pad's own styles.

   Scoped, so they arrive through BizTech.Portal.styles.css - already referenced from Pages/_Host.cshtml,
   which is why this file needs no host-page change. Case-list styles live in wwwroot/css/app.css instead
   because they restyle MudBlazor's own markup, which scoped CSS cannot reach. */

/* 🔴 W6-51. THE PAD IS ONE COLUMN, AND THE NUMBER HAS ONE HOME.

   The frame capped itself at the canvas's backing store while "Signing as" and the Clear/Sign row took
   the paper's full width, because all three were siblings. Three left edges aligned and three right edges
   not - reported from a browser on 2026-09-08, and worst on the agent's stage where the paper is 640px in
   a 1264px viewport and unbounded on a larger screen.

   A custom property rather than 600px written twice: the frame's own comment explains that the number IS
   the canvas's 600x200 backing store, and two copies of a number that must agree with a markup attribute
   is how they come to disagree. It inherits, so the frame below reads it without naming a selector. */
.biztech-signature[b-ck7cu6itya] {
    --biztech-signature-inline-size: 600px;

    max-inline-size: var(--biztech-signature-inline-size);
}

.biztech-signature-frame[b-ck7cu6itya] {
    /* Dashed, so it reads as "write here" rather than as a disabled input.

       The #cbd2d9 fallback is gone: it was here because --mud-palette-lines-default was a token the
       theme had never set, and T24's palette always sets it. */
    border: 1px dashed var(--biztech-palette-outline);

    /* T29 N3 · R5.5. This was a literal 4px, which quietly contradicted BizTechThemeProvider's own
       comment - it says the 6px default means "no visual change anywhere except the signature frame,
       which was 4px and becomes 6px like everything else". The frame never got the message, because
       nothing pointed it at the variable. Read the variable; do not restate the number. */
    border-radius: var(--biztech-radius-default);

    /* Surface, not white - so the frame matches the panel it sits in when the page is dark.
       The CANVAS inside still paints its own opaque white before any stroke, and that is
       deliberate rather than an oversight: a transparent PNG on a dark background is an invisible
       audit record. See biztech-signature.js. */
    background: var(--biztech-palette-surface);

    /* The canvas has a fixed 600x200 backing store and max-width lets the DISPLAYED size shrink below it
       on a phone. The module scales pointer coordinates by the displayed/backing ratio, so drawing still
       lands under the finger - see positionOf() in biztech-signature.js. */
    max-inline-size: var(--biztech-signature-inline-size, 600px);
    overflow: hidden;
}

.biztech-signature-canvas[b-ck7cu6itya] {
    display: block;
    width: 100%;
    height: auto;

    /* Both needed on touch. touch-action stops the browser claiming the gesture for a scroll or a
       double-tap zoom before pointermove ever fires; without it the pad is unusable on a phone and works
       perfectly on the desktop it was tested on. */
    touch-action: none;
    cursor: crosshair;
}
/* /Components/Compliance/ComplianceFigureBar.razor.rz.scp.css */
/*
    W8-17 · F10.9. One compliance dimension: a proportion bar, its figures, and the derived label.

    🔴 NO DATA-VIZ PALETTE (D2). Three declared semantic roles - success, warning, info - and nothing
    else. §16's U1 is about a CATEGORICAL ORDER and sequential ramps; three roles that already exist and
    already have contrast-checked "on" inks are neither, so U1 stays open and this card escalates
    nothing.

    Every colour is a --biztech-* token (PortalStyleSheetTests sweeps every scoped sheet for a
    hand-written hex), every length is a 4px-scale step, the radius reads its variable, and there is no
    @media (prefers-color-scheme) anywhere (I16, R7.3). The two modes differ because the tokens do.
*/
.biztech-compliance-figure[b-hceaxh6ysc] {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/*
    🔴 THERE IS NO RULE FOR THE TITLE, AND ITS ABSENCE IS A FINDING RATHER THAN AN OMISSION.

    This sheet carried `.biztech-compliance-figure__title { margin: 0 }` and it was DEAD: UiText renders
    its own element, so a scoped class handed to it is rewritten as `.x[b-xxxxxxxxxx]` against markup
    this component never emitted, and matches nothing. CaseProgressStrip's own comment records the same
    ::deep trap. UtilitySpecificityTests found it by flagging a reset on an element that is already in
    the document block - h1 to h6 are reset there once - which is the second reason it should not exist.
*/
/*
    The drawn part. flex rather than a grid of percentages, because the SHARE is what the data says and
    flex-grow is the one property that takes a bare ratio - so nothing here has to compute a percentage
    and no length is written into the markup.
*/
.biztech-compliance-figure__bar[b-hceaxh6ysc] {
    display: flex;
    flex-direction: row;
    inline-size: 100%;
    block-size: 12px;
    overflow: hidden;
    border-radius: var(--biztech-radius-pill);

    /* A ground for the case nothing is drawn on: an empty period renders an empty rail rather than a
       collapsed nothing, so the bar reads as "no cases" instead of as a missing element. */
    background-color: var(--biztech-palette-outline-subtle);
}

/*
    🔴 THE SHARE ARRIVES AS A UNITLESS COUNT IN --figure-share, SET ON THE ELEMENT.

    R5.4's subject is "a magic length that no stylesheet sweep can see". This is neither magic nor a
    length - it is the datum being drawn - and a proportion cannot be expressed any other way.

    🔴 AND IT IS DELIBERATELY OUTSIDE THE --biztech-* NAMESPACE, which TokenSufficiencyTests taught me
    by going red. That namespace is the TOKEN namespace: every --biztech-* read must have a
    declaration, because BizTechCssVariables.Emit is what declares them and a var() resolving to
    nothing paints nothing, with no error and no warning. This value is not a token - it is per-element
    data, set inline from a count, and declaring it centrally would be asserting that a datum is part
    of the design system. A local custom property says what it is.

    The fallback of 0 means a segment whose property never arrived takes no space rather than an equal
    share, which would misreport the split.
*/
.biztech-compliance-figure__segment[b-hceaxh6ysc] {
    flex-grow: var(--figure-share, 0);
    flex-shrink: 1;
    flex-basis: 0;
    min-inline-size: 2px;
}

.biztech-compliance-figure__segment--met[b-hceaxh6ysc] {
    background-color: var(--biztech-palette-success);
}

.biztech-compliance-figure__segment--outstanding[b-hceaxh6ysc] {
    background-color: var(--biztech-palette-warning);
}

.biztech-compliance-figure__segment--not-applicable[b-hceaxh6ysc] {
    background-color: var(--biztech-palette-info);
}

/*
    The figures, which are the ANSWER rather than the decoration above them. A row that wraps, so three
    answers sit side by side at 1440 and stack at 375 without a media query.
*/
.biztech-compliance-figure__figures[b-hceaxh6ysc] {
    display: flex;
    flex-flow: row wrap;
    gap: 16px;
    margin: 0;
    padding-inline-start: 0;
    list-style: none;
}

.biztech-compliance-figure__figure[b-hceaxh6ysc] {
    display: flex;
    flex-direction: row;
    align-items: baseline;
    gap: 8px;
}

/*
    The swatch ties a figure to its segment. It is aria-hidden and it is never the only channel - the
    count and the word sit beside it - so losing it costs nothing but the tie.
*/
.biztech-compliance-figure__swatch[b-hceaxh6ysc] {
    flex: 0 0 auto;
    inline-size: 8px;
    block-size: 8px;
    border-radius: var(--biztech-radius-pill);

    /* Nudged off the baseline so a round swatch sits with the digits rather than under them. */
    transform: translateY(-1px);
}

.biztech-compliance-figure__swatch--met[b-hceaxh6ysc] {
    background-color: var(--biztech-palette-success);
}

.biztech-compliance-figure__swatch--outstanding[b-hceaxh6ysc] {
    background-color: var(--biztech-palette-warning);
}

.biztech-compliance-figure__swatch--not-applicable[b-hceaxh6ysc] {
    background-color: var(--biztech-palette-info);
}

/*
    The link and the plain figure are the SAME BOX, so a viewer who may not open cases (D3) sees the
    figures in the same places rather than a differently-shaped page.
*/
.biztech-compliance-figure__link[b-hceaxh6ysc],
.biztech-compliance-figure__figure > .biztech-compliance-figure__count[b-hceaxh6ysc] {
    display: inline-flex;
    align-items: baseline;
    gap: 8px;
}

.biztech-compliance-figure__link[b-hceaxh6ysc] {
    color: inherit;
    text-decoration: none;
}

/* R3.11's focus ring is declared once, in app.css. Nothing here cancels or restates it. */
.biztech-compliance-figure__link:hover .biztech-compliance-figure__label[b-hceaxh6ysc] {
    text-decoration: underline;
}

.biztech-compliance-figure__count[b-hceaxh6ysc] {
    font-weight: 600;
}

.biztech-compliance-figure__label[b-hceaxh6ysc] {
    color: var(--biztech-palette-text-secondary);
}
/* /Components/Content/DocumentViewPanel.razor.rz.scp.css */
/*
    W2-09 · F4.12. The document viewer's shape — a head, a status line, the frame, and the actions.

    🔴 EVERY COLOUR IS A --biztech-palette-* ROLE AND THE RADIUS IS --biztech-radius-default.
    PortalStyleSheetTests sweeps scoped sheets for both, and DeckViewer.razor.css records what the sweep
    caught the first time it shipped: `var(--biztech-radius-md)`, a token that does not exist. There are no
    spacing variables in this system, so lengths are literal rem — AssigneeForm.razor.css's arrangement.

    🔴 EVERY LENGTH IS A WHOLE MULTIPLE OF THE 4px BASE UNIT (R5.1, R5.4), so the length sweep passes with
    NO layout-constant registry row. R5.4's own note is explicit that adding one is not the cheap way out of
    that failure, and it would also mean editing §5 of the guidelines — outside this card's projects.
    24rem is 96 base units; 0.5rem is 2; 1rem is 4.

    I16 · no prefers-color-scheme anywhere. The mode is a token, resolved by UiThemeTokens.
*/

.biztech-document-viewer[b-1ztnq49ncu] {
    /*
        A detail pane beside what it acts on (R9.6), so it wears a surface rather than floating: a frame
        with no ground of its own would read as part of the listing above it.

        🔴 OutlineSubtle, AND `--biztech-palette-divider` IS THE MISTAKE THIS FILE ALREADY MADE ONCE.
        EducationItemFrame.razor.css records it in as many words: that variable is not one of the palette's
        22 roles, "and a var() that resolves to nothing falls back to no colour at all, so the placeholder
        would have rendered invisible with every test still green. The colour sweep checks for literals, not
        for names that resolve." TokenSufficiencyTests is what does check, and it named this line.
        OutlineSubtle is the hairline role and it flips with the mode.
    */
    border: 1px solid var(--biztech-palette-outline-subtle);
    border-radius: var(--biztech-radius-default);
    background-color: var(--biztech-palette-surface);
    padding: 1rem;
    margin-bottom: 1rem;
}

.biztech-document-viewer-head[b-1ztnq49ncu] {
    /*
        The heading and the way out on one line, with the close control pushed to the end. `gap` rather than
        a margin on the button, so the two never collide when a long title wraps.
    */
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.5rem;
}

.biztech-document-status[b-1ztnq49ncu] {
    /*
        🔴 IT KEEPS ITS PLACE WHEN IT HAS NOTHING TO SAY, AND THAT IS R12.6a's SHAPE IN CSS RATHER THAN A
        NICETY. The region is present in every state so that its FIRST message is an announcement (see the
        component), which means it is empty on the Inline path — and an empty box that then grows by its own
        height would shift the frame down on the frame where the reader is least able to predict it. R9.10's
        argument about a loading region, applied to a live one.
    */
    min-height: 1.25rem;
    margin-top: 0.5rem;
}

.biztech-document-frame[b-1ztnq49ncu] {
    /*
        🔴 THE FOCUS TARGET, AND IT MUST SHOW WHERE THE FOCUS IS. R12.2 makes this element focusable so a
        keyboard reader can reach the framed document's own pager; R3.11 then requires the ring, and app.css
        declares it once for every custom interactive element — so there is deliberately NO outline
        declaration here. `outline: none` would be an R12.8 violation and W0-53 measured what it cost when
        MudBlazor's sheet did it globally.
    */
    margin-top: 1rem;
}

.biztech-document-object[b-1ztnq49ncu] {
    /*
        🔴 A HEIGHT IN rem RATHER THAN A VIEWPORT UNIT OR AN ASPECT RATIO, AND THE REASON IS THE 375px
        ACCEPTANCE. `100vh` on a phone is the wrong number twice over — the browser chrome is not in it, and
        it ignores the panel's own padding — while an aspect-ratio tall enough to read a portrait page would
        make the frame taller than the screen at narrow widths. 24rem is what the deck reading area uses and
        it is measured against the same acceptance.

        `width: 100%` with `border: 0`: the frame's border is the WRAPPER's, so a document with its own white
        ground does not draw a second line inside ours.

        🔴 AND NO overflow WRAPPER AROUND IT. PortalLandmarkOverflowTests' header records why the obvious fix
        is the wrong one here: one overflow-x on a wrapper excludes its whole SUBTREE from the landmark walk,
        so hiding the frame inside a scroller would turn a real overflow into a green test measuring almost
        nothing. The frame is exactly the element somebody would reach for that with, which is what makes
        DocumentViewerViewportTests' Eligible guard load-bearing on this surface.
    */
    display: block;
    width: 100%;
    height: 24rem;
    border: 0;
    border-radius: var(--biztech-radius-default);
    background-color: var(--biztech-palette-background);
}

.biztech-document-actions[b-1ztnq49ncu] {
    /*
        Download first, Reopen after it — the download is the one control present in every state, and R9.17's
        "one order for the whole console" argument applies to a pair as much as to a list: a reader scanning
        this panel twice should not have to find the same control in two places.

        `flex-wrap` so the pair stacks rather than overflowing at 375px, which is what R13.3 asks of every
        desktop-only row.
    */
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 1rem;
}
/* /Components/Content/LibraryFolderManager.razor.rz.scp.css */
/* W2-22 · F4.2. The folder management panel's own presentation, and nothing else's.

   Lengths are multiples of the 4px base unit (R5.1, R5.4), so no layout-constant registry row is needed.
   Colours come from --biztech-* only (R3), which PortalStyleSheetTests asserts over every scoped sheet.
   No prefers-color-scheme anywhere (I16): the mode is a token, not a media query. */

/* One folder's controls, stacked. STACKED RATHER THAN A ROW, and the viewport is why: at 375px a name
   field, a slug field, a switch and four buttons cannot share a line without one of them going off the
   edge - and the tree beside it has already taken part of the width. The two consequence sentences have
   to sit directly under the control they describe, which a wrapping flex row does not guarantee. */
.biztech-library-folder-row[b-pc6cou0o4e] {
    display: flex;
    flex-direction: column;
    gap: 8px;

    padding: 12px;

    /* A hairline between rows, so three folders read as three things rather than one long form.
       R5.5 · the radius is READ rather than restated, and the outline colour is a token (R3). */
    border: 1px solid var(--biztech-palette-outline-subtle);
    border-radius: var(--biztech-radius-default);
}
/* /Components/Content/LibraryFolderTree.razor.rz.scp.css */
/* W2-06 · F4.1. The folder tree's own presentation, and nothing else's.

   Lengths are multiples of the 4px base unit (R5.1, R5.4), so no layout-constant registry row is needed -
   and R5.4's note is explicit that adding one is not the cheap way out of that sweep.

   Colours come from --biztech-* only (R3), which PortalStyleSheetTests asserts over every scoped sheet it
   discovers. No prefers-color-scheme anywhere (I16): the mode is a token, not a media query. */

.biztech-library-branch[b-yer7n94jmd] {
    list-style: none;
    margin: 0;
    padding-inline-start: 0;
}

/* Nested levels step in by one base unit so the hierarchy reads, and no further: at eight permitted
   levels (CK_ContentFolders_Depth) a larger step would push the deepest folder off a 375px screen. */
.biztech-library-branch .biztech-library-branch[b-yer7n94jmd] {
    padding-inline-start: 12px;
}

/* 🔴 THE DISCLOSURE TRIANGLE IS KEPT, NOT REPLACED. It is the only affordance that says "this opens",
   the browser draws it in the platform's own idiom, and it rotates on open for free. A custom marker
   would be a glyph to keep, a rotation to animate and a reduced-motion guard to write (R6.8). */
.biztech-library-folder[b-yer7n94jmd] {
    display: flex;
    align-items: center;
    gap: 8px;

    /* R5.8's touch target under a coarse pointer. A summary is 20px tall by default, which is half of
       what a thumb can hit - and the tree is the narrowest surface this card has. */
    min-height: 44px;
    padding-inline: 4px;

    cursor: pointer;
    color: var(--biztech-palette-text-primary);
}

/* The only text in the tree that is not the folder's own name, so it reads as secondary. It is a real
   anchor (R9.18) and keeps .biztech-link's focus ring and underline from app.css. */
.biztech-library-open[b-yer7n94jmd] {
    display: inline-block;
    padding-block: 8px;
    padding-inline-start: 28px;
    font-size: var(--biztech-font-caption-size);
}
/* /Components/Content/PublishPackPanel.razor.rz.scp.css */
/*  W2-14 · F4.10.

    A published site's address is one long token with no space and no hyphen a browser will break at -
    `http://packs.acme.example.com/agency-disclosure/` - so its min-content width is the min-content
    width of whatever box it sits in. The frontend guidelines are explicit that the page body must never
    scroll horizontally, and an unbreakable token is the ordinary way that happens.

    `anywhere` rather than `break-word`, because break-word will not break a token that is already
    alone on its line, which is exactly this case. On the ELEMENT rather than the panel, so only the
    address wraps oddly and the sentence around it still breaks at its spaces.

    Defensive rather than a fix for a measured failure: the one geometry failure this panel did cause
    was about WHERE it was mounted, not how wide it is - see the comment on its call site in
    ResourceLibrary.razor.
*/

[data-publish-pack-address][b-vw7mzg5klu] {
    overflow-wrap: anywhere;
}
/* /Components/Education/EducationExternalLink.razor.rz.scp.css */
/*
    W7-02 · the link out of a content card.

    The glyph sits on the text's baseline row rather than above it, and the label wraps rather than
    truncating: the host is part of the sentence R13.4 requires to be readable without a hover, so cutting
    it off would defeat the rule it exists for.

    Colours are the palette's link and text roles; every length is a multiple of the 4px base unit.
*/

/*
    🔴 min-height IS R5.8's 44px BY CONSTRUCTION RATHER THAN BY ACCIDENT. Without it the target passed the
    coarse-pointer assertion only because the label happens to wrap to two lines at 375px - so a shorter
    label, or a translated one, would have dropped under 44px with the test still green on the long ones.
    44px is a named layout constant in PortalStyleSheetTests, not an off-scale length.
*/
.biztech-education-link[b-vchwsqc041] {
    align-items: center;
    color: var(--biztech-palette-primary);
    display: inline-flex;
    gap: 4px;
    margin-top: auto;
    min-height: 44px;
    padding-top: 8px;
}

.biztech-education-link:hover[b-vchwsqc041] {
    text-decoration: underline;
}
/* /Components/Education/EducationItemFrame.razor.rz.scp.css */
/*
    W7-02 · one content card.

    🔴 THE ASPECT RATIO IS DECLARED ONCE AND BOTH STATES READ IT, which is R11.7's "a decided aspect ratio"
    doing a second job: the picture and its absence occupy the same box, so a card does not resize under the
    reader when a thumbnail resolves out of OnAfterRenderAsync. 16/9 because the ported catalogue is
    articles, decks and video stills rather than portraits.

    Every colour is a --biztech-* role and every length is a multiple of the 4px base unit, which is what
    PortalStyleSheetTests' two sweeps require of any scoped sheet - they pick this file up with no edit.
*/

.biztech-education-card[b-66tbn88m70] {
    display: flex;
    flex-direction: column;
    gap: 8px;
    height: 100%;
    padding: 16px;
}

/*
    Both states, so they cannot disagree about the box. object-fit is R11.7's second requirement: a
    thumbnail is whatever an author uploaded, and cover crops it rather than distorting a face or a slide.
*/
.biztech-education-thumb[b-66tbn88m70] {
    aspect-ratio: 16 / 9;
    border-radius: var(--biztech-radius-default);
    display: block;
    object-fit: cover;
    width: 100%;
}

/*
    The designed absence: a dashed hairline on the paper's own surface, and nothing inside it. No icon and
    no word, because the type and the title are text directly below - anything drawn here would be a third
    statement of what the card already says.

    🔴 OutlineSubtle RATHER THAN A FILL, AND THE ALTERNATIVE WAS A VARIABLE THAT DOES NOT EXIST. This rule
    first read `--biztech-palette-action-disabled-background` and `--biztech-palette-divider`, neither of
    which BizTechPalette emits - and a var() that resolves to nothing falls back to no colour at all, so the
    placeholder would have rendered invisible with every test still green. The colour sweep checks for
    literals, not for names that resolve. The palette's 22 roles carry no neutral fill; OutlineSubtle is the
    hairline role, #E3E6EA on light and Neutral800 on dark, so it flips with the mode.
*/
.biztech-education-thumb-placeholder[b-66tbn88m70] {
    border: 1px dashed var(--biztech-palette-outline-subtle);
}

/*
    Two lines, then an ellipsis. A hub of 308 cards must keep its rows aligned, and a title is a content
    title rather than a label - so it is clamped rather than bounded at the column, and the full text stays
    in the DOM for a screen reader.

    🔴 ::deep, AND IT IS REQUIRED RATHER THAN STYLISTIC - the same trap CasePage.razor.css and
    MainNavMenu.razor.css both record, the second "found on screen rather than in a test". The <h3> is
    rendered by UiText, so Blazor stamps UiText's scope attribute on it and not this component's; without
    ::deep this rule compiles to `.biztech-education-title[b-xxxxxxxxxx]` and matches nothing. The class is
    still in the markup, so a bUnit Find succeeds and the sheet sweeps pass - which is why
    EducationItemViewportTests.The_cards_title_really_is_clamped_to_two_lines reads the COMPUTED style.
*/
[b-66tbn88m70] .biztech-education-title {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
}
/* /Components/Layout/AnonymousLayout.razor.rz.scp.css */
/* The anonymous /i/{key} shell. Deliberately plain: one branded band, one centred column, nothing
   clickable that is not part of the task.

   T24 replaced this file's five hard-coded colours with theme variables, and that one change is what
   makes dark mode on this page free. MudBlazor declares --mud-palette-* on :root and REGENERATES the
   block when IsDarkMode flips, so these follow the theme with no second variable system, no C#
   emission, and - importantly - no `@media (prefers-color-scheme: dark)` here. A media query would
   follow the OS while MudBlazor follows IsDarkMode, and the two disagree the moment they differ.

   The literals were not wrong before: this page had no IsDarkMode binding at all, so #f7f8fa and
   #0f4c81 were exactly what it rendered. They were consistent with a light-only page. Step 5 binds
   the mode, and these variables are what make the rest of the layout come with it. */

.biztech-anon[b-gxc2mmwtpr] {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    background: var(--biztech-palette-background);
}

.biztech-anon-header[b-gxc2mmwtpr] {
    background: var(--biztech-palette-primary);
    padding: 1rem 1.5rem;
}

.biztech-anon-logo[b-gxc2mmwtpr] {
    height: 2rem;
    display: block;
}

.biztech-anon-main[b-gxc2mmwtpr] {
    flex: 1;
    display: flex;
    justify-content: center;
    padding: 2rem 1rem;
}

/* 🔴 W0-59 · FINDING S6 · box-sizing IS THE WHOLE FIX, AND IT WAS MEASURED IN A BROWSER.
   At 375px this panel computed 368px inside a 360px client width, so /i/{key} scrolled sideways -
   scrollWidth 364 against clientWidth 360. `width: 100%` is a CONTENT width without this line, so the
   1rem padding app.css's phone block gives it and the 1px border below were both added on top of a
   width already equal to the space available. .biztech-paper declares it; this panel never did.
   It is the one page an outsider opens on a phone, which is the page R5.8's touch block exists for. */
.biztech-anon-panel[b-gxc2mmwtpr] {
    box-sizing: border-box;
    width: 100%;
    max-width: 46rem;
    background: var(--biztech-palette-surface);

    /* lines-default, not divider: this edge is what makes the panel read as an object, so it is a
       boundary under WCAG 1.4.11 and carries the 3:1 the palette's Outline role is tested at. */
    border: 1px solid var(--biztech-palette-outline);

    /* T29 N4 · R5.5. Was a literal 6px - the same value the theme sets, restated. One spelling of
       the radius, and it is the variable. */
    border-radius: var(--biztech-radius-default);
    padding: 2rem;
}

/* 🔴 W6-40 · F8.1 · R4.6 APPLIED TO THIS LAYOUT. A WORKING SURFACE IS NOT PROSE.

   .biztech-anon-panel is 46rem because "§7's assignee reads a case" - guidelines §1, 6.3. A stage with
   a participant rail beside it is the board's shape, not a case's, and R4.6 keeps working surfaces
   ("board, case table") uncapped on purpose.

   🔴 AN ALTERNATIVE TO .biztech-anon-panel, NOT A MODIFIER OF IT, AND THAT IS WHAT UtilitySpecificityTests
   FOUND. The first version was `.biztech-anon-panel .biztech-anon-panel-wide` with `padding: 0; border: 0`
   - and a scoped reset of a box-model property is exactly the silent-override shape that test exists to
   catch: Blazor rewrites a scoped selector as `.x[b-xxxxxxxxxx]`, (0,2,0) against a utility class's
   (0,1,0), so a `padding: 0` here would beat every biztech-pa-* anybody later passed - and beat
   app.css's own phone block, which sets `.biztech-anon-panel { padding: 1rem }` at 375px.

   The layout picks ONE class or the OTHER, so the wide panel simply never has a padding, a border, a
   background or a cap to remove. Nothing is zeroed, nothing is overridden, and the phone block does not
   apply because the class it names is not on the element.

   What is deliberately KEPT from the narrow panel is box-sizing and width - W0-59's finding S6, measured
   in a browser: without box-sizing this computed 368px inside a 360px client width and the page scrolled
   sideways. A full-bleed panel has no padding of its own, but its CHILDREN do, so the same trap is one
   nested element away.

   Two pages declare it - SessionViewer and CustomerPresentation - both through FormChromeChannel, so
   /i/{key} and /login keep the prose measure by construction. */
.biztech-anon-panel-wide[b-gxc2mmwtpr] {
    box-sizing: border-box;
    width: 100%;
}

/* T29 N22. The `font-size: 0.8125rem` that was here (13px) is gone rather than rounded, because it
   was the wrong KIND of fix: R4.3 says never pick a size independently of a slot, and 13px is not one
   - it sat between Caption's 12px and Body2's 14px for no recorded reason. The footer now renders as
   <MudText Typo="Typo.caption">, which is the slot for supporting copy, so the size comes from the
   type scale and follows it if the scale ever moves. Colour goes with it (mud-text-secondary). */
.biztech-anon-footer[b-gxc2mmwtpr] {
    padding: 1rem 1.5rem;
    text-align: center;
}
/* /Components/Layout/InstallPrompt.razor.rz.scp.css */
/* W1b-05 · F2.23 · the install strip.

   🔴 LAYOUT ONLY, AND NO BOX-MODEL PROPERTY THE UTILITY LAYER OWNS.

   R14.1a: Blazor's CSS isolation rewrites `.biztech-install` as `.biztech-install[b-xxxxxxxxxx]` -
   (0,2,0) against a utility class's (0,1,0) - and _Host.cshtml links BizTech.Portal.styles.css AFTER
   app.css on purpose, so a scoped rule wins twice over. W0-52 measured what that costs: nine utilities
   computing zero, including `biztech-pa-4` on every UiPaper in the application, with the class present
   on the element and doing nothing. A class that is present and inert passes every check in this
   repository.

   So no margin and no padding here. Spacing between this strip and the grid below it is the page's.

   The 12px gap is the one MainLayout.razor.css:70, MainNavMenu.razor.css:36 and UiAlert.razor.css:14 all
   declare - BizTechCssVariables emits no spacing custom property, so a literal is what this layer has and
   12px is what it consistently means by "between two controls" (R5.4: not a magic number, the house one).

   🔴 NO COLOUR. R14.1 layer 4's hex sweep forbids a literal, and there is nothing to declare anyway: the
   strip sits on the page's own ground and UiText inherits the ink. A background would need a semantic
   role, and "an unobtrusive affordance" is exactly the thing that should not have one.

   R13.8 · `flex-wrap` rather than a min-width: at 375px the sentence and the two controls do not fit one
   line, and wrapping is what keeps every element's right edge inside #biztech-main. Nothing here declares
   overflow - InstallPromptTests walks this subtree, and an `overflow-x` would exclude it from that walk
   while looking like a fix. */

.biztech-install[b-uur1rtvl9p] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
}

/* The sentence takes the room the two controls do not, so the buttons stay together when it wraps. */
.biztech-install-text[b-uur1rtvl9p] {
    flex: 1 1 12rem;
}

/* 🔴 R5.8's 44px, CARRIED BY THIS COMPONENT RATHER THAN BY WHICHEVER SHELL HOSTS IT.

   InstallPromptTests.Both_controls_meet_the_touch_minimum_under_a_coarse_pointer measures the rendered
   box in both axes, and it FAILED the moment W1b-08 moved `/portal/{slug}` and `/` off AnonymousLayout:
   42.5px and 32px against 44. Nothing about this strip changed - what changed is that app.css's coarse
   block is scoped to `.biztech-anon`, and this strip is now drawn inside PortalLayout too.

   That test's own message says where the fix goes: "Fix the control's size, never that block: it is a
   guardrail." So the floor is declared here, once, and holds on both shells - which is better than the
   state before this card, where the strip met R5.8 only because of where it happened to be rendered.

   The size class is named and both axes are floored for the icon button, for the reasons app.css's block
   records at length: UiButton floors the same property at (0,2,0) through scoped CSS, and UiIconButton
   sets `block-size` in both axes, so a single-axis floor leaves a 44x32 target.
   🔴 AND ::deep, WHICH IS THE HALF THAT WAS WRONG ON THE FIRST ATTEMPT AND IS R14.1a AGAIN.
   Without it these selectors were present and inert: scoped CSS appends this component's [b-xxxxx] to
   the last compound, and the buttons are rendered by the SEAM components, so they carry the seam's scope
   attribute and not this file's. The rule resolved, matched nothing, and the measurement did not move by
   a pixel. `X ::deep Y` compiles to `X[b-xxxxx] Y` - the attribute lands on the ancestor this file does
   own - which is the same reason PortalTileCard.razor.css reaches a section's anchors with it. */
@media (pointer: coarse) {
    .biztech-install[b-uur1rtvl9p]  .biztech-install-accept.biztech-button-small,
    .biztech-install[b-uur1rtvl9p]  .biztech-install-accept.biztech-button-medium,
    .biztech-install[b-uur1rtvl9p]  .biztech-install-dismiss {
        min-block-size: 44px;
    }

    .biztech-install[b-uur1rtvl9p]  .biztech-install-dismiss {
        min-inline-size: 44px;
    }
}
/* /Components/Layout/MainLayout.razor.rz.scp.css */
/* W0-51 · the operator shell's own chrome. W0-53 · and now the whole of it.

   🔴 THIS FILE GREW BECAUSE MUDBLAZOR STOPPED SIZING THE SHELL. Until W0-53 the app bar, the drawer
   and the main content were MudAppBar, MudDrawer and MudMainContent - MudBlazor laid them out and
   BizTechThemeProvider painted them - and this sheet held two rules because everything else already
   existed. The layout is <header>, <aside> and <main> now, so their box model is here.

   Every value below is a token or a layout constant, and there is no colour literal and no
   `prefers-color-scheme` (I16 · R7.3): UiThemeTokens declares --biztech-* and re-emits the block when
   the mode flips, so a media query would follow the OS while the application follows the toggle, and
   the two disagree the moment they differ. AnonymousLayout.razor.css carries the same note.

   Scoped rather than global, and that is R14.1 layer 3 read literally: every element these rules touch
   is one THIS component renders. The two exceptions are already global for the opposite reason -
   .biztech-skip-link and the shared focus ring in css/app.css are application rules that no single
   component owns. */

/* The frame. A grid rather than a flex row, because the app bar spans BOTH columns and the drawer and
   the main content share the row beneath it - which is one declaration as a grid and a nest of
   wrappers as anything else. */
.biztech-shell[b-y45if9bi06] {
    display: grid;
    grid-template-columns: auto 1fr;
    /* 🔴 W0-27 · A THIRD ROW, `auto`, so the footer takes its content's height and 1fr keeps giving the
       main landmark everything left over. min-block-size: 100vh below already pins the footer to the
       bottom of a short page. The narrow-viewport block redeclares only grid-template-columns, so this
       row holds at both widths. */
    grid-template-rows: var(--biztech-appbar-height) 1fr auto;
    min-block-size: 100vh;

    /* 🔴 W0-63 · FINDING 6 · THE CONSOLE'S DENSITY, AND IT IS A TOKEN SO THAT NOTHING ELSE MOVES.

       Custom properties inherit, and .biztech-shell is an element THIS component renders - so a scoped
       declaration reaches every descendant, including the seam's own elements, which is R14.1 layer 3
       read literally. app.css's --biztech-switch-ground is the in-repo precedent for narrowing a token by
       selector, and BizTechCssVariables.ControlHeight declares the 40px default at :root.

       This is what keeps /i/{key}, /login and /register at today's boxes without a single rule in this
       change mentioning them - which the card's ## Do not requires and
       AssigneeFormViewportTests.Every_tap_target_meets_the_touch_minimum measures.

       32px, not 36 or 30: the 4px base unit (R5.1), and it is what a small button already declares - so a
       field and the button beside it are the same box for the first time. R5.8 exempts the operator
       console by decision (mouse at a desk), and only the console. */
    --biztech-control-height: 32px;
    --biztech-control-padding-block: 4px;

    /* 🔴 W0-68 · THE RAIL'S WIDTH, DECLARED ONCE AND READ TWICE.

       It was a raw 240px on .biztech-drawer below, and the snackbar host's leading inset was a separate
       raw 16px measured against the VIEWPORT - so the message started underneath the rail and no test
       could see it. Two numbers that had to agree, in two files, with nothing relating them.

       240px is what MudDrawer rendered at and what T24's measurements were taken against; the drawer's
       own comment says moving it would silently invalidate a recorded measurement, which is precisely
       why the number now has one home. 60 x 4 on the base unit (R5.1), so no row is added to §5's
       layout constant registry.

       A scoped custom property rather than a :root token, and W0-63's --biztech-control-height two rules
       up is the precedent: custom properties inherit, so a declaration on an element THIS component
       renders reaches every descendant including other components' elements (R14.1 layer 3). The rail
       exists on this shell and nowhere else - AnonymousLayout and SignInLayout render no drawer - so a
       token at :root would be a global answer to a question only one layout asks. */
    --biztech-drawer-inline-size: 240px;

    /* 🔴 W0-68 · WHERE THE CONTENT AREA BEGINS, for a fixed overlay that must not go under the rail.

       16px here is the drawer-CLOSED answer: no rail, so the message starts at the gutter. The :has()
       rule below raises it while the drawer is showing, and the narrow block at the end of this file
       puts it back - see both for their own reasoning.

       R5.10 records the same arithmetic from the other side: "content starts at 240 + 16 = 256, measured
       clear". This is that number, derived rather than restated. */
    --biztech-content-inline-start: 16px;
}

/* Spans both tracks, like the app bar, so the drawer's 240px column does not indent it.
   🔴 AND DECLARES NO BOX OF ITS OWN. The chrome inside is nothing at all for every tenant that has
   configured neither block, which is every deployment until someone writes a row - so padding and a rule
   here would show a permanently empty bordered strip at the bottom of this console. TenantChrome's own
   wrapper carries them, and exists only when there is something to show. Found by looking at the running
   app, which is the only place it was visible. */
.biztech-footer[b-y45if9bi06] {
    grid-column: 1 / -1;
}

/* 🔴 THE APP BAR IS A BANNER, AND SINCE W0-65 IT IS PINNED TO THE VIEWPORT AS WELL AS PLACED IN THE
   GRID. MudAppBar was position: fixed with a spacer beneath it; a grid row plus `position: sticky`
   needs no spacer, because a sticky box stays in flow and its row still reserves the height. The
   `sticky` block below carries the measurement and the reason it is not `fixed`.

   The height comes from --biztech-appbar-height, which UiThemeTokens emits from the same constant
   MudBlazor's app bar used - so nothing on any page moves by a pixel. */
.biztech-appbar[b-y45if9bi06] {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    gap: 12px;
    padding-inline: 12px;

    background: var(--biztech-palette-appbar-background);
    color: var(--biztech-palette-on-appbar);

    /* 🔴 W0-69 · ADR 0026 D-E. A BORDER, NOT A SHADOW - AND `outline`, NOT `outline-subtle`.

       This was `box-shadow: var(--biztech-shadow-raised)`, which is D-E's own counter-example: an
       in-flow surface taking its depth from a shadow. R6.6 already said a border does on dark what a
       shadow does on light; D-E applies that to light too, and leaves the dialog as the one place in
       this application where a shadow reads as depth.

       It also became NECESSARY rather than merely preferable. D-A makes the light bar Neutral0, which
       is what DrawerBackground and Surface are in that mode - so the bar, the drawer and a card are
       all the same white, and this edge is the only thing separating the chrome from the page. That
       makes it a MEANINGFUL border under R3.5, so it takes `outline`: R3.7 declares `outline-subtle`
       ornament and leaves it DELIBERATELY UNTESTED, and a rule that depends on being seen must not
       depend on a ratio nobody checks.

       Measured 2026-09-03 by scripts/contrast-audit.py: Outline is 3.33:1 against Surface and 3.91:1
       against Background. The light bar IS Surface's value and the dark bar IS Background's, so both
       sides of this border are pairs PaletteContrast.RequiredPairs already holds - no pair was added.
       1px per R5.6. */
    border-block-end: 1px solid var(--biztech-palette-outline);

    /* 🔴 W0-63 · S2. `1fr` is `minmax(auto, 1fr)`, and this flex row's automatic minimum is what
       set the shell's single grid track to 382.391px at a 375px viewport - 32 + 166.4 + 0 + 32 + 36 + 32
       of children, 5 gaps of 12, 24 of inline padding. .biztech-main has carried this line since W0-59;
       the app bar spans the same track and never did, so every page showing the Developer Mode switch
       scrolled 7.4px sideways on a phone. /ui does not show that switch, which is why the browser
       suite's two routes both looked clean - PortalBrowserStubs grants the edit-in-context key now;
       W0b-07 then
   moved what that key buys from an app-bar switch to the editable regions' own gate, so the app bar this
   was measured against has two fewer children and roughly 40px of slack at 375. And
       The_shell_is_no_wider_than_the_viewport failed at 375 on both of them before this line. */
    min-inline-size: 0;

    /* 🔴 W0-65 · FINDING 1. STICKY, AND IT IS THE DRAWER'S OWN ARGUMENT ONE RULE DOWN.

       This block declared no `position` at all until this card, so on any page taller than the viewport
       the banner left the screen while .biztech-drawer - given `position: sticky` by W0-63 - stayed put.
       One chrome, two behaviours, decided by which of the two somebody had got to. Measured at 1440x900
       and at 375: the bar's top read -800 after scrolling 800px, against the drawer's 64.

       🔴 A GRID ITEM'S STICKY RANGE IS THE GRID CONTAINER, NOT ITS OWN GRID AREA, and this fix turns
       entirely on that. Row one is exactly var(--biztech-appbar-height) tall, so a banner clamped to its
       own area would have ZERO sticky range and this declaration would silently do nothing. Measured
       2026-08-31 in headless Chromium on a reduction of this grid before it was written: with sticky the
       bar's top reads 0 at scroll 800 where it read -800 without, and the drawer's box, the main
       landmark's origin and all three computed row tracks are unchanged.

       `sticky`, not `fixed`, which is the drawer's sentence verbatim: sticky stays in flow, so the grid
       row still reserves its height and .biztech-drawer, <main> and .biztech-footer keep the placements
       they have. A fixed banner would stop being a grid item and auto-placement would put the drawer in
       row one - it needs an explicit `grid-row` on three other elements to reach the same result.

       AND .biztech-footer IS NOT COVERED, which is that argument again: a sticky box is clipped to its
       containing block, so at the page's end the banner is still at the viewport's TOP while row three
       is at its bottom. */
    position: sticky;
    inset-block-start: 0;
    z-index: var(--biztech-layer-appbar);
}

/* Mirrors .biztech-anon-logo, which sits on the same band on the anonymous layout. The height is what
   keeps the wordmark inside the app bar's own 64px; the mark's aspect ratio does the rest. */
.biztech-appbar-logo[b-y45if9bi06] {
    height: 2rem;
    display: block;
}

/* The drawer. 240px is what MudDrawer rendered at and what T24's measurements were taken against -
   "content flush against the drawer at x=240" is in PortalPageShellTests' own header, so moving it
   would silently invalidate a recorded measurement.

   `hidden` does the hiding: the layout toggles the attribute rather than unmounting the nav, so the
   items keep their identity across a toggle. display: none is the user agent's default for [hidden]
   and is restated here only because the flex display below would otherwise win. */
.biztech-drawer[b-y45if9bi06] {
    /* 🔴 W0-68 · READ RATHER THAN WRITTEN. The number is declared once on .biztech-shell, because the
       snackbar host's leading inset is derived from it too - see that declaration.

       WITH A FALLBACK, and R5.5's own note is the reason: "a var() that resolves to nothing falls back
       to no radius at all - silently". This property is declared on .biztech-shell and NOWHERE else -
       there is no :root emission, unlike --biztech-control-height - so the day it stops reaching this
       aside, `inline-size` computes to `auto` and the rail collapses to its content's width. The host's
       own read carries a fallback for the same reason; this one had none. */
    inline-size: var(--biztech-drawer-inline-size, 240px);
    overflow-y: auto;
    border-inline-end: 1px solid var(--biztech-palette-outline-subtle);
    background: var(--biztech-palette-surface);

    /* 🔴 W0-63 · FINDING 1. STICKY AND HEIGHT-BOUNDED, WHICH IS WHAT MAKES THE `overflow-y` ABOVE
       MEAN ANYTHING.

       The grid row is `1fr` under `min-block-size: 100vh`, so on a page taller than the viewport the row
       grew to the main content and this box grew with it: measured 1,527.2px against 836px available on
       /inquiries, 3,288.9px on /ui, `scrollHeight == clientHeight` in both, and the last of 27 nav
       entries at y = 1,583.2 - 683px below the fold, reachable only by scrolling the page out from under
       the item you were aiming at. On a SHORT page (/Error) the same drawer measured 768.1px and scrolled
       inside itself correctly, which is how one console had two behaviours decided by page length.

       A definite block-size takes this element out of the row's height calculation, so the row is the
       main content's height and this box is the viewport's.

       `sticky`, not `fixed`: sticky stays in flow and is clipped to its own grid area, so .biztech-footer
       in row three is not covered - which a fixed drawer of this height would do on a short page, and
       which is a Done-when clause of its own. The narrow block at the end of this file redeclares
       `position: fixed` and wins by order, so the overlay drawer below sm is unchanged. */
    position: sticky;
    inset-block-start: var(--biztech-appbar-height);
    block-size: calc(100vh - var(--biztech-appbar-height));
    align-self: start;
}

.biztech-drawer[hidden][b-y45if9bi06] {
    display: none;
}

/* 🔴 W0-68 · THE SNACKBAR FOLLOWS THE DRAWER, AND THE STYLESHEET NEEDS NO HELP FROM THE MARKUP.

   `hidden` already carries open/closed - the layout toggles the attribute rather than unmounting the
   nav - so :has() reads the state that is already there. That is W0-59's own argument, one rule further
   on: its comment in MainLayout.razor records a data-narrow="@_drawer.Narrow" marker that matched
   NOTHING (a C# bool interpolates as "True"; a CSS attribute selector is case-sensitive) and concludes
   "the stylesheet needs no help from the markup to know either". A conditional CSS class here would not
   have that casing trap, and would still be a second representation of one state.

   🔴 THE FIRST :has() IN THIS REPOSITORY, and the rewriter was measured before it was written. Blazor's
   CSS isolation is Microsoft.Css.Parser, not a browser, and a scope attribute in the wrong place
   produces a rule that silently matches nothing - which every assertion reading the SOURCE file would
   still pass. Verified 2026-09-02 by building this project and reading
   obj/Debug/net10.0/scopedcss/bundle/BizTech.Portal.styles.css: this selector is rewritten to
   `.biztech-shell:has(> .biztech-drawer:not([hidden]))[b-0uclpy4pgk]`, the attribute appended at the
   END - the same shape UiButton.razor.css's `:hover:not(:disabled)` already gets - with 0 warnings.

   The CHILD combinator is deliberate. A descendant `:has(.biztech-drawer:not([hidden]))` would also
   match a nested drawer if the island ever rendered one inside this shell (ADR 0013 nests StudioLayout
   inside this layout), and the rail this rule is about is this layout's own <aside>. */
.biztech-shell:has(> .biztech-drawer:not([hidden]))[b-y45if9bi06] {
    --biztech-content-inline-start: calc(var(--biztech-drawer-inline-size) + 16px);
}

/* The main landmark is a <main> rather than the div MudMainContent rendered, so it needs to fill its
   grid cell the way that div filled its parent, or every page loses the layout it was written
   against. min-inline-size: 0 is what stops a wide table stretching the grid column instead of
   scrolling inside it. */

/* 🔴 W6-42 · `clip` AND NOT `hidden`, AND THE DIFFERENCE IS A SCROLL CONTAINER NOBODY WANTED.

   This line said `overflow-x: hidden` from W0-51 until W6-42. The two clip identically - that is the
   whole of what R13.8 and its suites measure, and none of their assertions change - but `hidden`
   ALSO makes the element a scroll container, and `clip` does not. That difference is invisible until
   something inside wants a scrollport.

   🔴 MEASURED ON 2026-09-07, on /session/{caseId} at 1264x615. SessionStage's band declares
   `position: sticky; top: 0`, the declaration applied, and the band still scrolled away: 64 -> -336
   over a 400px scroll, the full distance. The ancestor walk says why -

     DIV.biztech-session-stage__bar  position=sticky top=0px               height=39
     DIV (the frame's own)           position=static                       height=2370
     MAIN.biztech-main               overflowX=hidden overflowY=auto       height=2370

   A sticky element resolves against its nearest ancestor SCROLLPORT, and `overflow-x: hidden`
   computes overflow-y to `auto`, so .biztech-main was it. Its height equals its content, so it never
   scrolls: the band pinned faithfully to a box that never moves. The document was what scrolled.

   The same band on AnonymousLayout pinned correctly the whole time - 96 -> 0 - because that chain
   declares no overflow at all. Two layouts, one component, opposite behaviour, and nothing in any
   stylesheet looked wrong.

   🔴 WHY THIS IS NOT A WEAKENING OF R13.8. That rule's enforcement is PortalDrawerTests measuring the
   landmark's width against the space available and walking every element's layout right edge, plus
   the landmark-overflow exclusions. `clip` clips the same pixels, so `documentElement.scrollWidth`
   stays exactly as honest as it was and every one of those assertions holds. What is given up is a
   scroll container the layout never used - .biztech-main has never had a height that could scroll.

   R13.6's floor is evergreen Chrome, Edge, Firefox and Safari, all of which ship `overflow: clip`;
   the same section already names :has(), color-mix() and ResizeObserver as load-bearing. */
.biztech-main[b-y45if9bi06] {
    display: block;
    inline-size: 100%;
    min-inline-size: 0;
    overflow-x: clip;
}

/* ── 🔴 W0-59 · FINDING S5 · THE DRAWER BELOW sm ───────────────────────────────────────────────────

   THE DRAWER NEVER COLLAPSED, AND THE CONSOLE WAS UNUSABLE ON A PHONE. MudDrawer had a breakpoint and
   turned itself into a temporary overlay; W0-51 replaced it with the <aside> above and replaced the
   layout but not the behaviour, so `auto 1fr` gave the drawer its 240px at every width.

   Measured in a browser, 2026-08-13, at 375px:

     /board        #biztech-main 103px wide, content reaching x = 450
     /case-types   #biztech-main 103px wide, content reaching x = 580

   against a 360px client width - and .biztech-main declares overflow-x: hidden, so that was CLIPPED
   RATHER THAN SCROLLED. The "Create case" button, the case-type filter and the right-hand table columns
   were not reachable by any means.

   AT sm AND ABOVE NOTHING BELOW APPLIES, so no desktop pixel moves. That is deliberate and it is the
   same argument the board's own breakpoint makes: the console is a desktop tool, the demo is presented on
   a laptop, and a card repairing a phone layout must not touch the screen the work is actually done on.

   599.98px is MudBlazor's sm, which app.css already keys four blocks on - the board's stack, the
   participant-link column, the anonymous panel's padding and the table reflow. A second number here would
   let the drawer disagree with the board about where the layout changes, on the one screen where both are
   visible together. */
@media (max-width: 599.98px) {
    /* One column. The drawer is out of the flow below, so a track reserved for it would be an empty
       240px gutter down the side of every page. */
    .biztech-shell[b-y45if9bi06] {
        grid-template-columns: 1fr;

        /* 🔴 W6-50 · design §8. THE NARROWING IS REVERSED HERE, AND IT WAS THE ONE DESKTOP-ONLY RULE
           THIS BLOCK FORGOT TO REVERSE.

           §8: "Control height goes back to 40px off the console's 32px, which is the token's own
           narrowing." W0-63 narrowed it 265 lines up and its reasoning names its own exemption:
           "R5.8 exempts the operator console by decision (MOUSE AT A DESK), and only the console."
           Below sm there is no mouse and no desk - the pointer is a finger, which is the exact case
           R5.8's minimum exists for and the case the exemption was never claiming.

           Driven at a 485px viewport on 2026-09-08, every UiButton and every input on every page
           resolved to min-height: 32px. --biztech-control-height reads 40px at :root and this block
           changed the grid, the content inset and the drawer without ever putting the token back.

           🔴 THE PADDING GOES WITH IT. 4px of block padding under a 40px box leaves the label sitting
           high in it; 8px is what the 40px control was drawn with before the narrowing, and R5.1's 4px
           base unit is why it is 8 and not 6.

           🔴 THIS SELECTOR, NOT :root. The token is declared on .biztech-shell so that /i/{key},
           /login and /register keep their own boxes - W0-63's own requirement, measured by
           AssigneeFormViewportTests.Every_tap_target_meets_the_touch_minimum. Reversing it at :root
           would move three surfaces this rule has no business touching. Same selector, same
           specificity, later in source: it wins, and nothing else moves. */
        --biztech-control-height: 40px;
        --biztech-control-padding-block: 8px;
    }

    /* 🔴 W0-68 · below sm the drawer is an OVERLAY over the content (R13.8), so the content area starts
       at the viewport edge again and this property must say so - a reader told the content begins 256px
       into a 375px screen has been told the wrong thing.

       UiSnackbarHost.razor.css redeclares `inset-inline: 16px` at this width and is the last word on the
       pixels either way, so this rule changes nothing that renders. It exists because the property's
       NAME is a claim about the layout, and a claim that is false at one breakpoint is a trap for
       whoever reads it next.

       THE SELECTOR IS REPEATED VERBATIM rather than narrowed to `.biztech-shell`: :has() takes its
       argument's specificity, so the rule above is (0,3,0) and a bare `.biztech-shell` here would be
       (0,1,0) and would lose whatever the source order. A media query adds no specificity. */
    .biztech-shell:has(> .biztech-drawer:not([hidden]))[b-y45if9bi06] {
        --biztech-content-inline-start: 16px;
    }

    /* Out of the flow and over the content. `position: fixed` rather than absolute: the shell is a grid
       with no positioning context of its own, and the drawer must stay put while the page behind it
       scrolls - an absolutely positioned drawer scrolls away and leaves the scrim behind.

       Below the app bar rather than over it, so the button that opened it stays visible and reachable:
       an overlay that covers its own toggle is one an operator has to guess their way out of.

       🔴 NO ATTRIBUTE SELECTOR HERE, AND THE FIRST VERSION HAD ONE. It read
       `.biztech-drawer[data-narrow="true"]`, with the markup rendering data-narrow="@_drawer.Narrow" - and
       a C# bool interpolates as "True" while a CSS attribute selector matches its value CASE-SENSITIVELY,
       so the rule applied to nothing. Every geometry assertion still passed, because the single-column grid
       above was doing the work by itself. Inside this media query everything IS narrow, and
       .biztech-drawer[hidden] { display: none } outside it already carries open/closed - so the marker was
       both broken and redundant. */
    .biztech-drawer[b-y45if9bi06] {
        position: fixed;
        inset-block: var(--biztech-appbar-height) 0;
        inset-inline-start: 0;
        z-index: var(--biztech-layer-drawer);
        box-shadow: var(--biztech-shadow-overlay);
    }

    /* The scrim. Same inset as the drawer, so the app bar stays operable underneath it - the operator can
       press the menu button again, which is the affordance they already know.

       A <button>, so it is reset to a plain surface: the user agent gives one a border, a background and
       its own appearance, and none of that belongs on a scrim.

       🔴 NO `padding: 0`, and UtilitySpecificityTests is why I know: it flagged one here, and the flag was
       right for a reason worth keeping. Every edge of this element is pinned, so padding cannot change what
       it covers - it would have been a declaration with no effect that also outranked the utility layer.
       The guard from that commit caught it on the very next one. */
    .biztech-drawer-scrim[b-y45if9bi06] {
        position: fixed;
        inset-block: var(--biztech-appbar-height) 0;
        inset-inline: 0;
        z-index: var(--biztech-layer-drawer-scrim);

        appearance: none;
        border: 0;
        background: var(--biztech-scrim);
        cursor: pointer;
    }
}
/* /Components/Layout/MainNavMenu.razor.rz.scp.css */
/* W0-53 · the drawer's items, which MudNavMenu used to draw.

   Four rules, and between them they replace what MudNavLink rendered: a padded row, a glyph beside a
   label, a hover, and the current page marked. Nothing here is new design - the values are what the
   drawer measured at before this card, so an operator's eye lands where it already did.

   Scoped, because every element these rules touch is inside the <nav> this component renders (R14.1
   layer 3). The focus ring is NOT here: app.css declares exactly one, shared, and .biztech-nav-item is
   on its selector list - a second declaration in a scoped sheet is the thing that list exists to
   prevent.

   🔴 ::deep ON EVERY ITEM RULE, AND THIS WAS FOUND ON SCREEN RATHER THAN IN A TEST.
   Three of the four items are Blazor `NavLink` COMPONENTS, and Blazor stamps its scope attribute on
   the elements a component renders ITSELF - never on the markup a child component renders. So a bare
   `.biztech-nav-item` compiles to `.biztech-nav-item[b-xxxxxxxxxx]` and matches only the fourth item,
   the plain <a>. Measured in the browser on the running stack: the three NavLinks reported
   `min-block-size: 0px` and `text-decoration: underline` - default browser links - while the <a>
   reported 44px and none.

   ::deep compiles to `[b-xxxxxxxxxx] .biztech-nav-item`, a descendant selector rooted at the <nav>
   this component DOES render, which reaches both kinds and still cannot escape this subtree.
   CasePage.razor.css carries the identical fix for the identical reason, written when its two
   controls were MudBlazor components - the substrate changed and the rule did not. */

[b-mrrwcq35ki] .biztech-nav {
    list-style: none;
    margin: 0;
    padding: 8px 0;
}

/* 🔴 THE ROW IS 44px, WHICH IS R13.5's TOUCH TARGET AND NOT A COINCIDENCE. MudNavLink rendered 48px;
   the floor is what matters and the drawer is denser for it. */
[b-mrrwcq35ki] .biztech-nav-item {
    display: flex;
    align-items: center;
    gap: 12px;

    /* 🔴 W0-63 · FINDING 6. 40px, was 44. R5.8's 44px touch minimum exempts the operator console
       BY DECISION - "mouse at a desk" - and 44px stays in PortalStyleSheetTests.LayoutConstants for the
       (pointer: coarse) block that still uses it on /i/{key}. 40 is 10 x 4 on the base unit, and it is
       what took 27 nav items from 1,527px of drawer to a box the viewport bounds. */
    min-block-size: 40px;
    padding-inline: 16px;

    color: var(--biztech-palette-text-primary);
    text-decoration: none;

    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-body2-size);
    line-height: var(--biztech-font-body2-line-height);
}

[b-mrrwcq35ki] .biztech-nav-item:hover {
    background: color-mix(in srgb, currentcolor 6%, transparent);
}

/* 🔴 W0-59 · A NAV GROUP CAPTION. Two rules and an indent, which is the whole of what a group needs
   when it does not collapse. Three groups render it today - Cases, Oversight, and each administration
   group - so a change here moves all of them.

   The shape is secondary ink, caption scale, above its items. 16px of inline padding lines the
   caption's first letter up with the item labels' glyphs above it, and the block padding is asymmetric
   so the caption sits closer to what it labels than to what precedes it.

   (W0-59 chose this shape to match a SECOND drawer the app used to have, shipped by the retired
   designer shell, because an operator crossed between the two. There is one drawer now. The measurements
   above are kept on their own merits, and nothing outside this file constrains them any more.)

   No `padding: 0` on the sublist and no `margin: 0` on the caption: a <span> has neither, and the one
   reset here is the <ul>'s own block margin - which is why UtilitySpecificityTests carries a Declared
   entry for it beside .biztech-nav's, for the identical reason. */
[b-mrrwcq35ki] .biztech-nav-group-label {
    display: block;
    padding: 12px 16px 4px;

    color: var(--biztech-palette-text-secondary);

    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-caption-size);
    font-weight: var(--biztech-font-caption-weight);
    line-height: var(--biztech-font-caption-line-height);
    letter-spacing: var(--biztech-font-caption-letter-spacing);
}

[b-mrrwcq35ki] .biztech-nav-sublist {
    list-style: none;
    margin: 0;
    padding-inline-start: 0;
}

/* The indent is what says "these belong to the caption" once the disclosure triangle is not there to
   say it. 12px rather than a full glyph column: the two sub-items keep their own icons - each is a
   destination in its own right - so indenting by the icon's width would break the vertical line the
   other three items' glyphs sit on and buy nothing back. */
[b-mrrwcq35ki] .biztech-nav-sublist .biztech-nav-item {
    padding-inline-start: 28px;
}

/* `active` is Blazor's own class name, written by NavLink when the route matches - it is not ours to
   prefix, and renaming it would mean reimplementing the matching. The inset bar rather than a filled
   row for the reason .biztech-selected uses one on the case list: a tint alone is hard to see against
   a surface at a glance, and the two marks reading the same way is worth more than either. */
[b-mrrwcq35ki] .biztech-nav-item.active {
    color: var(--biztech-palette-primary);
    background: color-mix(in srgb, var(--biztech-palette-primary) 8%, transparent);
    box-shadow: inset 2px 0 0 var(--biztech-palette-primary);
}
/* /Components/Layout/PortalContextBar.razor.rz.scp.css */
/* W8-01 · F10.16 — the "where you are" bar. TOKENS ONLY: R3.1's sweep in PortalStyleSheetTests reads
   every *.razor.css in this project and refuses a hex or rgb() literal.

   🔴 A SCOPED SHEET RATHER THAN app.css, AND IT IS THE ESTABLISHED PATTERN RATHER THAN A PREFERENCE.
   Every layout component here already has one - PortalLayout.razor.css, TenantChrome.razor.css,
   MainNavMenu.razor.css - and these rules belong to one component that one layout renders. */

/* 🔴 IN NORMAL FLOW. NOT position: fixed, NOT sticky.

   R13.8's rule is that #biztech-main never yields its WIDTH, and the card extends it to height: "at
   375px it does not push the main landmark below the fold or reduce its width". A fixed bar would not
   push the landmark - it would OVERLAY it, which is the same defect wearing the opposite symptom, and
   at 375px it would cover the content it is describing.

   So: one line, above the header, in the flow, costing the landmark nothing but its own height. */
.biztech-portal-context[b-y0i12qcink] {
    display: flex;
    align-items: center;
    justify-content: space-between;

    /* 8px and 16px: whole multiples of the 4px base unit, which is what R5.4's sweep accepts. Tighter
       block padding than TenantChrome's 12px because this is a strip rather than a footer - the height
       it takes is the thing the card asks to be measured. */
    gap: 8px;
    padding: 8px 16px;

    /* R5.6's fixed border width, on the block-end only: the bar sits above the header, so the line it
       draws is the boundary between "where you are" and the portal itself. */
    border-block-end: 1px solid var(--biztech-palette-outline-subtle);

    /* Surface rather than the page ground, so the strip reads as chrome rather than as content. Both
       are declared tokens and PaletteContrast.RequiredPairs already tests the ink below against it. */
    background-color: var(--biztech-palette-surface);
}

/* R4.3 · from the type scale, never a literal - AnonymousLayout.razor.css lost a bare 0.8125rem for
   exactly this reason. Caption is the right step: this is orientation, not content. */
.biztech-portal-context__where[b-y0i12qcink] {
    /* min-width: 0 so a long tenant name can shrink and ellipsise rather than pushing the link off the
       row - which at 375px is what would make the way out unreachable, and being unable to leave is the
       whole defect this bar exists to fix. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--biztech-palette-text-secondary);
}

/* Primary, for TenantChrome.razor.css's stated reason: PaletteContrast.RequiredPairs already tests
   Primary against Surface, so this introduces no pair anybody has to measure. */
.biztech-portal-context__out[b-y0i12qcink] {
    /* flex-shrink: 0 - the way out never shrinks. The name beside it gives way first. */
    flex-shrink: 0;
    font-size: var(--biztech-font-caption-size);
    line-height: var(--biztech-font-caption-line-height);
    color: var(--biztech-palette-primary);
}
/* /Components/Layout/PortalLayout.razor.rz.scp.css */
/* 🔴 W1b-08 · F2.19, F2.21, F2.22 · the portal's own shell, and the FIFTH layout sheet.

   WHAT THIS IS NOT, twice over, because both alternatives were what the portal rendered inside before
   this card:

     .biztech-anon-panel   is max-inline-size: 46rem - §5's PROSE MEASURE (R4.6), there because §7's
                           assignee reads a case. W1a-08's plan measured what it did to a portal grid:
                           "12 tracks of 41px" on a 1536px viewport.
     .biztech-shell        is the operator console: a grid with a 240px drawer column, an app bar row
                           and a scrim. R13.8's measured defect site, and a customer must not see its
                           navigation at all.

   🔴 THE MAX WIDTH IS 71.25rem = 1140px, AND IT IS READ RATHER THAN CHOSEN. Legacy's shell wraps its
   main in a Bootstrap `.container` (_LayoutAdminDesign.cshtml:320), whose width at `xl` is 1140px. So
   the portal is centred at the width its own visitors have been reading it at.

   NO LayoutConstants ROW, and R5.4 is satisfied without one: 71.25 x 16 = 1140, and 1140 / 4 = 285, so
   this is a whole multiple of the 4px base unit - which is that rule's FIRST option, not its second.
   §5 is explicit that adding a registry row "is not the cheap way out" of that check, and
   SignInLayout's own 24rem panel takes the same route for the same reason (its Decision 5).

   EVERY OTHER LENGTH IS A 4px MULTIPLE and every colour is a --biztech-* token (R14.1 layer 1). No
   `prefers-color-scheme` anywhere (I16 · R7.3): UiThemeTokens re-emits the palette when the mode flips,
   so a media query here would follow the OS while the application follows its own field, and the two
   disagree the moment they differ. All four other layout sheets carry the same note.

   NO overflow OF ANY KIND (R13.8): "overflow-x: hidden is not a responsive strategy - it hides the
   symptom and the diagnostic together", and W0-55 exists because of it. The main keeps the whole
   viewport minus its gutters, which is what PortalHomeViewportTests measures against the landmark.

   LOGICAL PROPERTIES THROUGHOUT - min-block-size, max-inline-size, padding-inline - never height or
   width. */

.biztech-portal-shell[b-f04qs6ddqz] {
    display: flex;
    flex-direction: column;
    min-block-size: 100vh;
    background: var(--biztech-palette-background);
}

/* 🔴 A SURFACE WITH A BOTTOM EDGE RATHER THAN A PAINTED BAND, and that is why the mark this header
   shows is the mode-appropriate one rather than the reverse one.

   AnonymousLayout and SignInLayout both paint their header `Primary` and both therefore render
   Marks.Reverse - the white-ink mark - which BizTechBrand.LoginLogoUrl's own measurement settled: 48x25
   of dark wordmark on #2196F3, close to illegible. Legacy's portal navbar is not a colour block: it is
   a pill on the page's own ground (_LayoutAdminDesign.cshtml:178-185). So this band is `surface` with
   one `outline` edge, and Marks.For(dark) is the right call in this file rather than a copy of theirs.

   The height is not declared: the mark's 2rem plus the padding is what sets it, and a fixed height
   would be a length nothing measured. --biztech-appbar-height is deliberately NOT read - that token is
   the console's app bar, and borrowing it would tie this shell's proportions to the console's density. */
.biztech-portal-header[b-f04qs6ddqz] {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;

    background: var(--biztech-palette-surface);

    /* lines-default, not divider: this edge is what separates the shell from the page, so it is a
       boundary under WCAG 1.4.11 and carries the 3:1 the palette's Outline role is tested at - the same
       call .biztech-anon-panel and .biztech-signin-panel make about their own edges. */
    border-block-end: 1px solid var(--biztech-palette-outline);
}

/* One dimension constrained, and the viewBox carries the ratio (R11.6). 2rem is what all three other
   layouts give a header mark, so a tenant's logo is the same size on every surface it appears on. */
.biztech-portal-mark[b-f04qs6ddqz] {
    block-size: 2rem;
    display: block;
}

/* flex: 1 so the footer sits at the bottom of a short page rather than under the fold - the property
   .biztech-anon-main and .biztech-signin-main both use.

   NO align-items and NO justify-content, which is where this diverges from both of them deliberately:
   they centre a PANEL, and this holds a page. The centring here is margin-inline: auto on the measure
   below, so content flows from the top exactly as it does in the console. */
.biztech-portal-main[b-f04qs6ddqz] {
    flex: 1;
}

/* 🔴 THE CONTAINER LEGACY'S `.container` IS. margin-inline: auto is the centring; the max-inline-size is
   its 1140px; and the gutters and the vertical rhythm come from UiContainer inside it (R5.3, R5.10) so
   this sheet declares no padding of its own and cannot disagree with the rest of the application about
   what a page's inset is.

   A WRAPPER DIV RATHER THAN A RULE ON UiContainer: that component is the seam's, shared by every
   console page, and narrowing it here would centre and bound /inquiries too. R14.1's "go one layer down
   only when the layer above cannot reach" - this is one layer down, in this layout's own sheet, reaching
   only this layout's own element. */
.biztech-portal-measure[b-f04qs6ddqz] {
    box-sizing: border-box;
    max-inline-size: 71.25rem;
    margin-inline: auto;
}

/* Legacy's centred "Powered by" line (_LayoutAdminDesign.cshtml:320-324). The same padding and the same
   centring the other two tenant-facing footers use, so the three read as one product. */
.biztech-portal-footer[b-f04qs6ddqz] {
    padding: 16px 24px;
    text-align: center;
}

/* R13.7's phone width. The header's inline padding comes in to match UiContainer's own phone gutter, so
   the mark and the page's content share one left edge at 375px - which is the alignment
   PortalHomeViewportTests measures against the landmark rather than eyeballs. */
@media (max-width: 599.98px) {
    .biztech-portal-header[b-f04qs6ddqz] {
        padding-inline: 12px;
    }

    .biztech-portal-footer[b-f04qs6ddqz] {
        padding-inline: 12px;
    }
}

/* 🔴 R5.8's 44px FOR THE SHELL'S OWN CONTROLS, AND IT IS HERE BECAUSE THIS CARD TOOK THE
   PAGE OUT FROM UNDER THE RULE THAT USED TO PROVIDE IT.

   app.css's `@media (pointer: coarse)` block floors `.biztech-input`, `.biztech-button` (by size class)
   and `.biztech-icon-button` at 44px - and it is scoped to `.biztech-anon`, deliberately: "Scoped to
   .biztech-anon so the console is untouched." `/portal/{slug}` and `/` rendered inside AnonymousLayout
   until W1b-08, so every control on them inherited that floor. PortalLayout is not `.biztech-anon` and
   must not pretend to be - that class carries a whole shell's worth of anonymous-page styling, and the
   40rem prose measure is exactly what this card moved the portal OFF.

   So the floor is declared here, for the controls this shell owns. InstallPromptTests caught the same
   regression one component down and InstallPrompt.razor.css now carries its own; that is the same
   decision twice rather than a duplicate, because a control's minimum should travel with the control
   and a shell answers for the controls it draws itself.

   THE SIZE CLASS IS NAMED, WHICH IS W3-26's MEASUREMENT AND NOT A PRECAUTION. app.css records it in
   full: UiButton.razor.css declares `.biztech-button-small { min-block-size: 32px }` and
   `.biztech-button-medium { min-block-size: 40px }` - the SAME property - and scoped CSS puts its
   [b-xxxxx] attribute on the last compound only, so those weigh (0,2,0), exactly what an unnamed
   `.biztech-portal-header .biztech-button` would weigh. Naming the size class makes this (0,3,0) and
   settles the cascade rather than depending on sheet order.

   `min-inline-size` on the icon button too, because UiIconButton.razor.css declares `block-size: 32px`
   in BOTH axes: a min-* floor beats a fixed size, but only in the axis it names, and a 44x32 target is
   not a 44px target. PortalHomeViewportTests measures the RENDERED BOX in both axes, which is the only
   check that can tell this rule from an inert one (R14.1a).
   🔴 AND ::deep, WHICH IS THE HALF THAT WAS WRONG ON THE FIRST ATTEMPT AND IS R14.1a AGAIN.
   Without it these selectors were present and inert: scoped CSS appends this component's [b-xxxxx] to
   the last compound, and the buttons are rendered by the SEAM components, so they carry the seam's scope
   attribute and not this file's. The rule resolved, matched nothing, and the measurement did not move by
   a pixel. `X ::deep Y` compiles to `X[b-xxxxx] Y` - the attribute lands on the ancestor this file does
   own - which is the same reason PortalTileCard.razor.css reaches a section's anchors with it.

   🔴 AND `.biztech-menu-trigger` IS NAMED BECAUSE IT WAS MEASURED AT 32x32, NOT BECAUSE THE
   LIST LOOKED SHORT. app.css's block names `.biztech-input`, `.biztech-button` and
   `.biztech-icon-button` and stops there, which was complete for the anonymous shell: nothing on a
   sign-in page opens a menu. This header does - OperatorMenu is what gives a customer a way to sign out
   - and UiMenu's activator carries its own class and 32px in both axes, so it arrived under this rule
   and was still 12px short. Read off the rendered DOM rather than off the seam's source: the probe
   reported `biztech-icon-button ... | 44x44 || biztech-menu-trigger | 32x32`. */
@media (pointer: coarse) {
    .biztech-portal-header[b-f04qs6ddqz]  .biztech-icon-button,
    .biztech-portal-header[b-f04qs6ddqz]  .biztech-menu-trigger,
    .biztech-portal-header[b-f04qs6ddqz]  .biztech-button.biztech-button-small,
    .biztech-portal-header[b-f04qs6ddqz]  .biztech-button.biztech-button-medium {
        min-block-size: 44px;
    }

    .biztech-portal-header[b-f04qs6ddqz]  .biztech-icon-button,
    .biztech-portal-header[b-f04qs6ddqz]  .biztech-menu-trigger {
        min-inline-size: 44px;
    }
}
/* /Components/Layout/SignInLayout.razor.rz.scp.css */
/* W0-59 · the front door's own surface.

   🔴 WHAT THIS IS NOT: .biztech-anon-*, whose panel is max-width: 46rem - §5's PROSE MEASURE (R4.6),
   there because §7's assignee reads a case. /login put two fields and a button in it, and measured
   736 x 608 at 1440 before this commit: not just wide but TALL, because .biztech-anon-main declares no
   align-items, so the panel stretched to the full column height with its content hugging the top edge.

   Every length below is a 4px multiple and every colour is a --biztech-* token. No prefers-color-scheme
   (I16 · R7.3): UiThemeTokens re-emits the palette when the mode flips, so a media query would follow the
   OS while the application follows its own field. Both other layout sheets carry the same note. */

.biztech-signin[b-abtjk9s9fe] {
    display: flex;
    flex-direction: column;
    min-block-size: 100vh;
    background: var(--biztech-palette-background);
}

/* The same band AnonymousLayout renders, at the same measurements: a front door that did not look like
   the rest of the application would be a different kind of wrong. */
.biztech-signin-header[b-abtjk9s9fe] {
    background: var(--biztech-palette-primary);
    padding: 16px 24px;
}

.biztech-signin-logo[b-abtjk9s9fe] {
    height: 2rem;
    display: block;
}

/* 🔴 THE LINE THE OLD SURFACE WAS MISSING, AND IT IS align-items.
   .biztech-anon-main is `display: flex; justify-content: center`, which centres horizontally and leaves
   align-items at its `stretch` default - so its panel took the full column height. Correct on /i/{key},
   where a 23-field form fills it; absurd on a page with two fields.

   Centred on BOTH axes, and then pulled up: a sign-in panel dead-centre in a tall viewport reads as
   floating, and every front door worth copying sits above the middle. `justify-content: center` with
   `padding-block: 48px 96px` puts it in the upper third without a magic number - the asymmetric padding
   IS the offset, and both values are on the 4px scale. */
.biztech-signin-main[b-abtjk9s9fe] {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 48px 16px 96px;
}

/* 24rem = 384px, a whole multiple of the 4px base unit, so R5.4 is satisfied with no LayoutConstants row
   (Decision 5). §5 records that T29 met that failure three times and rounded to the scale each time, and
   the card's Do-not says a login-panel width "is a candidate row and should be argued for on its own
   merits, in the plan, or not taken". It is not taken: nothing about 384px is load-bearing to the pixel.

   box-sizing: border-box, which .biztech-anon-panel spent finding S6 not having: `width: 100%` plus
   padding plus a border overflows its parent without it, and at 375px that is a page that scrolls
   sideways. */
.biztech-signin-panel[b-abtjk9s9fe] {
    box-sizing: border-box;
    inline-size: 100%;
    max-inline-size: 24rem;

    background: var(--biztech-palette-surface);

    /* lines-default, not divider: this edge is what makes the panel read as an object, so it is a boundary
       under WCAG 1.4.11 and carries the 3:1 the palette's Outline role is tested at. The same call
       .biztech-anon-panel makes, for the same reason. */
    border: 1px solid var(--biztech-palette-outline);
    border-radius: var(--biztech-radius-default);
    padding: 32px;
}

.biztech-signin-footer[b-abtjk9s9fe] {
    padding: 16px 24px;
    text-align: center;
}

/* The phone rule app.css's block 4 gives .biztech-anon-panel, which never reached /login because that
   selector is not this one. 32px each side plus the main's 16px is 96px of a 375px screen. */
@media (max-width: 599.98px) {
    .biztech-signin-panel[b-abtjk9s9fe] {
        padding: 16px;
    }

    .biztech-signin-main[b-abtjk9s9fe] {
        padding: 24px 8px 48px;
    }
}
/* /Components/Layout/TenantChrome.razor.rz.scp.css */
/* W0-27 · the tenant's chrome. TOKENS ONLY - R3.1's sweep in PortalStyleSheetTests reads every
   *.razor.css in this project and refuses a hex or rgb() literal, with two allow-listed circuit-chrome
   blocks in app.css that this file is not one of. */

/* The chrome's own box. It exists only when there is chrome to show, which is what keeps an unconfigured
   tenant's footer at zero height rather than a permanently empty bordered strip - see the component's
   comment for why that lives here and not on the layout's <footer>. */
.biztech-chrome[b-z5u91wc0r2] {
    /* 12px and 1px: a whole multiple of the 4px base unit, and R5.6's fixed border width. Both are what
       R5.4's sweep in PortalStyleSheetTests accepts. */
    padding: 12px;
    text-align: center;
    border-block-start: 1px solid var(--biztech-palette-outline-subtle);
}

/* NO `margin: 0` here, and UtilitySpecificityTests is what stops it going back in. BizTechCssVariables'
   document reset already zeroes p's margin, and a scoped restatement would be (0,2,0) against a utility
   class's (0,1,0) - so it would silently beat every biztech-mb-* a page passes this component, while the
   class stayed present in the markup and UtilityClassTests kept reporting it as used. */
.biztech-poweredby[b-z5u91wc0r2] {
    /* R4.3 · from the type scale, never a literal. T29 N22 removed a bare `font-size: 0.8125rem` from
       AnonymousLayout.razor.css for exactly this reason: 13px sat between Caption's 12px and Body2's
       14px for no recorded reason, and a literal does not follow the scale if the scale moves. */
    font-size: var(--biztech-font-caption-size);
    line-height: var(--biztech-font-caption-line-height);
    color: var(--biztech-palette-text-secondary);
}

/* Both links take Primary, and that is what satisfies the card's contrast clause WITHOUT introducing a
   pair anybody has to measure: PaletteContrast.RequiredPairs already tests Primary against Surface and
   against Background at 4.5:1, in both modes, and BizTechThemeOptions.Validate refuses to boot a
   configured palette that fails either. A bespoke accent here would be a pair nothing tests. */
.biztech-poweredby a[b-z5u91wc0r2],
.biztech-cta[b-z5u91wc0r2] {
    color: var(--biztech-palette-primary);
}

/* R3.11 · NO `outline: none` here or anywhere. The one focus ring is app.css's, on :focus-visible, and it
   reaches these anchors already - this file deliberately declares nothing about focus, so there is
   nothing to beat and nothing to lose. */
.biztech-cta[b-z5u91wc0r2] {
    display: inline-block;

    /* 4px, one step of the base unit. R5.4's sweep accepts a whole multiple of 4 or a named layout
       constant, and this is the former. */
    margin-block-start: 4px;

    font-size: var(--biztech-font-body2-size);
    font-weight: var(--biztech-font-body2-weight);
}

/* 🔴 W1a-10 · F2.20. The static page links. A LIST, reset to look like a row of links: the <ul> is there so
   a screen reader is told this is a list of navigation destinations, and the reset is so a sighted reader is
   not shown bullets in a footer. Removing the element instead would trade the announcement for the styling,
   which is the wrong way round (R12.4 prefers the real element). */
.biztech-chrome-pages[b-z5u91wc0r2] {
    list-style: none;

    /* The document reset zeroes ul's margin and padding, so this file restates NEITHER - the same reasoning
       .biztech-poweredby's header gives for having no `margin: 0`: a scoped restatement is (0,2,0) against a
       utility class's (0,1,0) and would silently beat every biztech-m*-* a caller passes in. */
    display: flex;
    flex-wrap: wrap;
    justify-content: center;

    /* 4px and 12px, whole multiples of the 4px base unit, which is what R5.4's sweep accepts. The row gap
       matters as much as the column gap: these wrap at 375px (W1a-09's viewport), and wrapped rows touching
       each other read as one run-on line. */
    gap: 4px 12px;
    margin-block-start: 4px;
}

/* Primary, for the reason both links above take it: PaletteContrast.RequiredPairs already tests Primary
   against Surface and against Background at 4.5:1 in both modes, so this introduces no pair anybody has to
   measure. Caption size, because these are footer links rather than page content - the same step
   .biztech-poweredby takes, from the scale rather than a literal (R4.3). */
.biztech-chrome-page[b-z5u91wc0r2] {
    color: var(--biztech-palette-primary);
    font-size: var(--biztech-font-caption-size);
    line-height: var(--biztech-font-caption-line-height);
}
/* /Components/Pages/AgentAccess.razor.rz.scp.css */
/* W8-05 · F10.12 — the agent-access console. TOKENS ONLY: R3.1's sweep in PortalStyleSheetTests reads
   every *.razor.css in this project and refuses a hex or an rgb() literal. */

/* 🔴 THE CHECKBOX GRID, WHICH IS LEGACY'S CONTROL AND THE CARD'S "bulk is real work".
   RulesPermissionsPage.cshtml's own `.rp-pages-checks` is `repeat(auto-fill, minmax(180px, 1fr))` with a
   scroll cap, and this is that shape in this application's units - an operator who knows that screen
   meets the same control here.

   auto-fill rather than a fixed column count: a tenant may have three collections or thirty, and the
   grid has to be honest at both without a breakpoint per size. 14rem is wide enough for a collection
   name at the caption step without wrapping mid-word. */
.biztech-agent-collection-picks[b-el5iun7byc] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));

    /* 8px and 16px: whole multiples of the 4px base unit, which is what R5.4's sweep accepts. The row
       gap is tighter than the column gap because a checkbox and its label are one line - vertical air
       between them would read as more separation than there is. */
    gap: 8px 16px;

    /* 🔴 CAPPED AND SCROLLABLE, for the reason legacy caps its own at 220px: a tenant with thirty
       collections would otherwise push the Grant button below the fold, and a form whose submit control
       cannot be seen is one people abandon. The cap is in rem so it grows with the reader's type size. */
    max-height: 18rem;
    overflow-y: auto;

    /* R5.6's fixed border width. The box is a group of controls rather than a surface of its own, so it
       is outlined rather than filled - the UiPaper it sits inside already carries the surface. */
    border: 1px solid var(--biztech-palette-outline-subtle);
    border-radius: var(--biztech-radius-default);
    padding: 12px;
}
/* /Components/Pages/AgentSession.razor.rz.scp.css */
/* W6-45 · F8.1 · design §3 and §4. The agent's session console — the two rules that are about THIS
   page's own elements and could not live anywhere else.

   R14.1: go one layer down only when the layer above cannot reach. `SessionStage.razor.css` is the
   frame's sheet and reaches the frame's own boxes; `UiToolbar.razor.css` is the seam's and reaches the
   row. Neither can see a wrapper this page writes into a slot, because a scoped rule compiles to
   `.rule[b-owner]` and these elements carry THIS page's scope attribute — which is also why the frame
   needs `::deep` to reach them from the other side. */

/* 🔴 THE MARKERS MUST NOT BECOME BOXES.

   Every control in the toolbar is wrapped in a span carrying `data-session-control`, because
   Components/Ui takes DECLARED parameters only — an undeclared attribute on a seam component is
   rejected at render, which is the finding `data-show-section` already records. Those markers are what
   let the render tests assert the toolbar's SET rather than a handful of labels.

   A wrapper span is a flex ITEM, so without this rule each control would sit inside its own box and the
   row's gap would apply between the boxes rather than between the controls. `display: contents` removes
   the box and leaves the children as direct flex items of the row. It is the same mechanism
   `UiToolbar.razor.css` uses on `.biztech-toolbar-inline`, for the same reason and one layer up.

   It zeroes no box-model property (R14.1a, UtilitySpecificityTests): there is no padding, margin,
   border or size here to zero — the element simply stops generating a box of its own. */
.biztech-session-control[b-qjhkbrwfxq] {
    display: contents;
}

/* 🔴 W6-53 MOVED .biztech-session-heading TO app.css, AND THE REASON IS R8.2.

   The rule and its whole argument were here, scoped to this page. SessionViewer then needed the
   identical thing - design §3 empties the top of ITS stage too - and a scoped copy would have been the
   same rule in two files: "two components doing one job is the failure this rule exists for". A scoped
   sheet cannot be shared, so the class is app.css's now, with the reasoning intact. */
/* /Components/Pages/AssigneeForm.razor.rz.scp.css */
/* T26's session badge, moved here from wwwroot/css/app.css by T29 N17.

   WHY IT MOVED. R14.1 ranks the four styling layers and says to go one layer down only when the one
   above cannot reach. app.css is layer 4 - the last resort - and it is for host-page chrome outside
   the component tree, markup rendered by a CHILD component, or third-party classes. This badge is
   none of those: it is a <span> in AssigneeForm.razor's own markup, which a component-scoped sheet
   reaches directly. It was in the global file because that is where its neighbours happened to be.

   The rules themselves are unchanged, and the three PortalStyleSheetTests that pin them now read
   this file instead. Same assertions, same values - only the file moved. */

/* Legacy's green SESSION badge, from the 2026-07-30 screenshot of the old form.

   It carries the CASE REFERENCE rather than a session code of its own. The case IS the session
   (design D2), so a second identifier meaning "this same case" would be one meaning too many for
   one thing - rule 5's argument, applied to a label. Nothing the old system showed is lost: the
   reference was already on this page, as body text. This makes it look like the badge it was. */
.biztech-session-badge[b-rykkqe4yhp] {
    display: inline-flex;
    align-items: center;

    /* T29 N22. Was 0.2rem 0.65rem - 3.2px and 10.4px, neither a multiple of the 4px base unit nor a
       named layout constant, so R5.4 allows it as neither. Rounded to the scale (4px / 12px) rather
       than registered as a constant: a registry of "because it was already this" is the failure mode
       the registry exists to prevent. One pixel taller, two wider. */
    padding: 0.25rem 0.75rem;

    /* The pill, one of the two radii R5.5 allows. */
    border-radius: 999px;
    background-color: var(--biztech-palette-success);
    color: var(--biztech-palette-on-success);

    /* 0.75rem = 12px, the floor for body text. It was 0.72rem (11.52px), which made this badge
       small AND - before T24 gave --mud-palette-success a composed contrast partner - low
       contrast at 2.24:1. Those two defects compound; this fixes the half CSS owns. */
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.03em;

    /* nowrap so a reference never breaks mid-token; the header flex-wraps instead, which drops the
       badge onto its own line on a narrow screen rather than splitting it. */
    white-space: nowrap;
}

/* Left the session. Grey rather than green, because a live badge over a form that is no longer
   sharing is the one state this bar must not be able to show.

   T24 moved this off --mud-palette-text-disabled, which was the wrong token twice over. It is the
   colour of an INACTIVE CONTROL - WCAG 1.4.3 exempts those from contrast, and MudBlazor spends it in
   thirty places that are all disabled affordances - so it is deliberately dim, and it put this badge
   at 2.65:1 light and 1.24:1 dark. This badge is not a disabled control; it is a label reporting a
   real state, and text-secondary is the token that means "supporting copy, still legible": 7.86:1
   light, 8.50:1 dark. */
.biztech-session-badge-idle[b-rykkqe4yhp] {
    background-color: var(--biztech-palette-text-secondary);
    color: var(--biztech-palette-surface);
}

/* W3-17 · F6.21. The externally hosted form's frame. Full width, tall enough to hold a real form without
   the visitor scrolling a tiny box, and a 1px outline so it reads as a distinct surface (R5.6 fixes a
   border at 1px). The colour is the palette's, so it follows dark mode; the height is a 4px-scale multiple
   (40rem = 640px). No radius: the frame is content, not a control. */
.biztech-external-form[b-rykkqe4yhp] {
    width: 100%;
    min-height: 40rem;
    border: 1px solid var(--biztech-palette-outline);
}
/* /Components/Pages/BroadcastHost.razor.rz.scp.css */
/* W6-21 · F8.15. The broadcast host console.

   TOKENS ONLY — R3.1's sweep in PortalStyleSheetTests reads every *.razor.css in this project and refuses
   a hex or rgb() literal. Spacing is whole multiples of the 4px base unit, spelled as pixels, which is
   what R5.4 accepts and what StaticPage.razor.css and ChannelPage.razor.css beside it both do.

   🔴 NO prefers-color-scheme — invariant I16. Both modes come from the tokens the layout swaps.

   🔴 NO `margin: 0` RESET ON ANY <p> RULE. <p> is already in BizTechCssVariables' document reset, and
   restating it in a scoped sheet would beat every biztech-mb-* a caller passes — (0,2,0) against (0,1,0),
   silently. UtilitySpecificityTests named exactly that on ChannelPage's first draft.

   Every element here is declared in the razor file, so every rule is scoped and none needs ::deep. */

.biztech-broadcast-title[b-ld94va5j1x] {
    margin-block-end: 16px;
    color: var(--biztech-palette-text-primary);

    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-h5-size);
    font-weight: var(--biztech-font-h5-weight);
    line-height: var(--biztech-font-h5-line-height);
}

/* 🔴 THE REFUSAL IS BODY TEXT, NOT AN ERROR. A host who arrived twenty minutes early has done nothing
   wrong, and F8.27's complaint is precisely that the current window treats them as if they had. The
   sentence names the times and the leeway; the styling should not shout. */
.biztech-broadcast-note[b-ld94va5j1x] {
    margin-block-end: 16px;
    color: var(--biztech-palette-text-secondary);

    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-body1-size);
    font-weight: var(--biztech-font-body1-weight);
    line-height: var(--biztech-font-body1-line-height);
}

/* What the room is doing, and what went wrong. Both read as statements of fact — the severity a failure
   carries comes from UiAlert around it, not from this rule, so a stream state and a failure line are set
   identically and only their container differs. */
.biztech-broadcast-state[b-ld94va5j1x] {
    margin-block-start: 8px;
    color: var(--biztech-palette-text-primary);

    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-body2-size);
    font-weight: var(--biztech-font-body2-weight);
    line-height: var(--biztech-font-body2-line-height);
}
/* /Components/Pages/CasePage.razor.rz.scp.css */
/* 🔴 W4-23. MOVED FROM CaseDetailPanel.razor.css, character for character, when that panel was
   dissolved into this page. The rule and its reasoning are T29 N11's and are reproduced in full
   because the reasoning is the valuable half.

   T29 N11. The milestone control's width, which was two inline Style="min-width: 12rem;" attributes
   on the two mutually-exclusive controls in the panel's header - R5.4 (a raw length is a scale
   multiple or a named constant, and never an inline layout style) and R14.2 (inline Style for layout
   is forbidden), twice, in one file.

   ::deep, and it is required rather than stylistic. Blazor's scoped-CSS attribute is stamped on the
   elements THIS component renders; UiSelect and UiTextField are child components that render their
   own markup, so a bare `.biztech-milestone-control` here would be compiled to
   `.biztech-milestone-control[b-xxxxxxxxxx]` and match nothing. `::deep` compiles to
   `[b-xxxxxxxxxx] .biztech-milestone-control`, which reaches the class the Class= parameter hands
   down - still scoped to this component's subtree, so no other select in the app is touched.

   12rem = 192px, a 4px-scale multiple, so it stays a scale value rather than becoming a registry
   constant. It exists so the two controls do not collapse to their content width in the action row's
   flex row - a "Milestone" select that shrinks to the width of "Saved" reads as a chip. */
[b-p53pbnp121] .biztech-milestone-control {
    min-width: 12rem;
}
/* /Components/Pages/ChannelPage.razor.rz.scp.css */
/* W6-19 · F8.23. The tenant's branded channel page.

   🔴 NO COLOURS OF ITS OWN. Every colour here is a token, so the page takes W0-25's resolved palette
   through AnonymousLayout and nothing else. A hex literal would fail R3.1's sweep in
   PortalStyleSheetTests anyway, but the reason is sharper than the rule: a colour on this page would be
   the channel-level palette the card's third `## Do not` forbids, arriving through a stylesheet instead
   of through a column.

   🔴 NO prefers-color-scheme — invariant I16. Both modes come from the tokens, which the layout swaps.

   Spacing is whole multiples of the 4px base unit, spelled as pixels, which is what R5.4's sweep accepts
   and what StaticPage.razor.css beside it does. There is no --biztech-space-* scale in this project and
   inventing one here would be a second convention.

   Every element below is declared in the razor file, so every rule is scoped and none needs ::deep —
   unlike StaticPage, whose body arrives as a MarkupString. */

.biztech-channel-title[b-9biwncrvn8] {
    margin: 0 0 16px 0;
    color: var(--biztech-palette-text-primary);

    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-h5-size);
    font-weight: var(--biztech-font-h5-weight);
    line-height: var(--biztech-font-h5-line-height);
}

.biztech-channel-schedule-heading[b-9biwncrvn8] {
    margin: 0 0 8px 0;
    color: var(--biztech-palette-text-primary);

    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-h6-size);
    font-weight: var(--biztech-font-h6-weight);
    line-height: var(--biztech-font-h6-line-height);
}

/* 🔴 A REAL LIST, and the markers are removed while the semantics are not. A screen reader announces how
   many broadcasts there are before reading them, which is the one thing a stack of divs cannot do — R12,
   and the reason this is not styled into a grid of boxes. */
.biztech-channel-schedule[b-9biwncrvn8] {
    margin: 0;
    padding: 0;
    list-style: none;

    display: flex;
    flex-direction: column;
    gap: 12px;
}

.biztech-channel-event[b-9biwncrvn8] {
    padding: 12px;
    border: 1px solid var(--biztech-palette-outline-subtle);
    border-radius: var(--biztech-radius-default);
    background: var(--biztech-palette-surface);
}

/* 🔴 NO `margin: 0` HERE OR ON THE TWO RULES BELOW, and the absence is load-bearing. <p> is already in
   BizTechCssVariables' document reset; restating the reset in a scoped sheet would beat every
   biztech-mb-* a caller passes, at (0,2,0) against a utility class's (0,1,0), and silently — the class
   stays in the markup and only the rule stops applying. UtilitySpecificityTests is what says so, and it
   named these three rules the first time this file was written. */
.biztech-channel-event-name[b-9biwncrvn8] {
    color: var(--biztech-palette-text-primary);

    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-body1-size);
    font-weight: var(--biztech-font-h6-weight);
    line-height: var(--biztech-font-body1-line-height);
}

.biztech-channel-event-when[b-9biwncrvn8],
.biztech-channel-event-about[b-9biwncrvn8] {
    margin-block-start: 4px;
    color: var(--biztech-palette-text-secondary);

    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-body2-size);
    font-weight: var(--biztech-font-body2-weight);
    line-height: var(--biztech-font-body2-line-height);
}

/* Both empty states — "the schedule is hidden from you" and "there is nothing scheduled" — take one
   appearance, because they are one kind of message. What differs is the sentence, which is the half that
   carries the information. */
.biztech-channel-empty[b-9biwncrvn8] {
    color: var(--biztech-palette-text-secondary);

    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-body2-size);
    font-weight: var(--biztech-font-body2-weight);
    line-height: var(--biztech-font-body2-line-height);
}
/* /Components/Pages/EducationHub.razor.rz.scp.css */
/* W7-06 · the filter row, and nothing else.

   The cards are EducationItemFrame's and the grid is UiGrid's. A card style here would regress four
   merged cards - the card's own Do-not: "do not add a bespoke card component where W0-52's set has one."

   R13.1's breakpoint: sm is 600, and the media query below uses it as a min-width rather than as
   599.98px, because 599.98 is the value the MAX-width queries use. ViewportAssetTests pins that all three
   files naming the breakpoint agree; this file names the other side of it.

   🔴 R5.8's 44px UNDER A COARSE POINTER, and the card asks for it on THIS page even though R5.8 exempts
   the operator console "by decision (mouse at a desk)". The card's ## Done when overrides that exemption
   in terms: "44px targets under a coarse pointer". Keyed on the POINTER rather than on the viewport,
   because a touch laptop needs the bigger target and a narrow desktop window does not - which is
   UiGalleryViewportTests' measured reason for setting IsMobile rather than a width. */

/* 🔴 W0-75 · A GRID, AND IT WAS A FLEX ROW WHOSE `> *` CHILD RULE THIS FILE'S OWN NEXT COMMENT
   EXPLAINS THE DANGER OF. The two search and type wrappers ARE this page's own divs, so the rule did
   apply here - but "a scoped `> *` reaches only the children this component wrote itself" is not a fact
   a reader can check from the stylesheet, and four rules elsewhere in the application had the same shape
   and reached nothing at all. ScopedStyleReachTests refuses the shape now, so the sizing moved to where
   it cannot be wrong: two equal TRACKS.

   minmax(0, 1fr) carries what `min-width: 0` carried, and for the identical reason - a track's default
   minimum is its content's minimum, so a long option label would make the select refuse to narrow and
   push the document sideways. That is the failure R13.8 is about, one `overflow-x: hidden` away from
   being invisible.

   align-items: end because a search field and a select have labels of different heights, and a row of
   two controls at two heights reads as a mistake. */
.biztech-education-filters[b-8zcsfmtfpy] {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
}

@media (min-width: 600px) {
    .biztech-education-filters[b-8zcsfmtfpy] {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        align-items: end;
    }
}

/* 🔴 ::deep, AND WITHOUT IT THIS BLOCK MATCHES NOTHING AT ALL. Blazor's CSS isolation stamps THIS
   component's scope attribute on the elements in THIS file's markup, and rewrites a selector by appending
   that attribute to its LAST element - so `.biztech-education-filters input` compiles to
   `... input[b-thisfile]`. The <input> and <select> are rendered by UiTextField and UiSelect, so they
   carry THEIR scope and never this one, and the rule is dead.

   Found by EducationHubViewportTests measuring 32px where R5.8 asks for 44 - which is the only thing that
   could have found it. The class is present in the markup so bUnit locates it, and the sheet is
   well-formed so the colour and length sweeps pass. Only a computed style says whether a rule applied.

   EducationItemFrame.razor.css records the same trap for its own title, CasePage.razor.css and
   MainNavMenu.razor.css both record it too - the second one "found on screen rather than in a test". */
@media (pointer: coarse) {
    .biztech-education-filters[b-8zcsfmtfpy]  input,
    .biztech-education-filters[b-8zcsfmtfpy]  select {
        min-height: 44px;
    }
}
/* /Components/Pages/FormTemplates.razor.rz.scp.css */
/*
    W3-25 · the per-version lists inside a library row.

    🔴 SCOPED RATHER THAN IN app.css, AND THAT IS R14.1's LAYER 4. These <ul>s are rendered by THIS
    component, so a scoped sheet reaches them - unlike a rule aimed at the surrounding <td>, which UiTd
    renders and which UiTable's own header explains a scoped sheet cannot touch.

    Three columns each hold one line per version and the row has to read ACROSS, so every line is the
    same height in all three. line-height rather than a fixed height: a chip and a caption differ in
    intrinsic height, and pinning the box would clip whichever is taller the first time somebody adds a
    longer marker.
*/

.biztech-form-versions[b-nuwbwztfpp] {
    list-style: none;

    /* A REAL declaration rather than `padding: 0`, following the four <ul> resets already in
       UtilitySpecificityTests' Declared set. A scoped selector is (0,2,0) against a utility class's
       (0,1,0), so `padding: 0` here would silently beat every biztech-pa-* a caller passes - which is
       the defect that whole test file exists to catch. Only the block margin needs the exemption. */
    margin: 0;
    padding-inline-start: 0;
}

.biztech-form-versions > li[b-nuwbwztfpp] {
    display: flex;
    align-items: center;
    min-height: 1.75rem;
}

/* 🔴 W0-63 · `.biztech-form-open` WAS HERE, AND IT REACHED NOTHING. It sat on a Blazor NavLink, which
   renders its own <a> and therefore carries none of this component's scope attribute - so the compiled
   `.biztech-form-open[b-…]` matched no element in the document. It also read `--biztech-primary`, a
   variable nothing declares. Both are answered by the global `.biztech-link` in app.css, which needs no
   ::deep because it is not scoped at all; the reasoning that was here moved with it. */
/* /Components/Pages/Inquiries.razor.rz.scp.css */
/* W4-26 · /inquiries' own rules. Design §7's `.mk-seg` and `.mk-filters`, and §8's phone.

   🔴 EVERY SELECTOR HERE MATCHES AN ELEMENT THIS PAGE RENDERS ITSELF, so there is no ::deep in this
   file and none is needed. Blazor stamps a scoped sheet's attribute only on elements the component
   renders, and each of these is a plain <span> or <div> written in Inquiries.razor - never a Ui*
   component's own box. UiTab.razor's header records what happens when that distinction is missed: a
   rule compiled against a component's element matches NOTHING, not "matches less".

   🔴 AND THAT IS WHY THE VIEW SWITCH'S RULE IS HERE RATHER THAN IN CaseWorkListStrip.razor.css.
   The switch is passed THROUGH that component into UiTabs.Trailing, so the natural place to look for
   its styling is the component it passes through - but a RenderFragment written in THIS file compiles
   into THIS file's render tree, so the span carries INQUIRIES' scope attribute and only a rule here
   can reach it. CaseWorkListStrip deliberately has no sheet at all.

   🔴 NOTHING HERE ZEROES A MARGIN OR A PADDING (R14.1a) and nothing declares a block-axis size. The
   controls take their box from --biztech-control-height, which MainLayout.razor.css narrows to 32px
   for the console; a declaration here would fork that decision, and the fork would look right on the
   operator console and wrong on /i/{key} - the surface nobody opens while building a console page. */

/* 🔴 IT FRAMES THE TWO SEGMENTS AND RESTYLES NEITHER. Reaching into UiButton's own anchor would mean
   ::deep at the same specificity as UiButton's own declarations, decided by nothing more principled
   than filename order in the generated bundle - the hazard UiToolbar.razor.css's separator note
   records in full. The specimen's joined look is worth a border and a 2px gap; it is not worth
   forking a seam control's box. */
.biztech-view-switch[b-l6e5vpr3hw] {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 2px;
    border: 1px solid var(--biztech-palette-outline-subtle);
    border-radius: var(--biztech-radius-default);
}

/* 🔴 W4-26 · THE TOOLBAR'S LABELS ARE CLIPPED, NOT DELETED, AND THIS IS THE RULE THE ≤48px CLAUSE
   RESTS ON. Read this before "tidying" it away.

   THE ARITHMETIC. `.biztech-field` stacks a label above its box with a 4px gap, and the caption slot is
   12px at line-height 1.66 - 19.92px. So a labelled control in this row costs 24px MORE than its own
   box, and the card's clause is that the filter row measures 48px or less at 1264 wide. Design
   specimen 11 is drawn with no labels at all: `.mk-input` shows placeholder text inside the box and
   `.mk-select` reads "Stage: Signed". The card says twice that where prose and specimen disagree the
   SPECIMEN WINS, and this is the disagreement that clause depends on.

   🔴 CLIPPED RATHER THAN display:none, AND THE DIFFERENCE IS THE WHOLE POINT. The label keeps its
   `for` association, stays in the DOM and stays in the ACCESSIBILITY TREE, so every control in this row
   still has exactly the accessible name it had before - which `display: none` or `visibility: hidden`
   would take away, leaving three unnamed controls and an R12.4 failure that no test in this repository
   would see. This is app.css's own `.biztech-skip-link` idiom, and its note carries the reasoning:
   "present, focusable, and occupying no space".

   🔴 ::deep, AND ITS ROOT HAS TO BE ONE OF *THIS FILE'S* ELEMENTS - WHICH THE FIRST VERSION GOT
   WRONG AND A BROWSER PRICED AT 73.90625px. It was written
   `[data-case-filters] .biztech-toolbar ::deep .biztech-field-label`, which Blazor rewrites to
   `... .biztech-toolbar[b-xxxxxxxxxx] ...` - and `.biztech-toolbar` is rendered by UiToolbar, not by
   this page, so the scope attribute is never on it and the rule matched NOTHING. Not "matched less":
   nothing, which is exactly what UiTab.razor's header warns about and what MainNavMenu.razor.css
   records finding on screen rather than in a test.

   The two spans below ARE this file's, so the rewrite lands. They are also the only two labelled
   controls in the row, which is why this is two selectors rather than one on their container: the
   PANEL's fields keep their visible labels, and that is right - a panel that unfolds has room, and
   four unlabelled controls in a column would be four mysteries.

   R14.1a has no subject here: what that rule forbids is ZEROING a box-model property the utility layer
   owns - a margin or a padding - and nothing in app.css sets a label's size or its clip. */
.biztech-filter-search[b-l6e5vpr3hw]  .biztech-field-label,
.biztech-filter-stage-wide[b-l6e5vpr3hw]  .biztech-field-label {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* And with the labels out of flow every child of the row is its own single box, so UiToolbar's
   `align-items: center` centres like against like. That is what closes W0-63 finding 3 / W0-65
   finding 4 here - "a field stacks a caption-scale label above its box and a button does not, so
   `align-items: center` centres two different boxes and nothing lines up" - by removing the stack
   rather than by re-aligning the row. PortalPageShellTests' own theory is the guard, and this page's
   row is why its Inquiries entry moved. */

/* The search box is the row's one GROWING control - design §7's `mk-input-grow`, capped at 320px so it
   does not swallow a wide row and leave Stage and More adrift at the far edge. An inline size is not a
   spacing utility, so it lives here rather than in a Class (R8.7).

   `min-inline-size: 0` is what lets it shrink below its content, which is the same declaration
   UiToolbar makes on the row for the same reason: without it the box grows, the row grows, and
   `.biztech-main`'s `overflow-x: hidden` clips the result rather than scrolling it (R13.8). */
.biztech-filter-search[b-l6e5vpr3hw] {
    flex: 1 1 12rem;
    min-inline-size: 0;
    max-inline-size: 320px;
}

/* 🔴 D1 · THE TWO STAGE COPIES AND THE TWO MORE LABELS, AND EXACTLY ONE OF EACH IS DISPLAYED AT ANY
   WIDTH.

   `display: none` rather than `visibility: hidden` or a class swap, because it removes the other copy
   from the ACCESSIBILITY TREE as well as from the screen - which is what makes rendering a control
   twice honest rather than a duplicate announcement. UiToolbar.razor.css does the same thing at the
   same breakpoint and app.css's breadcrumb block states the identical reason: "a hidden ancestor is
   correctly absent from the accessibility tree rather than announced and invisible."

   The narrow pair is off by default and switched on below `sm`, so the desktop layout is the one with
   no media query behind it. 599.98px is how R13.1 says this application writes MudBlazor's `sm`. */
.biztech-filter-stage-narrow[b-l6e5vpr3hw],
.biztech-filter-more-narrow[b-l6e5vpr3hw] {
    display: none;
}

/* W4-26 · the applied-filter chips. Design §7's `.mk-chips`.

   Wrapping, so a row of five chips is never clipped by `.biztech-main`'s `overflow-x: hidden` at any
   width (R13.8). `align-items: center` puts the count caption on the chips' own centre line rather
   than on their baseline, which a chip's padding would otherwise push out of true. */
.biztech-filter-chips[b-l6e5vpr3hw] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
}

/* 🔴 W4-26 · THE SORT DIRECTION, AS ONE GLYPH ROTATED. Design §9: "One glyph, rotated by CSS for the
   other direction. That is one meaning with two orientations, not two meanings" - which is why UiIcons
   holds ArrowUpward and deliberately no ArrowDownward.

   The <span> is rendered by Inquiries.razor, so this sheet's scope attribute lands on it and no ::deep
   is needed - and rotating the span rather than the <svg> is what keeps this rule off UiIcon's own box,
   where it would have needed ::deep and would have applied to every icon on the page. */
.biztech-sort-glyph[b-l6e5vpr3hw] {
    display: inline-flex;
    align-items: center;
    margin-inline-start: 4px;
}

.biztech-sort-descending[b-l6e5vpr3hw] {
    transform: rotate(180deg);
}

/* The disclosure panel. In flow, below the toolbar - R9.6 names an inline panel as right for a filter
   and forbids a drawer - and wrapping, so five controls are never clipped at any width from 375px up
   (R13.8).

   `align-items: end` puts a date field's box on the same baseline as a select's despite their labels
   being different heights; without it the row reads as three controls at three heights. */
.biztech-filters-panel[b-l6e5vpr3hw] {
    display: flex;
    flex-wrap: wrap;
    align-items: end;
    gap: 8px;
    margin-block-start: 8px;
    padding: 8px;
    border: 1px solid var(--biztech-palette-outline-subtle);
    border-radius: var(--biztech-radius-default);
    background: var(--biztech-palette-surface);
}

/* 🔴 AND THE COLLAPSED PANEL HAS TO BE TOLD TO GO, WHICH A BROWSER PRICED AT 73.90625px OF DEAD
   SPACE ABOVE THE TABLE.

   `hidden` is an attribute, and its effect is a USER-AGENT rule - `[hidden] { display: none }` - which
   any author `display` declaration outranks. So the rule above switched the panel back on while the
   attribute was still there: the markup said hidden, the accessibility tree said hidden, and the layout
   said 74px. Measured on the running page at 1264x615 before this rule existed, with the panel
   collapsed: `panel: top=273.0 bottom=347.0 h=73.9`.

   This is the one thing `@if` would have got right for free, and it is still not worth it - the
   trigger's aria-controls has to point at an element that EXISTS, and a dangling reference is a
   promise about a panel a screen reader cannot find. One rule is cheaper than that. */
.biztech-filters-panel[hidden][b-l6e5vpr3hw] {
    display: none;
}

@media (max-width: 599.98px) {
    .biztech-filter-stage-wide[b-l6e5vpr3hw],
    .biztech-filter-more-wide[b-l6e5vpr3hw] {
        display: none;
    }

    /* `inline` restores each span's INITIAL value rather than resetting something the utility layer
       owns, so R14.1a has no subject here - what that rule forbids is zeroing a margin or a padding
       app.css declares. */
    .biztech-filter-more-narrow[b-l6e5vpr3hw] {
        display: inline;
    }

    /* ONE CONTROL PER LINE ON A PHONE. Two 40px selects side by side at 375px leaves neither label
       readable - the control height goes back to 40px off the console's 32px below `sm`, which is the
       token's own narrowing, so the panel is taller here than the same markup is on a laptop.
       R13.3's reflow-per-pattern, applied to a filter panel.

       🔴 W0-75 · THE PANEL TURNS, RATHER THAN EVERY CHILD BEING TOLD TO FILL THE LINE. This was
       `.biztech-filter-stage-narrow, .biztech-filters-panel > *  { display: block; flex: 1 1 100% }`, and
       the `> *` half is a shape ScopedStyleReachTests refuses now: Blazor stamps the scope attribute on
       a selector's last compound, so a scoped `> *` reaches only the children the component wrote itself
       and silently skips any child that is another component's root. It was CORRECT here - every child
       of this panel is a span this page writes - and it was inert in four other files, which is why the
       shape goes rather than the rule.

       A column flex container says the same thing without naming a child: one item per line, and
       stretch gives each the panel's width. `flex: 1 1 100%` came off with it, because a column's
       cross-axis stretch is what made it redundant.

       🔴 BUT `display: block` ON THE NARROW STAGE FILTER DID NOT COME OFF, AND THE FIRST VERSION OF
       THIS REWRITE DROPPED IT. `.biztech-filter-stage-narrow` is `display: none` by DEFAULT - the pair
       above declares it, so the desktop layout is the one with no media query behind it - and this rule
       is what switches it on below `sm`. Folded into a `> *` selector it looked like part of the
       one-per-line layout; it is not. Without it the stage filter is absent below 600px altogether,
       which is one step worse than the card's own ## Do not, "do not hide a filter that is switched on":
       InquiriesViewportTests.On_a_phone_the_toolbar_collapses_and_keeps_the_view_switch reported
       `[data-filter='stage']:visible` as 0 after opening the panel, and its own comment says that is
       exactly what the fact is there to catch. It stands alone now, naming this page's own class, which
       is what it always was. */
    .biztech-filter-stage-narrow[b-l6e5vpr3hw] {
        display: block;
    }

    .biztech-filters-panel[b-l6e5vpr3hw] {
        flex-direction: column;
        align-items: stretch;
    }
}
/* /Components/Pages/PortalChooser.razor.rz.scp.css */
/* 🔴 W0-75 · /portals' two layout facts, and both are here rather than in app.css because both are
   about THIS page. R14.1's layers: a utility is a rule the markup reaches for repeatedly, a component
   stylesheet is a rule one surface needs. Neither of these has a second call site.

   🔴 AND BOTH RULES NAME ELEMENTS THIS FILE'S OWN MARKUP CARRIES, which is not a detail:
   PortalComposer.razor.css held five rules for elements in two CHILD components, and every one of them
   compiled to a selector matching nothing. A rule here for a class only UiPaper renders would do the
   same. `::deep` is the escape hatch and it is used deliberately or not at all.

   NO overflow, no !important, and no new token: everything below reads a variable app.css declares. */

/* 🔴 THE CREATE PANEL'S FIELD GRID IS NOT HERE ANY MORE, AND THAT ABSENCE IS THE NOTE.
   `.biztech-portal-create-fields` declared `repeat(2, minmax(0, 1fr))` for the panel's four fields - a
   second twelve-column system for one panel, when R13.2 says "structure responsively in MARKUP; use CSS
   only for what the grid cannot reach" and R5.2's UiGrid reaches a two-up field pair exactly. The panel
   uses `<UiGrid>` with `Xs="12" Md="6"` now, which is the span the application's three other forms
   declare. /portals/{slug}/pages needing the identical layout is what settled it: one shared mechanism,
   or the same hand-rolled rule copied into a second scoped stylesheet where the two would drift.

   What the panel keeps from this file is one rule, below, that the grid genuinely cannot reach. */

/* 🔴 A CHECKBOX BESIDE A LABELLED FIELD STARTS ONE LABEL TOO HIGH, AND THIS IS THAT LABEL.
   `.biztech-field` is a label above a control; `.biztech-choice` is a box beside a word, with no label
   block at all. Sharing a grid row with the audience select, the box therefore lined up with the
   select's LABEL rather than with the select - measured at 16px too high, at every width where the two
   share a row, which is what an operator saw: "not aligned with the dropdown".

   align-self: end would bottom-align the two boxes instead, and it is the wrong tool: this label wraps
   to two lines in a narrow column, and the checkbox would then hang below the select. Offset by the
   height of the label block that is missing and the CONTROLS line up whatever the words do.

   CALCULATED FROM THE TWO TOKENS THAT MAKE THE LABEL, never typed as 24px: caption size times caption
   line height is the label's own box (12px x 1.66 = 19.92px), and 4px is `.biztech-field`'s own row gap.
   A type-scale change moves both together. `.biztech-choice` already declares min-block-size:
   --biztech-control-height, the same token `.biztech-input` uses, so with the offset the two boxes
   occupy exactly the same band.

   md (960px), WHICH IS THE BOUNDARY THE MARKUP'S OWN `Md="6"` USES - and it has to be, because below it
   every field is Xs="12" and the checkbox sits UNDERNEATH the select rather than beside it. An offset
   there would read as 24px of unexplained space in a 16px stack. R13.1's spelling for this side of the
   breakpoint is min-width: 960px. */
@media (min-width: 960px) {
    .biztech-portal-create-choice[b-uj15aepq5v] {
        margin-block-start: calc(
            var(--biztech-font-caption-size) * var(--biztech-font-caption-line-height) + 4px);
    }
}

/* The two reports, as two cards rather than two links on one line.

   🔴 A GRID RATHER THAN THE FLEX ROW THIS REPLACED, and the reason is what the flex row got wrong: the
   two links sat 12px apart with nothing between them, so "Check portal configuration health" and
   "Compare what two portals show" read as one run-on sentence - which is exactly what a browser showed.
   Equal tracks give each report its own bounded card, and the card's own border is the separation.

   minmax(0, 1fr) for the same reason the grid above carries it.

   ONE COLUMN UNTIL sm (600px): at 375px two cards side by side are two columns of about 170px, and a
   report name wraps to three lines in each. */
.biztech-portal-reports[b-uj15aepq5v] {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 12px;
}

@media (min-width: 600px) {
    .biztech-portal-reports[b-uj15aepq5v] {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

/* The card's own two lines: the link, then what it answers. The gap is the 4px scale R5.4 sweeps for. */
.biztech-portal-report-link[b-uj15aepq5v] {
    display: block;
    margin-block-end: 4px;
    font-weight: 600;
}
/* /Components/Pages/PortalCompare.razor.rz.scp.css */
/*
    W8-03 · F10.17 — the drift report's two choosers.

    🔴 THE ONLY RULE THIS FILE HAS. Everything else on the page is the seam's: the tables are UiTable, the
    scroll regions are app.css's shared .biztech-table-scroll, and the spacing is the biztech-* utilities.
    A scoped sheet that restated any of them would be a second opinion about a value the tokens already
    hold (R7.x), and PortalStyleSheetTests exists to catch exactly that.

    R13.8 · nothing here declares a width, a height, an overflow or a position on anything outside this
    component's own wrapper, so the main landmark keeps its width and nothing under #biztech-main clips.

    A literal gap rather than var(--biztech-space-3): there is no spacing token, and a var() that
    resolves to nothing paints nothing with no error and no warning. TokenSufficiencyTests caught it.

    R13.1's breakpoints, and the direction is mobile-first: one column below sm, because two selects side
    by side at 375px leave neither wide enough to read a portal's name in.
*/

/* 🔴 W0-75 · A GRID, AND IT WAS A FLEX ROW WHOSE CHILD RULE HAD NEVER APPLIED. "Equal halves rather
   than content-sized, so the pair does not jump as the names change length" was declared as
   `.biztech-compare-choosers > *` - and Blazor stamps the scope attribute on a selector's LAST compound,
   which here is the universal one. The children are UiSelect's roots, rendered by a child component, so
   `> *[b-xxxxxxxxxx]` matched nothing and the two choosers were content-sized after all: exactly the
   jump the comment says it prevents, at every width from 600px up.

   The fix is a grid rather than `::deep`, because equal tracks need no rule on a child at all - nothing
   has to reach an element this page does not own. PortalComposerCopyPanel.razor.css is the same pair of
   controls with the same defect and the same fix, and PortalComposerTilePanel.razor.css records what the
   shape cost the composer. `ScopedStyleReachTests` refuses a universal last compound now.

   minmax(0, 1fr) rather than 1fr: R13.8's grid trap, and a portal's name is exactly the long content
   that sets a track's minimum. */
.biztech-compare-choosers[b-btdfpbo2cu] {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1rem;
}

@media (min-width: 600px) {
    .biztech-compare-choosers[b-btdfpbo2cu] {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}
/* /Components/Pages/PortalComposer.razor.rz.scp.css */
/* 🔴 W1b-06 · F2.24 · the composer's two panes, and why the split is a grid rather than a float.

   NO overflow ANYWHERE IN THIS FILE. The card's ## Do not: "Do not use overflow-x: hidden to make a
   preview measurement pass, and do not weaken an existing viewport assertion to accommodate the
   composer's chrome." R13.8 states the general form - it hides the symptom and the diagnostic together -
   and here it would hide the one defect the preview exists to reveal. */

/* 🔴 W0-75 · ONE COLUMN, AND THIS FILE USED TO DESCRIBE TWO.

   The composer was `minmax(0, 1fr) minmax(0, 1fr)` above md: the arrangement on the left, a sticky
   preview pane on the right holding a 1440px iframe and three control groups. Read on a 1440px screen
   that gave the arrangement about 660px - so every tile panel's four-field geometry row wrapped - and
   scaled the preview down until the thing being previewed was the smallest element on the page.
   PortalPreview.razor is where the preview went, and its header carries the reasoning; what is left here
   is a page whose whole width arranges.

   The grid stays a grid rather than becoming nothing, because the track is what holds the trap below.

   minmax(0, 1fr) rather than 1fr, and this is the grid trap R13.8 exists around: a grid track's default
   minimum is `auto`, which is the CONTENT's minimum - so one wide child inside a `1fr` track would push
   the track past the page and the page sideways, at every viewport. */
.biztech-composer[b-3ww06owyyn] {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 24px;
}

/* 🔴 THE TILE PANELS, TWO ABREAST ABOVE md - the space the preview pane gave back, used rather than
   left as one long column of half-empty rows.

   R13.1 pins the breakpoints to MudBlazor's so a page never disagrees with its grid, and 959.98px is the
   `md` boundary in this application's spelling. One column below it: at 375px two panels are two columns
   of about 170px, and a four-field geometry row wraps to four lines in each.

   24px, which is the gap this page already uses between its own regions and a whole multiple of the 4px
   base unit R5.4 sweeps for. */
.biztech-composer-tiles[b-3ww06owyyn] {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 24px;
}

@media (min-width: 960px) {
    .biztech-composer-tiles[b-3ww06owyyn] {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

/* 🔴 THE PANEL'S OWN BOTTOM MARGIN IS ZEROED HERE, AND THE PRINCIPLE IS WHY RATHER THAN THE PIXELS:
   spacing between siblings is the CONTAINER's job. PortalComposerTilePanel carries `biztech-mb-4` from
   when it was one of a stack; inside a grid that margin lands underneath the gap, so a row of two panels
   sat 40px from the next instead of 24px, and only the last row looked right.

   ::deep is required, and MediaRoom.razor.css records the trap: the element carrying the utility is
   UiPaper's own root, rendered by another component, so this file's scope attribute never reaches it. No
   !important - the utility declares none, so a scoped rule of equal specificity and later origin wins on
   its own. */
.biztech-composer-tiles[b-3ww06owyyn]  .biztech-paper {
    margin-block-end: 0;
}

/* The arrangement pane. min-inline-size: 0 for the same grid reason the tracks above carry it - a tile
   panel holds a four-field geometry row that would otherwise set the track's minimum.

   🔴 W8-22 · AND IT CARRIES THE GAP BETWEEN ITS PANELS NOW, WHICH IS I18 APPLIED ONE LEVEL UP. Its
   children were spaced by a utility EACH - biztech-mb-4 on the launcher panel, biztech-mb-4 on the copy
   panel, biztech-mb-3 on the hint sentence, biztech-mb-3 on Add a tile, and biztech-mb-4 on a tile panel
   that the ::deep rule above then had to zero - so the distances down the page were 16, 12, 12, 16 and
   24px, and no two panels on the composer sat the same distance apart. Nothing in the markup says that,
   which is what makes it a defect only a reader reports: "fix the padding issues".

   24px, the same figure the page's own grid uses between its regions and a whole multiple of the 4px
   base unit (R5.4). The ::deep reset above stays exactly as it was - the tile panels inside
   .biztech-composer-tiles still carry their own biztech-mb-4, and that rule is still what keeps a row of
   two from sitting 40px from the next. */
.biztech-composer-arrangement[b-3ww06owyyn] {
    min-inline-size: 0;
    display: flex;
    flex-direction: column;
    gap: 24px;
}

/* 🔴 W0-75 · FIVE RULES LEFT THIS FILE, AND THEY HAD NEVER APPLIED. `.biztech-composer-tile-head`,
   `.biztech-composer-actions`, `.biztech-composer-geometry`, `.biztech-composer-section` and
   `.biztech-composer-sections` were declared here while every element they name lives in
   PortalComposerTilePanel or PortalComposerSectionPanel. Blazor stamps THIS page's scope attribute on
   THIS page's elements, so each one compiled to `.biztech-composer-tile-head[b-j6ks1ur5km]` and matched
   nothing: the ordering arrows stacked under the tile's heading, and the four geometry fields each took
   a full row. A browser found it; no test could, because a class with no effective rule renders exactly
   like a class with none.

   They now live beside the markup that names them - PortalComposerTilePanel.razor.css and
   PortalComposerSectionPanel.razor.css - and ScopedStyleReachTests is the guard that keeps any rule
   from drifting back here. (It was named ComposerStyleReachTests in this comment, which is not the name
   of anything: the guard is not the composer's, it reads every scoped stylesheet in the application.)

   What is left below is what this page's own markup names, plus one rule that says ::deep and means it. */

/* 🔴 R5.8's 44px UNDER A COARSE POINTER, and PortalTileCard.razor.css records what it cost to find: a
   bare <a> is an inline element and CSS 2.1 §10.7 says min-height does not apply to one, so the
   declaration resolved and governed nothing - measured at 17px. The fix is the display, not the minimum.
   The composer is a dense surface of small controls, which is where a coarse pointer suffers most. */
@media (pointer: coarse) {
    .biztech-composer[b-3ww06owyyn]  button,
    .biztech-composer[b-3ww06owyyn]  a {
        display: inline-flex;
        align-items: center;
        min-block-size: 44px;
    }
}
/* /Components/Pages/PortalHealth.razor.rz.scp.css */
/*
    W1b-07 · F2.25 — the rows behind each finding's count.

    🔴 SCOPED RATHER THAN IN app.css, AND THAT IS R14.1's LAYER 4. These <ul>s are rendered by THIS
    component, so a scoped sheet reaches them - the same standing FormTemplates.razor.css's per-version
    lists have, and the same reason: a rule aimed at markup another component renders would not carry this
    component's scope attribute and would match nothing.

    A LIST rather than a stack of divs, because it is one (R12.4's "native elements first"): a screen
    reader announces "list, 50 items", which is the one piece of context a reader of a capped finding most
    needs.

    🔴 NO `margin: 0`, AND ITS ABSENCE IS A DECISION UtilitySpecificityTests MADE FOR ME.
    That reset went in by reflex, copied from FormTemplates.razor.css, and the suite refused it: a scoped
    selector compiles to `.x[b-xxxxxxxxxx]`, which is (0,2,0) against a utility class's (0,1,0), so
    zeroing a box-model property here beats every biztech-mb- or biztech-pa- utility a caller passes
    (spelled without a wildcard on purpose: a `*` before a `/` closes this comment, which is what
    PortalStyleSheetTests caught the first draft of this paragraph doing) - silently, because
    the class is still in the markup. Six <ul> exemptions are declared in that file's `Declared` set and a
    seventh was available, but the browser's block margin above a list of rows is SEPARATION THIS PAGE
    WANTS rather than noise nobody chose - so the right answer was to stop resetting it, not to earn an
    exemption for doing so.
*/

.biztech-health-rows[b-s1klg2vg3v] {
    list-style: none;

    /* The list indent alone, because the marker it indents for is gone. Not a reset of a property the
       utility layer owns - see the header. */
    padding-inline-start: 0;
}

/* Each row is a thing and, sometimes, where it is and why it is here - so the lines group visually rather
   than running together into one paragraph of ids. */
.biztech-health-rows > li[b-s1klg2vg3v] {
    padding-block: 4px;
}

/* `outline-subtle` rather than a `divider` token, which this platform does not have: the palette declares
   outline and outline-subtle, and TokenSufficiencyTests is what said so - a var() naming a token nothing
   declares paints nothing, with no error and no warning. */
.biztech-health-rows > li + li[b-s1klg2vg3v] {
    border-block-start: 1px solid var(--biztech-palette-outline-subtle);
}
/* /Components/Pages/Profile.razor.rz.scp.css */
/*
    W0-13 · F3.17. The avatar block at the top of a person's own profile.

    🔴 SCOPED RATHER THAN app.css, AND THAT IS R14.1's LAYER 4. Every element these rules reach is
    rendered by THIS component - the <img>, the placeholder and the wrapper around the seam's file
    field - so a scoped sheet reaches them. The seam components inside carry their own.

    Every length is a multiple of the 4px base unit (R5.4), and 999px is the registry's pill radius
    (R5.5). No hex anywhere: the two colours come from the palette tokens, so the block follows a
    tenant theme and both modes without a second declaration (R3.1, R7.3 - there is no
    prefers-color-scheme here and there must not be, I16).
*/

.biztech-profile-avatar[b-7okhcg5pl0] {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
}

/*
    A circle, and object-fit: cover so a rectangular photograph is cropped rather than squashed. A
    person uploads whatever their phone produced; distorting their face is the failure this one
    declaration prevents.
*/
.biztech-profile-avatar-image[b-7okhcg5pl0],
.biztech-profile-avatar-empty[b-7okhcg5pl0] {
    inline-size: 4rem;
    block-size: 4rem;
    border-radius: 999px;
    flex: 0 0 auto;
}

.biztech-profile-avatar-image[b-7okhcg5pl0] {
    object-fit: cover;

    /* An outline rather than nothing, so a white-cornered image still reads as a bounded avatar
       against the surface behind it. */
    border: 1px solid var(--biztech-palette-outline-subtle);
}

.biztech-profile-avatar-empty[b-7okhcg5pl0] {
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: var(--biztech-palette-surface);
    border: 1px solid var(--biztech-palette-outline);
    color: var(--biztech-palette-text-secondary);
}

/*
    The controls sit beside the picture and stack under it on a narrow screen, which the wrapper's
    flex-wrap already does - this only stops the two controls themselves spreading apart when they do.
*/
.biztech-profile-avatar-actions[b-7okhcg5pl0] {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}
/* /Components/Pages/ResourceLibrary.razor.rz.scp.css */
/* W2-06 · F4.1. The console's two-column body: the folder tree beside the listing.

   🔴 ONE COLUMN BY DEFAULT, TWO ABOVE md - R13.2's "structure responsively", taken in CSS because a grid
   is what the browser can reach and MudGrid is not on this island. Below it the tree stacks ABOVE the
   listing, which is the reading order a person on a narrow screen wants: choose a folder, then read it.

   🔴 md AND NOT sm, AND THE NUMBER WAS MEASURED RATHER THAN CHOSEN. At sm the second column appears at
   600px, which leaves the listing about 500px - and the library's table needs about 650 for its five
   columns, so it overflowed its own track. Measured 2026-09-03 at 768px:
   ResourceLibraryViewportTests.Nothing_in_the_library_is_laid_out_past_the_landmark reported
   `TABLE.biztech-table right=892 limit=768`, in both modes.

   🔴 AND THE FIX IS NOT AN overflow-x WRAPPER, WHICH IS THE OBVIOUS ONE AND THE WRONG ONE HERE. One
   overflow-x on a wrapper excludes its whole SUBTREE from that walk - PortalLandmarkOverflowTests' header
   says so, and this suite asserts an Eligible count precisely to catch it - so hiding the table inside a
   scroller would have turned a real overflow into a green test measuring almost nothing. The tree column
   appears when there is room for it instead.

   Lengths are multiples of the 4px base unit (R5.1, R5.4) - 15rem is 60 of them - so
   PortalStyleSheetTests' length sweep passes with NO layout-constant registry row. R5.4's own note is
   explicit that adding one is not the cheap way out of that failure, and it would also mean editing §5 of
   the guidelines, which is outside this card's projects.

   R13.1 · the breakpoint is MudBlazor's md, so this page never disagrees with a grid elsewhere.
   I16 · no prefers-color-scheme anywhere; the mode is a token.
   R3 · colours come only from --biztech-*, which PortalStyleSheetTests asserts over every scoped sheet. */

.biztech-library[b-8bmuk20gha] {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 24px;
}

@media (min-width: 960px) {
    .biztech-library[b-8bmuk20gha] {
        /* minmax(0, 1fr) rather than 1fr on the second track: a grid item's default min-width is auto, so
           a wide table would push the column past the viewport instead of scrolling inside it. That is
           R13.8's overflow, and it is what the viewport suite walks every element's right edge for. */
        grid-template-columns: 15rem minmax(0, 1fr);
        align-items: start;
    }
}
/* /Components/Pages/StaticPage.razor.rz.scp.css */
/* W1a-10 · F2.20. The static page's body.

   TOKENS ONLY - R3.1's sweep in PortalStyleSheetTests reads every *.razor.css in this project and refuses
   a hex or rgb() literal, with two allow-listed circuit-chrome blocks in app.css that this file is not
   one of.

   🔴 EVERY RULE THAT REACHES INSIDE THE ARTICLE USES ::deep, AND THIS WAS FOUND ON SCREEN RATHER THAN IN A
   TEST - the third time this project has learned it, after MainNavMenu.razor.css and
   CasePage.razor.css, both of which say the same thing in their own headers.

   Blazor's scoped-CSS attribute is stamped on elements the RAZOR FILE declares. The body's elements are not
   declared here: they arrive as a MarkupString from a tenant's own document, so they carry no attribute at
   all. Without ::deep, `.biztech-static-page h2` compiles to `.biztech-static-page h2[b-xxxxxxxxxx]` and
   matches NOTHING, silently - the page renders with browser default spacing and every rule below is dead
   text. That is exactly what the first browser pass on this card showed: headings flush against the h1 and
   paragraphs with no rhythm, while the bUnit suite was green, because a render test asserts the DOM and
   never resolves a stylesheet.

   `.biztech-static-page ::deep h2` compiles to `.biztech-static-page[b-xxxxxxxxxx] h2` - the article keeps
   its scope, and the descendant match does not need one. The article itself is declared in the razor file,
   so the first rule below needs no ::deep. */

/* The article element's own box. Declared here, so scoped, so no ::deep. */
.biztech-static-page[b-h9eqglag6k] {
    /* R5.2's readable measure. 68ch rather than a pixel width because the constraint is CHARACTERS per line
       - the thing that actually governs reading - and it stays correct when the type scale moves. */
    max-inline-size: 68ch;
}

/* 🔴 VERTICAL RHYTHM, BY ELEMENT RATHER THAN BY `> * + *`. A universal-sibling rule would have to reach
   through ::deep too, and it would then apply at every depth rather than to the article's own children,
   because ::deep drops the scope for everything to its right. Naming the elements a prose document actually
   contains is both narrower and legible - and it bounds this file, which is the point: a rule for every
   element a tenant might type is a stylesheet nobody can keep true. What is not listed keeps the document
   reset in app.css, and F2.18's per-section branding is W1b's. */
.biztech-static-page[b-h9eqglag6k]  p,
.biztech-static-page[b-h9eqglag6k]  ul,
.biztech-static-page[b-h9eqglag6k]  ol,
.biztech-static-page[b-h9eqglag6k]  blockquote {
    /* 12px, three steps of the 4px base unit, which is what R5.4's sweep accepts. */
    margin-block-end: 12px;
}

/* Headings take MORE space above than below, so a section reads as belonging to the text under it rather
   than floating between two. 20px and 4px, both whole multiples of the base unit. */
.biztech-static-page[b-h9eqglag6k]  h2,
.biztech-static-page[b-h9eqglag6k]  h3 {
    margin-block-start: 20px;
    margin-block-end: 4px;
}

/* An author's headings take the type scale rather than the browser's defaults, so a tenant's privacy notice
   is set in the same faces as the page it renders inside. Two levels only: a static page is prose, and a
   document that needs h5 is a document that needs restructuring rather than a stylesheet rule. */
.biztech-static-page[b-h9eqglag6k]  h2 {
    font-size: var(--biztech-font-h6-size);
    font-weight: var(--biztech-font-h6-weight);
    line-height: var(--biztech-font-h6-line-height);
}

.biztech-static-page[b-h9eqglag6k]  h3 {
    font-size: var(--biztech-font-subtitle1-size);
    font-weight: var(--biztech-font-subtitle1-weight);
    line-height: var(--biztech-font-subtitle1-line-height);
}

/* Body text at the scale's body1, so a tenant's prose is not left at whatever the browser defaults to. */
.biztech-static-page[b-h9eqglag6k]  p,
.biztech-static-page[b-h9eqglag6k]  li {
    font-size: var(--biztech-font-body1-size);
    line-height: var(--biztech-font-body1-line-height);
}

/* A list needs its markers back: the document reset in app.css strips them from every ul, which is right
   for navigation and wrong for a tenant's prose. Scoped to this article, so only authored content gets them.
   16px of inline padding is four steps of the base unit and is what stops a marker sitting outside the
   measure. */
.biztech-static-page[b-h9eqglag6k]  ul,
.biztech-static-page[b-h9eqglag6k]  ol {
    padding-inline-start: 16px;
}

.biztech-static-page[b-h9eqglag6k]  ul {
    list-style: disc;
}

.biztech-static-page[b-h9eqglag6k]  ol {
    list-style: decimal;
}

/* An author's links take Primary, which satisfies contrast WITHOUT introducing a pair anybody has to
   measure: PaletteContrast.RequiredPairs already tests Primary against Surface and against Background at
   4.5:1 in both modes, and BizTechThemeOptions.Validate refuses to boot a palette that fails either. The
   reasoning is TenantChrome.razor.css's, and copying the decision rather than the value is the point.

   R3.11 · nothing here declares anything about focus, so app.css's one :focus-visible ring reaches these
   anchors with nothing to beat. */
.biztech-static-page[b-h9eqglag6k]  a {
    color: var(--biztech-palette-primary);
}
/* /Components/Portals/CarouselSection.razor.rz.scp.css */
/*
    W1b-01 · F2.9 · one cell of the gallery.

    🔴 THERE IS NO MOTION IN THIS FILE AND THERE MUST NOT BE. The legacy page the shape was read from -
    Views/Home/GenericDashboardcarousel.cshtml:225-243 - is a static grid of cards with no carousel
    script anywhere in it, so there is nothing here to advance, nothing to pause and nothing for
    prefers-reduced-motion to reduce. The card's ## Do not says it outright: "do not build a rotating
    carousel - no previous/next, no auto-advance, no slide indicator". A suite sweeps this file for
    `animation`, for a transition on a position property, and for `overflow-x`.

    🔴 AND NO overflow-x, WHICH IS THE SAME CLAUSE READ THE OTHER WAY. "Do not use overflow-x: hidden to
    make a carousel measurement pass." A gallery that scrolled sideways would need it; this one wraps,
    because UiGrid is a real grid and an item that does not fit goes to the next row on its own. The
    trap PortalLandmarkOverflowTests' header carries is the reason the sweep is broad rather than
    hidden-only: overflow-x computes to `auto` when overflow-y is not `visible`, so declaring either
    here would take every element below this one out of that suite's reach.

    THE COLUMN COUNT IS NOT HERE EITHER. 1-up / 2-up / 4-up is UiGridItem's Xs/Sm/Md spans over W0-52's
    twelve columns, declared in the markup where a reader can see the three numbers together. A media
    query here would be a second place the breakpoints live, and R13.1 fixes them at MudBlazor's sm 600
    and md 960 precisely so a page never disagrees with its grid.

    WHAT IS LEFT IS THE CELL BOX, and one rule is enough for it: a cell's content is a section's own
    render - a thumbnail above a title - and those two want to sit at the TOP of a cell rather than
    spread down it, or a card with a short caption pushes its picture away from a card with a long one.
    align-content rather than a height, because the cards in a row are already the same height: they are
    grid items in one row of one grid.

    LOGICAL PROPERTIES, on PortalSectionThumbnail's and PortalTileCard's line: inline-size rather than
    width, block-size rather than height - which is what lets PortalHomeRenderTests ban the string
    `height` outright as R13.3's container-height check.

    NO COLOUR, which PortalStyleSheetTests' hex sweep requires of every sheet under Components/. There
    is nothing to declare: the card's surface is UiPaper's and its radius is the palette's own.

    THE GAP IS 8px WRITTEN OUT, NOT A TOKEN, because there is no spacing token to read: W0-52 emits
    --biztech-radius-* and the palette, and nothing named --biztech-space-*. The first draft of this file
    invented one, and a var() that resolves to nothing falls back to no gap at all - silently, which is
    the failure mode PortalStyleSheetTests' radius rule records for exactly this mistake. PortalGrid's
    own sheet writes 16px for the same reason. 8px is the 4px scale's second step, so
    No_stylesheet_writes_a_length_that_is_neither_a_scale_step_nor_a_named_constant accepts it.

    NO position: sticky.
*/

.biztech-portal-gallery-cell[b-vm1oo3k2kz] {
    align-content: start;
    block-size: 100%;
    display: grid;
    gap: 8px;
    inline-size: 100%;
}
/* /Components/Portals/EditablePdfSection.razor.rz.scp.css */
/*
    W1b-01 · F2.6 · the pager beneath a fillable PDF page.

    ONE RULE, BECAUSE THERE IS ONE ELEMENT THIS FILE OWNS. The overlay's own geometry is
    PdfFormOverlay.razor.css's - it positions controls over a rendered page and scrolls ITSELF rather than
    the page body, so a 1240px page at 150dpi does not give a narrow viewport a horizontal scrollbar. That
    file is W3-18's and is mounted unchanged; nothing here reaches into it.

    THE PAGER IS A ROW THAT WRAPS, which is what makes it correct at 375px without a media query: below
    the width where three items fit, the announced indicator drops to its own line rather than squeezing
    the two controls under their 44px touch target (R13.5). space-between rather than a gap on each side,
    so the two controls sit at the edges a thumb reaches.

    align-items: center, because the indicator is text and the controls are buttons - their line boxes
    differ, and without this the words sit high against the buttons' padding.

    NO COLOUR, which PortalStyleSheetTests' hex sweep requires of every sheet under Components/. NO
    overflow and NO position: sticky. Every length is a multiple of the 4px base unit - 8px here, which is
    the same step CarouselSection's cell gap uses and for the same reason: there is no --biztech-space-*
    token to read, and a var() that resolves to nothing falls back to nothing at all.
*/

.biztech-portal-pdf-pager[b-49ga13j53e] {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    justify-content: space-between;
}
/* /Components/Portals/EmbeddedPageSection.razor.rz.scp.css */
/*
    W1b-01 · F2.7 · the frame an external page is drawn in.

    🔴 NO FIXED SIZE, WHICH IS THE WHOLE REASON THIS SHEET EXISTS. Legacy draws this page as an iframe at
    a hard 959 x 743px inside a body pinned to the same two figures - Views/Home/GenericIframeUI.cshtml:21
    and :31 - and that is the non-responsive shape the card replaces. Its words: "nothing here may assume
    a fixed height or a grid track", because the same renderer has to be correct on the tile page at
    71.25rem AND inside W1b-06's preview at whatever width that gives it.

    So: full inline size, and an ASPECT RATIO rather than a height. 16/9 matches
    PortalSectionThumbnail's, and the reason is the same kind of reason - it is the ratio the content
    tends to want - but it is doing different work here. A frame with no height at all collapses to
    150px, which is the HTML default for a replaced element and is not a design decision anybody made;
    a frame with a fixed height clips a partner's page on a phone. A ratio scales.

    A MINIMUM, though, because a ratio alone is not enough at 375px: 16/9 of a 343px content width is
    193px, which is not enough of somebody's benefits page to be worth framing. 320px is the floor -
    a multiple of the 4px base unit, and the width the narrowest supported viewport gives the content,
    so a frame is never shorter than the page is wide.

    NO BORDER, because a frame is not a control. The border-radius is the palette's own so the framed
    document does not overhang UiPaper's corners.

    NO overflow, NO position: sticky, and NO COLOUR - PortalStyleSheetTests' hex sweep requires the last
    of every sheet under Components/, and there is nothing to declare: what paints inside this box is
    somebody else's document.

    LOGICAL PROPERTIES: inline-size rather than width, on PortalSectionThumbnail's and PortalTileCard's
    line - which is what lets PortalHomeRenderTests ban the string `height` outright as R13.3's
    container-height check.
*/

.biztech-portal-embed-frame[b-dfhk711y1l] {
    aspect-ratio: 16 / 9;
    border: 0;
    border-radius: var(--biztech-radius-default);
    display: block;
    inline-size: 100%;
    min-block-size: 320px;
}
/* /Components/Portals/PortalAppMenuTile.razor.rz.scp.css */
/* W1b-02 · F2.14 · one tile's section launcher, as a wrapping grid.

   🔴 auto-fill RATHER THAN A BREAKPOINT, WHICH IS WHY THIS FILE DECLARES NONE. Legacy's app menu was
   four-up at lg, three-up at md and two-up at sm - three hand-written breakpoints for one grid.
   `repeat(auto-fill, minmax(8rem, 1fr))` says the same thing once and keeps saying it at widths nobody
   tested: cells are at least 8rem and the row holds as many as fit. Below `sm` a tile is one grid column
   wide, so the launcher collapses to a single cell per row on its own - R13.3's "every desktop-only rule
   must be reversed there" satisfied by never declaring one, which is PortalTileCard.razor.css's argument
   for its placement rules.

   8rem is 128px, which is a multiple of the 4px base unit, so R5.4 is satisfied without a layout
   constant. That rule is enforced over EVERY *.razor.css under Components/ by
   PortalStyleSheetTests.No_stylesheet_writes_a_length_that_is_neither_a_scale_step_nor_a_named_constant,
   and adding a LayoutConstants row would have been the cheap way out that test's own message warns
   against - T29 met it three times and rounded to the scale each time.

   The floor exists because a launcher cell holds a section TITLE, which is tenant-authored text: cells
   narrower than this would break one or two words per line, and `minmax(0, 1fr)` alone lets a grid item
   be narrower than its content.

   🔴 THE GAP IS A UTILITY. R5.1 - `.biztech-gap-2` is 8px in app.css and is on the element. `gap` works
   the same on a grid container as on a flex one, so the launcher and the stack read the same rhythm from
   the same class rather than from two declarations.

   NO WIDTH, NO HEIGHT, NO OVERFLOW, NO POSITION, NO COLOUR. The launcher sits inside a grid cell, and
   the revealed panel is deliberately OUTSIDE this container in the markup so that it takes the tile's
   width rather than a column's - that is a DOM decision, not a CSS one, so there is no rule here for it.

   AND NO 44px FLOOR - PortalTileCard.razor.css reaches these buttons through ::deep. */

.biztech-portal-launcher[b-oeilrh3bu8] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
}
/* /Components/Portals/PortalButtonMenuTile.razor.rz.scp.css */
/* W1b-02 · F2.13 · one tile's section menu, as a column.

   🔴 THE CONTAINER DOES ALL OF IT, AND NOTHING HERE REACHES INTO UiButton. `align-items: stretch` on a
   column flex is what makes each row full width, so the rows are wide without this file naming
   `.biztech-button` at all. A `::deep .biztech-button { inline-size: 100% }` would collide with that
   component's own scoped sheet at equal specificity - (0,2,0) against (0,2,0) - and which one won would
   depend on bundle order, which is not a thing to depend on. R8.7 also says a Ui component's Class slot
   is spacing utilities only, so the width could not have been passed in as a class either.

   🔴 THE GAP IS A UTILITY, NOT A DECLARATION HERE. R5.1: "Do not write margin or padding in CSS when a
   utility exists." `.biztech-gap-2` is 8px in app.css and is on the element in the markup. There is no
   --biztech-space-* token to read - the theme emits palette and font variables only - and a literal
   `gap: 8px` here would be a second place to change the rhythm.

   NO WIDTH, NO HEIGHT, NO OVERFLOW, NO POSITION, NO COLOUR, NO BREAKPOINT. The menu sits inside a grid
   cell, so a height or a scroller would fight the tile containing it, and `position: sticky` in a grid
   item pins to the item rather than the viewport. A column is a column at 375px and at 1440px, so
   R13.3's "every desktop-only rule must be reversed below sm" is satisfied by never declaring one -
   PortalTileCard.razor.css's own argument for its placement rules.

   AND NO 44px FLOOR. PortalTileCard.razor.css declares it under (pointer: coarse) with ::deep, which
   reaches these buttons; a second declaration would be a second answer to R5.8.

   R5.4 is enforced over every *.razor.css under Components/ by PortalStyleSheetTests, so any length here
   must be a 4px multiple or a named layout constant. This file declares none at all. */

.biztech-portal-menu[b-czndl8qxw2] {
    display: flex;
    flex-direction: column;
    align-items: stretch;
}
/* /Components/Portals/PortalComposerCopyPanel.razor.rz.scp.css */
/*
    W8-03 · F10.17 — the copy panel's two choosers.

    🔴 THIS FILE DECLARES ONE THING, AND WHAT IT DOES NOT DECLARE IS THE INTERESTING HALF. The fieldset's
    border and the legend's padding are app.css's .biztech-choice-group / -legend, written for UiRadioGroup
    - the same element doing the same job one control over. Restating them here is what
    UtilitySpecificityTests refuses: Blazor rewrites a scoped selector as `.x[b-xxxxxxxxxx]`, (0,2,0)
    against a utility class's (0,1,0), so a local `margin: 0` would silently beat every biztech-mb-* a
    caller passed the component while the class stayed visible in the markup.

    Everything else is the seam's too: UiPaper, UiSelect, UiCheckbox, UiButton and the biztech-* spacing
    utilities.

    A literal gap rather than var(--biztech-space-3): there is no spacing token. TokenSufficiencyTests
    catches a var() that resolves to nothing, which paints nothing with no error and no warning, and it
    caught this one.

    R13.1's breakpoints, mobile-first: one column below sm, because two selects side by side at 375px
    leave neither wide enough to read a portal's name in. PortalCompare.razor.css makes the same choice
    for the same pair of controls.
*/

/* 🔴 W0-75 · A GRID, FOR THE REASON PortalCompare.razor.css SPELLS OUT AT LENGTH: the equal-halves
   rule was `.biztech-copy-choosers > *`, whose last compound is what carries the scope attribute, and
   the children are UiSelect's roots. It matched nothing, so the two choosers were content-sized and did
   jump as a portal's name changed length. Equal grid tracks need no child rule, so there is nothing left
   to reach an element this component does not own. */
.biztech-copy-choosers[b-8vzxbrlhu1] {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1rem;
}

@media (min-width: 600px) {
    .biztech-copy-choosers[b-8vzxbrlhu1] {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

/* The checkboxes stack; the fieldset's own reset is the seam's, above. */
.biztech-copy-sections[b-8vzxbrlhu1] {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}
/* /Components/Portals/PortalComposerLauncherPanel.razor.rz.scp.css */
/* 🔴 W8-22 · D + E — I18's GAP, AND NOTHING ELSE.

   The panel's children are spaced by ONE container gap rather than by a margin each, which is invariant
   I18: "the two add up". W0-75 measured the composer's own tile panel at panel-child-gaps=0,0,0,12,12 -
   four controls touching inside a padded panel - because biztech-pa-4 spaces a panel from its border and
   nothing spaced the contents from each other.

   🔴 AND EVERY SELECTOR HERE ENDS ON A CLASS THIS COMPONENT'S OWN MARKUP CARRIES, which is I19. Blazor
   stamps the scope attribute on the LAST compound of a selector, so three shapes are silently inert: a
   rule for a class that appears only in a CHILD component, `.parent > *` where the child is another
   component's root, and `.parent element` where that element belongs to a child. ScopedStyleReachTests
   sweeps for both halves and W0-75 had NINE such rules across six files.

   That is why the gap is declared on .biztech-launcher-fields - a div THIS file renders - and why
   nothing here tries to reach a UiTextField or a UiButton, both of which are child components whose
   roots this scope cannot touch.

   🔴 A LITERAL GAP RATHER THAN var(--biztech-space-3): THERE IS NO SPACING TOKEN, and
   PortalComposerCopyPanel.razor.css beside this file records that it learned so the hard way -
   "TokenSufficiencyTests catches a var() that resolves to nothing, which paints nothing with no error
   and no warning, and it caught this one." Same figure as that panel, for the same kind of stack. */
.biztech-launcher[b-sen6ydfupb] {
  display: flex;
  flex-direction: column;

  gap: 1.25rem;
}

/* The heading and the sentence under it are ONE fact, so they sit at the tightest step on the scale -
   0.25rem, R5.1's base unit. Any further apart and the caption reads as the first line of the panel's
   contents rather than as part of its title. */
.biztech-launcher-heading[b-sen6ydfupb] {
  display: flex;
  flex-direction: column;

  gap: 0.25rem;
}

.biztech-launcher-fields[b-sen6ydfupb] {
  display: flex;
  flex-direction: column;

  gap: 1.25rem;
}

/* A labelled control, its caption and its button, as one block. Tighter than the gap BETWEEN blocks
   above, which is what makes the two readable as a hierarchy rather than as five evenly spaced things.
   */
.biztech-launcher-group[b-sen6ydfupb] {
  display: flex;
  flex-direction: column;

  gap: 0.5rem;
}

/* 🔴 A FIELD AND ITS BUTTON ON ONE LINE, ALIGNED ON THE CONTROL RATHER THAN THE LABEL. A field in this
   application is a label ABOVE a control, so `center` puts a button halfway up the label and `start`
   puts it level with the label's text - the same misalignment I20 records for a checkbox sharing a row
   with a field. `flex-end` is what makes the button's own box share an edge with the input's, whatever
   the label did. */
.biztech-launcher-row[b-sen6ydfupb] {
  display: flex;
  align-items: flex-end;

  gap: 0.75rem;
}

/* 🔴 ::deep, BECAUSE THE FIELD IS UiTextField'S OWN ROOT. This is I19's first shape exactly - a rule
   for a class that appears only in a CHILD component - so without ::deep it would be stamped with this
   panel's scope attribute, match nothing, and leave the field at its content width with the button
   floating beside it. min-inline-size: 0 is R13.8's grid-and-flex trap: a flex item's default minimum
   is its content's, so a long placeholder would push the button off the panel instead of shortening
   the field. */
.biztech-launcher-row[b-sen6ydfupb]  .biztech-field {
  flex: 1 1 auto;
  min-inline-size: 0;
}
/* /Components/Portals/PortalComposerPreviewFrame.razor.rz.scp.css */
/* 🔴 W1b-06 · F2.24 · the preview frame, and the two rules it genuinely needs.

   NO overflow ANYWHERE IN THIS FILE, which is the card's ## Do not in as many words: "Do not use
   overflow-x: hidden to make a preview measurement pass, and do not weaken an existing viewport assertion
   to accommodate the composer's chrome." R13.8 states the general form - "overflow-x: hidden is not a
   responsive strategy; it hides the symptom and the diagnostic together" - and here it would hide the one
   defect the preview exists to reveal.

   NO WIDTH ON THE FRAME. The chosen width is an attribute on the element, because it is content the
   operator selected rather than a design decision, and a class per width would spell the same three
   numbers a second time in a file that cannot see which is chosen. */

/* The controls sit above the frame and wrap, because three groups plus a select do not fit at 375px and
   this page is measured there like every other. `end` rather than `center`: the groups have different
   heights - the select carries a label and a control where a radio group carries a legend and two rows -
   so aligning their tops leaves the shorter one floating. */
.biztech-composer-preview-controls[b-5nr24cpp45] {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 24px;
}

/* 🔴 A BORDER AND A HEIGHT, AND NOTHING ELSE.

   The border is not decoration: without it a 375px frame on a 1440px screen reads as a broken layout
   rather than as a phone-width preview, because there is nothing to say where the document ends. The
   token is --biztech-palette-outline, which is UiPaper's - R3.6 puts the 3:1 on the border and R6.6 says
   a border does on dark what a shadow does on light, so the one role held to 3:1 against both grounds is
   the right one for an edge that has to be visible in either mode.

   The height is fixed rather than fitted to the content, and it has to be: an iframe cannot size itself to
   its document without script measuring across the boundary, and this document is same-origin only by
   accident of deployment - the manifest endpoint's own header records that assumption is not one to build
   on. 70vh is enough to show a launcher's first two rows at every width, and the frame scrolls internally
   for the rest, which is what a visitor's own window does.

   MAX-INLINE-SIZE 100%, so a 1440px preview on a narrower screen shrinks rather than pushing the page
   sideways. That is the one thing standing between this component and a horizontal scrollbar on the
   COMPOSER at 375px, and PortalComposerViewportTests measures it. */
.biztech-composer-preview-frame[b-5nr24cpp45] {
    display: block;
    max-inline-size: 100%;
    block-size: 70vh;
    border: 1px solid var(--biztech-palette-outline);
    border-radius: var(--biztech-radius-default);
    background-color: var(--biztech-palette-surface);
}

/* The wrapper. It carries no layout of its own beyond stacking its two children, and it exists so the
   controls and the frame move together when the pane becomes sticky above `md` - without it the sticky
   pane would have two children to position rather than one. min-inline-size: 0 for the grid reason the
   page's own stylesheet states: a track's default minimum is its CONTENT's, and the frame's content is
   1440px wide when that width is chosen. */
.biztech-composer-preview[b-5nr24cpp45] {
    display: flex;
    flex-direction: column;
    min-inline-size: 0;
}
/* /Components/Portals/PortalComposerSectionPanel.razor.rz.scp.css */
/* 🔴 W0-75 · THE SAME INERT-RULE DEFECT AS PortalComposerTilePanel.razor.css, and the same fix: these
   two classes are named in THIS component's markup, so they are declared here rather than in the page's
   stylesheet, where Blazor's scope attribute never reached them.

   Both rules are duplicated between this file and the tile panel's, and that is what CSS isolation
   costs: a scoped stylesheet cannot be shared, and the alternative — one app.css utility for a layout
   two components use — is the "utility library" R14.1 draws the line against. Two small files that say
   what they are for beat one class that could be anything. */

/* A section's row: the title takes the space and the controls sit at its end. */
.biztech-composer-section[b-2f3uir52aj] {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px;
    padding-block: 8px;
}

/* 🔴 AND THE SENTENCE ABOVE WAS A PROMISE NOTHING KEPT. "The title takes the space" needs a flex child
   that GROWS, and neither of this row's two fields declared one - so each took its content width (an
   `<input>`'s default is about 185px whatever the row is) and a 530px row ran out at 383px with 147px of
   nothing after the group picker. Measured over this stylesheet in Chromium, at every width from 1440
   down.

   ::deep, DELIBERATELY AND WITH THE REASON: `.biztech-field` is UiTextField's and UiSelect's own root,
   so this component's scope attribute never lands on it and a plain descendant rule would match nothing
   - which is the exact defect PortalComposerTilePanel.razor.css's geometry rule records, one file over,
   in the `> *` spelling that hid it for longer. There is no `.biztech-field` nested deeper inside this
   row for the unscoped half of the selector to reach by accident.

   12rem is the basis both share, so the two divide the slack evenly and neither collapses under the
   other; min-inline-size: 0 because a select's longest option is its content minimum otherwise, and that
   is the same trap R13.8 names for a grid track. */
.biztech-composer-section[b-2f3uir52aj]  .biztech-field {
    flex: 1 1 12rem;
    min-inline-size: 0;
}

.biztech-composer-actions[b-2f3uir52aj] {
    display: flex;
    align-items: center;
    gap: 4px;
}
/* /Components/Portals/PortalComposerTilePanel.razor.rz.scp.css */
/* 🔴 W0-75 · THIS FILE EXISTS BECAUSE EVERY RULE IN IT WAS INERT, AND A BROWSER IS WHAT SHOWED IT.

   All four rules below were declared in PortalComposer.razor.css — the PAGE's stylesheet — while the
   elements they name are in THIS component's markup. Blazor's CSS isolation stamps the page's scope
   attribute on the page's own elements only, so those rules compiled to
   `.biztech-composer-tile-head[b-j6ks1ur5km]` and matched nothing at all: no error, no warning, no
   styling. Read on the composer that was: the tile label's heading and its two ordering arrows stacked
   instead of sharing a line, and the four geometry fields each took a full row.

   MediaRoom.razor.css records the same trap from the other side, and PortalComposerPreviewFrame got it
   right by keeping its rules next to its own markup. That is the arrangement here: a component's layout
   lives with the component. The page keeps only what its OWN markup names — the tile grid — plus one
   ::deep rule it declares as ::deep deliberately.

   NO overflow anywhere, which is PortalComposer.razor.css's own ## Do not: it would hide the one defect
   the composer exists to reveal. */

/* The heading and its two ordering buttons share a line; everything else stacks. */
.biztech-composer-tile-head[b-zp5rzq8tjq] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

/* A tile's own controls, and a section's: 4px, because two adjacent icon buttons read as one control at
   any more and as one BUTTON at any less. */
.biztech-composer-actions[b-zp5rzq8tjq] {
    display: flex;
    align-items: center;
    gap: 4px;
}

/* 🔴 FOUR SHORT NUMBER FIELDS ABREAST, AND A GRID IS WHAT MAKES THEM STAY ABREAST. THIS RULE HAS NOW
   FAILED TWICE, EACH TIME FOR A DIFFERENT REASON, AND THE SECOND IS THE INTERESTING ONE.

   It began in PortalComposer.razor.css, where the class the page declared it for lives in THIS
   component's markup, so the rule compiled against the page's scope and matched nothing. Moving it here
   fixed that half — and left `.biztech-composer-geometry > *` behind, whose LAST compound selector is
   what Blazor stamps the scope attribute on. The children are UiNumberField's roots, rendered by a child
   component, so `> *[b-xxxxxxxxxx]` matched nothing either: `flex-wrap: wrap` with no basis, four fields
   taking their content width, and "Columns wide" dropped onto a line of its own from 1024px to about
   1200px — where a two-abreast tile panel is only ~360px wide. Measured, not guessed: a Chromium probe
   over this stylesheet reported `geometry-lines=2` at both widths.

   A GRID NEEDS NO CHILD RULE AT ALL, which is why it is the fix rather than `::deep`. The track sets the
   width, so nothing has to reach an element this component does not own, and there is no basis for a
   viewport to defeat: four fields, four tracks, one row at every width above sm. `ScopedStyleReachTests`
   now refuses a universal last compound outright, so the shape cannot come back.

   minmax(0, 1fr) rather than 1fr — R13.8's grid trap: a track's default minimum is its content's
   minimum, and a spinner plus five digits would push the track past the panel.

   align-items: end, and it is load-bearing at ~360px: "Columns wide" and "Rows tall" wrap to two lines
   there while "Row" and "Column" do not, and a field is a label above a control. Aligned at the START,
   two of the four controls sit a line lower than the others — the same misalignment the create panel's
   checkbox had. Aligned at the END, every control shares an edge whatever its label did.

   TWO TRACKS BELOW sm (600px), R13.1's own boundary: at 375px four tracks are 69px each, which holds
   1-24 and its spinner but gives every label two or three lines. Two by two reads. */
.biztech-composer-geometry[b-zp5rzq8tjq] {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
    align-items: end;
}

@media (min-width: 600px) {
    .biztech-composer-geometry[b-zp5rzq8tjq] {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

/* The list of sections under a tile. */
.biztech-composer-sections[b-zp5rzq8tjq] {
    display: flex;
    flex-direction: column;
}

/* 🔴 THE PANEL'S OWN VERTICAL RHYTHM, AND ITS ABSENCE IS THE REST OF WHAT "THE PADDING IS BROKEN" WAS.
   biztech-pa-4 put space between the panel's border and its contents; NOTHING put space between the
   contents. The heading, the tile label, the destination select and the geometry row are four `div`s in
   normal flow with no margin between any of them, so they sat at 0px apart — a Chromium probe over this
   stylesheet reported `panel-child-gaps=0,0,0,12,12`, the two 12s being biztech-mt-3 utilities on the
   last two children.

   A CONTAINER GAP RATHER THAN A MARGIN PER CHILD, which is the same argument the create panel's grid
   settled: spacing between siblings belongs to the container, and a margin on each child is a value
   somebody adds to the gap by accident later. The two biztech-mt-3 utilities came off with it.

   16px, which is what R5 gives stacked form fields and what UiGrid's Spacing="2" already puts
   between its rows — the two panels an operator sees one after the other now agree. */
.biztech-composer-tile-body[b-zp5rzq8tjq] {
    display: flex;
    flex-direction: column;
    gap: 16px;
}
/* /Components/Portals/PortalGrid.razor.rz.scp.css */
/* W1a-08 · F2.21, F2.22 · the portal grid CONTAINER.

   The portal declares its own width - Portals.ColumnCount, 1-24 - so the column count is a class rather
   than a constant. PortalGrid.razor falls back to twelve when a portal declares no grid, which §5 calls
   an ordinary state and which is what every portal created before this card holds.

   🔴 THREE BANDS SINCE W1b-08, WHICH IS LEGACY'S 3 / 2 / 1 ON R13.1's BREAKPOINTS.

   This sheet went from ONE column below `sm` straight to the portal's declared count at 600px, so a
   12-column portal at 601px was twelve 48px tracks. Legacy's home screen is 3-up above `lg`, 2-up at
   `md` and 1 below (Views/Home/Index.cshtml:80-88), and the card ports that onto the two breakpoints
   this application already has - without changing them, which its ## Do not requires:

     below `sm`      one column. Nothing is declared: grid-template-columns: 1fr is the base rule.
     `sm` to `md`    TWO columns for EVERY portal, whatever it declares. Tiles flow in DOM order, which
                     PortalGrid.razor guarantees is RANK order, because no tile declares a position
                     below 960px either (PortalTileCard.razor.css).
     `md` and above  the declared count, or three when the portal declares none.

   So the columns-N rules moved to 960px and a single two-column rule took their place at 600px. R13.3's
   "every desktop-only rule must be reversed there" is satisfied the way it was - by never DECLARING one
   below the breakpoint rather than by declaring and undoing it, which is the only form a later edit
   cannot silently leave behind, and it needs no !important to win.

   600px is MudBlazor's `sm` and 960px its `md` (R13.1), "so a page never disagrees with its grid".
   Neither is ours to choose.

   🔴 THE TILE'S OWN RULES ARE IN PortalTileCard.razor.css, NOT HERE, AND BLAZOR CSS ISOLATION IS WHY.
   A component's .razor.css is rewritten to `selector[b-<that component's hash>]`, and only elements in
   THAT component's markup carry the attribute. The tile div is PortalTileCard's, so a
   `.biztech-portal-tile` rule in this file would compile to a selector nothing on the page matches - a
   stylesheet that is present and inert, which R14.1a records as the failure no test in this repository
   catches: "A class that is present and inert passes every check in this repository." ::deep would also
   work and is not used, because the rules belong with the element rather than with its parent.

   NO overflow OF ANY KIND. R13.8: "overflow-x: hidden is not a responsive strategy - it hides the symptom
   and the diagnostic together." W0-55 exists because of it.

   NO COLOUR and NO `height`. R14.1 layer 1 puts colour in --biztech-* tokens; the tiles are UiPaper and
   are themed already.
*/

.biztech-portal-grid[b-cu78rdxk10] {
    display: grid;

    /* One column below `sm`; the columns-N rules widen it above the breakpoint. */
    grid-template-columns: 1fr;

    /* 16px is UiGrid's Spacing="2" - R5.2's house value, and the gutter every other grid in the Portal
       renders. Taken from UiGrid.razor.css rather than chosen, so this grid cannot disagree with them. */
    gap: 16px;
    box-sizing: border-box;
}

/* 🔴 THE MIDDLE BAND, AND IT IS ONE RULE FOR EVERY PORTAL. It deliberately does NOT read the
   columns-N class: between `sm` and `md` a portal's declared width is not what it lays out at, because
   legacy's own 2-up band does not vary by portal either. A portal declaring ONE column is the only case
   where this widens rather than narrows, and two columns of one tile each is what legacy would draw for
   it too. */
@media (min-width: 600px) {
    .biztech-portal-grid[b-cu78rdxk10] {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (min-width: 960px) {
    .biztech-portal-columns-1[b-cu78rdxk10] { grid-template-columns: repeat(1, minmax(0, 1fr)); }
    .biztech-portal-columns-2[b-cu78rdxk10] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .biztech-portal-columns-3[b-cu78rdxk10] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .biztech-portal-columns-4[b-cu78rdxk10] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
    .biztech-portal-columns-5[b-cu78rdxk10] { grid-template-columns: repeat(5, minmax(0, 1fr)); }
    .biztech-portal-columns-6[b-cu78rdxk10] { grid-template-columns: repeat(6, minmax(0, 1fr)); }
    .biztech-portal-columns-7[b-cu78rdxk10] { grid-template-columns: repeat(7, minmax(0, 1fr)); }
    .biztech-portal-columns-8[b-cu78rdxk10] { grid-template-columns: repeat(8, minmax(0, 1fr)); }
    .biztech-portal-columns-9[b-cu78rdxk10] { grid-template-columns: repeat(9, minmax(0, 1fr)); }
    .biztech-portal-columns-10[b-cu78rdxk10] { grid-template-columns: repeat(10, minmax(0, 1fr)); }
    .biztech-portal-columns-11[b-cu78rdxk10] { grid-template-columns: repeat(11, minmax(0, 1fr)); }
    .biztech-portal-columns-12[b-cu78rdxk10] { grid-template-columns: repeat(12, minmax(0, 1fr)); }
    .biztech-portal-columns-13[b-cu78rdxk10] { grid-template-columns: repeat(13, minmax(0, 1fr)); }
    .biztech-portal-columns-14[b-cu78rdxk10] { grid-template-columns: repeat(14, minmax(0, 1fr)); }
    .biztech-portal-columns-15[b-cu78rdxk10] { grid-template-columns: repeat(15, minmax(0, 1fr)); }
    .biztech-portal-columns-16[b-cu78rdxk10] { grid-template-columns: repeat(16, minmax(0, 1fr)); }
    .biztech-portal-columns-17[b-cu78rdxk10] { grid-template-columns: repeat(17, minmax(0, 1fr)); }
    .biztech-portal-columns-18[b-cu78rdxk10] { grid-template-columns: repeat(18, minmax(0, 1fr)); }
    .biztech-portal-columns-19[b-cu78rdxk10] { grid-template-columns: repeat(19, minmax(0, 1fr)); }
    .biztech-portal-columns-20[b-cu78rdxk10] { grid-template-columns: repeat(20, minmax(0, 1fr)); }
    .biztech-portal-columns-21[b-cu78rdxk10] { grid-template-columns: repeat(21, minmax(0, 1fr)); }
    .biztech-portal-columns-22[b-cu78rdxk10] { grid-template-columns: repeat(22, minmax(0, 1fr)); }
    .biztech-portal-columns-23[b-cu78rdxk10] { grid-template-columns: repeat(23, minmax(0, 1fr)); }
    .biztech-portal-columns-24[b-cu78rdxk10] { grid-template-columns: repeat(24, minmax(0, 1fr)); }
}
/* /Components/Portals/PortalSectionThumbnail.razor.rz.scp.css */
/*
    W1b-03 · F2.17 · one section's picture.

    🔴 THERE IS ONE RULE HERE BECAUSE THERE IS ONE ELEMENT. Unlike EducationItemFrame.razor.css, which
    declares the same box twice so a picture and its designed absence cannot disagree about it, this
    component renders NOTHING when there is no picture - see the component's header for why a portal
    section is not a card in a grid and a reserved empty box would be a visible gap.

    R11.7's five, and the three that belong in a stylesheet:

      ASPECT RATIO. 16/9, matching the education hub's, because the ported thumbnails are the same kind
      of thing - 17 image values across 101 legacy rows, all uploads by an administrator rather than
      generated crops. Declared so the box does not resize under the reader when the data: URI arrives
      out of OnAfterRenderAsync, which is the same reason that file gives.

      OBJECT-FIT. cover, so an author's upload is cropped rather than distorted. The alternative,
      contain, letterboxes into whatever paints behind - which on a tile is UiPaper's surface, so a
      portrait would sit in two grey bars nobody chose.

      LOADING. Not here - `loading="lazy"` is an attribute and lives in the markup.

    LOGICAL PROPERTIES: inline-size rather than width, block-size rather than height. PortalTileCard's
    own sheet takes the same line and records that it is what lets PortalHomeRenderTests ban the string
    `height` outright as R13.3's container-height check.

    NO COLOUR. Nothing here declares one, which is what PortalStyleSheetTests' hex sweep and
    PortalHomeRenderTests' two theory lists require of any sheet under Components/ - both enumerate the
    directory off disk, so this file is swept with no edit anywhere. There is nothing to declare: F2.18's
    measured option set contains no colour, no spacing and no radius, so a tenant cannot recolour this box
    and the radius is the palette's own.

    Every length is a multiple of the 4px base unit or a token. NO overflow, NO position: sticky.
*/

.biztech-portal-thumb[b-ougb3bz81y] {
    aspect-ratio: 16 / 9;
    border-radius: var(--biztech-radius-default);
    display: block;
    inline-size: 100%;
    object-fit: cover;
}
/* /Components/Portals/PortalTileCard.razor.rz.scp.css */
/* W1a-08 · F2.21, F2.22 · one tile's box and its placement in the grid.

   🔴 ENUMERATED ONE-LINE RULES RATHER THAN AN INLINE `grid-column`, AND UiGridItem.razor.css's header
   carries the identical argument for the identical reason: "Thirty-six one-line rules rather than an
   inline `grid-column: span N`, because R14.2 forbids an inline style for layout and R5.4 forbids a
   magic length - and a span arriving from a FORM DEFINITION is exactly the case where a component cannot
   know the value at authoring time." A tile's row, column and spans arrive from a DATABASE ROW, which is
   the same case exactly.

   The range is 1-24 because that is what CK_Portals_Grid bounds a portal's grid to and what
   PortalTileRules.MaxGridCount names. Nothing here is speculative in the sense the utility layer's test
   polices: every value is one a row may legitimately hold.

   🔴 THESE RULES ARE HERE RATHER THAN IN PortalGrid.razor.css BECAUSE OF BLAZOR CSS ISOLATION. A
   component's .razor.css is rewritten to `selector[b-<that component's hash>]` and only elements in that
   component's own markup carry the attribute. The tile div is THIS file's, so its rules must be too -
   in the grid's sheet they would compile to a selector nothing matches, which is R14.1a's "present and
   inert" failure: "A class that is present and inert passes every check in this repository."

   🔴 EVERY PLACEMENT RULE IS INSIDE @media (min-width: 960px) SINCE W1b-08, AND THE NUMBER CHANGED FOR
   A REASON THAT IS LEGACY'S. It was 600px - `sm` - so a portal went from one column straight to its
   declared count, which meant a 12-column portal at 601px was twelve 48px tracks. Legacy's home screen
   steps 3 / 2 / 1 (Index.cshtml:80-88), so v2 does too, on R13.1's own breakpoints and without changing
   them:

     below `sm`      one column, rank order. Nothing is declared here at all.
     `sm` to `md`    TWO columns, rank order. PortalGrid.razor.css declares the tracks; no tile declares
                     a position, so DOM order - which is rank order - is what places them.
     `md` and above  the portal's declared ColumnCount, and the geometry below. Three when the portal
                     declares none, which is W1a-02's "this portal declares no grid" drawn as legacy
                     draws it.

   So declared geometry (Row, Column, spans) applies from `md` ONLY, and in the two-column band tiles
   flow exactly as they do below `sm`. R13.3's "every desktop-only rule must be reversed there" is
   satisfied the way it always was - by never DECLARING one below the breakpoint rather than by undoing
   it, which is the only form a later edit cannot silently leave behind.

   LOGICAL PROPERTIES THROUGHOUT - min-inline-size, min-block-size - never height or width. min-inline-size
   on the base is what stops an unbroken string widening its column instead of wrapping, which
   UiGridItem.razor.css calls "the single most common cause of a grid that scrolls sideways on a phone".
   And using them is what lets PortalHomeRenderTests ban the string `height` outright as R13.3's
   container-height check while the coarse-pointer block below still declares a 44px floor.

   NO overflow, NO position: sticky, NO colour. R13.8 and R14.1 layer 1.
*/

/* 🔴 A COLUMN, BECAUSE THE CELL DOES NOT ALWAYS HOLD ONLY THE ANCHOR.
   PortalTileRegion may wrap this cell's contents in EditableRegion, which renders its affordance as a
   SIBLING of the anchor - see PortalTileCard.razor's header for why it must be a sibling rather than a
   child. A block container plus an anchor claiming `block-size: 100%` gave that sibling nowhere to go:
   it was laid out BELOW the grid cell, into the row gap and across the next row's tile.
   MEASURED, not reasoned: 34px on /portal/main at 1440px against the real stack, and 35px in the
   fixture, on the one tile a reader may edit in place. Declaring the column here and letting the
   anchor GROW into whatever its siblings leave is what makes both children fit the track they share.
   NO overflow and NO clipping: hiding the affordance would satisfy a containment check while taking
   the control away, which is the opposite of the fix. */
.biztech-portal-tile[b-6iuciq708g] {
    box-sizing: border-box;
    min-inline-size: 0;
    display: flex;
    flex-direction: column;
}

@media (min-width: 960px) {
    /* The origin cell. CK_PortalTiles_Position bounds both to 1 or more. */
    .biztech-portal-row-1[b-6iuciq708g] { grid-row-start: 1; }
    .biztech-portal-row-2[b-6iuciq708g] { grid-row-start: 2; }
    .biztech-portal-row-3[b-6iuciq708g] { grid-row-start: 3; }
    .biztech-portal-row-4[b-6iuciq708g] { grid-row-start: 4; }
    .biztech-portal-row-5[b-6iuciq708g] { grid-row-start: 5; }
    .biztech-portal-row-6[b-6iuciq708g] { grid-row-start: 6; }
    .biztech-portal-row-7[b-6iuciq708g] { grid-row-start: 7; }
    .biztech-portal-row-8[b-6iuciq708g] { grid-row-start: 8; }
    .biztech-portal-row-9[b-6iuciq708g] { grid-row-start: 9; }
    .biztech-portal-row-10[b-6iuciq708g] { grid-row-start: 10; }
    .biztech-portal-row-11[b-6iuciq708g] { grid-row-start: 11; }
    .biztech-portal-row-12[b-6iuciq708g] { grid-row-start: 12; }
    .biztech-portal-row-13[b-6iuciq708g] { grid-row-start: 13; }
    .biztech-portal-row-14[b-6iuciq708g] { grid-row-start: 14; }
    .biztech-portal-row-15[b-6iuciq708g] { grid-row-start: 15; }
    .biztech-portal-row-16[b-6iuciq708g] { grid-row-start: 16; }
    .biztech-portal-row-17[b-6iuciq708g] { grid-row-start: 17; }
    .biztech-portal-row-18[b-6iuciq708g] { grid-row-start: 18; }
    .biztech-portal-row-19[b-6iuciq708g] { grid-row-start: 19; }
    .biztech-portal-row-20[b-6iuciq708g] { grid-row-start: 20; }
    .biztech-portal-row-21[b-6iuciq708g] { grid-row-start: 21; }
    .biztech-portal-row-22[b-6iuciq708g] { grid-row-start: 22; }
    .biztech-portal-row-23[b-6iuciq708g] { grid-row-start: 23; }
    .biztech-portal-row-24[b-6iuciq708g] { grid-row-start: 24; }

    .biztech-portal-col-1[b-6iuciq708g] { grid-column-start: 1; }
    .biztech-portal-col-2[b-6iuciq708g] { grid-column-start: 2; }
    .biztech-portal-col-3[b-6iuciq708g] { grid-column-start: 3; }
    .biztech-portal-col-4[b-6iuciq708g] { grid-column-start: 4; }
    .biztech-portal-col-5[b-6iuciq708g] { grid-column-start: 5; }
    .biztech-portal-col-6[b-6iuciq708g] { grid-column-start: 6; }
    .biztech-portal-col-7[b-6iuciq708g] { grid-column-start: 7; }
    .biztech-portal-col-8[b-6iuciq708g] { grid-column-start: 8; }
    .biztech-portal-col-9[b-6iuciq708g] { grid-column-start: 9; }
    .biztech-portal-col-10[b-6iuciq708g] { grid-column-start: 10; }
    .biztech-portal-col-11[b-6iuciq708g] { grid-column-start: 11; }
    .biztech-portal-col-12[b-6iuciq708g] { grid-column-start: 12; }
    .biztech-portal-col-13[b-6iuciq708g] { grid-column-start: 13; }
    .biztech-portal-col-14[b-6iuciq708g] { grid-column-start: 14; }
    .biztech-portal-col-15[b-6iuciq708g] { grid-column-start: 15; }
    .biztech-portal-col-16[b-6iuciq708g] { grid-column-start: 16; }
    .biztech-portal-col-17[b-6iuciq708g] { grid-column-start: 17; }
    .biztech-portal-col-18[b-6iuciq708g] { grid-column-start: 18; }
    .biztech-portal-col-19[b-6iuciq708g] { grid-column-start: 19; }
    .biztech-portal-col-20[b-6iuciq708g] { grid-column-start: 20; }
    .biztech-portal-col-21[b-6iuciq708g] { grid-column-start: 21; }
    .biztech-portal-col-22[b-6iuciq708g] { grid-column-start: 22; }
    .biztech-portal-col-23[b-6iuciq708g] { grid-column-start: 23; }
    .biztech-portal-col-24[b-6iuciq708g] { grid-column-start: 24; }

    /* The extent. CK_PortalTiles_Span bounds both to 1 or more, and
       PortalTileProvisioner.RequireWithinGrid refuses one reaching past the declared grid. */
    .biztech-portal-rowspan-1[b-6iuciq708g] { grid-row-end: span 1; }
    .biztech-portal-rowspan-2[b-6iuciq708g] { grid-row-end: span 2; }
    .biztech-portal-rowspan-3[b-6iuciq708g] { grid-row-end: span 3; }
    .biztech-portal-rowspan-4[b-6iuciq708g] { grid-row-end: span 4; }
    .biztech-portal-rowspan-5[b-6iuciq708g] { grid-row-end: span 5; }
    .biztech-portal-rowspan-6[b-6iuciq708g] { grid-row-end: span 6; }
    .biztech-portal-rowspan-7[b-6iuciq708g] { grid-row-end: span 7; }
    .biztech-portal-rowspan-8[b-6iuciq708g] { grid-row-end: span 8; }
    .biztech-portal-rowspan-9[b-6iuciq708g] { grid-row-end: span 9; }
    .biztech-portal-rowspan-10[b-6iuciq708g] { grid-row-end: span 10; }
    .biztech-portal-rowspan-11[b-6iuciq708g] { grid-row-end: span 11; }
    .biztech-portal-rowspan-12[b-6iuciq708g] { grid-row-end: span 12; }
    .biztech-portal-rowspan-13[b-6iuciq708g] { grid-row-end: span 13; }
    .biztech-portal-rowspan-14[b-6iuciq708g] { grid-row-end: span 14; }
    .biztech-portal-rowspan-15[b-6iuciq708g] { grid-row-end: span 15; }
    .biztech-portal-rowspan-16[b-6iuciq708g] { grid-row-end: span 16; }
    .biztech-portal-rowspan-17[b-6iuciq708g] { grid-row-end: span 17; }
    .biztech-portal-rowspan-18[b-6iuciq708g] { grid-row-end: span 18; }
    .biztech-portal-rowspan-19[b-6iuciq708g] { grid-row-end: span 19; }
    .biztech-portal-rowspan-20[b-6iuciq708g] { grid-row-end: span 20; }
    .biztech-portal-rowspan-21[b-6iuciq708g] { grid-row-end: span 21; }
    .biztech-portal-rowspan-22[b-6iuciq708g] { grid-row-end: span 22; }
    .biztech-portal-rowspan-23[b-6iuciq708g] { grid-row-end: span 23; }
    .biztech-portal-rowspan-24[b-6iuciq708g] { grid-row-end: span 24; }

    .biztech-portal-colspan-1[b-6iuciq708g] { grid-column-end: span 1; }
    .biztech-portal-colspan-2[b-6iuciq708g] { grid-column-end: span 2; }
    .biztech-portal-colspan-3[b-6iuciq708g] { grid-column-end: span 3; }
    .biztech-portal-colspan-4[b-6iuciq708g] { grid-column-end: span 4; }
    .biztech-portal-colspan-5[b-6iuciq708g] { grid-column-end: span 5; }
    .biztech-portal-colspan-6[b-6iuciq708g] { grid-column-end: span 6; }
    .biztech-portal-colspan-7[b-6iuciq708g] { grid-column-end: span 7; }
    .biztech-portal-colspan-8[b-6iuciq708g] { grid-column-end: span 8; }
    .biztech-portal-colspan-9[b-6iuciq708g] { grid-column-end: span 9; }
    .biztech-portal-colspan-10[b-6iuciq708g] { grid-column-end: span 10; }
    .biztech-portal-colspan-11[b-6iuciq708g] { grid-column-end: span 11; }
    .biztech-portal-colspan-12[b-6iuciq708g] { grid-column-end: span 12; }
    .biztech-portal-colspan-13[b-6iuciq708g] { grid-column-end: span 13; }
    .biztech-portal-colspan-14[b-6iuciq708g] { grid-column-end: span 14; }
    .biztech-portal-colspan-15[b-6iuciq708g] { grid-column-end: span 15; }
    .biztech-portal-colspan-16[b-6iuciq708g] { grid-column-end: span 16; }
    .biztech-portal-colspan-17[b-6iuciq708g] { grid-column-end: span 17; }
    .biztech-portal-colspan-18[b-6iuciq708g] { grid-column-end: span 18; }
    .biztech-portal-colspan-19[b-6iuciq708g] { grid-column-end: span 19; }
    .biztech-portal-colspan-20[b-6iuciq708g] { grid-column-end: span 20; }
    .biztech-portal-colspan-21[b-6iuciq708g] { grid-column-end: span 21; }
    .biztech-portal-colspan-22[b-6iuciq708g] { grid-column-end: span 22; }
    .biztech-portal-colspan-23[b-6iuciq708g] { grid-column-end: span 23; }
    .biztech-portal-colspan-24[b-6iuciq708g] { grid-column-end: span 24; }
}

/* R5.8's 44px, under a coarse pointer only - a mouse at a desk does not need it, which is why that rule
   is keyed on the pointer rather than on the width. UiGalleryViewportTests measured that Chromium keys
   `pointer: coarse` on IsMobile/HasTouch, not on viewport size.

   ::deep, because the affordances are a section renderer's or EditableRegion's markup rather than this
   file's - and without it this rule would be scoped to elements this component does not render, which is
   the same present-and-inert failure the header above describes. */
/* 🔴 W1b-08 · THE WHOLE-CARD ANCHOR, AND IT NEEDS ITS OWN RULE FOR THE REASON THE BLOCK BELOW EXISTS.

   The card is now one <a> wrapping the chip, the label and the caption. An anchor is INLINE by default,
   so without this it would shrink-wrap its content instead of filling the cell it was given - and the
   tile would stop being a box at every width, not only under a coarse pointer.

   display: flex with a column direction, so the paper inside stretches to the anchor's width; height
   100% so a short tile still fills a row a taller neighbour set. text-decoration and colour are
   INHERITED rather than reset to a link's blue: the card reads as a card, and its label is a heading -
   R3's palette owns the ink, and an underlined h2 inside a bordered box would be two affordances drawn
   over each other. The focus ring is app.css's one shared ring (R3.11) and is not touched here. */
.biztech-portal-tile-link[b-6iuciq708g] {
    display: flex;
    flex-direction: column;

    /* 🔴 GROW, RATHER THAN `block-size: 100%`, AND THE DIFFERENCE IS A DEFECT THIS CARD SHIPPED.
       Both make a lone anchor fill its cell, so cards in one row stay the same height - which is all
       this declaration was ever for. They differ the moment the cell holds a second child: 100% of the
       cell is the WHOLE track whether or not a sibling needs part of it, while `flex: 1 1 auto` in the
       column above is 100% of what is LEFT. PortalHomeViewportTests
       .A_tiles_own_affordance_is_laid_out_inside_its_cell is the fact that tells the two apart; it
       failed at 35px before this line changed. */
    flex: 1 1 auto;
    text-decoration: none;
    color: inherit;
}

/* 🔴 W8-22 · AND THE PAPER GROWS WITH THE ANCHOR, WHICH IS WHAT MAKES A ROW OF CARDS ONE CARD.
   Legacy says it in two classes - `align-items-stretch` on the row and `h-100` on the card
   (Views/Home/Index.cshtml:51, :57) - so every card in a row is as tall as the tallest.

   The anchor already grew into its cell, and that was mistaken for the whole fix: the anchor is
   invisible, and what a reader sees is the BORDERED PAPER INSIDE IT, which took its content's size. So a
   tile with one line of label drew a short box inside a tall cell while its neighbour carrying the
   "not published yet" sentence drew a tall one, and a row of three read as three differently sized
   cards. Reported off the running stack in exactly those terms.

   `flex: 1 1 auto` rather than a hundred percent, for the reason the anchor's own rule records at
   length: 100% of a cell is the whole track whether or not a sibling needs part of it. ::deep because
   the paper is UiPaper's own root, so a bare class here would be stamped with this component's scope
   and match nothing (I19). It reaches BOTH branches deliberately - the linked card and the one with no
   route in this build - because a row holding one of each is precisely where the difference showed.

   🔴 AND THE CONTENT IS CENTRED IN IT, WHICH IS WHAT MAKING THE BOXES MATCH LEFT BEHIND. Filling the
   cell fixed the card and moved the defect one level in: a tile with a single line of label had a card
   the full height of its row with its chip and label clamped to the top, beside a neighbour whose
   paragraph filled the same box. Reported in exactly those terms - "properly aligned but the content is
   not centered". A flex column with `justify-content: center` costs the tall branch nothing, because
   content that fills its box cannot be centred anywhere else.

   THE GAP COMES WITH IT, AND THAT IS I18 RATHER THAN TIDINESS. The heading row carried `biztech-mb-2`,
   and a margin inside a centred column is counted as part of the content - so the chip and label would
   sit half that margin above the true middle of every short card. One gap on the container, no margin on
   any child, and the caption and the unavailable sentence are spaced by the same 8px the row used to
   spend on itself. */
.biztech-portal-tile[b-6iuciq708g]  .biztech-paper {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 8px;
    flex: 1 1 auto;
}

/* R5.8's 44px, under a coarse pointer only - a mouse at a desk does not need it, which is why that rule
   is keyed on the pointer rather than on the width. UiGalleryViewportTests measured that Chromium keys
   `pointer: coarse` on IsMobile/HasTouch, not on viewport size.

   ::deep, because the affordances are a child component's markup or EditableRegion's rather than this
   file's - and without it this rule would be scoped to elements this component does not render, which is
   the same present-and-inert failure the header above describes.

   🔴 AND THE CARD ANCHOR IS EXCLUDED, WHICH IS NOT A DETAIL. This block declares `display: inline-flex`,
   and the whole-card anchor MATCHES `::deep a` - so under a coarse pointer it would have been
   shrink-wrapped and vertically centred, and the tile would have stopped filling its cell on exactly the
   devices R13.7 measures. :not() keeps the block aimed at what it was written for: the affordances INSIDE
   a tile. The card's ## Do not forbids weakening a viewport assertion to accommodate this anchor, and
   this is the alternative - aim the rule, do not relax the measurement. */
@media (pointer: coarse) {
    .biztech-portal-tile[b-6iuciq708g]  a:not(.biztech-portal-tile-link),
    .biztech-portal-tile[b-6iuciq708g]  button {
        /* 🔴 THE DISPLAY IS WHAT MAKES THE MINIMUM APPLY AT ALL, AND WITHOUT IT THIS RULE IS PRESENT AND
           INERT. CSS 2.1 §10.7: min-height - and therefore min-block-size - DOES NOT APPLY to a
           non-replaced INLINE element. ExternalLinkSection's affordance is a bare <a>, which is inline,
           so the declaration below resolved and then governed nothing.

           MEASURED, not reasoned: PortalHomeViewportTests reported 17px - the line box - at all three
           widths under a coarse pointer, three failing cases out of three, before this line existed.
           That is exactly the failure R14.1a names: "A class that is present and inert passes every
           check in this repository." PortalHomeRenderTests asserts this block's TEXT and would have
           stayed green for ever.

           inline-flex rather than block or inline-block: an affordance inside a section's prose must not
           claim the whole line, and align-items keeps the label centred in the 44px box rather than
           sitting at its top - a target whose text is at the top of an oversized box reads as a
           misaligned link rather than as a bigger target. A button is already a flex container in
           app.css, so this restates what it has; an anchor is the case this fixes. */
        display: inline-flex;
        align-items: center;
        min-block-size: 44px;
    }
}

/* The card anchor's own floor under a coarse pointer. A card is far taller than 44px in practice, so
   this is a claim rather than a correction - and it is what PortalHomeViewportTests measures as a
   COMPUTED box, because the block above records what a declared-but-inapplicable minimum cost once. */
@media (pointer: coarse) {
    .biztech-portal-tile-link[b-6iuciq708g] {
        min-block-size: 44px;
    }
}
/* /Components/Portals/PortalTileChip.razor.rz.scp.css */
/* 🔴 W1b-08 · F2.19 · the tile's chip, at legacy's own size.

   60px is READ RATHER THAN CHOSEN: Views/Home/Index.cshtml:97 draws the chip at 60x60, and the whole
   card is a port of that home screen. It is also 15 x the 4px base unit, so R5.4 is satisfied without a
   LayoutConstants row - the same route SignInLayout's 24rem panel takes.

   🔴 W8-22 · THE BOX IS DRAWN HERE NOW, AND THIS PARAGRAPH USED TO SAY THE OPPOSITE. It read: "THE BOX
   IS DRAWN BY THE PARENT, NOT HERE ... the chip's outer box belongs to PortalTileCard and to the tile
   page's header - each renders its own - so this file styles only the two elements this component itself
   emits." Two things were wrong with that. Neither parent ever drew a box: both mount this component
   bare, so what a reader saw was a bare glyph at the seam's default size. And the file was already
   sizing both branches at 60px, so the size was this component's while the frame around it belonged to
   nobody - which is how the 60px this file has claimed since W1b-08 came to render at 24px.

   What is still the PARENT's is the chip's PLACEMENT: PortalTileCard centres it against the label
   (biztech-align-center), PortalTilePageBody aligns it to the heading's last line. Blazor CSS isolation
   is why that division has to hold - a component's .razor.css is rewritten to
   `selector[b-<that component's hash>]` and only elements in THAT component's own markup carry the
   attribute, so a rule for the row belongs to whoever renders the row. R14.1a names the failure:
   "a class that is present and inert passes every check in this repository."

   ONE COLOUR, AND IT IS A TOKEN (R14.1 layer 1): the glyph is tinted with --biztech-palette-primary, as
   legacy tints its own, so it follows a per-tenant palette in both modes without this file naming
   either. This paragraph used to say NO COLOUR, and that was true when the glyph inherited its card's
   ink. NO overflow (R13.8). Logical properties throughout - inline-size, block-size - never height or
   width. */

/* 🔴 W8-22 · THE FRAME IS DECLARED ONCE, FOR BOTH BRANCHES, AND IT IS LEGACY'S OWN `.service-icon`
   READ RATHER THAN CHOSEN: 60px square, a border, the page's ground behind it and the glyph centred
   inside (customizeTheme.css:362-376 - `width: 60px; height: 60px; background-color: var(--bs-body-bg);
   border: 2px solid var(--bs-icon-bg); display: flex; align-items: center; justify-content: center;
   font-size: 1.9rem; color: var(--bs-icon-bg)`). The launcher this ports is 103 glyph tiles of 118, so
   the framed chip IS what that home screen looks like - and until this card v2 drew an unframed 24px
   glyph for every one of them.

   ONE GROUPED RULE rather than two, because "a grid of tiles is a grid of one shape whichever branch
   each tile takes" is a claim that has to be true of the FRAME rather than of each branch separately -
   and two copies of it are two things that can drift.

   THE BOX'S PLACEMENT IS STILL THE PARENT'S: PortalTileCard centres it against the label,
   PortalTilePageBody aligns it to the heading's last line, and neither is here. What moved into this
   file is the box ITSELF, which both surfaces had already delegated by mounting this component bare.

   1px rather than legacy's 2px: R3's outline token is drawn at 1px everywhere else in this application
   (UiPaper, UiToolbar, UiMenu), and a chip with a heavier border than the card around it reads as the
   more important of the two. */
.biztech-portal-chip-mark[b-2vc8ymchfh],
.biztech-portal-chip-glyph[b-2vc8ymchfh] {
    box-sizing: border-box;
    flex: 0 0 auto;
    inline-size: 60px;
    block-size: 60px;
    border: 1px solid var(--biztech-palette-outline-subtle);
    border-radius: var(--biztech-radius-default);
    background: var(--biztech-palette-background);
}

/* The uploaded mark. object-fit is R11.7's requirement for the first surface to render entity imagery,
   and `contain` rather than `cover` is the decision: a partner logo cropped to fill a square is a partner
   logo with its edges cut off, which is worse than one with space around it. The aspect ratio is the
   file's own and is respected. Legacy uses `cover` here and is wrong about it for the same reason.

   The padding is what keeps a mark off its own frame - 4px, R5.1's base unit. */
.biztech-portal-chip-mark[b-2vc8ymchfh] {
    display: block;
    object-fit: contain;
    padding: 4px;
}

/* The glyph, centred in the frame rather than filling it. `color` is the one colour this file declares
   and it is a TOKEN (R14.1 layer 1) - legacy tints the glyph too, and it is what makes a grid of grey
   Material glyphs read as a tenant's own launcher. R11.3 holds: UiIcon still sets no colour of its own
   and inherits this one, so it follows a per-tenant palette in both modes with no rule here naming
   either. */
.biztech-portal-chip-glyph[b-2vc8ymchfh] {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--biztech-palette-primary);
}

/* 🔴 ::deep, AND IT IS THE HALF THIS FILE EXISTS TO GET RIGHT. The svg is UiIcon's own element, so a
   bare `.biztech-icon` here would be stamped with THIS component's scope and match nothing - the very
   defect the wrapper above was added to fix, arrived at from the other direction. The ancestor the
   ::deep hangs off is the span, which this component does render.

   32px is 8 x R5.1's base unit and the nearest scale value to legacy's 1.9rem (30.4px). UiIcon renders
   an inline svg, so the box is what sizes it; the font-size is declared beside it because a glyph that
   ever renders as a font-backed element must not be the one thing on the page that ignores the frame. */
.biztech-portal-chip-glyph[b-2vc8ymchfh]  .biztech-icon {
    inline-size: 32px;
    block-size: 32px;
    font-size: 32px;
}
/* /Components/Portals/PortalTilePageBody.razor.rz.scp.css */
/* 🔴 W1b-08 · F2.19 · the tile page's own two rules, and why it needs so few.

   The page sits inside PortalLayout's centred container, so it declares no width, no max-width, no
   gutter and no vertical rhythm: R5.3 and R5.10 put all four on the container, and a page adding its own
   would be "describing a spacing system that does not exist". The sections below the header are
   PortalSectionOutlet's, and every tile behaviour's chrome is W1b-02's component.

   NO overflow, NO position, NO colour, NO height (R13.8, R14.1 layer 1). */

/* The chip beside the heading. Baseline-ish rather than centred: the h1's cap height sits high in its
   line box, so `center` on a 60px chip against a 24px heading reads as the heading sagging. `end`
   against the first line is what legacy's own header does (DashboardSection.cshtml:109-120), and it is
   what keeps the two aligned when the label wraps to two lines at 375px. */
.biztech-portal-tile-page-header[b-wy9f9r51wk] {
    display: flex;
    align-items: flex-end;
    gap: 12px;
}

/* 🔴 R5.8's 44px, UNDER A COARSE POINTER ONLY, AND THIS RULE IS WHY THIS FILE EXISTS AT ALL.
   PortalTileCard.razor.css carries the identical block for the launcher and records what it cost to
   find: a bare <a> is an INLINE element, and CSS 2.1 §10.7 says min-height does not apply to one - so
   the declaration resolved and then governed nothing, measured at 17px at all three widths. The fix is
   the display, not the minimum.

   That block is scoped to `.biztech-portal-tile ::deep a`, an element THIS page does not render - the
   tile div belongs to the launcher's card. So the moved menu components and this header's meeting link
   would have lost their target floor in the move, silently, with every render test still green. Hence
   the second copy, deliberately, with the same values.

   ::deep, because the affordances are a section renderer's markup or W1b-02's rather than this file's -
   and without it the rule would be scoped to elements this page does not render, which is R14.1a's
   "present and inert" failure.

   🔴 AND THE SELECTOR IS THE PAGE'S WRAPPER, NOT ITS HEADER, WHICH THIS FILE GOT WRONG ONCE. The first
   draft scoped it to .biztech-portal-tile-page-header - so it reached the meeting link and NOTHING
   ELSE: the moved PortalButtonMenuTile and PortalAppMenuTile render in the BODY, below that div, and
   their rows are the majority of the tap targets on this page. Present, resolved, and governing almost
   nothing - which is the same failure class the paragraph above describes, arrived at from the other
   direction. The wrapper div exists for this rule.

   inline-flex rather than block: an affordance inside a section's prose must not claim the whole line,
   and align-items keeps the label centred in the 44px box rather than sitting at its top. */
@media (pointer: coarse) {
    .biztech-portal-tile-page[b-wy9f9r51wk]  a,
    .biztech-portal-tile-page[b-wy9f9r51wk]  button {
        display: inline-flex;
        align-items: center;
        min-block-size: 44px;
    }
}
/* /Components/Portals/PortalWelcomeCard.razor.rz.scp.css */
/* 🔴 W8-22 · F2.21 · the portal's heading and whatever its host puts beside it.

   ONE RULE, AND IT IS HERE RATHER THAN IN UTILITIES FOR ONE REASON: flex-wrap. `biztech-d-flex`,
   `biztech-align-center` and `biztech-gap-3` all exist and would have done the rest, but the heading is
   a TENANT'S OWN NAME - "Whann Garcia Insurance Agency Platform" is three lines at 375px - and a row
   that cannot wrap is R13.8's own failure: the aside would be squeezed into a column beside it, or the
   page would scroll sideways. R14.1's layer order asks for the layer above first; this is the case where
   it cannot reach.

   `center` rather than `baseline`: the aside is a pill with its own padding, and aligning two boxes of
   different heights on their text baselines leaves the shorter one's border sitting high.

   12px, a whole multiple of the 4px base unit (R5.4) and the same gap the tile page's own header uses
   between its chip and its heading.

   NO colour, NO overflow, NO height (R13.8, R14.1 layer 1). The h1's ink is W0-71's - hierarchy by size
   and weight, never by hue - and PageTitleInkTests is what enforces that this file adds none.

   🔴 EVERY SELECTOR ENDS ON A CLASS THIS COMPONENT'S OWN MARKUP CARRIES (I19). The aside is a
   RenderFragment somebody else wrote, so nothing here reaches inside it: its own component styles
   itself, which is why AgentOrganisationSwitcher keeps its own scoped sheet rather than being styled
   from here. */
.biztech-portal-welcome-heading[b-syyraau22j] {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
}
/* /Components/Portals/TemplateBackedSection.razor.rz.scp.css */
/*
    W1a-07 · a template-backed section's thumbnail.

    🔴 R11.7 IN FULL, AND IT IS NOT DECIDED HERE FOR THE FIRST TIME. That rule says entity imagery
    "needs a decided aspect ratio, object-fit, srcset/sizes, loading=lazy and a fallback - it is not a
    side effect of a feature". W7-02's EducationItemFrame.razor.css made those choices and this reuses
    them rather than making a second set: 16/9 because the catalogue is decks and slide stills rather
    than portraits, and object-fit: cover because a thumbnail is whatever an author uploaded, so
    cropping it beats distorting a slide. srcset is deliberately absent for that file's reason - there
    is one stored object per template, and a srcset naming one candidate promises the browser a choice
    it does not have. loading=lazy is on the element, and the fallback is that there is NO element at
    all when there is nothing to show.

    🔴 max-width, WHICH EducationItemFrame DOES NOT NEED AND THIS DOES. Its cards sit inside a
    UiGridItem at Xs=12 Sm=6 Md=4, so the column bounds the picture. A portal section is full-width and
    standalone today, so width: 100% alone would render a 16:9 image across the whole page and bury the
    text under it. 240px is the bound, and W1a-08 owns the grid this eventually sits in - at which
    point the column may make this line unnecessary, which is a thing for that card to check rather
    than for this one to guess.

    No aspect-ratio'd placeholder for the missing case, and that is the same reasoning read the other
    way: EducationItemFrame draws one because its cards sit in a grid that must not reflow when a
    thumbnail resolves late. Nothing here is aligned against anything, so a placeholder would be a box
    with no layout to hold.

    Every colour is a --biztech-* role and every length is a multiple of the 4px base unit, which is
    what PortalStyleSheetTests' two sweeps require of any scoped sheet - they pick this file up with no
    edit.
*/

.biztech-portal-template-thumb[b-tzmyykivzu] {
    aspect-ratio: 16 / 9;
    border-radius: var(--biztech-radius-default);
    display: block;
    max-width: 240px;
    object-fit: cover;
    width: 100%;
}
/* /Components/Presentations/DeckDeliveryPanel.razor.rz.scp.css */
/*
    W6-29 · F7.31 · F5.4. The delivery panel's one rule.

    🔴 EVERY COLOUR IS A --biztech-palette-* ROLE AND THE RADIUS IS --biztech-radius-default, which
    PortalStyleSheetTests sweeps scoped sheets for. There are no spacing variables in this system, so
    lengths are literal rem — DeckReadingArea.razor.css beside this one makes the same note.
*/

.biztech-deck-delivery-link[b-ixoi1sbj6i] {
    /*
        A customer's link is a base URI plus /i/ plus a GUID plus /presentation - about seventy characters
        with no space in it, which is precisely the string a browser will NOT break. Without this it
        overflows the paper on a narrow viewport and the presenter cannot see the end of the thing they
        are meant to copy.

        `anywhere` rather than `break-word`: the second only breaks when the word is alone on a line, and
        this one sits in a paragraph beside the sentence above it.
    */
    overflow-wrap: anywhere;

    /*
        Monospace, because this is a string a human copies and sometimes reads back over a phone. The
        stack is the one app.css uses for code; there is no --biztech-font-mono token to name.
    */
    font-family: ui-monospace, "Cascadia Mono", "Segoe UI Mono", Consolas, monospace;

    /*
        Set apart from the sentence above it, so it reads as the thing to copy rather than as more prose.
    */
    margin-top: 0.5rem;
    padding: 0.5rem;
    border: 1px solid var(--biztech-palette-outline);
    border-radius: var(--biztech-radius-default);
    background-color: var(--biztech-palette-surface);
}
/* /Components/Presentations/DeckReadingArea.razor.rz.scp.css */
/*
    W5-04's reading surface, moved here by W6-29 with the markup it styles.

    🔴 THE MOVE WAS FORCED RATHER THAN TIDY, AND NO TEST WOULD HAVE CAUGHT LEAVING IT BEHIND. Blazor's CSS
    isolation stamps a scope attribute on the elements written in a component's OWN file, and the generated
    selector carries it - so `.biztech-deck-reading-area[b-abc123]` in DeckViewer.razor.css stops matching
    the moment the element is rendered by DeckReadingArea instead. bUnit applies no stylesheet, so all 198
    Deck render tests stay green while the running page loses its frame, its 24rem floor and its
    touch-action. Found by reading the isolation rule rather than by a red test.

    🔴 EVERY COLOUR IS A --biztech-palette-* ROLE AND THE RADIUS IS --biztech-radius-default.
    PortalStyleSheetTests sweeps scoped sheets for both, and it earned its keep in the file these rules came
    from: it first shipped with `var(--biztech-radius-md)`, a token that does not exist. There are no spacing
    variables in this system, so lengths are literal rem.
*/

.biztech-deck-reading-area[b-6req186qvf] {
    /*
        🔴 pan-y, NOT none. R13.5's reasoning applied to a swipe rather than a canvas: `none` would take
        the browser's vertical scroll away from the reading area, so a reader on a phone could not scroll
        the page while their finger happened to be over the picture. `pan-y` keeps vertical scrolling with
        the browser and leaves the horizontal axis to the swipe handler.
    */
    touch-action: pan-y;

    display: flex;
    align-items: center;
    justify-content: center;

    /*
        One slide fills the reading area, which is the card's first sentence. A min-height rather than a
        fixed one, so a portrait slide is not cropped and the frame does not collapse when a slide's image
        is unavailable and an alert takes its place - a reading area that changed size between slides would
        move the controls under the reader's cursor.
    */
    min-height: 24rem;

    padding: 1rem;
    border: 1px solid var(--biztech-palette-outline);
    border-radius: var(--biztech-radius-default);
    background-color: var(--biztech-palette-surface);
}

.biztech-deck-image[b-6req186qvf] {
    /*
        Contained rather than covered: a slide is a document page, so seeing all of it matters more than
        filling the frame. `cover` would crop whatever the author put at the edges.
    */
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

.biztech-deck-controls[b-6req186qvf] {
    display: flex;
    gap: 0.5rem;
    justify-content: center;
    margin-top: 1rem;
}
/* /Components/Presentations/SlideArranger.razor.rz.scp.css */
/*
    W5-16 · F5.15. The author's controls, laid out like the reader's.

    A copy of .biztech-deck-controls' four declarations rather than a shared class, and that is a CSS
    isolation fact rather than a preference: Blazor scopes a sheet to the elements of its OWN component, so
    DeckViewer.razor.css's rule cannot reach anything rendered here. UiIconButton's own header records the
    same constraint one level down - "a class defined in UiText.razor.css cannot reach an element rendered
    here. Every component in this folder declares the classes it names."

    No colour and no radius token, so PortalStyleSheetTests' sweep of scoped sheets has nothing to find -
    the decision .biztech-deck-title-hidden's comment already records for this feature area.
*/

.biztech-slide-arranger[b-lsz65d9ftl] {
    display: flex;
    gap: 0.5rem;
    justify-content: center;
    margin-top: 1rem;
}
/* /Components/Presentations/SlidePlayer.razor.rz.scp.css */
/*
    W5-05 · F5.5. The player's own size, and the reason this file exists at all.

    🔴 A CLASS IN MARKUP WITH NO RULE ANYWHERE, WHICH A REVIEW FOUND AND NO TEST COULD HAVE.
    SlidePlayer named .biztech-slide-player on its <video> and nothing declared it. Every sweep in
    PortalStyleSheetTests reads the rules that EXIST and asserts things about them; none asked whether a
    class in the markup HAS one, so a class that styles nothing was invisible to all of them.

    It is not cosmetic. preload="metadata" resolves the intrinsic size, .biztech-deck-reading-area is a
    flex container with no max-width on its item, and a flex item does not shrink below its intrinsic
    width without one - so a 1920-wide recording lays out 1920 wide, and at R13.7's 375px floor the
    player overflows the main landmark five times over. That is R13.8's failure on this feature's happy
    path, and no viewport suite opens the deck viewer at all.

    🔴 HERE RATHER THAN IN DeckViewer.razor.css, and that is forced rather than tidy: CSS isolation
    stamps the scope attribute on DeckViewer's own elements only, so a rule there would need ::deep to
    reach a child component's <video>. The element's own component owns the element's own size.
*/
.biztech-slide-player[b-set0q5x7lk] {
    /*
        The three declarations .biztech-deck-image carries, for its reasons. Contained rather than
        covered: a recording is content to be seen whole, and `cover` would crop whatever is at the
        edges. Percentages rather than lengths, so there is nothing here for R5.4's scale to measure.
    */
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}
/* /Components/Ui/UiAlert.razor.rz.scp.css */
/* W0-52 · the inline alert. Outlined, because R3.6 puts the 3:1 on the border - so the one thing an
   alert must do, be visible as an object, is carried by the role the palette actually tests.

   The ink and the border are the severity's role, and PaletteContrast requires every status colour
   at 4.5:1 as text against BOTH Background and Surface. So an alert cannot be illegible on either
   surface in either mode on any palette that boots. The body text takes the page's own ink rather
   than the severity's: a whole paragraph in error red is harder to read than one in the ordinary
   colour beside a red border and a red glyph, and R12.7's second channel is already spent on the
   glyph. */

.biztech-alert[b-vx07oufthc] {
    display: flex;
    align-items: flex-start;
    gap: 12px;

    box-sizing: border-box;
    padding: 16px;
    border: 1px solid currentcolor;
    border-radius: var(--biztech-radius-default);

    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-body2-size);
    font-weight: var(--biztech-font-body2-weight);
    line-height: var(--biztech-font-body2-line-height);
    letter-spacing: var(--biztech-font-body2-letter-spacing);
}

/* R5.7's console density. /i/{key} is comfortable and leaves this off. */
.biztech-alert-dense[b-vx07oufthc] {
    padding: 8px 12px;
    gap: 8px;
}

.biztech-alert-info[b-vx07oufthc] { color: var(--biztech-palette-info); }
.biztech-alert-success[b-vx07oufthc] { color: var(--biztech-palette-success); }
.biztech-alert-warning[b-vx07oufthc] { color: var(--biztech-palette-warning); }
.biztech-alert-error[b-vx07oufthc] { color: var(--biztech-palette-error); }

.biztech-alert-icon[b-vx07oufthc] {
    /* The glyph inherits the severity colour from the block above - R11.3, and the reason UiIcon
       paints with currentColor rather than taking a colour of its own. */
    margin-block-start: 2px;
}

.biztech-alert-body[b-vx07oufthc] {
    flex: 1 1 auto;
    min-inline-size: 0;
    color: var(--biztech-palette-text-primary);

    /* R4.6's prose measure. A failure message is prose - R10.4 asks it to say what happened and then
       what to do next - and a sentence that runs the width of a console is one nobody finishes. */
    max-inline-size: 46rem;
}

.biztech-alert-action[b-vx07oufthc] {
    flex: none;
}
/* /Components/Ui/UiButton.razor.rz.scp.css */
/* W0-52 · the button.

   🔴 A <button> ARRIVES WITH USER-AGENT DEFAULTS, and app.css already records what happens when they
   are left alone: T24 round 3 turned the board card into a real <button> and had to undo "centred
   13px Arial and a shrink-to-fit width" - the card rendered as a small grey button rather than a
   card. MudBlazor's `*` reset hides some of that today and W0-53 removes it, so every one of them is
   declared here.

   The colours are the palette's, and the contrast is decided by the palette rather than by this
   file: a FILLED button paints an On* pair (OnPrimary on Primary, tested at 4.5:1), and an OUTLINED
   or TEXT one paints the role as ink on the page or a card, which PaletteContrast also requires at
   4.5:1 against both surfaces. Neither can fail on a palette that boots. */

.biztech-button[b-7pp4wp8xz3] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;

    box-sizing: border-box;
    border: 1px solid transparent;
    border-radius: var(--biztech-radius-default);

    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-button-size);
    font-weight: var(--biztech-font-button-weight);
    line-height: var(--biztech-font-button-line-height);
    letter-spacing: var(--biztech-font-button-letter-spacing);
    /* D1: sentence case. The token is `none` where MudTheme still says `uppercase`. */
    text-transform: var(--biztech-font-button-transform);

    text-align: center;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    background: transparent;
    color: inherit;

    /* R6.7: 120ms, ease-in-out, and only properties that do not lay out. */
    transition: background-color 120ms ease-in-out, border-color 120ms ease-in-out;
}

.biztech-button-small[b-7pp4wp8xz3] {
    min-block-size: 32px;
    padding: 4px 12px;
}

/* W0-63 · finding 6. The floor reads the token - 40px at :root, 32px under .biztech-shell - so this
   button compacts with the console and keeps its box everywhere else. The DEFINITE size that makes
   declared equal rendered is `.biztech-shell .biztech-button` in app.css; see its note for why a floor
   alone leaves a medium button rendering 42.5.

   .biztech-button-small keeps its literal 32px above: that is already the console's target and already
   on the scale, and outside the console a 32px small button is what /i/{key} was signed off with. */
.biztech-button-medium[b-7pp4wp8xz3] {
    min-block-size: var(--biztech-control-height);
    padding: var(--biztech-control-padding-block, 8px) 16px;
}

/* ── Outlined · the house default (R3.6: the 3:1 lives on the border) ──────────────────────────── */

.biztech-button-outlined[b-7pp4wp8xz3] {
    border-color: currentcolor;
    background: transparent;
}

.biztech-button-outlined:hover:not(:disabled)[b-7pp4wp8xz3] {
    background: color-mix(in srgb, currentcolor 8%, transparent);
}

/* ── Text · the lower-weight action beside a filled one ────────────────────────────────────────── */

.biztech-button-text[b-7pp4wp8xz3] {
    border-color: transparent;
    background: transparent;
}

.biztech-button-text:hover:not(:disabled)[b-7pp4wp8xz3] {
    background: color-mix(in srgb, currentcolor 8%, transparent);
}

/* ── Filled · one per surface ──────────────────────────────────────────────────────────────────── */

/* 🔴 THE BASE RULE CARRIES THE FALLBACK, AND IT EXISTS BECAUSE TWO MEMBERS OF UiColor HAD NO RULE AT
   ALL. `Filled` + `Default` and `Filled` + `Inherit` matched none of the seven role rules below, so a
   filled button rendered with NO GROUND and whatever ink it had inherited: on /case-types the SELECTED
   tab was indistinguishable from the unselected ones, and hovering it painted a dark ground under
   near-black ink at 1.24:1 against R3.1's 4.5 floor. Measured in Chrome on 2026-09-07.

   A fallback here rather than two more role rules, so the answer covers every member of the enum -
   including one added later, which is the case that would otherwise repeat this exactly. Primary is the
   fallback because this section's own heading is the reason: ONE FILLED COLOUR PER SURFACE, so a filled
   button that names no role is asking for that one. */
.biztech-button-filled[b-7pp4wp8xz3] {
    background: var(--biztech-button-fill, var(--biztech-palette-primary));
    color: var(--biztech-button-ink, var(--biztech-palette-on-primary));
    border-color: var(--biztech-button-fill, var(--biztech-palette-primary));
}

.biztech-button-filled.biztech-button-primary[b-7pp4wp8xz3] {
    --biztech-button-fill: var(--biztech-palette-primary);
    --biztech-button-ink: var(--biztech-palette-on-primary);
}

.biztech-button-filled.biztech-button-secondary[b-7pp4wp8xz3] {
    --biztech-button-fill: var(--biztech-palette-secondary);
    --biztech-button-ink: var(--biztech-palette-on-secondary);
}

.biztech-button-filled.biztech-button-tertiary[b-7pp4wp8xz3] {
    --biztech-button-fill: var(--biztech-palette-tertiary);
    --biztech-button-ink: var(--biztech-palette-on-tertiary);
}

.biztech-button-filled.biztech-button-success[b-7pp4wp8xz3] {
    --biztech-button-fill: var(--biztech-palette-success);
    --biztech-button-ink: var(--biztech-palette-on-success);
}

.biztech-button-filled.biztech-button-warning[b-7pp4wp8xz3] {
    --biztech-button-fill: var(--biztech-palette-warning);
    --biztech-button-ink: var(--biztech-palette-on-warning);
}

.biztech-button-filled.biztech-button-error[b-7pp4wp8xz3] {
    --biztech-button-fill: var(--biztech-palette-error);
    --biztech-button-ink: var(--biztech-palette-on-error);
}

.biztech-button-filled.biztech-button-info[b-7pp4wp8xz3] {
    --biztech-button-fill: var(--biztech-palette-info);
    --biztech-button-ink: var(--biztech-palette-on-info);
}

/* 🔴 THE MIX READS THE BUTTON'S OWN FILL, NOT THE BRAND. This named --biztech-palette-primary, so
   hovering a filled ERROR button moved it toward blue rather than toward its own red, and hovering the
   unstyled Default one is what made the contrast failure above visible. Every role now sets
   --biztech-button-fill, and the base rule's fallback answers for the ones that do not.

   Darkened toward the page's own ink rather than by an opacity: R3.9 forbids expressing a colour
   as opacity on inherited ink, and mixing keeps the result a real value something could measure. */
.biztech-button-filled:hover:not(:disabled)[b-7pp4wp8xz3] {
    background: color-mix(
        in srgb,
        currentcolor 12%,
        var(--biztech-button-fill, var(--biztech-palette-primary)));
}

/* ── Ink for the unfilled variants ─────────────────────────────────────────────────────────────── */

.biztech-button-outlined.biztech-button-default[b-7pp4wp8xz3],
.biztech-button-text.biztech-button-default[b-7pp4wp8xz3] { color: var(--biztech-palette-text-primary); }
.biztech-button-outlined.biztech-button-inherit[b-7pp4wp8xz3],
.biztech-button-text.biztech-button-inherit[b-7pp4wp8xz3] { color: inherit; }
.biztech-button-outlined.biztech-button-primary[b-7pp4wp8xz3],
.biztech-button-text.biztech-button-primary[b-7pp4wp8xz3] { color: var(--biztech-palette-primary); }
.biztech-button-outlined.biztech-button-secondary[b-7pp4wp8xz3],
.biztech-button-text.biztech-button-secondary[b-7pp4wp8xz3] { color: var(--biztech-palette-secondary); }
.biztech-button-outlined.biztech-button-tertiary[b-7pp4wp8xz3],
.biztech-button-text.biztech-button-tertiary[b-7pp4wp8xz3] { color: var(--biztech-palette-tertiary); }
.biztech-button-outlined.biztech-button-success[b-7pp4wp8xz3],
.biztech-button-text.biztech-button-success[b-7pp4wp8xz3] { color: var(--biztech-palette-success); }
.biztech-button-outlined.biztech-button-warning[b-7pp4wp8xz3],
.biztech-button-text.biztech-button-warning[b-7pp4wp8xz3] { color: var(--biztech-palette-warning); }
.biztech-button-outlined.biztech-button-error[b-7pp4wp8xz3],
.biztech-button-text.biztech-button-error[b-7pp4wp8xz3] { color: var(--biztech-palette-error); }
.biztech-button-outlined.biztech-button-info[b-7pp4wp8xz3],
.biztech-button-text.biztech-button-info[b-7pp4wp8xz3] { color: var(--biztech-palette-info); }

/* ── Disabled ─────────────────────────────────────────────────────────────────────────────────── */

/* TextDisabled is deliberately dim and deliberately exempt from contrast (R3.8): WCAG 1.4.3 exempts
   inactive components, and a "disabled" colour that cleared 4.5:1 would make a disabled control look
   enabled - which is a worse defect than the one it would fix. R9.2 puts the REASON in a caption
   beside the control, where it can actually be read. */
.biztech-button:disabled[b-7pp4wp8xz3] {
    color: var(--biztech-palette-text-disabled);
    border-color: var(--biztech-palette-outline-subtle);
    background: transparent;
    cursor: default;
}

.biztech-button-label[b-7pp4wp8xz3] {
    /* R4.7 prefers wrap over truncation, but a button's label is short by construction and a wrapped
       one changes the control's height mid-row. The label stays on one line; a label long enough to
       need two is a label to rewrite. */
    display: inline-block;
}

@media (prefers-reduced-motion: reduce) {
    .biztech-button[b-7pp4wp8xz3] {
        transition: none;
    }
}
/* /Components/Ui/UiChatComposer.razor.rz.scp.css */
/* W6-44 · the seam's chat composer.

   Tokens and scale steps only (R3.1, R5.4). The box's own look — border, radius, ground, ink, type — is
   `.biztech-input`, global in app.css, which is what stops a hand-shaped control drifting from what
   every other field renders. Nothing here restates any of it.

   🔴 THE TYPE AND COLOUR CLASSES ARE DECLARED HERE RATHER THAN REUSED. Blazor's CSS isolation scopes a
   sheet to its own component's elements, so a class defined in UiText.razor.css matches nothing rendered
   here — it would compile, render, and silently take the inherited value. Every component in this folder
   declares the classes it names; UiIconButton.razor.css records the same. */

/* 🔴 THE ROOT HAS A RULE, AND IT NEEDS ONE FOR TWO REASONS.
   Every `biztech-*` class the markup names must be declared somewhere
   (UtilityClassTests.Every_biztech_class_the_markup_names_is_declared_somewhere), and this is where the
   caption is stacked under the row. It declares no margin and no padding, so a spacing utility the
   caller passes still reaches the element at (0,1,0) with nothing to lose to — R14.1a, and the nine
   inert utilities it was written for. */
.biztech-chat-composer[b-re8ejwh9a4] {
    display: flex;
    flex-direction: column;
}

/* The padding and the hairline are the ROW's, which is where specimen 7 draws them (`.mk-composer`
   carries both). Declared here rather than passed down from the mounting panel: a class a parent hands
   to `Class=` lands on markup this component renders, so the parent's scoped sheet cannot reach it
   without ::deep — the trap CaseProgressStrip.razor.css records. */
.biztech-chat-composer__row[b-re8ejwh9a4] {
    display: flex;
    align-items: flex-end;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    border-block-start: 1px solid var(--biztech-palette-outline-subtle);
}

/* 🔴 CLIPPED, NOT display:none OR visibility:hidden. Both of those remove the element from the
   accessibility tree, which would take the box's accessible NAME with it — and the name is the whole
   reason there is a <label> here rather than a placeholder alone. This is app.css's skip-link idiom
   minus the focus half: a <label> is not focusable, so it never needs to come back.

   The design reference's composer is one row; W0-52's decision D1 puts the seam's labels above their
   fields. The specimen wins on the shape, R12.4 wins on the mechanism, and this rule is where the two
   meet. */
.biztech-chat-composer__label[b-re8ejwh9a4] {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* min-inline-size: 0 so a long draft shrinks the box rather than pushing the send control out of the
   rail. A flex item's default minimum is its content, which is the same trap SessionStage's
   minmax(0, 1fr) answers one level up. */
.biztech-chat-composer__box[b-re8ejwh9a4] {
    flex: 1 1 auto;
    min-inline-size: 0;

    /* 🔴 NOT RESIZABLE, AND THIS IS THE ONE PLACE THAT IS RIGHT. `.biztech-input-multiline` — which this
       box deliberately does NOT wear — declares `resize: vertical` and an 80px floor, both correct for a
       form field in a page that scrolls. In a 320px rail beside a fixed 240px log, a reader dragging the
       composer taller is the only way left to make the panel push what is under it, which is the whole
       subject of W6-42 one level in. Two rows, always.

       This is not a box-model zero and it is not a property the utility layer owns (R14.1a). */
    resize: none;
}

/* 🔴 NO `margin: 0` HERE, AND UtilitySpecificityTests CAUGHT THE ATTEMPT. A <p>'s user-agent margin is
   already zeroed once, in BizTechCssVariables' document reset beside h1-h6 and hr — whose own comment
   records why: "THE ELEMENT RESET, DECLARED ONCE INSTEAD OF THIRTEEN TIMES IN THE SEAM", because a
   scoped `.x[b-xxxxxxxxxx]` is (0,2,0) against a utility's (0,1,0) and would silently outrank every
   spacing class a caller passes. This rule would have been the fourteenth restatement. */
.biztech-chat-composer__caption[b-re8ejwh9a4] {
    padding-block-start: 0.5rem;
    font-size: var(--biztech-font-caption-size);
    line-height: var(--biztech-font-caption-line-height);
    color: var(--biztech-palette-text-secondary);
}
/* /Components/Ui/UiChip.razor.rz.scp.css */
/* W0-52 · a labelled pill.

   🔴 THE COLOURED VARIANTS ARE SAFE BY CONSTRUCTION, AND THAT IS MEASURED RATHER THAN HOPED.
   PaletteContrast requires every brand and status role at 4.5:1 as TEXT against BOTH Background and
   Surface - "Primary used as text or an icon on the page" and "...on a card", seven roles times two
   surfaces. So an outlined chip that paints its ink and its border in a role cannot fail R3.5 on
   either surface, in either mode, on any palette that boots (an AA-failing one refuses to start).

   🔴 W0-74 REVERSED THE LAST SENTENCE OF THAT PARAGRAPH, WHICH READ: "That is also why the chip is
   outlined rather than filled: a filled one would need its On* partner and a per-role decision about
   which surface it sits on." Both objections are now answered rather than argued with - the On*
   partner is DERIVED alongside the fill and cannot be missing, and the surface is a per-mode fact the
   palette itself holds (Surface in light, Background in dark). ADR 0026 D-H. */

.biztech-chip[b-l77f5whw4r] {
    display: inline-flex;
    align-items: center;
    box-sizing: border-box;
    padding: 2px 8px;
    border: 1px solid currentcolor;
    border-radius: var(--biztech-radius-pill);

    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-caption-size);
    font-weight: var(--biztech-font-caption-weight);
    line-height: var(--biztech-font-caption-line-height);
    letter-spacing: var(--biztech-font-caption-letter-spacing);

    /* R4.7: a chip carries a name or a count read aloud, so it wraps rather than truncating - and
       never shrinks below the 12px floor to fit, which is why the size above is a token. */
    white-space: nowrap;
}

/* `currentcolor` on the border above means an OUTLINED variant sets ONE property and the outline
   follows. A chip whose border and ink could drift apart is two decisions where there is one. */

.biztech-chip-inherit[b-l77f5whw4r] { color: inherit; }
.biztech-chip-default[b-l77f5whw4r] { color: var(--biztech-palette-text-secondary); }
.biztech-chip-secondary[b-l77f5whw4r] { color: var(--biztech-palette-secondary); }
.biztech-chip-tertiary[b-l77f5whw4r] { color: var(--biztech-palette-tertiary); }
.biztech-chip-info[b-l77f5whw4r] { color: var(--biztech-palette-info); }

/* 🔴 W0-74 · ADR 0026 D-H · THE FOUR ROLES WITH A CONTAINER READ AS A SOFT TONAL FILL.

   What makes Material 3 read as Material 3 is not its hues - it is that a status stops being an
   outline and becomes a fill with its own ink. The container roles are DERIVED from the roles above
   (R3.3, BizTechPalette's computed members), so a tenant that overrides Success gets a matching
   container for free and cannot author a failing one.

   THE INK IS SAFE BY CONSTRUCTION, AND THAT IS MEASURED RATHER THAN HOPED. PaletteContrast requires
   every On*Container at 4.5:1 on its own container, in both modes, on the shipped palette AND on
   whatever a deployment or a tenant configured - an AA-failing palette refuses to start. The worst of
   the eight is success at 6.38:1.

   THE BORDER IS THE FILL, NOT currentcolor. currentcolor is now the container's INK, so an inherited
   border would draw a dark ring round a soft pill. Painting the border the fill keeps it one shape,
   and it costs no rule: R3.6 puts 1.4.11's 3:1 on a border that CARRIES MEANING, and a chip is inert
   by design - UiChip declares no OnClick and no OnClose - so what has to be legible here is the label.

   🔴 AND THE SECOND ARGUMENT OF EACH var() IS THE OUTLINE TREATMENT, KEPT. Where no token block has
   been emitted, --biztech-palette-*-container resolves to nothing and each chain falls back to exactly
   what this file declared before this card: transparent ground, currentcolor border, the role as ink.
   PortalStyleSheetTests asserts BOTH halves of every chain, because a dropped fallback looks identical
   in the one context anybody tests.

   NOT secondary, tertiary or info: four roles are what the surfaces use, and R11.4's "add a row rather
   than a synonym" reasoning applies - add the fifth when something needs it. A page may therefore show
   a filled chip beside an outlined one, which is the decision above and not a miss. */

.biztech-chip-primary[b-l77f5whw4r] {
    color: var(--biztech-palette-on-primary-container, var(--biztech-palette-primary));
    background: var(--biztech-palette-primary-container, transparent);
    border-color: var(--biztech-palette-primary-container, currentcolor);
}

.biztech-chip-success[b-l77f5whw4r] {
    color: var(--biztech-palette-on-success-container, var(--biztech-palette-success));
    background: var(--biztech-palette-success-container, transparent);
    border-color: var(--biztech-palette-success-container, currentcolor);
}

.biztech-chip-warning[b-l77f5whw4r] {
    color: var(--biztech-palette-on-warning-container, var(--biztech-palette-warning));
    background: var(--biztech-palette-warning-container, transparent);
    border-color: var(--biztech-palette-warning-container, currentcolor);
}

.biztech-chip-error[b-l77f5whw4r] {
    color: var(--biztech-palette-on-error-container, var(--biztech-palette-error));
    background: var(--biztech-palette-error-container, transparent);
    border-color: var(--biztech-palette-error-container, currentcolor);
}
/* /Components/Ui/UiContainer.razor.rz.scp.css */
/* W0-52 · R5.3. The page's surface.

   🔴 W0-59 · THE BLOCK PADDING MUDMAINCONTENT USED TO SUPPLY. This file declared `padding: 0` and the
   gutters `padding-inline: 24px`, so the header above recorded only the HORIZONTAL half of the
   measurement it was built from - "24px, matching what Studio's own pages render at". The vertical half
   was never taken, because MudBlazor was still providing it and nothing was missing to measure. Six
   screens shipped flush against the app bar.

   On the container rather than on <main>, so it travels with the surface a page already wraps itself
   in: AnonymousLayout uses no UiContainer, and /i/{key}'s own 2rem panel padding is untouched. 24px
   matches the gutters below, so a page's content is inset the same distance on all four sides.

   🔴 AND THE PER-PAGE MARGINS COME OFF IN THE SAME COMMIT. /inquiries, /board and /case-types each
   carried `biztech-my-6` on their header row, which is where their 24px actually came from - measured
   in a browser, and it is why the plan's predicted gap of 0 was only true on /ui, whose header is a
   UiText and whose utility was inert (S4). Both halves at once would read 48px. Those three now carry
   `biztech-mb-6`, bottom only, and the top belongs to this rule on all four pages. R5.10 states it:
   vertical breathing room comes from the container, never from a per-page margin.

   The `padding: 0` and `margin: 0` that were here are gone rather than moved: a <div> has neither from
   the user agent, and a scoped reset outranks the utility layer (R14.1a). */

.biztech-container[b-9n4b5k6no7] {
    box-sizing: border-box;
    width: 100%;
    /* 🔴 W0-63 · FINDING 6. 16px, was 24. R5.3's drawer clearance holds - content starts at
       240 + 16 = 256 - and §5's R5.10 paragraph is rewritten in this same commit. 4 x 4 on the base
       unit. */
    padding-block: 16px;
}

/* 24px, matching what Studio's own pages render at - the measurement R5.3 was written from. */
.biztech-container-gutters[b-9n4b5k6no7] {
    /* W0-63 · finding 6. 16px, was 24, for the reason above. */
    padding-inline: 16px;
}
/* /Components/Ui/UiDialogHost.razor.rz.scp.css */
/* W0-52 · the dialog.

   🔴 NO z-index ANYWHERE IN THIS FILE. A modal <dialog> renders in the browser's TOP LAYER, above
   every stacking context on the page whatever their z-index - so R6.2's "MudBlazor owns 1100-1600,
   do not compete" is satisfied by not entering the argument at all. This is the one overlay in the
   application that cannot be covered by anything, including MudBlazor's own dialog, and it needed no
   layer to get there.

   ::backdrop is the browser's own scrim for a modal dialog, and it reads a TOKEN rather than a
   literal. A scrim is not a colour anything is read against - PaletteContrast has nothing to say
   about it - but the colour sweep in PortalStyleSheetTests cannot tell a scrim from a hand-picked
   brand colour and should not have to, so the value lives in BizTechCssVariables beside the two
   shadows, which are there for exactly the same reason. */

.biztech-dialog[b-8bopc17jwd] {
    box-sizing: border-box;
    margin: auto;
    padding: 0;
    inline-size: min(46rem, calc(100vw - 32px));
    border: 1px solid var(--biztech-palette-outline);
    border-radius: var(--biztech-radius-default);
    background: var(--biztech-palette-surface);
    color: var(--biztech-palette-text-primary);
    box-shadow: var(--biztech-shadow-overlay);
}

.biztech-dialog[b-8bopc17jwd]::backdrop {
    background: var(--biztech-scrim);
}

.biztech-dialog-form[b-8bopc17jwd] {
    display: flex;
    flex-direction: column;
    gap: 16px;
    padding: 24px;
    margin: 0;
}

.biztech-dialog-title[b-8bopc17jwd] {
    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-h6-size);
    font-weight: var(--biztech-font-h6-weight);
    line-height: var(--biztech-font-h6-line-height);
    letter-spacing: var(--biztech-font-h6-letter-spacing);
}

.biztech-dialog-message[b-8bopc17jwd] {
    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-body2-size);
    font-weight: var(--biztech-font-body2-weight);
    line-height: var(--biztech-font-body2-line-height);
    color: var(--biztech-palette-text-primary);
}

.biztech-dialog-actions[b-8bopc17jwd] {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    flex-wrap: wrap;
}

/* The two buttons are plain <button type="submit"> inside the form rather than UiButton, and that is
   deliberate: `method="dialog"` only closes the dialog when the SUBMITTER is a form control of this
   form, and routing that through a component that renders type="button" would break the whole
   no-JavaScript close path. They are styled to match UiButton rather than reusing it. */
.biztech-dialog-cancel[b-8bopc17jwd],
.biztech-dialog-confirm[b-8bopc17jwd] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    min-block-size: 40px;
    padding: 8px 16px;
    border: 1px solid currentcolor;
    border-radius: var(--biztech-radius-default);
    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-button-size);
    font-weight: var(--biztech-font-button-weight);
    line-height: var(--biztech-font-button-line-height);
    letter-spacing: var(--biztech-font-button-letter-spacing);
    text-transform: var(--biztech-font-button-transform);
    cursor: pointer;
    background: transparent;
}

.biztech-dialog-cancel[b-8bopc17jwd] {
    color: var(--biztech-palette-text-primary);
}

/* The destructive one. Error rather than Primary, because R9.13's dialog exists only to confirm
   something destructive and the button should look like what it does. Filled, so it is the one
   emphasised control on the surface. */
.biztech-dialog-confirm[b-8bopc17jwd] {
    background: var(--biztech-palette-error);
    color: var(--biztech-palette-on-error);
    border-color: var(--biztech-palette-error);
}

/* R13.3: below sm the dialog spans the screen it is on, and the actions stack so two verb labels
   are not squeezed onto one 375px row. */
@media (max-width: 599.98px) {
    .biztech-dialog[b-8bopc17jwd] {
        inline-size: calc(100vw - 32px);
    }

    .biztech-dialog-actions[b-8bopc17jwd] {
        flex-direction: column-reverse;
    }

    .biztech-dialog-cancel[b-8bopc17jwd],
    .biztech-dialog-confirm[b-8bopc17jwd] {
        inline-size: 100%;
    }
}
/* /Components/Ui/UiDivider.razor.rz.scp.css */
/* W0-52 · a separator. The user agent gives <hr> a border on all four sides and a block margin.

   🔴 W0-59 · THE BORDER IS STILL DECLARED AWAY HERE; THE MARGIN IS NOT, AND THAT IS THE POINT.
   A scoped `margin: 0` is (0,2,0) once Blazor rewrites it, so it outranked every `biztech-mb-4` the
   gallery passes this component - four dividers on /ui computing `margin: 0px`. The <hr> margin is the
   user agent's, so it is reset once by the document block BizTechCssVariables emits, where a utility
   can still beat it. The border is this component's own look and stays. */

.biztech-divider[b-vo0n8dmj0h] {
    border: 0;
    border-block-start: 1px solid var(--biztech-palette-outline-subtle);
}
/* /Components/Ui/UiGrid.razor.rz.scp.css */
/* W0-52 · twelve columns, and a gap rather than a cancelled margin.

   Only the spacing values the application uses are declared. R5.2 makes Spacing="2" the house value
   and every grid in the Portal sets exactly that; 0 and 4 are here because a grid with no gutter and
   a grid with a generous one are the two a caller reaches for next, and both are on the 4px scale.
   A speculative spacing-7 would be the utility layer's mistake in a different file. */

.biztech-grid[b-m2xkei2ahj] {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    box-sizing: border-box;
}

/* 🔴 W0-78 · THE 3 WAS MISSING, AND FIVE CALL SITES HAD BEEN RENDERING WITH NO GAP AT ALL.
   Found in a browser on /users/{id}/roles: the role picker's bottom edge and the button's top edge
   measured at the same y - 482 and 482 - so the two controls were touching inside a padded panel.
   UiGrid emits `biztech-grid-spacing-{Spacing}` unconditionally, and a class nobody defines renders
   exactly like a class nobody wrote: `gap` stayed `normal`, which on a grid is zero.
   It is the same failure mode I19 exists for, one layer down - nothing errors, nothing warns, and the
   markup reads as though it asked for a gap.
   AccessRulesWriters and ActivateUser (both W0-76) carried it too, so this one line closes all three
   pages. 24px because the scale is 8px steps and 3 sits between 2's 16 and 4's 32 - the value the
   callers have been asking for since the day they were written. */
.biztech-grid-spacing-0[b-m2xkei2ahj] { gap: 0; }
.biztech-grid-spacing-1[b-m2xkei2ahj] { gap: 8px; }
.biztech-grid-spacing-2[b-m2xkei2ahj] { gap: 16px; }
.biztech-grid-spacing-3[b-m2xkei2ahj] { gap: 24px; }
.biztech-grid-spacing-4[b-m2xkei2ahj] { gap: 32px; }
/* /Components/Ui/UiGridItem.razor.rz.scp.css */
/* W0-52 · column spans, twelve per breakpoint.

   Thirty-six one-line rules rather than an inline `grid-column: span N`, because R14.2 forbids an
   inline style for layout and R5.4 forbids a magic length - and a span arriving from a FORM
   DEFINITION (the assignee form's @group.ColumnSpan) is exactly the case where a component cannot
   know the value at authoring time. Twelve of anything is what "a twelve-column grid" means, so
   none of these is speculative in the sense the utility layer's test polices.

   min-inline-size: 0 on the base, so a long unbroken string - a 36-character access key - shrinks
   its column instead of overflowing the row. Grid items default to min-content, which is the single
   most common cause of a grid that scrolls sideways on a phone. */

.biztech-col[b-6o4w5ahxby] {
    box-sizing: border-box;
    min-inline-size: 0;
}

.biztech-col-xs-1[b-6o4w5ahxby] { grid-column: span 1; }
.biztech-col-xs-2[b-6o4w5ahxby] { grid-column: span 2; }
.biztech-col-xs-3[b-6o4w5ahxby] { grid-column: span 3; }
.biztech-col-xs-4[b-6o4w5ahxby] { grid-column: span 4; }
.biztech-col-xs-5[b-6o4w5ahxby] { grid-column: span 5; }
.biztech-col-xs-6[b-6o4w5ahxby] { grid-column: span 6; }
.biztech-col-xs-7[b-6o4w5ahxby] { grid-column: span 7; }
.biztech-col-xs-8[b-6o4w5ahxby] { grid-column: span 8; }
.biztech-col-xs-9[b-6o4w5ahxby] { grid-column: span 9; }
.biztech-col-xs-10[b-6o4w5ahxby] { grid-column: span 10; }
.biztech-col-xs-11[b-6o4w5ahxby] { grid-column: span 11; }
.biztech-col-xs-12[b-6o4w5ahxby] { grid-column: span 12; }

@media (min-width: 600px) {
    .biztech-col-sm-1[b-6o4w5ahxby] { grid-column: span 1; }
    .biztech-col-sm-2[b-6o4w5ahxby] { grid-column: span 2; }
    .biztech-col-sm-3[b-6o4w5ahxby] { grid-column: span 3; }
    .biztech-col-sm-4[b-6o4w5ahxby] { grid-column: span 4; }
    .biztech-col-sm-5[b-6o4w5ahxby] { grid-column: span 5; }
    .biztech-col-sm-6[b-6o4w5ahxby] { grid-column: span 6; }
    .biztech-col-sm-7[b-6o4w5ahxby] { grid-column: span 7; }
    .biztech-col-sm-8[b-6o4w5ahxby] { grid-column: span 8; }
    .biztech-col-sm-9[b-6o4w5ahxby] { grid-column: span 9; }
    .biztech-col-sm-10[b-6o4w5ahxby] { grid-column: span 10; }
    .biztech-col-sm-11[b-6o4w5ahxby] { grid-column: span 11; }
    .biztech-col-sm-12[b-6o4w5ahxby] { grid-column: span 12; }
}

@media (min-width: 960px) {
    .biztech-col-md-1[b-6o4w5ahxby] { grid-column: span 1; }
    .biztech-col-md-2[b-6o4w5ahxby] { grid-column: span 2; }
    .biztech-col-md-3[b-6o4w5ahxby] { grid-column: span 3; }
    .biztech-col-md-4[b-6o4w5ahxby] { grid-column: span 4; }
    .biztech-col-md-5[b-6o4w5ahxby] { grid-column: span 5; }
    .biztech-col-md-6[b-6o4w5ahxby] { grid-column: span 6; }
    .biztech-col-md-7[b-6o4w5ahxby] { grid-column: span 7; }
    .biztech-col-md-8[b-6o4w5ahxby] { grid-column: span 8; }
    .biztech-col-md-9[b-6o4w5ahxby] { grid-column: span 9; }
    .biztech-col-md-10[b-6o4w5ahxby] { grid-column: span 10; }
    .biztech-col-md-11[b-6o4w5ahxby] { grid-column: span 11; }
    .biztech-col-md-12[b-6o4w5ahxby] { grid-column: span 12; }
}
/* /Components/Ui/UiIcon.razor.rz.scp.css */
/* W0-52 · the glyph box.

   Sized in px rather than em, deliberately: R11.3 gives icons two sizes and an em-sized glyph would
   also inherit the type scale, so the same icon would render at four different sizes depending on
   which slot happened to surround it. Both values are on the 4px scale.

   `fill: currentColor` is on the element in markup rather than here, so a glyph is coloured even if
   this sheet fails to load - the one property whose loss would make an icon invisible rather than
   merely misplaced. */

.biztech-icon[b-miov41hmbq] {
    display: inline-block;
    flex: none;
    vertical-align: middle;
    /* The two paths Material ships include a fill="none" spacer sized to the 24-unit viewBox, so the
       glyph is already centred in its own box - nothing here needs to correct for it. */
}

.biztech-icon-small[b-miov41hmbq] {
    inline-size: 20px;
    block-size: 20px;
}

.biztech-icon-medium[b-miov41hmbq] {
    inline-size: 24px;
    block-size: 24px;
}
/* /Components/Ui/UiIconButton.razor.rz.scp.css */
/* W0-52 · an icon-only button.

   Square, so the glyph sits in the middle of a target rather than in the corner of a wide one, and
   round, because a circular hit area is what tells a pointer this is one control rather than part of
   a toolbar strip.

   🔴 THE COLOUR CLASSES ARE DECLARED HERE RATHER THAN REUSED FROM UiText.razor.css. Blazor's CSS
   isolation scopes a sheet to its own component's elements, so `biztech-ink-primary` defined over
   there matches nothing rendered here - it would compile, render, and silently take the inherited
   colour. Every component in this folder declares the classes it names. */

.biztech-icon-button[b-z230jmoezm] {
    display: inline-flex;
    align-items: center;
    justify-content: center;

    box-sizing: border-box;
    border: 1px solid transparent;
    border-radius: var(--biztech-radius-pill);
    background: transparent;
    color: inherit;
    cursor: pointer;

    transition: background-color 120ms ease-in-out;
}

/* 🔴 W6-53 · design §4. THE OUTLINED VARIANT, AND IT IS .mk-btn-icon's THREE DECLARATIONS:

     .mk-btn      { border: 1px solid var(--biztech-palette-outline);
                    border-radius: var(--biztech-radius-default); background: transparent }
     .mk-btn-icon { padding: 0; inline-size: 32px }

   A rounded SQUARE, not a circle - which is the one visible difference from the base rule above, and the
   reason the border is declared here rather than the radius being left alone. The size classes already
   give the box its 32px, so only the border and the corner change.

   Text stays the default and stays a round ghost: every icon button in the application except the
   session toolbar's media pair is one, and §4 draws none of those with a box. */
.biztech-icon-button-outlined[b-z230jmoezm],
.biztech-icon-button-filled[b-z230jmoezm] {
    border-color: var(--biztech-palette-outline);
    border-radius: var(--biztech-radius-default);
}

/* R5.8's 44px touch target is NOT applied here globally, and that is a decision rather than an
   omission: the operator console is exempt by decision (a mouse at a desk), and app.css's
   `@media (pointer: coarse)` block under .biztech-anon is what grows these on /i/{key}, which is
   the surface the rule names. */
.biztech-icon-button-small[b-z230jmoezm] {
    inline-size: 32px;
    block-size: 32px;
    padding: 4px;
}

.biztech-icon-button-medium[b-z230jmoezm] {
    inline-size: 40px;
    block-size: 40px;
    padding: 8px;
}

.biztech-icon-button:hover:not(:disabled)[b-z230jmoezm] {
    background: color-mix(in srgb, currentcolor 12%, transparent);
}

.biztech-icon-button:disabled[b-z230jmoezm] {
    color: var(--biztech-palette-text-disabled);
    cursor: default;
}

/* Inherit is the default: an icon button in the app bar takes the app bar's own ink, which is the
   OnAppbar role its background was tested against. */
.biztech-icon-button-inherit[b-z230jmoezm] { color: inherit; }
.biztech-icon-button-default[b-z230jmoezm] { color: var(--biztech-palette-text-primary); }
.biztech-icon-button-primary[b-z230jmoezm] { color: var(--biztech-palette-primary); }
.biztech-icon-button-secondary[b-z230jmoezm] { color: var(--biztech-palette-secondary); }
.biztech-icon-button-tertiary[b-z230jmoezm] { color: var(--biztech-palette-tertiary); }
.biztech-icon-button-success[b-z230jmoezm] { color: var(--biztech-palette-success); }
.biztech-icon-button-warning[b-z230jmoezm] { color: var(--biztech-palette-warning); }
.biztech-icon-button-error[b-z230jmoezm] { color: var(--biztech-palette-error); }
.biztech-icon-button-info[b-z230jmoezm] { color: var(--biztech-palette-info); }

@media (prefers-reduced-motion: reduce) {
    .biztech-icon-button[b-z230jmoezm] {
        transition: none;
    }
}
/* /Components/Ui/UiLifecycleMap.razor.rz.scp.css */
/*
    W4-22 · F7.2. The lifecycle map's own styles.

    Every colour is a --biztech-palette-* token (R3.1) and every size a --biztech-font-* one. Nothing
    here is a literal: unlike UiQrCode - which is read by a CAMERA and specified by ISO/IEC 18004 -
    this is read by a person, so it takes the tenant's palette like every other surface, and it has to
    survive both modes.
*/

/*
    🔴 R13.7. THE MAP SCROLLS, THE PAGE DOES NOT. A lifecycle is wider than a phone and that is fine;
    what is not fine is the page scrolling sideways, which moves every other control with it.
*/
.biztech-lifecycle-map[b-69ht8nkv2w] {
    overflow-x: auto;
    overflow-y: hidden;
    max-width: 100%;
}

.biztech-lifecycle-map svg[b-69ht8nkv2w] {
    /* Never shrink below its own geometry - a squeezed map overlaps its own labels rather than
       scrolling, and the scroll above is what makes the width honest. */
    min-width: max-content;
    display: block;
}

/*
    🔴 EVERY BORDER IS SOLID, ON EVERY STATE.

    The first build drew stages not yet reached with `stroke-dasharray` and it was the first thing
    called out on sight: a dashed box reads as an UNFINISHED DRAWING, not as "not yet". State is
    carried by fill and colour here, and by the word in the chip - which is the channel R12.7
    actually asks for.
*/
.biztech-lifecycle-card[b-69ht8nkv2w] {
    fill: var(--biztech-palette-surface);
    stroke: var(--biztech-palette-outline);
    stroke-width: 1;
}

/* A stage the case has left: quiet, but finished. */
.biztech-lifecycle-node--done .biztech-lifecycle-card[b-69ht8nkv2w] {
    stroke: var(--biztech-palette-success);
    stroke-width: 1.5;
}

/* Where the case is NOW. The one thing on the map that should catch the eye first. */
.biztech-lifecycle-node--current .biztech-lifecycle-card[b-69ht8nkv2w] {
    stroke: var(--biztech-palette-primary);
    stroke-width: 2.5;
}

/*
    Not reached yet: a muted plate, solid, with muted ink.

    🔴 W4-24 · R3.6, WCAG 1.4.11. THE STROKE WAS --outline-subtle AND THE BOX WAS NOT THERE. Measured
    on /cases/{caseId} rather than eyeballed: 1.25:1 against the paper behind it in light and 1.24:1 in
    dark, where 3:1 is required for a non-text boundary. The label ink was fine at 7.40:1, so the last
    stages read as floating text rather than as stages, and W4-22's own browser pass missed it because
    the reached states carry a coloured stroke and the eye goes to the lit part of the route.

    --outline measures 3.33:1 light and 3.51:1 dark. LifecycleNodeContrastTests computes both from the
    resolved token values rather than restating a hex, so a palette change cannot quietly undo this.

    🔴 THE FILL DOES NOT MOVE. W4-22's ruling stands - a stage not reached is quieter than one that has
    been - so this changes the BOUNDARY, which is what carries the shape, and not the state.
*/
.biztech-lifecycle-node--ahead .biztech-lifecycle-card[b-69ht8nkv2w] {
    fill: var(--biztech-palette-background);
    stroke: var(--biztech-palette-outline);
}

.biztech-lifecycle-node--ahead .biztech-lifecycle-label[b-69ht8nkv2w] {
    fill: var(--biztech-palette-text-secondary);
}

/* The status badge. */
.biztech-lifecycle-chip[b-69ht8nkv2w] {
    fill: var(--biztech-palette-outline-subtle);
}

.biztech-lifecycle-node--done .biztech-lifecycle-chip[b-69ht8nkv2w] {
    fill: var(--biztech-palette-success);
}

.biztech-lifecycle-node--current .biztech-lifecycle-chip[b-69ht8nkv2w] {
    fill: var(--biztech-palette-primary);
}

.biztech-lifecycle-chip-text[b-69ht8nkv2w] {
    fill: var(--biztech-palette-surface);
    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-caption-size);
    font-weight: var(--biztech-font-caption-weight);
    letter-spacing: var(--biztech-font-caption-letter-spacing);
}

/* An "ahead" chip sits on a muted plate rather than a coloured one, so its ink stays readable. */
.biztech-lifecycle-node--ahead .biztech-lifecycle-chip-text[b-69ht8nkv2w] {
    fill: var(--biztech-palette-text-secondary);
}

.biztech-lifecycle-label[b-69ht8nkv2w] {
    fill: var(--biztech-palette-text-primary);
    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-subtitle2-size);
    font-weight: var(--biztech-font-subtitle2-weight);
}

.biztech-lifecycle-footnote[b-69ht8nkv2w],
.biztech-lifecycle-sentence[b-69ht8nkv2w] {
    fill: var(--biztech-palette-text-secondary);
    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-caption-size);
}

.biztech-lifecycle-edge[b-69ht8nkv2w] {
    stroke: var(--biztech-palette-outline);
    stroke-width: 1.5;
}

.biztech-lifecycle-arrowhead[b-69ht8nkv2w] {
    fill: var(--biztech-palette-outline);
}

/* The route this case actually took. */
.biztech-lifecycle-edge--taken[b-69ht8nkv2w] {
    stroke: var(--biztech-palette-primary);
    stroke-width: 2.5;
}

.biztech-lifecycle-arrowhead--taken[b-69ht8nkv2w] {
    fill: var(--biztech-palette-primary);
}

/*
    A return - a revoke sending the case back to an earlier stage. Dashed HERE and only here, because
    on a line the dash means "this is not the main flow", which is true. On a node it meant "this
    drawing is unfinished", which was not.
*/
.biztech-lifecycle-edge--return[b-69ht8nkv2w] {
    stroke: var(--biztech-palette-outline-subtle);
    stroke-dasharray: 5 4;
}

/*
    W13-10. An interrupt that can fire from ANY stage - archiving. Dashed for the reason a return is,
    and with a finer dash so the two read as different kinds of "not the main flow" rather than as the
    same one.

    It is anchored at the first stage because a line has to start somewhere and LifecycleLayout draws
    no edge whose ends are not both nodes. The dash and the route list's "from any stage" carry the
    meaning; the anchor is only where the line starts.
*/
.biztech-lifecycle-edge--anystage[b-69ht8nkv2w] {
    stroke: var(--biztech-palette-outline-subtle);
    stroke-dasharray: 2 5;
}

/*
    🔴 R12.2. The route as sentences in document order, for a reader who cannot navigate an SVG of
    boxes and arrows however well each shape is labelled.

    Visually hidden with a scoped class rather than a shared utility, because app.css has none -
    TenantChrome's header states that, and DeckViewer.razor.css carries its own copy for the same
    reason. No colour and no radius token here, so PortalStyleSheetTests' sweep has nothing to find.
*/
.biztech-lifecycle-route[b-69ht8nkv2w] {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}
/* /Components/Ui/UiMenu.razor.rz.scp.css */
/* W0-52 · the disclosure panel.

   Anchored by ordinary positioning - absolute inside a relative wrapper - rather than by the top
   layer. That is a real trade and worth naming: a top-layer popover would never be clipped by an
   ancestor's overflow, but CSS anchor positioning is not baseline across R13.6's four browsers, so a
   top-layer panel would need JavaScript to follow its trigger. The one menu in this application sits
   in the app bar, which clips nothing.

   The layer is `affordance` from app.css's scale (R6.1: never a raw z-index). Inside the app bar's
   own stacking context that is enough to sit above the bar's content, and it does not enter
   MudBlazor's 1100-1600 band (R6.2). */

.biztech-menu[b-fa8bh3chd0] {
    position: relative;
    display: inline-flex;
}

.biztech-menu-trigger[b-fa8bh3chd0] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    inline-size: 32px;
    block-size: 32px;
    padding: 4px;
    border: 1px solid transparent;
    border-radius: var(--biztech-radius-pill);
    background: transparent;
    color: inherit;
    cursor: pointer;
}

.biztech-menu-trigger:hover:not(:disabled)[b-fa8bh3chd0] {
    background: color-mix(in srgb, currentcolor 12%, transparent);
}

.biztech-menu-trigger:disabled[b-fa8bh3chd0] {
    color: var(--biztech-palette-text-disabled);
    cursor: default;
}

/* Covers the viewport so a click anywhere else dismisses. Transparent, and BEHIND the panel. */
.biztech-menu-scrim[b-fa8bh3chd0] {
    position: fixed;
    inset: 0;
    z-index: var(--biztech-layer-scrim);
    background: transparent;
}

.biztech-menu-panel[b-fa8bh3chd0] {
    position: absolute;
    inset-block-start: calc(100% + 4px);
    inset-inline-end: 0;
    z-index: var(--biztech-layer-affordance);

    box-sizing: border-box;
    min-inline-size: 12rem;
    padding: 4px;
    border: 1px solid var(--biztech-palette-outline);
    border-radius: var(--biztech-radius-default);
    background: var(--biztech-palette-surface);
    color: var(--biztech-palette-text-primary);
    box-shadow: var(--biztech-shadow-raised);
}

/* 🔴 W0-73 · THE LABELLED TRIGGER, AS AN ADDITION RATHER THAN AN EDIT.

   Every rule above is untouched, so the three icon-only triggers compute exactly what they computed
   before this card - which is the half UiOverlayRenderTests measures on that branch.

   THE HEIGHT COMES OFF THE TOKEN, unlike the 32px literal above it, and that is deliberate: this
   trigger sits IN a row of controls, so it must be the same box as the buttons beside it - 32px
   inside .biztech-shell where W0-63 narrows --biztech-control-height, and 40px everywhere else.
   The icon-only trigger keeps its literal because it lives in the app bar, which is not such a row.

   A radius of `default` rather than `pill`: a pill around a word reads as a chip, and in this
   application a chip is a filter that has already been applied (§7's specimen draws both, one above
   the other). */
.biztech-menu-trigger-labelled[b-fa8bh3chd0] {
    inline-size: auto;
    block-size: var(--biztech-control-height);
    gap: 8px;
    padding-inline: 12px;
    border-radius: var(--biztech-radius-default);
}

/* The word must not break, or a narrow toolbar wraps "More" onto two lines inside its own button. */
.biztech-menu-trigger-label[b-fa8bh3chd0] {
    white-space: nowrap;
}
/* /Components/Ui/UiMenuItem.razor.rz.scp.css */
/* W0-52 · one row of the disclosure panel. Full width, left-packed, and the label and its glyph stay
   together (R5.9: a label and its value stay closer to each other than to the next pair). */

.biztech-menu-item[b-fagn8uwofg] {
    display: flex;
    align-items: center;
    gap: 12px;

    box-sizing: border-box;
    inline-size: 100%;
    min-block-size: 40px;
    padding: 8px 12px;
    border: 0;
    border-radius: var(--biztech-radius-default);
    background: transparent;
    color: inherit;
    cursor: pointer;

    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-body2-size);
    font-weight: var(--biztech-font-body2-weight);
    line-height: var(--biztech-font-body2-line-height);
    letter-spacing: var(--biztech-font-body2-letter-spacing);
    text-align: start;

    /* 🔴 W0b-03 · the anchor form. A user agent underlines a link, and the button form never was -
       so one underlined row among plain ones reads as a defect rather than as a link. Harmless on the
       button, which has no default decoration to remove. */
    text-decoration: none;
}

.biztech-menu-item:hover:not(:disabled)[b-fagn8uwofg] {
    background: color-mix(in srgb, currentcolor 8%, transparent);
}

.biztech-menu-item:disabled[b-fagn8uwofg] {
    color: var(--biztech-palette-text-disabled);
    cursor: default;
}

.biztech-menu-item-label[b-fagn8uwofg] {
    flex: 1 1 auto;
    min-inline-size: 0;
}
/* /Components/Ui/UiPaper.razor.rz.scp.css */
/* W0-52 · the flat surface. Border rather than shadow: R3.6 puts the 3:1 on the border, R6.6 says a
   border does on dark what a shadow does on light, and Outline is the role held to 3:1 against BOTH
   Surface and Background (R3.7). Reaching for a shadow here would be styling against roles nobody
   tested.

   1px per R5.6. The radius is read from the token, never restated (R5.5). */

.biztech-paper[b-5t289n8rsf] {
    box-sizing: border-box;
    background: var(--biztech-palette-surface);
    border: 1px solid var(--biztech-palette-outline);
    border-radius: var(--biztech-radius-default);
    color: var(--biztech-palette-text-primary);
}
/* /Components/Ui/UiProgressLinear.razor.rz.scp.css */
/* W0-52 · an indeterminate bar.

   🔴 R6.7 ALLOWS transform AND NOTHING THAT LAYS OUT, which is why the bar slides with translateX
   rather than animating `left` or `width`. A width animation runs on the main thread and reflows
   sixty times a second behind whatever is loading - on the one frame the operator is waiting on.

   R6.8: guarded in CSS. There is no JS half here to guard - the animation is entirely declarative,
   which is also why it keeps running if the circuit stalls, and that is correct: a bar that froze
   the moment the server stopped answering would look like the page had finished. */

.biztech-progress[b-sswtwrweo2] {
    position: relative;
    overflow: hidden;
    box-sizing: border-box;
    inline-size: 100%;
    block-size: 4px;
    border-radius: var(--biztech-radius-pill);
    background: color-mix(in srgb, var(--biztech-palette-primary) 16%, transparent);
}

.biztech-progress-bar[b-sswtwrweo2] {
    position: absolute;
    inset-block: 0;
    inline-size: 40%;
    border-radius: var(--biztech-radius-pill);
    background: var(--biztech-palette-primary);
    animation: biztech-progress-slide-b-sswtwrweo2 1200ms ease-in-out infinite;
}

@keyframes biztech-progress-slide-b-sswtwrweo2 {
    from { transform: translateX(-100%); }
    to { transform: translateX(350%); }
}

@media (prefers-reduced-motion: reduce) {
    .biztech-progress-bar[b-sswtwrweo2] {
        /* Still visibly a progress indicator, and still says "waiting" - it simply stops moving.
           WCAG 2.3.3, and the reason this is not `display: none`: removing the only sign that
           something is in flight is a worse answer than removing the motion. */
        animation: none;
        inline-size: 100%;
        opacity: .6;
    }
}
/* /Components/Ui/UiQrCode.razor.rz.scp.css */
/* W6-18 · the symbol's box.

   NO COLOUR IN HERE, AND THAT IS THE POINT. The plate and the ink are attributes in the markup, for
   the reason the component's header gives at length: a QR code is read by a camera and has to be
   dark-on-light in both modes. This sheet is swept by PortalStyleSheetTests like every other scoped
   sheet, and it has nothing for that sweep to find.

   9rem is 144px - a whole multiple of the 4px base unit, so R5.4 is satisfied without a named layout
   constant. At the 45-module extent a participant link encodes to, that is 3.2 CSS px per module,
   which scans at arm's length. Below about 2px per module a phone camera starts guessing.

   NO border-radius. Rounded corners would clip the finder patterns, which are the three things a
   scanner locates first - so there is nothing here for R5.5's radius rule to read a variable for. */

.biztech-qr[b-o6bdmbd8c9] {
    display: block;
    inline-size: 9rem;
    /* The viewBox is square, so height follows width and nothing has to restate the ratio (R11.6's
       "constrain ONE dimension - the viewBox carries the ratio", applied to something that is not a
       logo). */
    block-size: auto;
}
/* /Components/Ui/UiSkeleton.razor.rz.scp.css */
/* W0-52 · the placeholder.

   The two heights are the two the application already used - 48px for a row or a field, 120px for a
   card - and they live here rather than at nine call sites, which is R5.4's point. Both are on the
   4px scale, so neither needs a row in the layout-constant registry.

   Still, not shimmering. R12.8 and R6.8 both bear on it, and a shimmer is motion nobody asked for on
   the frame a person is already waiting through; the surface tint alone says "not yet". R3.12 leaves
   skeleton styling to the library, and this is the library now. */

.biztech-skeleton[b-0qqvlms747] {
    box-sizing: border-box;
    inline-size: 100%;
    border-radius: var(--biztech-radius-default);
    background: color-mix(in srgb, var(--biztech-palette-text-primary) 8%, transparent);
}

.biztech-skeleton-row[b-0qqvlms747] {
    block-size: 48px;
}

.biztech-skeleton-block[b-0qqvlms747] {
    block-size: 120px;
}
/* /Components/Ui/UiSnackbarHost.razor.rz.scp.css */
/* W0-52 · where snackbars sit.

   🔴 NO z-index, AND THAT IS DELIBERATE. R6.2: "MudBlazor owns 1100-1600. Do not compete." While
   both substrates are linked - which is until W0-53 - anything this host declared would either lose
   to MudBlazor's own snackbar layer or enter a band R6.2 forbids. `position: fixed` with no z-index
   puts it in the base stacking context, above ordinary in-flow content and below MudBlazor's
   overlays, which is the correct answer for a transitional card: a message that lands under a dialog
   is a message the operator can still read once the dialog closes, and a rule that entered the band
   would be a rule W0-53 has to unpick.

   The dialog does not have this problem - it is a native <dialog> in the top layer, above every
   z-index there is - which is why only this file has to say anything about layering at all. */

.biztech-snackbar-host[b-v52iwfuido] {
    position: fixed;
    inset-block-end: 16px;
    /* 🔴 W0-68 · THE LEADING EDGE IS THE CONTENT'S, NOT THE VIEWPORT'S.

       This read `inset-inline-start: 16px`, which is 16px from the VIEWPORT - and the navigation rail
       occupies the leading 240px of it, so on a desktop viewport with the drawer open every message lost
       its first words behind the rail. Observed 2026-09-02: five stacked messages each reading
       `"e case type configured, so there is nothing to open. An administrator …ings."` A refusal the
       operator cannot read is worse than no refusal, because it reads as malformed rather than obscured.

       The mobile block at the end of this file was already the precedent - "a 46rem message box on a
       375px screen is a message box off the side of the screen" - and nobody applied that reasoning to a
       46rem box that STARTS underneath a 240px rail.

       --biztech-content-inline-start is declared on .biztech-shell in MainLayout.razor.css, from the same
       declaration that sizes the drawer, and a :has() rule there moves it when the drawer opens and
       closes. So this cannot go under the rail by construction rather than by a number that happens to
       clear it at one drawer width.

       THE FALLBACK IS WHAT KEEPS THE OTHER TWO LAYOUTS AT TODAY'S PIXELS. AnonymousLayout and
       SignInLayout mount this host and render no drawer, so nothing declares the property there and 16px
       - the gutter, with no rail to clear - is the right answer. No rule in this change names them.

       🔴 AND THE TRAILING EDGE STAYS UNPINNED, which this card learned the hard way. A first draft added
       `inset-inline-end: 16px` so a long message would not run flush to the viewport's trailing edge -
       which the card never asked for, and which CSS 2.1 §10.3.7 makes a different BOX rather than a
       nicety. With the leading inset alone and `inline-size: auto` the used width is SHRINK-TO-FIT,
       clamped by the max-inline-size below; with both insets it is `containing block - start - end`, so
       46rem stops being a ceiling and becomes the width. The messages are stretch-aligned flex items, so
       every box followed: `Add("Saved.")` measured about 140px before and 736px after - on /i/{key},
       /login and /register too, the three surfaces this change promised not to touch. The flush trailing
       edge is older than this card (a long message reached the edge from 16px before it did from 256px)
       and belongs to whoever is asked for it. */
    inset-inline-start: var(--biztech-content-inline-start, 16px);

    display: flex;
    flex-direction: column;
    gap: 8px;

    /* The host is always mounted and usually empty, so it must not swallow clicks on what is behind
       it. The messages themselves take pointer events back. */
    pointer-events: none;
    max-inline-size: 46rem;
}

.biztech-snackbar-region[b-v52iwfuido] {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.biztech-snackbar[b-v52iwfuido] {
    display: flex;
    align-items: center;
    gap: 12px;

    box-sizing: border-box;
    padding: 12px 16px;
    border: 1px solid currentcolor;
    border-radius: var(--biztech-radius-default);
    background: var(--biztech-palette-surface);
    box-shadow: var(--biztech-shadow-overlay);
    pointer-events: auto;

    font-family: var(--biztech-font-family);
    font-size: var(--biztech-font-body2-size);
    line-height: var(--biztech-font-body2-line-height);
}

.biztech-snackbar-info[b-v52iwfuido] { color: var(--biztech-palette-info); }
.biztech-snackbar-success[b-v52iwfuido] { color: var(--biztech-palette-success); }
.biztech-snackbar-warning[b-v52iwfuido] { color: var(--biztech-palette-warning); }
.biztech-snackbar-error[b-v52iwfuido] { color: var(--biztech-palette-error); }

/* 🔴 W0-68 · what a region says about the messages the cap dropped.

   Secondary ink and no glyph, because it reports on the STACK rather than on an action: a severity
   colour would claim it was an outcome, and §9's four severities are all outcomes. It keeps
   .biztech-snackbar's own box so the report reads as part of the stack it is about rather than as
   floating text beside it. */
.biztech-snackbar-hidden[b-v52iwfuido] {
    color: var(--biztech-palette-text-secondary);
}

/* 🔴 AND ON THE TEXT ITSELF, BECAUSE THE RULE ABOVE CANNOT REACH IT.

   `.biztech-snackbar-text` below carries a direct `color: var(--biztech-palette-text-primary)` on the
   very <span> that holds the sentence, and a direct declaration beats an inherited one always - so the
   wrapper's colour reached only `currentcolor`, which here is just the border. The notice's words
   rendered in the same ink as the messages it sits above, which is the opposite of the intent. */
.biztech-snackbar-hidden .biztech-snackbar-text[b-v52iwfuido] {
    color: var(--biztech-palette-text-secondary);
}

.biztech-snackbar-text[b-v52iwfuido] {
    flex: 1 1 auto;
    min-inline-size: 0;
    color: var(--biztech-palette-text-primary);
}

/* R13.3. On a phone the host spans the width rather than sitting in a corner, because a 46rem
   message box on a 375px screen is a message box off the side of the screen. */
@media (max-width: 599.98px) {
    .biztech-snackbar-host[b-v52iwfuido] {
        inset-inline: 16px;
        max-inline-size: none;
    }
}
/* /Components/Ui/UiSpacer.razor.rz.scp.css */
/* W0-52 · takes whatever room is left in a flex row. `flex-grow` only: it must never claim a basis
   of its own, or it would push a control off the end of a narrow app bar rather than collapsing. */

.biztech-spacer[b-0g47ty6fwz] {
    flex: 1 1 auto;
    min-inline-size: 0;
}
/* /Components/Ui/UiTabs.razor.rz.scp.css */
/* W0-73 · the tab row. Design §6 and §7's `.mk-tabs`, with the specimen's colours read as roles.

   🔴 ::deep ON EVERY ENTRY RULE, AND IT IS NOT OPTIONAL.

   UiTab renders a Blazor NavLink, and Blazor stamps its scope attribute only on elements a component
   renders ITSELF. A bare `.biztech-tab` compiles to `.biztech-tab[b-xxxxxxxxxx]` and matches NOTHING
   here - not "matches less", nothing at all, because every entry is two components deep. ::deep
   compiles to `[b-xxxxxxxxxx] .biztech-tab`, a descendant selector rooted at the <nav> this component
   DOES render, which reaches the anchors and still cannot escape this subtree.

   MainNavMenu.razor.css carries the identical fix and records finding it on screen rather than in a
   test: three NavLinks reporting `min-block-size: 0px` and `text-decoration: underline` - default
   browser links - while the one plain <a> beside them was styled correctly.

   🔴 NO PADDING AND NO MARGIN ON THE ROOT, DELIBERATELY.

   The specimen's `padding: 12px 16px 0` is the PAGE's inset around this row, not the row's own box -
   W4-23 and W4-13 supply it with spacing utilities. Declaring it here would silently outrank every
   `biztech-p*` and `biztech-m*` a caller passes, because Blazor's rewrite puts a scoped rule at (0,2,0)
   against a utility class's (0,1,0). That is R14.1a's whole subject and the defect that had nine
   utilities on /ui computing zero. */

.biztech-tabs[b-w0b1sddi9v] {
    display: flex;

    /* flex-end, so a taller trailing control does not drag the entries off the hairline. */
    align-items: flex-end;
    gap: 8px;
    border-block-end: 1px solid var(--biztech-palette-outline-subtle);
}

/* The scrolling half.

   `min-inline-size: 0` is what lets a flex item shrink below its content and actually scroll. Without
   it this list grows to fit its entries, the <nav> grows with it, and the LANDMARK overflows instead -
   which is R13.8's measured defect one level down, and invisible because `.biztech-main` clips. */
.biztech-tabs-list[b-w0b1sddi9v] {
    display: flex;
    gap: 4px;
    min-inline-size: 0;
    overflow-x: auto;

    /* Hidden, per the specimen. See UiTabs.razor's header for why R12.2's labelled-region
       prescription has no subject here: every entry is a focusable link, so the keyboard reaches all
       of them, and the row only overflows below sm where the input is a finger. */
    scrollbar-width: none;
}

.biztech-tabs-list[b-w0b1sddi9v]::-webkit-scrollbar {
    display: none;
}

/* Stays put while the entries scroll under it. */
.biztech-tabs-trailing[b-w0b1sddi9v] {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-inline-start: auto;
    padding-block-end: 8px;
}

[b-w0b1sddi9v] .biztech-tab {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;

    /* 🔴 SITS ON THE ROW'S HAIRLINE RATHER THAN ABOVE IT, so the current entry's 2px REPLACES the 1px
       instead of stacking into 3px. R5.6 fixes every border at 1px and this is the one place a second
       weight is the signal rather than decoration - which is why the negative margin is a real value
       and not a reset (R14.1a: what is forbidden is ZEROING a box-model property the utility layer
       owns, and no utility sets a negative block-end margin).

       🔴 AND -1px IS NOT ON PortalStyleSheetTests' EXEMPTION LIST - IT IS INVISIBLE TO THE SWEEP.
       That test exempts `0px`, `1px` and `2px` by name, which is what covers the hairline above and the
       2px underline below. This value passes for a different reason: the sweep's pattern requires no
       word character or hyphen before the digits, so a leading minus makes it match nothing at all.
       One device pixel by construction, and it is the negative of the border it cancels - but nothing
       would stop `-13px` here either, so treat this line as unguarded. */
    border-block-end: 2px solid transparent;
    margin-block-end: -1px;

    color: var(--biztech-palette-text-secondary);
    text-decoration: none;

    /* Never wrap. A two-line entry is what the row scrolls in order to avoid. */
    white-space: nowrap;
}

[b-w0b1sddi9v] .biztech-tab:hover {
    color: var(--biztech-palette-text-primary);
    background: color-mix(in srgb, currentcolor 8%, transparent);
}

/* 🔴 THE CURRENT ENTRY, KEYED ON aria-current RATHER THAN ON NavLink's OWN `active` CLASS.

   The attribute is the accessibility contract; the class is the framework's implementation detail.
   Keying the underline on the attribute means what a screen reader announces and what an eye sees can
   never disagree - and if NavLink ever renamed its class, this row would still be correct.

   R12.7 - never colour alone - is satisfied twice over: the underline and the weight each read with no
   colour perception at all. */
[b-w0b1sddi9v] .biztech-tab[aria-current="page"] {
    color: var(--biztech-palette-text-primary);
    font-weight: 600;
    border-block-end-color: var(--biztech-palette-primary);
}

/* The token, not a literal, so the count is the same size as every other caption in the application
   and moves with the scale rather than beside it - `.biztech-text-caption` reads the same property. */
[b-w0b1sddi9v] .biztech-tab-count {
    color: var(--biztech-palette-text-secondary);
    font-size: var(--biztech-font-caption-size);
}
/* /Components/Ui/UiText.razor.rz.scp.css */
/* W0-52 · the type scale, spent.

   🔴 W0-59 · THE BOX-MODEL RESET THAT USED TO BE HERE IS GONE, AND ITS ABSENCE IS THE FIX.

   W0-52 wrote: "EVERY BOX-MODEL PROPERTY IS DECLARED, NOT INHERITED. MudBlazor.min.css ships a global
   `*` reset - margin: 0, padding: 0, border-width: 0 - and it is still linked on this document until
   W0-53. A component that leans on it looks correct today and moves the day the <link> goes: a <p>
   would gain the user agent's 1em block margins and an <h5> its own font-size and weight, on every
   screen at once. So this sheet sets what it depends on."

   The DIAGNOSIS was right and the LOCATION was wrong, and W0-59 measured what the location cost.
   Blazor rewrites a scoped selector as `.biztech-text[b-xxxxxxxxxx]`, which is (0,2,0) against a
   utility class's (0,1,0) - so `margin: 0` here outranked every `biztech-mb-*` and `biztech-my-*` a
   page passed this component. Measured on /ui, 2026-08-13: the gallery's own h1 carries
   `biztech-my-6` and computed `margin-top: 0px`, which is why that page's title sat flush against the
   app bar while three console pages (whose header row is a plain <div>) had their 24px.

   A <p>'s and an <h1>'s block margins really are the user agent's, so the reset is still needed - it
   just belongs to the DOCUMENT rather than to this component. BizTechCssVariables emits it once, for
   h1-h6, p, hr and the four form controls, and UtilitySpecificityTests fails the build if it comes
   back here. The font-family below stays: it is this component's own decision, not a reset. */

.biztech-text[b-36huect2gx] {
    font-family: var(--biztech-font-family);
}

/* The slots. Each reads its four facets together, which is R4.3 as CSS: a caller cannot take the
   size from one slot and the line-height from another, because there is nothing here to take. */

.biztech-text-body1[b-36huect2gx] {
    font-size: var(--biztech-font-body1-size);
    font-weight: var(--biztech-font-body1-weight);
    line-height: var(--biztech-font-body1-line-height);
    letter-spacing: var(--biztech-font-body1-letter-spacing);
}

.biztech-text-body2[b-36huect2gx] {
    font-size: var(--biztech-font-body2-size);
    font-weight: var(--biztech-font-body2-weight);
    line-height: var(--biztech-font-body2-line-height);
    letter-spacing: var(--biztech-font-body2-letter-spacing);
}

.biztech-text-h1[b-36huect2gx] {
    font-size: var(--biztech-font-h1-size);
    font-weight: var(--biztech-font-h1-weight);
    line-height: var(--biztech-font-h1-line-height);
    letter-spacing: var(--biztech-font-h1-letter-spacing);
}

.biztech-text-h2[b-36huect2gx] {
    font-size: var(--biztech-font-h2-size);
    font-weight: var(--biztech-font-h2-weight);
    line-height: var(--biztech-font-h2-line-height);
    letter-spacing: var(--biztech-font-h2-letter-spacing);
}

.biztech-text-h3[b-36huect2gx] {
    font-size: var(--biztech-font-h3-size);
    font-weight: var(--biztech-font-h3-weight);
    line-height: var(--biztech-font-h3-line-height);
    letter-spacing: var(--biztech-font-h3-letter-spacing);
}

.biztech-text-h4[b-36huect2gx] {
    font-size: var(--biztech-font-h4-size);
    font-weight: var(--biztech-font-h4-weight);
    line-height: var(--biztech-font-h4-line-height);
    letter-spacing: var(--biztech-font-h4-letter-spacing);
}

.biztech-text-h5[b-36huect2gx] {
    font-size: var(--biztech-font-h5-size);
    font-weight: var(--biztech-font-h5-weight);
    line-height: var(--biztech-font-h5-line-height);
    letter-spacing: var(--biztech-font-h5-letter-spacing);
}

.biztech-text-h6[b-36huect2gx] {
    font-size: var(--biztech-font-h6-size);
    font-weight: var(--biztech-font-h6-weight);
    line-height: var(--biztech-font-h6-line-height);
    letter-spacing: var(--biztech-font-h6-letter-spacing);
}

.biztech-text-subtitle1[b-36huect2gx] {
    font-size: var(--biztech-font-subtitle1-size);
    font-weight: var(--biztech-font-subtitle1-weight);
    line-height: var(--biztech-font-subtitle1-line-height);
    letter-spacing: var(--biztech-font-subtitle1-letter-spacing);
}

.biztech-text-subtitle2[b-36huect2gx] {
    font-size: var(--biztech-font-subtitle2-size);
    font-weight: var(--biztech-font-subtitle2-weight);
    line-height: var(--biztech-font-subtitle2-line-height);
    letter-spacing: var(--biztech-font-subtitle2-letter-spacing);
}

/* The button slot, and the one place W0-52's decision D1 is visible in CSS: the transform token is
   emitted as `none` where MudTheme still says `uppercase`. Read from the token rather than dropped,
   so the divergence lives in one place - BizTechCssVariables - and a test asserts it is the only
   one. */
.biztech-text-button[b-36huect2gx] {
    font-size: var(--biztech-font-button-size);
    font-weight: var(--biztech-font-button-weight);
    line-height: var(--biztech-font-button-line-height);
    letter-spacing: var(--biztech-font-button-letter-spacing);
    text-transform: var(--biztech-font-button-transform);
}

.biztech-text-caption[b-36huect2gx] {
    font-size: var(--biztech-font-caption-size);
    font-weight: var(--biztech-font-caption-weight);
    line-height: var(--biztech-font-caption-line-height);
    letter-spacing: var(--biztech-font-caption-letter-spacing);
}

.biztech-text-overline[b-36huect2gx] {
    font-size: var(--biztech-font-overline-size);
    font-weight: var(--biztech-font-overline-weight);
    line-height: var(--biztech-font-overline-line-height);
    letter-spacing: var(--biztech-font-overline-letter-spacing);
    text-transform: var(--biztech-font-overline-transform);
}

/* The ink. `inherit` declares nothing at all - it is the default and the point is that the caller
   did not choose - and every other role reads a token, so it follows the palette and dark mode
   without this file knowing which mode is on (R7.1). */

.biztech-ink-default[b-36huect2gx] { color: var(--biztech-palette-text-primary); }
.biztech-ink-primary[b-36huect2gx] { color: var(--biztech-palette-primary); }
.biztech-ink-secondary[b-36huect2gx] { color: var(--biztech-palette-secondary); }
.biztech-ink-tertiary[b-36huect2gx] { color: var(--biztech-palette-tertiary); }
.biztech-ink-success[b-36huect2gx] { color: var(--biztech-palette-success); }
.biztech-ink-warning[b-36huect2gx] { color: var(--biztech-palette-warning); }
.biztech-ink-error[b-36huect2gx] { color: var(--biztech-palette-error); }
.biztech-ink-info[b-36huect2gx] { color: var(--biztech-palette-info); }
/* /Components/Ui/UiToolbar.razor.rz.scp.css */
/* W0-73 · the toolbar. Design §7's `.mk-toolbar`, with the specimen's colours read as roles.

   🔴 NOTHING IN THIS FILE SIZES THE ROW IN THE BLOCK AXIS, and UiSeamTests asserts that by name -
   neither spelling appears in a declaration. The children take their box from the control-height
   token, which MainLayout.razor.css narrows for the console and BizTechCssVariables leaves alone
   everywhere else; a second declaration here would fork one decision into two.

   That constraint is also why the separator below uses `align-self: stretch` where design §7's
   `.mk-toolbar-sep` writes a fixed 20px: stretching makes the rule exactly as tall as whatever row it
   is in, at either control height, with no number in this file to keep in step. */

.biztech-toolbar[b-v7m0m5mdiv] {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;

    /* Lets the row shrink below its content so `flex-wrap` has something to do. Without it a long row
       grows and `.biztech-main` clips it - R13.8's defect, where the overflow is invisible because the
       clipping is what hides the clipping. */
    min-inline-size: 0;

    padding: 8px;
    border: 1px solid var(--biztech-palette-outline-subtle);
    border-radius: var(--biztech-radius-default);
    background: var(--biztech-palette-surface);
}

/* 🔴 THE VERTICAL SEPARATOR - D4, AND NO NEW COMPONENT FOR IT.

   A call site writes <UiDivider />, whose own sheet declares `border-block-start`. This turns that
   rule onto the inline axis instead. ::deep because the <hr> belongs to UiDivider, and Blazor stamps
   this file's scope attribute only on elements THIS component renders - the same rule that gives
   UiTabs its ::deep and MainNavMenu its own.

   🔴 A CHILD SELECTOR, NOT A DESCENDANT ONE, AND THE DIFFERENCE IS A VISIBLE DEFECT.
   `::deep .biztech-divider` compiles to `[b-xxxxxxxxxx] .biztech-divider` and reaches ANY divider
   under any element this component renders - which includes the More menu's panel, because that is
   rendered inside .biztech-toolbar-overflow. A caller putting a <UiDivider /> between two UiMenuItems
   in the Overflow slot got a 1px vertical sliver hard against the panel's left edge with no top
   border, instead of a horizontal rule between two items. `::deep > ` restricts it to the row's own
   direct children, which is what the sentence above always meant.

   🔴 AND IT WINS OVER UiDivider's OWN RULE ONLY ON SOURCE ORDER, WHICH IS WORTH KNOWING
   BEFORE RENAMING EITHER FILE. `[b-toolbar] > .biztech-divider` and `.biztech-divider[b-divider]` are
   both (0,2,0) - an attribute selector and a class each count once - so the tie is broken by position
   in the generated bundle, and that bundle concatenates the scoped sheets in filename order.
   UiDivider sorts before UiToolbar, so this rule is later and wins. Renaming either sheet so the
   order inverts would silently return the separator to a horizontal rule with no border, with nothing
   failing.

   Zeroing `border-block-start` is not R14.1a's forbidden reset: what that rule forbids is zeroing a
   box-model property THE UTILITY LAYER OWNS - margin and padding - and no utility in app.css sets a
   border.

   🔴 inline-size: 0, NOT 1px. There is no global `box-sizing: border-box` in this
   application - app.css sets it on #blazor-error-ui, on .biztech-input and inside one media block, and
   the seam's components declare it individually - so `inline-size` here is CONTENT width and 1px would
   have produced a 2px element holding a 1px rule, with a dead pixel beside it. Zero content width plus
   the border is the hairline.

   `align-self: stretch` rather than a height is sound only because BizTechCssVariables resets
   `hr { margin: 0 }` in the document block; without that the user agent's `margin-block: 0.5em` would
   inset this rule 8px at each end. */
[b-v7m0m5mdiv] > .biztech-divider {
    align-self: stretch;
    inline-size: 0;
    border-block-start: 0;
    border-inline-start: 1px solid var(--biztech-palette-outline-subtle);
}

/* 🔴 THE COLLAPSE - D2, AND THE WRAPPER EXISTS ONLY TO BE SWITCHED OFF.

   `display: contents` makes the inline wrapper vanish from layout, so the controls inside it stay
   direct flex children of the row and wrap and gap with everything else. A `display: block` wrapper
   would turn them into one unbreakable block that wraps as a unit.

   Both copies are in the DOM and exactly one is displayed. `display: none` removes the other from the
   accessibility tree as well as from the screen, which is what makes rendering the slot twice honest
   rather than a duplicate announcement - app.css's breadcrumb block states the same reason at the same
   breakpoint.

   599.98px, which is how R13.1 says this application writes MudBlazor's `sm`. */
.biztech-toolbar-inline[b-v7m0m5mdiv] {
    display: contents;
}

/* 🔴 margin-inline-start: auto IS A REACHABILITY FIX, NOT AN ALIGNMENT PREFERENCE.

   UiMenu anchors its panel `position: absolute; inset-inline-end: 0` at `min-inline-size: 12rem`, and
   its own sheet justifies not using the top layer with: "The one menu in this application sits in the
   app bar, which clips nothing." THIS is the call site that invalidates that sentence - the toolbar
   sits inside `.biztech-main`, which declares `overflow-x: hidden`.

   A right-anchored 192px panel is safe exactly when the trigger's right edge has 192px of room to its
   left. Measured on /ui at 375px: trigger 266..350, panel 158..350, 158px of headroom - safe, but only
   because the trigger shared a line with other controls. Wrap one more control in and the overflow is
   alone on the last line, where `justify-content: flex-start` puts it at x=24 - and the panel would
   start near -84px, clipped and reachable by nothing.

   The auto margin pins the trigger's right edge to the row's right edge on whatever line it lands on,
   which makes the headroom the row's own width rather than an accident of wrapping.

   🔴 AND NO TEST COVERS IT, WHICH IS STATED HERE RATHER THAN LEFT TO BE DISCOVERED.
   A test was written and then deleted for being vacuous: on /ui's specimen the trigger already sits at
   the row's right edge, so the assertion passed with this declaration REMOVED - proven by deleting it
   and watching the test stay green. The only layout that distinguishes is the overflow wrapping alone
   onto the last line, and producing it would mean tuning the gallery's specimen until a test could
   fail, which is designing the fixture around the test. The measurement above is the evidence; W4-13
   and W4-23 are the cards that will first compose a toolbar narrow enough to find out. */
.biztech-toolbar-overflow[b-v7m0m5mdiv] {
    display: none;
    margin-inline-start: auto;
}

@media (max-width: 599.98px) {
    .biztech-toolbar-inline[b-v7m0m5mdiv] {
        display: none;
    }

    .biztech-toolbar-overflow[b-v7m0m5mdiv] {
        display: block;
    }
}
