/* ============================================================
   MudBlazor, retuned for the Monochrome system.

   Principles applied here:
     · borders, not shadows — shadows only on floating layers
     · 32px controls, 40px table rows
     · one accent, and it is ink — near-black on the light build,
       white on the dark one; reserved for selection + focus
     · colour ONLY where it carries state (success/warning/error)
     · 110ms transitions on state, none on layout

   Keep selectors specific so MudBlazor upgrades don't silently
   regress.
   ============================================================ */

.mud-appbar {
    backdrop-filter: saturate(140%) blur(12px);
    border-bottom: 1px solid var(--apo-border);
}

/* Our sidebar is NOT a <MudDrawer> — it is a custom <aside class="apo-app-shell__drawer">
   that hosts a <MudNavMenu>. So every selector below uses .apo-nav (the inner container
   in NavMenu.razor) as the ancestor — NOT .mud-drawer, which would never match.

   The sidebar is theme-aware (a recessed paper panel in light mode, the deepest
   surface in dark), so colours come from --apo-nav-* tokens rather than from
   hardcoded rgba-on-navy. Nothing below assumes a dark background. */
.apo-app-shell__drawer {
    box-shadow: none !important;
}

/* Rows are inset rounded rectangles, the shape Linear settled on: 8px margin
   plus 8px padding keeps the icon on the sidebar's 16px axis with the masthead
   and section labels, while selection and hover paint a soft rounded fill
   with no marker bar and no border — the fill IS the whole signal. An ink wash
   rather than a tinted one: on a paper sidebar a coloured selection is the one
   thing on screen that would have to be explained. */
.apo-nav .mud-nav-link {
    position: relative;
    color: var(--apo-nav-fg) !important;
    background: transparent !important;
    border: none;
    border-radius: var(--apo-radius-md);
    padding: 0 8px;
    margin: 1px 8px;
    /* 30px, down from 40px — Linear's 28-32px register. With 32 links the
       40px rows stacked to ~1,870px and the sidebar always scrolled; the
       compact register puts the whole nav on one laptop screen. */
    min-height: 30px;
    height: 30px;
    font-size: var(--apo-text-body);
    font-weight: 450;
    letter-spacing: -0.006em;
    /* MudBlazor's own rule sets align-items:flex-start on nav links. With an
       explicit row height that pinned the icon 9.5px and the label 6.3px above
       the row's centre — the icon looked glued to the top edge. Both axes must
       be stated here or the default wins. */
    display: flex;
    align-items: center;
    transition: background var(--apo-duration-fast) var(--apo-ease),
                color var(--apo-duration-fast) var(--apo-ease);
}

/* MudBlazor wraps the label in a span whose line-height (21.3px) exceeds the
   glyph box; without this it re-introduces a sub-pixel vertical drift. */
.apo-nav .mud-nav-link .mud-nav-link-text {
    line-height: 1.2;
    display: block;
}

/* Force the label span to inherit link colour. MudBlazor's default repaints
   active links with var(--mud-palette-primary), which would override ours. */
.apo-nav .mud-nav-link .mud-nav-link-text {
    color: inherit !important;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Icons carry their own colour at full opacity. The old 0.35 opacity dropped
   them to ~1.8:1 against the sidebar — the icon column effectively rendered
   empty, and no amount of accent work elsewhere compensates for a nav whose
   glyphs you cannot see. Selection is signalled by the marker, not by dimming
   everything that isn't selected. */
.apo-nav .mud-nav-link .mud-nav-link-icon {
    color: var(--apo-nav-icon) !important;
    opacity: 1;
    font-size: var(--apo-icon-sm) !important;
    margin-inline-end: 10px;
    flex: 0 0 auto;
    transition: opacity var(--apo-duration-fast) var(--apo-ease),
                color var(--apo-duration-fast) var(--apo-ease);
}

.apo-nav .mud-nav-link:hover {
    background: var(--apo-nav-hover-bg) !important;
    color: var(--apo-nav-hover-fg) !important;
}

.apo-nav .mud-nav-link:hover .mud-nav-link-icon {
    color: var(--apo-nav-hover-fg) !important;
}

/* Keyboard parity with hover — the ink ring, inset so it never clips against
   the sidebar edge. */
.apo-nav .mud-nav-link:focus-visible {
    background: var(--apo-nav-hover-bg) !important;
    color: var(--apo-nav-hover-fg) !important;
    outline: 2px solid var(--apo-nav-marker);
    outline-offset: -2px;
}

/* Active state — full override so we win against MudBlazor's default
   `:not(.mud-nav-link-disabled)` rule that repaints text with the palette
   primary. */
.apo-nav .mud-nav-link.active,
.apo-nav .mud-nav-link.mud-nav-link-active,
.apo-nav .mud-nav-link.active:hover,
.apo-nav .mud-nav-link.mud-nav-link-active:hover,
.apo-nav .mud-nav-link.active:not(.mud-nav-link-disabled),
.apo-nav .mud-nav-link.mud-nav-link-active:not(.mud-nav-link-disabled) {
    background: var(--apo-nav-active-bg) !important;
    border: none;
    color: var(--apo-nav-active-fg) !important;
    font-weight: 550 !important;
    box-shadow: none;
}

/* 450 -> 550 thickens the stems slightly; the tracking compensates so the
   selected label occupies the same width as its idle state and the row does
   not appear to shift on navigation. */
.apo-nav .mud-nav-link.active .mud-nav-link-text,
.apo-nav .mud-nav-link.mud-nav-link-active .mud-nav-link-text {
    color: var(--apo-nav-active-fg) !important;
    letter-spacing: -0.002em;
}

/* The selected icon takes the full-strength ink the label does, so the row
   reads as one object changing state rather than as a label and a glyph that
   happened to brighten together. */
.apo-nav .mud-nav-link.active .mud-nav-link-icon,
.apo-nav .mud-nav-link.mud-nav-link-active .mud-nav-link-icon {
    color: var(--apo-nav-active-icon) !important;
    opacity: 1;
}

.apo-nav .mud-nav-group-title {
    font-family: var(--apo-font-body);
    font-size: var(--apo-text-caption1);
    font-weight: 500;
    text-transform: none;
    letter-spacing: var(--apo-tracking-caption);
    color: var(--apo-nav-fg-muted);
    padding: 12px 8px 4px;
}

/* Every nav link sits inside a MudTooltip whose label only arms while the
   rail is collapsed. The tooltip root defaults to inline-block, which would
   shrink-wrap the row; block restores the full-bleed hit target. */
.apo-nav .mud-tooltip-root {
    display: block;
}

/* Collapsed 56px icon rail (desktop only): labels vanish, icons centre in
   the 40px row the 8px margins leave. Dual selector as in
   MainLayout.razor.css — data-collapsed is Blazor's state, the html
   attribute is nav.js's pre-boot localStorage mirror. */
@media (min-width: 1024px) {
    html[data-nav-collapsed="true"] .apo-nav .mud-nav-link,
    .apo-app-shell[data-collapsed="true"] .apo-nav .mud-nav-link {
        justify-content: center;
        padding: 0;
    }

    html[data-nav-collapsed="true"] .apo-nav .mud-nav-link .mud-nav-link-text,
    .apo-app-shell[data-collapsed="true"] .apo-nav .mud-nav-link .mud-nav-link-text {
        display: none;
    }

    html[data-nav-collapsed="true"] .apo-nav .mud-nav-link .mud-nav-link-icon,
    .apo-app-shell[data-collapsed="true"] .apo-nav .mud-nav-link .mud-nav-link-icon {
        margin-inline-end: 0;
    }
}


/* ============================================================
   BUTTONS — one control: flat, edge-lit, still.

   The register is the command button in a modern desktop-class
   product: a 36px rectangle with a 10px corner, a flat fill, a
   hairline edge one step darker than that fill, and NOTHING
   underneath it. Its authority is restraint. No gradient, no drop
   shadow, no lift on hover — the only thing that changes when you
   point at it is the colour of the surface.

   What this replaced: a stadium pill with a top-lit gradient, a
   two-layer drop shadow and a 1px hover lift. Each of those is a
   device for making a control look tactile, and stacking three of
   them is what makes an interface read as a template. Removing
   them costs nothing legible — the button is still obviously a
   button — and buys the flat, dense, deliberate surface that
   enterprise software is actually judged on.

   Three variants, ONE geometry. Filled is the commit, Outlined the
   alternative, Text the exit. They differ only in how much surface
   they claim; height, corner and type are identical, because a
   dialog footer showing all three has to read as one row of
   controls rather than three components that happen to be adjacent.
   ============================================================ */
.mud-button {
    position: relative;
    isolation: isolate;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 500;
    letter-spacing: var(--apo-tracking-body);
    font-size: var(--apo-text-body);
    text-transform: none;
    border-radius: var(--apo-radius-control) !important;
    min-height: var(--apo-control-h);
    padding-inline: 14px;
    /* Declared on every variant, transparent by default, so the three
       variants never differ by a pixel of box size — only by whether
       that border is painted. */
    border: 1px solid transparent;
    box-shadow: none;
    -webkit-tap-highlight-color: transparent;
    /* Colour is the only channel. Nothing here animates geometry: a control
       that jumps under the cursor drags its label off the baseline it shares
       with every other control in the row, and the jump is the single most
       recognisable tell of a stock component library. */
    transition: background var(--apo-duration-fast) var(--apo-ease),
                border-color var(--apo-duration-fast) var(--apo-ease),
                color var(--apo-duration-fast) var(--apo-ease),
                box-shadow var(--apo-duration-fast) var(--apo-ease),
                opacity var(--apo-duration-fast) var(--apo-ease);
}

.mud-button .mud-button-label {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}

.mud-button .mud-button-icon-start,
.mud-button .mud-button-icon-end {
    font-size: var(--apo-icon-sm);
}

.mud-button .mud-button-icon-start {
    margin-inline-start: -2px;
    margin-inline-end: 8px;
}

.mud-button .mud-button-icon-end {
    margin-inline-start: 8px;
    margin-inline-end: -2px;
}

/* --- FILLED: the commit -------------------------------------------
   A flat black fill (white in dark mode) with an edge one step darker
   than itself. That edge is doing the job the drop shadow used to: it
   is what stops a dark rectangle from bleeding into a white page, and
   unlike a shadow it stays a crisp single pixel at every zoom level
   and in every screenshot. */
.mud-button-filled.mud-button-filled-primary,
.mud-button.mud-button-filled.mud-button-filled-secondary {
    background: var(--apo-cta-bg) !important;
    color: var(--apo-cta-fg) !important;
    border-color: var(--apo-cta-edge) !important;
    box-shadow: none !important;
}

.mud-button-filled.mud-button-filled-primary:hover,
.mud-button.mud-button-filled.mud-button-filled-secondary:hover {
    background: var(--apo-cta-hover) !important;
    border-color: var(--apo-cta-edge-hover) !important;
}

/* Pressed goes deeper still, so the three states are one direction of
   travel rather than a lift and a drop. */
.mud-button-filled.mud-button-filled-primary:active,
.mud-button.mud-button-filled.mud-button-filled-secondary:active {
    background: color-mix(in srgb, var(--apo-cta-hover) 91%, black) !important;
}

.mud-button-filled.mud-button-filled-secondary .mud-button-label,
.mud-button-filled.mud-button-filled-primary .mud-button-label {
    color: var(--apo-cta-fg) !important;
}

.mud-button:focus-visible {
    outline: none !important;
}

.mud-button-filled.mud-button-filled-primary:focus-visible,
.mud-button.mud-button-filled.mud-button-filled-secondary:focus-visible {
    box-shadow: var(--apo-button-focus-ring) !important;
}

.mud-button.mud-button-filled.mud-button-filled-secondary:disabled,
.mud-button.mud-button-filled.mud-button-filled-secondary.mud-button-disabled,
.mud-button-filled.mud-button-filled-primary:disabled,
.mud-button-filled.mud-button-filled-primary.mud-button-disabled {
    color: var(--apo-cta-fg) !important;
    box-shadow: none !important;
    opacity: 0.42;
    cursor: default;
}

/* --- OUTLINED: the alternative ------------------------------------
   Paper, not the near-paper tint it used to carry. On a white canvas
   a 3%-inked fill is invisible against the page and merely dulls the
   label; the border alone is the whole control, which is the point of
   an outlined variant. */
.mud-button-outlined {
    /* --apo-border, not --apo-border-strong. The strong step is the input
       ramp: a field has to advertise that it is empty and waiting, a button
       already advertises itself with its label. At the strong step a row of
       secondary buttons drew harder edges than the table beside them, which
       inverts the hierarchy — chrome should never out-draw data. */
    border: 1px solid var(--apo-border) !important;
    background: var(--apo-paper);
    color: var(--apo-ink-900) !important;
    box-shadow: none;
}

.mud-button-outlined:hover {
    background: var(--apo-surface-hover);
    border-color: var(--apo-border-strong) !important;
}

.mud-button-outlined:active {
    background: color-mix(in srgb, var(--apo-surface-hover) 55%, var(--apo-ink-100));
}

.mud-button-outlined:focus-visible,
.mud-button-text:focus-visible {
    box-shadow: var(--apo-button-focus-ring) !important;
}

/* --- TEXT: the exit ----------------------------------------------- */
.mud-button-text {
    color: var(--apo-ink-600) !important;
    padding-inline: 10px;
    background: transparent;
    border-color: transparent;
}

.mud-button-text:hover {
    background: var(--apo-surface-hover) !important;
    color: var(--apo-ink-900) !important;
}

.mud-button-text:active {
    background: var(--apo-ink-100) !important;
}

/* --- Status-filled actions (approve, confirm, delete) --------------
   MudBlazor computes these fills from the palette, so there is no token
   here to darken for hover. A flat translucent black laid over whatever
   the fill happens to be gives every semantic colour the same pressed
   behaviour as the slate CTA — one gradient stop repeated, which paints
   as a solid wash rather than as a sheen. */
.mud-button-filled:not(.mud-button-filled-primary):not(.mud-button-filled-secondary) {
    border-color: color-mix(in srgb, black 18%, transparent) !important;
    box-shadow: none !important;
    /* MudBlazor labels these with Colors.Shades.White, and the theme only
       overrides ContrastText for Primary and Secondary — so a filled Success
       button in DARK mode put white on #4CAF7D at about 2.4:1, well under AA.
       --apo-cta-fg is already the per-theme answer to "what reads on this
       theme's saturated fill": white on light, near-black on dark. Every dark
       semantic fill clears 6:1 with it. */
    color: var(--apo-cta-fg) !important;
}

.mud-button-filled:not(.mud-button-filled-primary):not(.mud-button-filled-secondary) .mud-button-label {
    color: var(--apo-cta-fg) !important;
}

.mud-button-filled:not(.mud-button-filled-primary):not(.mud-button-filled-secondary):hover {
    background-image: linear-gradient(rgba(0, 0, 0, 0.10), rgba(0, 0, 0, 0.10));
}

.mud-button-filled:not(.mud-button-filled-primary):not(.mud-button-filled-secondary):active {
    background-image: linear-gradient(rgba(0, 0, 0, 0.18), rgba(0, 0, 0, 0.18));
}

.mud-button-filled:not(.mud-button-filled-primary):not(.mud-button-filled-secondary):focus-visible {
    box-shadow: var(--apo-button-focus-ring) !important;
}

/* Semantic actions keep the same shape and motion; colour is the only new
   information. Tinted surfaces are calmer and more legible than stock
   Material red/green outlines against the clinical white canvas. */
.mud-button-outlined.mud-button-outlined-error {
    color: var(--apo-error) !important;
    border-color: color-mix(in srgb, var(--apo-error) 34%, var(--apo-border)) !important;
    background: color-mix(in srgb, var(--apo-error-bg) 48%, var(--apo-paper));
}

.mud-button-outlined.mud-button-outlined-error:hover {
    background: var(--apo-error-bg);
    border-color: color-mix(in srgb, var(--apo-error) 54%, var(--apo-border)) !important;
}

.mud-button-outlined.mud-button-outlined-success {
    color: var(--apo-success) !important;
    border-color: color-mix(in srgb, var(--apo-success) 34%, var(--apo-border)) !important;
    background: color-mix(in srgb, var(--apo-success-bg) 48%, var(--apo-paper));
}

.mud-button-outlined.mud-button-outlined-warning {
    color: var(--apo-warning) !important;
    border-color: color-mix(in srgb, var(--apo-warning) 34%, var(--apo-border)) !important;
    background: color-mix(in srgb, var(--apo-warning-bg) 48%, var(--apo-paper));
}

.mud-button-outlined.mud-button-outlined-info {
    color: var(--apo-info) !important;
    border-color: color-mix(in srgb, var(--apo-info) 34%, var(--apo-border)) !important;
    background: color-mix(in srgb, var(--apo-info-bg) 48%, var(--apo-paper));
}

.mud-button-outlined.mud-button-outlined-secondary {
    color: var(--apo-accent) !important;
    border-color: var(--apo-accent-line) !important;
    background: color-mix(in srgb, var(--apo-accent-soft) 58%, var(--apo-paper));
}

.mud-button-text.mud-button-text-error { color: var(--apo-error) !important; }
.mud-button-text.mud-button-text-success { color: var(--apo-success) !important; }
.mud-button-text.mud-button-text-warning { color: var(--apo-warning) !important; }
.mud-button-text.mud-button-text-info { color: var(--apo-info) !important; }
.mud-button-text.mud-button-text-secondary { color: var(--apo-accent) !important; }

/* ============================================================
   TABLES + DATA GRID — the densest surface in the product.

   Linear register (2026-08): the grid lost its card. Tables sit
   directly on the canvas — no shell border, no header band, no
   row rules. Hierarchy comes from the column header's type step
   against the rows, and from a rounded hover fill: the same
   inset-fill language the sidebar selection uses.
   ============================================================ */
.mud-table, .mud-data-grid {
    border-radius: 0;
    font-family: var(--apo-font-body);
    background: transparent;
    /* No box-shadow guard any more: the theme's Elevation array maps the
       grid's default Elevation=1 onto --apo-shadow-xs, and inside the
       .apo-data-card frame that whisper of a contact shadow is invisible —
       the stock Material level it used to fight is gone at the source. */
}

.mud-table-root .mud-table-cell {
    font-size: var(--apo-text-body);
    line-height: 1.45;
}

.mud-simple-table table th,
.mud-simple-table table td {
    padding: 8px 14px;
    border-bottom-color: var(--apo-border);
}

.mud-simple-table table th {
    color: var(--apo-ink-500);
    font-size: var(--apo-text-caption1);
    font-weight: 500;
    letter-spacing: var(--apo-tracking-caption);
    text-transform: none;
}

/* Column headers are the card's top rail: a recessed band, hairline-capped,
   visibly a different material from the rows. The BAND is what makes them
   read — a sentence-size label with no ground dissolves into the data, which
   this table already proved once.

   What changed is the type, not the device. These were 11px tracked SMALL
   CAPS, and tracked caps on a grey band is the single most recognisable
   signature of a generated admin template — every scaffolded CRUD screen
   since 2012 ships exactly that header. Sentence case at 12/600 is what the
   products this system cites actually use, and it costs nothing in
   visibility because the band is still doing that job. It also stops the
   header shouting a column name louder than the data underneath it.

   Scoped to .mud-table-head rather than .mud-data-grid: the four pages still
   on a plain <MudTable> share the same head markup. */
.mud-table-head .mud-table-cell,
.mud-data-grid .mud-table-head .mud-table-cell {
    background: var(--apo-surface-sunk);
    color: var(--apo-ink-600);
    font-weight: 600;
    font-size: var(--apo-text-caption1);
    letter-spacing: var(--apo-tracking-caption);
    text-transform: none;
    border-bottom: 1px solid var(--apo-border);
    padding: 6px 14px;
    height: 36px;
    white-space: nowrap;
}

/* Sticky column headers. The header band is already opaque
   (--apo-surface-sunk above), so pinning it costs nothing visually: with
   RowsPerPage at 50 the labels used to scroll away three rows into any
   long list. The header sticks to the top of the nearest scroll region —
   .mud-table-container when the grid scrolls inside its card (FixedHeader
   or a narrow viewport's horizontal overflow), the viewport otherwise.
   z-index keeps it above the rows' hover fills and focus rings. */
.apo-data-card .mud-table-head .mud-table-cell {
    position: sticky;
    top: 0;
    z-index: 2;
}

/* Sort affordances ride the rail quietly and answer on hover. */
.mud-table-head .mud-table-sort-label {
    color: inherit;
}

.mud-table-head .mud-table-sort-label:hover {
    color: var(--apo-ink-800);
}

/* Enterprise alignment: every cell reads from the left edge, exactly under
   its column title. start IS the default, but stating it as policy guards
   against a stray utility recentring a column; MudBlazor's explicit align
   classes stay available for deliberate exceptions (a money column may
   right-align — nothing centres). */
.mud-table-root .mud-table-cell:not(.mud-table-cell-align-center):not(.mud-table-cell-align-right):not(.mud-table-cell-align-justify) {
    text-align: start;
}

/* MudBlazor lays each header out with space-between, floating the column
   menu to the far edge of its column — a stray glyph in the middle of the
   table that read as misaligned content. Title, sort arrow and menu ride
   together on the left now, and the menu surfaces only when the header is
   hovered or reached by keyboard. */
.mud-data-grid .mud-table-head .column-header {
    justify-content: flex-start;
    gap: 2px;
}

.mud-data-grid .mud-table-head .mud-table-cell .mud-icon-button {
    opacity: 0;
    transition: opacity var(--apo-duration-fast) var(--apo-ease);
}

.mud-data-grid .mud-table-head .mud-table-cell:hover .mud-icon-button,
.mud-data-grid .mud-table-head .mud-table-cell .mud-icon-button:focus-visible {
    opacity: 1;
}

/* 40px single-line rows — the reference product's exact list row, and the
   floor of the 40-44px band this already sat in. Hairline separation stays;
   the last row hands its edge to the card.

   The height is only honest if nothing INSIDE the row is taller than it: the
   identity cell's avatar came down to 28px in the same pass (see
   .apo-identity__avatar in app.css), because a 32px disc plus this padding
   made every row carrying a person 8px taller than the rows around it. Four
   pixels a row is ~15% more records on a screen, which on a patient list is
   the difference between scrolling and not. */
.mud-data-grid .mud-table-body .mud-table-cell {
    font-size: var(--apo-text-body);
    color: var(--apo-ink-700);
    letter-spacing: var(--apo-tracking-body);
    border-bottom: 1px solid var(--apo-border);
    padding: 6px 14px;
    height: 40px;
    /* Every figure in every grid lines up in its column — dates, amounts,
       counts, IDs. Inter's proportional digits make a column of dates ripple;
       one declaration here fixes it for the whole product, and it is the kind
       of thing nobody notices except as "this looks properly made". */
    font-variant-numeric: tabular-nums;
}

/* ONE STRONG COLUMN PER ROW. Every cell used to be ink-800, so a row read as
   six items of equal importance and the eye had nowhere to land. The first
   column is the row's identity — the name, the title, the number you actually
   scan for — and it takes the full ink; everything after it is the answer to
   a question you have already asked, and steps back one stop.

   :first-child rather than a class, because the identity column is the first
   one on all 20-odd grids and adding a CellClass to each is a rule nobody
   will remember to apply to grid 21. */
.mud-data-grid .mud-table-body .mud-table-cell:first-child,
.mud-table-root .mud-table-body .mud-table-cell:first-child {
    color: var(--apo-ink-900);
    font-weight: 500;
}

.mud-data-grid .mud-table-body .mud-table-row:last-child .mud-table-cell {
    border-bottom: none;
}

/* A selection checkbox is not an identity, so it must not take the identity
   column's weight — otherwise a selectable grid promotes an empty box. */
.mud-data-grid .mud-table-body .mud-table-cell:first-child:has(.mud-checkbox),
.mud-table-root .mud-table-body .mud-table-cell:first-child:has(.mud-checkbox) {
    color: var(--apo-ink-700);
    font-weight: 400;
}

/* Flat hover inside the frame — rounded end-caps belonged to the full-bleed
   register, where the fill had to draw its own edges. */
.mud-data-grid .mud-table-body .mud-table-row:hover .mud-table-cell {
    background: var(--apo-surface-hover);
}

/* The pager is the card's quiet footer: capped by a hairline, no band. */
.mud-data-grid .mud-table-pagination {
    border-top: 1px solid var(--apo-border);
    min-height: 40px;
    color: var(--apo-ink-500);
    font-size: var(--apo-text-caption1);
}

.mud-table-pagination {
    background: transparent;
}

/* Four pages still use a plain <MudTable> rather than <MudDataGrid>
   (PatientDocumentsDialog, AppointmentsCalendar, Dashboard, Reports). The rule
   above never reached those, so their footer kept MudBlazor's 0.875rem —
   13.125px against the lever, off-ramp and 1px adrift from the grid's. */
.mud-table-pagination,
.mud-table-pagination .mud-select-input .mud-input-root {
    font-size: var(--apo-text-caption1);
}

/* Footer cells are deliberately NOT pulled down to caption1. They carry totals,
   which is data, and `.mud-table-root .mud-table-cell` already puts them on Body
   alongside the rows they total. MudBlazor's own 0.75rem default made them the
   smallest text in the table, which is backwards for a summary row. */

/* ============================================================
   INPUTS — 32px, hairline border, ink focus ring.
   ============================================================ */
/* The input radius on the shell AND the fieldset — the fieldset is what draws
   the outlined border. Covers text fields, selects, textareas and pickers
   alike. Tokenised: the 10px lives in --apo-radius-input, not here. */
.mud-input.mud-input-outlined,
.mud-input-outlined .mud-input-outlined-border {
    border-radius: var(--apo-radius-input) !important;
}

/* At rest: a 1px hairline, nothing else. The accent belongs to focus. */
.mud-input-outlined-border {
    border-color: var(--apo-border-strong);
    border-width: 1px;
    box-shadow: none;
}

.mud-input-control > .mud-input-control-input-container > .mud-input.mud-input-outlined {
    background: var(--apo-paper);
    min-height: 32px;
    font-size: var(--apo-text-body);
}

/* Focus keeps the hairline. MudBlazor's own rule is
   `.mud-input.mud-input-outlined:focus-within > .mud-input-outlined-border
   { border-width: 2px; border-color: var(--mud-palette-primary) }` — four
   simple selectors, which outranked the old three-class override here and
   painted a fat near-black frame the moment a field was focused. Focus is
   signalled the way the login fields do it: the 1px border turns to ink and
   a soft halo surfaces — value and light, not weight. The selectors
   below mirror MudBlazor's shapes exactly so this file's later position
   settles the tie. */
.mud-input.mud-input-outlined:focus-within > .mud-input-outlined-border,
.mud-input.mud-input-outlined:focus-within .mud-shrink > .mud-input-outlined-border,
.mud-input-outlined.mud-focused .mud-input-outlined-border {
    border-color: var(--apo-accent);
    border-width: 1px;
    box-shadow: var(--apo-ring);
}

/* ============================================================
   A LOCKED FIELD IS A VALUE, NOT AN EMPTY ONE

   MudBlazor renders a disabled input's CONTENT in the palette's
   text-disabled colour, which in this system is --apo-ink-400 — the
   same tone as a placeholder. So a page full of fields owned by an
   upstream system showed real values in the exact colour the product
   uses for "nothing here yet": the WordPress-sourced page editor
   displayed its title, "Tko smo", looking for all the world like a
   hint, and its summary like a greyed-out suggestion.

   Two different states cannot share one colour. A locked field is
   drawn as what it is — a value you may read but not change:

     · recessed ground, so it reads as a different material from the
       fields you can type in
     · the text at near-full ink, because it IS content
     · a hairline rather than the input ramp's stronger edge, because
       it is not soliciting input
     · a padlock on its own label, so the reason is stated at the
       field rather than only in a panel somewhere else on the page

   -webkit-text-fill-color is not belt-and-braces: WebKit ignores
   `color` on a disabled control and paints it from that property
   alone, so setting only `color` fixes Firefox and changes nothing
   in Chrome or Safari.
   ============================================================ */
.mud-input.mud-input-outlined.mud-disabled {
    background: var(--apo-surface-sunk);
    cursor: default;
}

.mud-input.mud-disabled .mud-input-slot,
.mud-input.mud-disabled input,
.mud-input.mud-disabled textarea {
    color: var(--apo-ink-700) !important;
    -webkit-text-fill-color: var(--apo-ink-700);
    opacity: 1;
    cursor: default;
}

.mud-input.mud-disabled > .mud-input-outlined-border {
    border-color: var(--apo-border) !important;
}

/* A DISABLED TEXTAREA MUST NOT CLIP. It is a value, and half a value
   is worse than none: the summary on the page editor was 145px of text
   in a 67px box, scrollable in principle but with this product's
   hover-only scrollbars there was nothing on screen to say so — the
   sentence simply stopped mid-word.

   field-sizing grows the control to its content. Where it is not
   supported the overflow stays scrollable, so nothing is ever lost;
   and the fields where this matters most are rendered as
   <LockedField> blocks instead, which cannot clip at all. */
.mud-input.mud-disabled textarea {
    field-sizing: content;
    overflow: hidden;
    resize: none;
}

/* The padlock. On the LABEL rather than inside the field, so it reads
   before the value does and does not eat into the value's measure. */
.mud-input-control:has(> .mud-input-control-input-container > .mud-input.mud-disabled) .mud-input-label::after {
    content: "";
    display: inline-block;
    width: 11px;
    height: 11px;
    margin-inline-start: 5px;
    vertical-align: -1px;
    background-color: currentColor;
    opacity: 0.6;
    -webkit-mask: url("data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17 10V8a5 5 0 0 0-10 0v2H6a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-9a1 1 0 0 0-1-1h-1zm-8 0V8a3 3 0 0 1 6 0v2H9z"/></svg>") center / contain no-repeat;
    mask: url("data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17 10V8a5 5 0 0 0-10 0v2H6a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-9a1 1 0 0 0-1-1h-1zm-8 0V8a3 3 0 0 1 6 0v2H9z"/></svg>") center / contain no-repeat;
}

/* A disabled SELECT keeps its chevron, which promises a menu that will
   never open. */
.mud-input.mud-disabled .mud-select-input .mud-input-adornment,
.mud-select .mud-input.mud-disabled .mud-icon-button {
    opacity: 0;
}

/* FIELD LABELS — stacked above the input, not floated inside it.

   MudBlazor's default is a Material floating label: it starts inside the field
   and animates up into a notch cut in the border. That pattern costs more than
   it looks. The label is only fully legible once the field is empty, it scales
   to 75% when floated so it never sits on a real type step without contriving
   the base size for it, and the notch means the field's own outline is broken
   by text. A label above the field is simply read, at a fixed size, and the
   outline stays a clean rectangle.

   No markup changed. The container is already `flex-direction: column` with
   the label as the LAST child, absolutely positioned — so `order: -1` plus
   static positioning lifts it above the field, everywhere, in one rule.

   This also retires the old scale arithmetic: the label used to be forced onto
   Subtitle2 purely so MudBlazor's 0.75 transform would land it on Caption1
   when floated. Nothing scales now, so it is Caption1 outright. */
.mud-input-control .mud-input-control-input-container {
    display: flex !important;
    flex-direction: column;
}

.mud-input-control .mud-input-control-input-container > .mud-input-label {
    position: static !important;
    transform: none !important;
    order: -1;
    margin: 0 0 6px 1px;
    max-width: 100%;
    /* The floating label is click-through so it cannot block the field it sits
       on top of. Above the field it should behave like a real label and focus
       the input. */
    pointer-events: auto;
    font-size: var(--apo-text-caption1) !important;
    font-weight: 500;
    line-height: var(--apo-leading-caption1);
    letter-spacing: var(--apo-tracking-caption);
    color: var(--apo-ink-700);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The required marker is information, not decoration — it earns the accent. */
.mud-input-control .mud-input-label .mud-input-required-mark {
    color: var(--apo-error);
    margin-left: 2px;
}

/* No notch to cut any more: the label is outside the border, so the legend
   must stay collapsed or it punches a gap in an otherwise clean outline. */
.mud-input.mud-input-outlined > .mud-input-outlined-border legend {
    width: 0 !important;
    max-width: 0 !important;
    padding: 0 !important;
    margin: 0 !important;
}

.mud-input-slot,
.mud-select-input,
.mud-picker-input-button {
    font-size: var(--apo-text-body);
}

.mud-form {
    font-size: var(--apo-text-body);
}

/* ============================================================
   SURFACES + FLOATING LAYERS
   ============================================================ */
.mud-card {
    box-shadow: none;
    border-radius: var(--apo-radius-lg);
    background: var(--apo-paper);
    border: 1px solid var(--apo-border);
}

/* MudChip takes the same outline register as .apo-badge, because they are the
   same object: a short label naming what something IS. They were two different
   pills — one hand-built in app.css for grid cells, one left near-stock for
   the detail pages — so a role read one way on the Users list and another way
   on the user it opened. */
.mud-chip {
    font-weight: 550;
    font-size: var(--apo-text-caption2);
    letter-spacing: var(--apo-tracking-caption);
    text-transform: none;
    border-radius: var(--apo-radius-sm);
    padding: 0 7px;
    height: 19px;
    min-height: 19px;
    box-shadow: none;
}

/* All three MudChip variants take the outline register, filled included. The
   last pass unified Text and Outlined and left Filled behind, so a status chip
   written without an explicit Variant still rendered as a solid red or green
   lozenge — one loud filled pill sitting on a card head beside a page of
   hairline chips. A chip is a chip whichever way it was declared. */
.mud-chip.mud-chip-outlined,
.mud-chip.mud-chip-text,
.mud-chip.mud-chip-filled {
    border: 1px solid var(--apo-border-strong);
    background-color: transparent !important;
    color: var(--apo-ink-700) !important;
}

/* The icon a semantic chip carries is a second statement of the colour the
   text already makes, at a size that outweighs it. */
.mud-chip .mud-chip-icon {
    font-size: var(--apo-icon-xs) !important;
    margin-inline-end: 4px;
}

/* The semantic chips keep their colour — a status is the one thing on a
   detail page that is allowed to be coloured — but as ink on a hairline of
   the same hue rather than as a filled lozenge. */
.mud-chip.mud-chip-text.mud-chip-color-success,
.mud-chip.mud-chip-outlined.mud-chip-color-success,
.mud-chip.mud-chip-filled.mud-chip-color-success {
    color: var(--apo-success) !important;
    border-color: color-mix(in srgb, var(--apo-success) 34%, transparent);
}

.mud-chip.mud-chip-text.mud-chip-color-warning,
.mud-chip.mud-chip-outlined.mud-chip-color-warning,
.mud-chip.mud-chip-filled.mud-chip-color-warning {
    color: var(--apo-warning) !important;
    border-color: color-mix(in srgb, var(--apo-warning) 34%, transparent);
}

.mud-chip.mud-chip-text.mud-chip-color-error,
.mud-chip.mud-chip-outlined.mud-chip-color-error,
.mud-chip.mud-chip-filled.mud-chip-color-error {
    color: var(--apo-error) !important;
    border-color: color-mix(in srgb, var(--apo-error) 34%, transparent);
}

/* THE TOOLBAR SEARCH IS NOT A FORM FIELD, so it stops being drawn as one.

   It was a 480px outlined input — the same object as a "Legal name" field on
   a settings page — sitting alone at the left end of an otherwise empty
   toolbar with a button stranded at the other end. Two problems in one: it
   claimed the width of a form on a row that is not a form, and its hairline
   made a filter control look like something you fill in and submit.

   A quiet filled field instead: recessed ground, no border at rest, edge on
   hover, full accent border plus ring on focus. That is the search field
   every workspace product actually uses, and the reduced width lets it sit as
   part of a left-hand CLUSTER with the filters beside it rather than as a
   lone object with a void after it.

   Focus is unchanged: the shared :focus-within rule below still paints the
   accent border and halo, so the field is at its loudest exactly when it is
   in use. */
.apo-page-search {
    min-width: min(260px, 46vw);
    max-width: 300px;
}

/* THE EDITOR'S TITLE IS THE MANUSCRIPT'S FIRST LINE, not a form field. The
   box goes — display size on the page itself, a placeholder standing in for
   the floating label — and the chrome returns only when something is wrong:
   the error state keeps MudBlazor's red border and message, because a
   required title that quietly looks optional is worse than a boxed one.
   Here and not in ArticleEditor.razor.css because the class sits on
   MudTextField's own root, which a scoped sheet can never match. */
.apo-editorial-title input.mud-input-slot {
    font-size: var(--apo-text-title2);
    font-weight: 650;
    letter-spacing: var(--apo-tracking-title);
}

/* :focus-within, not a focus class — MudBlazor 8.14 signals input focus with
   the pseudo-class alone. And the error guard is .mud-input-error, MudBlazor's
   actual class; guarding on a class that never appears would leave the red
   border standing only on the upstream !important this file elsewhere refuses
   to rely on. */
.apo-editorial-title .mud-input.mud-input-outlined:not(.mud-input-error) .mud-input-outlined-border {
    border-color: transparent;
}

.apo-editorial-title .mud-input.mud-input-outlined:not(.mud-input-error):hover .mud-input-outlined-border {
    border-color: var(--apo-border);
}

.apo-editorial-title .mud-input.mud-input-outlined:focus-within:not(.mud-input-error) .mud-input-outlined-border {
    border-color: var(--apo-border-strong);
}

/* SPECIFICITY, deliberately matched rather than escalated to !important.
   MudBlazor's own border rule is `.mud-input.mud-input-outlined
   .mud-input-outlined-border` — three classes — and the field's ground comes
   from a FOUR-class rule further up this file. A two-class override loses to
   both no matter how late it loads, which is exactly how this field kept its
   form-control chrome through an earlier attempt at this change. Each rule
   below restates the winning selector's shape and adds .apo-page-search. */
.apo-page-search .mud-input.mud-input-outlined .mud-input-outlined-border {
    border-color: transparent;
}

.apo-page-search .mud-input.mud-input-outlined:hover .mud-input-outlined-border {
    border-color: var(--apo-border-strong);
}

/* The auto margin that used to anchor this left is GONE. It existed when the
   toolbar was its own full-width row: the search pinned to one end, the
   commands to the other, and 700px of nothing between them. The controls are
   one right-hand cluster on the title row now (see PageHeader.razor.css), so
   pushing the search away from its neighbours would re-open the void this
   change closes. */

.mud-input-control.apo-page-search > .mud-input-control-input-container > .mud-input.mud-input-outlined {
    min-height: var(--apo-control-h);
    background: var(--apo-surface-sunk);
}

/* On focus it becomes a normal field again — paper ground under the accent
   border, so the recessed state reads as "resting" rather than as disabled. */
.mud-input-control.apo-page-search > .mud-input-control-input-container > .mud-input.mud-input-outlined:focus-within {
    background: var(--apo-paper);
}

/* Dialogs and menus DO float — they are the only things that
   earn a real shadow. */
.mud-dialog {
    border: 1px solid var(--apo-border);
    border-radius: var(--apo-radius-xl) !important;
    box-shadow: var(--apo-shadow-lg) !important;
    background: var(--apo-paper);
    overflow: hidden;
    /* Three bands: a header that stays put, a body that scrolls, a footer whose
       actions are always reachable. Without this a long form pushes its own Save
       button below the fold. */
    display: flex;
    flex-direction: column;
    max-height: min(88vh, 860px);
}

/* Scrim matches the reconnect dialog so every modal layer in the product
   dims the page the same way. */
.mud-overlay-dialog {
    background: color-mix(in srgb, var(--apo-ink-950) 45%, transparent);
    backdrop-filter: blur(2px);
}

.mud-dialog-title {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex: 0 0 auto;
    /* 12px, down from 15: the dialog's title rail is the same object as a
       card head and a table's column header, and those are 40px and 36px.
       At 15px this one stood 50px and read as a different component. */
    padding: 12px 18px;
    margin: 0;
    border-bottom: 1px solid var(--apo-border);
    background: var(--apo-surface-sunk);
    font-family: var(--apo-font-display);
    font-size: var(--apo-text-subtitle2);
    font-weight: 600;
    letter-spacing: var(--apo-tracking-subtitle);
    color: var(--apo-ink-900);
}

/* MudBlazor reserves 64px of right padding on the title for a close button it
   positions absolutely. We make that button a normal flex child instead, so
   the reservation becomes dead space — the X ended up 61px from the dialog
   edge while the title sat 24px from the left, which read as a misalignment
   because it was one. Padding is symmetric now and the icon's own optical
   inset does the rest. */
.mud-dialog-title {
    padding-right: 18px !important;
    padding-left: 18px !important;
}

/* MudBlazor drops the close button inside the title as a positioned icon
   button; make it part of the flex row instead of floating over the text. */
.mud-dialog-title .mud-button-root.mud-icon-button {
    position: static !important;
    margin: -4px -4px -4px 0 !important;
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
}

.mud-dialog-content {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 18px;
    min-height: 0;
}

/* The exact mobile preview is a device, not a form: it has to be given the
   dialog's whole height rather than asked how tall it would like to be, or the
   phone is cut off by the footer and the dialog becomes a second scroller
   competing with the app inside the frame. MudDialog puts Class on .mud-dialog
   itself, which no scoped sheet can reach — hence here. */
/* 92vh, above the house 88: height is the scarce dimension here — every pixel
   the dialog gives back is a pixel of the patient's screen the editor can see
   without scrolling. The house maximum has to be restated, not just the
   height, or .mud-dialog's own max-height clamps it back. */
.mud-dialog.apo-preview-dialog {
    height: min(92vh, 900px);
    max-height: min(92vh, 900px);
}

.mud-dialog.apo-preview-dialog .mud-dialog-content {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* A real footer: recessed, hairline-capped, actions pushed right so the
   primary action lands where the eye exits the form. */
.mud-dialog-actions {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: 12px 18px;
    gap: 8px;
    margin: 0;
    border-top: 1px solid var(--apo-border);
    background: var(--apo-surface-sunk);
}

/* Dialog actions are commit points, so they claim more room — but in WIDTH,
   not height. They used to stand 38px against the product's 36px, which is
   not "more substantial", it is a two-pixel drift that reads as a control
   rendered by a different system. One height everywhere; emphasis comes from
   the horizontal room and from the filled variant beside a text one. */
.mud-dialog-actions .mud-button {
    padding-inline: 18px;
}

.mud-dialog-actions .mud-button.mud-button-text {
    padding-inline: 12px;
}

@media (max-width: 600px) {
    .mud-dialog {
        max-height: 92vh;
    }

    /* Direction is set in the touch-target block below, which owns the
       stacked footer ordering. */

    .mud-dialog-actions .mud-button {
        width: 100%;
    }
}

.mud-popover-provider .mud-popover,
.mud-menu-popover {
    border: 1px solid var(--apo-border);
    border-radius: var(--apo-radius-lg);
    box-shadow: var(--apo-shadow-md);
    background: var(--apo-paper);
}

.mud-menu .mud-list-item,
.mud-popover .mud-list-item {
    min-height: 28px;
    font-size: var(--apo-text-body);
    border-radius: var(--apo-radius-sm);
    padding: 3px 8px;
    margin: 1px 4px;
}

.mud-menu .mud-list-item:hover,
.mud-popover .mud-list-item:hover {
    background: var(--apo-surface-hover);
}

/* TABS — an underline, not a pill and not a slab.

   MudBlazor's tab bar ships a full-width coloured slider and centred, padded,
   equal-width tabs. Left-aligned labels with a 2px ink rule under the active
   one is what a workspace product uses: the rule is the only thing that
   moves, the type carries the state, and the row costs 36px instead of 48. */
.mud-tabs-tabbar {
    border-bottom: 1px solid var(--apo-border);
    min-height: 36px;
}

.mud-tabs-tabbar .mud-tab {
    min-height: 36px;
    height: 36px;
    font-size: var(--apo-text-body);
    font-weight: 500;
    text-transform: none;
    letter-spacing: var(--apo-tracking-body);
    padding: 0 12px;
    min-width: 0;
    color: var(--apo-ink-500);
    transition: color var(--apo-duration-fast) var(--apo-ease);
}

.mud-tabs-tabbar .mud-tab:hover {
    color: var(--apo-ink-800);
}

/* Weight AND colour on the active tab, with the tracking eased the same way
   the sidebar's active row eases it, so the label does not reflow when the
   weight steps up and shift every tab after it. */
.mud-tabs-tabbar .mud-tab.mud-tab-active {
    color: var(--apo-ink-900) !important;
    font-weight: 600;
    letter-spacing: -0.017em;
}

/* The slider sits ON the bar's hairline rather than above it, so the active
   tab reads as connected to the panel below. */
.mud-tabs-tabbar .mud-tab-slider {
    background-color: var(--apo-accent) !important;
    height: 2px !important;
    border-radius: 0;
}

.mud-tabs-tabbar .mud-tab .mud-icon-root {
    font-size: var(--apo-icon-sm) !important;
}

/* MudBlazor ships these at 1.5rem / 1.125rem, which the 15px lever turns into
   22.5px and 16.875px — both off the icon ramp, and the 22.5px one is the
   largest glyph in most toolbars. */
.mud-icon-button {
    width: var(--apo-control-h);
    min-width: var(--apo-control-h);
    height: var(--apo-control-h);
    min-height: var(--apo-control-h);
    border: 1px solid transparent;
    /* The same corner as every other control. This one was already 10px as a
       literal before the token existed — the pill-shaped text button was the
       odd one out, not this. */
    border-radius: var(--apo-radius-control);
    background: transparent;
    color: var(--apo-ink-500);
    font-size: var(--apo-icon-md);
    box-shadow: none;
    transition: background var(--apo-duration-fast) var(--apo-ease),
                border-color var(--apo-duration-fast) var(--apo-ease),
                color var(--apo-duration-fast) var(--apo-ease),
                box-shadow var(--apo-duration-fast) var(--apo-ease);
}

/* 28px, which is the step below the 32px control — a toolbar's quiet
   actions (row overflow, refresh, close) sit one notch under the commands
   they accompany rather than matching them. */
.mud-icon-button.mud-icon-button-size-small {
    width: 28px;
    min-width: 28px;
    height: 28px;
    min-height: 28px;
    font-size: var(--apo-icon-sm);
    border-radius: var(--apo-radius-md);
}

.mud-icon-button.mud-icon-button-size-large {
    width: 44px;
    min-width: 44px;
    height: 44px;
    min-height: 44px;
    font-size: var(--apo-icon-lg);
}

.mud-icon-button:hover {
    background: var(--apo-surface-hover);
    border-color: var(--apo-border);
    color: var(--apo-ink-800);
}

.mud-icon-button:active {
    background: var(--apo-ink-100);
}

.mud-icon-button:focus-visible {
    outline: none !important;
    box-shadow: var(--apo-button-focus-ring) !important;
}

/* A filled icon button is the same object as a filled text button with the
   label removed, so it keeps the rectangle. Rounding it to a circle here is
   what used to make a toolbar read as two component libraries: a 36px disc
   beside a 36px rounded rectangle beside a 36px input. */
.mud-icon-button.mud-button-filled-primary,
.mud-icon-button.mud-button-filled-secondary {
    background: var(--apo-cta-bg) !important;
    border-color: var(--apo-cta-edge) !important;
    color: var(--apo-cta-fg) !important;
    box-shadow: none !important;
}

.mud-icon-button.mud-button-filled-primary:hover,
.mud-icon-button.mud-button-filled-secondary:hover {
    background: var(--apo-cta-hover) !important;
    border-color: var(--apo-cta-edge-hover) !important;
}

.mud-icon-button.mud-button-filled-primary:active,
.mud-icon-button.mud-button-filled-secondary:active {
    background: color-mix(in srgb, var(--apo-cta-hover) 91%, black) !important;
}

.mud-icon-button.mud-button-filled-primary:focus-visible,
.mud-icon-button.mud-button-filled-secondary:focus-visible {
    box-shadow: var(--apo-button-focus-ring) !important;
}

.mud-icon-button[class*="mud-button-filled-"]:not(.mud-button-filled-primary):not(.mud-button-filled-secondary) {
    border-color: color-mix(in srgb, black 18%, transparent) !important;
    box-shadow: none !important;
}

.mud-icon-button[class*="mud-button-filled-"]:not(.mud-button-filled-primary):not(.mud-button-filled-secondary):hover {
    background-image: linear-gradient(rgba(0, 0, 0, 0.10), rgba(0, 0, 0, 0.10));
}

.mud-icon-button[class*="mud-button-filled-"]:not(.mud-button-filled-primary):not(.mud-button-filled-secondary):focus-visible {
    box-shadow: var(--apo-button-focus-ring) !important;
}

.mud-alert {
    border-radius: var(--apo-radius-lg) !important;
    font-size: var(--apo-text-body);
    padding: 8px 12px;
}

/* ============================================================
   INLINE ALERTS THAT REPORT A PROBLEM

   ~45 pages render <MudAlert Severity="Warning">@_error</MudAlert>
   as their "we could not load this" banner. Stock MudBlazor gives
   them a tinted slab with a material triangle — the same banner
   every Blazor app ships, saying nothing about whose product it is.

   Every one of them becomes the same object as the system cards in
   app.css: the clinic's emblem in a toned ring, the message beside
   it, on paper. The severity lives in that ring alone — a coloured
   3px rail down the left edge used to carry it too, and a tinted bar
   bolted to the side of a bordered card is the house style of no
   house. Done here rather than at the call sites so all 45 stay in
   step, and so a page written tomorrow inherits it for free.

   Attribute matching covers text / outlined / filled in one pass:
   the root element's variant class always ends in the severity.
   Snackbars carry the same variant classes and are excluded — they
   are a different surface, handled further down.
   ============================================================ */
.mud-alert:not(.mud-snackbar)[class*="-warning"],
.mud-alert:not(.mud-snackbar)[class*="-error"] {
    position: relative;
    overflow: hidden;
    align-items: center;
    gap: 12px;
    padding: 11px 14px;
    background-color: var(--apo-paper) !important;
    color: var(--apo-ink-800) !important;
    border: 1px solid var(--apo-border);
    border-radius: var(--apo-radius-lg);
    box-shadow: none;
}

/* The emblem replaces the severity glyph rather than joining it: two marks
   side by side in a 13px banner is clutter, and the ring's own tint says
   which severity this is. */
.mud-alert:not(.mud-snackbar)[class*="-warning"] .mud-alert-icon,
.mud-alert:not(.mud-snackbar)[class*="-error"] .mud-alert-icon {
    position: relative;
    flex: 0 0 30px;
    width: 30px;
    height: 30px;
    padding: 0;
    margin: 0;
    border-radius: var(--apo-radius-full);
    border: 1px solid var(--apo-border);
    background-color: var(--apo-surface-sunk);
}

/* THE MARK RIDES ITS OWN LAYER, so the emblem and the severity tint can be
   adjusted independently — the disc is the element, the mark is the
   pseudo-element. Both are in their own colours; the separation is kept
   because it is the correct structure, and because any future treatment of one
   must not reach the other. */
.mud-alert:not(.mud-snackbar)[class*="-warning"] .mud-alert-icon::after,
.mud-alert:not(.mud-snackbar)[class*="-error"] .mud-alert-icon::after {
    content: "";
    position: absolute;
    inset: 0;
    background-image: url("/brand/emblem.png");
    background-repeat: no-repeat;
    background-position: center;
    background-size: 21px auto;
}

.mud-alert:not(.mud-snackbar)[class*="-warning"] .mud-alert-icon {
    border-color: color-mix(in srgb, var(--apo-warning) 32%, transparent);
    background-color: var(--apo-warning-bg);
}

.mud-alert:not(.mud-snackbar)[class*="-error"] .mud-alert-icon {
    border-color: color-mix(in srgb, var(--apo-error) 32%, transparent);
    background-color: var(--apo-error-bg);
}

/* The emblem's charcoal ring disappears against the dark theme's chrome, so
   the filled-disc cut takes over — the same swap BrandVariant.Adaptive makes. */
[data-theme="dark"] .mud-alert:not(.mud-snackbar)[class*="-warning"] .mud-alert-icon::after,
[data-theme="dark"] .mud-alert:not(.mud-snackbar)[class*="-error"] .mud-alert-icon::after {
    background-image: url("/brand/emblem-badge.png");
}

.mud-alert:not(.mud-snackbar)[class*="-warning"] .mud-alert-icon > *,
.mud-alert:not(.mud-snackbar)[class*="-error"] .mud-alert-icon > * {
    display: none;
}

.mud-alert:not(.mud-snackbar)[class*="-warning"] .mud-alert-message,
.mud-alert:not(.mud-snackbar)[class*="-error"] .mud-alert-message {
    padding: 0;
    line-height: 1.5;
}

/* INFO AND NORMAL take the same object. They were left on stock MudBlazor —
   a tinted slab with a Material glyph — so a page carrying an explanatory
   note (Appointment requests, Content sync) showed one surface drawn by this
   system and one drawn by the component library, side by side. Two visual
   languages on one screen is the loudest possible tell that nobody designed
   the page.

   Same geometry as warning/error, neutral rule, and the glyph stays a glyph:
   an emblem belongs on the messages the PRODUCT is sending about itself, not
   on a sentence explaining how a queue works. */
.mud-alert:not(.mud-snackbar)[class*="-info"],
.mud-alert:not(.mud-snackbar)[class*="-normal"] {
    position: relative;
    overflow: hidden;
    align-items: center;
    gap: 12px;
    padding: 11px 14px;
    background-color: var(--apo-paper) !important;
    color: var(--apo-ink-700) !important;
    border: 1px solid var(--apo-border);
    border-radius: var(--apo-radius-lg);
    box-shadow: none;
}

.mud-alert:not(.mud-snackbar)[class*="-info"] .mud-alert-icon,
.mud-alert:not(.mud-snackbar)[class*="-normal"] .mud-alert-icon {
    flex: 0 0 auto;
    padding: 0;
    margin: 0;
    color: var(--apo-ink-400);
}

.mud-alert:not(.mud-snackbar)[class*="-info"] .mud-alert-icon .mud-icon-root,
.mud-alert:not(.mud-snackbar)[class*="-normal"] .mud-alert-icon .mud-icon-root {
    font-size: var(--apo-icon-md) !important;
}

.mud-alert:not(.mud-snackbar)[class*="-info"] .mud-alert-message,
.mud-alert:not(.mud-snackbar)[class*="-normal"] .mud-alert-message {
    padding: 0;
    line-height: 1.5;
    font-size: var(--apo-text-body);
}

/* Dense alerts are used inside dialogs and the login sheet, where the card
   has to stay slim. Same object, tighter. */
.mud-alert.mud-dense:not(.mud-snackbar)[class*="-warning"],
.mud-alert.mud-dense:not(.mud-snackbar)[class*="-error"] {
    gap: 10px;
    padding: 8px 12px 8px 13px;
}

.mud-alert.mud-dense:not(.mud-snackbar)[class*="-warning"] .mud-alert-icon,
.mud-alert.mud-dense:not(.mud-snackbar)[class*="-error"] .mud-alert-icon {
    flex-basis: 24px;
    width: 24px;
    height: 24px;
}

.mud-alert.mud-dense:not(.mud-snackbar)[class*="-warning"] .mud-alert-icon::after,
.mud-alert.mud-dense:not(.mud-snackbar)[class*="-error"] .mud-alert-icon::after {
    background-size: 17px auto;
}

/* ============================================================
   TOASTS — quiet, on paper, severity carried by the icon.

   Stock MudBlazor snackbars arrive as filled Material slabs —
   saturated severity fills shouting from the corner of a product
   whose entire register is hairlines on paper. The Linear-style
   toast is a floating card instead: paper ground, 1px border,
   body type; the ONLY thing severity changes is the icon's colour,
   the same dot-not-pill logic the .apo-status column uses. The
   variant classes are listed alongside .mud-snackbar so the
   two-class selectors outrank MudBlazor's own variant fills.
   ============================================================ */
.mud-snackbar,
.mud-snackbar.mud-alert-filled-normal,
.mud-snackbar.mud-alert-filled-info,
.mud-snackbar.mud-alert-filled-success,
.mud-snackbar.mud-alert-filled-warning,
.mud-snackbar.mud-alert-filled-error {
    background: var(--apo-paper);
    color: var(--apo-ink-900);
    border: 1px solid var(--apo-border);
    border-radius: var(--apo-radius-lg);
    box-shadow: var(--apo-shadow-md);
    font-size: var(--apo-text-body);
    font-weight: 400;
}

/* Action + close controls sit on paper now, so they take the app's own
   quiet control colours rather than the filled toast's white. */
.mud-snackbar .mud-snackbar-close-button,
.mud-snackbar .mud-button {
    color: var(--apo-ink-600);
}

/* Severity is the icon's job alone. */
.mud-snackbar .mud-snackbar-icon {
    color: var(--apo-ink-500);
}

.mud-snackbar.mud-alert-filled-info .mud-snackbar-icon    { color: var(--apo-info); }
.mud-snackbar.mud-alert-filled-success .mud-snackbar-icon { color: var(--apo-success); }
.mud-snackbar.mud-alert-filled-warning .mud-snackbar-icon { color: var(--apo-warning); }
.mud-snackbar.mud-alert-filled-error .mud-snackbar-icon   { color: var(--apo-error); }

/* A toast that reports a FAILURE is a system message, not a receipt, so it
   joins the same family as the cards in app.css: emblem in a toned ring. Info
   and success keep the plain tinted glyph — a "saved" confirmation does not
   need the clinic to sign it. No severity rail here either: the ring is the
   whole signal, on this surface as on the inline alerts above. */
.mud-snackbar.mud-alert-filled-warning,
.mud-snackbar.mud-alert-filled-error {
    position: relative;
    overflow: hidden;
}

.mud-snackbar.mud-alert-filled-warning .mud-snackbar-icon,
.mud-snackbar.mud-alert-filled-error .mud-snackbar-icon {
    position: relative;
    flex: 0 0 28px;
    width: 28px;
    height: 28px;
    padding: 0;
    align-self: center;
    opacity: 1;
    border-radius: var(--apo-radius-full);
    border: 1px solid var(--apo-border);
}

/* Same two-layer arrangement as the alert above: the tint is the element,
   the mark is the pseudo-element. */
.mud-snackbar.mud-alert-filled-warning .mud-snackbar-icon::after,
.mud-snackbar.mud-alert-filled-error .mud-snackbar-icon::after {
    content: "";
    position: absolute;
    inset: 0;
    background-repeat: no-repeat;
    background-position: center;
    background-size: 20px auto;
    background-image: url("/brand/emblem.png");
}

[data-theme="dark"] .mud-snackbar.mud-alert-filled-warning .mud-snackbar-icon::after,
[data-theme="dark"] .mud-snackbar.mud-alert-filled-error .mud-snackbar-icon::after {
    background-image: url("/brand/emblem-badge.png");
}

.mud-snackbar.mud-alert-filled-warning .mud-snackbar-icon {
    border-color: color-mix(in srgb, var(--apo-warning) 32%, transparent);
    background-color: var(--apo-warning-bg);
}

.mud-snackbar.mud-alert-filled-error .mud-snackbar-icon {
    border-color: color-mix(in srgb, var(--apo-error) 32%, transparent);
    background-color: var(--apo-error-bg);
}

.mud-snackbar.mud-alert-filled-warning .mud-snackbar-icon > *,
.mud-snackbar.mud-alert-filled-error .mud-snackbar-icon > * {
    display: none;
}

.mud-divider {
    border-color: var(--apo-border);
}

@media (max-width: 600px) {
    .apo-page-search {
        min-width: 0;
        width: 100%;
    }

    .apo-data-card .mud-data-grid .mud-table-cell {
        padding-left: 10px;
        padding-right: 10px;
    }

    .mud-dialog-container {
        padding: 8px;
    }
}

/* Status chips read as tinted labels, matching the KPI delta pills. */
.mud-chip-text.mud-chip-color-success {
    background: var(--apo-success-bg);
    color: var(--apo-success);
}

.mud-chip-text.mud-chip-color-warning {
    background: var(--apo-warning-bg);
    color: var(--apo-warning);
}

.mud-chip-text.mud-chip-color-error {
    background: var(--apo-error-bg);
    color: var(--apo-error);
}

.mud-chip-text.mud-chip-color-default {
    background: var(--apo-surface-sunk);
    color: var(--apo-ink-600);
}

/* Skeletons sweep neutral — a gold shimmer read as decoration. */
.mud-skeleton {
    background-image: linear-gradient(
        90deg,
        var(--apo-ink-100) 0%,
        var(--apo-ink-50) 50%,
        var(--apo-ink-100) 100%
    );
    background-size: 200% 100%;
    border-radius: var(--apo-radius-sm);
}

/* MudBlazor 8 breadcrumb tuning */
.mud-breadcrumbs {
    padding: 0 !important;
    font-size: var(--apo-text-caption1);
}

.mud-breadcrumb-item {
    color: var(--apo-ink-500);
}

.mud-breadcrumb-item a {
    text-decoration: none;
}

.mud-breadcrumb-separator {
    color: var(--apo-ink-300);
    margin: 0 2px;
}

/* ============================================================
   RAMP ENFORCEMENT — MudBlazor rules the theme cannot reach.

   Typography.* in ApolonijaTheme.cs covers <MudText> and anything
   keyed off --mud-typography-*. It does NOT cover the ~66 rules in
   MudBlazor.min.css that hardcode a font-size on a component class.
   At the 15px root those land on values like 11.25 / 12.19 / 13.13 /
   14.06 / 16.88 / 17.25 / 18.75 / 22.5px — the exact near-miss noise
   the ramp exists to remove.

   Only the families this app actually renders are listed here,
   counted from the Razor rather than guessed: Alert x48, Button x77,
   TextField x63, DataGrid x28, IconButton x27, Chip x26, Icon x17,
   Switch x14, DatePicker x7, CheckBox x5, ExpansionPanels x2,
   List x2, SimpleTable x2. Adding a component MudBlazor sizes itself
   means checking it against the ramp here.

   Found by scanning all 29 deployed routes for computed sizes outside
   the ramp, not by reading this stylesheet — two of these (the switch
   label and the expansion-panel header) were live in the product and
   invisible to a code-only pass.
   ============================================================ */

/* Control labels — these sit beside an input, so they take Body like
   the input text does. MudBlazor ships them at 1rem (15px).

   !important is required, not defensive: MudBlazor declares the three
   .mud-switch-label-* rules `!important` themselves, so an equal-specificity
   override loses no matter how late it loads. Checkbox and radio do NOT use
   !important, which is exactly why an earlier pass fixed those two and left
   the switch at 15px — same selector shape, different outcome. */
.mud-switch-label-small,
.mud-checkbox-label-small,
.mud-radio-label-small {
    font-size: var(--apo-text-caption1) !important;
}

.mud-switch-label-medium,
.mud-checkbox-label-medium,
.mud-radio-label-medium {
    font-size: var(--apo-text-body) !important;
}

.mud-switch-label-large,
.mud-checkbox-label-large,
.mud-radio-label-large {
    font-size: var(--apo-text-subtitle2) !important;
}

/* SWITCHES — the iOS switch, on the monochrome palette.

   MudBlazor draws the Material toggle: a half-height 34x14 track with a 20px
   disc RIDING OVER its edge, the checked state a 50%-opacity wash. Both halves
   of that are wrong here. The wash merges with a white thumb on a monochrome
   palette, and the overhanging disc is the one control silhouette every
   scaffolded admin panel shares — a switch that looks like a bead on a wire
   rather than a thing that is on or off.

   What replaces it is the shape Apple settled and everyone recognises: a full
   pill the thumb travels INSIDE, 2px of track visible around the disc at both
   ends of the throw. The geometry below re-derives MudBlazor's three sizes so
   the stock checked translate (20px) still lands the thumb exactly 2px from
   the far edge — override the paddings and the travel stays correct for free.

               span        track     thumb   base pad   inset
     small     44 x 24     36 x 16    14px      5px      1px
     medium    58 x 38     46 x 26    22px      8px      2px
     large     72 x 52     50 x 30    26px     13px      2px

   Colour keeps the house ink/reverse pairing — full-strength accent track
   with a reversed disc when on, so the CTA, the checkbox and the switch still
   agree about what "yes" looks like. Off is Apple's own register: a quiet
   neutral well under a white disc, in both themes — the white disc on the
   dark build's dark well is what makes the off state read instantly there. */
.mud-switch-span-small.mud-switch-span  { width: 44px; height: 24px; padding: 4px; }
.mud-switch-span-medium.mud-switch-span { width: 58px; height: 38px; padding: 6px; }
.mud-switch-span-large.mud-switch-span  { width: 72px; height: 52px; padding: 11px; }

/* Explicit box, not padding: the base is a .mud-icon-button, which the
   control-height pass elsewhere in this file sizes to a fixed square and
   which CENTRES its content with flex — so MudBlazor's padding numbers are
   dead weight, and the thumb lands wherever the icon-button box says.
   Matching each base to its span's height makes the flex centring itself
   produce the inset: (38 − 22) / 2 = 8, exactly the 2px ring inside the
   26px track. !important because the icon-button sizing rule carries it. */
/* min-* as well as width/height: the icon-button passes clamp .mud-icon-button
   to min 32px everywhere and min 40px under 1024px for touch, and a min always
   beats a width — without these zeros the pill's geometry silently breaks on
   exactly the viewports the touch pass exists for. */
.mud-switch .mud-switch-base-small.mud-switch-base  { width: 24px !important; height: 24px !important; min-width: 0 !important; min-height: 0 !important; padding: 0 !important; }
.mud-switch .mud-switch-base-medium.mud-switch-base { width: 38px !important; height: 38px !important; min-width: 0 !important; min-height: 0 !important; padding: 0 !important; }
.mud-switch .mud-switch-base-large.mud-switch-base  { width: 52px !important; height: 52px !important; min-width: 0 !important; min-height: 0 !important; padding: 0 !important; }

/* The slide itself. MudBlazor puts the transform transition on
   .mud-switch-base, but this file's icon-button pass declares its own
   transition list on the same element at equal specificity and loads later —
   so the stock rule is dead and, without this, the thumb TELEPORTS between
   ends. Restated here at (0,2,0), on the motion tokens. */
.mud-switch .mud-switch-base {
    transition: transform var(--apo-duration-base) var(--apo-ease),
                left var(--apo-duration-base) var(--apo-ease);
}

.mud-switch .mud-switch-thumb-small  { width: 14px; height: 14px; }
.mud-switch .mud-switch-thumb-medium { width: 22px; height: 22px; }
.mud-switch .mud-switch-thumb-large  { width: 26px; height: 26px; }

.mud-switch .mud-switch-track {
    border-radius: 999px;
    background-color: var(--apo-ink-100) !important;
    /* The pill needs its own edge or it dissolves into a sunk surface behind
       it — Apple draws the same hairline inside the well. */
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--apo-ink-900) 7%, transparent);
    opacity: 1 !important;
}

.mud-switch .mud-switch-base.mud-checked ~ .mud-switch-track {
    background-color: var(--apo-accent) !important;
    box-shadow: none;
    opacity: 1 !important;
}

/* The disc. White at rest in BOTH themes — that is half of what makes the
   control read as Apple's — and reversed against the accent track when on.

   The shadow is deliberate, and not the Material three-layer stack this
   product removed from its buttons: a disc travelling inside a filled pill
   has to sit visibly ON it, and on the checked track a hairline alone
   vanishes into the fill. One soft contact shadow plus a half-pixel edge is
   the exact device the reference control uses. */
.mud-switch .mud-switch-thumb-small,
.mud-switch .mud-switch-thumb-medium,
.mud-switch .mud-switch-thumb-large {
    background-color: #FFFFFF !important;
    box-shadow: var(--apo-shadow-switch-thumb);
    transition: background-color var(--apo-duration-fast) var(--apo-ease),
                width var(--apo-duration-base) var(--apo-ease);
}

.mud-switch .mud-switch-base.mud-checked .mud-switch-thumb-small,
.mud-switch .mud-switch-base.mud-checked .mud-switch-thumb-medium,
.mud-switch .mud-switch-base.mud-checked .mud-switch-thumb-large {
    background-color: var(--apo-accent-fg) !important;
}

/* No halo, no hairline, no ripple: the base is a .mud-icon-button, and every
   affordance that pass gives a REAL icon button — the hover hairline border,
   the :focus-within fill that stock MudBlazor adds, the hover wash — would
   here paint a 38px rounded ghost over a 26px pill. All of it goes; the
   pointer hovering a switch already knows it is on a switch. :focus-within is
   in the list because the stock rule uses it, and a click focuses the input —
   without it every toggle left a grey square sitting on the track until blur. */
.mud-switch .mud-switch-base:hover,
.mud-switch .mud-switch-base:focus-within,
.mud-switch .mud-switch-base:focus-visible,
.mud-switch .mud-switch-base:active {
    background-color: transparent !important;
    border-color: transparent !important;
}

/* The ripple is not a child — .mud-ripple sits ON the base button and draws
   itself as an ::after animation. */
.mud-switch .mud-switch-base::after {
    display: none !important;
}

/* Keyboard focus draws the ring around the PILL — the visible control — not
   around the invisible button the input technically lives in. The BUTTON
   register, not the input's 14% halo, for the button's own documented reason:
   nothing else on the control changes on focus, so the ring has to carry the
   whole signal at full strength. It fits inside the span's padding, so
   overflow:hidden never clips it. */
.mud-switch .mud-switch-base:has(.mud-switch-input:focus-visible) ~ .mud-switch-track {
    box-shadow: var(--apo-button-focus-ring);
}

/* The press: the disc squashes long while the finger is down, and when it is
   sitting at the right end it squashes LEFTWARD — the far edge stays anchored,
   which is the detail that makes the control feel hinged rather than skinned.
   The arithmetic: flex centring puts a 26px pressed disc at (38−26)/2 = 6px,
   so holding its right edge at the resting 50px mark needs 50 − 26 − 6 = 18px
   of travel, not the resting 20. Medium only — it is the size every switch in
   the product actually uses — and never on a control that cannot be pressed:
   a disabled switch that squirms under the pointer is promising a change it
   will not make. */
.mud-switch:not(.mud-disabled):not(.mud-readonly) .mud-switch-base:not(.mud-switch-disabled):active .mud-switch-thumb-medium {
    width: 26px;
}

.mud-switch:not(.mud-disabled):not(.mud-readonly) .mud-switch-base:not(.mud-switch-disabled).mud-checked:active {
    transform: translateX(18px);
}

@media (prefers-reduced-motion: reduce) {
    .mud-switch .mud-switch-base,
    .mud-switch .mud-switch-thumb-small,
    .mud-switch .mud-switch-thumb-medium,
    .mud-switch .mud-switch-thumb-large {
        transition: none;
    }

    /* Both halves of the squish, or the un-animated press JUMPS the checked
       thumb 4px left and back — more motion than the motion it removes. */
    .mud-switch:not(.mud-disabled):not(.mud-readonly) .mud-switch-base:not(.mud-switch-disabled):active .mud-switch-thumb-medium {
        width: 22px;
    }

    .mud-switch:not(.mud-disabled):not(.mud-readonly) .mud-switch-base:not(.mud-switch-disabled).mud-checked:active {
        transform: translateX(20px);
    }
}

/* Disabled keeps the geometry and drops the contrast, rather than switching to
   a different pair of colours — a greyed switch that is ON must still read as
   ON, which is most of what a disabled control is for. */
.mud-switch.mud-disabled .mud-switch-track,
.mud-switch .mud-switch-base.mud-switch-disabled ~ .mud-switch-track {
    background-color: var(--apo-ink-100) !important;
    opacity: 0.6 !important;
}

.mud-switch.mud-disabled .mud-switch-base.mud-checked ~ .mud-switch-track {
    background-color: var(--apo-ink-400) !important;
    opacity: 1 !important;
}

/* Helper / validation text under a field — a caption by role. */
.mud-input-helper-text {
    font-size: var(--apo-text-caption1);
}

.mud-expand-panel .mud-expand-panel-header {
    font-size: var(--apo-text-body);
}

.mud-list-subheader {
    font-size: var(--apo-text-caption1);
}

/* .mud-simple-table th is handled above; the td half was left on
   MudBlazor's 0.875rem, so the two halves of the same table differed. */
.mud-simple-table table * tr > td {
    font-size: var(--apo-text-body);
}

/* Button size ladder. 62 of the 63 sized call sites are Size.Small, so
   this is mostly about that step landing on Caption1 rather than
   12.19px. */
.mud-button-text-size-small,
.mud-button-filled-size-small,
.mud-button-outlined-size-small,
.mud-button-group-root.mud-button-group-text-size-small .mud-button-root,
.mud-button-group-root.mud-button-group-filled-size-small .mud-button-root,
.mud-button-group-root.mud-button-group-outlined-size-small .mud-button-root {
    font-size: var(--apo-text-caption1);
    min-height: 30px;
    padding-inline: 12px;
}

.mud-button-text-size-large,
.mud-button-filled-size-large,
.mud-button-outlined-size-large,
.mud-button-group-root.mud-button-group-text-size-large .mud-button-root,
.mud-button-group-root.mud-button-group-filled-size-large .mud-button-root,
.mud-button-group-root.mud-button-group-outlined-size-large .mud-button-root {
    font-size: var(--apo-text-subtitle2);
    min-height: 44px;
    padding-inline: 18px;
}

/* --- Icons ---------------------------------------------------------
   Same contract as text: icons are declared with font-size, so they
   belong to the icon ramp. MudBlazor's 18.75 / 22.5 / 33.75px become
   16 / 20 / 32. */
.mud-icon-size-small  { font-size: var(--apo-icon-sm) !important; }
.mud-icon-size-medium { font-size: var(--apo-icon-md) !important; }
.mud-icon-size-large  { font-size: var(--apo-icon-xl) !important; }

.mud-button-icon-size-small > *:first-child {
    font-size: var(--apo-icon-sm);
}

.mud-button-icon-size-large > *:first-child {
    font-size: var(--apo-icon-md);
}

.mud-alert-icon,
.mud-snackbar-icon {
    font-size: var(--apo-icon-md);
}

.mud-table-sort-label .mud-table-sort-label-icon,
.mud-data-grid .mud-table-cell .column-header .sort-direction-icon,
.mud-data-grid-columns-panel .sort-direction-icon {
    font-size: var(--apo-icon-sm);
}

.mud-chip.mud-chip-size-small .mud-icon-root,
.mud-chip .mud-chip-close-button .mud-icon-size-small {
    font-size: var(--apo-icon-sm) !important;
}

.mud-chip.mud-chip-size-large .mud-chip-icon {
    font-size: var(--apo-icon-md) !important;
}

/* Avatar glyphs are initials, so these are text steps, not icon ones. */
.mud-avatar.mud-avatar-small  { font-size: var(--apo-text-caption1); }
.mud-avatar.mud-avatar-medium { font-size: var(--apo-text-subtitle1); }
.mud-avatar.mud-avatar-large  { font-size: var(--apo-text-title3); }

.mud-chip.mud-chip-size-small .mud-avatar  { font-size: var(--apo-text-caption2); }
.mud-chip.mud-chip-size-medium .mud-avatar { font-size: var(--apo-text-caption1); }
.mud-chip.mud-chip-size-large .mud-avatar  { font-size: var(--apo-text-subtitle2); }

/* Date picker — 8 call sites. */
.mud-picker-calendar .mud-day {
    font-size: var(--apo-text-caption1);
}

/* The month prev/next arrows are .mud-icon-button, but MudBlazor addresses
   them through a two-class selector that outranks this file's one-class base
   rule — so they alone kept a Material surface fill while every other icon
   button in the product sits transparent until hovered. Matching MudBlazor's
   selector shape is what settles it; this file loads later. */
.mud-picker-calendar-header-switch > .mud-icon-button {
    background-color: transparent;
    border-radius: var(--apo-radius-control);
    margin: 0;
}

.mud-picker-calendar-header-switch > .mud-icon-button:hover {
    background-color: var(--apo-surface-hover);
}

.mud-picker-datepicker-toolbar .mud-button-year {
    font-size: var(--apo-text-subtitle2);
}

.mud-picker-datepicker-toolbar .mud-button-date {
    font-size: var(--apo-text-title1);
}

/* ============================================================
   SECTION TABS — an editorial pivot, not a widget.

   MudBlazor's defaults fought the design on four counts:
     · `.mud-tab { width: 100% }` stretched five tabs across the
       full width, so "Korisnici" and "Aktivne sesije" sat 200px
       apart with nothing between them
     · Rounded put a filled grey pill behind the active tab WHILE
       the slider drew an underline — two signals for one state
     · the slider used palette primary (near-black), so selection
       had no relationship to the champagne used everywhere else
     · icons rendered at 20px, heavier than the 14px label

   The result is the same gesture as the sidebar: type comes to full
   strength and a champagne rule marks the selection. Nothing fills.
   ============================================================ */
.apo-section-tabs .mud-tabs-tabbar {
    background: transparent !important;
    border-radius: 0 !important;
    /* The hairline runs the full width so the tabs sit ON an edge rather
       than floating above the content. The slider then reads as a segment
       of that edge coming forward. */
    border-bottom: 1px solid var(--apo-border);
    min-height: 0;
}

.apo-section-tabs .mud-tabs-tabbar-inner,
.apo-section-tabs .mud-tabs-tabbar-content {
    padding-inline: 0;
}

/* MudBlazor wraps every tab in a tooltip root, so THAT is the flex child of the
   bar, not the tab itself — sizing rules on .mud-tab alone never reach the
   layout. */
.apo-section-tabs .mud-tooltip-root {
    display: inline-flex;
    flex: 0 0 auto;
    min-width: 0;
}

.apo-section-tabs .mud-tab {
    /* width:auto beats MudBlazor's width:100%, but the 160px floor is written as
       an INLINE style by the component, which no stylesheet rule outranks
       without !important. Both are needed: without the floor removed every tab
       renders 160px wide regardless of its label, which is what put "Korisnici"
       and "Aktivne sesije" 200px apart with dead space between them. */
    width: auto;
    min-width: 0 !important;
    flex: 0 0 auto;
    min-height: 38px;
    height: 38px;
    padding: 0 14px;
    gap: 8px;
    border-radius: var(--apo-radius-sm) var(--apo-radius-sm) 0 0;
    background: transparent !important;
    color: var(--apo-ink-500) !important;
    font-size: var(--apo-text-body);
    font-weight: 500;
    letter-spacing: var(--apo-tracking-body);
    text-transform: none;
    line-height: 1.2;
    white-space: nowrap;
    transition: color var(--apo-duration-fast) var(--apo-ease),
                background var(--apo-duration-fast) var(--apo-ease);
}

/* Icons take the label's colour so a tab reads as one object rather than a
   glyph next to some text. */
.apo-section-tabs .mud-tab .mud-icon-root {
    font-size: var(--apo-icon-sm) !important;
    color: currentColor !important;
    margin: 0 !important;
}

.apo-section-tabs .mud-tab:hover {
    background: var(--apo-surface-hover) !important;
    color: var(--apo-ink-900) !important;
}

.apo-section-tabs .mud-tab:focus-visible {
    outline: 2px solid var(--apo-accent);
    outline-offset: -2px;
}

/* 500 -> 600 with the tracking pulled in to compensate, so the row does not
   reflow as the selection moves — the same compensation the sidebar uses. */
.apo-section-tabs .mud-tab.mud-tab-active {
    color: var(--apo-ink-900) !important;
    font-weight: 600;
    letter-spacing: -0.017em;
}

.apo-section-tabs .mud-tab-slider {
    background: var(--apo-accent) !important;
    height: 2px;
}

/* DO NOT hide .mud-tabs-panels here. This block once carried
   `.apo-section-tabs .mud-tabs-panels { display: none }`, which was correct
   while the class was only ever on the Identity navigation bar — those panels
   were empty because each tab navigated to its own route. When the styling
   moved to the editor dialog, that rule went with it and hid the dialog's
   entire form: the user editor rendered as a tab strip above two buttons with
   nothing in between, and no way to create or edit a user.

   The navigation tabs no longer exist, so the rule has no valid use at all. */

/* The bar measures ~576px across five tabs, so it overflows a phone viewport.
   MudBlazor normally renders its own scroll buttons on overflow, but that
   decision comes from JS measurement and this block changes the very things it
   measures (width, min-width, and the flex behaviour of the tooltip wrapper).
   Rather than depend on that still working, make the bar scrollable directly —
   if MudBlazor's arrows do appear they remain functional alongside it, and if
   they do not, the tabs are still reachable by swipe.

   Scrollbar chrome is hidden: a visible bar under five tabs reads as a defect
   on a surface this small, and the partially-clipped last tab is the affordance. */
@media (max-width: 900px) {
    .apo-section-tabs .mud-tabs-tabbar-inner {
        overflow-x: auto;
        scrollbar-width: none;
        -webkit-overflow-scrolling: touch;
    }

    .apo-section-tabs .mud-tabs-tabbar-inner::-webkit-scrollbar {
        display: none;
    }
}

/* ============================================================
   DATA GRID — the refinements that make a grid feel authored.
   ============================================================ */

/* Dates and counts sat on proportional figures, so "19. 04. 2026." and
   "02. 08. 2026." put their digits in different places down the column and
   the edge of the column read as ragged. Tabular figures are the single
   cheapest thing that makes a table look engineered. */
.mud-data-grid .mud-table-body .mud-table-cell,
.mud-table-root .mud-table-cell {
    font-variant-numeric: tabular-nums;
}

/* One line per row. A wrapping cell silently doubles row height, which is
   what pushed these rows to 57px and broke the density of the whole grid. */
.mud-data-grid .mud-table-body .mud-table-cell {
    max-width: 320px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mud-data-grid .mud-table-body .mud-table-cell:has(.apo-identity),
.mud-data-grid .mud-table-body .mud-table-cell:has(.apo-tags) {
    overflow: visible;
}

/* Rows are the action now — no icon column — so the row must say so. The
   cursor plus the existing hover wash is the whole affordance; a chevron or a
   hover-revealed pencil would just be the icon column again in disguise. */
.apo-grid--clickable .mud-table-body .mud-table-row {
    cursor: pointer;
}

/* Keyboard row navigation (keymap.js): the roving highlight wears the same
   wash as hover so arrow keys and the mouse read as one affordance, and
   focus-within covers Tab landing on the row's .apo-row-open button. The
   accent hairline on the first cell marks which of the two the keyboard owns. */
.apo-grid--clickable .mud-table-body .mud-table-row[data-row-active] .mud-table-cell,
.apo-grid--clickable .mud-table-body .mud-table-row:focus-within .mud-table-cell {
    background: var(--apo-surface-hover);
}

.apo-grid--clickable .mud-table-body .mud-table-row[data-row-active] .mud-table-cell:first-child {
    box-shadow: inset 2px 0 0 var(--apo-accent);
}

/* The identity cell is a real <button>. Everything here is reset: the row
   carries the hover, not the control inside it. */
.apo-identity--action {
    appearance: none;
    border: none;
    background: none;
    padding: 0;
    margin: 0;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    width: 100%;
    border-radius: var(--apo-radius-sm);
}

/* Keyboard is the reason this control exists, so its focus ring is not
   optional — it is the only visible route to editing without a mouse. */
.apo-identity--action:focus-visible {
    outline: 2px solid var(--apo-accent);
    outline-offset: 3px;
}

/* The row wash and the cursor are the whole affordance — the champagne name
   flip that used to ride along was a second signal on a hover doctrine that
   allows exactly one. */

/* The pager is chrome, so it recedes: figures align with the body above it
   and the control sits on the sunk surface rather than competing with data. */
.mud-table-pagination {
    font-variant-numeric: tabular-nums;
}

/* ============================================================
   CHECKBOXES — drawn, not a Material glyph in a ripple target.

   MudBlazor renders a 24px SVG inside a ~40px icon button with a
   circular ripple. Three things follow from that: the box is a font
   glyph so its weight never matches the hairlines used everywhere
   else, the ripple paints a grey disc on click that belongs to a
   different design language, and the 40px target puts the box far
   from its own label.

   The glyph is replaced with a real box — same 1px hairline, same
   5px radius, same champagne as every other selected thing in the
   app — and the tick is drawn with a rotated border rather than
   loaded as an icon, so it inherits colour and scales cleanly.

   State comes from MudBlazor's own .mud-checkbox-true/-false on the
   wrapper, so this needs no :has() and works on older engines.
   ============================================================ */
.mud-checkbox .mud-icon-button .mud-icon-root {
    display: none;
}

.mud-checkbox .mud-button-root.mud-icon-button {
    width: 18px;
    height: 18px;
    min-width: 18px;
    padding: 0;
    margin: 0 10px 0 0;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--apo-radius-sm);
    border: 1px solid var(--apo-border-strong);
    background: var(--apo-paper);
    color: transparent;
    overflow: visible;
    transition: background var(--apo-duration-fast) var(--apo-ease),
                border-color var(--apo-duration-fast) var(--apo-ease);
}

/* The ripple is a filled disc from Material; it has no place on a 18px box. */
.mud-checkbox .mud-button-root.mud-icon-button::before,
.mud-checkbox .mud-ripple-checkbox::before {
    display: none !important;
}

.mud-checkbox:hover .mud-button-root.mud-icon-button {
    border-color: var(--apo-ink-400);
}

/* The tick: two borders of a rotated box, centred by transform rather than by
   flex plus margins.

   The margin approach broke on the real control. Flex centres the UNROTATED
   4x8 box, so the rotated glyph needs nudging back — but the nudge was picked
   against a test box that had no border, and the real box has a 1px one. Two
   pixels of difference put the tick's upper arm through the top-right corner,
   where it read as clipped.

   translate(-50%, -50%) centres the element on its own box before the rotation
   is applied, so the result is correct whatever the border does. 47% rather
   than 50% because a checkmark's visual mass sits slightly below its
   geometric centre. */
.mud-checkbox .mud-button-root.mud-icon-button {
    position: relative;
}

.mud-checkbox .mud-button-root.mud-icon-button::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 47%;
    width: 4px;
    height: 8px;
    /* Reversed out of the fill: white on black in light, black on white in
       dark. --apo-brand-gold-fg is exactly that pairing, so the tick never
       has to know which theme it is in. */
    border: 2px solid var(--apo-brand-gold-fg);
    border-top: 0;
    border-inline-start: 0;
    transform: translate(-50%, -50%) rotate(45deg);
    opacity: 0;
    transition: opacity var(--apo-duration-fast) var(--apo-ease);
}

.mud-checkbox .mud-button-root.mud-icon-button.mud-checkbox-true {
    background: var(--apo-brand-gold);
    border-color: var(--apo-brand-gold);
}

.mud-checkbox .mud-button-root.mud-icon-button.mud-checkbox-true::after {
    opacity: 1;
}

/* Keyboard focus has to land on the box, not on an invisible input. */
.mud-checkbox .mud-checkbox-input:focus-visible + svg,
.mud-checkbox .mud-button-root.mud-icon-button:focus-within {
    outline: 2px solid var(--apo-accent);
    outline-offset: 2px;
}

.mud-checkbox .mud-button-root.mud-icon-button.mud-disabled,
.mud-checkbox .mud-checkbox-input:disabled + svg {
    border-color: var(--apo-border);
    background: var(--apo-surface-sunk);
}

/* The label is the click target too, so it sits tight against the box and
   takes the body step rather than MudBlazor's inherited size. */
.mud-checkbox {
    align-items: center;
    margin-inline-start: 0;
}

.mud-checkbox .mud-checkbox-label,
.mud-checkbox > label {
    font-size: var(--apo-text-body);
    color: var(--apo-ink-800);
    line-height: var(--apo-leading-body);
    cursor: pointer;
}

/* ============================================================
   TOUCH TARGETS — below the drawer breakpoint.

   Measured on the deployed app at 595px: the session-revoke
   controls render 22x22, and text buttons 37px. Both are under
   every touch guideline going (44pt Apple, 48dp Material, WCAG
   2.5.5 at 44px), and 22px is small enough to be a genuine
   mis-tap risk on a control that ends someone's session.

   Keyed on width rather than `pointer: coarse` so a narrow window
   on a laptop gets the same treatment — larger targets cost
   nothing there, and it means the rule can actually be verified
   in a desktop browser instead of taken on trust.
   ============================================================ */
@media (max-width: 1023px) {
    .mud-icon-button {
        min-width: 40px;
        min-height: 40px;
    }

    .mud-button {
        min-height: 40px;
    }

    /* Commit points get the full 44px — these are the last thing a thumb
       aims at before the form is gone. */
    .mud-dialog-actions .mud-button {
        min-height: 44px;
    }

    /* Primary LAST, so it sits at the bottom edge of the stack where the
       thumb rests. The previous column-reverse put it on top and justified
       that as thumb reach, which is backwards — on a phone the bottom of a
       stack is the reachable end, not the top. Reading order now runs
       least to most important: profile link, cancel, save. */
    .mud-dialog-actions {
        flex-direction: column;
        align-items: stretch;
    }

    /* The stacked footer was centring its buttons at their intrinsic width
       between 601px and 1023px, so a phone-shaped tablet got two ~110px
       controls floating in the middle of a full-width bar. The `align-items:
       stretch` above never landed: MudBlazor's own `.mud-dialog .mud-dialog-
       actions { align-items: center }` is a two-class selector and outranks
       the one-class rule. Matching its shape settles it, and the buttons fill
       the footer the way a stacked footer is supposed to. */
    .mud-dialog .mud-dialog-actions {
        align-items: stretch;
    }

    /* The tertiary action should not look like a third peer of Save. */
    .mud-dialog-actions .mud-button.mr-auto {
        margin-right: 0 !important;
        order: -1;
    }
}

/* ============================================================
   CONTROL HEIGHT — one value, enforced regardless of Margin.Dense.

   Measured on the deployed app: 54px on /settings/clinic, 38px in
   the user dialog. Same control, same design system, 16px apart,
   because Margin.Dense was passed on some call sites and not
   others. The prop is now irrelevant to height.
   ============================================================ */
.mud-input-control .mud-input.mud-input-outlined:not(:has(textarea)) {
    min-height: var(--apo-control-h);
    height: var(--apo-control-h);
}

/* The slot carries MudBlazor's vertical padding, which is what differs between
   dense and normal. Zeroing it and letting the slot fill the control is what
   makes the two variants land on the same number. */
.mud-input-control .mud-input.mud-input-outlined:not(:has(textarea)) .mud-input-slot,
.mud-input-control .mud-input.mud-input-outlined:not(:has(textarea)) .mud-input-root {
    height: 100%;
    padding-top: 0;
    padding-bottom: 0;
}

/* MudSelect does not render its display value in an <input> — it uses a <div>,
   and a div does not centre its own text the way a browser centres an input's
   value. So zeroing the slot padding above (which is what makes dense and
   normal land on the same height) left every select's text sitting against the
   top edge of its box while text fields looked fine.

   line-height, not flex: the slot is single-line and needs to keep
   text-overflow: ellipsis, which it would lose as an anonymous flex item.
   Matching the line height to the control height centres it exactly, and it
   follows the token if that height ever changes. */
.mud-input-control .mud-input.mud-input-outlined:not(:has(textarea)) div.mud-input-slot {
    line-height: var(--apo-control-h);
}

/* Textareas set their own height from Lines and must keep growing — a fixed
   height here would collapse a 10-line editor into a single row. */
.mud-input-control .mud-input.mud-input-outlined:has(textarea) {
    min-height: var(--apo-control-h);
    height: auto;
}

/* Buttons share the height so a field and the button beside it in a toolbar
   sit on one line — the search box and "Novi korisnik" in a page header were
   38px against 30px. Dialog actions no longer make an exception to this: see
   the .mud-dialog-actions rule above for why 38-against-36 was drift rather
   than emphasis. */
.mud-button:not(.mud-icon-button) {
    min-height: var(--apo-control-h);
}

/* The size classes must win over the shared control-height rule above. */
.mud-button.mud-button-text-size-small,
.mud-button.mud-button-filled-size-small,
.mud-button.mud-button-outlined-size-small {
    min-height: 30px;
    padding-inline: 12px;
}

.mud-button.mud-button-text-size-large,
.mud-button.mud-button-filled-size-large,
.mud-button.mud-button-outlined-size-large {
    min-height: 44px;
    padding-inline: 20px;
}

@media (min-width: 1024px) {
    .mud-dialog-actions .mud-button {
        padding-inline: 18px;
    }

    .mud-dialog-actions .mud-button.mud-button-text {
        padding-inline: 12px;
    }
}

/* Keep compact visual variants comfortable to tap on phones. The final size
   rules above intentionally win on desktop, while touch targets retain the
   platform-recommended minimum without making their labels look oversized. */
@media (max-width: 1023px) {
    .mud-button.mud-button-text-size-small,
    .mud-button.mud-button-filled-size-small,
    .mud-button.mud-button-outlined-size-small {
        min-height: 40px;
    }

    .mud-dialog-actions .mud-button {
        min-height: 44px;
    }
}

.mud-button:disabled,
.mud-button.mud-button-disabled,
.mud-icon-button:disabled,
.mud-icon-button.mud-disabled {
    box-shadow: none !important;
    transform: none !important;
    opacity: 0.42;
}

/* ============================================================
   GROUP BANDS — Linear's "Active 1" strips.

   A grouped grid renders each section header as a single
   full-width cell (.mud-datagrid-group). A table cell cannot
   carry margins, so the strip's breathing room is faked with
   transparent block borders and padding-box clipping; the xl
   radius minus the 6px transparent edge leaves a visual 6px,
   the md corner the rest of the system uses. Section labels
   come from each page's GroupTemplate as .apo-group-band.
   ============================================================ */
.mud-data-grid .mud-table-body .mud-table-cell.mud-datagrid-group {
    height: 52px;
    padding: 0 6px;
    background: var(--apo-surface-sunk);
    background-clip: padding-box;
    border: 0 solid transparent;
    border-top-width: 6px;
    border-bottom-width: 6px;
    border-radius: var(--apo-radius-xl);
    max-width: none;
}

/* The band is a section header, not a data row — the hover wash and its
   end-cap radii must not repaint it. Same specificity tier as the hover
   rules, so position in this file settles it. */
.mud-data-grid .mud-table-body .mud-table-row:hover .mud-table-cell.mud-datagrid-group,
.mud-data-grid .mud-table-body .mud-table-row:hover .mud-table-cell.mud-datagrid-group:first-child,
.mud-data-grid .mud-table-body .mud-table-row:hover .mud-table-cell.mud-datagrid-group:last-child {
    background: var(--apo-surface-sunk);
    background-clip: padding-box;
    border-radius: var(--apo-radius-xl);
}

/* The collapse chevron is quiet chrome riding inside the band — glyph at
   the small icon step, no hover disc competing with the band's own fill. */
.mud-data-grid .mud-table-cell.mud-datagrid-group .mud-button-root.mud-icon-button {
    width: 26px;
    height: 26px;
    color: var(--apo-ink-400);
    font-size: var(--apo-icon-sm);
}

.mud-data-grid .mud-table-cell.mud-datagrid-group .mud-button-root.mud-icon-button:hover {
    background: transparent;
    color: var(--apo-ink-700);
}

.mud-data-grid .mud-table-cell.mud-datagrid-group .mud-icon-button .mud-icon-root {
    font-size: var(--apo-icon-sm) !important;
}

/* Label + muted count, the "Active 1" pairing. */
.apo-group-band {
    display: inline-flex;
    align-items: baseline;
    gap: 8px;
    font-size: var(--apo-text-body);
    font-weight: 550;
    color: var(--apo-ink-900);
    letter-spacing: var(--apo-tracking-body);
}

.apo-group-band__count {
    color: var(--apo-ink-400);
    font-weight: 500;
    font-size: var(--apo-text-body);
    font-variant-numeric: tabular-nums;
}
