﻿.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);
}

/* The collection chrome's title bar, shared by every collection editor whatever it renders
   its items with (NsTable's Title, NsListEditor's): reads as a header band over the
   collection, not a loose title row with air around it — the same hairline that already
   divides a table's own header from its body carries the seam here too. One rule and one
   class, so the two cannot come to look like different components.

   The horizontal inset carried here (stories.md, "Sales: el 'sin margenes' se modera")
   belongs to the whole family, not just the bar: Leonardo's original "sin margenes" ruling
   on the Seña killed the wrapper's own lateral pa-4 outright (SaleLinesEditor/TendersEditor
   both wrap in Class="py-4", horizontal dropped on purpose), so the bar, a list row, an
   empty collection's own message and the foot's totals all sat flush against the card's
   border — moderated, not reverted ("un padding minimo por favor"): the house's smallest
   step, the same 0.5rem this rule already spent on its own vertical seam, shared by every
   selector in the family below so nothing in one collection lines up against a different
   edge than its neighbour. */
.ns-collection-bar,
.ns-list-editor-item,
.ns-list-editor-empty,
.ns-collection-foot {
    padding-inline: 0.5rem;
}

.ns-collection-bar {
    padding-bottom: 0.5rem;
    border-bottom: 1px solid var(--mud-palette-table-lines);
}

/* NsListEditor's items. A real <ul>, so the collection is announced as a list and each item
   as one of it; the browser's own bullets and indent are chrome the house draws itself.

   The item is a flex row that WRAPS, which is the whole reason this is not a table: the
   fields an item carries differ from item to item, so a narrow container folds them onto a
   second line instead of strangling a column that half the items would leave empty. Only the
   list reset and the separator live here — the row's own flex is utility classes in the
   markup, where the element and its layout are read together. */
.ns-list-editor {
    list-style: none;
    margin: 0;
    padding: 0;
}

.ns-list-editor-item {
    padding-block: 4px;
}

.ns-list-editor-item + .ns-list-editor-item {
    border-top: 1px solid var(--mud-palette-table-lines);
}

/* A field is a flex item here, and MudBlazor's own control declares flex:1 1 auto — with no
   basis of its own a line of six fields shrinks all six instead of folding the last ones onto
   the next line, which is the strangling a table does and this component exists to end. The
   basis is what a field needs to be readable; growing past it is still allowed, so a lone
   field on a line does not sit half-width. */
.ns-list-editor-item .mud-input-control {
    flex: 1 1 12rem;
}

/* Iam.AccountLoginsList: one connected account, visually contained (Emmanuel's story,
   2026-08-11). Two glyphs at two different depths — unlink the account, disconnect a
   service — used to read as the same action; the border is what tells them apart. Same
   hairline and radius the house's other subtle borders already use (.ns-form-problem,
   .ns-tree), never a new token. */
.ns-account-block {
    border: 1px solid var(--mud-palette-table-lines);
    border-radius: var(--mud-default-borderradius);
    padding: 0.5rem;
}

/* The provider's services hang off the identity line above them, indented so the block
   reads as one account with services inside it rather than a list of equal siblings —
   each service row still ends at the SAME right edge the identity's own unlink does,
   because both sit inside the same padded block. */
.ns-account-services {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-top: 0.5rem;
    padding-left: 1.5rem;
}

/* The Sesión dialog's header column for the actor's picture: PartyAvatar sizes its photo
   branch itself but leaves its glyph fallback unsized, so neither branch alone guarantees a
   square footprint at a generous size. Fixed dimensions here give both the same frame; Size
   on PartyAvatar (64) matches the number below so the photo fills it exactly. */
.ns-session-avatar {
    width: 64px;
    height: 64px;
    flex: none;
}

/* A plain NsLink (As="Inline") renders a bare anchor with no color or underline of its own —
   honest chrome for a link that reads as clickable text rather than a button pretending to be
   one (the Sesión dialog's Mi Perfil, stories.md). The house has no primary+underlined link
   mode yet; this is the minimal class until a second caller earns one. */
.ns-link-primary {
    color: var(--mud-palette-primary);
    text-decoration: underline;
}

/* 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);
}

/* MudChip carries a vendor margin: 4px on every side, meant for a chip sitting loose among
   siblings — inside a table cell there are no siblings to space from, so the 4px becomes 8px
   of unearned row height stacked on the pill's own 32px, still the tallest thing in a row
   whose text sits comfortably in far less (1ac15086 grew the action icons for the same
   inversion; the pill itself stays exactly as large — killing the MARGIN is not the slimming
   Leonardo vetoed). Scoped to a table cell only: a chip anywhere else keeps the vendor's own
   spacing. */
.ns-table .mud-table-cell .mud-chip {
    margin: 0;
}

/* 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 catch the click on — and a class the
   document listener that decides whether to prevent that click's default answers on (ns.js).
   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 icon button (a 1.5rem glyph inside 3px of 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, the kebab overflow raises, and the row's constant where it carries
   one, held whether the kebab renders or not. The cell IS one toolbar (intentional-ui, grids:
   the page's fixed verbs ride in as Leading), so that reserve now covers the whole cell
   rather than a fragment of it sitting between two hand-written icons — which is what the
   hole Leonardo marked before the lupa was made of.

   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. The strip packs to the column's
   trailing edge — the cell aligns the toolbar (it is inline-flex, so text-align reaches it)
   and the toolbar packs its own icons the same way, which pins whichever control closes the
   row (the kebab when the row overflows — Leonardo, 2026-08-11: "el kebab cierra la fila" —
   the constant otherwise) at the fixed edge, and lets the rest grow leftward from it. Rows
   sharing the same overflow state then line their trailing control up on the same x; a row
   that overflows sits its constant one slot short of that edge, the kebab past it. */
.ns-table .ns-actions {
    --ns-action-slot: 2.125rem;
    width: 1%;
    text-align: right;
}

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

/* 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.

   The glyph takes the middle step of the icon scale, not the small one (Leonardo, on the OT
   grid: "icons sube! en todos lados.. son re chiquitos" — and, of the same row, "por qué el
   pill es más grande que el ícono?"). A 16px glyph beside a 32px status chip inverted the
   hierarchy: the passive label outweighed the controls that actually do something. The
   padding is deliberately NOT touched — 3px is what keeps the desktop grid dense — and the
   correction was the glyph alone: 16px to 20px, box 22px to 26px.

   Leonardo asked again (2026-08-11, "íconos de los comandos de la tabla un poco más grandes,
   igual que en las cards de Organization"): measured against OrganizationsPage's row actions
   (Directory.Shared, no Size override — MudBlazor's own unmodified Medium, the vendor's base
   .mud-icon-button rule, no `-size-medium` modifier class exists), the card's glyph is
   1.5rem/24px, one step past this column's 1.25rem/20px. The column grows to match it — 20px
   to 24px, box 26px to 30px — and padding stays untouched again: matching the card's GLYPH is
   the ask, matching its 12px of padding would cost the density this block exists to keep. */
.ns-table .ns-actions .mud-icon-button {
    padding: 3px;
}

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

/* A coarse pointer hits an area, not a point (Leonardo, on Android: "en mobile me cuesta
   meter el garfio"). The compact chrome above measures 30px, which is well under half of what
   a finger lands on, so the BOX grows to 48px. Padding alone carries it now: the glyph is
   already at 1.5rem above (and matches the card's own glyph, so nothing here shrinks it).

   The grid's action column is the one scope left to correct here. Everywhere else an Ns icon
   action is MudBlazor's own medium — 12px of padding around that same 1.5rem glyph, 48px by
   construction — which is also what NsActionToolbar renders outside a table (Organization's
   cards among them), so restating either property for .ns-action-toolbar would only re-derive
   what the size already gives, and the font-size half of it used to shrink the app bar's
   deliberately larger icons.

   The slot grows with the button, or the reserve would stop matching what it reserves for:
   12 + 24 + 12 plus the 4px between two of them is 3.25rem, as 3 + 24 + 3 + 4 is 2.125rem. The
   min-width above is a calc over the variable and needs no restatement.

   Not a breakpoint: a phone held in landscape is still a finger, and a touchscreen laptop
   reports its mouse as the primary pointer, so the desktop grid is untouched by measure. */
@media (pointer: coarse) {
    .ns-table .ns-actions {
        --ns-action-slot: 3.25rem;
    }

    .ns-table .ns-actions .mud-icon-button {
        padding: 12px;
    }
}

/* The stacked layout, where a row is a card and every cell a line of its own (MudTable's
   Breakpoint.Sm, which is .mud-sm-table under the vendor's own max-width: 960px — matched
   here rather than translated, since the stacking this corrects is that query's own doing).

   One of the column's own rules stops meaning anything there, and one of the vendor's
   misfires. width:1% is the shrink-to-fit idiom of a table COLUMN and there is no column
   left — the cell is a block-level flex box, so 1% is 1% of the card and the strip spills
   out of it from the left. And the vendor lays every cell out as label-then-value with
   space-between; the action cell is the one cell that names no field, so its label pseudo is
   empty and the pair it balances is an invisible box against the strip. The cell takes its
   full width back, and the empty pseudo goes — left in, it would hold its own 16px against
   whichever edge the strip packs to.

   Leonardo asked for the right edge first ("la botonera se tiene que ver alineada a la
   derecha", 4718a5a7), then a device screenshot of that build read as CENTERED rather than
   right-aligned, so ec20a9a2 tried the left edge instead — same defect, mirrored, because
   flipping this rule's own justify-content was never the fix. THE ACTUAL BUG lived one level
   down: NsActionToolbar renders even a single icon inside a span reserved for MaxVisible + 1
   slots (--ns-action-slots, min-width below), so it can hold its width across rows with
   different counts — correct on desktop, where the reserve sits to the RIGHT of a
   left-packed icon and nothing downstream cares. Here the toolbar is one flex item among
   others in the actions cell (NsStack, gap-1), so its own default packing (flex-start) left
   the dead reserve BETWEEN the toolbar's visible icon and its neighbour (the row's own
   NsPageLink, e.g. the lupa) — a gap invariant to whichever edge THIS rule packed the whole
   cluster to, which is exactly why every flip "did nothing": the bounding box moved, the hole
   inside it did not. The toolbar packs its own content to ITS box's trailing edge here, so
   the reserve moves to the toolbar's leading side (against its neighbour, hidden inside the
   cluster) instead of sitting in the gap that used to read as "not quite aligned either way";
   the icons then read as one strip, and the cell packs that strip to the right — Leonardo's
   final ruling ("donde sea menos ahí en el centro"), reached this time by closing the hole
   that was defeating the justify-content, not by flipping it again. The toolbar's own packing
   is no longer restated in this block: it is the action column's at every width now (above),
   since the cell it fills is one toolbar and whichever control closes the row (the constant,
   or the kebab past it once the row overflows) belongs at the trailing edge in a card exactly
   as in a row. */
@media (max-width: 960px) {
    .ns-table.mud-sm-table .ns-actions {
        width: auto;
        justify-content: flex-end;
    }

    .ns-table.mud-sm-table .ns-actions::before {
        display: none;
    }

    /* A second, later defect (Leonardo's stacked screenshot on Clientes, stories.md
       2026-08-07/08): "three packed left, one far right" — not a misalignment this time, the
       block above already reads as flush right. The toolbar's own min-width (--ns-action-slots
       worth, ns-mud.css above) is a SIZE, and justify-content only ever chose which side of
       that size the visible icon sits on. Zeroing the reserve is what actually removes the
       gap: a desktop COLUMN'S rows need it to stay the same width regardless of how many
       actions each row contributed, and a stacked card has no column to serve, so nothing
       downstream needs that width held once the row has already become its own box. Central
       here means every grid using NsActionToolbar inherits the fix, not just Clientes'. */
    .ns-table.mud-sm-table .ns-action-toolbar {
        min-width: 0;
    }

    /* The stacked label RECEDES so the value leads (Leonardo, 2026-08-12, Clientes on a
       phone: "'Nombre para mostrar' atrae toda mi atención, casi ni miro el nombre de la
       persona" — ruling: "NO quites la etiqueta, mejorale el color con opacity para que el
       valor tenga más importancia"). In a stacked card every cell is drawn label-then-value,
       and the label is the column header echoed through data-label on the vendor's ::before.
       The vendor paints that pseudo at font-weight:500 (.mud-sm-table .mud-table-cell:before,
       0-2-1) while the value inherits the cell's own 400 — the header outweighs the datum,
       the inversion Leonardo saw. Muting the LABEL (secondary text, the house's receding-text
       token used by every hint/helper) and dropping it to the value's weight lets the datum
       lead without removing the label a card still needs to name its lines. Scoped to
       .ns-table.mud-sm-table (0-3-1) so it beats the vendor, and to the pseudo that only
       exists once stacked — column-mode headers are their own <th> and never match. */
    .ns-table.mud-sm-table .mud-table-cell::before {
        color: var(--mud-palette-text-secondary);
        font-weight: 400;
    }

    /* Column importance survives the stack (stories.md, "el modo stackeado respeta los
       Breakpoint de columna" — Leonardo 2026-08-08, on WorkOrdersPage: "ocultar saldo
       paciente y a cobrar obra social... así en mobile no se hace tan largo"). NsTh/NsTd
       already compute a column's importance into d-c-none d-c-{bp}-table-cell
       (NsResponsive.CellClasses) — sized for a table COLUMN's display:table-cell, and
       overruled here by the vendor's own stacked rule (.mud-sm-table .mud-table-cell {
       display: flex }, specificity 0-2-0 — measured, cases) which paints every cell back as
       a card line no matter what it declared.

       GENERATED SHAPE -- one pair of rules per d-c-* breakpoint above (sm/md/lg/xl, the same
       four the utilities already use — never hand-edit one, rewrite the block): a hidden
       column's cell is OFF by default once stacked, back ON — as a flex row, the card's own
       display, never table-cell — only inside the SAME container threshold the column-mode
       query already keys on. Both rules land at the same specificity (four classes each), so
       the tie is broken by source order: the re-enabling rule comes second and wins exactly
       when its own @container condition is met, which is also why the base rule needs no
       breakpoint suffix of its own — it is every d-c-none cell's floor.

       A column with no declared Breakpoint carries neither class (CellClasses returns null),
       so the action cell and every undeclared field never match .d-c-none and stay untouched. */
    .ns-table.mud-sm-table .mud-table-cell.d-c-none {
        display: none;
    }

    @container (min-width: 600px) {
        .ns-table.mud-sm-table .mud-table-cell.d-c-sm-table-cell {
            display: flex;
        }
    }

    @container (min-width: 960px) {
        .ns-table.mud-sm-table .mud-table-cell.d-c-md-table-cell {
            display: flex;
        }
    }

    @container (min-width: 1280px) {
        .ns-table.mud-sm-table .mud-table-cell.d-c-lg-table-cell {
            display: flex;
        }
    }

    @container (min-width: 1920px) {
        .ns-table.mud-sm-table .mud-table-cell.d-c-xl-table-cell {
            display: flex;
        }
    }
}

/* The pager row at phone width (nsail#225 — Leonardo, on the Personas grid: the nav arrows sit
   clipped at the right edge). Every box in MudTablePager's toolbar declares flex-shrink: 0 —
   the "Rows per page" caption, the size select (min-width 52px plus 10px of margin each side),
   the row counter, the actions strip — and the toolbar itself is nowrap above the vendor's own
   416px query. The row's width is therefore a constant, measured at 392px with the Spanish
   caption, while the box it sits in is the viewport LESS the page's own lateral padding: a
   428px phone hands it about 380. And .mud-table-pagination carries overflow: auto, so the
   difference becomes a horizontal scroll nobody looks for rather than a wrap — the arrows end
   up past the visible edge. (Under 416px the vendor wraps instead, and holds 100px of footer
   for a row that is two lines there.)

   The caption is what goes, and only the one naming the page size: it sits inside
   .mud-table-pagination-display (the select's own box), while the row counter is its own
   sibling caption and survives. A select showing 10 beside the arrows reads as the page size
   without the word, and there is no label association to lose — the vendor renders that
   caption as a plain div, never a <label> bound to the combobox, so the control is exactly as
   named to a screen reader after this as before.

   Wrapping is the floor under it rather than the fix: a long counter ("1-100 de 12345") in a
   narrow enough box still outgrows the row, and the toolbar's fixed 52px height would clip the
   second line, so the height becomes a minimum here. That also undoes the 100px reserve the
   vendor's 416px query holds for a wrap that no longer has anything to do.

   599.98px is MudBlazor's own sm and the line this file already draws for a phone: the width
   where the row stops fitting, not where the table stacks (960px) — a tablet's stacked cards
   have room for the label and keep it. */
@media (max-width: 599.98px) {
    .ns-table .mud-table-pagination .mud-table-pagination-display .mud-table-pagination-caption {
        display: none;
    }

    .ns-table .mud-table-pagination .mud-table-pagination-toolbar {
        flex-wrap: wrap;
        height: auto;
        min-height: 52px;
        padding-top: 0;
    }
}

/* NsDrawer: width follows Surface.Size (animated for runtime resizes), and never covers the
   nav gutter. An aside is WIDE, never full-bleed (Leonardo, with Nueva OT open on a desktop:
   "sigue full screen y me tapa el menu"): the menu is how you leave the document you are
   looking at, so an overlay that paints over it has taken the app's only exit away.

   The gutter is not restated here — it is read from --mud-drawer-width-left, the custom
   property MudDrawerContainer writes on the layout root from the nav drawer's own declared
   Width. One number, declared once, on the drawer that owns it (NsMainLayout). It is present
   whether the nav is open or closed (measured: the layout carries it alongside
   mud-drawer-close-responsive-md-left), so a nav the user collapsed leaves its strip showing
   the page rather than the aside — the honest cost of a cap that cannot see a toggle, and the
   direction that errs toward the menu staying reachable. The 0px fallback is for a host with
   no nav drawer at all (the wizard's own layout), where there is no gutter to leave.

   Below the nav's docking breakpoint (MudBlazor's md, which is what NsMainLayout hands its
   Responsive drawer) the nav is a sheet and not a gutter, so there is nothing to leave room
   for: the aside takes the viewport, which is the mobile degradation the ruling allows. */
.ns-drawer {
    max-width: calc(100vw - var(--mud-drawer-width-left, 0px));
    transition: width 200ms ease;
}

@media (max-width: 959.98px) {
    .ns-drawer {
        max-width: 100vw;
    }
}

/* The aside occupies the region UNDER the app bar, whatever variant the breakpoint dressed it
   in. MudBlazor says the same through DrawerClipMode.Always, but only for the variant/Fixed
   combinations its own selectors list — anything falling outside them keeps the plain
   .mud-drawer-temporary geometry (top: 0, height: 100%) and covers the app's constant chrome.
   Restating it here makes the placement a property of the surface rather than of the variant
   it happens to be wearing. Deliberately one class short of MudBlazor's own clipped-* rules,
   which carry four: the shorter bar those declare below 600px and in landscape still wins. */
.ns-drawer.mud-drawer.mud-drawer-clipped-always {
    top: var(--mud-appbar-height);
    height: calc(100% - var(--mud-appbar-height));
}

/* 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 — the menu inside it owns its own overflow, the same doctrine everywhere else: only
   what is marked scrolls, and the region marked is the one that actually holds too much.

   That owner is now NsNavMenu's own panel content, one box further IN than this: the drawer
   holds a search field that has to stay reachable while the entries move under it, so a
   scroller out here would carry the field off the top of the screen with them (Leonardo,
   2026-08-10, screenshot of "Escribí para buscar" half cut). What this region owes the panel
   is only the definite height it grows against — MudBlazor already gives it height:100% and a
   flex column; min-height:0 is the link that lets a flex child shrink below its content, and
   without it the chain breaks here and the panel never scrolls internally at all. */
.ns-nav-drawer .mud-drawer-content {
    min-height: 0;
}

/* 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).

   The dialog is selected by .mud-dialog alone: MudBlazor's dialog is not a paper — it
   carries neither the class nor .mud-paper's rule, painting Surface from its own
   (.mud-dialog { background-color: var(--mud-palette-surface) }), so a compound selector
   naming both matches nothing at all. Pinned in NsPanelHostSurfaceTests. */
.mud-popover.mud-paper,
.mud-dialog {
    background-color: var(--mud-palette-background-gray);
}

/* NsHelp's bubble. What it holds is EXPLANATION, not a helper line, so it is given a measure
   to be read at instead of the popover's own shrink-to-fit — which would run a 200-character
   sentence off the edge of a wide screen and fold it into a column on a narrow one. The cap is
   the smaller of a comfortable measure and the viewport, because the bubble is fixed-positioned
   and has nothing else to be bounded by. The type is the body2 the NsText block it replaced
   already rendered, read from the theme's own properties rather than restated. */
.ns-help-popover .ns-help-text {
    max-width: min(24rem, 80vw);
    padding: 12px 16px;
    font-size: var(--mud-typography-body2-size);
    line-height: var(--mud-typography-body2-lineheight);
}

/* A lookup's create entry is the dropdown's permanent last ITEM and has to measure like one.
   MudBlazor renders an option as .mud-list-item (8px above and below, 16px gutters) holding a
   body1 paragraph whose own container adds 4px each way — 48px, 1rem text — but it renders
   the after-items and no-items seams, which is where the create entry lives, as bare divs
   padded 4px on every side, with the anchor inside falling back to the body's own 0.875rem.
   The entry came out at roughly 28px of 14px text against 48px of 16px, indented 4px where
   every row above it is indented 16px: "se sigue viendo más chico" (Leonardo, on the Paciente
   lookup), and the ruling is that an actionable last entry is never smaller than the passive
   rows it follows.

   The seam's own padding goes and the entry carries the item's numbers instead — 12px is the
   item's 8px plus the 4px its text container contributes, so the two boxes come out the same
   height without this restating a number the vendor could change under it. The type is read
   from the same custom properties .mud-typography-body1 spends, never a literal, so the entry
   follows the theme's body1 wherever it goes. Reached through ListClass because the popover
   is a portal: NsAutocomplete's own subtree does not contain the list it opens. */
.ns-lookup-list .mud-autocomplete-after-items:not(:empty),
.ns-lookup-list .mud-autocomplete-no-items:not(:empty) {
    padding: 0;
}

.ns-lookup-create {
    padding: 12px 16px;
    font-size: var(--mud-typography-body1-size);
    line-height: var(--mud-typography-body1-lineheight);
}

/* 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;
    }
}

/* 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;
}

/* SignInPage's contributed provider buttons (AppleSignInButton, GoogleSignInButton, and
   whatever ISignInContributor a future kit adds): each renders its own label at its own
   length, which is why they arrived at uneven widths. The stack's own Align (Stretch, its
   default) already fills every button to the container's width; this pins the vendor's
   Large-filled button geometry (mud-button-filled-size-large's own numbers) on top of it, so
   a third provider matches in height too without having to opt into NsLink Size="Lg" itself
   — the container decides both, nobody hand-tunes a button to its neighbour. */
.ns-sign-in-providers .mud-button-root {
    padding: 8px 22px;
    font-size: 0.9375rem;
}

/* 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);
}

/* THE GAP IS THE RESERVATION (nsail#214). A REFUSAL NEVER MOVES THE PAGE (intentional-ui,
   refusal placement) — but the field no longer buys that promise with a line of its own.
   It used to: every control box carried a padding-bottom sized for one message, spent
   whether or not anything was ever wrong, and the form grid's row gap was added on top of
   it. Two reservations for one message, on every row, is what made a form read as twice the
   air its content needs (Leonardo, New Product, 2026-08-14).

   The row gap is the reservation now, and it is one the layout was already paying: the field
   reserves nothing, the gap between rows stays exactly what NsFormGrid set, and the message
   is laid absolutely into that gap, anchored to the bottom of the field it names. Appearing
   and clearing both cost zero height, so nothing under the cursor moves — the same promise,
   at no whitespace.

   THE ANCHOR IS THE CONTROL BOX and it has to be: it is the one positioned ancestor that
   exists in every host a field can stand in — a grid cell, a dialog, an aside, a list-editor
   row, a table cell. Anchoring on the grid item instead would put the message at the bottom
   of whatever box happened to be positioned in a host that renders no grid item at all.

   MudBlazor renders .mud-input-control-helper-container only when there IS text to show
   (measured, not assumed — NsReservedGeometryTests pins both DOMs), which is exactly why
   nothing can be reserved by styling that element and why the absolute placement is what
   keeps the arriving node free. Scoped to .ns-form because that is where refusals land — a
   field standing outside a form has no submit that can be refused.

   ONE LINE, CLAMPED, AND THAT IS THE WHOLE MESSAGE ON SCREEN: the gap fits one line and the
   text is trimmed with an ellipsis rather than wrapping into the row below it. The vendor
   lays the helper text out as a flex row with the text in a child of its own, so the clamp
   goes on that child (min-width:0, or a flex item refuses to shrink below its content and
   the ellipsis never appears). --ns-field-error-line IS the grid's own row gap (gap-4, 1rem)
   written once: the message's line box and the space it is laid into are the same constant,
   so they cannot drift apart. */
.ns-form {
    --ns-field-error-line: 1rem;
}

.ns-form .mud-input-control {
    position: relative;
}

.ns-form .mud-input-control-helper-container {
    position: absolute;
    inset-inline: 0;
    top: 100%;
    height: var(--ns-field-error-line);
    margin-top: 0;
}

.ns-form .mud-input-control-helper-container .mud-input-helper-text {
    line-height: var(--ns-field-error-line);
}

.ns-form .mud-input-control-helper-container .mud-input-helper-text > * {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* The last row's message needs somewhere to land too, and the gap after the last row is the
   one gap the grid does not draw. The grid buys it here — once for the whole grid, not once
   per field, which is the difference between this and what was removed above. It is not
   optional dressing: a panel's content scrolls (overflow-y-auto), so a message hanging past
   the grid's own bottom edge would be clipped by that scroller, or grow it a scrollbar the
   form did not have. A form built from several grids reserves it per grid, which is right —
   each grid's last row has a section heading, not a gap, underneath it. */
.ns-form .ns-grid {
    padding-bottom: var(--ns-field-error-line);
}

/* The hint's house block (NsFieldHelper): rendered as a plain sibling AFTER the field, in
   NORMAL FLOW — real height, honest wrap, nothing absolutely positioned to overflow. A hint
   is content and pays for itself; only the error rides the gap. */
.ns-field-helper {
    display: block;
    margin-top: 3px;
}

/* Yielded, not gone. A field carrying both a hint and a refusal shows the refusal, and the
   refusal is drawn over the very pixels the hint occupies — so the hint must keep its box or
   the field would shrink by a line exactly when the message lands. visibility, not display:
   the height stays, the words leave, and a screen reader is not read two answers at once. */
.ns-field-helper-yielded {
    visibility: hidden;
}

/* 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 only exists
   while there is something to say: the panel footer that claims it seats it in the row the
   buttons already occupy (-inline below), so nothing is reserved when the form is clean and
   nothing moves when the refusal arrives — the buttons are right-anchored and the message
   takes the leftover width beside them. A block either way, so several issues stack as
   separate lines rather than running together the way the toast it replaced 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: row;
    align-items: center;
    gap: 12px;
    margin-top: 8px;
    padding: 10px 14px;
    border: 1px solid transparent;
    border-radius: var(--mud-default-borderradius);
    font-size: 0.875rem;
}

/* The messages keep the column they always had; the row above exists only so the act that
   answers a conflict can sit BESIDE them. A second line inside the footer's strip would grow
   that row and move the very buttons the strip was seated next to in order not to move. */
.ns-form-problem-text {
    display: flex;
    flex: 1 1 auto;
    min-width: 0;
    flex-direction: column;
    gap: 4px;
}

/* A text act, not a button: it lives inside a refusal already painted in the error colour,
   and a filled button there would compete with the submit standing beside it. */
.ns-form-problem-act {
    flex: 0 0 auto;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    text-decoration: underline;
    cursor: pointer;
}

/* Seated in the footer row: flexible beside the right-anchored buttons, tighter padding
   because the row's own height already frames it. min-width 0 lets long text wrap instead
   of pushing the buttons off the panel. */
.ns-form-problem-inline {
    flex: 1 1 auto;
    min-width: 0;
    margin-top: 0;
    padding: 6px 12px;
}

.ns-form-problem-text > span {
    line-height: 1.25rem;
}

.ns-form-problem-shown {
    border-color: var(--mud-palette-error);
    background-color: var(--mud-palette-error-hover);
    color: var(--mud-palette-error);
}

/* 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);
}

/* The semaphore's quiet third channel (state-dim): a disabled record's row WHISPERS rather
   than shouts — low opacity, the inverse of the wash above. Applied from the row's own
   IsEnabled, never a mix with the severity classes (a disabled row has nothing left to warn
   about). Opacity reads correctly against both themes without a color of its own. */
.ns-row-muted {
    opacity: 0.5;
}

/* The attention semaphore's other channel: Estado is quiet TEXT whose COLOR names the
   STATE's own tone, replacing a chip that decorated every status the same and so made none
   of them stand out. Opt-in via a custom property, never direct inheritance — a color rule
   here would have to out-specificity Mud's own text color, a fight custom properties skip
   by cascading instead of competing. ns-status-text is the only consumer; a status class
   sets the property and nothing else. Theme severity colors only. */
.ns-status-text {
    color: var(--ns-status-color, inherit);
}

.ns-status-muted {
    --ns-status-color: var(--mud-palette-text-disabled);
}

.ns-status-success {
    --ns-status-color: var(--mud-palette-success);
}

/* 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;
}

/* 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;
    }
}

/* The door NsDashboard overlays on a card that declared a PageType: MudCardHeader pads its
   content 16px (.mud-card-header-padding) and MudBlazor's own header-actions slot offsets an
   icon button -8px against that padding so the GLYPH lands on the padding line while the
   button's full touch target still reaches the corner (.mud-card-header-actions, vendor CSS).
   The door is not inside the header slot -- the chrome renders it, the card stays unwired --
   so the same offset is restated here rather than inherited.

   The item itself is flex-column: CSS Grid already stretches it to the row's tallest card
   (the block axis is what align-items: stretch governs by default), but a plain block card
   inside only ever takes its own content height and leaves the difference as air below --
   flex-column turns that spare space into something the rule below can hand out. */
.ns-dashboard-item {
    display: flex;
    flex-direction: column;
    position: relative;
}

/* A Tall card spans two rows, and its content fills them: the agenda beside two stat cards
   stands exactly their combined height. Below sm the grid is one column and a span means
   nothing, so it resets rather than doubling a phone's scroll. */
.ns-dashboard-tall {
    grid-row: span 2;
}

.ns-dashboard-tall > .ns-dashboard-card > * {
    height: 100%;
}

@media (max-width: 599.98px) {
    .ns-dashboard-tall {
        grid-row: auto;
    }
}

/* The slot (.ns-dashboard-card) is display: contents -- it generates no box, so per the
   box-generation spec its children act as flex items of ITS OWN parent, the item above.
   The card the slot holds is therefore the real flex item, and reaching through the slot
   rather than styling it is what the d-contents choice demands. A muted card renders no
   child at all, so this matches nothing for it and the collapse rule further down still
   takes the whole cell out untouched. Tall's own height:100% above already lands the same
   answer for a two-row card (its item's height is already definite there); flex-grow is
   what the ordinary one-row card was missing. */
.ns-dashboard-item > .ns-dashboard-card > * {
    flex: 1 1 auto;
}

.ns-dashboard-item .ns-dashboard-door {
    position: absolute;
    top: 8px;
    right: 8px;
}

/* A card that answered with nothing takes its cell with it. Cards render content only once
   their read has answered, and some never do -- an onboarding card with nothing left to ask
   is absent for good -- so the wrapper the door is positioned against would otherwise hold an
   empty cell open and the grid would flow around a hole instead of compacting. The question
   is asked of the card's own slot rather than of the wrapper, which always has the door in
   it, and of an ELEMENT inside that slot rather than of :empty, which whitespace alone would
   defeat. The door goes with the cell because it renders inside the element this rule takes
   out. */
.ns-dashboard-item:not(:has(> .ns-dashboard-card > *)) {
    display: none;
}

/* 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;
}

/* The scrollport the grid owns: the track inside it is always the whole day, and the framed
   window (--ns-time-window, written by the component) is how much of it stands open. The
   head row's 2rem is inside the box because the heads scroll with it and stick at its top —
   that way the day names sit in the same layout box as the columns they name and cannot drift
   from them by a scrollbar's width, which is exactly what a head row outside the scroller
   does. The sheet is painted for the sticky heads to hide behind; a transparent header would
   let the hour lines run through the day names. */
.ns-time-grid-scroll {
    max-height: calc(var(--ns-time-hour) * var(--ns-time-window, 24) + 2rem);
    overflow-y: auto;
    /* Explicit: overflow-y alone computes overflow-x to auto, which would offer a second
       scrollbar for the sub-pixel the columns' borders round to. */
    overflow-x: hidden;
    background-color: var(--mud-palette-surface);
    border-radius: var(--mud-default-borderradius);
}

/* Above the now line (2) and the blocks (1): what is pinned wins over what scrolls under it. */
.ns-time-grid-scroll .ns-time-grid-head {
    position: sticky;
    top: 0;
    z-index: 3;
    background-color: var(--mud-palette-surface);
}

/* Compact hours for a grid embedded in a card: the surface around it is the constraint,
   and ~20% less per hour keeps a working day readable inside one. */
.ns-time-grid.ns-time-dense {
    --ns-time-hour: 2.75rem;
}

/* The clock's own mark: a line across the column at Now, above the hour rows and beneath
   nothing — a block at the same minute still reads. */
.ns-time-grid-now {
    position: absolute;
    left: 0;
    right: 0;
    border-top: 2px solid var(--mud-palette-error);
    z-index: 2;
    pointer-events: none;
}

.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);
}

/* The column's width, divided by whatever collides inside it: --ns-time-lanes is how many
   blocks share these hours and --ns-time-lane is which of them this is (NsTimeLanes assigns
   both). Neither is written when a block collides with nothing, and the fallbacks are what
   make that the whole column — the 2px gutters at each edge, and one more between lanes,
   charged against the share rather than added to it so the lanes always add up to the column. */
.ns-time-block {
    position: absolute;
    inset-inline-start: calc(2px + (100% - 4px) * var(--ns-time-lane, 0) / var(--ns-time-lanes, 1));
    inline-size: calc((100% - 4px) / var(--ns-time-lanes, 1) - 2px * (var(--ns-time-lanes, 1) - 1) / var(--ns-time-lanes, 1));
    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;
}

/* Time that is not this agenda's own: dotted all round and dimmed, so it can never be read as
   a booking. Nothing here touches width. The first cut of the overlay pinned these blocks to
   the trailing half of the column so an appointment could not hide them; the lanes above answer
   that same question without charging for it when there is nothing to be hidden by (Emmanuel,
   2026-08-12: "al estar solo, debería ocupar toda la columna"). It is clickable, because the
   click opens the read-only detail dialog and pointer-events:none would leave that dialog
   reachable by keyboard alone. */
.ns-time-block-readonly {
    border-style: dotted;
    opacity: 0.8;
}

/* 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);
}

/* The note NsTimeGrid always renders once there is more than one day to collapse: silent
   until the same query below actually hides a column, so the two can never disagree about
   whether something is missing. */
.ns-time-grid-note {
    display: none;
    padding-block-end: 0.5rem;
}

/* 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. The note flips on with the
   exact same query — the collapse and its own disclosure share one source of truth. */
@container (max-width: 599.98px) {
    .ns-time-collapse-sm .ns-time-grid-aside {
        display: none;
    }

    .ns-time-collapse-sm .ns-time-grid-note {
        display: block;
    }
}

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

    .ns-time-collapse-md .ns-time-grid-note {
        display: block;
    }
}

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

    .ns-time-collapse-lg .ns-time-grid-note {
        display: block;
    }
}

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

    .ns-time-collapse-xl .ns-time-grid-note {
        display: block;
    }
}

/* 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;
    }
}

/* THE WHISPER (Emmanuel, 2026-08-11): a button that gave up its word says it on hover.
   A collapsed control is a glyph with no text anywhere on screen, and the doctrine's own
   honest cost ("a collapsed button has no hover tooltip") is what this pays off — for the
   pointer, at least; a touch device still has no hover and still relies on the glyph.

   The text is attr(aria-label), which is the SAME string the hidden span carries
   (NsButton/NsLink set it from Label on every chromed As) — nothing is written twice, so
   the bubble cannot come to disagree with the label it replaces. And the gate is the SAME
   container query that hid that label: the bubble is display:none by default and only the
   queries below turn it on, so a control whose label is visible can never wear both. That
   is why this is CSS and not a MudTooltip on the chromed branch: a vendor tooltip portals
   out of the container and could not be gated on the container's width at all, so it would
   double-bill the visible label at every width above the breakpoint.

   The look is the vendor tooltip's own, read from its variables rather than invented, so
   the whisper and the As="Icon" MudTooltip beside it are one tooltip to the eye. */
.ns-collapse-sm[aria-label]:not([aria-label=""])::after,
.ns-collapse-md[aria-label]:not([aria-label=""])::after,
.ns-collapse-lg[aria-label]:not([aria-label=""])::after,
.ns-collapse-xl[aria-label]:not([aria-label=""])::after,
.ns-panel-footer .mud-button-filled:has(.mud-button-icon-start):not(.mud-button-filled-primary):not(.mud-button-filled-error)[aria-label]:not([aria-label=""])::after {
    content: attr(aria-label);
    display: none;
    position: absolute;
    bottom: calc(100% + 4px);
    left: 50%;
    transform: translateX(-50%);
    padding: 4px 8px;
    border-radius: var(--mud-default-borderradius);
    background-color: var(--mud-palette-gray-darker);
    color: var(--mud-palette-dark-text);
    font-size: 12px;
    font-weight: 500;
    line-height: 1.4em;
    letter-spacing: normal;
    text-transform: none;
    white-space: nowrap;
    pointer-events: none;
    z-index: var(--mud-zindex-tooltip);
}

/* Two things the collapsed state has to give the bubble, and one it has to give up.
   MudBlazor's own button root carries .mud-ripple, whose overflow:hidden would clip
   anything drawn outside the box — so the box stops clipping while it is collapsed, and
   the ripple EFFECT (the expanding circle that overflow:hidden was containing) is the
   thing that goes instead: it would otherwise paint a disc outside the button on every
   click. The hover/active background MudBlazor paints is untouched, so the press still
   answers. */
@container (max-width: 599.98px) {
    .ns-collapse-sm {
        overflow: visible;
    }

    .ns-collapse-sm .mud-ripple-effect {
        display: none;
    }

    .ns-collapse-sm:hover::after,
    .ns-collapse-sm:focus-visible::after {
        display: block;
    }
}

@container (max-width: 959.98px) {
    .ns-collapse-md {
        overflow: visible;
    }

    .ns-collapse-md .mud-ripple-effect {
        display: none;
    }

    .ns-collapse-md:hover::after,
    .ns-collapse-md:focus-visible::after {
        display: block;
    }
}

@container (max-width: 1279.98px) {
    .ns-collapse-lg {
        overflow: visible;
    }

    .ns-collapse-lg .mud-ripple-effect {
        display: none;
    }

    .ns-collapse-lg:hover::after,
    .ns-collapse-lg:focus-visible::after {
        display: block;
    }
}

@container (max-width: 1919.98px) {
    .ns-collapse-xl {
        overflow: visible;
    }

    .ns-collapse-xl .mud-ripple-effect {
        display: none;
    }

    .ns-collapse-xl:hover::after,
    .ns-collapse-xl:focus-visible::after {
        display: block;
    }
}

/* Emphasis-ladder footers (NsPanel's own Footer wrapper above, "d-flex justify-end gap-2
   ns-panel-footer" — shared by every document page in the house: WorkOrderPage, the
   transition pages' Cobrar/Facturar/Entregar, PurchaseOrderPage, the Accounting document
   pages). Leonardo, 2026-08-06 with a screenshot of the OT page cramped at phone width:
   "los botones de abajo deberían hacerse íconos en este tamaño" — SECONDARY footer buttons
   collapse to icon-only under the phone breakpoint; the HERO keeps its pinned text (his own
   doctrine, "Important acts sit at the bottom with text").

   Scoped once, centrally, rather than per page: :has(.mud-button-icon-start) is what makes
   this safe against a secondary button with no icon (PurchaseOrderPage's Cancel/Issue/
   Receive today) — nothing to collapse TO, so it is left alone rather than going blank —
   and the two :not() clauses are what spare the hero, regardless of whatever Breakpoint
   value the page's own button happens to carry (several pin NsSize.None, which this rule
   simply outranks inside the footer). ns-button-label is NsResponsive's own stable hook
   (NsButton/NsLink), present whether or not the caller's Breakpoint would have collapsed
   it on its own.

   THE HERO COMES IN TWO COLOURS, which is why there are two exclusions and not one
   (Leonardo, 2026-08-10: "botón cerrar sesión sigue sin texto"). Primary is the hero of a
   document footer (Save, Entregar); ERROR is the hero of a footer whose one act is
   destructive — SessionMenuPage's full-width Cerrar Sesión, and every destructive confirm
   that will wear the same face. That footer lives in a dialog measuring ~430px, so it is
   under this breakpoint on every screen there is, and the sign-out came out a bare icon.
   The rule spares by EMPHASIS, and emphasis is what the filled colour encodes: a footer
   button in neither colour is a secondary act by construction, which is exactly what
   collapses. NsButton's own As→Color mapping is where these class names come from (Primary
   → Color.Primary, Danger → Color.Error).

   The accessible name survives the same as it always has for an icon-only control:
   NsButton/NsLink now set aria-label from the same Label string on the chromed branch too,
   not only As.Icon, because display:none content drops out of the accessible-name
   computation — the RowEditor-label precedent (a hidden Label is not a removed one). */
@container (max-width: 599.98px) {
    .ns-panel-footer .mud-button-filled:has(.mud-button-icon-start):not(.mud-button-filled-primary):not(.mud-button-filled-error) .ns-button-label {
        display: none;
    }

    .ns-panel-footer .mud-button-filled:has(.mud-button-icon-start):not(.mud-button-filled-primary):not(.mud-button-filled-error) .mud-button-icon-start {
        margin-inline: 0;
    }

    /* The whisper's own gate for this half of the ladder (the block above the collapse
       utilities): a footer button the page pinned with Breakpoint="None" carries no
       ns-collapse-* hook at all — this rule is the only thing that took its word, so it is
       the only thing that can give it back on hover. Same three moves as there. */
    .ns-panel-footer .mud-button-filled:has(.mud-button-icon-start):not(.mud-button-filled-primary):not(.mud-button-filled-error) {
        overflow: visible;
    }

    .ns-panel-footer .mud-button-filled:has(.mud-button-icon-start):not(.mud-button-filled-primary):not(.mud-button-filled-error) .mud-ripple-effect {
        display: none;
    }

    .ns-panel-footer .mud-button-filled:has(.mud-button-icon-start):not(.mud-button-filled-primary):not(.mud-button-filled-error):hover::after,
    .ns-panel-footer .mud-button-filled:has(.mud-button-icon-start):not(.mud-button-filled-primary):not(.mud-button-filled-error):focus-visible::after {
        display: block;
    }

    /* A narrow footer has no leftover width beside its buttons: the seated refusal takes
       its own line ABOVE them (order -1 keeps the buttons where the thumb expects them).
       Container-measured like everything else here, so an aside on a wide desktop wraps
       the same way a phone does. */
    .ns-panel-footer {
        flex-wrap: wrap;
    }

    .ns-panel-footer .ns-form-problem-inline {
        flex-basis: 100%;
        order: -1;
    }
}

/* 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. Before
   the loader reports its first sample the variable is unset, so the fallback reads an
   ellipsis, never a fabricated "0%" -- a number with no sample behind it is not information. */
.ns-splash-percent::after {
    content: var(--blazor-load-percentage-text, '"…"');
}

/* The track pulses from the very first paint, so the bar reads as alive during the window
   before the loader's first sample lands; the fill's own width (--blazor-load-percentage)
   takes over the instant real data arrives, the pulse keeps running underneath it. */
@keyframes ns-splash-pulse {
    0%, 100% { opacity: .55; }
    50% { opacity: 1; }
}

.ns-splash-track {
    animation: ns-splash-pulse 1.4s ease-in-out infinite;
}

/* NsWizard's progress rail, mounted in the layout's menu slot so it collapses below the
   drawer's breakpoint exactly like the nav menu does. Compact on purpose: one line per step,
   nothing that wraps and nothing that scrolls — a sequence long enough to need a scrollbar is
   too long to read as progress at all. The mark's box is reserved whether or not the check is
   in it, so the titles line up down the rail and none of them moves when a step completes. */
.ns-wizard-rail {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 12px 8px;
}

.ns-wizard-rail-step {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    border-radius: var(--mud-default-borderradius);
    color: var(--mud-palette-text-secondary);
    font-size: 0.875rem;
}

.ns-wizard-rail-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 1.25rem;
    width: 1.25rem;
}

.ns-wizard-rail-title {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.ns-wizard-rail-step-complete {
    color: var(--mud-palette-text-primary);
}

.ns-wizard-rail-step-current {
    background-color: var(--mud-palette-primary-hover);
    color: var(--mud-palette-primary);
}

/* NsPanel's own Footer wrapper is "d-flex justify-end gap-2" -- shared by every dialog and
   form footer in the house, which bunches its buttons together at the right on purpose (a
   Cancel/Save pair reads as one decision). The wizard's Back/Next read as two ends of a
   sequence instead, so only this footer pushes Back to the opposite edge -- margin-inline-end:
   auto inside a justify-end row is the standard trick, and it changes nothing else NsPanel
   hosts. */
.ns-wizard-back {
    margin-inline-end: auto;
}

/* A long document inside a step: the step's own height stays what the sequence agreed on and
   the reading scrolls inside its box, so the Next the user is walking towards never moves down
   the page as the text grows. */
.ns-contract {
    max-height: 22rem;
    overflow-y: auto;
}

.ns-contract-text {
    margin: 0;
    padding: 16px;
    white-space: pre-wrap;
    word-break: break-word;
    font-family: inherit;
    font-size: 0.875rem;
    line-height: 1.5;
    color: var(--mud-palette-text-primary);
}

/* The upload field's two surfaces. The field line is an ordinary Mud field line and carries
   exactly one action icon like any other; the previews are the second surface the Preview
   parameter creates, and that is the only place a clear may sit on top of what it clears.
   A card is square and crops what it shows, so a gallery of mixed aspect ratios still reads
   as a row of thumbnails and the drag target never changes size while it is being dragged. */
.ns-upload-cards {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.ns-upload-zone {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    min-height: 5.5rem;
    width: 100%;
}

.ns-upload-card {
    position: relative;
    width: 5rem;
    height: 5rem;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: var(--mud-default-borderradius);
    overflow: hidden;
    background-color: var(--mud-palette-background-gray);
}

.ns-upload-card img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* Circled so it reads on top of any image underneath it -- a bare glyph disappears into a
   light photograph, which is the one place a clear must never be hard to find.

   It stays on the SMALL step under a coarse pointer, unlike every other icon action: the
   growth above is written for the two scopes it names, and this one is deliberately not
   among them -- a 44px circle would cover most of the 5rem thumbnail it exists to clear. */
.ns-upload-card .ns-upload-clear {
    position: absolute;
    top: 2px;
    right: 2px;
    background-color: var(--mud-palette-surface);
    border-radius: 50%;
    padding: 2px;
}

/* The empty state: the shape of what will land there, so the box does not change size the
   moment the first file arrives. */
.ns-upload-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 5rem;
    height: 5rem;
    border: 1px dashed var(--mud-palette-lines-default);
    border-radius: var(--mud-default-borderradius);
    text-align: center;
    padding: 4px;
}

/* The placeholder glyph is an ILLUSTRATION, not an icon on the scale: it stands alone inside
   a 5rem frame with no text beside it to be measured against, so it takes half the frame
   instead of the 20px an icon means everywhere else. Muted, because it says "a picture goes
   here" and must not compete with the thumbnail that replaces it. */
.ns-upload-empty .mud-icon-root {
    font-size: 2.5rem;
    color: var(--mud-palette-text-disabled);
}

.ns-upload-line {
    min-height: 2rem;
}

/* PartyContactCard's channel row: a chromed NsLink (As="Action") stretched to the full
   width its vertical NsStack hands it (AlignItems.Stretch is the Stack's own default), so a
   long e-mail truncates against that width instead of pushing the card wider. The label
   span is what carries the text -- ns-button-label, NsResponsive's own hook -- so it is the
   span that clips, not the button; min-width:0 overrides the flex default that would
   otherwise refuse to shrink the label below its own content (the ns-wizard-rail-title
   trick, restated because a flex ROW's shrink axis is what needs it here). The full value
   still reaches an assistive reader from the button's own aria-label (NsLink sets it from
   the same Label on every chromed As, not only Icon). */
.ns-contact-channel .ns-button-label {
    display: inline-block;
    min-width: 0;
    max-width: 100%;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    vertical-align: bottom;
}

/* NsFormGrid: a form's fields laid out by their own WIDTH NATURE, not by a column fraction. The
   row is a flex-wrap band: a field takes the width its content deserves and never inflates to
   fill a lonely line, the growing ones share whatever is left, and the whole band wraps HONESTLY
   when the panel around it narrows -- measured by the CONTAINER (whatever declared ns-container:
   NsPanel, NsCard), never by the window, so a form in an aside on a wide desktop reads as the
   narrow box it actually is and wraps there. No @container span table survives: wrap is what a
   flex row does when the line runs out, and the widths below are all it needs to know.

   The width lives on the ITEM (a plain div this component owns), and the vendor field inside fills
   it at its own 100% -- so a content-width field renders at the item's width, never stretched to
   the row, and no vendor field CSS has to be fought. --ns-field-width is the one knob a field that
   wants a different natural width overrides; the default fits the known-width family (a date's
   dd/mm/aaaa, a DNI, a CUIT "20-12345678-9", an importe, a short select) with its floating label. */
.ns-grid {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    width: 100%;
}

/* Content-width (the default): the field's own bounded width, no grow, allowed to shrink below it
   only when the container cannot even seat one -- max-width caps it at the line so it never spills. */
.ns-grid > .ns-form-field {
    flex: 0 1 auto;
    width: var(--ns-field-width, 14rem);
    max-width: 100%;
}

/* Grow: fills the surplus and shares it equally with other growing items (equal basis), wrapping
   once the line can no longer seat the mins side by side. */
.ns-grid > .ns-form-field-grow {
    flex: 1 1 var(--ns-field-grow-basis, 12rem);
    min-width: var(--ns-field-grow-basis, 12rem);
    max-width: 100%;
}

/* Full: a block that owns its line -- a line editor, a notes area. Basis 100% cannot share, so it
   always starts fresh and takes the whole width. */
.ns-grid > .ns-form-field-full {
    flex: 1 1 100%;
    min-width: 0;
    max-width: 100%;
}

/* NsRecordState (nsail#213): every other field in a row draws the same chrome (label and
   outline), so the row's own flex-start leaves them flush at the top with nothing to
   notice -- but the switch renders none of it, so it is shorter than a
   labeled outline neighbour (New Product's Type) and floats high instead of reading as its
   row partner. Centered on the CELL the switch's own class marks, not the grid's
   align-items: a row where every field is a switch, or an unrelated row entirely, keeps its
   flex-start untouched. Leonardo's "grow" hunch was the width parameter (NsFormGridItem.Grow)
   -- unrelated to this cross-axis mismatch. */
.ns-grid > *:has(> .ns-record-state) {
    align-self: center;
}

/* NsPanel's scroll shadow (intentional-ui, Refusal placement's neighbour — Leonardo,
   Nueva Receta dialog: "a veces no se nota que hay más cosas abajo"; ruled a SHADOW, not
   a chevron). The scrolling-shadows technique (Lea Verou): two SOLID "cover" bands pinned
   to the CONTENT (background-attachment: local, so they travel with the scroll) sit on top
   of two low-alpha "shadow" gradients pinned to the VIEWPORT (attachment: scroll, so they
   never move) at the same edges. At rest the cover exactly overlaps its shadow and hides
   it; the moment a scroll reveals content past that edge, the cover slides out from under
   it on its way past, and the shadow shows through where it used to sit — no JS, no class
   toggled, nothing to fall out of sync with the DOM.

   THE COVER IS SOLID ACROSS THE WHOLE BAND, and that is the whole of it. Lea Verou's
   original covers fade out (solid to 30%, transparent by 100%) while the shadow under them
   fades over the full band — so from the point the cover starts thinning down to where the
   shadow finally reaches zero, part of the shadow is showing THROUGH the cover that is
   supposed to be hiding it. It peaks near mid-band at roughly 2.5% of text-primary and
   never goes away, at any scroll position, with or without anything to scroll: Leonardo's
   "queda una especie de barra transparente" (Nueva Venta) and the band drawn over the
   Sesión dialog, which has nothing to scroll at all. The cover paints the exact solid the
   surface already paints, so its own ramp was never visible for its own sake — its only
   job is masking, and a mask has to be opaque everywhere the thing it masks is not.

   The cover has to be the SAME solid the surface actually paints, or the seam between
   cover and content would show — and that solid differs by host: the main surface sits on
   Background (body, MudBlazor's own rule), every drawer on DrawerBackground (.mud-drawer's
   own rule — the aside and the nav drawer alike), a routed modal's NsDialog on Surface (its
   MudPaper's default), an ephemeral MudDialog (NsOpenDialog, ADialogHostedNsPanel...Tests)
   on the raised surface this file already paints it with above. --ns-panel-surface binds to
   whichever of those each host already paints with, so the technique works unmodified
   wherever NsPanel mounts rather than assuming one background for all four.

   EACH SELECTOR BELOW IS THE ONE THAT CARRIES THE PAINT, never a house class riding along
   with it — the var and the background it has to equal then cannot drift apart. Both
   halves of that had already drifted: .ns-drawer named the aside alone, so the nav drawer's
   own panel (8c0f65ac) fell through to the root default and painted a Background band over
   a DrawerBackground drawer; .mud-dialog.mud-paper named a compound MudBlazor does not
   render, so an ephemeral dialog's panel fell through the same way and painted a Background
   band — read as black — over the dialog. Pinned per host in NsPanelHostSurfaceTests.

   The shadow itself has no dedicated palette slot, so it is tinted off text-primary's own
   -rgb companion custom property (MudBlazor's own idiom for laying alpha over a palette
   color, e.g. its border-opacity rules — not a color invented here) at a low alpha over a
   short band, so it reads as an edge and not a bar. */
:root {
    --ns-panel-surface: var(--mud-palette-background);
}

.mud-drawer {
    --ns-panel-surface: var(--mud-palette-drawer-background);
}

.ns-dialog {
    --ns-panel-surface: var(--mud-palette-surface);
}

.mud-dialog {
    --ns-panel-surface: var(--mud-palette-background-gray);
}

/* NsDialog.GetStyle() writes width/height as custom properties rather than the properties
   themselves, so the phone override below can win on ordinary cascade order instead of
   fighting an inline style with !important (nowhere else in this file does that). Both axes
   are a fixed size against the viewport, matched on width and height, so a short first tab
   never opens a box too small for a taller one to grow later. The frame does not scroll its
   own content (NsPanel's job, inside) — overflow: hidden is what makes that a clip instead of
   content painting over the backdrop when a page forgets to wrap itself in one. */
.ns-dialog {
    width: var(--ns-dialog-width);
    height: var(--ns-dialog-height);
    overflow: hidden;
}

/* Below MudBlazor's own sm breakpoint (the same 599.98px CollapsedLabelWhisperTests and the
   dashboard grid above already use) a dialog is full screen and the Size steps stop
   mattering, same as every other surface's own mobile degradation (surfaces.md, the aside
   below its docking breakpoint). Square corners: a rounded box that exactly fills the
   viewport reads as clipped, not as chrome. */
@media (max-width: 599.98px) {
    .ns-dialog {
        width: 100vw;
        height: 100vh;
        border-radius: 0;
    }
}

.ns-scroll-shadow {
    background:
        linear-gradient(var(--ns-panel-surface), var(--ns-panel-surface)) local,
        linear-gradient(var(--ns-panel-surface), var(--ns-panel-surface)) 0 100% local,
        linear-gradient(rgba(var(--mud-palette-text-primary-rgb), .16), transparent) scroll,
        linear-gradient(transparent, rgba(var(--mud-palette-text-primary-rgb), .16)) 0 100% scroll;
    background-repeat: no-repeat;
    background-color: var(--ns-panel-surface);
    background-size: 100% 20px, 100% 20px, 100% 20px, 100% 20px;
    background-attachment: local, local, scroll, scroll;

    /* Leonardo's Personas screenshot (stories.md, Clientes' grid row, AMPLIADA 2026-08-08):
       a stacked table row wide enough to overflow scrolled with TWO bars, not one. NsPanel's
       Content div carries the vendor utility overflow-y-auto and nothing for the other axis —
       the CSS Overflow spec computes an unset axis to auto (not visible) once its sibling
       axis is not visible, so Content became a SECOND horizontal-scroll owner one box out
       from .mud-table-container's own deliberate overflow-x: auto. Content only ever meant
       to scroll vertically (the shadow above says exactly that), so the other axis is pinned
       shut here rather than left for the spec to fill in — one place, every host shares it. */
    overflow-x: hidden;
}

/* The house's own scrollbar, for a scroll box INSIDE a surface rather than at its edge — the
   agenda's day sheet is the first. The shell's scrollbars stay the platform's: a user's
   pointer settings own the bar they drag all day. This one is thin and the theme's line color
   so it reads as part of the sheet, and it is declared twice on purpose — scrollbar-width /
   scrollbar-color is the standard property Firefox honors, ::-webkit-scrollbar is what
   Chromium and Safari still answer to; neither one covers both. */
.ns-scroll-thin {
    scrollbar-width: thin;
    scrollbar-color: var(--mud-palette-lines-default) transparent;
}

.ns-scroll-thin::-webkit-scrollbar {
    width: 0.5rem;
    height: 0.5rem;
}

.ns-scroll-thin::-webkit-scrollbar-track {
    background: transparent;
}

.ns-scroll-thin::-webkit-scrollbar-thumb {
    background-color: var(--mud-palette-lines-default);
    border-radius: 999px;
}

.ns-scroll-thin:hover::-webkit-scrollbar-thumb {
    background-color: var(--mud-palette-text-secondary);
}
