﻿.d-contents {
    display: contents;
}

.fixed {
    position: fixed;
}

.inset-0 {
    inset: 0;
}

.z-max {
    z-index: 9999;
}

.min-h-0 {
    min-height: 0;
}

.min-w-0 {
    min-width: 0;
}

.w-full {
    width: 100%;
}

.h-full {
    height: 100%;
}

.flex-1 {
    flex: 1 1 auto;
}

.flex-fill {
    flex: 1 1 0%;
}

.bg-primary {
    background-color: var(--mud-palette-primary);
}

.bg-secondary {
    background-color: var(--mud-palette-secondary);
}

.bg-surface {
    background-color: var(--mud-palette-surface);
}

.bg-background {
    background-color: var(--mud-palette-background);
}

.text-primary {
    color: var(--mud-palette-primary);
}

/* NsTable structural layout: the scroll area grows so the pager stays pinned
   at the bottom when the table fills a flex container. */
.ns-table .mud-table-container {
    flex: 1 1 auto;
    overflow-y: auto;
}

/* Zebra banding already gives every row an edge, so MudBlazor's per-row hairline
   would only double it. The stacked layout below the breakpoint keeps its own
   separator (a more specific MudBlazor rule), which is the one that divides records
   there — the header hairline is restored below because it divides head from body. */
.ns-table .mud-table-body .mud-table-cell {
    border-bottom: none;
}

/* Column headers left at text-primary weigh exactly as much as the data underneath;
   dropping them to secondary is what gives the grid a spine. */
.ns-table .mud-table-head .mud-table-cell {
    color: var(--mud-palette-text-secondary);
    border-bottom: 1px solid var(--mud-palette-table-lines);
}

/* NsLink wraps a link it drives itself (a surface move, which replaces its history entry
   instead of pushing one) so it has an element to prevent the click's default on. The wrapper
   carries no presentation and must not become one: display:contents takes it out of layout
   entirely, so the chrome it holds stays the flex or grid item its parent already sized —
   a toolbar's icon slots and a row's action column count the button, not this span. Event
   propagation is unaffected by display:contents, which is the whole reason it can be used. */
.ns-link-intercept {
    display: contents;
}

/* The grid's action column, sized by the column and never by the grid that mounts it.
   NsTh/NsTd mark it: it is the one column naming no field and carrying no label, which is
   already why it never hides.

   A slot is one small icon button (a 1rem glyph inside MudBlazor's 3px padding) plus the
   4px separating two of them, and the reserve below is --ns-action-slots of those less that
   last separation — the toolbar's own cap plus the kebab overflow raises, held whether the
   kebab renders or not. That reserve is what keeps a row of three actions and a row of one
   the same width, so the first icon of every row starts at the same x.

   width:1% is the shrink-to-fit idiom for an auto-layout table: the cell asks for nothing
   past its content and the data columns take the rest, which leaves the icons at the
   column's own left edge with no slack for an alignment to argue about — no grid declares
   one, so no two grids can disagree. */
.ns-table .ns-actions {
    --ns-action-slot: 1.625rem;
    width: 1%;
}

.ns-table .ns-actions .ns-action-toolbar {
    min-width: calc(var(--ns-action-slots, 4) * var(--ns-action-slot) - 0.25rem);
}

/* Every icon in the column is one slot, whoever put it there. A grid's own row links are
   plain markup beside the outlet and were each expected to remember the presenter's compact
   chrome; the ones that did not rendered a medium button next to the contributed small ones,
   which is two icon boxes in one row and the raggedness this column exists to end. The
   column decides instead, so there is nothing left for a page to forget. */
.ns-table .ns-actions .mud-icon-button {
    padding: 3px;
}

.ns-table .ns-actions .mud-icon-root {
    font-size: 1rem;
}

/* NsDrawer: never wider than the viewport; width follows Surface.Size (animated for runtime resizes). */
.ns-drawer {
    max-width: 100vw;
    transition: width 200ms ease;
}

/* An end-anchored drawer casts sideways onto the page it covers, which the elevation
   levels — vertical offsets, for stacked paper — cannot express. Judged by eye against the
   light theme: the original -12px/32px/0.38 read as a spotlight flooding the main surface
   rather than a lifted panel beside it, so the cast is pulled back to a shorter reach and a
   quieter alpha. Not theme-conditional: MudThemeProvider swaps its CSS variables in place
   (no lasting light/dark selector), and NsSetup — the component that knows IsDarkMode — is
   Routes' sibling under App.razor's <Body>, not its ancestor, so nothing here can cascade
   from it. The pullback is one shared value; it reads correctly against a light background
   by construction (the reduced alpha is what a dark canvas needed less of already, per
   BrandMudTheme.BuildElevations' own note that this theme's shadows were retuned darker
   than Material's for a dark canvas). */
.ns-drawer.mud-drawer {
    box-shadow: -3px 0 8px rgba(0, 0, 0, 0.18), 0 0 0 1px rgba(255, 255, 255, 0.06);
}

/* The nav menu's own quiet counterpart: a start-anchored drawer casts sideways too, toward
   the content on its right. It had no lift at all (Elevation="0" on NsMainLayout's MudDrawer)
   where the light theme wants a little — enough to read as a surface, clearly under the
   title bar's own Elevation="2" (BrandMudTheme's ladder: ~0.24 alpha, ~6.8px blur, 3px
   offset) and about the aside's pulled-back value above. Same mechanism as .ns-drawer,
   mirrored offset, one notch quieter. */
.ns-nav-drawer.mud-drawer {
    box-shadow: 2px 0 6px rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(255, 255, 255, 0.06);
}

/* The nav drawer is a SIBLING of MudMainContent, never touched by the main-surface scroll
   work — its own content region is its own owner, the same doctrine everywhere else: only
   what is marked scrolls. MudBlazor's .mud-drawer-content stretches to the drawer's full
   height (flex-basis from its own height:100%) and declares no overflow of its own, so a
   menu taller than the viewport spills past it unreached — .mud-drawer's ambient
   overflow-y: auto sits one box further out than the actual squeeze, which is exactly the
   failure this doctrine warns about. Marking the content region itself is the cure. */
.ns-nav-drawer .mud-drawer-content {
    min-height: 0;
    overflow-y: auto;
}

/* MudBlazor paints every paper with Surface, so a menu opening over a card lands on
   the exact tone it floats above. These take the raised step of the brand's ladder
   (see BrandMudTheme: the brand's SurfaceRaised travels in BackgroundGray). */
.mud-popover.mud-paper,
.mud-dialog.mud-paper {
    background-color: var(--mud-palette-background-gray);
}

/* A floating label is promoted to text-primary by MudBlazor, which puts the name of a
   field at the same weight as its value. The focus rule is restated because it has the
   same specificity and would otherwise lose to the rule above it; the error rule wins
   on its own (MudBlazor marks it !important). */
.mud-shrink ~ label.mud-input-label.mud-input-label-inputcontrol {
    color: var(--mud-palette-text-secondary);
}

.mud-input:focus-within ~ label.mud-input-label.mud-input-label-inputcontrol {
    color: var(--mud-palette-primary);
}

/* The browser fills a saved credential by writing the input's value directly, without the
   event the binding listens to — so the component's Value stays empty, MudBlazor never adds
   .mud-shrink, and the resting label (the position it only holds for an EMPTY field) is
   printed on top of the text the browser just wrote: the field shows its value and its
   label-as-placeholder at once, on first paint, before anyone has touched it.
   :-webkit-autofill is the only signal that state offers CSS, and it is the portable
   spelling — Chrome, Edge, Safari and Firefox all match it. These restate MudBlazor's own
   shrink geometry under it, verbatim, so the label floats exactly where a typed value
   floats it; :has() also carries the extra specificity that makes them win the tie. */
.mud-input:has(> input:-webkit-autofill) ~ label.mud-input-label.mud-input-label-inputcontrol {
    transform: translate(0, 1.5px) scale(0.75);
    transform-origin: left top;
}

.mud-input:has(> input:-webkit-autofill) ~ label.mud-input-label.mud-input-label-inputcontrol.mud-input-label-outlined {
    transform: translate(14px, -6px) scale(0.75);
    max-width: calc(133.333% - 18.6667px);
}

.mud-input:has(> input:-webkit-autofill) ~ label.mud-input-label.mud-input-label-inputcontrol.mud-input-label-filled {
    transform: translate(12px, 10px) scale(0.75);
    max-width: calc(133.333% - 16px);
}

.mud-input:has(> input:-webkit-autofill) ~ label.mud-input-label.mud-input-label-inputcontrol.mud-input-label-filled.mud-input-label-margin-dense {
    transform: translate(12px, 7px) scale(0.75);
}

/* The outlined variant's notch is a fieldset legend that only opens for a shrunk label; left
   closed, the floated label above lands on the border line instead of in a gap. */
.mud-input.mud-input-outlined:has(> input:-webkit-autofill) > .mud-input-outlined-border legend {
    width: auto;
    padding: 0 5px;
}

/* The spin behind NsIcons.Progress. fill-box puts the origin at the centre of the shape
   itself, so it keeps spinning true whatever viewBox the icon is rendered into. Reduced
   motion slows it rather than stopping it — a frozen spinner still claims to be working. */
.ns-spin {
    transform-box: fill-box;
    transform-origin: center;
    animation: ns-spin 900ms linear infinite;
}

@keyframes ns-spin {
    to {
        transform: rotate(360deg);
    }
}

@media (prefers-reduced-motion: reduce) {
    .ns-spin {
        animation-duration: 2.4s;
    }
}

/* The record-state switch in a title bar reads as chrome, not as a field: no form margin of
   its own, and its label takes the secondary weight so the title still leads the row. */
.ns-record-state {
    margin: 0;
}

.ns-record-state span.mud-typography {
    font-size: 0.8125rem;
    color: var(--mud-palette-text-secondary);
}

/* Search box in a page-header toolbar: a comfortable width that can shrink on a
   narrow toolbar but never stretches end to end — the spacer past it holds the gap. */
.ns-toolbar-search {
    flex: 0 1 22rem;
}

/* The app's icon scale, in one place: NsIcon maps NsSize onto the vendor's three steps and
   this is what those steps measure. Material's own (20/24/36) are drawn for touch — beside
   14px text they read as illustrations. Set here rather than per control because a nav link,
   a button and a field adornment each render their own icon and never ask NsIcon. */
.mud-icon-size-small {
    font-size: 1rem;
}

.mud-icon-size-medium {
    font-size: 1.25rem;
}

.mud-icon-size-large {
    font-size: 1.5rem;
}

/* NsText Secondary: the muted rank of body text. Not Color.Secondary — that is the
   brand's spare accent, a different meaning of the same word. */
.ns-text-muted {
    color: var(--mud-palette-text-secondary);
}

/* A line that flags a broken record inline (a row whose stored document could not be
   loaded) — the same severity colour NsAlert's Danger uses, without the block-level
   padding an alert would force into a table cell. */
.ns-text-danger {
    color: var(--mud-palette-error);
}

/* NsForm's form-level refusal: what the server answered that names no field this form
   renders — a rule, a member nothing bound, a problem with no issue at all. It renders after
   the form's content, which is where the submit that asked for it sits, and it is a block so
   several issues stack as separate lines rather than running together the way the toast it
   replaces did. Same tint-plus-severity-colour idiom as the row semaphore below: the two
   palette variables already in use here, no new one assumed. */
.ns-form-problem {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-top: 8px;
    padding: 10px 14px;
    border: 1px solid var(--mud-palette-error);
    border-radius: var(--mud-default-borderradius);
    background-color: var(--mud-palette-error-hover);
    color: var(--mud-palette-error);
    font-size: 0.875rem;
}

/* The attention semaphore (intentional-ui): a grid row painted from a CONDITION its own
   data settles (an overdue promise, a ready order), never from a stored state and never a
   custom color — theme severity only, the same "-hover" alpha tint NsTimeBlock's own
   background already uses for a wash the text on top stays readable against. */
.ns-row-danger {
    background-color: var(--mud-palette-error-hover);
}

.ns-row-warning {
    background-color: var(--mud-palette-warning-hover);
}

.ns-row-success {
    background-color: var(--mud-palette-success-hover);
}

/* A node whose template is more than a name (NsTree stacks a fact and the row's actions
   under it) makes the row tall, and MudBlazor centres both the toggle and the row against
   that whole height. The row carries the same surface as a grid so the tree reads as one
   list of records rather than text on the page. Selection and hover are more specific
   rules of MudBlazor's own, so they still win over this background. */
.ns-tree .mud-treeview-item-content {
    align-items: flex-start;
    background-color: var(--mud-palette-surface);
    border-radius: var(--mud-default-borderradius);
    margin-bottom: 6px;
    padding: 10px 12px;
}

/* MudBlazor indents a level by 17px, which is less than the toggle column (2rem plus its
   margins) — a child ends up beside its parent instead of under it. 48px is where the
   parent's own title starts, so a child card hangs off the name it belongs to. Both
   properties, because MudBlazor sets both and logical-vs-physical order decides otherwise. */
.ns-tree .mud-treeview-group {
    margin-left: 48px;
    margin-inline-start: 48px;
}

/* The toggle's button box is 32px against a 24px title line: aligning the boxes leaves the
   glyph half that difference too low, so it comes back up by exactly that. */
.ns-tree .mud-treeview-item-arrow {
    margin-top: -4px;
}

/* NsDialog's backdrop (MudOverlay) and wrapper both cover the full viewport by default —
   correct on mobile, where the nav hides itself anyway. From the SAME breakpoint the nav
   drawer already uses to dock (NsMainLayout: Breakpoint.Md, a plain media query because a
   fixed-position overlay is sized against the viewport, never a container), both scope to
   the main-content region instead: inset from the left by the docked nav's own width,
   --ns-nav-width (NsMainLayout sets it; 0 when a layout declares no nav, so nothing insets
   against a nav that is not there). The nav stays visible and clickable under a big modal;
   a nav click still closes the modal like any other navigation (?modal= drops). left, not
   inset-inline-start: it overrides MudBlazor's own .mud-overlay, itself physical (left/
   right), so mixing models here would leave the two fighting over the same edge. */
@media (min-width: 960px) {
    .ns-modal-scope {
        left: var(--ns-nav-width, 0px);
    }
}

/* Container queries: a screen's chrome (NsPanel, NsCard) declares itself the
   measuring reference, so the d-c-* utilities below react to the width a control
   actually gets — a 960px aside drawer is narrow even on a 1920px viewport, which
   a plain media query cannot see. inline-size contains the horizontal axis only:
   these are flex columns whose width comes from the parent, never from content. */
.ns-container {
    container-type: inline-size;
}

/* Container-width display utilities, mirroring MudBlazor's d-{breakpoint}-{value}
   (which measures the viewport) at MudBlazor's own breakpoints. Deliberately only
   the values in use: the full 9-values x 6-breakpoints grid is 54 rules nobody reads. */
.d-c-none {
    display: none;
}

@container (min-width: 600px) {
    .d-c-sm-inline {
        display: inline;
    }

    .d-c-sm-flex {
        display: flex;
    }

    .d-c-sm-table-cell {
        display: table-cell;
    }
}

@container (min-width: 960px) {
    .d-c-md-inline {
        display: inline;
    }

    .d-c-md-flex {
        display: flex;
    }

    .d-c-md-table-cell {
        display: table-cell;
    }
}

@container (min-width: 1280px) {
    .d-c-lg-inline {
        display: inline;
    }

    .d-c-lg-flex {
        display: flex;
    }

    .d-c-lg-table-cell {
        display: table-cell;
    }
}

@container (min-width: 1920px) {
    .d-c-xl-inline {
        display: inline;
    }

    .d-c-xl-flex {
        display: flex;
    }

    .d-c-xl-table-cell {
        display: table-cell;
    }
}

/* NsDashboard: the grid decides how many cards a row holds, not the card. A card declares
   no width of its own, so the same set reflows from one column in a narrow surface to
   several on a wide one without any of them knowing where it was mounted. */
.ns-dashboard {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
    gap: 16px;
    width: 100%;
}

/* Below MudBlazor's own sm breakpoint (the same 599.98px the @container rules above use)
   a 20rem minimum no longer fits the viewport at all, so auto-fill leaves a column that
   scrolls horizontally instead of wrapping. One column always fits. */
@media (max-width: 599.98px) {
    .ns-dashboard {
        grid-template-columns: 1fr;
    }
}

/* NsTimeGrid: a time-of-day axis crossed by one column per day. The hour is the unit every
   block's offset and height are a calc() against, so the scale is set here and nowhere in
   C#; the gutter's hour rows and each column's stack the same number of them, which is what
   keeps the marks lined up with the lines they name.

   ns-container on the grid itself rather than relying on the panel around it: the width that
   decides whether seven columns fit is the grid's own, and this way the degradation holds
   wherever the grid is mounted. Safe here for the reason NsPanel is — a flex row taking its
   width from the parent, never from its content. */
.ns-time-grid {
    --ns-time-hour: 3.5rem;
    container-type: inline-size;
}

.ns-time-grid-gutter {
    display: flex;
    flex-direction: column;
    flex: 0 0 4rem;
}

/* Fixed, not content-derived: the gutter's spacer and the day names must agree to the pixel
   or every column's hours sit a little below their labels. */
.ns-time-grid-head {
    height: 2rem;
    flex: 0 0 2rem;
}

/* The day names carry the same divider their columns do, so the two rows read as one grid. */
.ns-time-grid-head > div {
    border-inline-start: 1px solid var(--mud-palette-table-lines);
}

.ns-time-grid-mark {
    color: var(--mud-palette-text-secondary);
    font-size: 0.75rem;
    line-height: 2rem;
    padding-inline: 6px;
    white-space: nowrap;
}

.ns-time-grid-hour {
    height: var(--ns-time-hour);
    flex: 0 0 var(--ns-time-hour);
    border-top: 1px solid var(--mud-palette-table-lines);
}

.ns-time-grid-hour .ns-time-grid-mark {
    line-height: 1;
    /* The mark names the line above it, so it rides up by half its own text height to sit
       on that line instead of hanging below it. */
    display: block;
    margin-top: -0.375rem;
    text-align: end;
}

/* The positioning context for the blocks inside it, and their clip: a block reaching past
   the window (an appointment running into the night) stops at the column's edge instead of
   painting over the day names. */
.ns-time-grid-column {
    position: relative;
    overflow: hidden;
    border-inline-start: 1px solid var(--mud-palette-table-lines);
}

.ns-time-block {
    position: absolute;
    inset-inline: 2px;
    min-height: 1.25rem;
    overflow: hidden;
    padding: 2px 6px;
    border: 1px solid var(--mud-palette-table-lines);
    border-inline-start: 3px solid var(--mud-palette-primary);
    border-radius: var(--mud-default-borderradius);
    background-color: var(--mud-palette-surface);
    /* The reset a <button> needs to stop looking like one; harmless on the div variant, so
       the two branches keep the same look. display:block undoes the UA button-layout
       algorithm (WHATWG "button layout"), which otherwise centers content vertically —
       a short label in a tall block (a two-hour free interval) would float at mid-height
       instead of sitting at the top, next to the gridline its Starts actually names. */
    display: block;
    font: inherit;
    color: inherit;
    text-align: start;
    z-index: 1;
}

.ns-time-block-background {
    border: 1px dashed var(--mud-palette-table-lines);
    background-color: var(--mud-palette-primary-hover);
    z-index: 0;
}

/* A place's color shown beside its name — legends and management lists alike, never color
   alone (agenda blocks carry the same rule through their own leading-edge accent above). */
.ns-place-swatch {
    display: inline-block;
    width: 0.75rem;
    height: 0.75rem;
    border-radius: 50%;
    border: 1px solid var(--mud-palette-table-lines);
}

/* Only the focused column survives a container narrower than its columns need (see
   NsResponsive.ColumnsClass). display:none rather than a squeeze: the remaining column
   takes the whole width back on its own, being a flex child. */
@container (max-width: 599.98px) {
    .ns-time-collapse-sm .ns-time-grid-aside {
        display: none;
    }
}

@container (max-width: 959.98px) {
    .ns-time-collapse-md .ns-time-grid-aside {
        display: none;
    }
}

@container (max-width: 1279.98px) {
    .ns-time-collapse-lg .ns-time-grid-aside {
        display: none;
    }
}

@container (max-width: 1919.98px) {
    .ns-time-collapse-xl .ns-time-grid-aside {
        display: none;
    }
}

/* Collapsed icon+label control (see NsButton/NsLink Breakpoint): MudBlazor gives the
   start icon asymmetric margins (-4px left to offset the button's padding, 8px right to
   clear the label). With the label hidden those 8px are left over and the icon sits off
   centre, so the margin is neutralised — not restated — below each breakpoint, keeping
   MudBlazor free to change its own spacing. */
@container (max-width: 599.98px) {
    .ns-collapse-sm .mud-button-icon-start {
        margin-inline: 0;
    }
}

@container (max-width: 959.98px) {
    .ns-collapse-md .mud-button-icon-start {
        margin-inline: 0;
    }
}

@container (max-width: 1279.98px) {
    .ns-collapse-lg .mud-button-icon-start {
        margin-inline: 0;
    }
}

@container (max-width: 1919.98px) {
    .ns-collapse-xl .mud-button-icon-start {
        margin-inline: 0;
    }
}

/* NsRemoteImage's own box: the <img> and the inlined <svg> are two different elements
   with two different natural sizing rules, so the wrapper is what Height actually
   constrains -- both children fill it and keep their own aspect ratio. */
.ns-remote-image {
    display: inline-flex;
    align-items: center;
}

.ns-remote-image > svg,
.ns-remote-image > img {
    height: 100%;
    width: auto;
}

/* NsSplash's live percentage: Blazor's own boot script writes --blazor-load-percentage-text
   as an already-quoted string ("45%") for exactly this -- content is the only place a CSS
   custom property becomes visible text without a JS poll re-reading it every frame. */
.ns-splash-percent::after {
    content: var(--blazor-load-percentage-text, '"0%"');
}
