/* ─────────────────────────────────────────────────────────────────────────────────────────────
   theme.css — the Portal's whole look, and its one re-branding surface.

   The look is a console: a near-black canvas, surfaces one step up, hairline borders, indigo only
   for "selected / primary", and every other colour a low-alpha tint — a saturated fill on a dark
   field is a flashlight. Light mode is the same design on a pale canvas.

   Three layers, in this order:
     1. Tokens      — every colour, radius and font, for light and for dark. Re-brand here.
     2. Frameworks  — Bootstrap 5.3 (inside AdminLTE 4) and Radzen are pointed at the tokens, so
                      pages keep their ordinary Bootstrap classes and need no change to fit.
     3. Components  — the shell, the sign-in card, the page header, stat tiles, and the pieces the
                      frameworks leave looking like a different product.

   Loaded after AdminLTE and Radzen (App.razor, Pages/Shared/_Layout.cshtml), so equal specificity
   wins. `!important` only where AdminLTE or Radzen set the same property with it, or inline.
   ───────────────────────────────────────────────────────────────────────────────────────────── */

/* ── 1. Tokens ────────────────────────────────────────────────────────────────────────────────
   ImportAlert's colours: navy for selected and primary, amber as the accent (the bell in the mark).
   The name and mark are in Components/Shared/Theme/PortalBrand.cs. */

:root,
[data-bs-theme="light"] {
    --app-canvas: #f3f4f8;
    --app-surface: #ffffff;
    --app-surface-2: #f6f7fb;
    --app-field: #ffffff;
    --app-line: rgba(15, 23, 42, .09);
    --app-line-strong: rgba(15, 23, 42, .16);
    /* The outline of an unticked checkbox, radio or switch: a control has to be findable before it is ticked. */
    --app-check-border: rgba(15, 23, 42, .38);
    --app-hover: rgba(15, 23, 42, .04);

    --app-heading: #0f172a;
    --app-ink: #1e293b;
    --app-ink-2: #334155;
    --app-muted: #64748b;
    --app-faint: #94a3b8;

    --app-primary: #0e3a5b;
    --app-primary-rgb: 14, 58, 91;
    --app-primary-hi: #17557f;
    --app-primary-hover: #0a2c45;
    --app-primary-soft: rgba(43, 131, 196, .12);
    --app-primary-ink: #0e3a5b;
    --app-accent: #f2a31b;

    /* Status colours are tints: a soft fill and a readable ink. */
    --app-ok: #059669;       --app-ok-soft: rgba(16, 185, 129, .12);  --app-ok-ink: #047857;
    --app-warn: #d97706;     --app-warn-soft: rgba(245, 158, 11, .14); --app-warn-ink: #b45309;
    --app-bad: #e11d48;      --app-bad-soft: rgba(244, 63, 94, .12);   --app-bad-ink: #be123c;
    --app-info: #0284c7;     --app-info-soft: rgba(14, 165, 233, .12); --app-info-ink: #0369a1;

    --app-shadow: 0 1px 2px rgba(15, 23, 42, .04), 0 8px 24px -12px rgba(15, 23, 42, .12);
    --app-ring: 0 0 0 3px rgba(43, 131, 196, .3);
    --app-radius: 12px;
    --app-radius-sm: 9px;

    /* The system UI face: Segoe UI on Windows, SF on Apple, Roboto on Android. Nothing to download,
       which is the rule for this template (no CDN; see App.razor). To use a web font, vendor it
       under wwwroot/lib and put it first here. */
    --app-font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

    /* The sidebar is dark in both modes: it is the frame, not the content. */
    --app-sidebar: #12151d;
}

[data-bs-theme="dark"] {
    --app-canvas: #171b24;
    --app-surface: #1e2330;
    --app-surface-2: #262c3b;
    --app-field: #141824;
    --app-line: rgba(255, 255, 255, .08);
    --app-line-strong: rgba(255, 255, 255, .14);
    --app-check-border: rgba(255, 255, 255, .34);
    --app-hover: rgba(255, 255, 255, .04);

    /* Text tops out below pure white: full white on a dark field glares. */
    --app-heading: #ffffff;
    --app-ink: #e2e8f0;
    --app-ink-2: #b6c0d0;
    --app-muted: #94a3b8;
    --app-faint: #64708a;

    /* Navy on a near-black surface disappears, so dark mode lifts the primary pair a few steps. */
    --app-primary: #1f6fa8;
    --app-primary-rgb: 31, 111, 168;
    --app-primary-hi: #2b83c4;
    --app-primary-soft: rgba(43, 131, 196, .18);
    --app-primary-ink: #8ec1ea;
    --app-primary-hover: #2b83c4;

    --app-ok: #34d399;   --app-ok-soft: rgba(16, 185, 129, .14);  --app-ok-ink: #6ee7b7;
    --app-warn: #fbbf24; --app-warn-soft: rgba(245, 158, 11, .14); --app-warn-ink: #fcd34d;
    --app-bad: #fb7185;  --app-bad-soft: rgba(244, 63, 94, .14);   --app-bad-ink: #fda4af;
    --app-info: #38bdf8; --app-info-soft: rgba(14, 165, 233, .14); --app-info-ink: #7dd3fc;

    --app-shadow: none;
    --app-ring: 0 0 0 3px rgba(43, 131, 196, .35);
}

/* ── 2. Frameworks follow the tokens ─────────────────────────────────────────────────────────── */

/* Bootstrap 5.3 declares these on :root and on each [data-bs-theme]; redeclaring them on the same
   selectors, later, is all it takes for every Bootstrap and AdminLTE rule to follow. */
:root,
[data-bs-theme="light"],
[data-bs-theme="dark"] {
    color-scheme: light;
    accent-color: var(--app-primary-hi);

    --bs-font-sans-serif: var(--app-font);
    --bs-body-font-family: var(--app-font);
    --bs-body-font-size: .875rem;
    --bs-body-bg: var(--app-canvas);
    --bs-body-color: var(--app-ink);
    --bs-emphasis-color: var(--app-heading);
    --bs-heading-color: var(--app-heading);
    --bs-secondary-color: var(--app-muted);
    --bs-tertiary-color: var(--app-faint);
    --bs-secondary-bg: var(--app-surface-2);
    --bs-tertiary-bg: var(--app-surface-2);
    --bs-border-color: var(--app-line);
    --bs-border-color-translucent: var(--app-line);
    --bs-border-radius: var(--app-radius-sm);
    --bs-border-radius-lg: var(--app-radius);

    --bs-primary: var(--app-primary);
    --bs-primary-rgb: var(--app-primary-rgb);
    --bs-link-color: var(--app-primary-ink);
    --bs-link-color-rgb: var(--app-primary-rgb);
    --bs-link-hover-color: var(--app-primary-hi);
    --bs-focus-ring-color: rgba(43, 131, 196, .3);

    --bs-success-bg-subtle: var(--app-ok-soft);     --bs-success-text-emphasis: var(--app-ok-ink);     --bs-success-border-subtle: var(--app-ok-soft);
    --bs-warning-bg-subtle: var(--app-warn-soft);   --bs-warning-text-emphasis: var(--app-warn-ink);   --bs-warning-border-subtle: var(--app-warn-soft);
    --bs-danger-bg-subtle: var(--app-bad-soft);     --bs-danger-text-emphasis: var(--app-bad-ink);     --bs-danger-border-subtle: var(--app-bad-soft);
    --bs-info-bg-subtle: var(--app-info-soft);      --bs-info-text-emphasis: var(--app-info-ink);      --bs-info-border-subtle: var(--app-info-soft);
    --bs-primary-bg-subtle: var(--app-primary-soft); --bs-primary-text-emphasis: var(--app-primary-ink); --bs-primary-border-subtle: var(--app-primary-soft);
}

[data-bs-theme="dark"] { color-scheme: dark; }

/* Radzen reads these at run time, so mapping them is enough: no component selectors to keep in step
   with a Radzen upgrade. Use Radzen's `standard` theme, not `material`, whose elevation and ripple
   fight the flat shell. */
:root,
[data-bs-theme="light"],
[data-bs-theme="dark"] {
    --rz-primary: var(--app-primary);
    --rz-primary-dark: var(--app-primary-hover);
    --rz-primary-light: var(--app-primary-soft);
    --rz-primary-lighter: var(--app-primary-soft);

    --rz-text-font-family: var(--app-font);
    --rz-text-color: var(--app-ink);
    --rz-text-secondary-color: var(--app-muted);
    --rz-text-title-color: var(--app-heading);
    --rz-base-background-color: var(--app-surface);
    --rz-body-background-color: var(--app-canvas);
    --rz-panel-background-color: var(--app-surface);

    --rz-border-radius: var(--app-radius-sm);
    --rz-border-color: var(--app-line);

    --rz-input-background-color: var(--app-field);
    --rz-input-color: var(--app-ink);
    --rz-input-border: 1px solid var(--app-line-strong);
    --rz-input-hover-border: 1px solid var(--app-primary-hi);
    --rz-input-focus-border: 1px solid var(--app-primary-hi);

    /* Grids — made to read like a .table (section 8): hairline rows, no column rules, no stripes,
       uppercase muted headers on surface-2. Names checked against Radzen 8.4.0's standard themes. */
    --rz-grid-background-color: var(--app-surface);
    --rz-grid-border: 1px solid var(--app-line);
    --rz-grid-shadow: none;

    /* Stripes: transparent, so a row carries no colour of its own and data-row-state (section 12)
       can give it one. Radzen stripes by default and paints the cells, not the row. */
    --rz-grid-stripe-background-color: transparent;
    --rz-grid-stripe-odd-background-color: transparent;
    --rz-grid-frozen-stripe-background-color: var(--app-surface);
    --rz-grid-frozen-stripe-odd-background-color: var(--app-surface);
    --rz-grid-frozen-cell-background-color: var(--app-surface);

    /* Rows: a hairline under each; no vertical rule between columns. */
    --rz-grid-cell-padding: .6rem .9rem;
    --rz-grid-cell-font-size: .875rem;
    --rz-grid-cell-color: var(--app-ink);
    --rz-grid-bottom-cell-border: 1px solid var(--app-line);
    --rz-grid-right-cell-border: none;
    --rz-grid-hover-background-color: var(--app-hover);
    --rz-grid-hover-color: var(--app-ink);
    --rz-grid-selected-background-color: var(--app-primary-soft);
    --rz-grid-selected-color: var(--app-heading);

    /* Headers: the th has no padding of its own; the title inside it carries the inline inset, so
       it lines up with the cell text below it. */
    --rz-grid-header-background-color: var(--app-surface-2);
    --rz-grid-header-sorted-background-color: var(--app-surface-2);
    --rz-grid-header-color: var(--app-muted);
    --rz-grid-header-font-size: .69rem;
    --rz-grid-header-font-weight: 650;
    --rz-grid-header-text-transform: uppercase;
    --rz-grid-header-cell-padding: .6rem 0;
    --rz-grid-header-title-padding-inline: .9rem;
    --rz-grid-header-cell-border: none;
    --rz-grid-header-cell-border-bottom: 1px solid var(--app-line);
    --rz-grid-sort-icon-color: var(--app-faint);

    /* The expand column of a grid with a detail template: wide enough for its chevron, and no
       padding — Radzen centres the toggle itself. Forcing cell padding onto it is what squeezed the
       chevron out and printed "…" in its header. */
    --rz-grid-column-icon-width: 2.75rem;
    --rz-grid-column-icon-padding: 0;

    /* The expanded detail row: a darker inset under the row it opens. */
    --rz-grid-detail-template-background-color: var(--app-surface-2);
    --rz-grid-detail-template-border: 0;
    --rz-grid-detail-template-border-radius: 0;
    --rz-grid-detail-template-padding: 1rem 1.25rem;

    --rz-grid-loading-indicator-background-color: var(--app-surface);
    --rz-grid-loading-indicator-color: var(--app-primary-hi);

    /* Dialogs: a card, lifted. The title bar is the card header: transparent, hairline under it. */
    --rz-dialog-background-color: var(--app-surface);
    --rz-dialog-border-radius: 14px;
    --rz-dialog-shadow: 0 30px 80px -20px rgba(0, 0, 0, .55);
    /* Solid: Radzen fades the mask with its own opacity (.5), so an rgba here dims only half as much
       as it says. */
    --rz-dialog-mask-background-color: rgb(8, 10, 16);
    --rz-dialog-title-background-color: transparent;
    --rz-dialog-title-color: var(--app-heading);
    --rz-dialog-title-font-size: 1rem;
    --rz-dialog-title-font-weight: 650;
    --rz-dialog-title-border: 1px solid var(--app-line);
    --rz-dialog-close-color: var(--app-muted);
    --rz-dialog-close-hover-color: var(--app-heading);

    /* Notifications: the tint on an opaque surface. The tints are translucent, and a notification
       floats over whatever page is behind it — on a transparent container the page shows through. */
    --rz-notification-container-background-color: var(--app-surface);
    --rz-notification-border-radius: 12px;
    --rz-notification-shadow: 0 18px 44px -14px rgba(0, 0, 0, .45);
    --rz-notification-success-background-color: var(--app-ok-soft);
    --rz-notification-success-color: var(--app-ok-ink);
    --rz-notification-success-icon-color: var(--app-ok-ink);
    --rz-notification-warning-background-color: var(--app-warn-soft);
    --rz-notification-warning-color: var(--app-warn-ink);
    --rz-notification-warning-icon-color: var(--app-warn-ink);
    --rz-notification-error-background-color: var(--app-bad-soft);
    --rz-notification-error-color: var(--app-bad-ink);
    --rz-notification-error-icon-color: var(--app-bad-ink);
    --rz-notification-info-background-color: var(--app-info-soft);
    --rz-notification-info-color: var(--app-info-ink);
    --rz-notification-info-icon-color: var(--app-info-ink);
}

/* ── 3. Base ─────────────────────────────────────────────────────────────────────────────────── */

body {
    font-family: var(--app-font);
    font-size: .875rem;
    -webkit-font-smoothing: antialiased;
    background-color: var(--app-canvas);
    color: var(--app-ink);
}

.bg-body-tertiary,
.bg-body-secondary { background-color: var(--app-canvas) !important; }

.bg-body { background-color: var(--app-surface) !important; }

h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
    color: var(--app-heading);
    font-weight: 650;
    letter-spacing: -.01em;
}

/* The router focuses each page's h1 after a navigation (FocusOnNavigate), so a screen reader
   announces the new page. The browser then drew a focus box round the heading: a white rectangle
   on every page. The heading is not a control; it needs the focus, not the box. */
h1:focus { outline: none; }

a { color: var(--app-primary-ink); }
a:hover { color: var(--app-primary-hi); }

hr { border-color: var(--app-line); opacity: 1; }

code {
    padding: .12em .4em;
    border-radius: 5px;
    background: var(--app-primary-soft);
    color: var(--app-primary-ink);
    font-size: .82em;
}

pre {
    padding: .75rem 1rem;
    border: 1px solid var(--app-line);
    border-radius: 10px;
    background: var(--app-field);
    color: var(--app-ink-2);
}

pre code { padding: 0; background: none; color: inherit; }

::selection { background: rgba(43, 131, 196, .35); }

.text-body-secondary, .text-muted, .text-secondary { color: var(--app-muted) !important; }
.text-success { color: var(--app-ok-ink) !important; }
.text-warning { color: var(--app-warn-ink) !important; }
.text-danger  { color: var(--app-bad-ink) !important; }
.text-info    { color: var(--app-info-ink) !important; }
.text-primary { color: var(--app-primary-ink) !important; }

/* ── 4. Shell (AdminLTE 4: .app-header · .app-sidebar · .app-main · .app-footer) ────────────── */

.app-main { background: var(--app-canvas); }

/* AdminLTE's fixed layout makes .app-main overflow:auto, but the document is what scrolls here, so
   .app-main became a scroll container that never scrolled — and position:sticky inside it (the save
   bar, the access profile summary) never stuck. clip still keeps wide content from pushing the page
   sideways, without being a scroll container. Three classes deep to outweigh AdminLTE's
   .sidebar-expand-*.layout-fixed .app-main; this file loads after it, so the tie goes to this rule. */
.layout-fixed .app-wrapper > .app-main { overflow-x: clip; overflow-y: visible; }

.app-content > .container-fluid { padding: 1.5rem 1.75rem 2rem; }

@media (max-width: 767.98px) {
    .app-content > .container-fluid { padding: 1rem; }
}

/* Top bar: sticky, translucent, with the page scrolling under a blur. */
.app-header.navbar {
    position: sticky;
    top: 0;
    z-index: 1030;
    min-height: 57px;
    border-bottom: 1px solid var(--app-line);
    background: color-mix(in srgb, var(--app-canvas) 85%, transparent) !important;
    backdrop-filter: saturate(140%) blur(10px);
    box-shadow: none;
}

.app-icon-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0 !important;
    border: 0;
    border-radius: 10px;
    background: transparent;
    color: var(--app-muted) !important;
    font-size: 1.05rem;
    transition: background-color .15s ease, color .15s ease;
}

.app-icon-btn:hover,
.show > .app-icon-btn {
    background: var(--app-hover);
    color: var(--app-heading) !important;
}

.app-icon-btn:focus-visible { outline: none; box-shadow: var(--app-ring); }

/* The count on the bell. */
.app-count {
    position: absolute;
    top: 1px;
    right: -1px;
    min-width: 17px;
    height: 17px;
    padding: 0 4px;
    border: 2px solid var(--app-canvas);
    border-radius: 999px;
    background: #f43f5e;
    color: #fff;
    font-size: .56rem;
    font-weight: 700;
    line-height: 13px;
    text-align: center;
}

.app-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--app-primary) 0%, var(--app-accent) 100%);
    color: #fff;
    font-size: .8rem;
    font-weight: 700;
    box-shadow: 0 0 0 2px var(--app-canvas), 0 0 0 3px rgba(43, 131, 196, .35);
}

.app-avatar--lg { width: 40px; height: 40px; font-size: .9rem; box-shadow: none; }

.app-avatar-btn {
    padding: 0;
    margin-left: .35rem;
    border: 0;
    border-radius: 50%;
    background: none;
}

.app-avatar-btn:focus-visible { outline: none; box-shadow: var(--app-ring); }

/* Sidebar: the dark frame in both modes (it carries data-bs-theme="dark"). */
.app-sidebar {
    border-right: 1px solid rgba(255, 255, 255, .06);
    background: var(--app-sidebar) !important;
    box-shadow: none !important;
}

.app-sidebar .sidebar-brand {
    height: 57px;
    border-bottom: 1px solid rgba(255, 255, 255, .06);
}

.app-sidebar .brand-link {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: 0 1rem;
    color: #fff !important;
    text-decoration: none;
}

.app-brand-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 34px;
    height: 34px;
    border-radius: 10px;
    background: linear-gradient(135deg, var(--app-primary) 0%, var(--app-accent) 100%);
    color: #fff;
    font-size: 1rem;
    box-shadow: 0 4px 12px -4px rgba(var(--app-primary-rgb), .6);
}

.app-sidebar .brand-text {
    margin: 0 !important;
    color: #fff !important;
    font-size: 1.05rem;
    font-weight: 700 !important;
    letter-spacing: -.01em;
    white-space: nowrap;
}

.app-brand-tag {
    padding: .2rem .45rem;
    border-radius: 6px;
    background: rgba(255, 255, 255, .07);
    color: #94a3b8;
    font-size: .58rem;
    font-weight: 700;
    letter-spacing: .12em;
    text-transform: uppercase;
}

/* The environment, outside Production: amber, so a staging window is never mistaken for the live one. */
.app-brand-tag--env {
    background: rgba(245, 158, 11, .18);
    color: #fbbf24;
}

.app-sidebar .sidebar-wrapper { padding: .5rem .65rem 1.25rem; }

.app-sidebar .sidebar-menu .nav-header {
    padding: 1.1rem .75rem .4rem !important;
    background: transparent !important;
    color: #5c677b !important;
    font-size: .64rem !important;
    font-weight: 650;
    letter-spacing: .11em;
    text-transform: uppercase;
}

.app-sidebar .sidebar-menu .nav-link {
    display: flex;
    align-items: center;
    margin: 1px 0;
    padding: .55rem .75rem;
    border-radius: 9px;
    color: #aab3c5 !important;
    font-size: .875rem;
    font-weight: 500;
    transition: background-color .15s ease, color .15s ease;
}

.app-sidebar .sidebar-menu .nav-link > .nav-icon {
    width: 1.5rem;
    margin-right: .6rem !important;
    color: #6f7a8e;
    font-size: 1rem;
    text-align: center;
    transition: color .15s ease;
}

.app-sidebar .sidebar-menu .nav-link > p { margin: 0; white-space: nowrap; }

.app-sidebar .sidebar-menu .nav-link:hover {
    background: rgba(255, 255, 255, .04) !important;
    color: #e2e8f0 !important;
}

.app-sidebar .sidebar-menu .nav-link:hover > .nav-icon { color: #b6c0d0; }

/* The current page: an indigo wash and a hairline, not a saturated slab. */
.app-sidebar .sidebar-menu .nav-link.active {
    background: rgba(43, 131, 196, .16) !important;
    box-shadow: inset 0 0 0 1px rgba(43, 131, 196, .35) !important;
    color: #fff !important;
    font-weight: 600;
}

.app-sidebar .sidebar-menu .nav-link.active > .nav-icon { color: #a5b4fc; }

.app-sidebar .sidebar-menu .nav-link:focus-visible { outline: none; box-shadow: var(--app-ring) !important; }

.app-footer {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: .5rem 1rem;
    padding: 1rem 1.75rem 1.25rem;
    border-top: 1px solid var(--app-line);
    background: var(--app-canvas);
    color: var(--app-faint);
    font-size: .75rem;
}

/* ── 5. Page header (PageTitleBar) ───────────────────────────────────────────────────────────── */

.app-page-head { margin-bottom: 1.5rem; }

.app-page-head__row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .75rem 1rem;
}

.app-page-head__text { min-width: 0; }

.app-page-title {
    margin: 0;
    color: var(--app-heading);
    font-size: 1.6rem;
    font-weight: 700;
    letter-spacing: -.02em;
    line-height: 1.2;
}

.app-page-sub {
    margin: .3rem 0 0;
    color: var(--app-muted);
    font-size: .9rem;
}

.app-page-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem;
}

.app-section-label {
    margin: 1.75rem 0 .75rem;
    color: var(--app-muted);
    font-size: .7rem;
    font-weight: 650;
    letter-spacing: .1em;
    text-transform: uppercase;
}

.app-section-label:first-child { margin-top: 0; }

/* The row between a page's heading and its table: the search on the left, filters and quieter
   actions on the right. Wraps under itself on a narrow screen. */
.app-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem .75rem;
    margin-bottom: 1rem;
}

.app-toolbar__end {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .5rem .75rem;
    margin-left: auto;
}

/* A search box with its icon inside the field, not in a grey add-on beside it. */
.app-search {
    position: relative;
    flex: 1 1 16rem;
    max-width: 24rem;
}

.app-search > .bi {
    position: absolute;
    top: 50%;
    left: .8rem;
    color: var(--app-faint);
    pointer-events: none;
    transform: translateY(-50%);
}

.app-search > .form-control { padding-left: 2.25rem; }

/* Search boxes use type=search for the keyboard and the clear button; the browser's own clear
   glyph is drawn in a fixed dark grey, so it takes the muted colour instead. */
.app-search > .form-control::-webkit-search-cancel-button { filter: grayscale(1) opacity(.6); }

/* ── 6. Cards ────────────────────────────────────────────────────────────────────────────────── */

.card {
    --bs-card-bg: var(--app-surface);
    --bs-card-color: var(--app-ink);
    --bs-card-cap-bg: transparent;
    --bs-card-border-color: var(--app-line);
    --bs-card-border-radius: var(--app-radius);
    --bs-card-inner-border-radius: calc(var(--app-radius) - 1px);
    border: 1px solid var(--app-line);
    box-shadow: var(--app-shadow);
}

.card.card-outline { border-top: 1px solid var(--app-line) !important; }

.card-header {
    padding: .9rem 1.1rem;
    border-bottom: 1px solid var(--app-line);
    color: var(--app-heading);
    font-weight: 650;
}

/* AdminLTE's clearfix on the header becomes a third flex item in a flex header, pulling a
   right-hand link back to the middle. */
.card-header.d-flex::after { content: none; }

.card-title { margin: 0; color: var(--app-heading); font-size: .95rem; font-weight: 650; }

.card-body { padding: 1.1rem; }

.card-footer {
    border-top: 1px solid var(--app-line);
    background: var(--app-hover);
}

/* ── 7. Stat tiles (StatTile) and the attention pill ─────────────────────────────────────────── */

.app-stat {
    display: flex;
    flex-direction: column;
    gap: .15rem;
    height: 100%;
    padding: 1rem 1.1rem 1.05rem;
    border: 1px solid var(--app-line);
    border-radius: var(--app-radius);
    background: var(--app-surface);
    box-shadow: var(--app-shadow);
    color: var(--app-ink);
    text-decoration: none;
    transition: border-color .15s ease, transform .15s ease;
}

a.app-stat:hover { border-color: rgba(43, 131, 196, .45); color: var(--app-ink); }
a.app-stat:focus-visible { outline: none; box-shadow: var(--app-ring); }

.app-stat__chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    margin-bottom: .55rem;
    border-radius: 8px;
    font-size: .95rem;
}

.app-stat__value {
    color: var(--app-heading);
    font-size: 1.75rem;
    font-weight: 700;
    letter-spacing: -.02em;
    line-height: 1.15;
    font-variant-numeric: tabular-nums;
}

.app-stat__label { color: var(--app-muted); font-size: .85rem; }

.app-stat__note { margin-top: .2rem; color: var(--app-faint); font-size: .75rem; }

.app-stat__trend { margin-top: .2rem; font-size: .78rem; font-weight: 600; }
.app-stat__trend--up { color: var(--app-ok-ink); }
.app-stat__trend--down { color: var(--app-bad-ink); }

/* Chip tints: one hue per tile, all at the same low alpha. */
.app-tint--indigo  { background: rgba(43, 131, 196, .16); color: #818cf8; }
.app-tint--teal    { background: rgba(20, 184, 166, .16); color: #2dd4bf; }
.app-tint--violet  { background: rgba(139, 92, 246, .16); color: #a78bfa; }
.app-tint--rose    { background: rgba(244, 63, 94, .16);  color: #fb7185; }
.app-tint--emerald { background: rgba(16, 185, 129, .16); color: #34d399; }
.app-tint--amber   { background: rgba(245, 158, 11, .16); color: #fbbf24; }
.app-tint--sky     { background: rgba(14, 165, 233, .16); color: #38bdf8; }

/* Skeleton tiles for the dashboard's first load: the tiles' own shape with shimmering bars where the
   chip, number and label will be, so the page does not jump when the figures land. */
.app-stat--skeleton { pointer-events: none; }

.app-sk {
    display: block;
    border-radius: 5px;
    background: linear-gradient(90deg, var(--app-hover) 25%, var(--app-line-strong) 50%, var(--app-hover) 75%);
    background-size: 200% 100%;
    animation: app-shimmer 1.4s ease-in-out infinite;
}

/* Each bar plus its margins is the height of the line it stands in for (value 32px, label 20px,
   note 21px with its gap), so the skeleton tile is the real tile's height and nothing moves. */
.app-sk--chip  { width: 30px; height: 30px; margin-bottom: .55rem; border-radius: 8px; }
.app-sk--value { width: 45%; height: 1.4rem; margin: .3rem 0; }
.app-sk--label { width: 60%; height: .65rem; margin: .3rem 0; }
.app-sk--note  { width: 40%; height: .55rem; margin: .48rem 0 .28rem; }

@keyframes app-shimmer {
    from { background-position: 200% 0; }
    to   { background-position: -200% 0; }
}

/* Quick actions: verbs, not destinations — "Add a user", not "Users". A tinted chip, the verb, what
   it does, and a chevron that arrives on hover. */
.app-actions { display: flex; flex-direction: column; gap: 2px; padding: .4rem; }

.app-action {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .55rem .6rem;
    border-radius: var(--app-radius-sm);
    color: var(--app-ink);
    text-decoration: none;
    transition: background-color .15s ease;
}

.app-action:hover { background: var(--app-hover); color: var(--app-ink); }
.app-action:focus-visible { outline: none; box-shadow: var(--app-ring); }

.app-action .app-stat__chip { flex: none; margin-bottom: 0; }

.app-action__body { display: flex; flex-direction: column; min-width: 0; }
.app-action__name { font-size: .875rem; font-weight: 600; }

/* Wraps rather than ellipsising: on a phone a one-line hint lost most of its sentence. */
.app-action__hint { color: var(--app-muted); font-size: .78rem; line-height: 1.35; }

.app-action__go {
    margin-left: auto;
    color: var(--app-muted);
    font-size: .8rem;
    opacity: 0;
    transform: translateX(-3px);
    transition: opacity .15s ease, transform .15s ease;
}

.app-action:hover .app-action__go,
.app-action:focus-visible .app-action__go { opacity: 1; transform: none; }

/* "Nothing needs attention" / "3 things need attention". */
.app-attention {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    padding: .5rem .95rem;
    border: 1px solid;
    border-radius: 999px;
    font-size: .85rem;
    font-weight: 600;
}

.app-attention--clear { border-color: rgba(52, 211, 153, .35); background: var(--app-ok-soft); color: var(--app-ok-ink); }
.app-attention--needs { border-color: rgba(251, 191, 36, .4); background: var(--app-warn-soft); color: var(--app-warn-ink); }

/* ── 7A. Tabs (TabStrip) and empty states (EmptyState) ──────────────────────────────────────── */

/* Underlined labels on a hairline, under the page heading. Scrolls sideways on a narrow screen
   rather than wrapping onto a second row. */
.app-tabs {
    display: flex;
    gap: .15rem;
    margin-top: 1.1rem;
    border-bottom: 1px solid var(--app-line);
    overflow-x: auto;
    scrollbar-width: none;
}

.app-tabs::-webkit-scrollbar { display: none; }

.app-tab {
    position: relative;
    flex: none;
    padding: .55rem .8rem .7rem;
    border: 0;
    border-radius: 8px 8px 0 0;
    background: transparent;
    color: var(--app-muted);
    font-size: .8125rem;
    font-weight: 550;
    white-space: nowrap;
    transition: color .15s ease, background-color .15s ease;
}

.app-tab:hover { background: var(--app-hover); color: var(--app-ink); }

.app-tab.active { color: var(--app-heading); }

.app-tab.active::after {
    content: "";
    position: absolute;
    right: .7rem;
    bottom: -1px;
    left: .7rem;
    height: 2px;
    border-radius: 2px 2px 0 0;
    background: var(--app-primary-hi);
}

.app-tab:focus-visible { outline: none; box-shadow: var(--app-ring); }

.app-tab__count {
    margin-left: .35rem;
    padding: .05rem .4rem;
    border-radius: 999px;
    background: var(--app-primary-soft);
    color: var(--app-primary-ink);
    font-size: .68rem;
    font-weight: 650;
}

/* Nothing here, still loading, or could not load. */
.app-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .35rem;
    padding: 3rem 1rem;
    color: var(--app-muted);
    text-align: center;
}

.app-empty--compact { padding: 1.5rem 1rem; }

.app-empty__icon { font-size: 2.25rem; line-height: 1; opacity: .35; }
.app-empty--compact .app-empty__icon { font-size: 1.5rem; }
.app-empty--error .app-empty__icon { color: var(--app-bad-ink); opacity: .8; }

.app-empty__spinner { color: var(--app-primary-hi); }

.app-empty__title { margin: .35rem 0 0; color: var(--app-ink-2); font-size: .9rem; font-weight: 600; }
.app-empty__hint { max-width: 32rem; margin: 0; font-size: .8rem; }
.app-empty__action { margin-top: .6rem; }

/* A whole page that is only an empty state: not found, access denied, an error. The icon is the
   page's picture here, so it is larger and not faded; its colour comes with it (text-danger …). */
.app-empty--page { justify-content: center; min-height: 60vh; }
.app-empty--page .app-empty__icon { font-size: 3.25rem; opacity: .85; }
.app-empty--page .app-empty__title { color: var(--app-heading); font-size: 1.35rem; font-weight: 700; letter-spacing: -.01em; }
.app-empty--page .app-empty__hint { font-size: .9rem; }

/* The maintenance and can't-reach-the-server page (SystemStatePage): the only thing in the window,
   with no shell around it, so a card in the middle of it. */
.app-state {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 80vh;
    padding: 1rem;
}

.app-state__card { width: 100%; max-width: 34rem; }
.app-state__card .app-empty { min-height: 0; padding: 2.25rem 1.5rem; }

/* The same page shown as a preview on Settings ("what users will see"): the window's canvas in a
   frame, at its own size — the full-window height is what used to need a scale(.8) and still left
   a screen of blank space under it. */
.app-state-preview {
    padding: .75rem;
    border: 1px solid var(--app-line);
    border-radius: var(--app-radius);
    background: var(--app-canvas);
}

.app-state-preview .app-state { min-height: 0; padding: 0; }

/* ── 8. Tables ───────────────────────────────────────────────────────────────────────────────── */

.table {
    --bs-table-bg: transparent;
    --bs-table-color: var(--app-ink);
    --bs-table-striped-bg: transparent;
    --bs-table-hover-bg: var(--app-hover);
    --bs-table-hover-color: var(--app-heading);
    --bs-table-border-color: var(--app-line);
    margin-bottom: 0;
    font-variant-numeric: tabular-nums;
}

/* No zebra stripes: hairlines carry the rows. */
.table > :not(caption) > * > * {
    padding: .7rem 1rem;
    border-bottom: 1px solid var(--app-line);
    vertical-align: middle;
}

.table > thead > tr > th {
    background: var(--app-surface-2);
    color: var(--app-muted);
    font-size: .69rem;
    font-weight: 650;
    letter-spacing: .06em;
    text-transform: uppercase;
    white-space: nowrap;
}

.table > tbody > tr:last-child > * { border-bottom: 0; }

.table-sm > :not(caption) > * > * { padding: .45rem .75rem; }

/* A heading row inside a table's body — "Account and security", "System alerts". A band a step up
   from the surface with a small uppercase label, like the column headers it sits under. It replaces
   Bootstrap's `table-light`, which is a light-grey row with black text whatever the theme. */
.table > tbody > tr.app-group-row > td {
    padding: .5rem 1rem;
    border-bottom: 1px solid var(--app-line);
    background: var(--app-surface-2);
    color: var(--app-muted);
    font-size: .69rem;
    font-weight: 650;
    letter-spacing: .08em;
    text-transform: uppercase;
}

/* An audit change set (AuditChanges): field, before, after — fixed so the before and after columns
   line up row under row whatever the values' lengths. */
.table.app-changes { table-layout: fixed; }
.table.app-changes > thead > tr > th:first-child { width: 30%; }
.table.app-changes > tbody > tr > td { overflow-wrap: anywhere; }

/* A state dot before a name: green fine, amber degraded, red down. For a column of states read at a
   glance, beside (not instead of) the words. */
.app-dot {
    display: inline-block;
    width: .5rem;
    height: .5rem;
    margin-right: .5rem;
    border-radius: 50%;
    vertical-align: .1em;
}

.app-dot--ok   { background: var(--app-ok);   box-shadow: 0 0 0 3px var(--app-ok-soft); }
.app-dot--warn { background: var(--app-warn); box-shadow: 0 0 0 3px var(--app-warn-soft); }
.app-dot--bad  { background: var(--app-bad);  box-shadow: 0 0 0 3px var(--app-bad-soft); }

/* An unread item in a list (My notifications): an indigo bar down its left edge and a heading-
   coloured title. Not a tinted background — on the dark theme that read as darker, i.e. quieter,
   which is the opposite of unread. */
.list-group-item.app-unread { box-shadow: inset 3px 0 0 var(--app-primary-hi); }
.list-group-item.app-unread .app-unread__title { color: var(--app-heading); }
.list-group-item:not(.app-unread) .app-unread__title { color: var(--app-ink-2); font-weight: 600; }

/* The second, quieter line of a two-line cell: an email under a name, a key under a label. */
.app-cell__sub { margin-top: .1rem; color: var(--app-muted); font-size: .78rem; line-height: 1.3; }

/* A technical third line for whoever reads the logs or the code: a class name, a cron expression. */
.app-cell__code { margin-top: .1rem; color: var(--app-faint); font-family: ui-monospace, Consolas, monospace; font-size: .7rem; }

/* A count inside a solid button ("Save preferences 3"): the tints are for surfaces, not for sitting
   on the primary colour, so it gets a plain white wash. */
.btn-primary > .badge { background-color: rgba(255, 255, 255, .22); color: #fff; }

/* ── 9. Forms ────────────────────────────────────────────────────────────────────────────────── */

.form-control,
.form-select {
    min-height: 38px;
    border: 1px solid var(--app-line-strong);
    border-radius: var(--app-radius-sm);
    background-color: var(--app-field);
    color: var(--app-ink);
    transition: border-color .15s ease, box-shadow .15s ease;
}

.form-control-sm, .form-select-sm { min-height: 31px; }

.form-control::placeholder { color: var(--app-faint); opacity: 1; }

.form-control:focus,
.form-select:focus {
    border-color: var(--app-primary-hi);
    background-color: var(--app-field);
    box-shadow: var(--app-ring);
    color: var(--app-ink);
}

.form-label, .col-form-label { color: var(--app-ink-2); font-size: .8rem; font-weight: 600; }

/* Bootstrap draws an unticked box with --bs-border-color, which on the dark surface was a dark square
   on a dark card: findable only by hovering. */
.form-check-input { border: 1.5px solid var(--app-check-border); background-color: var(--app-field); }
.form-check-input:hover:not(:disabled) { border-color: var(--app-primary-hi); }

.form-check-input:checked { border-color: var(--app-primary); background-color: var(--app-primary); }
.form-check-input:focus { border-color: var(--app-primary-hi); box-shadow: var(--app-ring); }

.input-group-text {
    border: 1px solid var(--app-line-strong);
    background: var(--app-surface-2);
    color: var(--app-muted);
}

.required::after { content: " *"; color: var(--app-bad-ink); }

/* A number box sized for a number, not stretched across its column. */
.app-input-number { max-width: 10rem; }

/* A field sized for what goes in it, in three steps: a short search, a code with its button, a
   one-line note. Classes rather than inline widths, which is how a max-width:40rem card once ended
   up nested inside a tab (the UI guard test bans style="max-width). */
.app-field-sm { max-width: 14rem; }
.app-field-md { max-width: 18rem; }
.app-field-lg { max-width: 24rem; }

/* An authenticator QR code: big enough for a phone camera across a desk, never wider than its column. */
.app-qr { width: 180px; max-width: 100%; }
.app-qr--center { margin: 0 auto; }
.app-qr svg { display: block; width: 100%; height: auto; border-radius: 6px; }

/* The save bar of a long form (the job title editor): stuck to the bottom of the window while the
   form scrolls, a surface with a hairline over it, the full width of the content column. */
.app-savebar {
    position: sticky;
    bottom: 0;
    z-index: 5;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: .5rem;
    margin-top: 1rem;
    padding: .75rem 1.1rem;
    border: 1px solid var(--app-line);
    border-bottom: 0;
    border-radius: var(--app-radius) var(--app-radius) 0 0;
    background: var(--app-surface);
    box-shadow: 0 -12px 24px -18px rgba(0, 0, 0, .45);
}

.app-savebar__note {
    margin-right: auto;
    color: var(--app-warn-ink);
    font-size: .8rem;
    font-weight: 600;
}

/* A group of fields inside a dialog ("SMTP settings"): a section label over the fields, not a card
   inside the dialog's own card. Bootstrap floats a legend and sizes it as a heading; this undoes both. */
.app-fieldset { min-width: 0; margin: 0; padding: 0; border: 0; }

.app-fieldset > legend.app-section-label {
    float: none;
    width: auto;
    padding: 0;
    font-size: .7rem;
    line-height: inherit;
}

/* The rendered email in the template editor. White whatever the theme: it is what the recipient's
   mail client shows, and an email body that sets no background assumes one. */
.app-preview-frame {
    display: block;
    width: 100%;
    height: 20rem;
    border: 1px solid var(--app-line-strong);
    border-radius: var(--app-radius-sm);
    background: #fff;
}

/* A card that stays in view while the column beside it scrolls — clear of the sticky top bar. On a
   phone the columns stack and nothing needs to stick. */
@media (min-width: 992px) {
    .app-sticky { position: sticky; top: calc(57px + 1rem); }
}

/* A list inside a card that scrolls rather than stretching the card (people holding a profile). */
.app-scroll-list { max-height: 18rem; overflow: auto; }

/* ── The access profile editor's permission matrix ──
   One area per card; one tile per permission. The tile is the label, so anywhere on it ticks the box,
   and a ticked tile is tinted — the state is readable across the room, not only at the checkbox. */
.app-perm-group { scroll-margin-top: calc(57px + 1rem); }

.app-perm {
    display: flex;
    gap: .75rem;
    align-items: flex-start;
    height: 100%;
    margin: 0;
    padding: .7rem .85rem;
    border: 1px solid var(--app-line);
    border-radius: var(--app-radius-sm);
    background: var(--app-surface-2);
    cursor: pointer;
    transition: border-color .15s, background-color .15s;
}

.app-perm:hover { border-color: var(--app-line-strong); }
.app-perm.is-on { border-color: rgba(var(--app-primary-rgb), .55); background: var(--app-primary-soft); }
.app-perm.is-readonly { cursor: default; }
.app-perm .form-check-input { flex-shrink: 0; margin: .15rem 0 0; }

.app-perm__text { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.app-perm__name { color: var(--app-heading); font-size: .88rem; font-weight: 600; }
.app-perm__desc { color: var(--app-muted); font-size: .8rem; line-height: 1.4; }
.app-perm__key { color: var(--app-faint); font-family: ui-monospace, Consolas, monospace; font-size: .7rem; }

/* The summary: the total, then one line per area with a thin bar of how much of it is ticked. */
.app-perm-total { display: flex; align-items: baseline; gap: .4rem; margin-bottom: .9rem; }
.app-perm-total__value { color: var(--app-heading); font-size: 1.75rem; font-weight: 700; line-height: 1; }
.app-perm-total__label { color: var(--app-muted); font-size: .85rem; }

.app-perm-areas { margin: 0; padding: 0; list-style: none; }

.app-perm-area {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: .2rem .5rem;
    width: 100%;
    padding: .4rem .5rem;
    border: 0;
    border-radius: 7px;
    background: none;
    text-align: left;
    color: var(--app-ink);
    font-size: .82rem;
    text-decoration: none;
}

.app-perm-area:hover { background: var(--app-hover); color: var(--app-heading); }
.app-perm-area.is-empty { color: var(--app-muted); }
.app-perm-area__count { color: var(--app-muted); font-variant-numeric: tabular-nums; }

.app-perm-area__bar {
    grid-column: 1 / -1;
    height: 3px;
    overflow: hidden;
    border-radius: 2px;
    background: var(--app-line);
}

.app-perm-area__bar > span { display: block; height: 100%; background: var(--app-primary-hi); }

.validation-message { color: var(--app-bad-ink); font-size: .8em; }
.valid.modified:not([type=checkbox]) { outline: 1px solid rgba(16, 185, 129, .5); }
.invalid { outline: 1px solid rgba(244, 63, 94, .6); }

/* ── 10. Buttons ─────────────────────────────────────────────────────────────────────────────── */

.btn {
    --bs-btn-border-radius: var(--app-radius-sm);
    --bs-btn-font-weight: 600;
    --bs-btn-font-size: .85rem;
    --bs-btn-padding-x: .95rem;
    --bs-btn-padding-y: .45rem;
    transition: background-color .15s ease, border-color .15s ease, color .15s ease, box-shadow .15s ease;
}

.btn-sm { --bs-btn-padding-x: .6rem; --bs-btn-padding-y: .28rem; --bs-btn-font-size: .78rem; --bs-btn-border-radius: 7px; }

.btn:focus-visible { box-shadow: var(--app-ring) !important; }

/* Solid indigo is the one primary. */
.btn-primary {
    --bs-btn-bg: var(--app-primary);
    --bs-btn-border-color: var(--app-primary);
    --bs-btn-hover-bg: var(--app-primary-hover);
    --bs-btn-hover-border-color: var(--app-primary-hover);
    --bs-btn-active-bg: var(--app-primary-hover);
    --bs-btn-active-border-color: var(--app-primary-hover);
    --bs-btn-disabled-bg: var(--app-primary);
    --bs-btn-disabled-border-color: var(--app-primary);
}

/* Everything else is a tint: coloured enough to read, never louder than primary. */
.btn-success { --bs-btn-bg: var(--app-ok-soft);   --bs-btn-border-color: rgba(52, 211, 153, .32); --bs-btn-color: var(--app-ok-ink);   --bs-btn-hover-bg: rgba(16, 185, 129, .24); --bs-btn-hover-color: var(--app-ok-ink);   --bs-btn-hover-border-color: rgba(52, 211, 153, .45); }
.btn-warning { --bs-btn-bg: var(--app-warn-soft); --bs-btn-border-color: rgba(251, 191, 36, .32); --bs-btn-color: var(--app-warn-ink); --bs-btn-hover-bg: rgba(245, 158, 11, .24); --bs-btn-hover-color: var(--app-warn-ink); --bs-btn-hover-border-color: rgba(251, 191, 36, .45); }
.btn-danger  { --bs-btn-bg: var(--app-bad-soft);  --bs-btn-border-color: rgba(251, 113, 133, .32); --bs-btn-color: var(--app-bad-ink);  --bs-btn-hover-bg: rgba(244, 63, 94, .24);  --bs-btn-hover-color: var(--app-bad-ink);  --bs-btn-hover-border-color: rgba(251, 113, 133, .45); }
.btn-info    { --bs-btn-bg: var(--app-info-soft); --bs-btn-border-color: rgba(56, 189, 248, .32); --bs-btn-color: var(--app-info-ink); --bs-btn-hover-bg: rgba(14, 165, 233, .24); --bs-btn-hover-color: var(--app-info-ink); --bs-btn-hover-border-color: rgba(56, 189, 248, .45); }

.btn-secondary,
.btn-light,
.btn-dark {
    --bs-btn-bg: var(--app-hover);
    --bs-btn-border-color: var(--app-line-strong);
    --bs-btn-color: var(--app-ink);
    --bs-btn-hover-bg: var(--app-line);
    --bs-btn-hover-border-color: var(--app-line-strong);
    --bs-btn-hover-color: var(--app-heading);
}

.btn-outline-primary   { --bs-btn-color: var(--app-primary-ink); --bs-btn-border-color: rgba(43, 131, 196, .45); --bs-btn-hover-bg: var(--app-primary-soft); --bs-btn-hover-color: var(--app-primary-ink); --bs-btn-hover-border-color: rgba(43, 131, 196, .6); }
.btn-outline-secondary { --bs-btn-color: var(--app-ink-2); --bs-btn-border-color: var(--app-line-strong); --bs-btn-hover-bg: var(--app-hover); --bs-btn-hover-color: var(--app-heading); --bs-btn-hover-border-color: var(--app-line-strong); }
.btn-outline-success   { --bs-btn-color: var(--app-ok-ink);   --bs-btn-border-color: rgba(52, 211, 153, .4);  --bs-btn-hover-bg: var(--app-ok-soft);   --bs-btn-hover-color: var(--app-ok-ink);   --bs-btn-hover-border-color: rgba(52, 211, 153, .55); }
.btn-outline-warning   { --bs-btn-color: var(--app-warn-ink); --bs-btn-border-color: rgba(251, 191, 36, .4);  --bs-btn-hover-bg: var(--app-warn-soft); --bs-btn-hover-color: var(--app-warn-ink); --bs-btn-hover-border-color: rgba(251, 191, 36, .55); }
.btn-outline-danger    { --bs-btn-color: var(--app-bad-ink);  --bs-btn-border-color: rgba(251, 113, 133, .4); --bs-btn-hover-bg: var(--app-bad-soft);  --bs-btn-hover-color: var(--app-bad-ink);  --bs-btn-hover-border-color: rgba(251, 113, 133, .55); }

.btn-link { --bs-btn-color: var(--app-primary-ink); --bs-btn-hover-color: var(--app-primary-hi); font-weight: 600; }

[data-bs-theme="dark"] .btn-close { filter: invert(1) grayscale(1) brightness(1.6); }

/* Filter button groups → a segmented control. The selected button is whichever one is NOT an
   outline — that is how pages already mark it — or carries .active. Row-action groups inside
   tables are left as plain buttons. */
.btn-group:not(td .btn-group) {
    gap: 2px;
    padding: 3px;
    border: 1px solid var(--app-line);
    border-radius: 10px;
    background: var(--app-field);
}

.btn-group:not(td .btn-group) > .btn {
    margin: 0 !important;
    padding: .32rem .8rem;
    border: 0 !important;
    border-radius: 7px !important;
    background-color: transparent;
    color: var(--app-muted);
    font-size: .8rem;
}

.btn-group:not(td .btn-group) > .btn:hover { background-color: var(--app-hover); color: var(--app-ink); }

.btn-group:not(td .btn-group) > .btn:not([class*="btn-outline-"]),
.btn-group:not(td .btn-group) > .btn.active {
    background-color: var(--app-primary-soft);
    box-shadow: inset 0 0 0 1px rgba(43, 131, 196, .4);
    color: var(--app-heading);
}

/* ── 11. Badges, alerts, tabs, lists, pagination, dropdowns, modals ──────────────────────────── */

.badge { padding: .32em .7em; border-radius: 999px; font-size: .7rem; font-weight: 600; }

.badge.text-bg-success, .badge.bg-success { background-color: var(--app-ok-soft) !important;   color: var(--app-ok-ink) !important; }
.badge.text-bg-danger,  .badge.bg-danger  { background-color: var(--app-bad-soft) !important;  color: var(--app-bad-ink) !important; }
.badge.text-bg-warning, .badge.bg-warning { background-color: var(--app-warn-soft) !important; color: var(--app-warn-ink) !important; }
.badge.text-bg-info,    .badge.bg-info    { background-color: var(--app-info-soft) !important; color: var(--app-info-ink) !important; }
.badge.text-bg-primary, .badge.bg-primary { background-color: var(--app-primary-soft) !important; color: var(--app-primary-ink) !important; }
.badge.text-bg-secondary, .badge.bg-secondary,
.badge.text-bg-light, .badge.text-bg-dark { background-color: var(--app-hover) !important; color: var(--app-ink-2) !important; }

.alert { border-radius: 10px; font-size: .85rem; }
.alert-secondary, .alert-light { border-color: var(--app-line); background: var(--app-hover); color: var(--app-ink-2); }

.nav-tabs { gap: .15rem; border-bottom: 1px solid var(--app-line); }

.nav-tabs .nav-link {
    margin-bottom: -1px;
    border: 0;
    color: var(--app-muted);
    font-weight: 600;
}

.nav-tabs .nav-link:hover { color: var(--app-ink); }

.nav-tabs .nav-link.active {
    background: transparent;
    box-shadow: inset 0 -2px 0 var(--app-primary-hi);
    color: var(--app-heading);
}

.nav-pills .nav-link.active {
    background: var(--app-primary-soft);
    box-shadow: inset 0 0 0 1px rgba(43, 131, 196, .4);
    color: var(--app-heading);
}

.list-group { --bs-list-group-bg: transparent; --bs-list-group-border-color: var(--app-line); --bs-list-group-color: var(--app-ink); }

.page-link { border-radius: 8px; color: var(--app-ink-2); }
.pagination { gap: .25rem; }
.page-item.active .page-link { border-color: var(--app-primary); background: var(--app-primary); }

.dropdown-menu {
    --bs-dropdown-bg: var(--app-surface);
    --bs-dropdown-border-color: var(--app-line-strong);
    --bs-dropdown-link-color: var(--app-ink-2);
    --bs-dropdown-link-hover-bg: var(--app-hover);
    --bs-dropdown-link-hover-color: var(--app-heading);
    --bs-dropdown-link-active-bg: var(--app-primary-soft);
    --bs-dropdown-link-active-color: var(--app-heading);
    padding: .35rem;
    border-radius: 12px;
    box-shadow: 0 18px 44px -14px rgba(0, 0, 0, .45);
}

.dropdown-item { border-radius: 8px; font-size: .85rem; }

.modal-content {
    border: 1px solid var(--app-line-strong);
    border-radius: 14px;
    background: var(--app-surface);
}

.progress { border-radius: 999px; background: var(--app-hover); }

/* ── 12. Radzen: grids and overlays read like the rest ───────────────────────────────────────── */

/* Most of the grid's look is the --rz-grid-* variables in section 2. What is left here is what no
   variable reaches. No padding is forced onto cells: that rule is what broke the expand column. */

.rz-data-grid,
.rz-datatable {
    overflow: hidden;
    border: 1px solid var(--app-line) !important;
    border-radius: var(--app-radius) !important;
    background: var(--app-surface) !important;
    font-family: var(--app-font) !important;
}

/* A grid that fills a card's body is the card's table, not a box inside the card: one frame, not
   two. Pages put it in `card-body p-0` for exactly this. */
.card > .card-body.p-0 > .rz-data-grid,
.card > .card-body.p-0 > .rz-datatable {
    border: 0 !important;
    border-radius: 0 !important;
}

.rz-grid-table thead th .rz-column-title { letter-spacing: .06em; }

/* The last row sits on the frame or the pager, each of which draws its own line. */
.rz-grid-table > tbody > tr:last-child > td { border-bottom: 0; }

.rz-grid-table .rz-col-icon .rz-row-toggler { color: var(--app-muted); }
.rz-grid-table .rz-col-icon .rz-row-toggler:hover { color: var(--app-heading); }

/* The column without a Width — Summary, Message, Subject, Description: the one that takes the rest
   of the row. Radzen lays grids out as fixed tables, so on a phone, where the fixed columns alone
   are wider than the screen, "the rest" is nothing and that column is drawn 0px wide — the grid's
   most useful text vanishes. Radzen writes its <col> with an empty style; give it a width there and
   the grid scrolls sideways inside its card instead. Desktop layouts are left alone. */
@media (max-width: 767.98px) {
    .rz-grid-table > colgroup > col[style=""] { width: 14rem; }
}

/* A table inside an expanded row belongs to the row: no second header band. */
.rz-expanded-row-template .table > thead > tr > th { background: transparent; }
.rz-expanded-row-content > td { border-bottom: 1px solid var(--app-line); }

/* What a grid says when it has no rows. */
.rz-grid-table td.rz-datatable-emptymessage {
    padding: 2.5rem 1rem;
    color: var(--app-muted);
    text-align: center;
}

.rz-pager, .rz-paginator {
    border-top: 1px solid var(--app-line) !important;
    background: transparent !important;
    color: var(--app-muted);
}

.rz-pager .rz-pager-summary { color: var(--app-muted); font-size: .8rem; }
.rz-pager .rz-pager-page.rz-state-active { background: var(--app-primary-soft); color: var(--app-heading); }

/* Dialogs render in an overlay outside .app-wrapper; the variables in section 2 do the rest. */
.rz-dialog { border: 1px solid var(--app-line-strong); color: var(--app-ink); }

/* Radzen's own buttons — the ones Dialogs.Confirm draws (OK is rz-primary, Cancel rz-base) — sized
   and coloured like ours, so a confirmation reads like every other dialog's footer. */
.rz-button { border-radius: var(--app-radius-sm); font-weight: 600; }

.rz-button.rz-base.rz-variant-filled {
    background-color: transparent;
    box-shadow: inset 0 0 0 1px var(--app-line-strong);
    color: var(--app-ink-2);
}

.rz-button.rz-base.rz-variant-filled:not(.rz-state-disabled):hover {
    background-color: var(--app-hover);
    color: var(--app-heading);
}

.rz-notification-item-wrapper {
    overflow: hidden;
    border: 1px solid var(--app-line-strong);
    border-radius: 12px;
    box-shadow: var(--rz-notification-shadow);
}

/* ── Row state ──────────────────────────────────────────────────────────────────────────────
   A row that needs noticing carries data-row-state="warn" or "bad" (set in a grid's RowRender, or
   written on a <tr>). The cells are coloured, not the row: grids paint their cells, so a colour on
   the row itself never shows. The tint outranks hover on purpose: the state is the thing to see. */
.rz-grid-table tr[data-row-state="warn"] > td,
.table > tbody > tr[data-row-state="warn"] > * { background-color: var(--app-warn-soft) !important; }

.rz-grid-table tr[data-row-state="bad"] > td,
.table > tbody > tr[data-row-state="bad"] > * { background-color: var(--app-bad-soft) !important; }

/* ── 13. Sign-in and the other anonymous pages (Pages/Account/Login/*.cshtml) ────────────────── */

.auth-page {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    padding: 1.5rem 1rem;
    background: radial-gradient(1200px 600px at 15% -10%, rgba(43, 131, 196, .35), transparent 60%),
                radial-gradient(900px 500px at 110% 110%, rgba(16, 185, 129, .22), transparent 55%),
                var(--app-auth-bg, #101426);
}

[data-bs-theme="light"] .auth-page { --app-auth-bg: #eef0f7; }

.auth-shell { width: 100%; max-width: 420px; }

.auth-card {
    overflow: hidden;
    border: 1px solid var(--app-line-strong);
    border-radius: 18px;
    background: var(--app-surface);
    box-shadow: 0 24px 64px -24px rgba(0, 0, 0, .55);
}

.auth-head { padding: 2rem 2rem 0; text-align: center; }

.auth-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    margin-bottom: .9rem;
    border-radius: 15px;
    background: linear-gradient(135deg, var(--app-primary), var(--app-accent));
    color: #fff;
    font-size: 1.45rem;
    box-shadow: 0 10px 24px -10px rgba(var(--app-primary-rgb), .8);
}

.auth-title { margin: 0; color: var(--app-heading); font-size: 1.35rem; font-weight: 750; letter-spacing: -.02em; }
.auth-title .auth-title__accent { color: var(--app-primary-ink); }

.auth-sub { margin: .35rem 0 0; color: var(--app-muted); font-size: .9rem; }

.auth-body { padding: 1.6rem 2rem 2rem; }

.auth-body .form-control { min-height: 46px; padding: .65rem .95rem; font-size: .92rem; }

.auth-body .input-group-text { min-width: 46px; justify-content: center; }

.auth-btn {
    width: 100%;
    padding: .7rem;
    border: 0;
    border-radius: 10px;
    background: linear-gradient(90deg, var(--app-primary), var(--app-primary-hi));
    box-shadow: 0 10px 24px -12px rgba(var(--app-primary-rgb), .8);
    color: #fff;
    font-size: .95rem;
    font-weight: 700;
    transition: transform .08s ease, filter .15s ease;
}

.auth-btn:hover { filter: brightness(1.07); color: #fff; }
.auth-btn:active { transform: translateY(1px); }
.auth-btn:focus-visible { outline: none; box-shadow: var(--app-ring); }

.auth-links { margin-top: 1.1rem; font-size: .85rem; text-align: center; }

/* The pages' own markup, styled here rather than edited in six places: the line that says what the
   page is for, and the form's main button as the gradient sign-in button. */
.auth-body .login-box-msg { margin: 0 0 1.25rem; color: var(--app-ink-2); text-align: center; }

.auth-body .d-grid > .btn-primary {
    padding: .7rem;
    border: 0;
    border-radius: 10px;
    background: linear-gradient(90deg, var(--app-primary), var(--app-primary-hi));
    box-shadow: 0 10px 24px -12px rgba(var(--app-primary-rgb), .8);
    font-size: .95rem;
    font-weight: 700;
}

.auth-body .d-grid > .btn-primary:hover { filter: brightness(1.07); }

/* The error page (Pages/Error.cshtml) is an empty state in this card; its title is the page's heading. */
.auth-body .app-empty__title { color: var(--app-heading); font-size: 1.1rem; font-weight: 700; }

.auth-foot { margin-top: 1.1rem; color: var(--app-muted); font-size: .8rem; text-align: center; }
[data-bs-theme="dark"] .auth-foot { color: rgba(255, 255, 255, .5); }

/* Links back to the public pages (Pages/Site) under the sign-in card. */
.auth-legal { margin-top: .5rem; font-size: .8rem; text-align: center; }
.auth-legal a { margin: 0 .4rem; }

.auth-codes { font-family: ui-monospace, Consolas, monospace; }

/* ── 14. Blazor's own error and reconnect UI ─────────────────────────────────────────────────
   Rendered by Blazor with fixed ids and classes, so it cannot be restyled by wrapping it. */

#blazor-error-ui {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    width: 100%;
    z-index: 1100;
    padding: .7rem 1.25rem;
    background: var(--app-warn-soft);
    color: var(--app-warn-ink);
    box-shadow: 0 -1px 4px rgba(0, 0, 0, .2);
}

#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: .75rem; top: .5rem; }

.blazor-error-boundary { padding: 1rem 1.25rem; background: var(--app-bad); color: #fff; }
.blazor-error-boundary::after { content: "An error has occurred."; }

@media (prefers-reduced-motion: reduce) {
    .app-sidebar .sidebar-menu .nav-link, .app-stat, .btn, .form-control, .form-select, .auth-btn,
    .app-tab, .app-action, .app-action__go { transition: none; }

    .app-sk { animation: none; }
}

/* ── My account: the identity card ─────────────────────────────────────────────────────────── */

/* A larger avatar than the top bar's, for the one place the account is the subject. */
.app-avatar--xl {
    width: 64px;
    height: 64px;
    font-size: 1.4rem;
    letter-spacing: .02em;
    box-shadow: 0 0 0 3px var(--app-surface), 0 0 0 5px rgba(43, 131, 196, .35);
}

.app-profile__head {
    display: flex;
    align-items: center;
    gap: 1rem;
    min-width: 0;
}

.app-profile__who { min-width: 0; }

.app-profile__name {
    color: var(--app-heading);
    font-size: 1.15rem;
    font-weight: 700;
    letter-spacing: -.01em;
    line-height: 1.25;
    overflow-wrap: anywhere;
}

.app-profile__email { color: var(--app-muted); font-size: .88rem; }

.app-profile__chips {
    display: flex;
    flex-wrap: wrap;
    gap: .4rem;
    margin-top: 1rem;
}

/* A small rounded label with an icon: an access profile, a state. Tinted with .app-tint--*. */
.app-chip {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .25rem .6rem;
    border-radius: 999px;
    background: var(--app-hover);
    color: var(--app-muted);
    font-size: .76rem;
    font-weight: 600;
    line-height: 1.2;
}
.app-chip .bi { font-size: .85em; }

/* Label-value pairs in a tight two-column grid. Wide variant for a card with room to spare. */
.app-facts {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: .55rem 1rem;
    margin: 1.1rem 0 0;
    padding-top: 1rem;
    border-top: 1px solid var(--app-line);
    font-size: .86rem;
}
.app-facts dt { margin: 0; color: var(--app-muted); font-weight: 500; }
.app-facts dd { display: flex; align-items: center; margin: 0; min-width: 0; color: var(--app-ink); }
.app-facts a { text-decoration: none; }
.app-facts a:hover { text-decoration: underline; }
.app-facts--wide { margin-top: 0; padding-top: 0; border-top: 0; grid-template-columns: 9rem 1fr; margin-bottom: 1rem; }

/* The save bar inside a card sits on the card's own bottom edge. */
.card > .app-savebar { margin-top: 0; border: 0; border-top: 1px solid var(--app-line); border-radius: 0 0 var(--app-radius) var(--app-radius); }

/* ── Option rows: one choice per row with a glyph, a name, a hint and its button (two-factor). ── */
.app-options { display: flex; flex-direction: column; gap: .6rem; }

.app-option {
    display: flex;
    align-items: center;
    gap: .9rem;
    padding: .85rem 1rem;
    border: 1px solid var(--app-line);
    border-radius: var(--app-radius);
    background: var(--app-surface-2);
}
.app-option .app-stat__chip { flex: none; margin: 0; }
.app-option__body { flex: 1; min-width: 0; }
.app-option__name { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; color: var(--app-heading); font-weight: 600; }
.app-option__hint { margin-top: .1rem; color: var(--app-muted); font-size: .8rem; }
.app-option .btn { flex: none; }

/* ── The notification preferences matrix ───────────────────────────────────────────────────── */
.app-matrix { margin-bottom: 0; }
.app-matrix > :not(caption) > * > * { padding: .8rem .75rem; border-bottom-color: var(--app-line); }
.app-matrix thead th { vertical-align: top; border-bottom: 1px solid var(--app-line-strong); background: transparent; }
.app-matrix__type { min-width: 16rem; }
.app-matrix thead .app-matrix__type { vertical-align: bottom; }

.app-matrix__channel { width: 10.5rem; text-align: center; }
.app-matrix__channel .app-stat__chip { width: 36px; height: 36px; margin: 0 auto .45rem; font-size: 1.05rem; }
.app-matrix__channel-name { color: var(--app-heading); font-size: .86rem; font-weight: 700; }
.app-matrix__status { display: flex; flex-direction: column; align-items: center; gap: .3rem; margin-top: .35rem; color: var(--app-muted); font-size: .76rem; font-weight: 500; letter-spacing: 0; text-transform: none; }
.app-matrix__addr { display: inline-block; max-width: 9.5rem; }
.app-matrix__channel.is-off .app-stat__chip,
.app-matrix__channel.is-off .app-matrix__channel-name { opacity: .5; }

.app-matrix__title { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; color: var(--app-ink); font-weight: 600; }

.app-matrix__cell { text-align: center; }
.app-matrix__switch { display: inline-flex; justify-content: center; min-height: 0; margin: 0; padding: 0; }
.app-matrix__switch .form-check-input { float: none; width: 2.5em; height: 1.35em; margin: 0; cursor: pointer; }
.app-matrix__switch .form-check-input:disabled { cursor: not-allowed; opacity: .45; }
.app-matrix__na { color: var(--app-faint); font-size: 1.4rem; line-height: 1; }

.app-matrix tbody tr:not(.app-group-row):hover > td { background: var(--app-hover); }

/* ── The journey ladder: every stage of a consignment's route, done ones ticked, the next one lit ── */
.app-ladder { position: relative; margin: 0; padding: 0; list-style: none; }

.app-ladder__step { position: relative; display: flex; gap: .85rem; padding-bottom: .9rem; }
.app-ladder__step:last-child { padding-bottom: 0; }

/* The rail between two marks: green up to the last stage done, the line colour after it. */
.app-ladder__step:not(:last-child)::before {
    content: "";
    position: absolute;
    top: 1.5rem;
    bottom: .1rem;
    left: calc(.75rem - 1px);
    width: 2px;
    border-radius: 1px;
    background: var(--app-line-strong);
}
.app-ladder__step.is-done:not(:last-child)::before { background: var(--app-ok); }

.app-ladder__mark {
    position: relative;
    z-index: 1;
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    border: 2px solid var(--app-line-strong);
    border-radius: 50%;
    background: var(--app-surface);
    color: #fff;
    font-size: .8rem;
}
.is-done > .app-ladder__mark { border-color: var(--app-ok); background: var(--app-ok); }
.is-next > .app-ladder__mark { border-color: var(--app-primary-hi); box-shadow: var(--app-ring); }
.is-next > .app-ladder__mark::after { content: ""; width: .5rem; height: .5rem; border-radius: 50%; background: var(--app-primary-hi); }

.app-ladder__body { min-width: 0; padding-top: .1rem; }
.app-ladder__name { display: flex; flex-wrap: wrap; align-items: center; gap: .45rem; color: var(--app-faint); font-size: .9rem; font-weight: 500; line-height: 1.3; }
.is-done .app-ladder__name { color: var(--app-ink); }
.is-next .app-ladder__name { color: var(--app-heading); font-weight: 700; }
.app-ladder__when { margin-top: .1rem; color: var(--app-muted); font-size: .76rem; }
