html, body {
    margin: 0;
    padding: 0;
    font-family: var(--bz-font-sans);
    font-size: var(--bz-fs-md);
    line-height: var(--bz-lh);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
}

*, *::before, *::after { box-sizing: border-box; }

/* Fluid page canvas with proportionate gutters on smaller screens. */
.bz-shell-main { min-width: 0; }
.bz-shell-main > .kt-container-fluid,
.bz-shell-context > .kt-container-fluid {
    padding-inline: clamp(var(--bz-space-3), 2vw, var(--bz-space-6));
}
.bz-shell-context { padding-bottom: var(--bz-space-2); }
.bz-shell-title { margin: 0; font-size: var(--bz-fs-sm); font-weight: var(--bz-fw-medium); overflow-wrap: anywhere; }
.bz-shell-main:has(.bz-page-header) .bz-shell-title { display: none; }
.bz-shell-content { min-width: 0; }
.bz-shell-content > * { min-width: 0; }

/* Native `<select>` popups, scrollbars and date/time inputs are painted by the browser itself,
   not by page CSS — nothing under .bz-* or --dxbl-* reaches them (GitLab #150.3, the Assistant
   Mode dropdown staying light in dark mode). `color-scheme` is the one property the browser reads
   to pick which palette to paint those controls with, so it is declared here against the same
   `html.dark` class the rest of the app themes on — it just follows the class, no extra JS and no
   page refresh needed when the user toggles theme. */
:root { color-scheme: light; }
html.dark { color-scheme: dark; }

a {
    text-decoration: none;
}

a:hover { text-decoration: underline; }

button {
    font-family: inherit;
    font-size: inherit;
    color: inherit;
}

/* Consistent keyboard focus ring across all interactive elements. */
:focus-visible {
    outline: 2px solid var(--bz-brand-primary);
    outline-offset: 2px;
    border-radius: var(--bz-radius-sm);
}

/* Subtle scrollbars. WebKit/Chromium/Edge get a custom track+thumb;
   Firefox falls back to its native thin scrollbar via scrollbar-width.
   Other engines keep their platform default — acceptable progressive
   enhancement, no JS-based scrollbar polyfill needed. */
html { scrollbar-width: thin; scrollbar-color: var(--bz-color-border) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
    background: var(--bz-color-border);
    border-radius: var(--bz-radius-pill);
    border: 2px solid var(--bz-color-bg);
}
::-webkit-scrollbar-thumb:hover { background: var(--bz-color-border-strong); }

/* Tenant-uploaded auth artwork is an image layer inside the shared AuthExperience.
   Its copy lives on the opaque hero surface, so legibility never depends on the upload. */
.auth-branded-bg { background-image: var(--bz-login-image, url('/branding/login-default.svg')); }
/* Match the tenant picker to authentication inputs through vendor variables. */
.bz-auth .login-combo {
    --dxbl-text-edit-font-size: var(--bz-fs-lg);
    --dxbl-text-edit-padding-y: var(--bz-space-3);
    --dxbl-text-edit-border-radius: var(--bz-radius-md);
}
html.dark .auth-branded-bg { background-image: var(--bz-login-image-dark, var(--bz-login-image, url('/branding/login-default-dark.svg'))); }

/* Theme-aware brand logo pair. When a tenant has both a light and dark logo, the shell renders both
   <img>s and CSS shows the one matching the active theme. Default (no theme class yet) = light. */
.bz-dark-only { display: none; }
html.dark .bz-light-only { display: none; }
html.dark .bz-dark-only { display: revert; }

/* ── Browser autofill — preserve dark mode colours ────────────────────
   Chromium/Safari forcibly paint a light-yellow/white overlay on
   autofilled inputs via :-webkit-autofill.  The only reliable cross-
   browser override is box-shadow inset (background-color is ignored
   by the UA stylesheet).  We re-apply Metronic's --card / --foreground
   tokens so the input keeps its field colour (--background, as .kt-input
   and the DevExpress editors) in both modes. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
    -webkit-box-shadow: 0 0 0 1000px var(--background, #fff) inset !important;
    -webkit-text-fill-color: var(--foreground, #09090b) !important;
    caret-color: var(--foreground, #09090b);
    transition: background-color 0s 600000s; /* delay so Chrome never flashes */
}

#sidebar_menu {
    flex-shrink: 1;
    min-height: 0;
    overflow: hidden;
}

#sidebar_menu > .kt-scrollable-y-auto {
    max-height: 100%;
    min-height: 0;
    overflow-y: auto;
}

/* ── DevExpress Blazor — global grid + button overrides ──────────────
   Applied here (global) so every DxGrid instance across all modules
   shares the same visual language, including raw DxGrid usage in
   HostAdmin / TenantAdmin pages that don't go through ProjectionGrid.
   We map BizsOn design tokens to DevExpress CSS variables and add
   a few structural fixes for filling available width. */

/* Every DxGrid fills its parent container horizontally */
.dxbl-grid {
    width: 100%;
}

/* Map BizsOn design tokens to DevExpress grid CSS variables.
   Task 317 (#166): six of the names this block used do not exist in DevExpress 26.1 and
   were silently doing nothing — -header-border-color, -row-hover-bg, -pager-bg,
   -filter-row-bg, -group-bg, -font-size, -font-family. The real names are below; the
   pager has its own family (see the bridge section at the end of this file) and the two
   font variables have no equivalent at all, so the grid font is set as a property. */
.dxbl-grid {
    --dxbl-grid-bg:                var(--bz-color-surface);
    --dxbl-grid-color:             var(--bz-color-fg);
    --dxbl-grid-border-color:      var(--bz-color-border);
    --dxbl-grid-outer-border-color: var(--bz-color-border);
    --dxbl-grid-h-line-color:      var(--bz-color-border);
    --dxbl-grid-v-line-color:      var(--bz-color-border);
    --dxbl-grid-fixed-column-border-color: var(--bz-color-border-strong);
    --dxbl-grid-header-bg:         var(--bz-color-grid-head);
    --dxbl-grid-header-color:      var(--bz-color-fg);
    --dxbl-grid-row-bg:            var(--bz-color-surface);
    --dxbl-grid-hover-bg:          var(--bz-color-surface-3);
    --dxbl-grid-hover-color:       var(--bz-color-fg);
    --dxbl-grid-selection-bg:      color-mix(in srgb, var(--bz-color-accent) 16%, var(--bz-color-surface));
    --dxbl-grid-selection-color:   var(--bz-color-fg);
    --dxbl-grid-selected-hover-bg: color-mix(in srgb, var(--bz-color-accent) 24%, var(--bz-color-surface));
    --dxbl-grid-focus-bg:          color-mix(in srgb, var(--bz-color-accent) 10%, var(--bz-color-surface));
    --dxbl-grid-focus-color:       var(--bz-color-fg);
    --dxbl-grid-focus-frame-color: var(--bz-color-accent);
    --dxbl-grid-edit-row-bg:       var(--bz-color-surface-2);
    --dxbl-grid-edit-row-color:    var(--bz-color-fg);
    --dxbl-grid-footer-bg:         var(--bz-color-surface-2);
    --dxbl-grid-footer-color:      var(--bz-color-fg);
    --dxbl-grid-group-footer-bg:   var(--bz-color-surface-2);
    --dxbl-grid-group-footer-color: var(--bz-color-fg);
    --dxbl-grid-draggable-header-bg: var(--bz-color-surface-2);
    --dxbl-grid-draggable-header-color: var(--bz-color-fg);
    --dxbl-grid-draggable-header-border-color: var(--bz-color-border-strong);
    --dxbl-grid-highlighted-text-bg: color-mix(in srgb, var(--bz-color-accent) 28%, transparent);
    --dxbl-grid-highlighted-text-color: var(--bz-color-fg);
    /* Group expand/collapse chevrons — --dxbl-image-fill is currentcolor, so the icon
       follows these. This is the grid half of the icon-contrast defect in #166. */
    --dxbl-grid-expand-btn-bg:     transparent;
    --dxbl-grid-expand-btn-color:  var(--bz-color-fg-muted);
    --dxbl-grid-expand-btn-hover-bg: var(--bz-color-surface-3);
    --dxbl-grid-expand-btn-hover-color: var(--bz-color-fg);
    /* Header text stays at full foreground on purpose. The retired (and dead) header rule asked for
       --bz-color-fg-muted, and putting that colour on the real selector measures 4.39:1 in light and
       3.09:1 in dark — below AA, which DesignSystemContrastSmokeTests catches. The header is
       separated from the data by size, weight and letter-spacing instead (see the header rule
       further down), which costs no contrast. */
    --dxbl-grid-header-color:      var(--bz-color-fg);
    --dxbl-grid-font-family:       var(--bz-font-sans);
}

/* ── The one grid density (task 322) ─────────────────────────────────
   `.dxbl-grid.dxbl-grid` is the same element matched twice. That buys exactly one class of
   specificity, which is what it takes to outrank DevExpress's own per-SizeMode blocks
   (`.dxbl-grid.dxbl-lg`, `.dxbl-sm`, `.dxbl-md`) without repeating this list three times and
   without an !important.

   Why it has to outrank them: 78 grids asked for SizeMode.Small and ~320 took BzGrid's default
   Large, so the product shipped two row heights depending on which page you opened — the most
   visible difference between any two list screens. The metrics below are the house density for
   every grid; SizeMode still governs the grid's internal chrome, not the rhythm of the rows.

   These were previously written as `!important` property overrides on `.dxbl-grid-header-cell`
   and `.dxbl-grid-data-row` — two class names DevExpress does not ship in 26.1 — so none of the
   intended metrics ever reached a screen. 26.1 exposes them as variables, which is the house
   rule anyway: assign variables, never override properties. */
.dxbl-grid.dxbl-grid {
    --dxbl-grid-text-cell-padding-y: 0.5rem;
    --dxbl-grid-text-cell-padding-x: 0.625rem;
    /* The filter row and inline editors sit tighter than the data they filter. */
    --dxbl-grid-editor-cell-padding-y: 0.25rem;
    --dxbl-grid-editor-cell-padding-x: 0.375rem;
    --dxbl-grid-header-font-weight: var(--bz-fw-semibold);
    --dxbl-grid-header-spacing: 0.375rem;
    /* "No data" is a state, not an error: give it room rather than a cramped single line. */
    --dxbl-grid-empty-data-area-padding-y: 3rem;
    --dxbl-grid-empty-data-area-padding-x: 1.5rem;
    /* Body text is one size everywhere, including SizeMode.Small — DevExpress drops that grid to
       12px, which is below the product's smallest body size. Declared here rather than beside
       --dxbl-grid-font-family because `.dxbl-grid.dxbl-sm` re-declares it. */
    --dxbl-grid-font-size: var(--bz-fs-md);
    --dxbl-grid-line-height: var(--bz-lh);
    --dxbl-grid-command-btn-spacing-x: var(--bz-space-1);
}

/* Command cells (data rows only): row of small action buttons, vertically
   centred. Scoped to td — the command-column HEADER cell must stay a normal
   table cell so it matches the height/alignment of every other header cell. */
.dxbl-grid td.dxbl-grid-command-cell {
    display: flex;
    flex-direction: row;
    align-items: center;
    /* Task 322: the same gap as .bz-grid-actions, so the 108 command columns that predate
       BzGridActions and the ones written with it space their buttons identically.

       Deliberately NOT right-aligned, though the column sits at the end of the row and that is
       where the eye expects it: `justify-content: flex-end` makes a cell whose buttons are wider
       than its column overflow to the LEFT, under the neighbouring cell, which then swallows the
       click. Playwright caught exactly that on the two "remove tenant admin" flows. These columns
       were authored with any number of buttons and hand-picked widths, so some of them do
       overflow; alignment is not worth a dead button. */
    gap: var(--bz-space-1);
    padding: 0.25rem 0.5rem !important;
    flex-wrap: wrap;
}

/* Command-column header: same metrics as other headers; centre any residual
   built-in header button (grids that intentionally keep the new-row button) */
.dxbl-grid th.dxbl-grid-command-cell {
    vertical-align: middle;
    text-align: center;
}
.dxbl-grid th.dxbl-grid-command-cell .dxbl-btn {
    width: 28px;
    height: 28px;
    min-width: 28px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--bz-radius-sm);
}

/* Make Link-style DxButtons inside command cells look like visible
   ghost actions rather than plain hyperlinks */
.dxbl-grid-command-cell .dxbl-btn-link,
.dxbl-grid-command-cell .dxbl-btn-link:visited {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.2rem 0.55rem;
    border-radius: var(--bz-radius-sm);
    border: 1px solid var(--bz-color-border);
    background: var(--bz-color-surface);
    color: var(--bz-color-fg) !important;
    text-decoration: none !important;
    font-size: var(--bz-fs-sm);
    font-weight: var(--bz-fw-medium);
    line-height: 1.4;
    cursor: pointer;
    white-space: nowrap;
    /* transform repeated here (and in the two rules below): these selectors outrank
       the shared press-feedback rule, so omitting it would silently drop the press. */
    transition: background var(--bz-duration-fast) var(--bz-ease),
                border-color var(--bz-duration-fast) var(--bz-ease),
                transform var(--bz-duration-press) var(--bz-ease);
}

.dxbl-grid-command-cell .dxbl-btn-link:hover {
    background: var(--bz-color-surface-3) !important;
    border-color: var(--bz-color-border-strong) !important;
    color: var(--bz-brand-primary) !important;
    text-decoration: none !important;
}

/* ══ Button system (task 285 — GitLab #113) ════════════════════════════════════
   One shared definition for every clickable control in the app. Buttons used to
   read as floating glyphs rather than controls: the DevExpress normalization was
   gated on .toolbar/.bz-toolbar, .bz-icon-action was a fully transparent ghost
   whose only affordance was :hover — which a phone does not have — and nothing
   anywhere enforced a finger-sized target.

   Three rules hold this section together:

   1. DevExpress metrics and colours are written as --dxbl-btn-* variables, never
      as property overrides. DevExpress paints .dxbl-btn from those variables, and
      every contextual override it ships (pager, calendar header, editor buttons,
      SizeMode.Small) sets the same variables at HIGHER specificity — so its own
      metrics survive untouched and not one !important is needed here.
   2. Every colour comes from a --bz-* token, so both themes follow automatically.
      Never add a prefers-color-scheme block to this section.
   3. Touch sizing lives in the single @media (pointer: coarse) block at the end.
      Never size or box a button per page. Coarse pointer, not a width breakpoint:
      a tablet in landscape is wide AND finger-driven. */

/* ── 1. DevExpress normalization, app-wide ────────────────────────────────── */
.dxbl-btn {
    --dxbl-btn-font-family: var(--bz-font-sans);
    --dxbl-btn-font-size: var(--bz-fs-sm);
    --dxbl-btn-font-weight: var(--bz-fw-medium);
    --dxbl-btn-line-height: 1.2;
    --dxbl-btn-border-radius: var(--bz-radius-sm);
    --dxbl-btn-padding-x: 0.8rem;
    --dxbl-btn-padding-y: 0.3rem;
    --dxbl-btn-image-spacing: var(--bz-space-2);
    /* DevExpress's own :focus-visible rule reads these three, so the ring matches
       the app's global outline. The retired forced box-shadow reset on the icon
       button removed the focus affordance altogether. */
    --dxbl-btn-focus-outline-color: var(--bz-brand-primary);
    --dxbl-btn-focus-outline-size: 2px;
    --dxbl-btn-focus-outline-offset: 2px;
    min-height: var(--bz-control-height);
    cursor: pointer;
}

/* DevExpress composites size their own embedded buttons — a combo box's drop-down
   arrow, a spin edit's steppers, pager pages, the calendar's month header. Those
   are widget internals, not app actions: the app-wide height floor above would
   stretch the control around them. */
.dxbl-text-edit .dxbl-btn,
.dxbl-pager .dxbl-btn,
.dxbl-calendar .dxbl-btn,
.dxbl-menu .dxbl-btn,
.dxbl-treeview .dxbl-btn,
.dxbl-pivot-table .dxbl-btn {
    min-height: 0;
}

.dxbl-btn:disabled,
.dxbl-btn.dxbl-disabled {
    opacity: .5;
    cursor: not-allowed;
}

/* ── Press feedback ─────────────────────────────────────────────────
   Every pressable surface confirms it heard the click. transform only, so the
   whole thing stays on the GPU, and :not(disabled) so dead controls stay dead.
   scale() also scales the label and icon inside — that is the point: the whole
   button compresses as one object rather than the border moving alone. */
:is(.dxbl-btn, .bz-icon-action, .kt-btn, .bz-chip-x,
    .bz-avatar-action, .bz-toast-close, .bz-tabs a) {
    transition: transform var(--bz-duration-press) var(--bz-ease);
}

:is(.dxbl-btn, .bz-icon-action, .kt-btn, .bz-chip-x,
    .bz-avatar-action, .bz-toast-close, .bz-tabs a):active:not(:disabled):not(.dxbl-disabled) {
    transform: scale(0.97);
}

/* A form control paired with a trailing icon button (e.g. a field + its info/help button). */
.bz-inline-field {
    display: flex;
    align-items: center;
    gap: var(--bz-space-2);
}

.bz-inline-field > :first-child {
    flex: 1 1 auto;
    min-width: 0;
}

/* Task 300 (GitLab #137): the page-size DxComboBox of a hand-rolled pager (Documents workspace).
   A flex toolbar squeezes a DevExpress editor to nothing without an explicit basis. */
.bz-toolbar .bz-page-size { flex: 0 0 auto; width: 6rem; }

/* A plain flex row of inline items (icon + label + badges + …) — unlike .bz-toolbar it carries no
   page-level padding/margin, so it stacks cleanly when repeated as list rows (stage/gate/document
   rows, list-item actions) instead of a toolbar's spacing between them. */
.bz-inline-row {
    display: flex;
    align-items: center;
    gap: var(--bz-space-2);
    flex-wrap: wrap;
}

/* ── 1b. Render-style colours from the app tokens ─────────────────────────────
   Fluent HAS a dark mode (modes/dark.min.css) and IThemeChangeService swaps to it —
   the older claim here that it was registered light "regardless of the app theme"
   was superseded by that SetTheme path. What it cannot do is agree with Metronic:
   Fluent's palette is its own, so a Secondary button on a dark page was a DevExpress
   grey next to a Metronic zinc. Driving --dxbl-btn-* from the --bz-* tokens makes the
   button follow the PAGE in either theme, and Primary follow the tenant's brand.
   The same treatment for every other component family is at the end of this file. */
.dxbl-btn.dxbl-btn-secondary {
    --dxbl-btn-bg: var(--bz-color-surface);
    --dxbl-btn-color: var(--bz-color-fg);
    --dxbl-btn-border-color: var(--bz-color-border);
    --dxbl-btn-hover-bg: var(--bz-color-surface-2);
    --dxbl-btn-hover-color: var(--bz-color-fg);
    --dxbl-btn-hover-border-color: var(--bz-color-border-strong);
    --dxbl-btn-active-bg: var(--bz-color-surface-3);
    --dxbl-btn-active-color: var(--bz-color-fg);
    --dxbl-btn-active-border-color: var(--bz-color-border-strong);
}

.dxbl-btn.dxbl-btn-primary {
    --dxbl-btn-bg: var(--bz-color-accent);
    --dxbl-btn-color: var(--bz-color-accent-fg);
    --dxbl-btn-border-color: var(--bz-color-accent);
    --dxbl-btn-hover-bg: var(--bz-color-accent-hover);
    --dxbl-btn-hover-color: var(--bz-color-accent-fg);
    --dxbl-btn-hover-border-color: var(--bz-color-accent-hover);
    --dxbl-btn-active-bg: var(--bz-color-accent-hover);
    --dxbl-btn-active-color: var(--bz-color-accent-fg);
    --dxbl-btn-active-border-color: var(--bz-color-accent-hover);
}

.dxbl-btn.dxbl-btn-danger {
    --dxbl-btn-bg: var(--bz-color-danger);
    --dxbl-btn-color: #fff;
    --dxbl-btn-border-color: var(--bz-color-danger);
    --dxbl-btn-hover-bg: color-mix(in srgb, var(--bz-color-danger) 85%, black);
    --dxbl-btn-hover-color: #fff;
    --dxbl-btn-hover-border-color: color-mix(in srgb, var(--bz-color-danger) 85%, black);
    --dxbl-btn-active-bg: color-mix(in srgb, var(--bz-color-danger) 75%, black);
    --dxbl-btn-active-color: #fff;
    --dxbl-btn-active-border-color: color-mix(in srgb, var(--bz-color-danger) 75%, black);
}

.dxbl-btn.dxbl-btn-warning {
    --dxbl-btn-bg: var(--bz-color-warn);
    --dxbl-btn-color: #fff;
    --dxbl-btn-border-color: var(--bz-color-warn);
    --dxbl-btn-hover-bg: color-mix(in srgb, var(--bz-color-warn) 85%, black);
    --dxbl-btn-hover-color: #fff;
    --dxbl-btn-hover-border-color: color-mix(in srgb, var(--bz-color-warn) 85%, black);
    --dxbl-btn-active-bg: color-mix(in srgb, var(--bz-color-warn) 75%, black);
    --dxbl-btn-active-color: #fff;
    --dxbl-btn-active-border-color: color-mix(in srgb, var(--bz-color-warn) 75%, black);
}

.dxbl-btn.dxbl-btn-success {
    --dxbl-btn-bg: var(--bz-color-ok);
    --dxbl-btn-color: #fff;
    --dxbl-btn-border-color: var(--bz-color-ok);
    --dxbl-btn-hover-bg: color-mix(in srgb, var(--bz-color-ok) 85%, black);
    --dxbl-btn-hover-color: #fff;
    --dxbl-btn-hover-border-color: color-mix(in srgb, var(--bz-color-ok) 85%, black);
    --dxbl-btn-active-bg: color-mix(in srgb, var(--bz-color-ok) 75%, black);
    --dxbl-btn-active-color: #fff;
    --dxbl-btn-active-border-color: color-mix(in srgb, var(--bz-color-ok) 75%, black);
}

.dxbl-btn.dxbl-btn-link {
    --dxbl-btn-bg: transparent;
    --dxbl-btn-color: var(--bz-color-accent-text);
    --dxbl-btn-border-color: transparent;
    --dxbl-btn-hover-bg: transparent;
    --dxbl-btn-hover-color: var(--bz-color-accent-hover);
    --dxbl-btn-hover-border-color: transparent;
    --dxbl-btn-active-bg: transparent;
    --dxbl-btn-active-color: var(--bz-color-accent-hover);
    --dxbl-btn-active-border-color: transparent;
}

/* Pressed state of a two-state action (Watch/Unwatch, …). On a phone the page-header
   icon-collapse clips the label, so "Watch" and "Unwatch" render the same bare eye —
   the state has to live on the chip itself (task 290, GitLab #128). Accent tint and
   border, the app's own "active" language (see .bz-board-pill--active); deliberately
   not red, which means danger here. Declared AFTER the render-style blocks above so it
   out-orders .dxbl-btn-secondary at equal specificity. */
.dxbl-btn.bz-toggle-on {
    --dxbl-btn-bg: color-mix(in srgb, var(--bz-color-accent) 12%, var(--bz-color-surface));
    --dxbl-btn-color: var(--bz-color-accent);
    --dxbl-btn-border-color: var(--bz-color-accent);
    --dxbl-btn-hover-bg: color-mix(in srgb, var(--bz-color-accent) 18%, var(--bz-color-surface));
    --dxbl-btn-hover-color: var(--bz-color-accent);
    --dxbl-btn-hover-border-color: var(--bz-color-accent);
    --dxbl-btn-active-bg: color-mix(in srgb, var(--bz-color-accent) 24%, var(--bz-color-surface));
    --dxbl-btn-active-color: var(--bz-color-accent);
    --dxbl-btn-active-border-color: var(--bz-color-accent);
}

/* ── 2. Icon-only action ─────────────────────────────────────────────────────────────────────────────────
   Shared by DxButton (CssClass="bz-icon-action"), plain <button> and <a>.

   Task 285 boxed every one of them. #113 round 2 says that is right for a
   STANDALONE action and wrong everywhere else: an icon sitting INLINE in a grid
   row, a toolbar, a navigation strip or an input adornment (the login password
   eye, the header bell) turns a page into a wall of little boxes.

   So the chip is context-bound now. The shared definition is lightweight — no
   border, transparent at rest, tinted on hover/focus, SAME size and same 44px
   touch target — and only `.bz-page-actions`, the page-header action row, paints
   it back into a chip, because that is the one place an icon action really does
   stand alone with nothing around it to give it an edge. */
.bz-icon-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--bz-control-height);
    min-width: var(--bz-control-height);
    height: var(--bz-control-height);
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--bz-radius-sm);
    background: transparent;
    color: var(--bz-color-fg-muted);
    line-height: 1;
    cursor: pointer;
    text-decoration: none;
    transition: background var(--bz-duration-fast) var(--bz-ease),
                border-color var(--bz-duration-fast) var(--bz-ease),
                color var(--bz-duration-fast) var(--bz-ease),
                transform var(--bz-duration-press) var(--bz-ease);
}

.bz-icon-action i,
.bz-icon-action .dxbl-image {
    margin: 0;
    font-size: 1.05rem;
    line-height: 1;
}

/* DxButton flavour: rest/hover/active/disabled all ride DevExpress's own variables,
   so every state stays consistent with the rest button above without fighting its
   cascade — and the .bz-page-actions block below only has to re-point the same
   three variables to turn the chip back on. */
.dxbl-btn.bz-icon-action {
    --dxbl-btn-bg: transparent;
    --dxbl-btn-border-color: transparent;
    --dxbl-btn-hover-bg: var(--bz-color-surface-2);
    --dxbl-btn-hover-color: var(--bz-color-accent);
    --dxbl-btn-hover-border-color: transparent;
    --dxbl-btn-active-bg: var(--bz-color-surface-3);
    --dxbl-btn-active-color: var(--bz-color-accent);
    --dxbl-btn-active-border-color: transparent;
}

/* Semantic variants keep their colour at rest and tint the surface on interaction.
   Keyed off the DevExpress render-style class, so a Danger DxButton is a red chip. */
.dxbl-btn.bz-icon-action.dxbl-btn-danger {
    color: var(--bz-color-danger);
    --dxbl-btn-hover-bg: color-mix(in srgb, var(--bz-color-danger) 12%, transparent);
    --dxbl-btn-hover-color: var(--bz-color-danger);
    --dxbl-btn-hover-border-color: transparent;
    --dxbl-btn-active-bg: color-mix(in srgb, var(--bz-color-danger) 20%, transparent);
    --dxbl-btn-active-color: var(--bz-color-danger);
    --dxbl-btn-active-border-color: transparent;
}

.dxbl-btn.bz-icon-action.dxbl-btn-warning {
    color: var(--bz-color-warn);
    --dxbl-btn-hover-bg: color-mix(in srgb, var(--bz-color-warn) 12%, transparent);
    --dxbl-btn-hover-color: var(--bz-color-warn);
    --dxbl-btn-hover-border-color: transparent;
    --dxbl-btn-active-bg: color-mix(in srgb, var(--bz-color-warn) 20%, transparent);
    --dxbl-btn-active-color: var(--bz-color-warn);
    --dxbl-btn-active-border-color: transparent;
}

.dxbl-btn.bz-icon-action.dxbl-btn-primary {
    color: var(--bz-color-accent);
    --dxbl-btn-hover-bg: color-mix(in srgb, var(--bz-color-accent) 12%, transparent);
    --dxbl-btn-hover-color: var(--bz-color-accent);
    --dxbl-btn-hover-border-color: transparent;
    --dxbl-btn-active-bg: color-mix(in srgb, var(--bz-color-accent) 20%, transparent);
    --dxbl-btn-active-color: var(--bz-color-accent);
    --dxbl-btn-active-border-color: transparent;
}

.dxbl-btn.bz-icon-action.dxbl-btn-success {
    color: var(--bz-color-ok);
    --dxbl-btn-hover-bg: color-mix(in srgb, var(--bz-color-ok) 12%, transparent);
    --dxbl-btn-hover-color: var(--bz-color-ok);
    --dxbl-btn-hover-border-color: transparent;
    --dxbl-btn-active-bg: color-mix(in srgb, var(--bz-color-ok) 20%, transparent);
    --dxbl-btn-active-color: var(--bz-color-ok);
    --dxbl-btn-active-border-color: transparent;
}

.bz-icon-action:disabled,
.bz-icon-action.dxbl-disabled {
    opacity: .45;
    cursor: not-allowed;
}

/* Plain <button>/<a> flavour (RecordDocuments.razor, PM Files.razor, …) — same
   lightweight action, without a DevExpress cascade to route through. */
button.bz-icon-action:hover:not(:disabled),
a.bz-icon-action:hover {
    background: var(--bz-color-surface-2);
    border-color: transparent;
    color: var(--bz-color-accent);
    text-decoration: none;
}

button.bz-icon-action.kt-btn-danger,
a.bz-icon-action.kt-btn-danger {
    color: var(--bz-color-danger);
}

button.bz-icon-action.kt-btn-danger:hover:not(:disabled),
a.bz-icon-action.kt-btn-danger:hover {
    background: color-mix(in srgb, var(--bz-color-danger) 12%, transparent);
    border-color: transparent;
    color: var(--bz-color-danger);
}

/* The one chip context: the page-header action row (PageHeader.razor's
   `.bz-page-actions`). These actions stand alone — nothing around them draws an
   edge, and on a phone the header row IS the page's control strip — so here the
   border and surface come back. Everything else inherits the lightweight rest
   state above. Never widen this selector to "any toolbar": that is #113 again. */
.bz-page-actions .bz-icon-action {
    border-color: var(--bz-color-border);
    background: var(--bz-color-surface);
}

.bz-page-actions .dxbl-btn.bz-icon-action {
    --dxbl-btn-bg: var(--bz-color-surface);
    --dxbl-btn-border-color: var(--bz-color-border);
    --dxbl-btn-hover-border-color: var(--bz-color-border-strong);
    --dxbl-btn-active-border-color: var(--bz-color-border-strong);
}

.bz-page-actions button.bz-icon-action:hover:not(:disabled),
.bz-page-actions a.bz-icon-action:hover {
    border-color: var(--bz-color-border-strong);
}

/* ── 3. Text actions ──────────────────────────────────────────────────────────
   On a desktop these stay links — that is the right weight for a secondary action
   next to a real button. On a touch device a bare word is not a target, so the
   coarse-pointer block below turns them into ghost buttons. `.bz-page-back` and
   the dashboard's own text actions carry the same treatment in their own
   stylesheets (PageHeader.razor.css, dashboard.css) because they are scoped. */
.dxbl-btn.dxbl-btn-link:hover,
.kt-btn-ghost:hover,
.kt-btn-dim:hover {
    text-decoration: underline;
}

/* ── 4. Metronic classes the theme never shipped ──────────────────────────────
   Profile.razor and the support-session banner in MainLayout.razor ask for
   kt-btn-light / kt-btn-success / kt-btn-danger; Metronic defines none of them, so
   those buttons fell back to the bare .kt-btn primary fill. Defining them here
   fixes every call site at once and needs no page edits. */
.kt-btn.kt-btn-light {
    background-color: var(--bz-color-surface-2);
    color: var(--bz-color-fg);
    border: 1px solid var(--bz-color-border);
}

.kt-btn.kt-btn-light:hover {
    background-color: var(--bz-color-surface-3);
    border-color: var(--bz-color-border-strong);
    color: var(--bz-color-fg);
}

.kt-btn.kt-btn-success {
    background-color: var(--bz-color-ok);
    color: #fff;
    border: 1px solid var(--bz-color-ok);
}

.kt-btn.kt-btn-success:hover {
    background-color: color-mix(in srgb, var(--bz-color-ok) 85%, black);
    border-color: color-mix(in srgb, var(--bz-color-ok) 85%, black);
    color: #fff;
}

.kt-btn.kt-btn-danger:not(.bz-icon-action) {
    background-color: var(--bz-color-danger);
    color: #fff;
    border: 1px solid var(--bz-color-danger);
}

.kt-btn.kt-btn-danger:not(.bz-icon-action):hover {
    background-color: color-mix(in srgb, var(--bz-color-danger) 85%, black);
    border-color: color-mix(in srgb, var(--bz-color-danger) 85%, black);
    color: #fff;
}

/* ── 5. Touch targets ─────────────────────────────────────────────────────────
   The ONE place button sizes grow for a finger. Deliberately keyed on
   (pointer: coarse) rather than a width breakpoint: a landscape tablet is wide and
   still finger-driven, and a narrow desktop window is neither. The DevExpress
   composite exclusions above out-specify `.dxbl-btn` here too, so widget internals
   are left alone. */
@media (pointer: coarse) {
    /* 16px prevents focus zoom on phone browsers; editors remain large enough to tap. */
    .dxbl-text-edit.dxbl-text-edit {
        --dxbl-text-edit-font-size: var(--bz-fs-lg);
        --dxbl-text-edit-padding-y: var(--bz-space-2);
        min-height: 44px;
    }
    .bz-input.bz-input,
    .bz-search-input.bz-search-input input,
    .kt-input.kt-input,
    .kt-textarea.kt-textarea,
    .kt-select.kt-select { min-height: 44px; font-size: var(--bz-fs-lg); }

    .dxbl-btn {
        min-height: 44px;
        --dxbl-btn-padding-x: 1rem;
        --dxbl-btn-padding-y: 0.5rem;
    }

    .bz-icon-action {
        width: 44px;
        min-width: 44px;
        height: 44px;
    }

    .bz-icon-action i,
    .bz-icon-action .dxbl-image {
        font-size: 1.25rem;
    }

    .kt-btn { height: 44px; padding-inline: 16px; }
    .kt-btn.kt-btn-icon { width: 44px; height: 44px; }
    .kt-btn.kt-btn-sm { height: 40px; }
    .kt-btn.kt-btn-sm.kt-btn-icon { width: 40px; height: 40px; }

    /* Hit area only — these sit inline in a chip/toast/avatar row, so growing the
       box instead of the padding would move the layout around them. */
    .bz-chip-x { padding: var(--bz-space-2); }
    .bz-toast-close { padding: var(--bz-space-2); }
    .bz-avatar-action { width: 40px; height: 40px; }

    /* Grid command cells stay denser than the rest — a row of 44px buttons turns a
       10-row grid into a scroll marathon — but 28px is not a target either. */
    .dxbl-grid td.dxbl-grid-command-cell .dxbl-btn { min-height: 36px; }

    .dxbl-grid td.dxbl-grid-command-cell .bz-icon-action {
        width: 36px;
        min-width: 36px;
        height: 36px;
    }

    .bz-toolbar,
    .bz-form-actions { gap: var(--bz-space-3); }

    /* Text actions become ghost buttons: a bare word is not a tappable control. */
    .dxbl-btn.dxbl-btn-link {
        --dxbl-btn-bg: var(--bz-color-surface);
        --dxbl-btn-border-color: var(--bz-color-border);
        --dxbl-btn-hover-bg: var(--bz-color-surface-2);
        --dxbl-btn-text-decoration: none;
        --dxbl-btn-padding-x: var(--bz-space-3);
    }

    .dxbl-btn.dxbl-btn-link:hover { text-decoration: none; }

    /* Text ghosts only — an ICON-only ghost/dim button (the login password eye,
       the search-modal close, the sidebar logout) stays lightweight per #113 round 2. */
    .kt-btn-ghost:not(.kt-btn-icon),
    .kt-btn-dim:not(.kt-btn-icon) {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
        padding-inline: var(--bz-space-3);
        border: 1px solid var(--bz-color-border);
        border-radius: var(--bz-radius-sm);
        background-color: var(--bz-color-surface);
        text-decoration: none;
    }

    .kt-btn-ghost:not(.kt-btn-icon):hover,
    .kt-btn-dim:not(.kt-btn-icon):hover { text-decoration: none; }

    /* GitLab #127: dropdown options were ~34px rows with tight padding on a phone. DevExpress paints
       a list-box row entirely from its own variables — --dxbl-list-box-item-padding-* for the <li>
       (list pattern: DxComboBox, DxTagBox, DxListBox) and --dxbl-list-box-item-cell-padding-* for the
       <td> (table pattern) — so widen those rather than the element, per the button-system rule. The
       .dxbl-sm / .dxbl-lg size variants out-specify the bare container in the DevExpress theme,
       hence the size-variant selectors and still no !important. Native <select> options are drawn by the OS
       and cannot be styled at all — nothing to do there. */
    .dxbl-list-box,
    .dxbl-list-box.dxbl-sm,
    .dxbl-list-box.dxbl-lg,
    .dxbl-list-box-render-container,
    .dxbl-list-box-render-container.dxbl-sm,
    .dxbl-list-box-render-container.dxbl-lg {
        --dxbl-list-box-item-padding-y: 0.7rem;
        --dxbl-list-box-item-padding-x: 0.9rem;
        --dxbl-list-box-item-cell-padding-y: 0.7rem;
        --dxbl-list-box-item-cell-padding-x: 0.9rem;
        --dxbl-list-box-font-size: var(--bz-fs-md);
    }

    /* The padding above lands just under 44px at the small size mode; this is the floor. */
    /* Verified DOM (26.1.3): a DxComboBox dropdown is .dxbl-list-box-render-container > dxbl-scroll-viewer
       > .dxbl-scroll-viewer-content > ul > li — the variables live on the render container, not on a
       .dxbl-list-box element, which only the standalone DxListBox renders. */
    :is(.dxbl-list-box, .dxbl-list-box-render-container) .dxbl-scroll-viewer-content > ul > li {
        min-height: 44px;
        display: flex;
        align-items: center;
    }

    /* GitLab #126: BzTabs now renders DevExpress's prev/next scroll buttons. They are ordinary
       .dxbl-btn instances, so the height floor above already reaches them — this only guarantees the
       horizontal half of the target, which their icon-only width does not.
       Task 322: they carry no `.dxbl-tabs-scroll-btn-*` class (the name this rule used to target);
       they are the direct button children of a scrollable tab list, so the finger-sized width was
       never actually applied on a phone. */
    .dxbl-tabs > .dxbl-tabs-tablist.dxbl-tabs-tablist-scrollable > .dxbl-btn {
        min-width: 44px;
    }
}

/* Plain <button> and <a> styled as buttons via legacy module CSS classes —
   normalize to match DxButton visual weight */
.btn-primary, a.btn-primary {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.3rem 0.8rem;
    background: var(--bz-color-accent);
    color: #fff;
    border: 1px solid var(--bz-color-accent);
    border-radius: var(--bz-radius-sm);
    font-family: var(--bz-font-sans);
    font-size: var(--bz-fs-sm);
    font-weight: var(--bz-fw-medium);
    cursor: pointer;
    text-decoration: none;
    line-height: 1.4;
}
.btn-primary:hover, a.btn-primary:hover {
    background: var(--bz-color-accent-hover);
    border-color: var(--bz-color-accent-hover);
    text-decoration: none;
    color: #fff;
}

.btn-secondary, a.btn-secondary {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.3rem 0.8rem;
    background: var(--bz-color-surface);
    color: var(--bz-color-fg);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-sm);
    font-family: var(--bz-font-sans);
    font-size: var(--bz-fs-sm);
    font-weight: var(--bz-fw-medium);
    cursor: pointer;
    text-decoration: none;
    line-height: 1.4;
}
.btn-secondary:hover, a.btn-secondary:hover {
    background: var(--bz-color-surface-3);
    border-color: var(--bz-color-border-strong);
    text-decoration: none;
    color: var(--bz-color-fg);
}

.btn-warning, a.btn-warning {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.3rem 0.8rem;
    background: var(--bz-color-warn);
    color: #fff;
    border: 1px solid var(--bz-color-warn);
    border-radius: var(--bz-radius-sm);
    font-family: var(--bz-font-sans);
    font-size: var(--bz-fs-sm);
    font-weight: var(--bz-fw-medium);
    cursor: pointer;
    text-decoration: none;
    line-height: 1.4;
}

.btn-danger, a.btn-danger {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.3rem 0.8rem;
    background: var(--bz-color-danger);
    color: #fff;
    border: 1px solid var(--bz-color-danger);
    border-radius: var(--bz-radius-sm);
    font-family: var(--bz-font-sans);
    font-size: var(--bz-fs-sm);
    font-weight: var(--bz-fw-medium);
    cursor: pointer;
    text-decoration: none;
    line-height: 1.4;
}

/* Metronic's accent-as-text classes read --primary directly, so they get the dark-mode text tint
   here (task 343). Scoped to html.dark: in light mode they stay byte-for-byte Metronic's. */
html.dark .text-primary,
html.dark .kt-link,
html.dark .hover\:text-primary:hover {
    color: var(--bz-color-accent-text);
}

.btn-link, a.btn-link {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.3rem 0.5rem;
    background: transparent;
    color: var(--bz-color-accent-text);
    border: 1px solid transparent;
    border-radius: var(--bz-radius-sm);
    font-family: var(--bz-font-sans);
    font-size: var(--bz-fs-sm);
    font-weight: var(--bz-fw-medium);
    cursor: pointer;
    text-decoration: none;
    line-height: 1.4;
}
.btn-link:hover, a.btn-link:hover {
    background: var(--bz-color-surface-3);
    text-decoration: none;
    color: var(--bz-brand-primary);
}

.btn-icon-only, a.btn-icon-only {
    justify-content: center;
    min-width: 2rem;
    width: 2rem;
    padding-inline: 0;
}

.btn-icon-only i,
.btn-icon-only .ki-filled,
.btn-icon-only .ki-outline,
.btn-icon-only .ki-solid,
a.btn-icon-only i,
a.btn-icon-only .ki-filled,
a.btn-icon-only .ki-outline,
a.btn-icon-only .ki-solid {
    font-size: 1rem;
}

/* Toolbar layout — legacy class kept for backward compat */
.toolbar {
    display: flex;
    gap: var(--bz-space-2);
    align-items: center;
    margin-bottom: var(--bz-space-3);
    flex-wrap: wrap;
}

/* ── Page card — wraps a DxGrid (or any content) inside a surface card ── */
.bz-page-card {
    --bz-card-pad: var(--bz-space-4);
    min-width: 0;
    background: var(--bz-color-surface);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-lg);
    box-shadow: var(--bz-shadow-sm);
    /* The card owns the inset so no content ever touches its border; the genuinely full-bleed
       children (grids, head bars) cancel it below. */
    padding: var(--bz-card-pad);
    /* overflow-x: auto (not hidden) so a grid widened by column resize scrolls within the card
       instead of escaping to the page's own scroll container and shifting the whole layout. */
    overflow-x: auto;
    overflow-y: hidden;
    margin-bottom: var(--bz-space-5);
}

/* Full-bleed children: DevExpress grids (direct or inside a wrapper element) and the padded
   head bars own the card's edges, exactly as they did before the card gained padding. */
.bz-page-card > :is(.dxbl-grid, .bz-dxgrid, *:has(> .dxbl-grid),
                    .bz-page-card-head, .bz-dash-section-head) {
    margin-left: calc(var(--bz-card-pad) * -1);
    margin-right: calc(var(--bz-card-pad) * -1);
    /* Grids carry width:100%, which resolves against the card's CONTENT box — without this the
       negative margins would shift them left and leave a gap on the right. */
    width: calc(100% + var(--bz-card-pad) * 2);
}
.bz-page-card > :is(.dxbl-grid, .bz-dxgrid, *:has(> .dxbl-grid),
                    .bz-page-card-head, .bz-dash-section-head):first-child {
    margin-top: calc(var(--bz-card-pad) * -1);
}
.bz-page-card > :is(.dxbl-grid, .bz-dxgrid, *:has(> .dxbl-grid)):last-child {
    margin-bottom: calc(var(--bz-card-pad) * -1);
}
/* Head bar keeps its divider gap to the now-inset body below it. */
.bz-page-card > :is(.bz-page-card-head, .bz-dash-section-head):not(:last-child) {
    margin-bottom: var(--bz-card-pad);
}
/* These already carried their own inset — don't double it now that the card has one. */
.bz-page-card > .bz-card-pad { padding: 0; }
.bz-page-card > .bz-filter-bar { padding-left: 0; padding-right: 0; }

.bz-page-card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--bz-space-3) var(--bz-space-4);
    border-bottom: 1px solid var(--bz-color-border);
    background: var(--bz-color-surface-2);
    gap: var(--bz-space-3);
    flex-wrap: wrap;
}

.bz-page-card-title {
    font-size: var(--bz-fs-md);
    font-weight: var(--bz-fw-semibold);
    color: var(--bz-color-fg);
    margin: 0;
}

.bz-page-card-body { overflow: auto; }

/* ── Styled toolbar — replaces bare <section class="toolbar"> ───────── */
.bz-toolbar {
    display: flex;
    align-items: center;
    gap: var(--bz-space-2);
    padding: var(--bz-space-3) 0;
    margin-bottom: var(--bz-space-3);
    flex-wrap: wrap;
}

/* Heading toolbar row above a grid/section card (pattern B — see AGENTS.md UI standard) */
.bz-grid-toolbar {
    margin-bottom: var(--bz-space-4);
}

/* Search input with leading magnifier icon */
.bz-search-input {
    position: relative;
    display: inline-flex;
    align-items: center;
    min-width: min(220px, 100%);
    max-width: 340px;
    flex: 1 1 220px;
}

/* `>`: the clear button's own icon must not be pulled over to the magnifier's spot. */
.bz-search-input > i {
    position: absolute;
    left: 10px;
    font-size: 15px;
    color: var(--bz-color-fg-subtle);
    pointer-events: none;
    line-height: 1;
}

.bz-search-input input {
    width: 100%;
    /* Right padding leaves room for the clear button (task 340). */
    padding: 0.38rem 2rem 0.38rem 2.1rem;
    /* Field colour, like .kt-input and the DevExpress editors (task 343). */
    background: var(--bz-color-bg);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    color: var(--bz-color-fg);
    font-family: var(--bz-font-sans);
    font-size: var(--bz-fs-sm);
    line-height: 1.5;
    transition: border-color var(--bz-duration-fast) var(--bz-ease),
                box-shadow var(--bz-duration-fast) var(--bz-ease);
    min-height: var(--bz-control-height);
}

.bz-search-input input::placeholder { color: var(--bz-color-fg-subtle); }

.bz-search-input input:focus {
    outline: none;
    border-color: var(--bz-brand-primary);
    box-shadow: 0 0 0 3px var(--bz-color-focus-ring);
}

/* Task 340 (GitLab #212): BzSearchInput's clear button. Shown only while the box holds text — in CSS,
   because the component deliberately does not render while the user types. `.bz-search-bare` is the
   wrapper of the icon-less variant (ShowIcon=false), which keeps the input's own classes. */
.bz-search-bare {
    position: relative;
    display: flex;
    align-items: center;
    max-width: 100%;
}

.bz-search-bare > input {
    flex: 1 1 auto;
    padding-right: 2rem;
}

.bz-search-clear {
    position: absolute;
    right: 4px;
    top: 50%;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    padding: 0;
    border: 0;
    border-radius: var(--bz-radius-sm);
    background: transparent;
    color: var(--bz-color-fg-subtle);
    line-height: 1;
    cursor: pointer;
}

.bz-search-clear i { font-size: 0.8rem; }

.bz-search-clear:hover,
.bz-search-clear:focus-visible {
    background: var(--bz-color-surface-2);
    color: var(--bz-color-fg);
}

input:placeholder-shown ~ .bz-search-clear { display: none; }

/* BzClearFilters: a toolbar item, never stretched by the flex row. */
.bz-filters-clear { flex: 0 0 auto; }

/* Toolbar select filter */
.bz-toolbar-select {
    padding: 0.38rem 0.7rem;
    background: var(--bz-color-surface);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    color: var(--bz-color-fg);
    font-family: var(--bz-font-sans);
    font-size: var(--bz-fs-sm);
    min-height: var(--bz-control-height);
    cursor: pointer;
    transition: border-color var(--bz-duration-fast) var(--bz-ease);
}

.bz-toolbar-select:focus {
    outline: none;
    border-color: var(--bz-brand-primary);
    box-shadow: 0 0 0 3px var(--bz-color-focus-ring);
}

/* Toolbar spacer — pushes right-side actions to the end */
.bz-toolbar-spacer { flex: 1; }

/* Toolbar action group */
.bz-toolbar-actions {
    display: flex;
    align-items: center;
    gap: var(--bz-space-2);
    flex-shrink: 0;
}

/* ── Global status badge utilities ──────────────────────────────────── */
/* THE status chip for the whole product: `.bz-badge` plus exactly one tone. `StatusPill` renders
   the same two classes, the dashboards' `.bz-dash-badge*` duplicate was folded into it in task
   322, and no page defines a chip of its own.

   Five tones, and five is the whole list: neutral (no state), info, ok, warn, danger. Four other
   spellings were in circulation before task 322 — `success` and `muted` for two of these five, and
   `warning`, which was defined nowhere at all and rendered as unstyled text in a column of
   coloured chips. They are gone, and `DesignSystemRules` fails the build if a sixth name appears:
   a status that needs another colour needs a design decision, not a new class.

   A tone is never the only signal: every chip carries a word as well (see the accessibility
   section of docs/dev/guides/design-system.md). */
.bz-badge {
    display: inline-flex;
    align-items: center;
    padding: 2px 9px;
    border-radius: var(--bz-radius-pill);
    font-size: var(--bz-fs-xs);
    font-weight: var(--bz-fw-medium);
    white-space: nowrap;
    line-height: 1.5;
}

.bz-badge-ok      { color: var(--bz-color-ok);      background: color-mix(in srgb, var(--bz-color-ok)     13%, transparent); }
.bz-badge-warn    { color: var(--bz-color-warn);    background: color-mix(in srgb, var(--bz-color-warn)   13%, transparent); }
.bz-badge-danger  { color: var(--bz-color-danger);  background: color-mix(in srgb, var(--bz-color-danger) 13%, transparent); }
.bz-badge-info    { color: var(--bz-color-info);    background: color-mix(in srgb, var(--bz-color-info)   13%, transparent); }
.bz-badge-neutral { color: var(--bz-color-fg-muted); background: var(--bz-color-surface-2); }

/* Row of badges in a grid cell */
.bz-badge-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--bz-space-1);
    align-items: center;
}

/* ── Text utility helpers (referenced from multiple modules) ─────────── */
.bz-text-danger  { color: var(--bz-color-danger) !important; }
.bz-text-warn    { color: var(--bz-color-warn) !important; }
.bz-text-success { color: var(--bz-color-ok) !important; }

/* A bulleted list inside a card or alert, without the browser's default margins. */
.bz-plain-list {
    margin: var(--bz-space-2) 0 0;
    padding-inline-start: var(--bz-space-5);
}

/* `.text-muted` is NOT Bootstrap's here: the Metronic/Tailwind build emits it as
   `color: var(--muted)` — the muted *surface* color, not a text color (that is
   `--muted-foreground`). It therefore renders near-invisible, badly so in dark mode.
   ~258 usages across the web modules mean it, so alias it to the same theme-aware
   token `.bz-text-muted` uses instead of rewriting every call site. */
.text-muted,
.bz-text-muted   { color: var(--bz-color-fg-muted) !important; }
.bz-text-subtle  { color: var(--bz-color-fg-subtle) !important; }
.bz-text-accent  { color: var(--bz-color-accent-text) !important; }
.bz-fw-medium    { font-weight: var(--bz-fw-medium) !important; }
.bz-fw-semibold  { font-weight: var(--bz-fw-semibold) !important; }
.bz-fs-xs        { font-size: var(--bz-fs-xs) !important; }
.bz-fs-sm        { font-size: var(--bz-fs-sm) !important; }

/* ── Suggestion/alert strip (used in TenantAdmin) ───────────────────── */
.bz-suggestion {
    display: flex;
    align-items: flex-start;
    gap: var(--bz-space-3);
    padding: var(--bz-space-3) var(--bz-space-4);
    border-radius: var(--bz-radius-md);
    background: color-mix(in srgb, var(--bz-color-info) 8%, transparent);
    border: 1px solid color-mix(in srgb, var(--bz-color-info) 25%, transparent);
    margin-top: var(--bz-space-3);
    font-size: var(--bz-fs-sm);
    color: var(--bz-color-fg-muted);
}
.bz-suggestion i { color: var(--bz-color-info); margin-top: 2px; flex-shrink: 0; font-size: 16px; }

/* ── Form feedback row ──────────────────────────────────────────────── */
.bz-form-feedback {
    margin-bottom: var(--bz-space-3);
}

/* ── Improved DxGrid inside page cards ─────────────────────────────── */
/* Remove outer border when grid sits inside a bz-page-card */
.bz-page-card .dxbl-grid {
    border: none !important;
    border-radius: 0 !important;
}

/* Column headers. Padding, weight and colour are variables (see the density block above); only
   the three things DevExpress has no variable for are written as properties here.
   `.dxbl-grid-header-cell` — what this rule used to target — is not a class DevExpress 26.1
   renders. The real header cell is a `th` in the header row of the grid table. */
.dxbl-grid .dxbl-grid-table > thead .dxbl-grid-header-row > th {
    font-size: var(--bz-fs-sm);
    text-transform: none;
    letter-spacing: 0;
    white-space: nowrap;
    vertical-align: middle;
}

/* BzCategoryPicker drop-down body (task 343, GitLab #226): the tree renders all its rows, so the
   body must be bounded and scroll itself or the popup outgrows the viewport and clips. */
.bz-cat-picker-body {
    max-height: min(24rem, 60dvh);
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* Standalone grids (outside bz-page-card) get soft corners, and scroll horizontally in place
   rather than letting a resized column push the whole page (see .bz-page-card above). */
.bz-dxgrid {
    border-radius: var(--bz-radius-md);
    overflow-x: auto;
    overflow-y: hidden;
}

/* Filter-row editors: compact, fill the cell, no visual competition with data rows */
.dxbl-grid .dxbl-grid-filter-row td {
    padding: 4px 6px !important;
    background: var(--bz-color-surface);
}
.dxbl-grid .bz-filter-cell {
    width: 100%;
    min-width: 0;
    font-size: var(--bz-fs-sm);
}
.dxbl-grid .bz-filter-range {
    display: inline-flex;
    gap: 4px;
    width: 100%;
}
.dxbl-grid .bz-filter-range .bz-filter-cell {
    width: 50%;
}

/* Row height used to live here as `!important` padding on `.dxbl-grid-data-row td`, a class
   DevExpress 26.1 does not render. It is now --dxbl-grid-text-cell-padding-y in the density
   block above, which the real cells actually read. */

/* Link styling inside grids */
.dxbl-grid a {
    color: var(--bz-color-accent-text);
    text-decoration: none;
    font-weight: var(--bz-fw-medium);
}
.dxbl-grid a:hover { text-decoration: underline; }

/* Pager. The grid's pager is a `.dxbl-pager`; `.dxbl-grid-pager` (what this rule used to name)
   is not a class DevExpress renders, so the separator and the type size below never applied.
   Everything here has a variable, so nothing is written as a property. */
.dxbl-grid .dxbl-pager {
    --dxbl-pager-bg: var(--bz-color-surface);
    --dxbl-pager-color: var(--bz-color-fg-muted);
    --dxbl-pager-font-size: var(--bz-fs-sm);
    --dxbl-pager-border-color: var(--bz-color-border);
    --dxbl-pager-page-btn-color: var(--bz-color-fg);
    --dxbl-pager-page-btn-hover-bg: var(--bz-color-surface-3);
    --dxbl-pager-active-page-btn-bg: color-mix(in srgb, var(--bz-color-accent) 16%, var(--bz-color-surface));
    --dxbl-pager-active-page-btn-color: var(--bz-color-fg);
    --dxbl-pager-active-page-btn-border-color: color-mix(in srgb, var(--bz-color-accent) 40%, transparent);
    border-top: 1px solid var(--bz-color-border);
}

/* Empty and loading states. Both are `.dxbl-grid-empty-data-area` content and were never styled
   at all, so "no data" read as body text in the middle of an otherwise framed grid. Colour only
   — the padding is a variable in the density block. */
.dxbl-grid .dxbl-grid-empty-data-area {
    color: var(--bz-color-fg-muted);
    font-size: var(--bz-fs-sm);
    text-align: center;
}

/* The not-found line is the one a user reads after filtering something away, so it carries the
   normal foreground; the loading line stays muted because it is transient chrome. */
.dxbl-grid .dxbl-grid-empty-data-area span.dxbl-grid-empty-data-not-found {
    color: var(--bz-color-fg);
    font-weight: var(--bz-fw-medium);
}

/* ── DevExpress popup / window — border refinements ─────────────────
   Dark-mode colours are handled by the DevExpress dark.min.css which is
   loaded dynamically by syncDxDarkTheme() in App.razor / SidebarFooter.razor.js
   whenever html.dark is active.  Only visual overrides that go beyond what
   the DevExpress theme provides are placed here. */
.dxbl-modal,
.dxbl-window {
    border-radius: var(--bz-radius-lg);
    box-shadow: var(--bz-shadow-lg);
}

/* Keep the width requested by each dialog's content, bounded to the viewport.
   DevExpress continues to own sizing and adaptive layout. */
.dxbl-modal .dxbl-popup {
    --dxbl-popup-margin: var(--bz-space-4);
    --dxbl-popup-scrollable-max-height: calc(100dvh - var(--bz-space-6));
    max-width: calc(100vw - var(--bz-space-6)) !important;
}

/* Many existing dialogs do not opt into vendor scrollable mode. Bound their content
   centrally and scroll the body, so long forms keep their header/footer reachable. */
.dxbl-modal .dxbl-popup > .dxbl-modal-content {
    max-height: var(--dxbl-popup-scrollable-max-height);
    min-height: 0;
    overflow: hidden;
}
.dxbl-modal .dxbl-modal-content > .dxbl-modal-body {
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* Dialog footer. The element is `.dxbl-popup-buttons-area`; `.dxbl-popup-footer` is a variable
   family, not a class, so this separator never appeared under a dialog's buttons. */
.dxbl-modal .dxbl-popup-buttons-area,
.dxbl-window .dxbl-popup-buttons-area {
    border-top: 1px solid var(--bz-color-border);
    gap: var(--bz-space-2);
    flex-wrap: wrap;
}

/* ── Modal / drawer form polish ──────────────────────────────────────
   Consistent presentation for DxFormLayout-based forms hosted inside
   DevExpress popups (create/edit/confirm dialogs across all modules).
   Inputs fill their column so forms never look cramped, and captions
   read consistently. Scoped to popups so page-level layouts are intact.

   Every selector this rule used to carry was a name DevExpress 26.1 does not render: the form
   layout root is `.dxbl-fl` (not `.dxbl-form-layout`), and DxTextBox, DxMemo, DxSpinEdit,
   DxDateEdit, DxComboBox and DxTagBox all share the `.dxbl-text-edit` root rather than having
   one class each. So no dialog in the product ever got full-width inputs. */
:is(.dxbl-modal, .dxbl-window) .dxbl-fl .dxbl-fl-item > .dxbl-fl-ctrl > :is(.dxbl-text-edit, .dxbl-dropdown) {
    width: 100%;
}

/* ── DxGrid inline edit form ─────────────────────────────────── */
.bz-edit-form {
    background: var(--bz-color-surface-2);
    border-top: 2px solid var(--bz-color-accent);
}

.bz-edit-form-footer {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 12px 16px 16px;
    border-top: 1px solid var(--bz-color-border);
}

/* ── IntelligentAutomation visibility hardening (prompt 040) ─────────── */

/* Filter bar: a row of labelled inputs above a grid. */
.bz-filter-bar {
    display: flex;
    flex-wrap: wrap;
    gap: var(--bz-space-3);
    align-items: flex-end;
    padding: var(--bz-space-3) var(--bz-space-4);
}

.bz-filter-bar > * { max-width: 100%; }
.bz-filter-bar > .bz-search-input { align-self: flex-end; }

.bz-inline-label { display: inline-flex; align-items: center; gap: var(--bz-space-2); margin: 0; }
.bz-input-inline { width: auto; min-width: 9rem; }

/* Chip editor (allowed tools / permissions). */
.bz-chip-editor { margin: var(--bz-space-3) 0; }
.bz-chips { display: flex; flex-wrap: wrap; gap: var(--bz-space-2); margin: var(--bz-space-2) 0; }
.bz-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--bz-space-1);
    padding: 2px var(--bz-space-2);
    font-size: var(--bz-fs-sm);
    background: var(--bz-color-surface-2);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-pill);
}
/* Assignee chip carrying a face instead of initials — same footprint as .bz-chip so a mixed row
   (some people have a picture, most do not) stays on one baseline. */
.bz-chip-img {
    width: 22px;
    height: 22px;
    border-radius: var(--bz-radius-pill);
    object-fit: cover;
    border: 1px solid var(--bz-color-border);
    background: var(--bz-color-surface-2);
    flex: 0 0 auto;
}
.bz-chip-read    { color: var(--bz-color-accent); background: color-mix(in srgb, var(--bz-color-accent) 12%, transparent); }
.bz-chip-propose { color: var(--bz-color-ok); background: color-mix(in srgb, var(--bz-color-ok) 12%, transparent); }
.bz-chip-x {
    border: none;
    background: transparent;
    cursor: pointer;
    color: var(--bz-color-fg-muted);
    font-size: 1rem;
    line-height: 1;
    padding: 0 2px;
}
.bz-chip-x:hover { color: var(--bz-color-danger); }

/* Disabled (no-access) dashboard quick-action card. */
.bz-dash-nav-card.bz-disabled { opacity: .55; cursor: not-allowed; pointer-events: none; }

/* Subtle inner panel (e.g. MCP tool preview). */
.bz-subtle { background: var(--bz-color-surface-2); box-shadow: none; }

/* Ordered/unordered helper list (MCP setup steps). */
.bz-list { margin: var(--bz-space-2) 0; padding-left: var(--bz-space-5); }
.bz-list li { margin-bottom: var(--bz-space-1); }

/* Review item + secret banner + advanced disclosure (shared by IA pages). */
.bz-review-item {
    padding: var(--bz-space-3) var(--bz-space-4);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    margin-bottom: var(--bz-space-3);
    background: var(--bz-color-surface);
}
.bz-review-item-head { display: flex; align-items: center; gap: var(--bz-space-2); margin-bottom: var(--bz-space-2); }
.bz-advanced { margin: var(--bz-space-2) 0; }
.bz-advanced > summary { cursor: pointer; color: var(--bz-color-fg-muted); font-size: var(--bz-fs-sm); }
.bz-secret-banner { border-left: 3px solid var(--bz-color-accent); padding: var(--bz-space-3) var(--bz-space-4); }

/* ─────────────────────────────────────────────────────────────────────────
   Shared form controls & helper text.
   These utility classes are used by hand-authored (non-DevExpress) forms across
   many module pages (AI smart entry, MCP clients, settings panels, wizards…).
   Defining them globally here keeps those pages visually consistent with the
   DevExpress surfaces instead of falling back to unstyled native controls.
   ───────────────────────────────────────────────────────────────────────── */

/* Field label — block by default so the control sits on its own line. */
.bz-label {
    display: block;
    margin-bottom: var(--bz-space-1);
    font-size: var(--bz-fs-sm);
    font-weight: var(--bz-fw-medium);
    color: var(--bz-color-fg-muted);
}

/* Text input / textarea / select — full-width control matching the search box. */
.bz-input {
    display: block;
    width: 100%;
    padding: 0.45rem 0.75rem;
    background: var(--bz-color-surface);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    color: var(--bz-color-fg);
    font-family: var(--bz-font-sans);
    font-size: var(--bz-fs-sm);
    line-height: 1.5;
    min-height: var(--bz-control-height);
    transition: border-color var(--bz-duration-fast) var(--bz-ease),
                box-shadow var(--bz-duration-fast) var(--bz-ease);
}
textarea.bz-input { min-height: 5rem; resize: vertical; }
.bz-input::placeholder { color: var(--bz-color-fg-subtle); }
.bz-input:focus {
    outline: none;
    border-color: var(--bz-brand-primary);
    box-shadow: 0 0 0 3px var(--bz-color-focus-ring);
}
.bz-input:disabled,
.bz-input[readonly],
.bz-input[readonly="readonly"] {
    background: var(--bz-color-surface-2);
    color: var(--bz-color-fg-muted);
    cursor: not-allowed;
}
.bz-input:is(.invalid, [aria-invalid="true"]),
.kt-input:is(.invalid, [aria-invalid="true"]) { border-color: var(--bz-color-danger); }
.validation-message { color: var(--bz-color-danger); font-size: var(--bz-fs-sm); margin-top: var(--bz-space-1); }
.bz-mono { font-family: var(--bz-font-mono); font-size: var(--bz-fs-xs); }

/* GitLab #165 / task 250 (#37): large drag-and-drop upload zone — a stretched invisible InputFile
   over a styled label gives native click-to-browse AND drag-drop with zero JS. Promoted from
   ProjectManagement's IncomingFilesGrid.razor.css so every module can use the same one definition;
   this is a plain (non-`::deep`) global rule, unlike the scoped copy it replaced. */
.bz-import-dropzone {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--bz-space-2);
    padding: 2rem 1rem;
    border: 2px dashed var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    cursor: pointer;
    text-align: center;
}
.bz-import-dropzone:hover { border-color: var(--bz-brand-primary); }
.bz-import-dropzone input[type="file"] {
    position: absolute;
    inset: 0;
    opacity: 0;
    cursor: pointer;
}

/* Staged record-document upload — file chip, type picker and description composer with the
   Upload button parked in the textarea's bottom-right corner. */
.bz-doc-upload {
    display: flex;
    flex-direction: column;
    gap: var(--bz-space-2);
    margin-bottom: var(--bz-space-3);
    padding: var(--bz-space-3);
    background: var(--bz-color-surface-2);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
}
.bz-doc-upload-file { display: flex; align-items: center; gap: var(--bz-space-2); }
.bz-doc-compose { position: relative; }
.bz-doc-compose textarea.bz-input { padding-bottom: 2.9rem; }
.bz-doc-compose > .kt-btn { position: absolute; right: var(--bz-space-2); bottom: var(--bz-space-2); }

/* Inline control (e.g. the voice-language picker) keeps its intrinsic width even
   though .bz-input sets width:100% — compound selector wins regardless of order. */
.bz-input.bz-input-inline { width: auto; min-width: 9rem; }

/* Responsive two-column form grid for review / detail field sets. */
.bz-form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
    gap: var(--bz-space-3) var(--bz-space-4);
    margin: var(--bz-space-3) 0;
}

/* Muted helper / hint line with an optional leading icon. */
.bz-hint {
    display: flex;
    align-items: flex-start;
    gap: var(--bz-space-2);
    margin: var(--bz-space-2) 0 0;
    color: var(--bz-color-fg-muted);
    font-size: var(--bz-fs-sm);
    line-height: 1.45;
}
.bz-hint i { color: var(--bz-color-info); margin-top: 1px; flex-shrink: 0; font-size: 15px; }

/* Inline error and empty-state messages. */
.bz-error-msg {
    margin: var(--bz-space-2) 0 0;
    color: var(--bz-color-danger);
    font-size: var(--bz-fs-sm);
}
.bz-empty-msg {
    margin: var(--bz-space-3) 0;
    color: var(--bz-color-fg-subtle);
    font-size: var(--bz-fs-sm);
}

/* Small button size modifier (pairs with .btn-primary/.btn-secondary). */
.btn-sm {
    padding: 0.2rem 0.55rem;
    font-size: var(--bz-fs-xs);
}

/* Lightweight data table for simple read-only lists (non-DevExpress surfaces). */
/* Task 303: horizontal scroll container for a table wider than the viewport (the body must never scroll sideways). */
.bz-table-scroll {
    overflow-x: auto;
    max-width: 100%;
}

.bz-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--bz-fs-sm);
    margin: var(--bz-space-2) 0;
}
.bz-table th,
.bz-table td {
    text-align: left;
    padding: 0.45rem 0.6rem;
    border-bottom: 1px solid var(--bz-color-border);
}
.bz-table th {
    color: var(--bz-color-fg-muted);
    font-weight: var(--bz-fw-semibold);
    background: var(--bz-color-surface-2);
}
.bz-table tbody tr:hover { background: var(--bz-color-surface-2); }

/* GitLab #124: a row that navigates says so. The hover highlight above is already the feedback, so
   this is only the cursor — the @onclick and the stopPropagation on the interactive cells live in
   the component (TaskInboxList). */
.bz-table tr.bz-row-link { cursor: pointer; }

/* ── Business Partner (Relationship Management) wizard + detail ─────────
   This app ships only a subset of Bootstrap utilities (spacing/sizing,
   not flex/grid/alert/form-select), so these layout helpers live here as
   first-class bz-* classes loaded globally via tokens+shell. */

/* Generic flex helpers (replacements for the missing d-flex utilities). */
.bz-form-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--bz-space-3);
    margin-top: var(--bz-space-5);
}
.bz-actions-end {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: var(--bz-space-2);
    margin-bottom: var(--bz-space-3);
}
.bz-actions-end-inline { display: inline-flex; gap: var(--bz-space-2); }
/* BzCalculateMenu: the inline margin-goal input and the resulting-margin note beside the button. */
.bz-calc-margin-goal { align-items: center; margin-left: var(--bz-space-2); }
.bz-calc-margin-input { width: 8rem; }

/* Centered wizard column. */
.bz-wizard { max-width: 980px; margin: 0 auto; padding: var(--bz-space-5); }
/* Wide variant: fill 90% of the available width (tenant onboarding). The shared EmptyLayout caps
   its content column (max-width 728px), so a page hosting a wide wizard lifts that cap too. */
.bz-wizard-wide { max-width: 90%; }
/* EmptyLayout caps content via max-inline-size and sizes shrink-to-fit as a flex item — lift both. */
main.empty-layout:has(.bz-wizard-wide) { max-inline-size: none; width: 100%; flex: 1 1 auto; }

/* Horizontal step indicator. */
.bz-wizard-steps {
    display: flex;
    /* 7+ steps with long labels overflow the card and clip the last tab — wrap instead. */
    flex-wrap: wrap;
    gap: var(--bz-space-2);
    list-style: none;
    padding: 0;
    margin: 0 0 var(--bz-space-5);
}
.bz-wizard-steps li {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: break-word;
    text-align: center;
    padding: 0.6rem 0.5rem;
    border-radius: var(--bz-radius-md);
    background: var(--bz-color-surface-2);
    color: var(--bz-color-fg-subtle);
    font-weight: var(--bz-fw-medium);
    font-size: var(--bz-fs-sm);
    border: 1px solid var(--bz-color-border);
}
.bz-wizard-steps li.active {
    background: var(--bz-brand-primary);
    color: var(--bz-color-accent-fg);
    border-color: var(--bz-brand-primary);
}
.bz-wizard-steps li.done {
    background: var(--bz-color-surface);
    color: var(--bz-color-ok);
    border-color: var(--bz-color-ok);
}
.bz-wizard-steps-clickable li { cursor: pointer; transition: background .12s, color .12s; }
.bz-wizard-steps-clickable li:hover { border-color: var(--bz-brand-primary); }
/* Card picker for "which service is this?" — one tile per choice, its own line of purpose text.
   Used by the external-connection wizard; any step that asks the user to pick one of a handful of
   named things can reuse it. */
.bz-choice-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr));
    gap: var(--bz-space-2);
    margin: var(--bz-space-2) 0 var(--bz-space-4);
}
.bz-choice {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    text-align: left;
    padding: var(--bz-space-3);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    background: var(--bz-color-surface);
    color: var(--bz-color-fg);
    cursor: pointer;
}
.bz-choice:hover { border-color: var(--bz-brand-primary); }
.bz-choice.is-selected { border-color: var(--bz-brand-primary); box-shadow: inset 0 0 0 1px var(--bz-brand-primary); }
.bz-choice-title { font-weight: var(--bz-fw-semibold); }

.bz-wizard-step { padding: var(--bz-space-2) 0 var(--bz-space-4); }
.bz-wizard-step h5 {
    font-size: var(--bz-fs-md);
    font-weight: var(--bz-fw-semibold);
    margin: 0 0 var(--bz-space-2);
    color: var(--bz-color-fg);
}
.bz-wizard-step h6 {
    font-size: var(--bz-fs-sm);
    font-weight: var(--bz-fw-semibold);
    margin: var(--bz-space-4) 0 var(--bz-space-2);
    color: var(--bz-color-fg);
}

/* Selectable platform cards in the Add-connector wizard. */
.bz-connector-platform {
    display: flex;
    align-items: center;
    gap: var(--bz-space-3);
    padding: var(--bz-space-3);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    background: transparent;
    color: var(--bz-color-fg);
    cursor: pointer;
    text-align: left;
    transition: background .12s, border-color .12s, box-shadow .12s;
}
.bz-connector-platform:hover { border-color: var(--bz-color-accent); }
.bz-connector-platform-active {
    border-color: var(--bz-color-accent);
    background: color-mix(in srgb, var(--bz-color-accent) 12%, transparent);
    box-shadow: inset 0 0 0 1px var(--bz-color-accent);
}
.bz-connector-platform-active .ki-filled { color: var(--bz-color-accent); }

/* Field label + radio group row. */
.bz-field-label {
    display: block;
    font-size: var(--bz-fs-sm);
    font-weight: var(--bz-fw-medium);
    color: var(--bz-color-fg-muted);
    margin-bottom: var(--bz-space-2);
}
.bz-field-block { margin-bottom: var(--bz-space-4); }
.bz-radio-row { display: flex; gap: var(--bz-space-5); flex-wrap: wrap; }
.bz-radio-row label { display: inline-flex; align-items: center; gap: var(--bz-space-1); cursor: pointer; }
.bz-help { color: var(--bz-color-fg-subtle); font-size: var(--bz-fs-sm); margin: 0 0 var(--bz-space-4); }

/* Padded body for a bz-page-card whose content sits directly under a bz-page-card-head
   (the card itself has no inner padding and clips to its radius). */
.bz-card-pad { padding: var(--bz-space-4) var(--bz-space-5); }

/* MCP clients page: endpoint row + tool-picker dropdown option + effective-tools preview. */
.bz-mcp-endpoint { display: flex; align-items: center; gap: var(--bz-space-2); flex-wrap: wrap; margin: var(--bz-space-2) 0; }
.bz-mcp-option { padding: var(--bz-space-1) 0; }
.bz-mcp-option-head { display: flex; align-items: center; gap: var(--bz-space-2); }
/* GitLab #90: .bz-page-card clips (overflow-y: hidden) rather than scrolling, so a tag box or
   preview block that grows past the card's height was pushing its own content unreachably off the
   bottom (e.g. "Select all" on either picker). Both scroll internally instead of growing unbounded. */
.bz-mcp-tagbox { display: block; width: 100%; max-height: 30vh; overflow-y: auto; }
.bz-mcp-preview {
    margin-top: var(--bz-space-5);
    padding: var(--bz-space-4);
    background: var(--bz-color-surface-2);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    max-height: 25vh;
    overflow-y: auto;
}
.bz-mcp-preview > .bz-label {
    display: block;
    margin-bottom: var(--bz-space-2);
    text-transform: uppercase;
    font-size: var(--bz-fs-xs);
    letter-spacing: .04em;
    color: var(--bz-color-fg-subtle);
}

/* The row-actions cell (BzGridActions): the same 4px gap the grid toolbar uses between its own
   icon buttons, and no wrapping — a cell that wraps to a second line makes one row taller than
   the rest of the grid. inline-flex, so the box is as wide as its buttons and an under-sized
   column overflows the way ordinary inline content does rather than sliding left under the
   neighbouring cell and eating the click (see the command-cell rule above). */
.bz-grid-actions {
    display: inline-flex;
    align-items: center;
    gap: var(--bz-space-1);
    flex-wrap: nowrap;
}

/* "Select all / Clear" shortcuts under a tag-box picker. */
.bz-picker-actions { display: flex; gap: var(--bz-space-3); margin-top: var(--bz-space-2); }

/* Assistant chat: rendered markdown inside message bubbles. */
.bz-ai-chat-md { font-size: var(--bz-fs-sm); line-height: 1.5; }
.bz-ai-chat-md p { margin: 0 0 var(--bz-space-2); }
.bz-ai-chat-md p:last-child { margin-bottom: 0; }
.bz-ai-chat-md ul, .bz-ai-chat-md ol { margin: 0 0 var(--bz-space-2); padding-left: 1.2rem; }
.bz-ai-chat-md code {
    font-family: var(--bz-font-mono);
    font-size: var(--bz-fs-xs);
    background: var(--bz-color-surface-2);
    border-radius: var(--bz-radius-sm);
    padding: 0 4px;
}
.bz-ai-chat-md pre {
    background: var(--bz-color-surface-2);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    padding: var(--bz-space-2) var(--bz-space-3);
    overflow-x: auto;
}
.bz-ai-chat-md table { border-collapse: collapse; margin: var(--bz-space-2) 0; max-width: 100%; }
.bz-ai-chat-md th, .bz-ai-chat-md td {
    border: 1px solid var(--bz-color-border);
    padding: 4px 8px;
    text-align: left;
    font-size: var(--bz-fs-xs);
}
.bz-ai-chat-md th { background: var(--bz-color-surface-2); font-weight: var(--bz-fw-semibold); }

/* GitLab #123: the assistant HAS been emitting record links since task 201 — they were simply
   invisible. Metronic's preflight resets every anchor to `color: inherit; text-decoration: inherit`
   and this file's own base rule drops the underline, so a linked business partner rendered as plain
   body text. Chat output is the one place an anchor must announce itself, so restate both here. */
.bz-ai-chat-md a,
.bz-ai-chat-table a:not(.bz-icon-action) {
    color: var(--bz-color-accent-text);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.bz-ai-chat-md a:hover,
.bz-ai-chat-table a:not(.bz-icon-action):hover {
    color: var(--bz-color-accent-hover);
    text-decoration-thickness: 2px;
}

/* Assistant chat: Results cards (read-tool datasets rendered as tables + export actions).
   The block sizing lives with the shared .bz-ai-chat-dock group (task 300 / GitLab #137). */
.bz-ai-chat-result-card {
    background: var(--bz-color-surface);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    padding: var(--bz-space-2) var(--bz-space-3);
    margin-bottom: 0; /* the dock body's gap separates cards */
    /* The card grows with the dock so its table — not the whole block — is what scrolls; it never
       shrinks below its table's minimum, so several result sets make the dock body scroll instead
       of squeezing every table to a single row. */
    flex: 1 0 auto;
    display: flex; flex-direction: column;
}
.bz-ai-chat-result-head {
    display: flex;
    align-items: center;
    gap: var(--bz-space-2);
    font-weight: var(--bz-fw-semibold);
    font-size: var(--bz-fs-sm);
    margin-bottom: var(--bz-space-2);
}
.bz-ai-chat-result-head .bz-hint { margin: 0 0 0 auto; }
.bz-ai-chat-result-scroll { overflow: auto; flex: 1 1 auto; min-height: 12rem; border: 1px solid var(--bz-color-border); border-radius: var(--bz-radius-sm); }
.bz-ai-chat-table { width: 100%; border-collapse: collapse; font-size: var(--bz-fs-xs); }
.bz-ai-chat-table th, .bz-ai-chat-table td {
    border-bottom: 1px solid var(--bz-color-border);
    padding: 4px 8px;
    text-align: left;
    white-space: nowrap;
}
.bz-ai-chat-table th { position: sticky; top: 0; background: var(--bz-color-surface-2); font-weight: var(--bz-fw-semibold); }
.bz-ai-chat-result-actions { display: flex; gap: var(--bz-space-2); margin-top: var(--bz-space-2); flex: 0 0 auto; }

/* Schema-driven review form (AiSchemaForm): nested object groups + repeatable array items. */
.bz-schema-form { margin: var(--bz-space-2) 0; }
.bz-schema-form-caption { display: block; margin: var(--bz-space-3) 0 var(--bz-space-1); font-weight: var(--bz-fw-semibold); }
.bz-schema-form-group { margin-top: var(--bz-space-2); }
.bz-schema-form-item {
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    padding: var(--bz-space-2) var(--bz-space-3);
    margin-bottom: var(--bz-space-2);
    background: var(--bz-color-surface-2);
}

/* Geo/address search row. */
.bz-geo-search { display: flex; gap: var(--bz-space-2); align-items: stretch; margin-bottom: var(--bz-space-3); }
.bz-geo-search .bz-search-input { max-width: none; flex: 1; }

/* Inline editable people grid in the wizard. */
.bz-mini-grid { width: 100%; border-collapse: collapse; margin-bottom: var(--bz-space-3); }
.bz-mini-grid th {
    text-align: left;
    font-size: var(--bz-fs-xs);
    font-weight: var(--bz-fw-semibold);
    color: var(--bz-color-fg-subtle);
    padding: var(--bz-space-2);
    border-bottom: 1px solid var(--bz-color-border);
}
.bz-mini-grid td { padding: var(--bz-space-2); vertical-align: middle; }

/* Alert boxes (no Bootstrap .alert in this theme). */
.bz-alert {
    border: 1px solid transparent;
    border-radius: var(--bz-radius-md);
    padding: var(--bz-space-3) var(--bz-space-4);
    margin-bottom: var(--bz-space-4);
    font-size: var(--bz-fs-sm);
}
.bz-alert ul { margin: var(--bz-space-2) 0 0; padding-left: 1.1rem; }
.bz-alert-danger { background: color-mix(in srgb, var(--bz-color-danger) 12%, var(--bz-color-surface)); color: var(--bz-color-danger); border-color: color-mix(in srgb, var(--bz-color-danger) 40%, transparent); }
.bz-alert-warn { background: color-mix(in srgb, var(--bz-color-warn) 12%, var(--bz-color-surface)); color: var(--bz-color-warn); border-color: color-mix(in srgb, var(--bz-color-warn) 40%, transparent); }
.bz-alert-info { background: color-mix(in srgb, var(--bz-color-info) 12%, var(--bz-color-surface)); color: var(--bz-color-info); border-color: color-mix(in srgb, var(--bz-color-info) 40%, transparent); }

/* Support session banner (task 277). Sticky inside #scrollable_content so a host operator acting
   as a tenant user can never scroll away from the fact — or from the way out. */
.bz-support-banner {
    position: sticky;
    top: 0;
    z-index: 30;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--bz-space-3);
    padding: var(--bz-space-2) var(--bz-space-5);
    font-size: var(--bz-fs-sm);
    color: var(--bz-color-warn);
    background: color-mix(in srgb, var(--bz-color-warn) 14%, var(--bz-color-surface));
    border-bottom: 1px solid color-mix(in srgb, var(--bz-color-warn) 45%, transparent);
}
.bz-support-banner-icon { font-size: 1rem; }
.bz-support-banner-text { font-weight: var(--bz-fw-medium); }
.bz-support-banner-expiry { color: color-mix(in srgb, var(--bz-color-warn) 80%, var(--bz-color-fg)); }
.bz-support-banner-action { margin-inline-start: auto; }

/* Session banner (GitLab #154) — the one place the app reports on the user's session.
   Fixed to the top of the VIEWPORT, not the scroll area: it is mounted beside ToastHost, outside the
   layout's scrolling column, and it has to stay readable while the user scrolls back through the page
   they are about to lose. It never covers the page: the layout keeps its own scroll, and the banner is
   two lines tall at most. */
.bz-session-banner {
    position: fixed;
    top: 0;
    inset-inline: 0;
    z-index: 60;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--bz-space-3);
    padding: var(--bz-space-2) var(--bz-space-5);
    font-size: var(--bz-fs-sm);
    box-shadow: var(--bz-shadow-sm);
}
.bz-session-banner-text { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--bz-space-2); min-width: 0; }
.bz-session-banner-action { margin-inline-start: auto; flex: 0 0 auto; }
.bz-session-banner.is-expired {
    color: var(--bz-color-danger);
    background: color-mix(in srgb, var(--bz-color-danger) 14%, var(--bz-color-surface));
    border-bottom: 1px solid color-mix(in srgb, var(--bz-color-danger) 45%, transparent);
}
/* Blazor reconnect modal (GitLab #157). Blazor owns the state classes on #components-reconnect-modal;
   everything here is which of the three lines and which of the two buttons that state reveals.
   Hidden outright until Blazor says otherwise, so it never flashes on a healthy page load. */
.bz-reconnect { display: none; }
.bz-reconnect.components-reconnect-show,
.bz-reconnect.components-reconnect-failed,
.bz-reconnect.components-reconnect-rejected {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 2000;
    align-items: center;
    justify-content: center;
    /* Deliberately translucent rather than opaque: the page underneath is still the user's work, and
       being able to see it is the difference between "hold on" and "everything is gone". */
    background: color-mix(in srgb, var(--bz-color-fg) 35%, transparent);
    backdrop-filter: blur(2px);
}
.bz-reconnect-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--bz-space-3);
    max-width: min(30rem, calc(100vw - 2rem));
    padding: var(--bz-space-5);
    text-align: center;
    color: var(--bz-color-fg);
    background: var(--bz-color-surface);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-lg);
    box-shadow: var(--bz-shadow-lg);
}
.bz-reconnect-spinner {
    width: 1.5rem;
    height: 1.5rem;
    border: 2px solid var(--bz-color-border);
    border-top-color: var(--bz-brand-primary);
    border-radius: 50%;
    animation: bz-reconnect-spin 900ms linear infinite;
}
@keyframes bz-reconnect-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    .bz-reconnect-spinner { animation: none; }
}
/* One line and one button at a time. */
.bz-reconnect-line, .bz-reconnect-btn { display: none; margin: 0; }
.components-reconnect-show .bz-reconnect-line-connecting { display: block; }
.components-reconnect-failed .bz-reconnect-line-failed { display: block; }
.components-reconnect-failed .bz-reconnect-btn-retry { display: inline-flex; }
.components-reconnect-rejected .bz-reconnect-line-rejected { display: block; }
.components-reconnect-rejected .bz-reconnect-btn-reload { display: inline-flex; }
/* Retrying is automatic while connecting, so the spinner only belongs to that state. */
.components-reconnect-failed .bz-reconnect-spinner,
.components-reconnect-rejected .bz-reconnect-spinner { display: none; }

.bz-session-banner.is-degraded {
    color: var(--bz-color-warn);
    background: color-mix(in srgb, var(--bz-color-warn) 14%, var(--bz-color-surface));
    border-bottom: 1px solid color-mix(in srgb, var(--bz-color-warn) 45%, transparent);
}

/* Review definition list (replaces the missing .row/.col grid). */
.bz-review { display: grid; grid-template-columns: minmax(0, 180px) minmax(0, 1fr); gap: var(--bz-space-2) var(--bz-space-4); margin: 0; }
.bz-review dt { font-weight: var(--bz-fw-medium); color: var(--bz-color-fg-subtle); }
.bz-review dd { margin: 0; color: var(--bz-color-fg); overflow-wrap: anywhere; }

/* Entity detail field grid — each child <div> is one <label>Caption</label><div>Value</div> pair,
   wrapping into a responsive multi-column layout (used by ProjectManagement Detail/Config pages). */
.bz-detail-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr));
    gap: var(--bz-space-4);
    margin: var(--bz-space-3) 0;
}
.bz-detail-grid > div > label {
    display: block;
    font-size: var(--bz-fs-sm);
    font-weight: var(--bz-fw-medium);
    color: var(--bz-color-fg-subtle);
    margin-bottom: var(--bz-space-1);
}
.bz-detail-grid > div { min-width: 0; overflow-wrap: anywhere; }
.bz-detail-grid > div > div { color: var(--bz-color-fg); }
/* Span-based variant of the same pair, used by read-only quick views / detail pages
   (AddressQuickView, AddressDetail, ContactDetail). */
.bz-detail-field > .bz-detail-label {
    display: block;
    font-size: var(--bz-fs-sm);
    font-weight: var(--bz-fw-medium);
    color: var(--bz-color-fg-subtle);
    margin-bottom: var(--bz-space-1);
}

.bz-detail-desc { color: var(--bz-color-fg-muted); margin: var(--bz-space-3) 0; line-height: 1.5; }

/* Rendered rich-text (RichTextView) — the tag set SafeHtml allows, nothing else. */
.bz-richtext p, .bz-richtext ul, .bz-richtext ol, .bz-richtext blockquote { margin: 0 0 var(--bz-space-2); }
.bz-richtext > :last-child { margin-bottom: 0; }
.bz-richtext ul, .bz-richtext ol { padding-left: 1.2rem; }
.bz-richtext blockquote {
    border-left: 3px solid var(--bz-color-border);
    padding-left: var(--bz-space-3);
    color: var(--bz-color-fg-subtle);
}
.bz-richtext code {
    font-family: var(--bz-font-mono);
    font-size: var(--bz-fs-xs);
    background: var(--bz-color-surface-2);
    border-radius: var(--bz-radius-sm);
    padding: 0 4px;
}
.bz-richtext pre {
    background: var(--bz-color-surface-2);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    padding: var(--bz-space-2) var(--bz-space-3);
    overflow-x: auto;
}

/* Horizontal sub-page navigation strip (e.g. project Detail tabs: WBS, diary, defects…). */
.bz-tabs {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    min-width: 0;
    gap: var(--bz-space-1);
    border-top: 1px solid var(--bz-color-border);
    margin-top: var(--bz-space-4);
    padding-top: var(--bz-space-3);
}
.bz-tabs a {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    min-height: var(--bz-control-height-lg);
    white-space: nowrap;
    padding: var(--bz-space-2) var(--bz-space-3);
    border-radius: var(--bz-radius-md);
    color: var(--bz-color-fg-muted);
    text-decoration: none;
    font-size: var(--bz-fs-sm);
    font-weight: var(--bz-fw-medium);
    /* transform repeated: this rule outranks the shared press-feedback selector. */
    transition: background var(--bz-duration-fast) var(--bz-ease),
                color var(--bz-duration-fast) var(--bz-ease),
                transform var(--bz-duration-press) var(--bz-ease);
}
.bz-tabs a:hover { background: var(--bz-color-surface-2); color: var(--bz-color-fg); }
.bz-tabs a:is(.active, [aria-current="page"]) {
    color: var(--bz-color-accent-text);
    background: color-mix(in srgb, var(--bz-color-accent) 12%, var(--bz-color-surface));
    font-weight: var(--bz-fw-semibold);
}

/* Detail summary metric cards. */
.bz-summary-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: var(--bz-space-4);
    margin: var(--bz-space-4) 0;
}
.bz-card {
    background: var(--bz-color-surface);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-lg);
    padding: var(--bz-space-4);
    box-shadow: var(--bz-shadow-sm);
}
.bz-card-label { font-size: var(--bz-fs-xs); color: var(--bz-color-fg-subtle); text-transform: uppercase; letter-spacing: 0.03em; }
.bz-card-value { font-size: var(--bz-fs-lg); font-weight: var(--bz-fw-semibold); margin-top: var(--bz-space-1); color: var(--bz-color-fg); }

/* Header row pairing a <RecordDocumentAvatar> with a <PageHeader> on detail pages. */
.bz-page-header-row {
    display: flex;
    align-items: center;
    gap: var(--bz-space-4);
    /* GitLab #109: on a phone the 88px avatar squeezed the title into one word per line. */
    flex-wrap: wrap;
}
@media (max-width: 640px) {
    .bz-page-header-row {
        flex-direction: column;
        align-items: flex-start;
    }
    .bz-page-header-row .bz-page-header { width: 100%; }
}

/* ── Phone-compact toolbars and summary grids (task 286 — GitLab #117–#120) ──
   Below 640px a search box with a 220px floor, a row of 170px filters and a
   180px-minimum card grid each collapse to one item per row: a list page opened
   on nothing but its own toolbar, and a detail page on a column of single-value
   cards. Search takes a full row, filters pair up, cards become a 2–3 column
   grid. Deliberately shared: it reaches every .bz-toolbar and all 19 pages that
   use .bz-summary-cards, which is the point — the density problem is the same
   on every one of them. */
/* Desktop shows every filter at once, so the toggle only exists on a phone. Declared
   here, BEFORE the ≤640 block that turns it on — equal specificity, source order wins. */
/* ── Filters in a toolbar / filter bar ──────────────────────────────────────
   GitLab #161. Task 315 replaced 46 native <select> elements with DxComboBox. A native select sizes
   itself to its content; a DevExpress editor root renders `width: 100%`, so as a flex item in an
   already-wrapping bar it claimed an entire row each. Business Partners went from one row of filters
   to NINE, and every migrated page did the same.

   Scoped to the bar, not to .bz-filter / .bz-toolbar-select, because those two classes are also worn
   by editors inside form layouts (the appointment editor's attendee picker, the calendar share row,
   the assistant's own mode combo) where filling the container is exactly right. Only an editor that
   is a direct child of a toolbar or filter bar is a toolbar item.

   --bz-filter-width is the per-page knob the issue asks for: a bar that wants three wide columns
   instead of five narrow ones sets the variable on itself, rather than every page re-specifying
   widths per control. The phone rules further down set an explicit flex-basis of 50%, which beats
   `width` for a flex item's main size, so two-per-row on a phone is unaffected.

   `.bz-page-size` is excluded by name: the pager's page-size combo lives in a `.bz-toolbar` too, and
   `.bz-toolbar .bz-page-size` (six rems, further up this file) has the SAME specificity as the rule
   below — so without the exclusion the later rule silently wins and the pager's compact selector
   grows to filter width. Any future control with its own toolbar width needs the same treatment, or
   a selector that outranks this one. */
.bz-toolbar > [class*="dxbl-"]:not(.bz-page-size),
.bz-filter-bar > [class*="dxbl-"]:not(.bz-page-size) {
    flex: 0 0 auto;
    width: var(--bz-filter-width, 11rem);
    min-width: 0;
}

/* A label wrapping its editor is the flex item; size the label, and let the editor fill it. */
.bz-toolbar > .bz-inline-label,
.bz-filter-bar > .bz-inline-label { flex: 0 0 auto; max-width: 100%; }
.bz-toolbar > div.bz-filter,
.bz-filter-bar > div.bz-filter-input { flex: 0 1 var(--bz-filter-width, 11rem); min-width: 0; }
.bz-toolbar > .bz-inline-label > [class*="dxbl-"],
.bz-filter-bar > .bz-inline-label > [class*="dxbl-"] {
    width: var(--bz-filter-width, 11rem);
    min-width: 0;
}

.bz-filters-toggle { display: none; }

/* ── Record-picker add-rows (task 332, GitLab #198) ─────────────────────────
   Dependencies / Linked records add a task or another record from a search
   box plus a kind/type selector plus an Add button. They used to share
   `.bz-toolbar`, so the rule above (built for read-only FILTER bars) pinned
   the search picker to the same 11rem as a filter combo, leaving the result
   list too narrow to read and the row mostly empty space. `.bz-link-row` is
   its own container: the picker gets the room, the kind/type selector sizes
   for its short option text, and the button stays content-sized. */
.bz-link-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--bz-space-2);
    /* The vertical rhythm of the .bz-toolbar this row replaced. */
    padding: var(--bz-space-3) 0;
    margin-bottom: var(--bz-space-3);
}
.bz-link-row-picker { flex: 1 1 18rem; min-width: 14rem; }
.bz-link-row-type { flex: 0 1 12rem; min-width: 9rem; }
.bz-link-row > .dxbl-btn { flex: 0 0 auto; }

@media (max-width: 640px) {
    .bz-toolbar { gap: var(--bz-space-2); }

    .bz-page-card { --bz-card-pad: var(--bz-space-3); margin-bottom: var(--bz-space-4); }
    .bz-filter-bar { padding-inline: 0; }
    .bz-filter-bar > .bz-search-input { flex: 1 1 calc(100% - 56px); min-width: 0; max-width: none; }
    .bz-review { grid-template-columns: minmax(0, 1fr); gap: var(--bz-space-1); }
    .bz-review dd:not(:last-child) { margin-bottom: var(--bz-space-3); }
    .bz-tabs a { min-height: 44px; }
    .dxbl-modal .dxbl-popup {
        --dxbl-popup-margin: var(--bz-space-2);
        --dxbl-popup-scrollable-max-height: calc(100dvh - var(--bz-space-4));
        max-width: calc(100vw - var(--bz-space-4)) !important;
    }

    /* Task 289 (GitLab #121): the board opened on four stacked rows of chrome — view
       pills, board pills, saved views, filters — before a single card. Two moves fix
       it without hiding anything: a pill row SWIPES instead of wrapping (that rule
       sits with .bz-board-pills further down, after the base rule it overrides), and
       the filters collapse behind a toggle so search plus that toggle own one row.
       The page-header chips that take over "Manage boards" / "Save current view"
       live in the pages themselves (`.bz-mobile-contents`). */
    /* Search shares its row with the toggle instead of claiming a full one. Compound
       selector on purpose: `.bz-toolbar .bz-search-input` below is the same specificity
       and later in source, so it would win otherwise. */
    /* Basis "everything but the toggle": with a 0 basis the search shrank far enough for the
       first filter to squeeze onto its row (task 291 review), which broke the two-per-row pairing. */
    .bz-toolbar.bz-filters .bz-search-input { flex: 1 1 calc(100% - 9rem); }

    .bz-filters-toggle {
        display: inline-flex;
        align-items: center;
        gap: var(--bz-space-1);
        flex: 0 0 auto;
        min-height: 44px;
        padding-inline: var(--bz-space-3);
        border: 1px solid var(--bz-color-border);
        border-radius: var(--bz-radius-sm);
        background: var(--bz-color-surface);
        color: var(--bz-color-fg);
        font-size: var(--bz-fs-sm);
        cursor: pointer;
    }

    .bz-filters--open .bz-filters-toggle {
        border-color: var(--bz-primary);
        color: var(--bz-primary);
    }

    /* Clear filters (task 340) stays reachable while the filters are collapsed. */
    .bz-filters:not(.bz-filters--open) > :not(.bz-search-input):not(.bz-filters-toggle):not(.bz-filters-clear) {
        display: none;
    }

    /* ...as a 44px chip on the search row, next to the toggle. */
    .bz-filters-clear {
        flex: 0 0 44px;
        width: 44px;
        min-width: 44px;
        height: 44px;
        border-color: var(--bz-color-border);
        background: var(--bz-color-surface);
    }

    .bz-toolbar .bz-search-input {
        flex: 1 1 100%;
        min-width: 0;
        max-width: none;
    }

    /* The search box leaves room for the Clear-filters chip (and, in a collapsible bar, the toggle). */
    .bz-toolbar:has(> .bz-filters-clear) > .bz-search-input,
    .bz-filter-bar:has(> .bz-filters-clear) > .bz-search-input {
        flex-basis: calc(100% - 44px - var(--bz-space-2));
    }
    .bz-toolbar.bz-filters:has(> .bz-filters-clear) > .bz-search-input {
        flex-basis: calc(100% - 9rem - 44px - var(--bz-space-2));
    }

    /* Two per row on a phone. The DevExpress-root selectors repeat the desktop rule's exact shape,
       :not() included, because that rule now sets an explicit `width` — and a plain `.bz-filter`
       override would lose to it on specificity even here. Stated in the same terms, this block wins
       on source order, which is what a responsive override is supposed to do. */
    .bz-toolbar > [class*="dxbl-"]:not(.bz-page-size),
    .bz-filter-bar > [class*="dxbl-"]:not(.bz-page-size),
    .bz-toolbar > .bz-inline-label:not(.bz-page-size),
    .bz-filter-bar > .bz-inline-label:not(.bz-page-size),
    .bz-filter-bar > div.bz-filter-input,
    .bz-toolbar .bz-filter,
    .bz-toolbar > .bz-search-bare,
    .bz-toolbar .bz-toolbar-select {
        flex: 1 1 calc(50% - var(--bz-space-2));
        width: auto;
        min-width: 0;
    }

    /* Once everything wraps there is no "end of the row" left to push anything to. */
    .bz-toolbar-spacer { display: none; }

    /* .bz-link-row: picker and type each take a full row, Add spans full width below them —
       same one-per-row collapse the filter bar uses above, without pairing the picker with
       the short type selector (that pairing is what made the picker cramped in the first place). */
    .bz-link-row-picker,
    .bz-link-row-type { flex: 1 1 100%; }
    .bz-link-row > .dxbl-btn { flex: 1 1 100%; width: 100%; }

    .bz-summary-cards {
        grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
        gap: var(--bz-space-2);
        margin: var(--bz-space-3) 0;
    }

    .bz-card {
        padding: var(--bz-space-2) var(--bz-space-3);
        border-radius: var(--bz-radius-md);
    }

    .bz-card-value {
        font-size: var(--bz-fs-md);
        margin-top: 2px;
    }
}
.bz-page-header-row .bz-page-header { flex: 1; }

/* <RecordDocumentAvatar> — circular image/initials with a hover-reveal upload/remove overlay. */
.bz-avatar {
    position: relative;
    flex: 0 0 auto;
    border-radius: var(--bz-radius-pill);
    overflow: hidden;
    background: var(--bz-primary-50);
    box-shadow: var(--bz-shadow-sm);
}
.bz-avatar-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.bz-avatar-initials {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--bz-primary);
    font-weight: var(--bz-fw-semibold);
    font-size: 1.5rem;
}
.bz-avatar-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--bz-space-2);
    background: color-mix(in srgb, black 45%, transparent);
    opacity: 0;
    transition: opacity var(--bz-duration-fast) var(--bz-ease);
}
.bz-avatar:hover .bz-avatar-overlay,
.bz-avatar:focus-within .bz-avatar-overlay {
    opacity: 1;
}
.bz-avatar-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: var(--bz-radius-pill);
    border: none;
    background: var(--bz-surface);
    color: var(--bz-color-fg);
    cursor: pointer;
    padding: 0;
}
.bz-avatar-action:hover { background: var(--bz-primary-50); color: var(--bz-primary); }
.bz-avatar-error {
    margin-top: var(--bz-space-1);
    font-size: var(--bz-fs-xs);
    color: var(--bz-color-danger);
}

/* <RecordDocumentAvatar Shape="rect"> — rectangular logo variant (e.g. tenant logo). Square corners,
   object-fit:contain so the whole image shows instead of a cropped/cover circle. Reuses
   .bz-avatar-initials/-overlay/-action; only the hover-reveal trigger needs its own selector. */
.bz-logo-upload {
    position: relative;
    flex: 0 0 auto;
    border-radius: var(--bz-radius-md);
    overflow: hidden;
    background: var(--bz-color-surface-2);
    border: 1px solid var(--bz-color-border);
    box-shadow: var(--bz-shadow-sm);
}
.bz-logo-img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}
.bz-logo-upload:hover .bz-avatar-overlay,
.bz-logo-upload:focus-within .bz-avatar-overlay {
    opacity: 1;
}

/* image-crop.js — JS-owned crop overlay (outside the Blazor render tree, so plain CSS here). */
.bz-cropper-backdrop {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: color-mix(in srgb, black 55%, transparent);
}
.bz-cropper-backdrop:modal { width: 100vw; height: 100dvh; max-width: none; max-height: none; margin: 0; padding: 0; border: 0; color: var(--bz-color-fg); }
.bz-cropper-backdrop::backdrop { background: transparent; }
.bz-cropper-modal {
    background: var(--bz-color-popover);
    border-radius: var(--bz-radius-lg);
    box-shadow: var(--bz-shadow-lg);
    padding: var(--bz-space-5);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--bz-space-3);
    max-width: 90vw;
}
.bz-cropper-title {
    font-weight: var(--bz-fw-semibold);
    font-size: var(--bz-fs-lg);
    color: var(--bz-color-fg);
    align-self: flex-start;
}
.bz-cropper-stage {
    position: relative;
    border-radius: var(--bz-radius-pill);
    overflow: hidden;
    background: var(--bz-color-surface-2);
    touch-action: none;
    cursor: grab;
}
.bz-cropper-stage:active { cursor: grabbing; }
/* Shape="rect" (e.g. tenant logo) — rectangular stage, no pill mask. */
.bz-cropper-stage-rect { border-radius: var(--bz-radius-md); }
.bz-cropper-img {
    position: absolute;
    max-width: none;
    pointer-events: none;
    user-select: none;
}
.bz-cropper-hint {
    font-size: var(--bz-fs-xs);
    color: var(--bz-color-fg-subtle);
}
.bz-cropper-controls {
    width: 100%;
    display: flex;
    align-items: center;
}
.bz-cropper-zoom { width: 100%; accent-color: var(--bz-primary); }
.bz-cropper-actions {
    display: flex;
    gap: var(--bz-space-2);
    align-self: flex-end;
}

/* Filter select reusing the toolbar-select look. */
.bz-filter {
    padding: 0.38rem 0.7rem;
    background: var(--bz-color-surface);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    color: var(--bz-color-fg);
    font-size: var(--bz-fs-sm);
    min-width: 170px;
}

/* Settings field label with info button + status badge. */
.bz-setting-label {
    display: flex;
    align-items: center;
    gap: var(--bz-space-2);
    font-size: var(--bz-fs-sm);
    font-weight: var(--bz-fw-medium);
    color: var(--bz-color-fg-muted);
    margin-bottom: var(--bz-space-2);
}
.bz-info-btn {
    border: none;
    background: transparent;
    cursor: pointer;
    color: var(--bz-brand-primary);
    padding: 0;
    display: inline-flex;
    align-items: center;
}
.bz-info-btn i { font-size: 16px; }

/* AI-assist panel on the create wizard. */
.bz-ai-panel {
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-lg);
    background: linear-gradient(180deg, rgba(31,95,216,0.06), rgba(31,95,216,0.02));
    padding: var(--bz-space-4);
    margin-bottom: var(--bz-space-4);
}
.bz-ai-head {
    display: flex;
    align-items: center;
    gap: var(--bz-space-2);
    font-weight: var(--bz-fw-semibold);
    color: var(--bz-brand-primary);
    margin-bottom: var(--bz-space-1);
}
.bz-ai-head i { font-size: 18px; }

/* "or" divider between AI assist and the manual form. */
.bz-or {
    display: flex;
    align-items: center;
    text-align: center;
    color: var(--bz-color-fg-subtle);
    font-size: var(--bz-fs-sm);
    margin: var(--bz-space-2) 0 var(--bz-space-4);
}
.bz-or::before, .bz-or::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--bz-color-border);
}
.bz-or span { padding: 0 var(--bz-space-3); }

/* ============================================================
   Global AI assistant chat widget (bottom-right). Built from --bz-* tokens.
   Opened from the sidebar entry / phone nav (GitLab #231 removed the floating launcher bubble,
   and with it the footer's right gutter and the Messaging composer's).
   ============================================================ */
.bz-ai-chat-root { position: fixed; bottom: var(--bz-space-5); right: var(--bz-space-5); z-index: 50; }

/* Embedded (full-page) variant: same panel, undocked and stretched to fill the host page. */
.bz-ai-chat-root.is-embedded { position: static; z-index: auto; }
.bz-ai-chat-root.is-embedded .bz-ai-chat-panel {
    width: 100%; max-width: none;
    height: calc(100vh - 230px); min-height: 460px; max-height: none;
    animation: none;
}

.bz-ai-chat-panel {
    /* GitLab #231: ai-float.js writes the resized size onto the root (desktop only). */
    position: relative;
    width: var(--bz-ai-panel-w, 480px); max-width: calc(100vw - 2 * var(--bz-space-4));
    height: var(--bz-ai-panel-h, 700px); max-height: calc(100vh - 2 * var(--bz-space-5));
    background: var(--bz-color-surface);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    box-shadow: var(--bz-shadow-md);
    display: flex; flex-direction: column; overflow: hidden;
    /* Anchored in the bottom-right corner, so it scales out of that corner rather than
       out of its own centre. 280ms, not 120ms: this is a 480x700 panel, and
       button-press timing on it reads as a snap. */
    transform-origin: bottom right;
    animation: bz-ai-chat-slide-up var(--bz-duration-slow) var(--bz-ease);
    transition: width var(--bz-duration-base) var(--bz-ease), height var(--bz-duration-base) var(--bz-ease);
}
/* Full-screen within the tab/viewport; restore via the header icon. */
.bz-ai-chat-panel.is-maximized {
    position: fixed; inset: var(--bz-space-4);
    width: auto; height: auto; max-width: none; max-height: none;
}
/* The small scale makes it read as emerging FROM its corner; a pure translate
   reads as an unrelated panel sliding past. Never from scale(0) — nothing in the
   real world appears out of nothing. */
@keyframes bz-ai-chat-slide-up {
    from { opacity: 0; transform: translateY(12px) scale(0.97); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* GitLab #208: minimized = hidden but still MOUNTED, so DxAIChat and the turn it is awaiting survive;
   the sidebar entry restores this same panel. */
.bz-ai-chat-panel.is-minimized { display: none; }
/* GitLab #231: the sidebar entry and the phone nav button (AssistantIndicator) tell a minimized chat's
   story, as the removed launcher did: a pulsing ring while a turn runs, a dot once it has answered. */
.bz-ai-entry { position: relative; }
.bz-ai-indicator {
    position: absolute; top: var(--bz-space-1); inset-inline-end: var(--bz-space-1);
    width: 10px; height: 10px; border-radius: 50%;
    pointer-events: none;
}
.bz-ai-indicator.is-unread { background: var(--bz-color-danger); }
.bz-ai-indicator.is-busy {
    border: 2px solid currentColor;
    animation: bz-ai-chat-busy calc(4 * var(--bz-duration-slow)) var(--bz-ease) infinite;
}
@keyframes bz-ai-chat-busy {
    from { opacity: 0.9; transform: scale(1); }
    to   { opacity: 0.2; transform: scale(1.35); }
}
@media (prefers-reduced-motion: reduce) {
    .bz-ai-indicator.is-busy { animation: none; opacity: 0.6; }
}

/* Moved / resized panel (ai-float.js writes the custom properties and the class onto the root; the
   handles are the bare panel head and the corner grip). Desktop only — the phone block owns the
   layout below 1024px, and the script does not apply stored values there either. */
.bz-ai-chat-resize-grip { display: none; }
@media (min-width: 1024px) {
    .bz-ai-chat-root:not(.is-embedded) .bz-ai-chat-head { touch-action: none; }
    .bz-ai-chat-root:not(.is-embedded) .bz-ai-chat-head { cursor: grab; user-select: none; }
    .bz-ai-chat-panel.is-maximized .bz-ai-chat-head { cursor: auto; } /* not draggable while maximized */
    .bz-ai-chat-head.is-dragging { cursor: grabbing; }

    /* Bottom-right corner grip (#231): two diagonal strokes, over whatever sits in the corner. */
    .bz-ai-chat-panel:not(.is-maximized) .bz-ai-chat-resize-grip {
        display: block; position: absolute; right: 0; bottom: 0; z-index: 2;
        width: 14px; height: 14px;
        cursor: nwse-resize; touch-action: none;
        background: linear-gradient(135deg,
            transparent 45%, var(--bz-color-border-strong) 45%, var(--bz-color-border-strong) 55%,
            transparent 55%, transparent 70%, var(--bz-color-border-strong) 70%, var(--bz-color-border-strong) 80%,
            transparent 80%);
    }
    /* No width/height transition lagging behind the pointer while the grip is dragged. */
    .bz-ai-chat-panel.is-resizing { transition: none; user-select: none; }

    .bz-ai-chat-root.has-panel-pos .bz-ai-chat-panel:not(.is-maximized) {
        position: fixed; left: var(--bz-ai-panel-left); top: var(--bz-ai-panel-top);
        transform-origin: top left;
    }
}

.bz-ai-chat-head {
    display: flex; align-items: center; gap: var(--bz-space-2);
    padding: var(--bz-space-3) var(--bz-space-4);
    border-bottom: 1px solid var(--bz-color-border);
    background: var(--bz-color-bg);
}
.bz-ai-chat-title { font-weight: 600; flex: 1; min-width: 0; color: var(--bz-color-fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bz-ai-chat-icon-btn {
    border: none; background: transparent; cursor: pointer;
    color: var(--bz-color-fg); padding: 4px; border-radius: var(--bz-radius-sm);
    display: inline-flex; align-items: center; justify-content: center;
}
.bz-ai-chat-icon-btn:hover { background: var(--bz-color-border); }

/* position: relative — the containing block for a dock panel in `is-fullscreen`, so it covers the
   chat surface and the thread list but leaves the chat head (and its way out) visible above. */
.bz-ai-chat-body { flex: 1; display: flex; min-height: 0; position: relative; }
.bz-ai-chat-threads {
    width: 150px; border-right: 1px solid var(--bz-color-border);
    overflow-y: auto; background: var(--bz-color-bg);
}
.bz-ai-chat-threads-empty { padding: var(--bz-space-3); color: var(--bz-color-fg-muted); font-size: 13px; }
.bz-ai-chat-thread-item {
    display: flex; align-items: center; gap: 4px;
    padding: var(--bz-space-2) var(--bz-space-3); cursor: pointer;
    border-bottom: 1px solid var(--bz-color-border);
}
.bz-ai-chat-thread-item:hover { background: var(--bz-color-border); }
.bz-ai-chat-thread-item.is-active { background: var(--bz-color-border); font-weight: 600; }
.bz-ai-chat-thread-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
.bz-ai-chat-thread-rename, .bz-ai-chat-thread-del { opacity: 0; }
.bz-ai-chat-thread-item:hover .bz-ai-chat-thread-rename,
.bz-ai-chat-thread-item:hover .bz-ai-chat-thread-del { opacity: 1; }
.bz-ai-chat-thread-rename-input {
    flex: 1; min-width: 0; font-size: 13px; padding: 2px 4px;
    color: var(--bz-color-fg); background: var(--bz-color-bg);
    border: 1px solid var(--bz-color-accent); border-radius: var(--bz-radius-sm);
}

.bz-ai-chat-main { flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; }
/* min-height, not 0: a dock panel below may grow to 80% of the column, and a chat surface
   collapsed to nothing looks like the assistant died. */
.bz-ai-chat-surface { flex: 1; min-height: 120px; }

.bz-ai-chat-welcome {
    flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: var(--bz-space-3); text-align: center; padding: var(--bz-space-5); color: var(--bz-color-fg);
}
.bz-ai-chat-welcome-icon { font-size: 36px; color: var(--bz-color-accent); }

/* Dock panels under the chat (Results, Proposals — AiChatDock.razor). Task 300 (GitLab #137): the
   two blocks had different chrome, so close / drag-resize / full screen are one shared group here.
   Height is a custom property the drag handle overrides from JS (ai-dock-resize.js) without a Blazor
   round-trip per pointermove; the % default is a share of the panel column, so `is-maximized` scales
   it up with the panel instead of capping it. */
.bz-ai-chat-dock {
    /* flex-basis (not height): resolves against the flex column's main size, so the default tracks
       the panel — including `is-maximized` — and a px value from the drag handle overrides it.
       0 1 (not 0 0): two open docks share the column instead of squeezing the chat out. */
    flex: 0 1 var(--bz-ai-dock-h, 45%);
    min-height: 132px; /* one collapsed card */
    max-height: 80%;
    display: flex; flex-direction: column;
    border-top: 1px solid var(--bz-color-border);
    background: var(--bz-color-bg);
    overflow: hidden;
}
/* Full screen within the chat body, so the chat head — and the way back out — stays on screen. */
.bz-ai-chat-dock.is-fullscreen {
    position: absolute; inset: 0; z-index: 3;
    flex: none; min-height: 0; max-height: none; border-top: 0;
}
/* Closed: just the head bar with its reopen button. */
.bz-ai-chat-dock.is-closed { flex: 0 0 auto; min-height: 0; }
.bz-ai-chat-dock-grip {
    flex: 0 0 auto; height: 10px; cursor: ns-resize;
    display: flex; align-items: center; justify-content: center;
    background: var(--bz-color-bg);
    touch-action: none; /* the pointer drag owns the gesture, not the scroller */
}
/* The grippy itself: a short bar, widening slightly on hover so the handle reads as grabbable. */
.bz-ai-chat-dock-grip::before {
    content: ""; width: 32px; height: 3px; border-radius: var(--bz-radius-pill);
    background: var(--bz-color-border);
    transition: width var(--bz-duration-fast) var(--bz-ease), background-color var(--bz-duration-fast) var(--bz-ease);
}
.bz-ai-chat-dock-grip:hover::before,
.bz-ai-chat-dock-grip.is-dragging::before { width: 48px; background: var(--bz-color-accent); }

.bz-ai-chat-dock-head {
    display: flex; align-items: center; gap: var(--bz-space-2);
    padding: 0 var(--bz-space-2) var(--bz-space-1) var(--bz-space-3);
    font-weight: var(--bz-fw-semibold); font-size: var(--bz-fs-sm);
}
.bz-ai-chat-dock.is-closed .bz-ai-chat-dock-head { padding-top: var(--bz-space-1); }
.bz-ai-chat-dock-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bz-ai-chat-dock-body { flex: 1; min-height: 0; overflow-y: auto; padding: var(--bz-space-2) var(--bz-space-3); }
/* Results: cards share the dock's height and scroll inside, so the sticky header keeps working at any size. */
.bz-ai-chat-dock-results .bz-ai-chat-dock-body { display: flex; flex-direction: column; gap: var(--bz-space-2); }

.bz-ai-chat-proposal-card {
    border: 1px solid var(--bz-color-border); border-radius: var(--bz-radius-sm);
    margin-bottom: var(--bz-space-3); background: var(--bz-color-surface);
    overflow: hidden;
}
.bz-ai-chat-proposal-head {
    display: flex; align-items: center; gap: 6px; font-weight: 600;
    padding: var(--bz-space-2) var(--bz-space-3);
    border-bottom: 1px solid var(--bz-color-border);
    background: var(--bz-color-bg);
}
.bz-ai-chat-proposal-icon { color: var(--bz-color-accent); }
.bz-ai-chat-proposal-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Items are separated by a rule rather than margin — several staged records in one card otherwise
   read as one long form. */
.bz-ai-chat-proposal-item { padding: var(--bz-space-3); }
.bz-ai-chat-proposal-item + .bz-ai-chat-proposal-item { border-top: 1px solid var(--bz-color-border); }
.bz-ai-chat-proposal-fields { list-style: none; margin: 4px 0 0; padding: 0; font-size: 13px; }
.bz-ai-chat-proposal-fields li { padding: 1px 0; }
.bz-ai-chat-field-name { color: var(--bz-color-fg-muted); }
.bz-ai-chat-proposal-validation { color: var(--bz-color-danger); font-size: 13px; margin: 2px 0; }
/* Sticky bottom bar: approve/reject stay reachable while a long item list scrolls past. */
.bz-ai-chat-proposal-actions {
    position: sticky; bottom: 0; z-index: 1;
    display: flex; align-items: center; gap: var(--bz-space-2); flex-wrap: wrap;
    padding: var(--bz-space-2) var(--bz-space-3);
    border-top: 1px solid var(--bz-color-border);
    background: var(--bz-color-bg);
}
.bz-ai-chat-skip-dup { margin-left: auto; font-size: 13px; }
.bz-ai-chat-proposal-note { color: var(--bz-color-fg-muted); font-size: 13px; margin: 0; padding: var(--bz-space-2) var(--bz-space-3); }

/* ── Progress bars (storage migrations / backups live progress) ─────────────── */
.bz-progress { height: 8px; background: var(--bz-surface-3); border-radius: var(--bz-radius-pill); overflow: hidden; }
.bz-progress-fill { height: 100%; background: var(--bz-primary); transition: width var(--bz-duration-slow) var(--bz-ease); }
.bz-progress-fill--indeterminate { width: 40%; animation: bz-indeterminate 1.1s var(--bz-ease-in-out) infinite; }
/* Over 100% of a known capacity (workload board). The bar itself stays clamped at full
   width — the number next to it carries the overshoot; a bar wider than its track lies. */
.bz-progress-fill--over { background: var(--bz-color-danger); }
/* transform, NOT margin-left: margin forces layout -> paint -> composite on every
   frame, and this loop runs for the whole life of every API call in the app.
   translateX is a % of the element's OWN width (40% of the track), so the track's
   -40%..100% margin sweep becomes -100%..250% here. */
@keyframes bz-indeterminate {
    from { transform: translateX(-100%); }
    to   { transform: translateX(250%); }
}

/* Global API activity bar (MainLayout). Pinned above everything so a slow save is visible wherever
   the user has scrolled to; fades out rather than disappearing, so a burst of quick calls does not
   flicker. pointer-events:none — it must never intercept a click. */
.bz-topbar-progress {
    position: fixed;
    top: 0; left: 0; right: 0;
    height: 3px;
    z-index: 2000;
    overflow: hidden;
    background: transparent;
    pointer-events: none;
    opacity: 1;
    transition: opacity .25s ease;
}
.bz-topbar-progress--idle { opacity: 0; }



/* ── Toasts (<ToastHost>, rendered once in MainLayout) ─────────────────────────
   Lives here rather than in a ToastHost.razor.css: AGENTS.md puts cross-cutting
   component classes in shell.css, and Blazor's scoped-CSS rewriter has no defined
   behaviour for the @starting-style at-rule below. */
.bz-toast-host {
    position: fixed;
    right: var(--bz-space-4);
    max-width: calc(100vw - var(--bz-space-6));
    /* --header-height is set on <body> in App.razor (60px); this tracks the mobile topbar. */
    top: calc(var(--header-height) + var(--bz-space-3));
    display: flex;
    flex-direction: column;
    gap: var(--bz-space-2);
    z-index: 2000;
    pointer-events: none;
}

.bz-toast {
    pointer-events: auto;
    min-width: min(260px, 100%);
    max-width: 420px;
    padding: var(--bz-space-3) var(--bz-space-4);
    background: var(--bz-color-surface);
    color: var(--bz-color-fg);
    border: 1px solid var(--bz-color-border);
    border-left: 4px solid var(--bz-color-accent);
    border-radius: var(--bz-radius-md);
    box-shadow: var(--bz-shadow-md);
    display: flex;
    align-items: center;
    gap: var(--bz-space-3);
    font-size: var(--bz-fs-sm);
    transition: opacity var(--bz-duration-base) var(--bz-ease),
                transform var(--bz-duration-base) var(--bz-ease);
}

/* Enters from above — the direction the stack grows from, so the motion says
   where it came from. Never from scale(0): things do not appear from nothing.
   ponytail: enter only. An exit needs IToastService to hold the item through its
   exit window (a C# change); a toast vanishing is far less jarring than one
   popping into existence, so the enter is where the value is. */
@starting-style {
    .bz-toast { opacity: 0; transform: translateY(-8px) scale(0.97); }
}

.bz-toast-success { border-left-color: var(--bz-color-ok); }
.bz-toast-warning { border-left-color: var(--bz-color-warn); }
.bz-toast-error { border-left-color: var(--bz-color-danger); }

.bz-toast-msg { flex: 1; min-width: 0; overflow-wrap: anywhere; }

.bz-toast-close {
    background: transparent;
    border: 0;
    color: var(--bz-color-fg-muted);
    font-size: 18px;
    cursor: pointer;
    padding: 0 var(--bz-space-1);
}

.bz-toast-close:hover { color: var(--bz-color-fg); }


/* ── Search-term highlight (record-document search results) ─────────────────── */
/* Built from --bz-color-warn so it flips with the theme: a hard-coded yellow is
   unreadable in dark mode (same trap as .text-muted mapping to a surface token). */
.bz-mark {
    background: color-mix(in srgb, var(--bz-color-warn) 38%, transparent);
    color: inherit;
    border-radius: var(--bz-radius-sm);
    padding: 0 2px;
    font-weight: 600;
}


/* ── Task boards (/task-management/board) ───────────────────────────────────── */
/* Hand-built columns: no Kanban package is referenced anywhere in the repo and one
   flex row of scrolling columns does not justify adding one. Drag/drop affordances
   (--over on a hovered column, --drop-before as the placeholder, --dragging on the
   held card) are toggled by kanban.js; every colour comes from a --bz-* token so the
   board flips with the theme. */
.bz-kanban {
    display: flex;
    flex-direction: column;
    gap: var(--bz-space-3);
    padding: var(--bz-space-3) 0;
    min-height: 12rem;
}

/* One swimlane row. A board without swimlanes renders exactly one of these, so the
   column strip behaves identically whether or not lanes exist. */
.bz-kanban-lane {
    display: flex;
    gap: var(--bz-space-3);
    align-items: flex-start;
    overflow-x: auto;
}

/* A swimlane block = its header + the column strip below it. Nested flex rather than
   one CSS grid: the columns already scroll horizontally as a flex row, and a grid
   would have to re-declare the column track per lane just to stay aligned. */
.bz-kanban-swimlane {
    display: flex;
    flex-direction: column;
    gap: var(--bz-space-2);
}

.bz-kanban-lane-head {
    display: flex;
    align-items: center;
    gap: var(--bz-space-2);
    padding: var(--bz-space-2) var(--bz-space-3);
    background: var(--bz-color-surface-2);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    /* Sticks to the left edge while the column strip scrolls sideways, so the lane a
       row belongs to never scrolls out of sight. */
    position: sticky;
    left: 0;
}

.bz-kanban-lane-name { font-weight: var(--bz-fw-semibold); }

/* Lanes stack vertically, so a full-height column per lane would push the next lane
   off screen. Each cell gets its own modest scroll instead. */
.bz-kanban-swimlane .bz-kanban-col { max-height: 26rem; }

/* Workload rollup under a column header (Σ hours / capacity + the unestimated badge). */
.bz-kanban-col-load {
    display: flex;
    flex-direction: column;
    gap: var(--bz-space-1);
    padding: var(--bz-space-2) var(--bz-space-3);
    border-bottom: 1px solid var(--bz-color-border);
}

.bz-kanban-col-load-text {
    display: flex;
    align-items: center;
    gap: var(--bz-space-2);
    font-size: var(--bz-fs-xs);
    color: var(--bz-color-fg-muted);
}

/* Board selector: which board is open, not a filter — hence pills, not a select. */
.bz-board-pills {
    display: flex;
    align-items: center;
    gap: var(--bz-space-2);
    flex-wrap: wrap;
    padding: var(--bz-space-2) 0;
}

.bz-board-pill {
    padding: var(--bz-space-1) var(--bz-space-3);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-pill);
    background: var(--bz-color-surface);
    color: var(--bz-color-fg);
    font-size: var(--bz-fs-sm);
    cursor: pointer;
}

.bz-board-pill:hover { border-color: var(--bz-primary); }

.bz-board-pill--active {
    background: color-mix(in srgb, var(--bz-primary) 12%, var(--bz-color-surface));
    border-color: var(--bz-primary);
    color: var(--bz-primary);
    font-weight: var(--bz-fw-semibold);
}

.bz-board-pill:disabled { opacity: .6; cursor: default; }

/* Task 289 (GitLab #121): on a phone the pill rows swipe instead of wrapping. Declared
   AFTER the base rule above on purpose — same specificity, so source order decides. */
@media (max-width: 640px) {
    .bz-board-pills {
        flex-wrap: nowrap;
        overflow-x: auto;
        scrollbar-width: none;
    }

    .bz-board-pills::-webkit-scrollbar { display: none; }

    .bz-board-pill {
        flex: 0 0 auto;
        white-space: nowrap;
    }
}

/* Task 291 (GitLab #129/#131): the phone scrollers above hide their scrollbar, so nothing
   said "there is more to the right". MainLayout.razor.js marks whichever element actually
   overflows with `bz-scroll-more`; these rules turn that into a sticky arrow at the end of a
   swiping row and a one-line hint above a wide grid, and both vanish once the scroller has
   been swiped to its end. Declared as compound selectors so they never shadow the plain
   base pill/lane rules the phone override above depends on. */
.bz-scroll-hint { display: none; }

@media (max-width: 640px) {
    .bz-board-pills.bz-scroll-more::after,
    .bz-kanban-lane.bz-scroll-more::after {
        content: "➜";
        position: sticky;
        inset-inline-end: 0;
        flex: 0 0 auto;
        align-self: center;
        padding: 0 var(--bz-space-2);
        color: var(--bz-primary);
        background: linear-gradient(to right, transparent, var(--bz-color-bg) 40%);
        pointer-events: none;
    }

    /* Which element scrolls depends on the page: inside a `.bz-page-card` it is the card, a
       standalone grid scrolls in its own `.bz-dxgrid` — and a DxGrid with fixed column widths
       scrolls INSIDE itself (`.dxbl-scroll-viewer-content`, the usual case). The hint sits above
       the grid either way, so it is matched as a descendant of the marked scroller, as the
       marked grid's earlier sibling, and as the earlier sibling of the grid that CONTAINS the
       marked viewer. */
    .bz-scroll-more .bz-scroll-hint,
    .bz-grid-toolbar:has(~ .bz-dxgrid.bz-scroll-more) .bz-scroll-hint,
    .bz-grid-toolbar:has(~ .bz-dxgrid .bz-scroll-more) .bz-scroll-hint,
    .bz-scroll-hint:has(~ .bz-dxgrid.bz-scroll-more),
    .bz-scroll-hint:has(~ .bz-dxgrid .bz-scroll-more) {
        display: block;
        position: sticky;
        inset-inline-start: 0;
        font-size: var(--bz-fs-xs);
        color: var(--bz-color-fg-muted);
        padding: var(--bz-space-1) var(--bz-space-3);
    }
}

.bz-kanban-col {
    display: flex;
    flex-direction: column;
    flex: 0 0 20rem;
    max-height: calc(100vh - 19rem);
    background: var(--bz-color-surface-2);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-lg);
}

.bz-kanban-col--over {
    border-color: var(--bz-primary);
    background: color-mix(in srgb, var(--bz-primary) 8%, var(--bz-color-surface-2));
}

/* A collapsed column keeps its header (name + count) so nothing disappears silently;
   it just stops taking a full column's width. */
.bz-kanban-col--collapsed { flex: 0 0 14rem; }

.bz-kanban-col-toggle {
    border: 0;
    background: none;
    color: var(--bz-color-fg-muted);
    padding: 0;
    line-height: 1;
    cursor: pointer;
    flex: 0 0 auto;
}

.bz-kanban-col-toggle:hover { color: var(--bz-primary); }

.bz-kanban-col-head {
    display: flex;
    align-items: center;
    gap: var(--bz-space-2);
    padding: var(--bz-space-3);
    border-bottom: 1px solid var(--bz-color-border);
}

.bz-kanban-col-name {
    font-weight: var(--bz-fw-semibold);
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.bz-kanban-dot {
    /* Explicit, not inherited from a flex parent: inside a DxGrid cell the dot is a lone inline
       span, and an inline box takes no width/height at all — the Color columns rendered empty. */
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: var(--bz-radius-pill);
    flex: 0 0 auto;
}

/* Only the card list scrolls, so the header and the "new task" footer stay put. */
.bz-kanban-list {
    display: flex;
    flex-direction: column;
    gap: var(--bz-space-2);
    padding: var(--bz-space-2);
    overflow-y: auto;
    flex: 1;
    min-height: 3rem;
}

.bz-kanban-col-foot {
    padding: var(--bz-space-2);
    border-top: 1px solid var(--bz-color-border);
}

.bz-kanban-card {
    display: flex;
    flex-direction: column;
    gap: var(--bz-space-2);
    padding: var(--bz-space-3);
    background: var(--bz-color-surface);
    border: 1px solid var(--bz-color-border);
    border-left: 3px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    cursor: pointer;
}

.bz-kanban-card:hover { border-color: var(--bz-primary); }

.bz-kanban-card--dragging { opacity: .45; }

/* Drop placeholder: the card the dragged one would land above. */
.bz-kanban-card--drop-before { box-shadow: 0 -3px 0 0 var(--bz-primary); }

.bz-kanban-card-top {
    display: flex;
    align-items: center;
    gap: var(--bz-space-2);
    justify-content: space-between;
}

.bz-kanban-card-no {
    font-size: var(--bz-fs-xs);
    color: var(--bz-color-fg-muted);
}

.bz-kanban-card-title { line-height: 1.35; }

.bz-kanban-card-foot {
    display: flex;
    align-items: center;
    gap: var(--bz-space-2);
    flex-wrap: wrap;
}

/* ── Comment @mentions (task comments) ──────────────────────────────────────── */
.bz-mention {
    background: color-mix(in srgb, var(--bz-primary) 16%, transparent);
    color: var(--bz-color-accent-text);
    border-radius: var(--bz-radius-sm);
    padding: 0 2px;
    font-weight: var(--bz-fw-medium);
}

/* Autocomplete list under the comment box. Not a popup: it sits in flow so it never
   needs positioning JS and can never be clipped by the tab panel's overflow. */
.bz-mention-list {
    display: flex;
    flex-direction: column;
    margin-top: var(--bz-space-1);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    background: var(--bz-color-surface);
    max-height: 14rem;
    overflow-y: auto;
}

.bz-mention-item {
    display: flex;
    align-items: center;
    gap: var(--bz-space-2);
    padding: var(--bz-space-2) var(--bz-space-3);
    background: none;
    border: 0;
    text-align: left;
    cursor: pointer;
    color: inherit;
}

.bz-mention-item:hover { background: var(--bz-color-surface-2); }

/* ── Aufgabenplan workspace (task plan pin viewer) ──────────────────────────── */
/* Split layout: pin list on the left, pdf.js page + marker overlay on the right.
   CSS grid rather than the shell's row/col helpers, which this app does not ship. */
.bz-plan-layout {
    display: grid;
    grid-template-columns: minmax(18rem, 22rem) minmax(0, 1fr);
    gap: var(--bz-space-4);
    align-items: start;
}

@media (max-width: 1100px) {
    .bz-plan-layout { grid-template-columns: minmax(0, 1fr); }
}

.bz-plan-side {
    display: flex;
    flex-direction: column;
    gap: var(--bz-space-2);
    max-height: 78vh;
    overflow-y: auto;
}

/* Scroll box around the page canvas — a zoomed-in plan is meant to pan, not to stretch the page.
   Deliberately NOT a flex container: the viewer measures its own element's clientWidth to compute the
   fit-width scale, and a flex item shrinks to its content (i.e. to zero before the first render). */
.bz-plan-stage {
    overflow: auto;
    max-height: 78vh;
    background: var(--bz-color-surface-2);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    padding: var(--bz-space-3);
}

/* Centred while it fits, overflowing (and scrollable) once zoomed past the stage. */
.bz-plan-canvas {
    position: relative;
    display: block;
    margin: 0 auto;
    line-height: 0;
    box-shadow: 0 1px 6px rgb(0 0 0 / 18%);
    cursor: crosshair;
    touch-action: none; /* pointer events own the drag; the browser must not pan instead */
}

.bz-plan-canvas.is-dragging { cursor: grabbing; }

.bz-plan-page { display: block; }

/* The overlay never takes pointer events — the wrapper does all hit-testing. */
.bz-plan-markers {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.bz-plan-pin {
    display: flex;
    align-items: flex-start;
    gap: var(--bz-space-2);
    width: 100%;
    padding: var(--bz-space-2);
    border: 1px solid var(--bz-color-border);
    border-radius: var(--bz-radius-md);
    background: var(--bz-color-surface);
    text-align: left;
    cursor: pointer;
    color: inherit;
}

.bz-plan-pin:hover { background: var(--bz-color-surface-2); }

.bz-plan-pin.is-active {
    border-color: var(--bz-primary);
    box-shadow: 0 0 0 1px var(--bz-primary);
}

.bz-plan-pin-badge {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.65rem;
    height: 1.65rem;
    border-radius: 999px;
    font-size: var(--bz-fs-sm);
    font-weight: var(--bz-fw-semibold);
}

.bz-plan-pin-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    flex: 1;
}

.bz-plan-pin-title {
    line-height: 1.3;
    overflow-wrap: anywhere;
}

/* ── Calendar availability strip (Phase 2) ─────────────────────────────────
   One track per attendee across a fixed 07:00–20:00 working window; blocks are
   positioned as percentages by the component, so the track needs no grid. */
.bz-availability { margin-top: var(--bz-space-3); }

.bz-availability-row {
    display: flex;
    align-items: center;
    gap: var(--bz-space-2);
    margin-bottom: var(--bz-space-1);
}

.bz-availability-name {
    flex: 0 0 10rem;
    font-size: var(--bz-fs-sm);
    color: var(--bz-color-fg-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.bz-availability-track {
    position: relative;
    display: block;
    flex: 1 1 auto;
    height: 1.25rem;
    border-radius: var(--bz-radius-sm);
    background: var(--bz-color-surface-2);
}

.bz-availability-block {
    position: absolute;
    top: 0;
    bottom: 0;
    border-radius: var(--bz-radius-sm);
}

.bz-availability-busy { background: var(--bz-color-accent); }
.bz-availability-tentative { background: var(--bz-color-warn); opacity: .6; }
.bz-availability-oof { background: var(--bz-color-danger); }

/* Ranked "Suggest times" results, rendered under the availability strip. */
.bz-availability-suggestions {
    display: flex;
    flex-direction: column;
    gap: var(--bz-space-2);
    margin-top: var(--bz-space-3);
    padding-top: var(--bz-space-3);
    border-top: 1px solid var(--bz-color-border);
}

/* ── Appointment editor: form + day overview (work items #52, #53) ──────────
   Two columns inside the popup body; the day panel is advisory context, so it
   is the half that goes away on a narrow viewport rather than wrapping under
   a form the user is already scrolling. */
.bz-editor-cols {
    display: flex;
    align-items: flex-start;
    gap: var(--bz-space-4);
}

.bz-editor-main { flex: 1 1 auto; min-width: 0; }

.bz-day-panel {
    flex: 0 0 15rem;
    padding: var(--bz-space-3);
    border-left: 1px solid var(--bz-color-border);
}

/* Vertical twin of .bz-availability-track: the same fixed 07:00–20:00 window,
   with blocks positioned top/height as percentages by the component. */
.bz-day-track {
    position: relative;
    height: 520px;
    margin-top: var(--bz-space-2);
    border-radius: var(--bz-radius-sm);
    background: var(--bz-color-surface-2);
}

.bz-day-tick {
    position: absolute;
    left: 0;
    font-size: var(--bz-fs-xs);
    color: var(--bz-color-fg-muted);
    border-top: 1px solid var(--bz-color-border);
    width: 100%;
    padding-left: 2px;
}

.bz-day-block {
    position: absolute;
    left: 2.75rem;
    right: var(--bz-space-1);
    overflow: hidden;
    padding: 0 var(--bz-space-1);
    border-radius: var(--bz-radius-sm);
    font-size: var(--bz-fs-xs);
    line-height: 1.1rem;
    color: #fff;
    background: var(--bz-color-accent);
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* The unsaved draft: outlined rather than filled, so it never reads as an
   existing commitment on the day. */
.bz-day-draft {
    background: transparent;
    color: var(--bz-color-fg);
    border: 2px dashed var(--bz-color-accent);
}

/* Date + time as two editors over one value; the time half stays narrow. */
.bz-datetime-row {
    display: flex;
    gap: var(--bz-space-2);
}

.bz-time-edit { flex: 0 0 7rem; }

/* One break row: start / end / duration, each column the same width as the label above it so the
   header line actually sits over the field it names. */
.bz-break-col {
    flex: 0 0 7rem;
    display: inline-block;
}

@media (max-width: 991px) {
    .bz-day-panel { display: none; }
}

/* ── Resource booking blocks (Calendar Phase 4) ──────────────────────────────
   A PENDING booking is not holding the slot yet: it renders hollow (dashed
   outline, transparent fill) so the grid never claims a resource is taken when
   any other request can still win it. An approved/no-approval booking is a
   solid block, i.e. the scheduler's own default. */
.bz-res-block {
    display: block;
    width: 100%;
    height: 100%;
    padding: 0 var(--bz-space-1);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.bz-res-block-pending {
    background: repeating-linear-gradient(
        45deg,
        transparent,
        transparent 4px,
        var(--bz-color-surface-2) 4px,
        var(--bz-color-surface-2) 8px);
    border: 1px dashed var(--bz-color-warn);
    border-radius: var(--bz-radius-sm);
    color: var(--bz-color-fg-muted);
    font-style: italic;
}

/* ── Breadcrumbs (#30) ────────────────────────────────────────────────────
   Moved here from Web.Common/Layout/Breadcrumbs.razor.css: the same bz-breadcrumb* classes are also
   emitted by PageHeader.razor and BreadcrumbItem.razor, two different components whose scoped-CSS
   `[b-xxx]` attribute never matches Breadcrumbs.razor's own scope — so only Breadcrumbs.razor itself
   ever picked up the styling. Global rules here cover all three emitters.
   Visual styling matches the Metronic Demo10 inline breadcrumb (small, secondary color, muted
   separators). */
.bz-breadcrumbs {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    font-size: 0.875rem;
    line-height: 1.25rem;
    font-weight: 400;
    flex-wrap: wrap;
}

.bz-breadcrumb-item {
    color: var(--bz-color-fg-muted);
    text-decoration: none;
    transition: color var(--bz-duration-fast) ease;
}

.bz-breadcrumb-item:hover {
    color: var(--bz-brand-primary);
}

.bz-breadcrumb-active {
    color: var(--bz-color-fg);
    font-weight: 500;
    cursor: default;
}

.bz-breadcrumb-active:hover {
    color: var(--bz-color-fg);
}

.bz-breadcrumb-sep {
    color: var(--bz-color-fg-subtle);
    user-select: none;
}

/* ── Print (GitLab #66) ─────────────────────────────────────────────────────
   Hide the shell chrome so any page — the calendar print view, AddressListPrint,
   or a plain Ctrl+P — prints content only.
   Selectors come from Layouts/MainLayout.razor: #header (mobile header), #sidebar
   (the aside), footer.footer, the global activity bar and the AI chat widget. The
   main column indents by var(--sidebar-width), so it is zeroed alongside. Overflow
   containers clip to one page when printing, hence the visible/auto overrides. */
@media print {
    #header,
    #sidebar,
    footer.footer,
    .bz-topbar-progress,
    .bz-ai-chat-root,
    .bz-bottom-nav {
        display: none !important;
    }

    :root {
        --sidebar-width: 0px;
    }

    #scrollable_content {
        overflow: visible !important;
        height: auto !important;
    }
}

/* ── Phone shell: header toggle, bottom navigation (GitLab #107) ──────────
   Everything here is scoped to below Tailwind's lg breakpoint (1024px), which is exactly where
   the sidebar turns into a KTUI drawer. Written as bz-* classes on --bz-* tokens because the
   compiled Metronic/Tailwind CSS is committed and not rebuilt on every change. */

/* The mobile header sits on bg-mono (light) / bg-background (dark) — both dark surfaces — so its
   contents are always light. Without this BrandMark's <strong> inherited the dark foreground and
   the brand name was invisible against the black bar. */
.bz-mobile-header { color: #fff; }
.bz-mobile-header strong,
.bz-mobile-header a { color: inherit; }
/* GitLab #111: a real tenant display name is long ("Anawaz Bau- und Sanierungs GmbH"). Let it
   truncate on one line instead of wrapping the header (flex-wrap) onto a second row. */
.bz-mobile-header .brand { min-width: 0; }
.bz-mobile-header .brand strong {
    display: block;
    max-width: calc(100vw - 96px);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 44px touch target, leading the header. */
.bz-mobile-nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin-inline-start: -10px;
    border: none;
    border-radius: var(--bz-radius-md);
    background: transparent;
    color: inherit;
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
}
.bz-mobile-nav-toggle:active { background: rgba(255, 255, 255, .14); }

/* Blocks that only exist on a phone (the Home "My Day" digest), and their counterpart:
   blocks that only exist on a desktop (task 286 — the calendar's own ← Today → row, which
   moves into the page header on a phone). Neither carries a `display` of its own outside the
   breakpoint, so a page may style the INNER element freely without out-specifying the
   utility — put the class on a wrapper, never on the element you are laying out. */
.bz-mobile-only { display: none; }

/* Same pair, for a wrapper whose CHILDREN have to stay in the parent's flex row:
   `display: contents` makes the wrapper itself disappear from layout, which
   `bz-mobile-only`'s `display: block` cannot do — a block inside a header action
   row would break the row instead of joining it (task 289 — GitLab #121). */
.bz-mobile-contents { display: none; }
.bz-desktop-contents { display: contents; }

.bz-bottom-nav { display: none; }

@media (max-width: 1023.98px) {
    :root { --bz-mobile-nav-h: 64px; }

    .bz-mobile-only { display: block; }
    .bz-desktop-only { display: none; }
    .bz-mobile-contents { display: contents; }
    .bz-desktop-contents { display: none; }

    /* The saved-views bar is a label plus "no saved views yet" until the user saves
       one — a whole row of nothing above the board. Its Save action moves into the
       page header on a phone, so the empty bar has nothing left to show. */
    .bz-views-bar--empty { display: none; }

    .bz-bottom-nav {
        position: fixed;
        inset-inline: 0;
        bottom: 0;
        /* Below the drawer (100) and its backdrop (99), above page content. */
        z-index: 30;
        display: flex;
        align-items: stretch;
        background: var(--bz-color-surface);
        border-top: 1px solid var(--bz-color-border);
        box-shadow: 0 -1px 3px rgba(0, 0, 0, .08);
        padding-bottom: env(safe-area-inset-bottom, 0px);
    }

    .bz-bottom-nav-item {
        flex: 1 1 0;
        min-width: 0;
        min-height: 44px;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 2px;
        padding: var(--bz-space-2) 2px;
        border: none;
        background: transparent;
        color: var(--bz-color-fg-muted);
        font-size: var(--bz-fs-xs);
        text-decoration: none;
        cursor: pointer;
    }
    .bz-bottom-nav-item i { font-size: 20px; line-height: 1; }
    .bz-bottom-nav-item span {
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .bz-bottom-nav-item.is-active { color: var(--bz-color-accent); }
    .bz-bottom-nav-item:active { background: var(--bz-color-surface-2); }
    /* #231: the AI item's indicator sits on its icon's shoulder, not at the far edge of the item. */
    .bz-bottom-nav-item .bz-ai-indicator { inset-inline-end: calc(50% - 16px); }

    /* Keep the last rows of any page reachable above the bar, and lift the floating chrome. */
    #scrollable_content { padding-bottom: var(--bz-mobile-nav-h); }
    .bz-toast-host { top: calc(var(--header-height) + var(--bz-space-3)); }

    /* GitLab #110: the docked panel was sized in vh while its root had been lifted by the bottom
       bar, so `top` resolved NEGATIVE on a phone (-28px at 375x667, worse once the URL bar shows)
       and the header controls were clipped with nowhere to scroll. On a phone the panel is simply
       the screen: no vh anywhere, so the URL bar and the software keyboard (viewport meta carries
       interactive-widget=resizes-content) resize the layout viewport and the panel with it.
       Excludes the embedded SmartEntry variant, which is laid out by its host page. */
    .bz-ai-chat-root:not(.is-embedded) .bz-ai-chat-panel {
        position: fixed;
        inset: 0;
        width: auto;
        height: auto;
        max-width: none;
        max-height: none;
        border: 0;
        border-radius: 0;
        padding-top: env(safe-area-inset-top, 0px);
        padding-bottom: env(safe-area-inset-bottom, 0px);
        animation: none;
    }
    /* Already full-screen — the maximize toggle has nothing left to do. */
    .bz-ai-chat-root:not(.is-embedded) [data-testid="ai-chat-maximize"] { display: none; }
    /* The head must never be squeezed by the message list, and the DevExpress surface must be
       allowed to shrink so ITS list is the scroller (desktop keeps the 120px floor). */
    .bz-ai-chat-root:not(.is-embedded) .bz-ai-chat-head { flex: 0 0 auto; }
    .bz-ai-chat-root:not(.is-embedded) .bz-ai-chat-surface { min-height: 0; }

}

/* ══ DevExpress ⇄ design-token bridge (task 317 — GitLab #166) ═══════════════════════════
   Every DevExpress component paints itself from its own --dxbl-* variables, which resolve
   to the Fluent theme's --dxds-* palette. IThemeChangeService swaps that palette's light
   and dark file, so DevExpress is not "light-only" — but its palette is not Metronic's, so
   the controls drifted away from the page around them, worst in dark mode.

   Mapping the --dxbl-* variables onto the --bz-* tokens (which follow Metronic since task
   317) makes the controls follow the PAGE in both themes, whichever Fluent mode file
   happens to be loaded. Everything below is a VARIABLE assignment, never a property
   override, with two documented exceptions: DevExpress keeps owning layout and state
   logic, we only supply the colours.

   These rules match DevExpress's own selectors at equal specificity and win on source
   order — shell.css is linked after the theme in App.razor.

   SCOPE. The families here plus the button and grid blocks earlier in this file are the
   eight that carry >95% of the surface: DxButton (1664 uses), DxFormLayoutItem (1554),
   DxGridDataColumn (1334), DxTextBox (602), DxComboBox (454), DxSpinEdit (353),
   DxCheckBox (293), DxPopup (282), DxDateEdit (109), DxMemo (85), DxTagBox (38).
   DELIBERATELY NOT THEMED: Scheduler (13 tags), TreeList, RichEdit, HtmlEditor,
   DateRangePicker and Calendar (1 each) — about 20 usages between them, none of which
   justifies its own variable map. Theme one of those when a page actually needs it.

   KEEP IT HERE. The repo is ~92% centralized; only five files carry .dxbl-* selectors at
   all. A new DevExpress override belongs in this section, not in a module's .razor.css,
   because a scoped stylesheet cannot reach a popup that renders outside the component.
   ════════════════════════════════════════════════════════════════════════════════════ */

/* ── Overlay layering ────────────────────────────────────────────────────────────────
   The stack the app already uses, written down so the next overlay picks a band instead
   of escalating a z-index. These are documentation of the existing numbers, not a new
   scheme — nothing is renumbered here.

     30      sticky page chrome (support banner, mobile bottom nav)
     50-60   floating app chrome (AI chat root, mobile header)
     1000    DevExpress drop-downs and pop-ups rendered into the portal
     1055    DevExpress modal root (.dxbl-modal > .dxbl-modal-root)
     1070    DevExpress flyout
     2000    app-level overlays that must beat a modal: busy bar, toast host,
             image-cropper backdrop, the reconnect overlay

   A DevExpress drop-down opened from inside a DevExpress modal is handled by DevExpress
   itself (the portal re-parents it); do not lift a dropdown above 1055 by hand — it will
   then also float above an unrelated modal. */

/* ── Text editors — DxTextBox, DxMemo, DxSpinEdit, DxDateEdit, DxComboBox, DxTagBox ──
   All of them render a .dxbl-text-edit root, so one block is the whole form-control
   family: identical height, padding, border width, radius, placeholder and focus ring
   next to a Metronic .kt-input. */
.dxbl-text-edit {
    --dxbl-text-edit-font-family: var(--bz-font-sans);
    --dxbl-text-edit-font-size: var(--bz-fs-md);
    --dxbl-text-edit-border-radius: var(--bz-radius-sm);
    --dxbl-text-edit-border-width: 1px;
    /* The page colour, not the card: an inset field like Metronic's .kt-input (background:
       var(--background)), so in dark mode a field reads as distinct from the card or dialog it sits
       on (task 343). Both are white in light mode. */
    --dxbl-text-edit-bg: var(--bz-color-bg);
    --dxbl-text-edit-color: var(--bz-color-fg);
    --dxbl-text-edit-placeholder-color: var(--bz-color-fg-subtle);
    --dxbl-text-edit-border-color: var(--bz-color-border);
    --dxbl-text-edit-border-hovered-color: var(--bz-color-border-strong);
    --dxbl-text-edit-border-active-color: var(--bz-color-accent);
    --dxbl-text-edit-focus-border-color: var(--bz-color-accent);
    /* Fluent underlines the focused field; keep the affordance, in our accent. */
    --dxbl-text-edit-underline-color: var(--bz-color-border);
    --dxbl-text-edit-underline-hovered-color: var(--bz-color-border-strong);
    --dxbl-text-edit-underline-focused-color: var(--bz-color-accent);
    --dxbl-text-edit-underline-active-color: var(--bz-color-accent);
    --dxbl-text-edit-validation-invalid-color: var(--bz-color-danger);
    --dxbl-text-edit-validation-valid-color: var(--bz-color-ok);

    /* Read-only and disabled. DevExpress's own .dxbl-readonly / .dxbl-disabled rules
       feed --dxbl-text-edit-bg from these, so declaring them once here is enough —
       no higher-specificity rule of ours is needed. */
    --dxbl-text-edit-readonly-bg: var(--bz-color-surface-2);
    --dxbl-text-edit-disabled-bg: var(--bz-color-surface-2);
    --dxbl-text-edit-disabled-color: var(--bz-color-fg-subtle);
    --dxbl-text-edit-disabled-border-color: var(--bz-color-border);

    /* The embedded buttons: drop-down arrow, calendar, spin up/down, clear, search.
       #166 reported these as unreadable in dark mode. --dxbl-image-fill is already
       `currentcolor` in the Fluent theme, so the icon follows whatever colour these
       set — the defect was the COLOUR, not the fill, which is why every state below
       is given one and the fills are pinned back to currentColor. */
    --dxbl-text-edit-btn-bg: transparent;
    --dxbl-text-edit-btn-color: var(--bz-color-fg-muted);
    --dxbl-text-edit-btn-image-color: currentColor;
    --dxbl-text-edit-btn-hover-bg: var(--bz-color-surface-2);
    --dxbl-text-edit-btn-hovered-bg: var(--bz-color-surface-2);
    --dxbl-text-edit-btn-hover-color: var(--bz-color-fg);
    --dxbl-text-edit-btn-hover-image-color: currentColor;
    --dxbl-text-edit-btn-box-shadow-color: var(--bz-color-focus-ring);
    --dxbl-text-edit-btn-disabled-bg: transparent;
    --dxbl-text-edit-btn-disabled-color: var(--bz-color-fg-subtle);
    --dxbl-text-edit-btn-disabled-image-color: currentColor;
    --dxbl-text-edit-btn-disabled-fill: currentColor;
    --dxbl-text-edit-btn-readonly-fill: currentColor;
    --dxbl-text-edit-close-btn-bg: transparent;
    --dxbl-text-edit-close-btn-color: var(--bz-color-fg-muted);
    --dxbl-text-edit-close-btn-hovered-bg: var(--bz-color-surface-2);
    --dxbl-text-edit-close-btn-hovered-color: var(--bz-color-fg);
}

/* Keyboard focus must stay visible in both themes. DevExpress paints the focused field
   with a border colour only; on zinc-950 a 1px accent border is a thin line, so the
   token focus ring is added on top — the same ring :focus-visible gives everything else. */
.dxbl-text-edit {
    /* Named property, not `all` — and the task-116 hover duration, so the ring arrives in the
       same moment as every other hover/focus response in the app. */
    transition: box-shadow var(--bz-duration-fast) var(--bz-ease);
}

.dxbl-text-edit:focus-within {
    box-shadow: 0 0 0 3px var(--bz-color-focus-ring);
}

/* ── Tags inside a DxTagBox ────────────────────────────────────────────────────────── */
.dxbl-tag-box {
    --dxbl-tag-box-tag-bg: var(--bz-color-surface-3);
    --dxbl-tag-box-tag-color: var(--bz-color-fg);
    --dxbl-tag-box-tag-close-btn-bg: transparent;
    --dxbl-tag-box-tag-close-btn-color: var(--bz-color-fg-muted);
    --dxbl-tag-box-tag-close-btn-hover-bg: color-mix(in srgb, var(--bz-color-danger) 14%, transparent);
    --dxbl-tag-box-tag-close-btn-hover-color: var(--bz-color-danger);
    --dxbl-tag-box-tag-outline-focus-color: var(--bz-color-accent);
    --dxbl-tag-box-tag-disabled-bg: var(--bz-color-surface-2);
    --dxbl-tag-box-tag-disabled-color: var(--bz-color-fg-subtle);
    --dxbl-tag-box-tag-readonly-bg: var(--bz-color-surface-2);
    --dxbl-tag-box-tag-readonly-color: var(--bz-color-fg-muted);
}

/* ── Drop-down surfaces ─────────────────────────────────────────────────────────────
   A DevExpress drop-down or pop-up is rendered OUTSIDE the component's DOM, into a
   portal on <body>, so nothing scoped can reach it and "dark control, light popup" is
   the default failure. These are the portal roots themselves.
   --dxbl-popups-* is declared by the theme on exactly this selector list, which is why
   one rule covers drop-downs, message boxes, modals and windows alike. */
.dxbl-dropdown,
.dxbl-itemlist-dropdown,
.dxbl-message-box,
.dxbl-modal,
.dxbl-window {
    --dxbl-popups-bg: var(--bz-color-popover);
    --dxbl-popups-color: var(--bz-color-fg);
    --dxbl-popups-body-bg: var(--bz-color-popover);
    --dxbl-popups-body-color: var(--bz-color-fg);
    --dxbl-popups-border-color: var(--bz-color-border);
    --dxbl-popups-box-shadow: var(--bz-shadow-lg);
    --dxbl-popups-header-bg: var(--bz-color-popover);
    --dxbl-popups-header-color: var(--bz-color-fg);
    --dxbl-popups-header-border-color: var(--bz-color-border);
    --dxbl-popups-footer-bg: var(--bz-color-popover);
    --dxbl-popups-footer-color: var(--bz-color-fg);
    /* The close X in the header — same icon-contrast defect as the editor buttons. */
    --dxbl-popups-header-close-btn-bg: transparent;
    --dxbl-popups-header-close-btn-color: var(--bz-color-fg-muted);
    --dxbl-popups-header-close-btn-hover-bg: var(--bz-color-surface-2);
    --dxbl-popups-header-close-btn-hover-color: var(--bz-color-fg);
    --dxbl-popups-header-close-btn-outline-color: var(--bz-color-accent);
    --dxbl-popups-backdrop-bg: rgb(0 0 0 / .45);
}

/* The window/popup families read their own names rather than --dxbl-popups-*. */
.dxbl-modal,
.dxbl-window {
    --dxbl-popup-bg: var(--bz-color-popover);
    --dxbl-popup-color: var(--bz-color-fg);
    --dxbl-popup-body-bg: var(--bz-color-popover);
    --dxbl-popup-body-color: var(--bz-color-fg);
    --dxbl-popup-border-color: var(--bz-color-border);
    --dxbl-popup-shadow: var(--bz-shadow-lg);
    --dxbl-popup-header-bg: var(--bz-color-popover);
    --dxbl-popup-header-color: var(--bz-color-fg);
    --dxbl-popup-header-btn-bg: transparent;
    --dxbl-popup-header-btn-color: var(--bz-color-fg-muted);
    --dxbl-popup-header-btn-hover-bg: var(--bz-color-surface-2);
    --dxbl-popup-header-btn-hover-color: var(--bz-color-fg);
    --dxbl-popup-header-btn-outline-color: var(--bz-color-accent);
    --dxbl-popup-footer-bg: var(--bz-color-popover);
    --dxbl-popup-footer-color: var(--bz-color-fg);
    --dxbl-popup-back-bg: rgb(0 0 0 / .45);
    --dxbl-window-bg: var(--bz-color-popover);
    --dxbl-window-color: var(--bz-color-fg);
    --dxbl-window-body-bg: var(--bz-color-popover);
    --dxbl-window-body-color: var(--bz-color-fg);
    --dxbl-window-border-color: var(--bz-color-border);
    --dxbl-window-box-shadow: var(--bz-shadow-lg);
    --dxbl-window-header-bg: var(--bz-color-popover);
    --dxbl-window-header-color: var(--bz-color-fg);
    --dxbl-window-header-btn-bg: transparent;
    --dxbl-window-header-btn-color: var(--bz-color-fg-muted);
    --dxbl-window-header-btn-hover-bg: var(--bz-color-surface-2);
    --dxbl-window-header-btn-hover-color: var(--bz-color-fg);
    --dxbl-window-header-btn-outline-color: var(--bz-color-accent);
    --dxbl-window-footer-bg: var(--bz-color-popover);
    --dxbl-window-footer-color: var(--bz-color-fg);
    --dxbl-window-focus-outline-color: var(--bz-color-accent);
}

/* The editor drop-down wrapper (combo box, date edit, tag box, lookup). */
.dxbl-edit-dropdown {
    --dxbl-edit-dropdown-bg: var(--bz-color-popover);
    --dxbl-edit-dropdown-color: var(--bz-color-fg);
    --dxbl-edit-dropdown-border-color: var(--bz-color-border);
    --dxbl-edit-dropdown-border-width: 1px;
    --dxbl-edit-dropdown-border-radius: var(--bz-radius-md);
    --dxbl-edit-dropdown-box-shadow: var(--bz-shadow-md);
    --dxbl-edit-dropdown-font-family: var(--bz-font-sans);
    --dxbl-edit-dropdown-font-size: var(--bz-fs-md);
    --dxbl-edit-dropdown-footer-bg: var(--bz-color-popover);
    --dxbl-edit-dropdown-footer-color: var(--bz-color-fg);
    --dxbl-edit-dropdown-footer-border-color: var(--bz-color-border);
    --dxbl-edit-dropdown-btn-focus-outline-color: var(--bz-color-accent);
}

.dxbl-dropdown,
.dxbl-itemlist-dropdown {
    --dxbl-dropdown-bg: var(--bz-color-popover);
    --dxbl-dropdown-color: var(--bz-color-fg);
    --dxbl-dropdown-border-color: var(--bz-color-border);
    --dxbl-dropdown-border-width: 1px;
    --dxbl-dropdown-border-radius: var(--bz-radius-md);
    --dxbl-dropdown-box-shadow: var(--bz-shadow-md);
    --dxbl-dropdown-body-bg: var(--bz-color-popover);
    --dxbl-dropdown-body-color: var(--bz-color-fg);
    --dxbl-dropdown-body-font-family: var(--bz-font-sans);
    --dxbl-dropdown-body-font-size: var(--bz-fs-md);
    --dxbl-dropdown-header-bg: var(--bz-color-popover);
    --dxbl-dropdown-header-color: var(--bz-color-fg);
    --dxbl-dropdown-header-border-color: var(--bz-color-border);
    --dxbl-dropdown-header-font-family: var(--bz-font-sans);
    --dxbl-dropdown-footer-bg: var(--bz-color-popover);
    --dxbl-dropdown-footer-color: var(--bz-color-fg);
    --dxbl-dropdown-footer-font-family: var(--bz-font-sans);
}

/* ── List boxes — the item rows inside every drop-down, and the standalone DxListBox ──
   The metrics for these live in the (pointer: coarse) block earlier in this file; this
   is the colour half, deliberately unconditional. A combo box renders
   .dxbl-list-box-render-container, a standalone list box renders .dxbl-list-box
   (verified DOM, 26.1.3) — both are the element the theme declares these on.
   Selected and selected+hovered use the Metronic accent, not Fluent's own blue. */
.dxbl-list-box,
.dxbl-list-box-render-container {
    --dxbl-list-box-bg: var(--bz-color-popover);
    --dxbl-list-box-color: var(--bz-color-fg);
    --dxbl-list-box-border-color: var(--bz-color-border);
    --dxbl-list-box-item-bg: transparent;
    --dxbl-list-box-item-color: var(--bz-color-fg);
    --dxbl-list-box-item-border-color: transparent;
    --dxbl-list-box-item-hover-bg: var(--bz-color-surface-2);
    --dxbl-list-box-item-hover-color: var(--bz-color-fg);
    /* Selected row: an accent TINT with the normal foreground, not a solid accent fill.
       A solid fill means accent-foreground (white) on --primary, which is 3.7:1 against
       Metronic's light blue-500 and 5.2:1 against its dark blue-600 — a selected option label
       is body text, so the light theme fails WCAG AA outright. Measured live at 3.76:1, which
       is what IntelligentAutomationSmokeTests caught. The tint keeps the Metronic hue, reads
       as clearly selected, matches the multi-select and grid-selection treatments below, and
       measures about 15:1 in both themes. Buttons and the pager keep the solid Metronic fill
       on purpose — those are UI components at the 3:1 threshold, not body text. */
    --dxbl-list-box-item-selected-bg: color-mix(in srgb, var(--bz-color-accent) 22%, var(--bz-color-popover));
    --dxbl-list-box-item-selected-color: var(--bz-color-fg);
    --dxbl-list-box-item-selected-hover-bg: color-mix(in srgb, var(--bz-color-accent) 30%, var(--bz-color-popover));
    --dxbl-list-box-item-selected-hover-color: var(--bz-color-fg);
    --dxbl-list-box-item-selected-focused-color: var(--bz-color-fg);
    --dxbl-list-box-item-selected-disabled-bg: var(--bz-color-surface-2);
    --dxbl-list-box-item-selected-disabled-color: var(--bz-color-fg-subtle);
    --dxbl-list-box-item-multi-selected-bg: color-mix(in srgb, var(--bz-color-accent) 16%, transparent);
    --dxbl-list-box-item-multi-selected-color: var(--bz-color-fg);
    --dxbl-list-box-item-multi-selected-hover-bg: color-mix(in srgb, var(--bz-color-accent) 24%, transparent);
    --dxbl-list-box-item-multi-selected-hover-color: var(--bz-color-fg);
    --dxbl-list-box-item-multi-selected-disabled-bg: var(--bz-color-surface-2);
    --dxbl-list-box-item-multi-selected-disabled-color: var(--bz-color-fg-subtle);
    --dxbl-list-box-item-disabled-bg: transparent;
    --dxbl-list-box-item-disabled-color: var(--bz-color-fg-subtle);
    --dxbl-list-box-disabled-bg: var(--bz-color-surface-2);
    --dxbl-list-box-disabled-color: var(--bz-color-fg-subtle);
    --dxbl-list-box-readonly-bg: var(--bz-color-surface-2);
    --dxbl-list-box-group-item-color: var(--bz-color-fg-muted);
    --dxbl-list-box-group-item-disabled-color: var(--bz-color-fg-subtle);
    --dxbl-list-box-header-bg: var(--bz-color-surface-2);
    --dxbl-list-box-header-color: var(--bz-color-fg);
    /* "No data" state — Fluent grey on our surface before this. */
    --dxbl-list-box-empty-data-text-color: var(--bz-color-fg-muted);
    --dxbl-list-box-empty-data-icon-color: var(--bz-color-fg-subtle);
    --dxbl-list-box-highlighted-text-bg: color-mix(in srgb, var(--bz-color-accent) 28%, transparent);
    --dxbl-list-box-highlighted-text-color: var(--bz-color-fg);
    --dxbl-list-box-focus-color: var(--bz-color-fg);
    --dxbl-list-box-focus-frame-color: var(--bz-color-accent);
    --dxbl-list-box-check-element-selected-disabled-background-color: var(--bz-color-surface-2);
    --dxbl-list-box-check-element-selected-disabled-border-color: var(--bz-color-border);
    --dxbl-list-box-check-element-selected-disabled-color: var(--bz-color-fg-subtle);
    --dxbl-list-box-validation-invalid-color: var(--bz-color-danger);
    --dxbl-list-box-validation-valid-color: var(--bz-color-ok);
}

/* DevExpress draws its own scroll bar inside a scroll viewer — it is a DIV, not a native
   scroll bar, so ::-webkit-scrollbar never reaches it and it kept the Fluent grey inside
   our popups. There is no variable for it; this is one of the two property overrides. */
.dxbl-scroll-viewer-scroll-thumb {
    background-color: var(--bz-color-border-strong);
    border-radius: var(--bz-radius-pill);
}

/* ── Column chooser ─────────────────────────────────────────────────────────────────
   Renders into the pop-up portal, so its surface, border and shadow come from the
   .dxbl-modal / .dxbl-window rules above. These two variables belong to the GRID family
   and would otherwise resolve against a .dxbl-grid ancestor the portalled dialog does
   not have — which is why they are repeated on the dialog itself. */
.dxbl-grid,
.dxbl-grid-column-chooser,
.dxbl-grid-column-chooser-dialog {
    --dxbl-grid-column-chooser-separator-color: var(--bz-color-border);
    --dxbl-grid-column-chooser-focus-frame-color: var(--bz-color-accent);
}

/* ── Pager ──────────────────────────────────────────────────────────────────────────
   Previously addressed through --dxbl-grid-pager-bg, which does not exist: the pager is
   its own family and its arrows are among the icons #166 called out. */
.dxbl-pager {
    --dxbl-pager-bg: var(--bz-color-surface-2);
    --dxbl-pager-color: var(--bz-color-fg);
    --dxbl-pager-border-color: var(--bz-color-border);
    --dxbl-pager-btn-bg: transparent;
    --dxbl-pager-btn-color: var(--bz-color-fg-muted);
    --dxbl-pager-btn-border-color: transparent;
    --dxbl-pager-btn-hover-bg: var(--bz-color-surface-3);
    --dxbl-pager-btn-hover-color: var(--bz-color-fg);
    --dxbl-pager-btn-hover-border-color: transparent;
    --dxbl-pager-btn-active-bg: var(--bz-color-surface-3);
    --dxbl-pager-btn-active-color: var(--bz-color-fg);
    --dxbl-pager-btn-active-border-color: transparent;
    --dxbl-pager-btn-disabled-bg: transparent;
    --dxbl-pager-btn-disabled-color: var(--bz-color-fg-subtle);
    --dxbl-pager-btn-disabled-border-color: transparent;
    --dxbl-pager-btn-focus-outline-color: var(--bz-color-accent);
    --dxbl-pager-page-btn-bg: transparent;
    --dxbl-pager-page-btn-color: var(--bz-color-fg);
    --dxbl-pager-page-btn-border-color: transparent;
    --dxbl-pager-page-btn-hover-bg: var(--bz-color-surface-3);
    --dxbl-pager-page-btn-hover-color: var(--bz-color-fg);
    --dxbl-pager-page-btn-hover-border-color: transparent;
    --dxbl-pager-page-btn-active-bg: var(--bz-color-accent);
    --dxbl-pager-page-btn-active-color: var(--bz-color-accent-fg);
    --dxbl-pager-page-btn-active-border-color: var(--bz-color-accent);
    --dxbl-pager-active-page-btn-bg: var(--bz-color-accent);
    --dxbl-pager-active-page-btn-color: var(--bz-color-accent-fg);
    --dxbl-pager-active-page-btn-border-color: var(--bz-color-accent);
    --dxbl-pager-page-edit-bg: var(--bz-color-surface);
    --dxbl-pager-page-edit-color: var(--bz-color-fg);
    --dxbl-pager-page-edit-text-color: var(--bz-color-fg);
    --dxbl-pager-page-edit-border-color: var(--bz-color-border);
    --dxbl-pager-page-edit-underline-color: var(--bz-color-border);
    --dxbl-pager-page-edit-underline-focused-color: var(--bz-color-accent);
}

/* ── Check boxes and switches ───────────────────────────────────────────────────────
   DevExpress derives --dxbl-checkbox-check-element-bg from the primary/success/danger
   variables per state, so only these need supplying. */
.dxbl-checkbox {
    --dxbl-checkbox-color: var(--bz-color-fg);
    --dxbl-checkbox-disabled-color: var(--bz-color-fg-subtle);
    --dxbl-checkbox-primary-unchecked-check-element-bg: var(--bz-color-surface);
    --dxbl-checkbox-primary-unchecked-check-element-trigger-bg: var(--bz-color-border-strong);
    --dxbl-checkbox-primary-unchecked-hover-check-element-trigger-bg: var(--bz-color-fg-muted);
    --dxbl-checkbox-check-element-primary-unchecked-hover-bg: var(--bz-color-surface-2);
    --dxbl-checkbox-primary-checked-check-element-bg: var(--bz-color-accent);
    --dxbl-checkbox-primary-checked-check-element-trigger-bg: var(--bz-color-accent-fg);
    --dxbl-checkbox-primary-checked-hover-check-element-bg: var(--bz-color-accent-hover);
    --dxbl-checkbox-primary-checked-hover-check-element-trigger-bg: var(--bz-color-accent-fg);
    --dxbl-checkbox-primary-checked-readonly-check-element-bg: var(--bz-color-surface-3);
    --dxbl-checkbox-primary-checked-readonly-check-element-trigger-bg: var(--bz-color-fg-subtle);
    --dxbl-checkbox-primary-unchecked-readonly-check-element-bg: var(--bz-color-surface-2);
    --dxbl-checkbox-primary-unchecked-readonly-check-element-trigger-bg: var(--bz-color-fg-subtle);
    --dxbl-checkbox-primary-indeterminate-readonly-check-element-bg: var(--bz-color-surface-3);
    --dxbl-checkbox-indeterminate-check-element-bg: var(--bz-color-accent);
    --dxbl-checkbox-indeterminate-hover-check-element-bg: var(--bz-color-accent-hover);
    --dxbl-checkbox-hover-check-element-bg: var(--bz-color-surface-2);
    --dxbl-checkbox-hover-check-element-trigger-bg: var(--bz-color-fg-muted);
    --dxbl-checkbox-check-element-bg: var(--bz-color-surface);
    --dxbl-checkbox-check-element-check-icon-color: var(--bz-color-accent-fg);
    --dxbl-checkbox-check-element-check-icon-primary-color: var(--bz-color-accent-fg);
    --dxbl-checkbox-check-element-switch-trigger-bg: var(--bz-color-accent-fg);
    --dxbl-checkbox-focused-check-element-frame-outline-color: var(--bz-color-accent);
    --dxbl-checkbox-success-checked-check-element-bg: var(--bz-color-ok);
    --dxbl-checkbox-success-checked-hover-check-element-bg: var(--bz-color-ok);
    --dxbl-checkbox-success-checked-check-element-trigger-bg: var(--bz-color-accent-fg);
    --dxbl-checkbox-success-checked-check-element-hover-trigger-bg: var(--bz-color-accent-fg);
    --dxbl-checkbox-success-indeterminate-check-element-bg: var(--bz-color-ok);
    --dxbl-checkbox-success-indeterminate-hover-check-element-bg: var(--bz-color-ok);
    --dxbl-checkbox-danger-checked-check-element-bg: var(--bz-color-danger);
    --dxbl-checkbox-danger-checked-hover-check-element-bg: var(--bz-color-danger);
    --dxbl-checkbox-danger-checked-check-element-trigger-bg: var(--bz-color-accent-fg);
    --dxbl-checkbox-danger-checked-hover-check-element-trigger-bg: var(--bz-color-accent-fg);
    --dxbl-checkbox-danger-indeterminate-check-element-bg: var(--bz-color-danger);
    --dxbl-checkbox-danger-indeterminate-hover-check-element-bg: var(--bz-color-danger);
}

/* ── Form layout — the caption/group chrome around 1554 DxFormLayoutItem uses ──────── */
.dxbl-fl {
    --dxbl-fl-caption-color: var(--bz-color-fg);
    --dxbl-fl-group-bg: var(--bz-color-surface);
    --dxbl-fl-group-border-color: var(--bz-color-border);
    --dxbl-fl-group-header-bg: var(--bz-color-surface-2);
    --dxbl-fl-group-header-color: var(--bz-color-fg);
    --dxbl-fl-group-header-btn-bg: transparent;
    --dxbl-fl-group-header-btn-color: var(--bz-color-fg-muted);
    --dxbl-fl-group-header-btn-hover-bg: var(--bz-color-surface-3);
    --dxbl-fl-group-header-btn-hover-color: var(--bz-color-fg);
}

/* Tabs use the same type, selection tint and focus affordance as the surrounding application.
   Keep DevExpress navigation, overflow buttons and keyboard semantics. */
.dxbl-tabs.dxbl-tabs {
    --dxbl-tabs-tab-font-family: var(--bz-font-sans);
    --dxbl-tabs-tab-font-size: var(--bz-fs-md);
    --dxbl-tabs-tab-font-weight: var(--bz-fw-medium);
    --dxbl-tabs-tab-color: var(--bz-color-fg);
    --dxbl-tabs-tab-bg: transparent;
    --dxbl-tabs-tab-hover-color: var(--bz-color-fg);
    --dxbl-tabs-tab-hover-bg: var(--bz-color-surface-2);
    --dxbl-tabs-tab-active-color: var(--bz-color-accent-text);
    --dxbl-tabs-tab-active-bg: color-mix(in srgb, var(--bz-color-accent) 10%, var(--bz-color-surface));
    --dxbl-tabs-tab-active-font-weight: var(--bz-fw-semibold);
    --dxbl-tabs-tab-active-hover-bg: color-mix(in srgb, var(--bz-color-accent) 16%, var(--bz-color-surface));
    --dxbl-tabs-tab-active-hover-color: var(--bz-color-accent-text);
    --dxbl-tabs-tab-icon-color: var(--bz-color-fg-muted);
    --dxbl-tabs-tab-icon-hover-color: var(--bz-color-fg);
    --dxbl-tabs-tab-active-icon-color: var(--bz-color-accent-text);
    --dxbl-tabs-tab-active-icon-hover-color: var(--bz-color-accent-text);
    --dxbl-tabs-tab-focus-outline-color: var(--bz-brand-primary);
    --dxbl-tabs-tab-focus-outline-size: 2px;
    --dxbl-tabs-tab-focus-outline-offset: 2px;
}
@media (pointer: coarse) {
    .dxbl-tabs.dxbl-tabs { --dxbl-tabs-tab-padding-y: var(--bz-space-3); }
    .bz-filter-bar > label:has(input[type="checkbox"]) { min-height: 44px; display: inline-flex; align-items: center; }
    /* Older link actions share the same target floor as DevExpress and Metronic buttons. */
    :is(.btn-primary, .btn-secondary, .btn-danger, .btn-link) { min-height: 44px; }
    :is(button, a).btn-icon-only { width: 44px; min-width: 44px; padding-inline: 0; }
}
:is(.btn-primary, .btn-secondary, .btn-danger, .btn-link):is(:disabled, [aria-disabled="true"]) {
    opacity: .55;
    cursor: not-allowed;
}
