/* =====================================================================
 * FlexOps Shared Radzen Theme Overrides
 * =====================================================================
 * Common Radzen component overrides for all FlexOps Blazor portals.
 * Applies the FlexOps design system to Radzen Material 3 components.
 *
 * Requires: flexops-tokens.css loaded first.
 * Portal-specific rules remain in each portal's theme.css.
 * =====================================================================
 * Copyright (c) 2021-2026 by FlexOps, LLC. All rights reserved.
 * =================================================================== */


/* =================================================================
   0. ICON FONT
   Radzen renders every icon as ligature TEXT - <i class="rzi">save</i> -
   in whatever font --rz-icon-font-family names. Pointing that at our own
   font swaps the entire icon set without touching a single .razor file,
   including the icons markup cannot reach: DataGrid sort arrows, filter
   buttons, the paginator, dropdown chevrons, dialog close, date picker.
   Those come from inside compiled Radzen components and take no Icon
   parameter.

   Built by icons/build-font.mjs from Untitled UI outlines. Its ligature
   names are the MATERIAL names, which is why nothing else has to change.

   font-display: block is deliberate. With swap (or the default) the
   fallback face renders during load - and the "fallback" here is the
   ligature's own source text, so every icon would flash the literal
   string "local_shipping" before resolving.

   THERE IS NO WORKING FALLBACK. "Material Symbols" is listed second, but
   it can never be reached, and an icon name this font does not know
   renders as NOTHING - a blank 1em box, not a glyph and not raw text.

   Why: CSS font fallback is per-CHARACTER, not per-ligature. Building
   ligatures requires this font to carry cmap entries for the lowercase
   letters, digits and underscore that spell the names, and those component
   glyphs are blank. The browser therefore finds "l", "o", "c"... in this
   font, is satisfied, and never consults the next family. Verified: in
   this font alone, "abc XYZ 123" renders as "XYZ 13".

   The consequence is that COVERAGE MUST BE COMPLETE, because a gap is
   invisible rather than obvious. icons/check-icon-map.mjs gates every name
   in icons/icon-inventory.json, and that inventory must be kept honest
   against a runtime sweep of .rzi text - Radzen's own internal icons
   (grid sort, filter, paginator, chevrons, dialog close, date picker)
   appear in no source file and can only be observed at runtime.
   ================================================================= */

@font-face {
    font-family: 'FlexOps Icons';
    src: url('../fonts/flexops-icons.woff2') format('woff2');
    font-style: normal;
    /* Literal on purpose: inside @font-face this is a DESCRIPTOR of the font
       file, not a style declaration, and custom properties do not resolve
       there — var() would make the whole line invalid and drop it. */
    font-weight: 400;
    font-display: block;
}


/* =================================================================
   1. RADZEN PRIMARY COLOR OVERRIDES
   Map Radzen CSS custom properties to FlexOps brand tokens.
   ================================================================= */

:root {
    --rz-icon-font-family: 'FlexOps Icons', 'Material Symbols';

    /* No --rz-icon-weight here on purpose. It drove the Material Symbols
       variable-weight axis, which our font does not have - it is a single
       static weight, drawn at Untitled UI's native 2px stroke on a 24 grid,
       the same weight flexops.io renders. Setting it would silently do
       nothing. To change icon weight, change the stroke width in
       icons/build-font.mjs and rebuild the font. */

    /* Force Radzen components onto the canonical FlexOps font (Inter). Radzen's
       base theme otherwise falls back to Roboto, which left Inter/Roboto/Arial
       mixed across the portals and Ship Manager. One mapping fixes every
       .rz-* component. */
    --rz-text-font-family: var(--font-body);

    --rz-primary: var(--color-brand-600);
    --rz-primary-lighter: var(--color-brand-400);
    --rz-primary-light: var(--color-brand-500);
    --rz-primary-dark: var(--color-brand-700);
    --rz-primary-darker: var(--color-brand-800);

    --rz-secondary: var(--color-gray-600);
    --rz-secondary-lighter: var(--color-gray-400);
    --rz-secondary-light: var(--color-gray-500);
    --rz-secondary-dark: var(--color-gray-700);
    --rz-secondary-darker: var(--color-gray-800);

    --rz-info: var(--color-blue-600);
    --rz-success: var(--color-success-600);
    --rz-warning: var(--color-warning-600);
    --rz-danger: var(--color-error-600);

    --rz-text-color: var(--color-text-primary);
    --rz-text-secondary-color: var(--color-text-secondary);
    --rz-text-tertiary-color: var(--color-text-tertiary);
    --rz-text-disabled-color: var(--color-text-disabled);

    --rz-base-background-color: var(--color-bg-primary);
    --rz-base-900-background-color: var(--color-gray-900);

    --rz-border-color: var(--color-border-secondary);

    --rz-tabs-tab-color: var(--color-text-primary);
    --rz-tabs-tab-selected-color: var(--color-fg-brand-primary);

    /* Grid header type. These MUST be set as Radzen variables, not as a rule on
       `thead th`: Radzen renders the label in a .rz-column-title span that carries
       its own font-size/weight/text-transform from these variables, so a th rule
       styles a box whose text ignores it. Ship Manager had exactly that — a 14px
       th wrapping a 12px label — and the 14px never appeared on screen.

       Radzen's defaults are 0.75rem and text-transform:uppercase, so every grid in
       every portal has been rendering ALL-CAPS headers. Capitals are wider and
       slower to scan, and they are why long titles hit the column edge first.

       Sentence case is the readability win; the size is deliberately left at 12px.
       Every column width in the product was laid out against a 12px header, so
       raising it to 14px tightened fixed-width columns across all five apps at once
       — it made "Confirmed" on New Orders cramped, and it would have kept surfacing
       elsewhere. Dropping the capitals costs no width at all: the same title renders
       NARROWER than it did before. */
    --rz-grid-header-font-size: var(--text-xs);
    --rz-grid-header-font-weight: var(--font-weight-semibold);
    --rz-grid-header-text-transform: none;
    /* Radzen's 1rem leaves no room for a wrapped second line. */
    --rz-grid-header-line-height: 1.25;
}


/* =================================================================
   2. SIDEBAR (Light theme)
   ================================================================= */

.rz-sidebar {
    background: var(--color-bg-primary) !important;
    border-right: 1px solid var(--color-border-secondary) !important;
}

/* Nav item text */
.rz-sidebar .rz-navigation-item-text,
.rz-sidebar .rz-panel-menu-item-text {
    color: var(--color-text-secondary) !important;
    font-size: 0.875rem;
    font-weight: var(--font-weight-medium);
}

/* Nav item icons */
.rz-sidebar .rz-navigation-item-icon,
.rz-sidebar .rz-panel-menu-item-icon {
    color: var(--color-gray-400) !important;
}

/* Active / selected nav item
 *
 * `.rz-navigation-item-wrapper-active` is the class Radzen 10.2.1 actually
 * emits; `.rz-state-active` lands on a different element in the tree. Every
 * rule in this block used to target only the latter, so the whole active-nav
 * treatment — background, text, icon AND the dark-theme AA lift below — was
 * dead, and Radzen's own default (`--rz-primary-lighter`, which :root remaps
 * to our light brand-400) painted the item instead. Harmless in light, but in
 * dark it put #D8D4DD text on a light purple pill at 1.87:1.
 *
 * Both class forms are kept so this survives a Radzen version that emits
 * either one. */
.rz-sidebar .rz-state-active > .rz-navigation-item-wrapper,
.rz-panel-menu .rz-navigation-item-wrapper.rz-state-active,
.rz-sidebar .rz-navigation-item-wrapper-active,
.rz-panel-menu .rz-navigation-item-wrapper-active {
    background: var(--color-bg-brand-primary) !important;
    border-radius: var(--radius-md);
}

.rz-sidebar .rz-state-active .rz-panel-menu-item-text,
.rz-sidebar .rz-state-active > .rz-navigation-item-wrapper .rz-navigation-item-text,
.rz-sidebar .rz-navigation-item-wrapper-active .rz-navigation-item-text,
.rz-sidebar .rz-navigation-item-wrapper-active .rz-panel-menu-item-text,
.rz-panel-menu .rz-navigation-item-wrapper-active .rz-navigation-item-text,
.rz-panel-menu .rz-navigation-item-wrapper-active .rz-panel-menu-item-text {
    color: var(--color-brand-700) !important;
    font-weight: var(--font-weight-semibold);
}

.rz-sidebar .rz-state-active .rz-panel-menu-item-icon,
.rz-sidebar .rz-state-active > .rz-navigation-item-wrapper .rz-navigation-item-icon,
.rz-sidebar .rz-navigation-item-wrapper-active .rz-navigation-item-icon,
.rz-sidebar .rz-navigation-item-wrapper-active .rz-panel-menu-item-icon,
.rz-panel-menu .rz-navigation-item-wrapper-active .rz-navigation-item-icon,
.rz-panel-menu .rz-navigation-item-wrapper-active .rz-panel-menu-item-icon {
    color: var(--color-brand-600) !important;
}

/* Dark theme: brand-700/600 fail AA on the dark active-nav surface
   (~2.0:1). Lift to lighter brand stops. --color-brand-* aliases can't be
   remapped globally because they also drive --rz-primary button fills. */
[data-theme="dark"] .rz-sidebar .rz-state-active .rz-panel-menu-item-text,
[data-theme="dark"] .rz-sidebar .rz-state-active > .rz-navigation-item-wrapper .rz-navigation-item-text,
[data-theme="dark"] .rz-sidebar .rz-navigation-item-wrapper-active .rz-navigation-item-text,
[data-theme="dark"] .rz-sidebar .rz-navigation-item-wrapper-active .rz-panel-menu-item-text,
[data-theme="dark"] .rz-panel-menu .rz-navigation-item-wrapper-active .rz-navigation-item-text,
[data-theme="dark"] .rz-panel-menu .rz-navigation-item-wrapper-active .rz-panel-menu-item-text {
    color: var(--color-brand-300) !important;
}

[data-theme="dark"] .rz-sidebar .rz-state-active .rz-panel-menu-item-icon,
[data-theme="dark"] .rz-sidebar .rz-state-active > .rz-navigation-item-wrapper .rz-navigation-item-icon,
[data-theme="dark"] .rz-sidebar .rz-navigation-item-wrapper-active .rz-navigation-item-icon,
[data-theme="dark"] .rz-sidebar .rz-navigation-item-wrapper-active .rz-panel-menu-item-icon,
[data-theme="dark"] .rz-panel-menu .rz-navigation-item-wrapper-active .rz-navigation-item-icon,
[data-theme="dark"] .rz-panel-menu .rz-navigation-item-wrapper-active .rz-panel-menu-item-icon {
    color: var(--color-brand-400) !important;
}

/* Hover state */
.rz-sidebar .rz-navigation-item-wrapper:hover,
.rz-panel-menu .rz-navigation-item-wrapper:hover {
    background: var(--color-bg-secondary) !important;
    border-radius: var(--radius-md);
}

.rz-sidebar .rz-navigation-item-wrapper:hover .rz-panel-menu-item-text,
.rz-sidebar .rz-navigation-item-wrapper:hover .rz-navigation-item-text {
    color: var(--color-text-primary) !important;
}

.rz-sidebar .rz-navigation-item-wrapper:hover .rz-panel-menu-item-icon,
.rz-sidebar .rz-navigation-item-wrapper:hover .rz-navigation-item-icon {
    color: var(--color-brand-500) !important;
}

/* Sidebar title text.
   RadzenText emits the TextStyle class alone (rz-text-subtitle1), never a
   bare `rz-text` alongside it, so the `.rz-text.` form this and the caption
   rule below used matched nothing and both were dead. */
.rz-sidebar .rz-text-subtitle1 {
    color: var(--color-text-primary) !important;
    font-weight: var(--font-weight-semibold);
}

/* Sidebar footer text (version + copyright).
 *
 * Tertiary, not the disabled token. A version string is de-emphasised, not
 * disabled, and --flexops-text-disabled is one of the few text tokens with no
 * dark-theme override (tokens line 114) — so it stayed at the light-mode
 * gray-500 on the dark surface, giving 3.78:1 in every portal. Tertiary is
 * 8.35:1 there and still clearly recedes in light.
 *
 * Deliberately not fixed by giving --flexops-text-disabled a dark value:
 * that token's job is to make genuinely disabled controls look disabled, and
 * lifting it platform-wide would undo that. */
.rz-sidebar .rz-text-caption {
    color: var(--color-text-tertiary) !important;
}

/* Panel menu expand/collapse arrow */
.rz-sidebar .rz-panel-menu-item-icon-children {
    color: var(--color-gray-400) !important;
}


/* =================================================================
   3. HEADER
   ================================================================= */

.rz-header {
    background: var(--color-bg-primary);
    border-bottom: 1px solid var(--color-border-secondary);
    box-shadow: var(--shadow-xs);
}


/* =================================================================
   4. BUTTONS
   ================================================================= */

/* Primary button */
/* `.rz-button` doubled for the same reason as .rz-secondary below: Radzen's
 * `.rz-button.rz-primary.rz-shade-default` is three classes and owns both
 * `background-color` and `color`, so the two declarations here lost. No
 * visual change today — --rz-primary and --color-bg-brand-solid both resolve
 * to brand-600, and --rz-on-primary is the white we want — but a dead
 * declaration that happens to agree is a trap: remap --rz-primary and these
 * buttons change colour with nothing in this file to explain it. */
.rz-button.rz-button.rz-primary:not(.rz-variant-outlined):not(.rz-variant-text) {
    position: relative;
    background: var(--color-bg-brand-solid);
    border: 1px solid var(--color-bg-brand-solid);
    color: var(--color-text-white);
    box-shadow: var(--shadow-xs-skeumorphic);
    border-radius: var(--radius-lg);
    font-weight: var(--font-weight-semibold);
    font-size: 0.875rem;
}

/* Dark mode: our primary buttons always have a solid brand background, but the
   Flat variant (e.g. the inventory Export split button) keeps brand-colored
   text, which reads as faded purple-on-purple in dark. Force white text/icon on
   any primary button or split button so it stays high-contrast. */
[data-theme="dark"] .rz-button.rz-primary,
[data-theme="dark"] .rz-button.rz-primary .rzi,
[data-theme="dark"] .rz-splitbutton.rz-primary .rz-button,
[data-theme="dark"] .rz-splitbutton .rz-button.rz-primary {
    color: #fff !important;
}

/* Inner gradient border — matches the Next.js website button feel.
   A semi-transparent white border at the top that fades to invisible
   at the bottom, creating a subtle "light reflecting from above" depth. */
.rz-button.rz-primary::before {
    content: "";
    position: absolute;
    inset: 1px;
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: calc(var(--radius-lg) - 1px);
    mask-image: linear-gradient(to bottom, black, transparent);
    -webkit-mask-image: linear-gradient(to bottom, black, transparent);
    pointer-events: none;
}

/* A hover rule that fills a solid background MUST also claim the foreground.
 * Radzen owns `color` for the outlined/text variants at four classes
 * (`.rz-button.rz-variant-outlined.rz-primary.rz-shade-default` → purple
 * text). Filling the background without claiming colour left purple-on-purple
 * on hover for every outlined primary button. Doubling `.rz-button` brings
 * this to four too, and this file loads after the theme, so it wins. */
.rz-button.rz-button.rz-primary:hover {
    background: var(--color-bg-brand-solid_hover);
    border-color: var(--color-bg-brand-solid_hover);
    color: var(--color-text-white);
    box-shadow: var(--shadow-md);
    transform: translateY(-0.5px);
}

.rz-button.rz-primary:active {
    transform: translateY(1px);
    box-shadow: var(--shadow-xs);
}

/* Danger button also gets the inner gradient border */
.rz-button.rz-danger::before {
    content: "";
    position: absolute;
    inset: 1px;
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: calc(var(--radius-lg) - 1px);
    mask-image: linear-gradient(to bottom, black, transparent);
    -webkit-mask-image: linear-gradient(to bottom, black, transparent);
    pointer-events: none;
}

/* Secondary / light button
 *
 * `.rz-button` is doubled deliberately. Radzen's theme ships
 * `.rz-button.rz-secondary.rz-shade-default { color: var(--rz-on-secondary) }`
 * — three classes — and RadzenButton emits `rz-shade-default` whenever no
 * Shade is given. A plain `.rz-button.rz-secondary` here is only two classes,
 * so it LOST the colour at rest (leaving Radzen's #ffffff) while still winning
 * the :hover background, which produced white-on-near-white on hover. Doubling
 * ties Radzen's specificity, and this file loads after the theme, so it wins —
 * for every shade variant at once, without enumerating them.
 *
 * `.rz-light` has no shade-qualified rule in the theme, so it needs no bump. */
.rz-button.rz-button.rz-secondary,
.rz-button.rz-light {
    background: var(--color-bg-primary);
    /* Same reasoning as the inputs: this button's fill matches the card behind it,
       so the border is the control boundary rather than decoration. */
    border: 1px solid var(--color-border-control);
    color: var(--color-text-secondary);
    box-shadow: var(--shadow-xs);
    border-radius: var(--radius-lg);
    font-weight: var(--font-weight-semibold);
    font-size: 0.875rem;
}

/* Colour is restated, not inherited: the rest-state rule losing its colour is
   exactly what caused the white-on-white, so the pair is kept self-contained. */
.rz-button.rz-button.rz-secondary:hover,
.rz-button.rz-light:hover {
    background: var(--color-bg-secondary);
    color: var(--color-text-secondary);
    box-shadow: var(--shadow-md);
    transform: translateY(-0.5px);
}

/* Outlined button hover — primary and secondary only.
 *
 * The :root block above remaps `--rz-primary-lighter` and
 * `--rz-secondary-lighter` onto our brand/gray ramps, but their
 * `--rz-on-*-lighter` partners were left at Radzen's Material defaults. So the
 * background moved to our palette while the foreground stayed keyed to theirs,
 * and Radzen's outlined hover rule combined the two into unrelated hues:
 *   primary   → #4340D2 indigo on #B28BE4 purple = 2.65:1
 *   secondary → #e31c65 magenta on #B0ABB8 gray  = 2.03:1
 * Both fail WCAG AA (4.5:1).
 *
 * Filling solid on hover reads as a stronger affordance than tinting, and
 * lands at 8.89:1 (white on brand-700) and 8.6:1 (white on gray-700).
 *
 * Radzen's rule is 4 classes + :not() + :hover; the doubled `.rz-button` puts
 * this one class above it. Only these two styles are affected: info, warning
 * and danger never had their `-lighter` stops remapped, so their background
 * and foreground defaults still match each other. */
.rz-button.rz-button.rz-variant-outlined.rz-primary:not(.rz-state-disabled):hover {
    background-color: var(--color-bg-brand-solid_hover);
    border-color: var(--color-bg-brand-solid_hover);
    color: var(--color-text-white);
}

.rz-button.rz-button.rz-variant-outlined.rz-secondary:not(.rz-state-disabled):hover {
    background-color: var(--flexops-gray-700);
    border-color: var(--flexops-gray-700);
    color: var(--color-text-white);
}

/* Icon-only text buttons (grid row actions).
 *
 * Radzen designs the `light` palette for DARK surfaces: it renders
 * `.rz-button.rz-variant-text.rz-light { color: var(--rz-base-200) }` — a
 * near-white — with an `rgba(255,255,255,.12)` hover. On our light grids the
 * icon is barely visible and the hover does nothing, so an ENABLED action
 * reads as disabled.
 *
 * Both rules are scoped `:not(.rz-state-disabled)` on purpose: pages like
 * IdentityRoles disable the edit action for protected roles, and that state
 * must stay visually distinct from the enabled one. Radzen's pale colour is
 * left in place for genuinely disabled buttons. */
.rz-button.rz-button.rz-variant-text.rz-light:not(.rz-state-disabled) {
    color: var(--color-text-secondary);
}

.rz-button.rz-button.rz-variant-text.rz-light:not(.rz-state-disabled):hover {
    background-color: var(--color-bg-tertiary);
    color: var(--color-text-primary);
}

/* Danger button */
/* Doubled for the same reason as .rz-primary above; likewise no visual change
   today, since --rz-danger and --color-bg-error-solid are both error-600. */
.rz-button.rz-button.rz-danger:not(.rz-variant-outlined):not(.rz-variant-text) {
    position: relative;
    background: var(--color-bg-error-solid);
    border: 1px solid var(--color-bg-error-solid);
    color: var(--color-text-white);
    border-radius: var(--radius-lg);
    font-weight: var(--font-weight-semibold);
}

/* Same reasoning as .rz-primary:hover above. */
.rz-button.rz-button.rz-danger:hover {
    background: var(--color-error-700);
    border-color: var(--color-error-700);
    color: var(--color-text-white);
    box-shadow: var(--shadow-md);
    transform: translateY(-0.5px);
}

.rz-button.rz-danger:active {
    transform: translateY(1px);
    box-shadow: var(--shadow-xs);
}

/* All buttons: consistent radius + snappy transitions */
.rz-button {
    border-radius: var(--radius-lg);
    transition: background-color 150ms ease,
                border-color 150ms ease,
                box-shadow 150ms ease,
                transform 100ms ease;
}


/* =================================================================
   5. FORM INPUTS
   ================================================================= */

/* border-control, not border-primary: the background below is the same colour as
   the card these sit on, so this 1px edge is the only thing marking the control. */
.rz-textbox,
.rz-inputtext,
.rz-dropdown,
.rz-multiselect,
.rz-datepicker-input,
.rz-textarea {
    border: 1px solid var(--color-border-control);
    border-radius: var(--radius-lg);
    background: var(--color-bg-primary);
    color: var(--color-text-primary);
    font-family: var(--font-body);
    font-size: 0.875rem;
    box-shadow: var(--shadow-xs);
}

.rz-textbox:focus,
.rz-inputtext:focus,
.rz-dropdown.rz-state-focused,
.rz-multiselect.rz-state-focused,
.rz-datepicker-input:focus,
.rz-textarea:focus {
    border-color: var(--color-border-brand);
    box-shadow: 0 0 0 4px var(--color-focus-ring);
}

.rz-textbox::placeholder,
.rz-inputtext::placeholder,
.rz-textarea::placeholder {
    color: var(--color-text-placeholder);
}

.rz-textbox:disabled,
.rz-inputtext:disabled {
    background: var(--color-bg-disabled);
    color: var(--color-text-disabled);
    border-color: var(--color-border-disabled);
    cursor: not-allowed;
}

/* Read-only is not disabled. Radzen's own themes carry no [readonly] selector at all, so a
   read-only input renders pixel-identical to one you can type in - Ship Manager's Create Order
   page showed Order Date, Subtotal and Total that way and operators read them as editable.

   Deliberately NOT the disabled palette above: a computed total is live, meaningful data, and
   dimming it to --color-text-disabled would say "unavailable" about the number the operator most
   needs. Inset background and the default cursor say "derived, not yours to type" instead, and
   the text colour is left alone.

   The border is deliberately left alone, so a read-only field keeps the same
   --color-border-control edge as the editable field beside it and the whole signal is carried by
   the inset fill and the missing shadow. A dashed edge was tried and read as an error state or a
   drop target - louder than an editable control, which is backwards for a field that should
   recede. Re-colouring it to --color-border-secondary is not an option either: in the dark theme
   that token and --color-bg-tertiary are both #2C2A33, so the border would disappear entirely.

   Two selector shapes because RadzenNumeric renders its input inside a bare .rz-numeric wrapper
   that carries no chrome of its own, while RadzenTextBox and RadzenTextArea render their class on
   the input directly. */
.rz-textbox[readonly],
.rz-inputtext[readonly],
.rz-textarea[readonly],
.rz-numeric input[readonly] {
    background: var(--color-bg-tertiary);
    cursor: default;
    box-shadow: none;
}

/* ...and it must stay read-only while focused. These fields are focusable on purpose - that is
   the whole reason a locked Customer ID uses ReadOnly rather than Disabled, so the operator can
   select and copy it - but Radzen repaints any focused input with
   --rz-input-focus-background-color via:

       .rz-textbox:not(:disabled):not(.rz-state-disabled):focus        (0,4,0)
       .rz-numeric:focus-within:not(.rz-state-disabled)                (0,3,0)

   Both exclude disabled; NEITHER excludes readonly. So clicking a read-only text box turned it
   white and it read as editable again. The numeric half is already safe (that rule paints the
   .rz-numeric wrapper, and the input's own background sits on top of it), but the text box rule
   targets the input directly and out-specifies the rule above, so this has to out-specify it in
   turn rather than rely on file order - hence the mirrored :not() chain.

   The focus ring is deliberately kept. Only the "you may type here" fill is undone. */
.rz-textbox[readonly]:not(:disabled):not(.rz-state-disabled):focus,
.rz-inputtext[readonly]:not(:disabled):not(.rz-state-disabled):focus,
.rz-textarea[readonly]:not(:disabled):not(.rz-state-disabled):focus {
    background: var(--color-bg-tertiary);
    border-color: var(--color-border-brand);
    box-shadow: 0 0 0 4px var(--color-focus-ring);
}

/* DropDownDataGrid popups should read like one full-width picker, not a narrow
   grid floating inside a wider dropdown panel. */
.rz-popup:has(.rz-data-grid),
.rz-popup:has(.rz-datatable),
.rz-dropdown-panel:has(.rz-data-grid),
.rz-dropdown-panel:has(.rz-datatable) {
    box-sizing: border-box;
}

.rz-popup:has(.rz-data-grid) > *,
.rz-popup:has(.rz-datatable) > *,
.rz-dropdown-panel:has(.rz-data-grid) > *,
.rz-dropdown-panel:has(.rz-datatable) > * {
    box-sizing: border-box;
    max-width: 100% !important;
    min-width: 100% !important;
    width: 100% !important;
}

.rz-popup .rz-data-grid,
.rz-popup .rz-datatable,
.rz-popup .rz-data-grid-data,
.rz-popup .rz-datatable-data,
.rz-popup .rz-datatable-scrollable,
.rz-popup .rz-datatable-scrollable-wrapper,
.rz-popup .rz-datatable-scrollable-view,
.rz-popup .rz-datatable-scrollable-header,
.rz-popup .rz-datatable-scrollable-header-box,
.rz-popup .rz-datatable-scrollable-body,
.rz-popup .rz-datatable-scrollable-table-wrapper,
.rz-popup .rz-datatable-tablewrapper,
.rz-dropdown-panel .rz-data-grid,
.rz-dropdown-panel .rz-datatable,
.rz-dropdown-panel .rz-data-grid-data,
.rz-dropdown-panel .rz-datatable-data,
.rz-dropdown-panel .rz-datatable-scrollable,
.rz-dropdown-panel .rz-datatable-scrollable-wrapper,
.rz-dropdown-panel .rz-datatable-scrollable-view,
.rz-dropdown-panel .rz-datatable-scrollable-header,
.rz-dropdown-panel .rz-datatable-scrollable-header-box,
.rz-dropdown-panel .rz-datatable-scrollable-body,
.rz-dropdown-panel .rz-datatable-scrollable-table-wrapper,
.rz-dropdown-panel .rz-datatable-tablewrapper {
    box-sizing: border-box;
    max-width: 100% !important;
    min-width: 100% !important;
    width: 100% !important;
}

.rz-popup .rz-data-grid .rz-grid-table,
.rz-popup .rz-datatable .rz-grid-table,
.rz-popup .rz-datatable table,
.rz-popup .rz-datatable-scrollable-table,
.rz-dropdown-panel .rz-data-grid .rz-grid-table,
.rz-dropdown-panel .rz-datatable .rz-grid-table,
.rz-dropdown-panel .rz-datatable table,
.rz-dropdown-panel .rz-datatable-scrollable-table {
    min-width: 100% !important;
    table-layout: auto;
    width: 100% !important;
}

.rz-popup .rz-data-grid colgroup,
.rz-popup .rz-datatable colgroup,
.rz-dropdown-panel .rz-data-grid colgroup,
.rz-dropdown-panel .rz-datatable colgroup {
    width: 100% !important;
}

/* DataGrid header (.rz-grid-table thead). The .rz-datatable-thead rule below
   only covers the older DataTable component; RadzenDataGrid renders
   .rz-grid-table, whose header had no override and fell back to a light Radzen
   surface — a white bar in dark mode, on every in-page grid AND DropDownDataGrid
   popup across all portals and Ship Manager. Pin it to the flipping semantic
   tokens (matches the DataTable header, so light mode is consistent too).
   ShipManager's .data-grid header still wins where it applies, and its
   .data-grid.operator-grid modifier wins over that (custom.css loads later,
   at equal or higher specificity). */
.rz-data-grid .rz-grid-table thead th {
    background: var(--color-bg-secondary);
    color: var(--color-text-secondary);
    border-bottom: 1px solid var(--color-border-secondary);
}

/* The rest of the DataGrid chrome (footer, pager) chains through the same base
   surface vars as the header, so it leaks light in dark mode the same way.
   Dark-scoped overrides — they cannot affect light mode, only force these
   surfaces dark where the overlay theme leaves them light. */
[data-theme="dark"] .rz-grid-table tfoot td {
    background: var(--color-bg-secondary);
    color: var(--color-text-secondary);
}

[data-theme="dark"] .rz-pager {
    background: var(--color-bg-primary);
    color: var(--color-text-secondary);
    border-top: 1px solid var(--color-border-secondary);
}

[data-theme="dark"] .rz-pager .rz-pager-page {
    color: var(--color-text-secondary);
}

[data-theme="dark"] .rz-pager .rz-pager-page.rz-state-active {
    background: var(--color-bg-brand-primary);
    color: var(--color-brand-300);
}

/* Error state */
.rz-textbox.rz-state-invalid,
.rz-inputtext.rz-state-invalid {
    border-color: var(--color-border-error);
    box-shadow: 0 0 0 4px rgba(240, 68, 56, 0.12);
}

/* Labels */
.rz-form-field-label {
    color: var(--color-text-secondary);
    font-weight: var(--font-weight-medium);
    font-size: 0.875rem;
}


/* =================================================================
   6. DATA GRID
   ================================================================= */

/* LIVE, despite the name: RadzenDataGrid puts BOTH classes on its root element
   ("rz-data-grid rz-datatable rz-datatable-scrollable"), so this reaches every
   grid in every portal. Its four declarations are also set on .rz-data-grid near
   the bottom of this file; that block is later, so its box-shadow (--shadow-xs)
   is the one in effect. Left here because the two classes are not guaranteed to
   travel together — Radzen also emits rz-datatable without rz-data-grid. */
.rz-datatable {
    border: 1px solid var(--color-border-secondary);
    border-radius: var(--radius-lg);
    overflow: clip;
    box-shadow: var(--shadow-sm);
}

/* REMOVED 2026-07-31: 28 lines styling `.rz-datatable .rz-datatable-thead > tr > th`
   and `.rz-datatable .rz-datatable-tbody > tr > td` (header type, cell padding, row
   hover, last-row border, a sticky header). Those two class names do not exist in
   Radzen 11.2.0 — the grid renders .rz-grid-table with plain thead/tbody — so none
   of it ever matched an element. The DATAGRID section at the bottom of this file is
   where that styling actually lives and works. */


/* =================================================================
   7. CHARTS
   ================================================================= */

.flexops-chart-panel {
    height: 100%;
    padding: var(--flexops-space-5);
}

.flexops-chart-heading {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--flexops-space-4);
}

.flexops-chart-title {
    margin: 0;
    color: var(--color-text-primary);
    font-size: var(--text-lg);
    font-weight: var(--font-weight-semibold);
    line-height: var(--line-height-lg);
}

.flexops-chart-summary {
    margin: var(--flexops-space-1) 0 var(--flexops-space-3);
    color: var(--color-text-secondary);
    font-size: var(--text-sm);
    line-height: var(--line-height-sm);
}

.flexops-chart-filter {
    min-width: 11rem;
}

.flexops-chart-surface {
    min-width: 0;
    padding: var(--flexops-space-3);
    overflow: hidden;
}

.flexops-chart {
    width: 100%;
    height: 18rem;
}

.flexops-chart-compact {
    height: 12.5rem;
}

.flexops-chart-tooltip {
    display: grid;
    gap: var(--flexops-space-1);
}

.flexops-chart-tooltip span {
    color: var(--color-text-secondary);
    font-size: var(--text-xs);
}

.flexops-chart-tooltip strong {
    color: var(--color-text-primary);
    font-size: var(--text-sm);
}

@media (max-width: 768px) {
    .flexops-chart-heading {
        flex-direction: column;
    }

    .flexops-chart-filter {
        width: 100%;
    }

    .flexops-chart {
        height: 15rem;
    }
}


/* =================================================================
   8. CARDS
   ================================================================= */

.rz-card {
    border: 1px solid var(--color-border-secondary);
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-sm);
    background: var(--color-bg-primary);
}

/* Dark mode: shadows are near-invisible on the dark page, so cards lean on a
   stronger border (border-primary, not -secondary) to keep a crisp edge. */
[data-theme="dark"] .rz-card,
[data-theme="dark"] .settings-card {
    border-color: var(--color-border-primary);
}


/* =================================================================
   9. DIALOG / MODAL
   ================================================================= */

.rz-dialog {
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-xl);
    border: 1px solid var(--color-border-secondary);
}

.rz-dialog-titlebar {
    border-bottom: 1px solid var(--color-border-secondary);
    padding: 16px 24px;
}

.rz-dialog-title {
    font-size: 1.125rem;
    font-weight: var(--font-weight-semibold);
    color: var(--color-text-primary);
}

.rz-dialog-content {
    padding: 24px;
}


/* =================================================================
   9. NOTIFICATION
   ================================================================= */

.rz-notification {
    font-family: var(--font-body);
}

.rz-notification-item {
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
}

.rz-notification-success .rz-notification-item {
    border-left: 4px solid var(--color-success-500);
}

.rz-notification-error .rz-notification-item {
    border-left: 4px solid var(--color-error-500);
}

.rz-notification-warn .rz-notification-item {
    border-left: 4px solid var(--color-warning-500);
}

.rz-notification-info .rz-notification-item {
    border-left: 4px solid var(--color-blue-500);
}


/* =================================================================
   11. PANEL MENU (Sidebar Nav)
   ================================================================= */

.rz-panel-menu {
    font-family: var(--font-body);
}

.rz-panel-menu .rz-navigation-item-wrapper {
    border-radius: var(--radius-md);
    margin: 2px 8px;
}

.rz-panel-menu .rz-navigation-item-wrapper:hover {
    background: rgba(255, 255, 255, 0.08);
}

.rz-panel-menu .rz-navigation-item-wrapper.rz-state-active {
    background: rgba(141, 89, 207, 0.15);
}


/* =================================================================
   12. LOGIN PAGE
   ================================================================= */

.login-card {
    background: var(--color-bg-primary);
    border: 1px solid var(--color-border-secondary);
    border-radius: var(--radius-xl);
    padding: 32px;
    box-shadow: var(--shadow-lg);
    max-width: 420px;
    margin: 0 auto;
}

.login-title {
    font-size: 1.5rem;
    font-weight: var(--font-weight-semibold);
    color: var(--color-text-primary);
    text-align: center;
    margin: 0 0 4px 0;
}

/* The auth pages programmatically focus this <h1> on load (Blazor FocusAsync) so
   screen readers announce the page on navigation. It is tabindex=-1 and never
   keyboard-tabbable, so a visible focus ring on it serves no one and reads as a
   bug. A :not(:focus-visible) guard is NOT enough: after a keyboard-initiated
   navigation (and in headless Chromium) the post-load programmatic focus IS
   classified :focus-visible, so the ring still showed. Suppress it
   unconditionally — focus still moves here for screen-reader users, there's just
   no visible box. (Safe per WCAG 2.4.7: the heading is not a control the user
   operates.) */
.login-title:focus,
.login-title:focus-visible {
    outline: none;
}

.login-subtitle {
    font-size: 0.875rem;
    color: var(--color-text-tertiary);
    text-align: center;
    margin: 0 0 24px 0;
}

.login-error {
    background: var(--color-bg-error-primary);
    border: 1px solid var(--color-error-200);
    border-radius: var(--radius-lg);
    padding: 12px 16px;
    color: var(--color-error-700);
    font-size: 0.875rem;
    margin-bottom: 16px;
    display: flex;
    align-items: center;
}

.login-input {
    display: block;
    width: 100%;
    padding: 10px 14px;
    /* The login field is a second copy of the input rule above; it needs the same
       control boundary, and it is the first control a new operator ever meets. */
    border: 1px solid var(--color-border-control);
    border-radius: var(--radius-lg);
    background: var(--color-bg-primary);
    color: var(--color-text-primary);
    font-family: var(--font-body);
    font-size: 0.875rem;
    line-height: 1.25rem;
    box-shadow: var(--shadow-xs);
    outline: none;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
    margin-top: 6px;
    min-height: 44px;
    box-sizing: border-box;
}

.login-input:focus {
    border-color: var(--color-border-brand);
    box-shadow: 0 0 0 4px var(--color-focus-ring);
}

.login-input-valid {
    border-color: var(--color-success-700);
}

.login-input-valid:focus {
    border-color: var(--color-success-700);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-success-700) 15%, transparent);
}

.login-input::placeholder {
    color: var(--color-text-placeholder);
}

.login-button {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    padding: 10px 18px;
    background: var(--color-bg-brand-solid);
    color: var(--color-text-white);
    border: 1px solid var(--color-bg-brand-solid);
    border-radius: var(--radius-lg);
    font-family: var(--font-body);
    font-size: 0.875rem;
    font-weight: var(--font-weight-semibold);
    line-height: 1.25rem;
    cursor: pointer;
    box-shadow: var(--shadow-xs-skeumorphic);
    transition: background 0.1s ease, box-shadow 0.1s ease, transform 0.1s ease;
}

.login-button::before {
    content: "";
    position: absolute;
    inset: 1px;
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: calc(var(--radius-lg) - 1px);
    mask-image: linear-gradient(to bottom, black, transparent);
    -webkit-mask-image: linear-gradient(to bottom, black, transparent);
    pointer-events: none;
}

.login-button:hover {
    background: var(--color-bg-brand-solid_hover);
    border-color: var(--color-bg-brand-solid_hover);
    box-shadow: var(--shadow-md);
    transform: translateY(-0.5px);
}

.login-button:active {
    transform: translateY(1px);
    box-shadow: var(--shadow-xs);
}

.login-link {
    color: var(--color-fg-brand-primary);
    font-size: 0.875rem;
    font-weight: var(--font-weight-medium);
    text-decoration: none;
    transition: color 0.1s ease;
    /* Enlarge the tap target without shifting layout (negative margin cancels
       the added padding). Keeps the visible text identical. */
    display: inline-block;
    padding: 10px 4px;
    margin: -6px -4px;
}

.login-link:hover {
    color: var(--color-bg-brand-solid_hover);
    text-decoration: underline;
}

.login-password-wrapper {
    position: relative;
}

.login-password-wrapper .login-input {
    padding-right: 44px;
}

.login-password-toggle {
    position: absolute;
    right: 1px;
    top: 50%;
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
    background: none;
    border: none;
    border-radius: 0 var(--radius-lg) var(--radius-lg) 0;
    cursor: pointer;
    color: var(--color-text-quaternary);
    display: flex;
    align-items: center;
    justify-content: center;
    transition: color 0.1s ease;
}

.login-password-toggle:hover {
    color: var(--color-text-secondary);
}

.login-password-toggle .rzi {
    font-size: 1.15rem;
}

/* Available to screen readers, clipped from the visual layout. Used for live-region
   status text that would be redundant next to a visible indicator. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

.password-rules {
    list-style: none;
    margin: 8px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.password-rules li {
    font-size: var(--text-sm);
    line-height: 1.25rem;
    display: flex;
    align-items: center;
    transition: color 0.15s ease;
}

.password-rules .rule-pass {
    color: var(--color-success-700);
}

.password-rules .rule-fail {
    color: var(--color-text-quaternary);
}

.login-success {
    background: var(--color-bg-success-primary);
    border: 1px solid var(--color-success-200);
    border-radius: var(--radius-lg);
    padding: 12px 16px;
    color: var(--color-success-700);
    font-size: 0.875rem;
    margin-bottom: 16px;
    display: flex;
    align-items: center;
}


/* =================================================================
   13. ICON RENDERING
   Ensure Material Icons render properly in all Radzen apps.
   Prevents clipping in cards and misaligned icon glyphs.
   ================================================================= */

.rz-icon,
i.rz-icon {
    line-height: 1 !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    overflow: visible !important;
    vertical-align: middle !important;
}

.material-icons,
.rz-icon {
    font-feature-settings: 'liga' !important;
    -webkit-font-smoothing: antialiased !important;
    text-rendering: optimizeLegibility !important;
}

/* REMOVED 2026-07-31: `.rz-card > div { overflow: visible !important }`.
   `visible` is a div's initial value, so that rule did nothing EXCEPT beat the
   three places that deliberately set overflow on a box that happens to sit
   directly inside a card — .fx-table-wrap's overflow-x:auto (the wallet table
   then pushed its full width up to .rz-body and scrolled the whole page
   sideways), and the `overflow: clip` on .rz-data-grid / .rz-datatable that
   makes their rounded corners actually clip. It also cancelled the mobile
   `.rz-data-grid { overflow-x: auto }` rules further down.
   Wide grids are unaffected: Radzen scrolls them on .rz-data-grid-data, which
   carries its own overflow:auto, and Radzen renders dropdown/date-picker panels
   into a body-level popup, so nothing that needs to escape a card is clipped by
   its own ancestor. Icon glyphs keep the overflow:visible on .rz-icon above —
   which is what this section was for. Do not reintroduce a blanket rule here. */


/* =================================================================
   14. DATAGRID — HIDE NUMERIC FILTER SPIN BUTTONS
   Convention: ShowUpDown="false" on all RadzenNumeric. For auto-
   generated DataGrid filter rows, CSS is the only enforcement.
   ================================================================= */

.rz-cell-filter .rz-numeric-up,
.rz-cell-filter .rz-numeric-down {
    display: none !important;
}


/* =================================================================
   15. BUTTON NORMALIZATION
   Remove Radzen default uppercase and add consistent micro-
   interactions across all portals.
   ================================================================= */

button.rz-button,
.rz-button {
    text-transform: none !important;
    letter-spacing: 0 !important;
}

.rz-button:active {
    transform: translateY(1px);
}


/* =================================================================
   16. ALERTS & MESSAGES (.rz-message)
   Status-colored alert bars with left border accent.
   ================================================================= */

.rz-message {
    border-radius: var(--radius-xl) !important;
    padding: 16px !important;
    border-left-width: 4px !important;
}

.rz-message.rz-info {
    background-color: var(--color-blue-50) !important;
    border-left-color: var(--color-blue-600) !important;
}

.rz-message.rz-success {
    background-color: var(--color-success-50) !important;
    border-left-color: var(--color-success-600) !important;
}

.rz-message.rz-warning {
    background-color: var(--color-warning-50) !important;
    border-left-color: var(--color-warning-600) !important;
}

.rz-message.rz-error {
    background-color: var(--color-error-50) !important;
    border-left-color: var(--color-error-600) !important;
}


/* =================================================================
   17. PROGRESS BAR
   ================================================================= */

.rz-progressbar {
    border-radius: var(--radius-full) !important;
    height: 8px !important;
    background-color: var(--color-gray-200) !important;
}

.rz-progressbar-value {
    border-radius: var(--radius-full) !important;
    background: linear-gradient(90deg, var(--color-brand-500), var(--color-brand-600)) !important;
}


/* =================================================================
   18. DATAGRID STRIPED ROWS

   REMOVED 2026-07-31: `.rz-datatable-striped .rz-datatable-row:nth-child(even)`.
   Neither class exists in Radzen 11.2.0, so striping was never applied to a row.
   AllowAlternatingRows on RadzenDataGrid is the supported route if it is wanted.
   ================================================================= */


/* =================================================================
   19. RADZEN BASE NEUTRAL SCALE
   Align Radzen's internal neutral palette with FlexOps gray tokens.

   LIGHT ONLY. This file loads after material-dark-base.css, and a bare
   :root here ties that file's specificity and wins on order — which pinned
   the whole base ramp to these light values in dark mode too. Radzen
   components that reach for the light end of the ramp then rendered as
   near-white chips on the dark surface; the DataGrid filter-operator button
   (--rz-base-100, #F3F1F5) was the visible one, on every filterable grid.

   material-dark-base.css already ships a coherent dark ramp (#121212 to
   #a0a0a0), so excluding dark here hands those stops back to it rather than
   hand-inverting fifteen values. Its neutrals are untinted where ours carry
   a slight purple cast — if that shows up as a mismatch against our own dark
   surfaces, map these to the --flexops-gray-* stops instead of widening the
   selector back.
   ================================================================= */

:root:not([data-theme="dark"]) {
    --rz-white: #FFFFFF;
    --rz-black: #110F15;
    --rz-base: #F3F1F5;
    --rz-base-50: #FAF9FB;
    --rz-base-100: #F3F1F5;
    --rz-base-200: #E9E6EC;
    --rz-base-300: #D8D4DD;
    --rz-base-400: #B0ABB8;
    --rz-base-500: #787480;
    --rz-base-600: #635F6B;
    --rz-base-700: #4A4651;
    --rz-base-800: #2C2A33;
    --rz-base-900: #1C1A21;
    --rz-base-light: #FAF9FB;
    --rz-base-lighter: #FFFFFF;
    --rz-base-dark: #635F6B;
    --rz-base-darker: #2C2A33;
}


/* =================================================================
   20. SCROLLBAR
   Thin, neutral scrollbar for all portals.
   ================================================================= */

::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}

::-webkit-scrollbar-track {
    background: var(--flexops-bg-tertiary);
    border-radius: 4px;
}

::-webkit-scrollbar-thumb {
    background: var(--flexops-border-primary);
    border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
    background: var(--flexops-fg-quaternary);
}

* {
    scrollbar-width: thin;
    scrollbar-color: var(--flexops-border-primary) var(--flexops-bg-tertiary);
}


/* =================================================================
   21. FOCUS STATES
   Brand purple focus ring on all interactive elements.
   ================================================================= */

.rz-button:focus-visible,
.rz-textbox:focus-visible,
.rz-dropdown:focus-visible {
    outline: 2px solid var(--rz-primary);
    outline-offset: 2px;
}


/* =================================================================
   22. HIGH CONTRAST MODE
   ================================================================= */

@media (prefers-contrast: high) {
    :root {
        --rz-primary: var(--color-brand-700, #5C368C);
        --rz-base-200: #CBD5E1;
    }
}


/* =================================================================
   23. REDUCED MOTION — WCAG 2.3.3
   ================================================================= */

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


/* =================================================================
   24. BLAZOR ERROR UI
   ================================================================= */

#blazor-error-ui {
    background: var(--color-bg-warning-primary, #FFFAEB);
    border-top: 2px solid var(--color-warning-400, #FDB022);
    bottom: 0;
    box-shadow: var(--shadow-lg);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
    color: var(--color-text-primary);
    font-size: 0.875rem;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 0.75rem;
    top: 0.5rem;
}


/* =================================================================
   25. MOBILE RESPONSIVE LAYOUT
   Radzen component adjustments for tablet and phone viewports.
   ================================================================= */

@media (max-width: 768px) {
    .rz-sidebar {
        position: fixed !important;
        left: -260px;
        top: 0;
        height: 100vh;
        width: 260px;
        transition: left 200ms ease;
        box-shadow: none;
    }

    .rz-sidebar.rz-sidebar-expanded {
        left: 0;
        box-shadow: 4px 0 16px rgba(0, 0, 0, 0.15);
    }

    /* Dimming scrim behind the open off-canvas drawer. Defined at the same
       768px breakpoint where the drawer becomes off-canvas — previously it
       only existed at <=480px, so 481-768px opened with no backdrop and taps
       fell through to the page behind. */
    .rz-sidebar.rz-sidebar-expanded::after {
        content: '';
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 260px;
        background: rgba(0, 0, 0, 0.3);
        z-index: 1;
    }

    .rz-body {
        margin-left: 0 !important;
    }

    .rz-header .rz-row > .rz-col-5,
    .rz-header .rz-row > .rz-col-7 {
        flex: 0 0 auto !important;
        max-width: none !important;
    }

    .rz-body .rz-row {
        padding-left: 0.75rem !important;
        padding-right: 0.75rem !important;
    }

    .rz-datatable-scrollable-body,
    .rz-data-grid {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }

    .rz-card {
        padding: 0.75rem !important;
    }

    .rz-form-field {
        width: 100% !important;
        max-width: 100% !important;
    }

    .rz-stack[data-orientation="horizontal"] {
        flex-wrap: wrap;
    }

    .rz-dialog {
        width: calc(100vw - 2rem) !important;
        max-width: 100% !important;
        margin: 1rem;
    }
}

@media (max-width: 480px) {
    .rz-body .rz-row {
        padding-left: 0.5rem !important;
        padding-right: 0.5rem !important;
        padding-top: 0.5rem !important;
    }

    .rz-header {
        padding: 0.25rem 0.5rem !important;
    }

    .rz-data-grid .rz-cell.hide-mobile {
        display: none !important;
    }

    .rz-button {
        min-height: 44px;
        min-width: 44px;
    }

    .rz-button.rz-button-xs {
        min-height: 36px;
        min-width: 36px;
    }

    .rz-tabview-nav {
        overflow-x: auto;
        flex-wrap: nowrap !important;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
    }

    .rz-tabview-nav::-webkit-scrollbar {
        display: none;
    }

    .rz-profile-menu .rz-popup {
        right: 0 !important;
        left: auto !important;
        min-width: 200px;
    }

    .rz-panel-menu-item-text {
        padding: 0.75rem 1rem !important;
    }

    .rz-card .rz-stack[data-orientation="horizontal"] {
        flex-direction: column !important;
        align-items: stretch !important;
    }

    .rz-form-field .rz-form-field-label {
        position: static !important;
        transform: none !important;
        margin-bottom: 0.25rem;
    }

    .rz-text-h4 {
        font-size: 1.25rem !important;
    }

    .rz-text-h5 {
        font-size: 1.1rem !important;
    }

    .rz-text-h6 {
        font-size: 1rem !important;
    }
}


/* =================================================================
   26. TOUCH-FRIENDLY ENHANCEMENTS
   ================================================================= */

@media (pointer: coarse) {
    .rz-button,
    .rz-dropdown,
    .rz-textbox {
        min-height: 44px;
    }

    .rz-chkbox,
    .rz-radiobutton {
        min-width: 44px;
        min-height: 44px;
    }

    .rz-data-grid .rz-data-row td {
        padding-top: 0.75rem !important;
        padding-bottom: 0.75rem !important;
    }

    ::-webkit-scrollbar {
        width: 12px;
        height: 12px;
    }
}


/* =================================================================
   27. PRINT
   ================================================================= */

@media print {
    .rz-sidebar,
    .rz-header,
    .rz-sidebar-toggle,
    .rz-button {
        display: none !important;
    }

    .rz-body {
        margin: 0 !important;
        padding: 0 !important;
    }

    .rz-card {
        box-shadow: none !important;
        border: 1px solid #ddd;
    }
}


/* =================================================================
   28. DESKTOP DENSITY — DataGrid + Sidebar
   Tightens Radzen defaults to ops-tool ergonomics (Linear/Stripe/
   Shippo class). Mobile overrides in each portal's responsive-grid.css
   layer on top via @media (max-width: 767px).
   ================================================================= */

/* DataGrid cells: ~13px text, half the default vertical padding. */
.rz-datatable .rz-cell {
    padding: 0.5rem 0.75rem;
    font-size: var(--text-sm);
    line-height: 1.4;
}

.rz-datatable .rz-cell-data {
    min-height: 32px;
}

/* DataGrid headers: small-caps style for clearer column scanning. */
.rz-datatable .rz-column-title {
    font-size: var(--text-xs);
    font-weight: var(--font-weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--flexops-text-tertiary);
}

.rz-datatable thead th {
    padding: 0.5rem 0.75rem;
    background: var(--flexops-bg-secondary);
    border-bottom: 1px solid var(--flexops-border-secondary);
}

/* Hairline row dividers. */
.rz-datatable tbody tr {
    border-bottom: 1px solid var(--flexops-border-secondary);
}

/* Tighten in-cell action buttons so they don't inflate row height. */
.rz-datatable .rz-cell .rz-button-sm,
.rz-datatable .rz-cell .rz-button-xs {
    padding: 0.25rem 0.5rem;
    font-size: 0.75rem;
}

/* Sidebar panel-menu density: ~25% more menu items per viewport. */
.rz-panel-menu .rz-navigation-item-link {
    padding: 0.4rem 0.75rem;
    font-size: var(--text-sm);
    min-height: 32px;
}

.rz-panel-menu .rz-navigation-item-link .rzi,
.rz-panel-menu .rz-navigation-item-link .rz-icon,
.rz-panel-menu .rz-navigation-item-link .material-icons {
    font-size: 1.125rem;
}

/* Give every sidebar nav icon the SAME BOX, not just the same font-size.
   Without a width the box is whatever the glyph happens to measure: 24px for a
   top-level item in .rz-panel-menu, 19px for a nested one in
   .rz-navigation-menu. The label sits after that box, so top-level labels
   started at x=60 and nested labels at x=55 — a 5px ragged left edge down the
   whole sidebar, on every page of every portal.

   It looked deliberate because the nested items DO get lined up: Radzen
   indents them with padding-left: 48px and a margin-left: -36px on the icon
   pulls them back flush. That cancellation aligns the ICONS. Nothing was
   aligning the labels, because nothing fixed the width between them.

   24px matches the wider of the two, so top-level rows do not move. */
.rz-sidebar .rz-navigation-item-link .rzi,
.rz-sidebar .rz-navigation-item-link .rz-icon,
.rz-sidebar .rz-navigation-item-link .material-icons {
    width: 24px;
    flex-shrink: 0;
    text-align: center;
}

/* Headings use the display font (Space Grotesk); body stays Inter. */
h1, h2, h3,
.rz-text-h1, .rz-text-h2, .rz-text-h3,
.rz-text-display-h1, .rz-text-display-h2, .rz-text-display-h3,
.flexops-portal-hero h1,
.flexops h1, .flexops h2, .flexops h3 {
  font-family: var(--flexops-font-display);
  letter-spacing: -0.015em;
}

/* Density (compact): the Radzen grid variables in flexops-tokens.css do the
   heavy lifting; these element-level rules reach what the vars can't —
   the data-cell padding/min-height floor, header padding, and sidebar rows. */
[data-density="compact"] .rz-datatable tbody td {
  padding-block: 0.15rem;
}
[data-density="compact"] .rz-datatable .rz-cell-data {
  min-height: 20px;
}
[data-density="compact"] .rz-datatable thead th {
  padding-block: 0.25rem;
}
[data-density="compact"] .rz-panel-menu .rz-navigation-item-link {
  padding-block: 0.2rem;
  min-height: 26px;
}

/* =====================================================================
 * Theme toggle button + dark-mode chrome fixes
 * ===================================================================== */
/* Shared header icon-button (theme toggle, density toggle, etc.) */
.flexops-iconbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: var(--flexops-radius-md);
  color: var(--flexops-fg-tertiary);
  cursor: pointer;
  transition: background-color var(--flexops-transition-fast), color var(--flexops-transition-fast);
}
.flexops-iconbtn:hover {
  background: var(--flexops-bg-tertiary);
  color: var(--flexops-fg-primary);
}
.flexops-iconbtn:focus-visible {
  outline: none;
  box-shadow: var(--flexops-shadow-focus);
}

/* Sidebar did not flip with the rest of the chrome in the spike — pin it to
 * the dark surface tokens so it tracks the theme. */
[data-theme="dark"] .rz-sidebar {
  background-color: var(--flexops-bg-primary);
  border-color: var(--flexops-border-secondary);
}
[data-theme="dark"] .rz-sidebar .rz-navigation-item-link,
[data-theme="dark"] .rz-sidebar .rz-navigation-item-text {
  color: var(--flexops-text-secondary);
}


/* =================================================================
   RADZEN DATAGRID  (.rz-data-grid / .rz-grid-table)

   The grid styling above targets .rz-datatable — the OLD RadzenDataTable
   component. Every portal uses RadzenDataGrid, which renders .rz-data-grid >
   .rz-grid-table, and only its `thead th` was ever pinned. So all that header/
   row/border work never reached a single grid on screen: 103 grids across the
   four portals have been rendering Radzen's Material defaults.

   That is what makes the screens read as an admin tool — dense rows, gridlines
   on every cell, and a filter row showing "*A*" as the operator.

   Same tokens as .fx-table, so a plain table and a DataGrid look identical.
   ================================================================= */

.rz-data-grid {
  border: 1px solid var(--color-border-secondary);
  border-radius: var(--radius-lg);
  overflow: clip;
  box-shadow: var(--shadow-xs);
}

/* Header. The background/colour is already pinned further up (dark-mode fix);
   this is the type and rhythm. */
/* Type comes from the --rz-grid-header-* variables in §1 so the th and its
   .rz-column-title label can never disagree; only rhythm is set here. */
.rz-data-grid .rz-grid-table thead th {
  padding: 0.75rem 1rem;
  font-size: var(--text-xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: normal;
}

.rz-data-grid .rz-grid-table tbody td {
  padding: 0.875rem 1rem;
  font-size: 0.875rem;
  color: var(--color-text-secondary);
  border-bottom: 1px solid var(--color-border-secondary);
}

/* Deliberately NOT var-driven, and the density toggle deliberately does not override it.
   Grid cell density is a per-screen decision, not a user preference: Ship Manager's
   operator queues are always dense (.data-grid.operator-grid), report and config grids
   are always comfortable. The toggle still thins grid rows via .rz-cell-data min-height
   below — ~4 more rows per 900px — which is the part that works and is enough.
   Guarded by DensityToggleTests. */

.rz-data-grid .rz-grid-table tbody tr:hover td {
  background: var(--color-bg-secondary);
}

.rz-data-grid .rz-grid-table tbody tr:last-child td {
  border-bottom: 0;
}

/* Vertical gridlines are the single biggest "spreadsheet, not product" signal.
   Rows read as records; cells boxed in borders read as a database dump.
   Horizontal rules only. */
.rz-grid-table.rz-grid-gridlines-both > thead > tr > th:not(:last-child),
.rz-grid-table.rz-grid-gridlines-both > tbody > tr > td:not(:last-child),
.rz-grid-table.rz-grid-gridlines-both > tfoot > tr > td:not(:last-child) {
  border-inline-end: 0;
}

/* ---- Filter row ----
   Every filterable column renders its operator as the literal text "*A*", which is
   the loudest "this is a debug tool" signal on the screens. Radzen ships a proper
   filter icon in the button and then hides it, showing the glyph instead:

       <button class="rz-filter-button" title="Contains">
         <i class="rzi">filter_alt</i>*A*
       </button>

   The glyph is a BARE TEXT NODE — no element wraps it — so no class selector can
   reach it. font-size:0 on the button is the only way to suppress it; the icon gets
   its size back explicitly. The operator stays discoverable via the button's title
   ("Contains"), and filtering itself is untouched.

   Both rules need .rz-cell-filter + .rz-button to outrank Radzen's own
   `.rz-filter-button .rzi { display: none }` and the .rz-button-md font sizing —
   at equal specificity they lose and this silently does nothing. */
.rz-data-grid .rz-cell-filter .rz-filter-button.rz-button {
  font-size: 0;
}

.rz-data-grid .rz-cell-filter .rz-filter-button.rz-button .rzi {
  display: inline-flex;
  font-size: 1rem;
  color: var(--color-text-tertiary);
}

.rz-cell-filter {
  padding: 0.5rem 0.75rem;
}

.rz-cell-filter .rz-textbox,
.rz-cell-filter .rz-datepicker,
.rz-cell-filter .rz-dropdown {
  font-size: var(--text-sm);
}

/* ---- Pager ---- */
.rz-data-grid .rz-pager {
  border-top: 1px solid var(--color-border-secondary);
  background: var(--color-bg-primary);
  padding: 0.75rem 1rem;
}

.rz-data-grid .rz-pager .rz-pager-element {
  border-radius: var(--radius-md);
  color: var(--color-text-secondary);
  font-weight: var(--font-weight-medium);
}

.rz-data-grid .rz-pager .rz-pager-page.rz-state-active {
  background: var(--color-bg-secondary);
  color: var(--color-text-primary);
  font-weight: var(--font-weight-semibold);
}

.rz-data-grid .rz-pager .rz-pager-summary {
  color: var(--color-text-tertiary);
  font-size: var(--text-sm);
}


/* -----------------------------------------------------------------
   Column widths.

   Pages pin every column in pixels — 580 Width="NNNpx" attributes across 66
   files. Addresses.razor sums to 1380px inside a 1265px container. With
   table-layout:fixed those widths are law, so headers truncate to "Compa...",
   "Validati...", "A" and the grid scrolls sideways.

   Treat them as hints instead: auto layout fits the table to its container and
   sizes columns to their content. Address Book goes 1380px -> 1265px (exactly
   fits) and the headers read "Company", "Validation", "Actions".

   Wide grids still scroll — Pricing Plans genuinely needs 1739px for 14 columns
   — but with readable headers rather than truncated ones. Auto layout fixes
   truncation, not overflow.

   EXCLUDES grids with frozen columns. Radzen positions those with sticky offsets
   derived from the column widths, so changing the widths can misalign them.
   :not(:has()) means a browser without :has() drops the rule entirely and falls
   back to today's behaviour — it fails safe. Verified: Shipped Orders (15 frozen
   cells) keeps table-layout:fixed and its cells stay sticky at left:0.
   ----------------------------------------------------------------- */
.rz-data-grid:not(:has(.rz-frozen-cell)) .rz-grid-table {
  table-layout: auto;
  width: 100%;
}

.rz-data-grid:not(:has(.rz-frozen-cell)) .rz-grid-table col {
  width: auto;
}


/* =================================================================
   29. BADGES — RadzenBadge -> Untitled UI pill
   Radzen ships solid, saturated fills that read as alert chips sitting
   next to our own .badge-* pills (flexops-portal-base.css). There are
   560 BadgeStyle usages across the portals and Ship Manager, so doing
   it here is markup-free — every RadzenBadge picks it up with no .razor
   edits and no per-portal duplication.

   Mapped by semantic hue, not by Radzen's name:
     Success -> success       Warning -> warning
     Danger/Error -> error    Info -> blue
     Primary -> brand         Secondary/Light -> gray

   Deliberately NOT restyled: .rz-badge-dark (zero usages) and
   .rz-badge-pill (a shape modifier — the base rule is already a pill).

   Dark mode follows the rationale in flexops-portal-base.css: the light
   -50 fills read as bright chips on a dark surface, so tint the -500 and
   lift the text. These pair with the .badge-* rules there — change both
   together or the two badge systems drift apart.
   ----------------------------------------------------------------- */
.rz-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  /* Same stretch opt-out as .badge — a badge dropped straight into a
     RadzenStack column would otherwise inflate to the full card width. */
  width: fit-content;
  padding: 2px 10px;
  border-radius: var(--radius-full);
  font-size: 0.75rem;
  font-weight: var(--font-weight-medium);
  line-height: 1.125rem;
  /* Radzen's base rule sets text-transform:uppercase and
     letter-spacing:0.02rem. Same box as .badge-*, but the text metrics
     differ, so the identical word rendered wider here than in a
     hand-rolled pill. Untitled UI badges are never uppercased — reset
     both so the two systems are genuinely interchangeable. */
  text-transform: none;
  letter-spacing: normal;
}

.rz-badge-success {
  background: var(--color-bg-success-primary);
  color: var(--color-success-700);
  border: 1px solid var(--color-success-200);
}

.rz-badge-warning {
  background: var(--color-bg-warning-primary);
  color: var(--color-warning-700);
  border: 1px solid var(--color-warning-200);
}

.rz-badge-danger,
.rz-badge-error {
  background: var(--color-bg-error-primary);
  color: var(--color-error-700);
  border: 1px solid var(--color-error-200);
}

.rz-badge-info {
  background: var(--color-blue-50);
  color: var(--color-blue-700);
  border: 1px solid var(--color-blue-100);
}

.rz-badge-primary {
  background: var(--color-bg-brand-primary);
  color: var(--color-brand-700);
  border: 1px solid var(--color-brand-200);
}

.rz-badge-secondary,
.rz-badge-light {
  background: var(--color-bg-secondary);
  color: var(--color-text-secondary);
  border: 1px solid var(--color-border-secondary);
}

[data-theme="dark"] .rz-badge-success {
  background: color-mix(in srgb, var(--flexops-success-500) 20%, transparent);
  color: var(--flexops-success-300);
  border-color: color-mix(in srgb, var(--flexops-success-500) 40%, transparent);
}

[data-theme="dark"] .rz-badge-warning {
  background: color-mix(in srgb, var(--flexops-warning-500) 20%, transparent);
  color: var(--flexops-warning-300);
  border-color: color-mix(in srgb, var(--flexops-warning-500) 40%, transparent);
}

[data-theme="dark"] .rz-badge-danger,
[data-theme="dark"] .rz-badge-error {
  background: color-mix(in srgb, var(--flexops-error-500) 20%, transparent);
  color: var(--flexops-error-300);
  border-color: color-mix(in srgb, var(--flexops-error-500) 40%, transparent);
}

[data-theme="dark"] .rz-badge-primary {
  background: color-mix(in srgb, var(--flexops-brand-500) 22%, transparent);
  color: var(--flexops-brand-300);
  border-color: color-mix(in srgb, var(--flexops-brand-500) 40%, transparent);
}

/* Text stop is blue-100, not the -300 the other hues use: the blue ramp
   is partial (50/100/500/600/700) and has no -300. Referencing a missing
   var would fail silently and leave the stock Radzen fill in place. */
[data-theme="dark"] .rz-badge-info {
  background: color-mix(in srgb, var(--flexops-blue-500) 20%, transparent);
  color: var(--flexops-blue-100);
  border-color: color-mix(in srgb, var(--flexops-blue-500) 40%, transparent);
}


/* =================================================================
   30. GRID HEADERS — wrap long titles instead of clipping them
   Radzen renders a header label as TWO nested spans, and BOTH clip:

     .rz-column-title         { white-space:nowrap; overflow:hidden;
                                text-overflow:ellipsis }
     .rz-column-title-content { white-space:nowrap; overflow:hidden;
                                text-overflow:ellipsis }

   Relaxing only the outer one changes nothing, because the inner span
   holds the text — which is why this rule names both.

   When a column has an explicit Width, or the table keeps
   table-layout:fixed (every grid with a frozen column does — §28
   excludes them from auto layout), a title wider than its column is
   silently cut to an ellipsis. Reported on Ship Manager's New Orders
   and the API Portal's API Keys grid; operator grids feel it soonest
   because their headers are uppercased, and capitals are wider.

   Wrapping is the fix that does NOT touch column widths. That matters:
   Radzen derives the sticky offsets for frozen columns from those
   widths, which is the whole reason §28 leaves fixed layout alone. A
   two-line header reads; a truncated one does not.
   ----------------------------------------------------------------- */
.rz-data-grid .rz-grid-table thead th .rz-column-title,
.rz-data-grid .rz-grid-table thead th .rz-column-title-content {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  line-height: 1.25;
}


/* -----------------------------------------------------------------

 * Data-grid empty state — centre it on what the operator can SEE.

 *

 * The empty message lives in a colspan <td>, so it inherits the TABLE's

 * width. When a grid scrolls horizontally that is much wider than the

 * scroll port: measured on Ship Manager's Active Shipments at 1440px, the

 * table was 1532px inside a 1074px port, so `align-items: center` centred

 * "No active shipments found" at x=1099 when the visible centre was x=870.

 * The icon and title sat out to the right under the last column, looking

 * like a rendering fault rather than an empty result.

 *

 * Pin the panel to the scroll port instead: `sticky/left: 0` anchors it to

 * the left edge of the visible area, and 100cqw sizes it to the port rather

 * than the table, so its own centring lands where the eye expects. The

 * container is declared behind :has() so this only ever applies to a grid

 * that is actually empty — a populated grid keeps stock Radzen layout.

 * ----------------------------------------------------------------- */

.rz-data-grid-data:has(.rz-datatable-emptymessage) {

    container-type: inline-size;

}



.rz-data-grid .rz-grid-table td.rz-datatable-emptymessage:has(.flexops-empty-state) {
    /* The cell's own 8px/12px padding offsets the sticky origin, leaving the
       panel 12px right of the port. EmptyState brings its own 48px/24px. */
    padding: 0;
}

.rz-data-grid .rz-grid-table td.rz-datatable-emptymessage .flexops-empty-state {

    position: sticky;

    left: 0;

    width: 100cqw;

}

