/* ==========================================================================
   Rota — application stylesheet
   --------------------------------------------------------------------------
   Everything that MudBlazor's theme cannot express lives here, and nowhere
   else. Colour is not repeated: each token below is either derived from the
   MudBlazor palette (var(--mud-palette-*), which MudThemeProvider rewrites
   when dark mode changes) or defined once per theme in the two blocks at the
   top. Pages must not carry <style> blocks.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

/* Four roles, and only four. Every coloured thing in this application is one of
   them, so a colour learnt on the grid means the same on the overview, in a chip,
   on an area card and in a conflict message.

     booked   green   cover is in place                 name, upright
     gap      red     work to do, or work that reaches nobody   GAP, bold caps
     pending  amber   somebody has to decide            name, italic
     notreq   grey    deliberately nothing              an en dash

   Colour is never the only channel: the right-hand column above is what the cell
   actually says, and it is what survives greyscale, a colour vision deficiency and
   forced-colours mode. Blue is structure — navigation, links, focus, today — and is
   never a status, so a status colour is always the odd one out on the screen.

   ConflictPolicy's three severities map onto the same three status roles rather than
   inventing a fourth vocabulary: Block is refused work (gap), Overridable and Warn
   are both "a coordinator has to look at this" (pending).

   THE MAJORITY IS NEVER MARKED. A role's colour is spent the moment it lands on most
   of what is on screen: ~90% of grid cells are booked, every area on /areas has work
   outstanding, so a green wash on the grid and a red figure on all nine cards told a
   coordinator nothing and buried the few cells and the one card that mattered. Booked
   therefore paints nothing in the grid (§4), and the ordinary "has work outstanding"
   area carries no edge (§3b). The green above is still the booked role — it appears on
   the counts, the stack bar and the coverage meter, where booked is one item among a
   handful and the colour still separates something. */

.rota-theme {
    /* The three rule colours are the theme's own Success/Error/Warning, not copies of
       them. RotaTheme.cs is the single source; MudThemeProvider rewrites these on a
       dark-mode change, so the grid, a MudAlert and a MudChip cannot drift apart and
       the dark block below has three fewer things to keep in step. */
    --rota-booked-bg: #e1eee8;
    --rota-booked-fg: #1a1f27;
    --rota-booked-rule: var(--mud-palette-success);

    --rota-gap-bg: #f6c9c3;
    --rota-gap-fg: #8f1d13;
    --rota-gap-rule: var(--mud-palette-error);

    --rota-pending-bg: #fcead5;
    --rota-pending-fg: #7a3406;
    --rota-pending-rule: var(--mud-palette-warning);

    /* Was #6a7686, which measured 3.89:1 against the weekend row — under the 4.5:1 floor on
       exactly the rows where most "no cover needed" dashes fall. #5c6875 is 4.79:1 there and
       5.69:1 on plain surface. */
    --rota-notreq-fg: #5c6875;

    /* Structure. Weekends are neutral on purpose: amber is spoken for. */
    --rota-weekend-bg: #e8ecf1;

    /* Alternate pharmacy column-groups. Structural, not status: it says "this is one pharmacy",
       the same thing the weekend tint says about a row, turned ninety degrees. Fainter than the
       weekend tint on purpose — a row is read constantly, a column group only when tracing one
       pharmacy down eighty columns, and this must never be mistaken for a state.
       1.03:1 against surface. */
    --rota-band-bg: #f4f7fa;

    /* The grid's weekend tint, translucent so a column band shows through it. The opaque
       --rota-weekend-bg above keeps its four other consumers (the gaps grid, the print sheet, the
       compare table) where there is no band underneath to reveal. Same colour, 88% of it. */
    --rota-weekend-veil: rgb(232 236 241 / 88%);
    --rota-head-bg: #f5f8fa;
    --rota-head-fg: #1a1f27;
    --rota-head-muted: #566171;
    --rota-rule: var(--mud-palette-table-lines);
    --rota-rule-strong: #9aa7b6;
    /* The group's own colour, not a literal: MudThemeProvider emits this per tenant, so a
       coordinator's "today" marker and focus ring match their branding rather than McKeevers'. */
    --rota-today: var(--mud-palette-primary);
    --rota-focus: var(--mud-palette-primary);

    /* Urgency — how soon an unfilled shift falls. Three bands because a coordinator does three
       different things with them: ring somebody today, plan it into the fortnight, leave it for
       now. Ranking by soonest is the whole premise of the coverage screen, so the ranking is
       given colour rather than left to the reader to infer from a column of dates.

       These are the theme's own Error and Warning, not new hues: a shift falling this week is
       the same "work to do" red the grid uses for a gap, and one falling in the next fortnight
       is the same "somebody has to decide" amber. Nothing new is introduced to be learnt, and
       the third band has no token because it has no colour — an ordinary date in ordinary text
       is exactly what "no hurry" should look like.

       A fourth band, and it is not on the scale at all: a gap whose date has passed is a shift
       that already went uncovered. It cannot be filled, only recorded, so it is not urgent — it
       is finished. It takes secondary text rather than a status colour, because red on it would
       send a coordinator to ring a pharmacy about last Tuesday. It has no token either: nothing
       is being defined, it is the ordinary muted text this application already has. */
    --rota-urgent-fg: var(--mud-palette-error);
    --rota-soon-fg: var(--mud-palette-warning);

    /* Grid metrics, swapped wholesale by the density control. */
    --rota-col-w: 104px;
    --rota-day-w: 82px;
    --rota-cell-py: 3px;
    --rota-cell-px: 7px;
    --rota-grid-size: 12px;
    --rota-head1-h: 24px;

    /* 42% left the yellow-green end of the twelve hues at 2.69:1 against a weekend row — under
       the 3:1 a graphical object wants, on the rows where the person edge is hardest to see.
       39% puts the worst of the twelve at 3.11:1 there and 3.68:1 on plain surface, and three
       points of lightness does not measurably close the hues on each other. */
    --rota-person-l: 39%;
    --rota-person-s: 48%;
}

.rota-theme.rota-dark {
    /* Only the tints are restated here; the three rule colours follow the theme. */
    --rota-booked-bg: #20322a;
    --rota-booked-fg: #e6eaf0;

    --rota-gap-bg: #4a211c;
    --rota-gap-fg: #ffd9d4;

    --rota-pending-bg: #3e2f19;
    --rota-pending-fg: #f5d3a3;

    --rota-notreq-fg: #96a2b1;

    /* Was #1e242c, which is 1.08:1 against the surface — all but invisible. It got away with it
       while booked cells painted green over the weekend rows and the banding you actually saw was
       the green, not the tint. With booked painting nothing the tint is the whole of the band, so
       it has to be one: #171c23 is 1.18:1, matching what light has always been (1.19:1), and it is
       the appbar's own colour rather than a new value. Text on it is 14.18:1 and the "no cover
       needed" dash 6.60:1. */
    --rota-weekend-bg: #171c23;
    --rota-weekend-veil: rgb(23 28 35 / 88%);

    /* 1.04:1 against the dark surface, matching light's 1.03:1. */
    --rota-band-bg: #1a2029;

    --rota-head-bg: #1c222a;
    --rota-head-fg: #e6eaf0;
    --rota-head-muted: #a3aebd;
    --rota-rule-strong: #4d5866;
    --rota-today: #7fb3dc;
    --rota-focus: #a6cce9;

    --rota-person-l: 66%;
    --rota-person-s: 55%;
}

/* Every control that sits on a row with another control is this tall. It lives on the theme
   rather than on .rota-toolbar so a shared component — MonthPicker — can honour the same
   contract when it is used somewhere else. */
.rota-theme {
    --rota-control-h: 40px;
}

/* Density is a preference, not a breakpoint: coordinators on a 27" screen want
   every column they can get, and the same people on a laptop do not. */
.rota-theme .density-comfortable {
    --rota-col-w: 122px;
    --rota-day-w: 92px;
    --rota-cell-py: 6px;
    --rota-cell-px: 9px;
    --rota-grid-size: 13px;
    --rota-head1-h: 28px;
}

/* --------------------------------------------------------------------------
   2. Accessibility helpers
   -------------------------------------------------------------------------- */

.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* First tab stop on every page: a keyboard user should not have to walk the
   whole navigation to reach a 2,800-cell grid. */
.skip-link {
    position: absolute;
    left: 8px;
    top: -100px;
    z-index: 2000;
    padding: 10px 16px;
    border-radius: 6px;
    background: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text);
    font-weight: 600;
    text-decoration: none;
    transition: top .12s ease-out;
}

.skip-link:focus {
    top: 8px;
}

/* MudBlazor's own focus treatment is inconsistent across components; this makes
   one visible ring the rule. 3:1 against both surfaces, 2px thick, offset clear
   of the control (WCAG 2.2 §2.4.11). */
.rota-theme :focus-visible {
    outline: 2px solid var(--rota-focus);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
        scroll-behavior: auto !important;
    }
}

@media (forced-colors: active) {
    .rota-grid td.gap::after {
        content: " (gap)";
    }
    .rota-grid td:focus {
        outline: 3px solid Highlight;
    }

    /* Forced colours drop every background, so both the pharmacy band and the person hue go. The
       band's job is taken over by the store-edge borders, which are drawn in system ink and were
       already there. The person hue has no fallback and needs none: it is an aid to scanning, not
       a carrier of meaning, and the name is in the cell. */
    .rota-grid col.band {
        background: none;
    }
}

/* --------------------------------------------------------------------------
   3. Page furniture
   -------------------------------------------------------------------------- */

.rota-page {
    display: block;
}

/* A link inside prose must look like a link before somebody happens to hover it. This applies to
   alerts, empty states, explanatory copy and table text; buttons and navigation have their own
   affordances and are not MudLink elements inside the page. */
.rota-page .mud-link {
    text-decoration-line: underline;
    text-decoration-thickness: .08em;
    text-underline-offset: .16em;
}

/* Tenant artwork and accent identify the organisation without changing scheduling status colours. */
.rota-appbar {
    border-bottom: 3px solid var(--rota-brand-accent, var(--mud-palette-primary));
}

.rota-brand {
    min-width: 0;
    gap: 10px;
    margin-right: 24px;
    line-height: 1;
}

.rota-brand-mark {
    width: 38px;
    height: 38px;
    object-fit: contain;
    flex: 0 0 38px;
}

.rota-brand-mark-wide {
    width: min(145px, 38vw);
    height: 42px;
    flex: 0 1 145px;
}

.rota-brand-wide {
    gap: 16px;
}

.rota-brand-copy {
    min-width: 0;
    display: flex;
    flex-direction: column;
    text-transform: uppercase;
    white-space: nowrap;
}

.rota-brand-name {
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: .92rem;
    font-weight: 600;
    letter-spacing: .13em;
}

.rota-brand-product {
    margin-top: 3px;
    font-size: .62rem;
    font-weight: 500;
    letter-spacing: .18em;
    opacity: .9;
}

@media (max-width: 420px) {
    .rota-brand {
        gap: 7px;
        margin-right: 8px;
    }

    .rota-brand-wide {
        gap: 0;
    }

    .rota-brand-mark-wide {
        width: min(132px, 40vw);
        height: 38px;
        flex-basis: 132px;
    }

    .rota-brand-wide .rota-brand-copy {
        display: none;
    }

    .rota-brand-name {
        font-size: .8rem;
    }

    .rota-brand-product {
        font-size: .55rem;
        letter-spacing: .12em;
    }
}

/* The toolbar above a dense view: filters left, actions right, wrapping to a
   second line rather than pushing the actions off screen.

   Every control in the row is forced to one height. Left to itself MudBlazor gives a
   dense outlined input a 38.8px box and a small icon button a 30px one, and it hangs
   `margin: 8px 0 4px` on every labelled input control. The old `align-items: flex-end`
   then aligned the *margin* edges, so the buttons sat 4px below the select outlines and
   8.4px off their centres. Measured, not guessed. Normalising the height fixes every
   toolbar on the app at once rather than nudging one page's control with a margin. */
.rota-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    margin-bottom: 12px;
}

/* MudBlazor inputs are width:100% by design, so in a flex row two of them share the whole width
   and the toolbar's proportions change with how many filters a page happens to have. Each control
   states its own width instead. */
.rota-toolbar > * {
    flex: 0 0 auto;
}

/* The margins that broke the alignment. Spacing in this row is the flex gap's job.
   MudBlazor sets these from a three-class selector, so a two-class one loses however
   late this sheet loads; the compound selectors below are what actually win. */
.rota-toolbar .mud-input-control,
.rota-toolbar .mud-input-control.mud-input-outlined-with-label,
.rota-toolbar .mud-input-control.mud-input-control-margin-dense,
.rota-toolbar .mud-input-control.mud-input-control-margin-normal,
.rota-toolbar .mud-input-control.mud-input-control-margin-dense.mud-input-outlined-with-label,
.rota-toolbar .mud-input-control.mud-input-control-margin-normal.mud-input-outlined-with-label {
    margin-top: 0;
    margin-bottom: 0;
}

/* One height for inputs, pickers and buttons alike. `.mud-input` is the element that
   draws the outline, so sizing it is what makes the boxes agree. */
.rota-toolbar .mud-input-control .mud-input,
.rota-toolbar > .mud-button-root,
.rota-toolbar > .mud-tooltip-root > .mud-button-root,
.rota-toolbar .rota-toolbar-actions .mud-button-root {
    min-height: var(--rota-control-h);
}

/* Square off the steppers so a chevron is as tall as the select it steps. */
.rota-toolbar > .mud-tooltip-root > .mud-icon-button,
.rota-toolbar > .mud-icon-button {
    min-width: var(--rota-control-h);
    min-height: var(--rota-control-h);
}

/* The same bug, one level deeper.
   Every height rule above is a child selector, so wrapping controls in a component put them out
   of reach: MonthPicker renders its own flex row inside the toolbar, and its two chevrons went
   back to MudBlazor's 30px small icon-button while the selects beside them stayed at 40px —
   measured at 30px against 40px, sitting 5px low, on /rota, /pharmacy, /pharmacist and
   /coverage at once. A shared control has to state the contract for what it owns, because the
   toolbar cannot reach through it. */
.rota-monthpicker {
    display: flex;
    align-items: center;
    gap: 8px;
    max-width: 100%;
}

.rota-monthpicker > .mud-select {
    min-width: 0;
    flex-shrink: 1;
}

.rota-monthpicker .mud-input-control {
    max-width: 100%;
}

.rota-monthpicker .mud-icon-button {
    min-width: var(--rota-control-h);
    min-height: var(--rota-control-h);
}

.rota-monthpicker .mud-tooltip-root.mud-tooltip-inline {
    display: inline-flex;
}

/* MudBlazor hangs `margin: 8px 0 4px` on a labelled input wherever it renders, not only as a
   direct child of the toolbar. */
.rota-monthpicker .mud-input-control,
.rota-monthpicker .mud-input-control.mud-input-outlined-with-label,
.rota-monthpicker .mud-input-control.mud-input-control-margin-dense,
.rota-monthpicker .mud-input-control.mud-input-control-margin-dense.mud-input-outlined-with-label {
    margin-top: 0;
    margin-bottom: 0;
}

.rota-monthpicker .mud-input-control .mud-input {
    min-height: var(--rota-control-h);
}

/* At 320px, and at 400% zoom on a desktop viewport, four controls cannot remain on one line.
   Keep their reading and tab order intact while giving the labelled fields the available width. */
@media (max-width: 600px) {
    /* Wrapping in DOM order rather than placing by class. The placement rules depended on the
       month and year classes landing on this element's own children, which they do not — MudSelect
       puts them further in — so the year was auto-placed into whatever cell was left and squeezed
       to "Y…", which is the very thing this control exists to prevent. Flex wrapping needs no such
       assumption, and a select never goes below a width its value fits in. */
    .rota-monthpicker {
        flex-wrap: wrap;
        width: 100%;
    }

    .rota-monthpicker > * {
        flex: 1 1 auto;
        min-width: 0;
    }

    .rota-monthpicker > .mud-select,
    .rota-monthpicker .mud-input-control {
        width: auto !important;
        min-width: 7.5rem;
    }

    .rota-monthpicker .mud-tooltip-root {
        flex: 0 0 auto;
    }
}

/* A row action whose label wrapped to two lines inside its own button. */
.rota-see-them {
    white-space: nowrap;
}

/* A switch has no outlined box to size, so it gets the height as a hit area instead. */
.rota-toolbar .mud-input-control.mud-input-control-boolean-input {
    min-height: var(--rota-control-h);
    justify-content: center;
}

/* MudTooltip wraps its child in an inline-block, which adds a baseline gap under the
   button and drops it a few pixels below everything else in the row. */
.rota-toolbar .mud-tooltip-root.mud-tooltip-inline {
    display: inline-flex;
}

/* A control given a fixed pixel width in markup still has to fit the screen. Without this the
   pharmacist picker on /pharmacist kept its 430px at 320px wide and pushed the page sideways. */
.rota-toolbar > .mud-select,
.rota-toolbar > .mud-input-control,
.rota-toolbar .mud-select .mud-input-control,
.rota-toolbar .mud-input-control .mud-input {
    max-width: 100%;
}

.rota-toolbar .rota-toolbar-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-inline-start: auto;
}

/* The breakdown combines a four-control month picker with three wide financial totals. Let each
   group use a complete row on a phone instead of clipping the next-month button or the total. */
@media (max-width: 600px) {
    .breakdown-toolbar .rota-monthpicker,
    .breakdown-toolbar .rota-toolbar-actions {
        width: 100%;
    }

    .breakdown-toolbar .rota-toolbar-actions {
        margin-inline-start: 0;
    }
}

/* Locum rows remain a real table at narrow widths and scroll as one labelled region. Expanded
   shift detail follows the same rule: columns stay aligned rather than being clipped or stacked
   into detached labels. */
.breakdown-locum-grid .mud-table-container,
.breakdown-shifts .mud-table-container {
    overflow-x: auto;
}

.breakdown-locum-grid .mud-table-root {
    min-width: 680px;
}

.breakdown-shifts .mud-table-root {
    min-width: 720px;
}

/* Counts that sit above a table. Tabular figures so the digits do not dance
   when a filter changes. */
.rota-stat-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 20px;
    align-items: baseline;
    font-variant-numeric: tabular-nums;
}

.rota-stat {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
}

.rota-shortcut-help {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 8px;
}

.rota-shortcut-list {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    gap: 8px 16px;
    width: min(540px, calc(100vw - 96px));
    margin: 0;
}

.rota-shortcut-list dd {
    margin: 0;
}

.rota-shortcut-list kbd {
    display: inline-block;
    padding: 1px 5px;
    border: 1px solid var(--mud-palette-divider);
    border-radius: 4px;
    background: var(--mud-palette-background-gray);
    font: inherit;
    font-weight: 600;
}

@media (max-width: 600px) {
    .rota-shortcut-list {
        grid-template-columns: minmax(0, 1fr);
        gap: 2px;
    }

    .rota-shortcut-list dd {
        margin-bottom: 8px;
    }
}

/* Message history is a sequence of sends, not a wall of equally prominent cards. Keep each
   collapsed send compact, let its outcomes wrap independently, and reserve the full-width table
   for somebody who deliberately opens it. */
.message-round-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 8px 24px;
}

.message-round-summary {
    display: flex;
    flex: 0 1 auto;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 2px 4px;
}

/* The messaging check's channel options, stacked. Left alone they flow in a row and wrap into a
   two-and-one arrangement that reads as deliberate grouping rather than one list of three.
   The `.mud-input-control` is carrying its weight: MudBlazor rows these up from
   `.mud-input-control.mud-input-control-boolean-input .mud-radio-group`, so a selector any shorter
   than three classes loses on specificity and silently does nothing. */
.mud-input-control.messaging-check .mud-radio-group {
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
}

/* Settings are decisions and short forms, so keep their text and controls in one readable column
   on wide monitors. Each breakdown explanation belongs directly below its radio label. */
.settings-page {
    max-width: 1280px;
}

.settings-page > .mud-paper > .mud-typography-body2 {
    max-width: 100ch;
}

.settings-feature-description {
    padding-inline-start: 42px;
}

.mud-input-control.settings-audience .mud-radio-group {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
}

.settings-audience-option {
    max-width: 80ch;
}

.settings-audience-description {
    margin: 0;
    padding-inline-start: 42px;
    font-size: .875rem;
    line-height: 1.5;
}

.settings-rate-form > .settings-rate-locum { width: 240px; }
.settings-rate-form > .settings-rate-hourly { width: 118px; }
.settings-rate-form > .settings-rate-pharmacy { width: 180px; }
.settings-rate-form > .settings-rate-days { width: 140px; }
.settings-rate-form > .settings-rate-date { width: 160px; }

.settings-rate-form > div > .mud-select {
    width: 100%;
}

.settings-rate-actions {
    display: flex;
    align-items: center;
    gap: 12px;
}

.settings-rate-actions .settings-rate-note { width: 190px; }
.settings-rate-actions .mud-button-root { min-height: var(--rota-control-h); }

/* An empty rate list is one useful sentence, not a 200px placeholder in an already outlined card. */
.settings-rate-empty {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    max-width: 76ch;
    margin-top: 16px;
    padding-top: 16px;
    border-top: 1px solid var(--mud-palette-divider);
}

.settings-rate-empty .mud-icon-root {
    flex: 0 0 auto;
    color: var(--mud-palette-text-secondary);
}

@media (max-width: 700px) {
    .settings-rate-form > .mud-select,
    .settings-rate-form > .mud-input-control,
    .settings-rate-form > .settings-rate-date,
    .settings-rate-form > .settings-rate-locum,
    .settings-rate-form > .settings-rate-pharmacy,
    .settings-rate-form > .settings-rate-days,
    .settings-rate-form > .settings-rate-actions {
        flex: 1 1 100%;
        width: 100%;
    }

    .settings-rate-actions {
        align-items: stretch;
        flex-direction: column;
    }

    .settings-rate-actions .settings-rate-note { width: 100%; }
}

/* The same for the list of templates approved at Twilio (ADR-61): one under another, each with its
   wording beneath its name, and the wording indented to sit under the label rather than the radio. */
.mud-input-control.whatsapp-templates .mud-radio-group {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
}

.whatsapp-template > .mud-typography {
    padding-left: 42px;
}

/* The wording screen's "Add a field" menu: each field's example under its name, quieter. */
.rota-field-example {
    font-size: .75rem;
}

/* The wording screen's Publish bar. Sticks to the foot of the window while the wording above it is in
   view, so Publish and whether anything is unsaved are never a long scroll from the box being typed
   in; it goes back into the page past the wording, where the WhatsApp section has its own buttons. */
.rota-action-bar {
    position: sticky;
    bottom: 0;
    z-index: 5;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 10px 16px;
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: var(--mud-default-borderradius);
    box-shadow: 0 -2px 8px rgba(0, 0, 0, .08);
}

.rota-action-bar-state {
    font-weight: 500;
}

/* On a phone: the state on its own line, the three controls together beneath it, kept low. */
@media (max-width: 600px) {
    .rota-action-bar {
        gap: 4px 8px;
        padding: 8px 12px;
    }

    .rota-action-bar-state {
        flex-basis: 100%;
    }
}

@media (max-width: 700px) {
    .message-round-header {
        display: block;
    }

    .message-round-summary {
        justify-content: flex-start;
        margin-top: 8px;
    }

    .messages-toolbar > .mud-input-control {
        width: 100% !important;
    }
}

.rota-stat .n {
    font-size: 1.0625rem;
    font-weight: 600;
    line-height: 1.2;
}

.rota-stat .l {
    color: var(--mud-palette-text-secondary);
    font-size: .8125rem;
}

.rota-stat .dot {
    width: 10px;
    height: 10px;
    border-radius: 2px;
    align-self: center;
}

/* The four roles as count modifiers, so every page says "this many still to fill" in
   the same red and every page's dot is drawn from the same token. Before this each
   page repeated `style="background:var(--rota-gap-rule)"` inline, which is how two
   screens drift apart. The label beside the number always names the status in words. */
.rota-stat.is-gap .n { color: var(--rota-gap-rule); }
.rota-stat.is-gap .dot { background: var(--rota-gap-rule); }

.rota-stat.is-booked .n { color: var(--rota-booked-rule); }
.rota-stat.is-booked .dot { background: var(--rota-booked-rule); }

.rota-stat.is-pending .n { color: var(--rota-pending-rule); }
.rota-stat.is-pending .dot { background: var(--rota-pending-rule); }

.rota-stat.is-quiet .dot { background: var(--mud-palette-lines-inputs); }

/* A warning chip can sit on the neutral weekend tint. MudBlazor's warning foreground measures
   4.21:1 there at this chip's 12px text, so use the darker pending-role foreground already tested
   on both plain and tinted surfaces. */
.rota-theme .gap-reason.mud-chip-color-warning { color: var(--rota-pending-fg); }

/* Gaps are the work, so on a screen about gaps the gap count is not another number in
   a row of numbers — it is the headline, and it is what the eye should land on. */
.rota-stat.is-lead .n {
    font-size: 1.75rem;
    line-height: 1.1;
}

.rota-stat.is-lead .dot {
    width: 13px;
    height: 13px;
}

/* The legend. Without it "—" and "GAP" are two dashes of meaning apart, and the
   workbook this replaces taught people that ***** means "nobody needed". */
.rota-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 18px;
    align-items: center;
    font-size: .75rem;
    color: var(--mud-palette-text-secondary);
    list-style: none;
    margin: 0;
    padding: 0;
}

.rota-legend .key {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.rota-legend .swatch {
    width: 14px;
    height: 14px;
    border-radius: 3px;
    border: 1px solid var(--rota-rule);
    display: inline-block;
}

/* Each swatch is a miniature of the cell it names, so the legend cannot come to describe a
   grid that no longer looks like that. Booked and "no cover needed" are both plain cells now,
   and the thing that tells them apart in the grid — a name against a centred dash — is the
   thing that tells the two swatches apart here. */
.rota-legend .swatch.booked { background: var(--mud-palette-surface); border-color: var(--rota-rule-strong); }
.rota-legend .swatch.gap {
    background: var(--rota-gap-bg);
    border-color: var(--rota-gap-rule);
    box-shadow: inset 3px 0 0 var(--rota-gap-rule);
}
.rota-legend .swatch.pending { background: var(--rota-pending-bg); border-color: var(--rota-pending-rule); }

.rota-legend .swatch.notrequired {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--mud-palette-surface);
    border-color: var(--rota-rule-strong);
    color: var(--rota-notreq-fg);
    font-size: 11px;
    line-height: 1;
}

.rota-legend .swatch.notrequired::before { content: "\2013"; }

/* A miniature of a shaded cell, same as the others: a plain cell carrying a hue down its leading
   edge. One of the twelve stands for all of them — the key says a colour per person, not which
   colour is whose, because with roughly twenty people to a hue there is no such mapping to show. */
.rota-legend .swatch.person {
    background: var(--mud-palette-surface);
    border-color: var(--rota-rule-strong);
    box-shadow: inset 4px 0 0 hsl(210deg var(--rota-person-s) var(--rota-person-l));
}

/* What stands in the way of a booking, read out in the confirmation dialog. Each line carries the
   icon and colour its severity already has in the dropdown, so the amber thing in the list is the
   amber thing that was on the row. Colour comes from MudIcon's palette colours for the same reason
   the chips do — the dialog renders outside .rota-theme. */
.rota-conflicts {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.rota-conflicts li {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    font-size: .8125rem;
    line-height: 1.45;
}

/* The picker needs more room than a grid cell, but must also fit a narrow viewport. Its width
   is independent of the cell and of the longest booking-history sentence. Popovers sit outside
   .rota-theme, so use the MudBlazor palette shared by both light and dark mode. */
.rota-grid td.editing .mud-autocomplete,
.rota-candidate-input,
.rota-candidate-list {
    width: min(360px, calc(100vw - 24px));
}

.rota-candidate-input .mud-input {
    height: 34px;
    font-size: .8125rem;
    font-weight: 400;
}

.rota-candidate-input .mud-input-slot {
    padding: 6px 10px !important;
}

.rota-candidate-list {
    --rota-candidate-max-height: min(320px, calc(100dvh - 96px));
    /* MudPopover sets an inline maximum height; keep it within short/zoomed viewports too. */
    max-height: var(--rota-candidate-max-height) !important;
    overflow: hidden;
    border: 1px solid var(--mud-palette-divider);
    box-shadow: 0 3px 10px rgb(0 0 0 / 14%);
}

/* On a phone neither a left- nor right-aligned popover fits beside a middle grid cell.
   Pin it to the viewport gutter instead of relying on the popover's horizontal flip. */
@media (max-width: 600px) {
    .rota-grid td.editing .mud-autocomplete,
    .rota-candidate-input {
        width: 190px;
    }

    .rota-candidate-list {
        left: 12px !important;
    }
}

.rota-candidate-list .mud-list {
    padding-block: 0;
    /* Keyboard navigation scrolls this list. Allow for the popover's two border pixels so
       only the list scrolls, rather than also overflowing its outer container. */
    max-height: calc(var(--rota-candidate-max-height) - 2px);
    overflow-y: auto;
    overscroll-behavior: contain;
}

.rota-candidate-list .mud-autocomplete-before-items {
    background: var(--mud-palette-surface);
    border-bottom: 1px solid var(--mud-palette-divider);
}

.rota-candidate-heading,
.rota-candidate-state {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 8px 12px;
    font-size: .8125rem;
    line-height: 1.45;
    color: var(--mud-palette-text-secondary);
    overflow-wrap: anywhere;
}

.rota-candidate-heading strong,
.rota-candidate-state strong {
    color: var(--mud-palette-text-primary);
    font-size: .8125rem;
    font-weight: 600;
}

.rota-candidate-heading {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 2px 8px;
    font-size: .75rem;
}

.rota-candidate-help {
    padding: 6px 12px;
    border-top: 1px solid var(--mud-palette-divider);
    font-size: .75rem;
    line-height: 1.45;
    color: var(--mud-palette-text-secondary);
}

.rota-candidate-list .mud-list-item {
    min-height: 32px;
    padding: 6px 12px;
}

.rota-candidate-list .mud-selected-item {
    box-shadow: inset 3px 0 0 var(--mud-palette-primary);
}

@media (pointer: coarse) {
    .rota-candidate-input .mud-input {
        height: 44px;
    }

    .rota-candidate-list .mud-list-item {
        min-height: 44px;
    }
}

.rota-candidate-list .mud-list-item:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: -2px;
}

@media (forced-colors: active) {
    .rota-candidate-list {
        border: 1px solid CanvasText;
    }

    /* Arrow keys keep focus in the search field, so mark the active option as well. */
    .rota-candidate-list .mud-selected-item {
        outline: 2px solid Highlight;
        outline-offset: -2px;
    }
}

/* Keep names on one consistent left edge, with quieter history underneath. Colour and icons
   belong to booking conflicts, which need to stand out even when somebody has worked here before. */
.rota-candidate {
    display: flex;
    flex-direction: column;
    gap: 1px;
    width: 100%;
    min-width: 0;
    white-space: normal;
    overflow-wrap: anywhere;
}

.rota-candidate .who {
    font-size: .8125rem;
    font-weight: 500;
    line-height: 1.4;
}

.rota-candidate-group {
    display: block;
    margin: 2px 0 4px;
    color: var(--mud-palette-text-secondary);
    font-size: .6875rem;
    font-weight: 600;
    line-height: 1.4;
}

.rota-candidate-first-line {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 2px 10px;
}

.rota-candidate-first-line .who {
    flex: 1 1 auto;
}

.rota-candidate-workload {
    color: var(--mud-palette-text-secondary);
    font-size: .75rem;
    line-height: 1.45;
    white-space: nowrap;
}

.rota-candidate-signals {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-block: 2px;
}

.rota-candidate-signal {
    border: 1px solid var(--mud-palette-divider);
    border-radius: 3px;
    padding: 1px 5px;
    color: var(--mud-palette-text-secondary);
    font-size: .6875rem;
    font-weight: 600;
    line-height: 1.4;
}

.rota-candidate-signal.is-offer {
    border-color: var(--mud-palette-primary);
    color: var(--mud-palette-primary);
}

.rota-candidate-detail {
    font-size: .75rem;
    line-height: 1.45;
    color: var(--mud-palette-text-secondary);
}

.rota-candidate-warning {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    color: var(--mud-palette-text-primary);
    font-size: .8125rem;
    line-height: 1.45;
}

.rota-candidate-warning .mud-icon-root {
    flex-shrink: 0;
}

/* One bar, three segments: booked, still to fill, and nobody needed. Same three colours as
   the grid, so the overview teaches the legend rather than inventing a second vocabulary. */
.rota-stack {
    display: flex;
    height: 14px;
    border-radius: 7px;
    overflow: hidden;
    background: var(--mud-palette-lines-default);
}

.rota-stack > span {
    display: block;
    height: 100%;
}

.rota-stack .s-booked { background: var(--rota-booked-rule); }
.rota-stack .s-gap { background: var(--rota-gap-rule); }
.rota-stack .s-notreq { background: var(--mud-palette-lines-inputs); }

/* Weekend rows in the MudDataGrid on the gaps list, tinted the same way the month grid tints
   them so the two screens agree about what a Saturday looks like. */
.rota-theme .weekend-row > td {
    background: var(--rota-weekend-bg);
}

/* Which days a standing arrangement covers. Seven fixed positions read faster than
   "Mon Wed Fri", because the eye compares shapes instead of reading words. */
.daystrip {
    display: inline-flex;
    gap: 2px;
}

.daystrip span {
    width: 18px;
    height: 18px;
    border-radius: 3px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    font-weight: 600;
    background: var(--mud-palette-lines-default);
    color: var(--mud-palette-text-secondary);
}

.daystrip span.on {
    background: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text);
}

/* Secondary detail that a narrow screen has no room for. A media query rather than MudHidden:
   MudHidden cannot know the viewport until the circuit reports it, so on a pre-rendered page its
   content is missing from the first paint and appears a moment later. */
.hide-narrow {
    display: none !important;
}

@media (min-width: 960px) {
    .hide-narrow {
        display: inline-flex !important;
    }
}

/* Bars used on the home page to rank pharmacies by how much cover they need. Here the bar
   measures the gap itself, so a longer red bar is a worse pharmacy and red grows with the
   problem. Compare .rota-meter below, which measures the opposite quantity and therefore has
   to be drawn the opposite way round. */
.rota-bar {
    height: 8px;
    border-radius: 4px;
    background: var(--mud-palette-lines-default);
    overflow: hidden;
}

.rota-bar > span {
    display: block;
    height: 100%;
    border-radius: 4px;
    background: var(--mud-palette-error);
}

/* --------------------------------------------------------------------------
   3b2. Overview tiles
   --------------------------------------------------------------------------
   Six white boxes in a row, each with a number in the same black. Two of them
   are the state of the rota and four are the size of the setup behind it, and
   nothing said which was which — so the eye had to read all six to find the one
   that wanted doing something about.

   A tile that is a state of the rota takes that state's colour — a rule along the
   top, and the status colour on the number itself for the two that are counts of
   shifts. A tile that is only the size of the setup behind it stays neutral, and
   the standing-arrangements tile crosses over: it is neutral until some of them
   need checking, which is the only time it is something to act on. The rule is
   the same three roles again, and every tile still says what it is in words.
   -------------------------------------------------------------------------- */

.rota-tile {
    border-top: 3px solid transparent;
}

.rota-tile.is-booked { border-top-color: var(--rota-booked-rule); }
.rota-tile.is-gap { border-top-color: var(--rota-gap-rule); }
.rota-tile.is-pending { border-top-color: var(--rota-pending-rule); }

.rota-tile .n {
    font-size: 1.5rem;
    font-weight: 600;
    line-height: 1.2;
    font-variant-numeric: tabular-nums;
}

.rota-tile.is-booked .n { color: var(--rota-booked-rule); }
.rota-tile.is-gap .n { color: var(--rota-gap-rule); }

@media (forced-colors: active) {
    .rota-tile { border-top-width: 1px; }
    .rota-tile.is-booked,
    .rota-tile.is-gap,
    .rota-tile.is-pending { border-top-width: 4px; }
}

/* --------------------------------------------------------------------------
   3c. How soon
   --------------------------------------------------------------------------
   A date on its own does not rank. "Tue 1 Sept" and "Sat 12 Sept" are the same
   weight of black on the same white, and the coverage screen exists to say that
   the first one is the emergency — one gap tomorrow beats nine in five weeks.

   Four bands, and three channels for each: the colour, the words already in
   the cell ("in 3 days" reads differently from "in 4 weeks"), and the height of
   the leading mark. Greyscale, forced colours and a colour vision deficiency all
   leave two of the three standing.

   The fourth band is the one whose date has passed, and it was the first band's
   problem before it was its own. `<= 7 days` catches -2 as readily as 2, so a
   shift that went uncovered last Tuesday drew the same red, the same bold and the
   same tall mark as one falling tomorrow — and on /coverage, which ranks by the
   soonest gap, it drew them at the top of the list. A gap in the past cannot be
   filled. Telling a coordinator to chase it is not a small overstatement; it puts
   the one row they cannot act on above the one they must.

   So it recedes rather than shouts: secondary text, ordinary weight, and a mark
   that is hollow instead of solid — the urgent bar's silhouette, spent. Nothing is
   asserted about what happened, because nothing is known: the words say "2 days
   ago" and stop there.
   -------------------------------------------------------------------------- */

.rota-when {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-variant-numeric: tabular-nums;
}

.rota-when .mark {
    flex: none;
    width: 4px;
    border-radius: 2px;
    background: currentColor;
}

.rota-when.is-urgent {
    color: var(--rota-urgent-fg);
    font-weight: 700;
}

.rota-when.is-urgent .mark { height: 20px; }

.rota-when.is-soon {
    color: var(--rota-soon-fg);
    font-weight: 600;
}

.rota-when.is-soon .mark { height: 13px; }

.rota-when.is-later {
    color: var(--mud-palette-text-primary);
}

.rota-when.is-later .mark {
    height: 6px;
    background: var(--mud-palette-lines-inputs);
}

/* Already gone. The mark keeps the urgent band's 20px so the two are the same silhouette,
   and is hollow so they can never be read for one another — an outline is a different shape,
   not a paler shade, and it survives greyscale. 6.28:1 on plain surface, 5.29:1 on a weekend
   row, 5.58:1 on a hovered row; 6.44:1 dark. */
.rota-when.is-past {
    color: var(--mud-palette-text-secondary);
    font-weight: 400;
}

.rota-when.is-past .mark {
    height: 20px;
    background: none;
    border: 1px solid currentColor;
}

.rota-when.is-past .when-rel { font-weight: 600; }

/* The second line under the date — "in 3 days" — keeps the band's colour when the band is one
   that wants chasing, and drops to secondary text when it does not. */
.rota-when .when-date { display: block; }

.rota-when .when-rel {
    display: block;
    font-size: .75rem;
    font-weight: 400;
}

.rota-when.is-later .when-rel { color: var(--mud-palette-text-secondary); }

/* A background is repainted in forced colours, so a solid mark would vanish into the canvas.
   A border is not — it takes a system colour and stays. `forced-color-adjust: none` would have
   kept the author's red, which is the opposite of what somebody in a high-contrast theme asked
   for. The words beside it carry the meaning either way. */
@media (forced-colors: active) {
    .rota-when .mark {
        border: 1px solid currentColor;
        min-width: 4px;
    }
}

/* --------------------------------------------------------------------------
   3d. The coverage meter
   --------------------------------------------------------------------------
   This bar was drawn with MudProgressLinear coloured Error whenever a pharmacy
   had any gap at all, over a Value of *percent covered* — so a pharmacy at 97%
   showed a nearly full red bar and one at 50% showed a half-empty one. More red
   meant better covered, which is backwards everywhere else in this application.

   Here the track is what is still to fill and the fill is what is covered, so
   red shrinks as the work gets done. The boundary between them is the thing
   being read: #2F6F44 against #f6c9c3 is 4.04:1, over the 3:1 a graphical
   object needs. The percentage is written out beside it regardless.
   -------------------------------------------------------------------------- */

.rota-meter {
    height: 8px;
    border-radius: 4px;
    background: var(--rota-gap-bg);
    overflow: hidden;
}

.rota-meter > span {
    display: block;
    height: 100%;
    background: var(--rota-booked-rule);
}

/* Nothing left to fill: no red to shrink, so the track goes quiet rather than
   showing a sliver of alarm at the end of a finished bar. */
.rota-meter.is-clear {
    background: var(--mud-palette-lines-default);
}

.rota-meter-row {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 150px;
}

.rota-meter-row .rota-meter { flex: 1 1 auto; }

/* Both bar and track are repainted in forced colours, so the boundary between them — the thing
   being read — disappears. An outline keeps the bar visible as a shape, and the percentage
   printed beside it is what actually carries the value. */
@media (forced-colors: active) {
    .rota-meter {
        border: 1px solid currentColor;
    }

    .rota-meter > span {
        border-right: 2px solid currentColor;
    }
}

.rota-meter-row .pct {
    flex: none;
    font-size: .75rem;
    font-variant-numeric: tabular-nums;
    color: var(--mud-palette-text-secondary);
    min-width: 3.2em;
    text-align: right;
}

/* --------------------------------------------------------------------------
   3e. A count beside a heading
   --------------------------------------------------------------------------
   MudBadge positions its bubble at the top-right corner of whatever it wraps.
   Around a heading that puts the number above the words and overlapping them —
   measured on /planning at 16px above the heading's top edge and 4px into it.
   A badge is for a corner of an icon, not for a count that belongs in the
   sentence. This is a pill on the same baseline, and it says what it counts.
   -------------------------------------------------------------------------- */

/* Centre, not baseline. An inline-flex pill takes its baseline from its first line box, and the
   icon inside it is 20px against 12px of text — measured, the pill's text sat 6.5px above the
   heading's baseline. Centring the two boxes is what actually makes them look level, because a
   pill is a box with a border and reads by its middle, not by the bottom of its letters. */
.rota-heading-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 10px;
}

.rota-heading-row > h2,
.rota-heading-row > .mud-typography {
    margin: 0;
}

.rota-count {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    border-radius: 11px;
    border: 1px solid currentColor;
    font-size: .75rem;
    font-weight: 600;
    line-height: 1.4;
    white-space: nowrap;
}

.rota-count .mud-icon-root {
    font-size: 1.05rem;
    width: 1.05rem;
    height: 1.05rem;
}

.rota-count.is-pending {
    color: var(--rota-pending-fg);
    background: var(--rota-pending-bg);
    border-color: var(--rota-pending-rule);
}

.rota-count.is-gap {
    color: var(--rota-gap-fg);
    background: var(--rota-gap-bg);
    border-color: var(--rota-gap-rule);
}

.rota-count.is-booked {
    color: var(--rota-booked-fg);
    background: var(--rota-booked-bg);
    border-color: var(--rota-booked-rule);
}

/* --------------------------------------------------------------------------
   3b. Area cards
   --------------------------------------------------------------------------
   NO PER-AREA COLOUR. A palette giving each of the twelve areas its own hue was designed and
   contrast-tested (all 4.1-5:1) and then dropped, and the reason is worth keeping so it is not
   redesigned: in this application green, red and amber already mean something, so a teal swatch
   beside "Belfast" reads as a status rather than as an identity. Twelve bespoke hex values across
   two themes would also be the loudest change in the stylesheet, to say something the area's name
   already says. The state edge below is what answers "the Areas screen is hard to read" — it
   colours whether an area can be reached, which is a fact a coordinator acts on.

   An area decides who a cover request reaches, so the card answers three
   questions in the order a coordinator asks them: can this area's gaps reach
   anybody, how much work is in it, and what is in it. Before this the card was a
   title, a row of same-sized chips, a run of pharmacy names and a button — and
   because MudText renders a caption as an inline <span> while MudButton renders
   an inline-flex <button>, the names and the button shared a line box and
   overlapped. Every region below is a block, so that cannot recur.
   -------------------------------------------------------------------------- */

.area-card {
    display: flex;
    flex-direction: column;
    gap: 14px;
    width: 100%;
    padding: 16px 16px 16px 14px;
    border-inline-start: 4px solid transparent;
}

/* THE EDGE MARKS EXCEPTIONS, AND ONLY EXCEPTIONS.

   An area nobody will travel to is not a lesser version of a normal card; it is the one thing
   on this page worth acting on, so it is marked down its whole edge. An area whose work is
   finished is the other end of the same scale, and worth confirming.

   An area with shifts still to fill is neither. It is what every area looks like most of the
   time — there were nine of them on the day this was measured — so an amber edge on it marked
   the whole page and left the one red edge to be picked out from eight near neighbours of a
   similar hue at 4px wide. That is the failure mode the red figures below had too: a mark on
   the majority is not a mark. The ordinary card now carries no edge at all, which makes the
   two that do carry one the only two the eye has to consider.

   No per-area hue was added, and this is the place it would have gone. Twelve tested cool hues
   were designed and dropped: a coloured token beside "Belfast" would be the only colour in this
   application that means nothing, in a system whose whole premise is that a colour learnt on
   the grid means the same everywhere. Areas are created and renamed on this very screen, so a
   fixed palette would either run out or start recycling, and a recycled hue is worse than none.
   Areas are already identified by names that cannot be confused with one another. What was hard
   to read here was the hierarchy — the name was the smallest thing on its own card, at 15px
   under a 22px figure — and that is fixed below, in type. */
.area-card.is-unreachable {
    border-inline-start-color: var(--rota-gap-rule);
}

.area-card.is-clear {
    border-inline-start-color: var(--rota-booked-rule);
}

/* `flex-start` aligned the pencil's 30px margin box to the heading's 21px one, so the icon
   sat low against the words. The heading is one line by design, so centring is what makes the
   two look level. */
.area-card-head {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 32px;
}

.area-card-head h2 {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
}

/* Two or three figures, tabular, with the gap count first. A card with 872 to fill and a card
   with 63 differ at a glance on the digits alone; they were at 1.375rem, which put them 7px
   clear of the card's own name and made the name the thing you had to hunt for on a page of
   nine. One step down the type scale leaves the figure the largest thing in its row and the
   name the largest thing on the card. */
.area-figures {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 22px;
    font-variant-numeric: tabular-nums;
}

.area-figure {
    display: flex;
    flex-direction: column;
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

.area-figure .n {
    font-size: 1.1875rem;
    font-weight: 600;
    line-height: 1.15;
}

.area-figure .l {
    font-size: .75rem;
    color: var(--mud-palette-text-secondary);
}

/* Red on this page means what it means everywhere: work that reaches nobody. It belongs to the
   "0 will travel here" on an unreachable area and to nothing else.

   It used to sit on every card's "to fill" figure, which put nine red numbers on the screen —
   and the smallest of them, Castleblayney's 63, was on the one card with a real problem while
   England's 1,080 shouted beside it. 872 shifts across a year is a workload, not an emergency,
   and a colour that appears on all nine cards has stopped being a signal. The figure keeps its
   size and its place; what it loses is a false alarm. */
.area-figure.is-gap .n { color: var(--rota-gap-rule); }

/* Blue, because the figure is a link and blue is what navigation is in this application — the
   one thing red was doing usefully there was saying "this is clickable", and that is structure,
   not status. The label is underlined at rest so the affordance does not rest on colour either
   (8.86:1 light, 6.47:1 dark). */
a.area-figure .n { color: var(--mud-palette-primary); }
a.area-figure .l { text-decoration: underline; }

a.area-figure:hover .n,
a.area-figure:focus-visible .n { text-decoration: underline; }

/* Said in words as well as in red, and it says what follows from it rather than
   just naming the state. */
.area-flag {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin: 0;
    padding: 8px 10px;
    border: 1px solid var(--rota-gap-rule);
    border-radius: 6px;
    background: var(--rota-gap-bg);
    color: var(--rota-gap-fg);
    font-size: .8125rem;
    line-height: 1.35;
}

/* Forced colours repaint every background, so the tint above disappears and a
   transparent border is painted opaque — which would give every card the edge that is
   meant to mark only the broken ones. The border, the icon and the bold first sentence
   are what carry the meaning there. */
@media (forced-colors: active) {
    .area-card {
        border-inline-start-width: 1px;
    }

    .area-card.is-unreachable,
    .area-card.is-clear {
        border-inline-start-width: 5px;
    }
}

/* One tag per pharmacy rather than a dot-separated run: an area of nine reads as
   nine things, an area of one reads as one, and neither can collide with the button
   below because this is a list, not a line of inline text. */
.area-pharmacies {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.area-pharmacies li {
    padding: 2px 8px;
    border-radius: 10px;
    background: var(--mud-palette-lines-default);
    color: var(--mud-palette-text-primary);
    font-size: .75rem;
}

.area-pharmacies li.empty {
    background: none;
    padding-inline: 0;
    color: var(--mud-palette-text-secondary);
    font-style: italic;
}

/* Pinned to the bottom so the action sits on the same line across a row of cards
   however many pharmacies each one holds. */
.area-actions {
    margin-top: auto;
    padding-top: 2px;
}

/* --------------------------------------------------------------------------
   4. The month grid
   --------------------------------------------------------------------------
   A hand-written table, deliberately: ~80 columns by ~35 rows is 2,800 cells,
   and a component per cell is a render tree Blazor Server cannot diff at
   interactive speed. Everything below is therefore plain CSS over plain
   elements, with no per-cell wrapper.
   -------------------------------------------------------------------------- */

.grid-scroll {
    overflow: auto;
    max-height: min(74vh, 900px);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: var(--mud-default-borderradius, 6px);
    background: var(--mud-palette-surface);
    overscroll-behavior: contain;
}

.grid-scroll[aria-busy="true"] {
    opacity: .45;
    pointer-events: none;
}

.rota-grid {
    border-collapse: separate;
    border-spacing: 0;
    table-layout: fixed;
    font-size: var(--rota-grid-size);
    font-variant-numeric: tabular-nums;
    color: var(--mud-palette-text-primary);
}

.rota-grid col.c-day { width: var(--rota-day-w); }
.rota-grid col.c-shift { width: var(--rota-col-w); }

.rota-grid th,
.rota-grid td {
    border-right: 1px solid var(--rota-rule);
    border-bottom: 1px solid var(--rota-rule);
    padding: var(--rota-cell-py) var(--rota-cell-px);
    text-align: left;
    overflow: hidden;
}

/* A cell holds one name and must never widen its column; the whole value is in the title. */
.rota-grid td,
.rota-grid th.day,
.rota-grid thead tr.store-row th {
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Shift codes run long ("Dobbin Lane Rota 6-7") and are the only thing separating two columns of
   the same pharmacy, so the header wraps instead of truncating. Every header in the row takes the
   height of the tallest, so the columns still line up. */
.rota-grid thead tr.shift-row th .code {
    white-space: normal;
    overflow-wrap: anywhere;
    line-height: 1.2;
}

/* Two sticky header rows: pharmacy, then the shift codes under it. Grouping the
   pharmacy name into one spanning cell removes it from 80 repetitions and turns
   "which shop is this column" into a glance instead of a read. */
.rota-grid thead th {
    position: sticky;
    background: var(--rota-head-bg);
    color: var(--rota-head-fg);
    font-weight: 600;
    z-index: 2;
}

.rota-grid thead tr.store-row th {
    top: 0;
    height: var(--rota-head1-h);
    font-size: .6875rem;
    letter-spacing: .02em;
    text-transform: uppercase;
    color: var(--rota-head-muted);
    border-bottom: 1px solid var(--rota-rule-strong);
}

.rota-grid thead tr.shift-row th {
    top: var(--rota-head1-h);
    vertical-align: bottom;
    line-height: 1.25;
    border-bottom: 2px solid var(--rota-rule-strong);
}

.rota-grid thead th.corner {
    left: 0;
    top: 0;
    z-index: 4;
    border-right: 2px solid var(--rota-rule-strong);
    border-bottom: 2px solid var(--rota-rule-strong);
    vertical-align: bottom;
}

.rota-grid .code {
    display: block;
    font-weight: 600;
}

.rota-grid .gapcount {
    display: block;
    font-size: 10px;
    font-weight: 500;
    color: var(--rota-head-muted);
}

.rota-grid .gapcount.has-gaps {
    color: var(--rota-gap-rule);
    font-weight: 700;
}

/* The day column stays put while 80 columns scroll past it. */
.rota-grid th.day {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--mud-palette-surface);
    font-weight: 500;
    border-right: 2px solid var(--rota-rule-strong);
}

/* The weekend tint is painted on the row, not on its cells, so a status colour laid on the cell
   always covers it. Tinting the cells instead made a Saturday GAP grey — a more specific selector
   winning over the one that mattered, on exactly the day most gaps fall.

   It is the translucent veil rather than the opaque tint because the pharmacy column band is
   painted on the <col>, which CSS lays *under* the row. Opaque here would have hidden the band on
   two days in seven. The day header keeps the opaque value: it sits in the sticky first column,
   over no band, and wants to be solid against the scrolling body. */
.rota-grid tbody tr.weekend {
    background: var(--rota-weekend-veil);
}

.rota-grid tr.weekend th.day {
    background: var(--rota-weekend-bg);
    font-weight: 700;
}

/* A heavier rule where a week starts, so a month reads as five chunks rather
   than thirty-one lines. */
.rota-grid tr.week-start th,
.rota-grid tr.week-start td {
    border-top: 2px solid var(--rota-rule-strong);
}

.rota-grid tr.today th.day {
    box-shadow: inset 3px 0 0 var(--rota-today);
    font-weight: 700;
}

/* Where one pharmacy's columns end and the next begins. */
.rota-grid th.store-edge,
.rota-grid td.store-edge {
    border-right: 2px solid var(--rota-rule-strong);
}

/* Alternate pharmacy groups take a faint tint, so one pharmacy can be traced down a grid that is
   eighty columns wide. The edge above says where a group ends; this says which group you are in,
   which is the question you have while reading rather than while scanning.

   PAINTED ON THE <col>, AND IT HAS TO BE. CSS lays table backgrounds cell > row > column > table,
   so a column background sits under everything a cell or a row paints. That ordering is the whole
   design:

     · td.gap and td.pending have opaque backgrounds, so status covers the band completely. A gap
       is what this grid exists to surface and nothing structural may dilute it.
     · Booked and "no cover needed" cells paint no background — deliberately, see below — so the
       band is what shows through them. That is where the grid gets its colour, and it is colour
       that means "this is one pharmacy", not colour that means a state.
     · The weekend row would have covered it, being a row; hence --rota-weekend-veil.

   Anyone adding a cell background here should know they are painting over this. */
.rota-grid col.band {
    background: var(--rota-band-bg);
}

/* Status. Colour is the fast signal; the text in the cell is the real one, so
   the grid still reads correctly in greyscale or with colour vision deficiency.

   THERE IS DELIBERATELY NO `td.booked` RULE. A booked cell is a plain cell with a name in
   it, and the name is the whole of what it has to say.

   It used to carry a green wash, and the wash was the reason gaps did not pop. About 90% of
   cells are booked, so the green was not marking anything — it was the background, and the
   cells it left white were the "no cover needed" ones, which made the least interesting
   column on the screen the most visually salient. A gap was then one pale tint among eighty
   columns of pale tint. With the wash gone the grid has exactly two coloured things in it,
   red and amber, and both mean somebody has to do something.

   Three further things follow, and each is a reason not to put it back:

     · §9 of the spec says the grid should feel like Excel, and a spreadsheet does not colour
       a cell for containing a value. Colour there means exception, which is now what it means
       here.
     · The printed sheet never had a booked fill (§5 below), so screen and paper now agree
       instead of quietly disagreeing.
     · The weekend tint is painted on the row and a cell background covers it. Booked cells
       covered it on five days in seven, so the banding only showed on the sparse rows. It now
       runs the width of the grid, which is what it was for: "which row am I on" across eighty
       columns.

   Nothing is lost that colour was carrying. Booked keeps its green on the counts, the stack
   bar and the coverage meter, where it separates three or four things rather than 2,500, and
   the legend states the four cell appearances regardless. */

/* A gap is the one thing on this screen somebody is hunting for, and it is being hunted across
   eighty columns of pale tints. A fill alone does not survive that: at the edge of vision one
   pale rectangle looks like another. The solid rule down the leading edge is a shape, and shape
   is what the eye picks out of a field. It is also the channel that carries the 3:1 that a tint
   cannot — #B42318 measures 4.40:1 against the tint and 6.57:1 against plain surface, where the
   tint itself is 1.49:1 and was never doing that job. Against the plain surface a booked cell
   now leaves it measures 6.57:1, and 5.54:1 against a weekend row.

   Person shading uses the same edge, but a gap has nobody in it and so never carries a `who-`
   class; the two cannot land on one cell. */
.rota-grid td.gap {
    background: var(--rota-gap-bg);
    color: var(--rota-gap-fg);
    font-weight: 700;
    letter-spacing: .03em;
    box-shadow: inset 3px 0 0 var(--rota-gap-rule);
}

.rota-grid td.pending {
    background: var(--rota-pending-bg);
    color: var(--rota-pending-fg);
    font-style: italic;
}

.rota-grid td.notrequired {
    color: var(--rota-notreq-fg);
    text-align: center;
}

.rota-grid td.editing {
    padding: 0;
    overflow: visible;
}

.rota-grid td.editing .mud-input-control {
    background: var(--mud-palette-surface);
}

/* The spreadsheet cursor is a promise that a value can be typed here, so it follows bookable
   rather than navigable: a "no cover needed" cell is reachable, and answers a keystroke with the
   sentence naming the key that reverses it, but nothing can be entered into it. */
.rota-grid td[data-bookable="1"] {
    cursor: cell;
}

.rota-grid td[data-navigable="1"]:hover {
    box-shadow: inset 0 0 0 1px var(--rota-rule-strong);
}

/* Focus has to survive being drawn over four different cell tints, so it is a
   solid inset ring rather than a colour change, and the row it sits in lifts
   with it — in eighty columns, "which row am I on" is the question. */
.rota-grid td:focus,
.rota-grid td:focus-visible {
    outline: 3px solid var(--rota-focus);
    outline-offset: -3px;
    position: relative;
    z-index: 1;
}

.rota-grid tbody tr:focus-within th.day {
    background: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text);
}

/* Per-person shading, on by default and switchable off from the toolbar.
   A deterministic hash of the name picks one of twelve hues at render time. It
   is never stored: the workbook this replaces died of 200,376 saved per-name
   formatting rules. The hue is a rule down the leading edge, not a fill, so it
   can never compete with status for the cell's background.

   Twelve hues over ~258 pharmacists means roughly twenty people share each one. That is fine and
   is what it is for: the hue answers "is this the same person as the cell above" while your eye is
   already on one column, not "who is this" — the name in the cell says that. */
.rota-grid.shade-people td[class*="who-"] {
    box-shadow: inset 4px 0 0 hsl(var(--rota-hue) var(--rota-person-s) var(--rota-person-l));
}

/* Both are box-shadow, and `.rota-grid.shade-people td[class*="who-"]` and
   `.rota-grid td[data-navigable="1"]:hover` are both (0,3,1) — so whichever came last in the file
   won outright and the other vanished. Shading came last, so turning it on silently cost every
   shaded cell its hover ring. They are not alternatives; compose them. */
.rota-grid.shade-people td[class*="who-"][data-navigable="1"]:hover {
    box-shadow:
        inset 4px 0 0 hsl(var(--rota-hue) var(--rota-person-s) var(--rota-person-l)),
        inset 0 0 0 1px var(--rota-rule-strong);
}

.rota-grid td.who-0 { --rota-hue: 210deg; }
.rota-grid td.who-1 { --rota-hue: 340deg; }
.rota-grid td.who-2 { --rota-hue: 32deg; }
.rota-grid td.who-3 { --rota-hue: 265deg; }
.rota-grid td.who-4 { --rota-hue: 158deg; }
.rota-grid td.who-5 { --rota-hue: 12deg; }
.rota-grid td.who-6 { --rota-hue: 288deg; }
.rota-grid td.who-7 { --rota-hue: 190deg; }
.rota-grid td.who-8 { --rota-hue: 52deg; }
.rota-grid td.who-9 { --rota-hue: 318deg; }
.rota-grid td.who-10 { --rota-hue: 128deg; }
.rota-grid td.who-11 { --rota-hue: 238deg; }

/* --------------------------------------------------------------------------
   5. The printable pharmacy sheet
   --------------------------------------------------------------------------
   The data and semantics are shared, but the reading conditions are not. On
   screen the grid has comfortable rows and owns its horizontal scroll. On
   paper it is tightened just enough to fit one A4 landscape sheet and remains
   legible on a dispensary wall.
   -------------------------------------------------------------------------- */

.pharmacy-toolbar .pharmacy-select {
    width: 280px;
    max-width: 100%;
}

@media (max-width: 600px) {
    .pharmacy-toolbar .pharmacy-select,
    .pharmacy-toolbar .rota-monthpicker {
        width: 100%;
    }

}

.print-sheet {
    background: var(--mud-palette-surface);
    color: var(--mud-palette-text-primary);
    padding: 24px 26px 18px;
}

.print-sheet .sheet-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 16px;
    margin-bottom: 14px;
}

.print-sheet h2 {
    font-size: 1.375rem;
    font-weight: 600;
    margin: 0;
}

.print-sheet .sheet-title p {
    margin: 2px 0 0;
    color: var(--mud-palette-text-secondary);
    font-size: .9375rem;
}

.print-sheet .sheet-summary {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 3px 14px;
    margin: 0;
    padding: 8px 11px;
    border-inline-start: 4px solid var(--rota-booked-rule);
    background: var(--mud-palette-background-gray);
    font-size: .875rem;
    font-variant-numeric: tabular-nums;
}

.print-sheet .sheet-summary.has-gaps {
    border-inline-start-color: var(--rota-gap-rule);
}

.print-sheet .sheet-summary.has-gaps strong {
    color: var(--rota-gap-fg);
}

.print-sheet .sheet-summary span {
    color: var(--mud-palette-text-secondary);
}

.print-sheet .sheet-key {
    display: flex;
    flex-wrap: wrap;
    gap: 5px 20px;
    margin: 0 0 12px;
    padding: 8px 10px;
    border: 1px solid var(--rota-rule);
    background: var(--rota-head-bg);
    color: var(--mud-palette-text-secondary);
    font-size: .8125rem;
    line-height: 1.35;
}

.print-sheet .sheet-key b,
.print-sheet .sheet-key em {
    color: var(--mud-palette-text-primary);
}

.pharmacy-grid-scroll {
    max-width: 100%;
    overflow: auto;
    overscroll-behavior-inline: contain;
    border: 1px solid var(--rota-rule-strong);
    border-radius: 4px;
    transition: opacity .12s ease-out;
}

.pharmacy-grid-scroll:focus-visible {
    outline-offset: 3px;
}

.pharmacy-grid-scroll.is-loading {
    opacity: .62;
}

.print-grid {
    border-collapse: collapse;
    width: 100%;
    min-width: 640px;
    font-size: 14px;
    line-height: 1.3;
    font-variant-numeric: tabular-nums;
}

.print-grid th,
.print-grid td {
    border: 0;
    border-right: 1px solid var(--rota-rule);
    border-bottom: 1px solid var(--rota-rule);
    padding: 8px 10px;
    text-align: left;
}

.print-grid tr > :last-child {
    border-right: 0;
}

.print-grid tbody tr:last-child > * {
    border-bottom: 0;
}

.print-grid thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--rota-head-bg);
    color: var(--rota-head-fg);
    font-size: 13px;
    font-weight: 700;
    line-height: 1.3;
    border-bottom: 2px solid var(--rota-rule-strong);
}

.print-grid tbody th {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--mud-palette-surface);
    white-space: nowrap;
    font-weight: 600;
}

.print-grid thead th:first-child {
    left: 0;
    z-index: 3;
}

.print-grid th:first-child {
    width: 104px;
}

.print-grid th:not(:first-child),
.print-grid td {
    min-width: 155px;
}

/* `.print-grid tr.weekend td` is (0,2,2) and `.print-grid td.gap` is (0,2,1), so the weekend
   grey was winning and every GAP falling on a Saturday or Sunday printed grey — measured at
   rgb(232,236,241), the weekend token, on the sheet that goes on a dispensary wall. Most gaps
   fall at the weekend. The month grid learnt this once and painted the tint on the row instead;
   this sheet paints per cell, so the gap rule is raised to match instead. */
.print-grid tr.weekend td,
.print-grid tr.weekend th {
    background: var(--rota-weekend-bg);
}

.print-grid tr td.gap,
.print-grid td.gap {
    background: var(--rota-gap-bg);
    color: var(--rota-gap-fg);
    font-weight: 700;
    text-align: center;
    box-shadow: inset 3px 0 0 var(--rota-gap-rule);
}

.print-grid td.notrequired {
    color: var(--rota-notreq-fg);
    text-align: center;
}

.print-sheet .sheet-foot {
    font-size: .75rem;
    margin-top: 10px;
    margin-bottom: 0;
    color: var(--mud-palette-text-secondary);
}

@media print {
    .no-print,
    .mud-appbar,
    .mud-drawer,
    .mud-snackbar-provider,
    .skip-link {
        display: none !important;
    }

    .mud-main-content {
        padding: 0 !important;
        margin: 0 !important;
    }

    .print-sheet {
        padding: 0;
        background: #fff;
        color: #000;
    }

    .print-sheet h2 {
        font-size: 16pt;
    }

    .print-sheet .sheet-title p {
        color: #333;
        font-size: 10pt;
    }

    .print-sheet .sheet-head {
        margin-bottom: 7px;
    }

    .print-sheet .sheet-summary {
        padding: 5px 8px;
        border-inline-start-color: #000;
        background: #fff;
        color: #000;
        font-size: 9pt;
    }

    .print-sheet .sheet-summary.has-gaps strong,
    .print-sheet .sheet-summary span {
        color: #000;
    }

    .print-sheet .sheet-key {
        gap: 4px 16px;
        margin-bottom: 7px;
        padding: 5px 7px;
        border-color: #999;
        background: #f4f4f4;
        color: #222;
        font-size: 8pt;
    }

    .print-sheet .sheet-key b,
    .print-sheet .sheet-key em {
        color: #000;
    }

    .pharmacy-grid-scroll {
        overflow: visible;
        border: 1px solid #777;
        border-radius: 0;
    }

    .print-grid {
        min-width: 0;
        font-size: 9pt;
        line-height: 1.2;
    }

    .print-grid th,
    .print-grid td {
        position: static;
        min-width: 0;
        padding: 3px 6px;
        border-color: #999;
    }

    .print-grid thead th {
        background: #e8e8e8;
        color: #000;
        font-size: 8.5pt;
        border-bottom: 2px solid #555;
    }
    .print-grid tr.weekend td, .print-grid tr.weekend th { background: #f4f4f4; }
    /* Same specificity trap as on screen, and the same fix. On paper the box-shadow is dropped
       by most print pipelines, so the edge is a real border here. */
    .print-grid tr td.gap,
    .print-grid td.gap {
        background: #ffd9d3;
        color: #000;
        border-left: 3px solid #000;
    }
    .print-grid td.notrequired { color: #666; }
    .print-grid tbody tr { break-inside: avoid; }
    .print-sheet .sheet-foot {
        margin-top: 6px;
        color: #333;
        font-size: 7.5pt;
    }
    thead { display: table-header-group; }

    @page {
        size: A4 landscape;
        margin: 8mm;
    }
}

/* --------------------------------------------------------------------------
   6. Comparison cards (one pharmacist's month, up to three side by side)
   -------------------------------------------------------------------------- */

.compare-table {
    width: 100%;
    border-collapse: collapse;
    font-size: .8125rem;
}

.compare-table th,
.compare-table td {
    padding: 4px 8px;
    border-bottom: 1px solid var(--rota-rule);
    text-align: left;
    vertical-align: top;
}

.compare-table thead th {
    position: sticky;
    top: 0;
    background: var(--rota-head-bg);
    font-size: .6875rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--rota-head-muted);
    z-index: 1;
}

.compare-table tr.weekend td { background: var(--rota-weekend-bg); }

.compare-table tr.clash td {
    background: var(--rota-pending-bg);
    color: var(--rota-pending-fg);
}

.compare-table .shift-code {
    font-size: .6875rem;
    color: var(--mud-palette-text-secondary);
}

.compare-scroll {
    max-height: 62vh;
    overflow: auto;
}

/* --------------------------------------------------------------------------
   7. Blazor's own error and reconnect surfaces
   -------------------------------------------------------------------------- */

#blazor-error-ui {
    color-scheme: light only;
    background: #fff4d6;
    border-top: 3px solid #b54708;
    color: #40260a;
    bottom: 0;
    box-shadow: 0 -2px 8px rgba(0, 0, 0, .18);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: .85rem 3rem .9rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 3000;
    font-size: .875rem;
}

#blazor-error-ui .reload {
    color: #7a3406;
    font-weight: 600;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 1rem;
    top: .7rem;
    font-size: 1rem;
    line-height: 1;
    background: none;
    border: 0;
    color: inherit;
    padding: 4px 8px;
}

.blazor-error-boundary {
    background: #b42318;
    color: #fff;
    padding: 1rem 1.25rem;
    border-radius: 6px;
}

.blazor-error-boundary::after {
    content: "Something went wrong on this page. Reload to try again.";
}

/* --- rotating postings: one day-strip per week of the cycle --- */
.daystrip + .daystrip { margin-top: 2px; }

.weeklabel {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.1rem;
    margin-right: .25rem;
    font-size: .62rem;
    font-weight: 700;
    color: var(--mud-palette-text-secondary);
}

/* --- standing arrangement editor: one row per week of the cycle --- */
.posting-days th { font-weight: 600; font-size: .72rem; text-align: center; }
.posting-days tbody th { text-align: left; white-space: nowrap; padding-right: .75rem; }
.posting-days td { text-align: center; padding: 0; }

/* A file input is unstyleable, so the real one is hidden inside a label that carries the button's
   appearance. The label stays a real <label>, so clicking the text opens the picker and a screen
   reader still announces the control it wraps. */
.rota-file-button {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-height: var(--rota-control-h, 40px);
    padding: 0 16px;
    border-radius: var(--mud-default-borderradius, 4px);
    background: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text);
    font-size: 0.875rem;
    font-weight: 500;
    cursor: pointer;
}

.rota-file-button input[type="file"] {
    /* Not display:none — that removes it from the accessibility tree and from tab order. */
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

.rota-file-button:hover { filter: brightness(1.08); }

.rota-file-button:focus-within {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

.rota-file-button.is-busy {
    background: var(--mud-palette-action-disabled-background);
    color: var(--mud-palette-text-disabled);
    cursor: progress;
}

/* --- Target size (WCAG 2.2 AA, 2.5.8) -------------------------------------------------------
   A standalone call to action needs a 24x24 CSS px target. The overview tiles and the coverage
   table end in caption-sized links — "Fill them", "See the rota" — which measured 15px tall.
   They are not the inline-links-in-a-sentence exception: each is the tile's one action.

   The text stays caption-sized; only the hit area grows, so nothing about the layout changes.
   Deliberately not applied to every MudLink: a link inside a sentence is exempt, and padding
   those would break the line box it sits in.

   Not covered here, and exempt rather than overlooked: the year stepper on /planning is 20x20,
   and 2.5.8 allows an undersized control when the same function is available another way — the
   year can be typed into the field beside it. */
.rota-tile a.mud-typography-caption,
.rota-needs-cover a.mud-typography-caption {
    display: inline-flex;
    align-items: center;
    min-height: 24px;
}

/* MudBlazor's pager select is 30x20. It is the only way to change the page size, so there is no
   equivalent to fall back on. */
.mud-table-pagination .mud-select-input,
.mud-table-pagination .mud-input-control {
    min-height: 24px;
}
/* Audit JSON is deliberately constrained: expanded state should be inspectable without turning a
   single long value into a page-wide horizontal scroll. */
.rota-audit-json {
    margin: .35rem 0 0;
    padding: .65rem;
    max-height: 18rem;
    overflow: auto;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    border-radius: .25rem;
    background: var(--mud-palette-background-gray);
    font-size: .75rem;
}

/* ── Profile pages ──────────────────────────────────────────────────────────
   /pharmacists/{id} and /pharmacies/{id}. Everything about one person or one
   pharmacy on one surface, because the job is a cross-check: the areas decide
   who can be asked, the never-works days and time off decide when, and the
   standing arrangements decide what is already committed. Those three
   constrain each other, so they have to be visible together. Tabs would
   rebuild the six-screen hop inside one page.

   Two columns above 1264px. The layout has no max-width and measured 1,656px
   of content at 1920, so there is room. Below that it stacks, and the setup
   trio stays first either way. */
.rota-profile {
    display: grid;
    gap: 0 1.25rem;
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
}

/* A grid child defaults to min-width:auto, so it refuses to shrink below its widest
   content and the column grows to fit. On a 390px phone that pushed the whole page
   95px sideways off a table inside a card. The page body must never scroll sideways;
   wide content scrolls inside its own box below. */
.rota-profile > * {
    min-width: 0;
}

/* Tables here are wider than a phone and must take their scrolling with them. */
.rota-profile table {
    display: block;
    overflow-x: auto;
    width: 100%;
}

/* A card's title and its controls sit on one line until they cannot. The month picker
   is four controls wide and was hanging off the right edge of a phone. */
@media (min-width: 1264px) {
    .rota-profile {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    }
}

/* The identity stays put while seven cards scroll past. Without it a
   coordinator halfway down the page cannot see whose record they are editing,
   which on a 727-name register is a real way to change the wrong person. */
.rota-profile-header {
    position: sticky;
    top: 0;
    z-index: 3;
    background: var(--mud-palette-background);
    padding: .5rem 0 .75rem;
    border-bottom: 1px solid var(--mud-palette-lines-default);
    margin-bottom: 1rem;
}

@media print {
    .rota-profile-header {
        position: static;
    }

    .rota-profile {
        grid-template-columns: 1fr;
    }
}

/* Skip links across the cards. Eight cards is a long keyboard journey and this
   is the one screen a coordinator works through with the keyboard. */
.rota-profile-contents {
    display: flex;
    flex-wrap: wrap;
    gap: .375rem;
    margin-bottom: 1rem;
}

.rota-card-empty {
    display: flex;
    align-items: flex-start;
    gap: .5rem;
    padding: .625rem .75rem;
    border-radius: .375rem;
    background: var(--mud-palette-background-gray);
}

/* The days somebody never works, as a control rather than a badge.

   Deliberately not .daystrip: those squares are 18px and aria-hidden, which is
   fine for a read-only badge and fails 2.2 AA target size (24x24) the moment
   they can be pressed. These are 40px, carry the full day name as their
   accessible name, and put the state in aria-pressed rather than only in a
   class — "M T W T F S S" has two Ts and two Ss and cannot name a control. */
.rota-daytoggle {
    display: flex;
    flex-wrap: wrap;
    gap: .375rem;
    border: 0;
    padding: 0;
    margin: 0;
}

.rota-daytoggle button {
    min-width: 40px;
    min-height: 40px;
    padding: 0 .5rem;
    border-radius: .375rem;
    border: 1px solid var(--mud-palette-lines-default);
    background: var(--mud-palette-surface);
    color: var(--mud-palette-text-primary);
    font-size: .8125rem;
    font-weight: 600;
    cursor: pointer;
}

.rota-daytoggle button:hover:not(:disabled) {
    border-color: var(--mud-palette-primary);
}

.rota-daytoggle button:disabled {
    cursor: default;
    opacity: .6;
}

/* Pressed carries a border, a fill and a weight change, so the state does not
   rest on hue alone. */
.rota-daytoggle button[aria-pressed="true"] {
    background: var(--mud-palette-primary);
    border-color: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text);
}

.rota-daytoggle button:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

/* Where you just landed. The section chips move the viewport and the focus, and neither
   is visible to somebody using a mouse — with two columns a jump can even land at the
   same scroll position as the card beside it. Respects reduced motion, and fades to
   nothing so it never becomes permanent state. */
.rota-jumped {
    animation: rota-jumped 1.4s ease-out;
}

@keyframes rota-jumped {
    from { box-shadow: 0 0 0 2px var(--mud-palette-primary); }
    to { box-shadow: 0 0 0 2px transparent; }
}

@media (prefers-reduced-motion: reduce) {
    .rota-jumped {
        animation: none;
        outline: 2px solid var(--mud-palette-primary);
        outline-offset: 1px;
    }
}

/* Booked, and the pharmacist has told us they are away that day.

   Not a gap and not a mistake — a decision nobody has made yet, so it must not
   wear the gap colour or it would be counted as work to do. A corner wedge and
   a dotted underline, which read on top of the twelve person colours the grid
   already uses without fighting any of them. The tooltip and the leave screen
   carry the words; this only says "look here". */
.rota-grid td.says-away {
    position: relative;
    text-decoration: underline dotted var(--mud-palette-warning);
    text-underline-offset: 2px;
}

.rota-grid td.says-away::after {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    border-width: 0 6px 6px 0;
    border-style: solid;
    border-color: transparent var(--mud-palette-warning) transparent transparent;
}

/* G6: a gap a pharmacist can take without a coordinator. Still a gap, so it keeps the gap colour;
   a lightning mark after the word says "anybody can take this", and the tooltip says it in words. */
.rota-grid td.gap.first-come::after {
    content: " \26A1";
    font-weight: 400;
}

@media (forced-colors: active) {
    .rota-grid td.gap.first-come::after {
        content: " (gap, first come)";
    }
}

/* ---------------------------------------------------------------------------------------------
   Sign-in pages (ADR-24)

   Standalone, because these render statically and outside MudBlazor entirely — see SignInLayout
   for why. Everything here is scoped under .signin-page so none of it can reach the application.
   --------------------------------------------------------------------------------------------- */

.signin-page {
    --signin-muted: #566171;
    --signin-link: var(--signin-link-light, #0f4c81);
    --signin-focus: var(--signin-focus-light, #258514);
    min-height: 100vh;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 3rem 1rem;
    background: var(--rota-signin-page, #f6f8fa);
    color: #1a1f27;
    font: 16px/1.5 Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
}

@media (prefers-color-scheme: dark) {
    .signin-page {
        --signin-muted: #9aa4b2;
        --signin-link: var(--signin-link-dark, #91b8dd);
        --signin-focus: var(--signin-focus-dark, #91bd89);
        background: #14171c;
        color: #e6e9ee;
    }
}

.signin-card {
    width: 100%;
    max-width: 26rem;
    background: #fff;
    border: 1px solid #dce2e9;
    border-radius: 8px;
    padding: 1.75rem;
}

@media (prefers-color-scheme: dark) {
    .signin-card { background: #1c2027; border-color: #313842; }
}

.signin-brand { display: flex; align-items: center; gap: .5rem; margin-bottom: 1.5rem; }
.signin-brand img { height: 32px; width: auto; }
.signin-brand-name { font-weight: 600; }
.signin-brand-product { color: var(--signin-muted); }

.signin-card h1 { font-size: 1.375rem; margin: 0 0 .5rem; }
.signin-lead { color: var(--signin-muted); margin: 0 0 1.25rem; }

.signin-field { margin: 0 0 1rem; }
.signin-field label { display: block; font-weight: 500; margin-bottom: .3rem; }

.signin-field input {
    width: 100%;
    /* 44px, which is the minimum target the rest of the site is held to. */
    min-height: 44px;
    padding: .5rem .7rem;
    font: inherit;
    color: inherit;
    background: transparent;
    border: 1px solid #97a2b0;
    border-radius: 4px;
}

.signin-field input:focus-visible,
.signin-card button:focus-visible,
.signin-card a:focus-visible {
    outline: 2px solid var(--signin-focus);
    outline-offset: 3px;
}

.signin-field input:focus-visible {
    border-color: var(--signin-focus);
}

.signin-card button[type="submit"], .signin-button {
    display: inline-block;
    min-height: 44px;
    padding: .6rem 1.2rem;
    font: inherit;
    font-weight: 500;
    color: var(--signin-brand-text, #fff);
    background: var(--signin-brand, #0f4c81);
    border: 0;
    border-radius: 4px;
    cursor: pointer;
    text-decoration: none;
}

.signin-card button[type="submit"]:hover, .signin-button:hover { filter: brightness(1.1); }

.signin-warn, .signin-note {
    border-radius: 4px;
    padding: .6rem .8rem;
    margin: 0 0 1.25rem;
    font-size: .9375rem;
}

.signin-warn { background: #fdecea; color: #a3271f; }
.signin-note { background: #eef2f7; color: #3d4756; }

.signin-aside { margin: 1.5rem 0 0; font-size: .875rem; color: var(--signin-muted); }
.signin-link { color: var(--signin-link); text-decoration: underline; }

.signin-codes {
    list-style: none;
    padding: .75rem;
    margin: 0 0 1rem;
    border: 1px solid #dce2e9;
    border-radius: 4px;
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: .35rem .75rem;
    font-family: ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace;
    font-size: 1rem;
    letter-spacing: .08em;
}

.signin-secret {
    font-family: ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace;
    font-size: 1.0625rem;
    letter-spacing: .12em;
    word-break: break-all;
    background: #eef2f7;
    color: #1a1f27;
    padding: .6rem .8rem;
    border-radius: 4px;
    margin: 0 0 1rem;
}

/* The security page is the one sign-in-family page with lists on it. */

.signin-card.signin-wide { max-width: 36rem; }

.signin-card h2 {
    font-size: 1.0625rem;
    margin: 1.75rem 0 .35rem;
    padding-top: 1.25rem;
    border-top: 1px solid #dce2e9;
}

@media (prefers-color-scheme: dark) {
    .signin-card h2 { border-top-color: #313842; }
}

/* The rule is a separator between sections, so the first one has nothing to separate from. */
.signin-card h2:first-of-type { border-top: 0; padding-top: 0; margin-top: 1.25rem; }

.signin-list { list-style: none; padding: 0; margin: 0 0 1rem; }

.signin-list li {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: .6rem 0;
    border-bottom: 1px solid #dce2e9;
}

@media (prefers-color-scheme: dark) {
    .signin-list li { border-bottom-color: #313842; }
}

.signin-list form { margin: 0; }

/* Specific enough to beat `.signin-card button[type="submit"]`, which it otherwise loses to —
   the quiet buttons rendered as filled primaries, which made two alternatives look like the
   main action. */
.signin-card button.signin-quiet, .signin-quiet {
    display: inline-block;
    min-height: 44px;
    padding: .5rem 1rem;
    font: inherit;
    font-weight: 400;
    color: inherit;
    background: transparent;
    border: 1px solid #97a2b0;
    border-radius: 4px;
    cursor: pointer;
}

.signin-card button.signin-quiet:hover { background: #eef2f7; }

@media (prefers-color-scheme: dark) {
    .signin-card button.signin-quiet:hover { background: #262c35; }
}

/* Each form is its own block, so stacked buttons do not sit on top of one another. */
.signin-card form { margin: 0 0 .75rem; }
.signin-card form:last-of-type { margin-bottom: 0; }

/* An aside that is followed by a form, rather than ending a section. */
.signin-aside.signin-spaced { margin-bottom: 1.25rem; }
