@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 300 700;
    font-display: swap;
    src: url('../fonts/inter-cyrillic-ext.woff2') format('woff2');
    unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 300 700;
    font-display: swap;
    src: url('../fonts/inter-cyrillic.woff2') format('woff2');
    unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 300 700;
    font-display: swap;
    src: url('../fonts/inter-greek-ext.woff2') format('woff2');
    unicode-range: U+1F00-1FFF;
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 300 700;
    font-display: swap;
    src: url('../fonts/inter-greek.woff2') format('woff2');
    unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 300 700;
    font-display: swap;
    src: url('../fonts/inter-vietnamese.woff2') format('woff2');
    unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 300 700;
    font-display: swap;
    src: url('../fonts/inter-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 300 700;
    font-display: swap;
    src: url('../fonts/inter-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Material Symbols Outlined';
    font-style: normal;
    font-weight: 400;
    font-display: block;
    src: url('../fonts/material-symbols-outlined.woff2') format('woff2');
}

/* Material Symbols, replacing Font Awesome app-wide in the nav tree and ribbon (icon-family
   review this session: WinForms bitmaps vs Telerik SVG vs Font Awesome vs Material -- Material
   won on licensing, Apache 2.0 vs Font Awesome Free's CC BY attribution question). A ligature
   name goes as the element's text content, e.g. <span class="msym">person</span>. */
.msym {
    font-family: 'Material Symbols Outlined';
    font-weight: normal;
    font-style: normal;
    font-size: 18px;
    line-height: 1;
    letter-spacing: normal;
    text-transform: none;
    display: inline-block;
    white-space: nowrap;
    word-wrap: normal;
    direction: ltr;
    -webkit-font-feature-settings: 'liga';
    -webkit-font-smoothing: antialiased;
    flex-shrink: 0;
}

/* Custom brand colour swatch, kept separate from the Kendo theme picker (ThemeSwitcher.razor):
   these --kendo-color-* tokens only apply for the "default" Kendo theme (or before JS sets
   data-kendo-theme at all, so first paint isn't unstyled). Picking Bootstrap/Material/Fluent/
   Meridian instead leaves that theme's own colour tokens alone -- only the density/typography
   block further down (which is theme-agnostic) still applies on top of it. */
:root:not([data-kendo-theme]), :root[data-kendo-theme="default"] {
    /* Color System Swatches */
    /* Misc */
    --kendo-color-app-surface: #000000;
    --kendo-color-surface: #292929;
    --kendo-color-surface-alt: #1F1F1F;
    --kendo-color-on-app-surface: #EBEBEB;
    --kendo-color-subtle: #A3A3A3;
    --kendo-color-border: rgba(255,255,255, .16);
    --kendo-color-border-alt: rgba(255,255,255, .32);
    /*  Base */
    --kendo-color-base-subtle: #3D3D3D;
    --kendo-color-base-subtle-hover: #525252;
    --kendo-color-base-subtle-active: #666666;
    --kendo-color-base: #525252;
    --kendo-color-base-hover: #666666;
    --kendo-color-base-active: #6A6A6A;
    --kendo-color-base-emphasis: #7A7A7A;
    --kendo-color-on-base: #EBEBEB;
    --kendo-color-base-on-surface: #EBEBEB;
    --kendo-color-base-on-subtle: #EBEBEB;
    --kendo-color-primary-subtle: rgba(248, 148, 29, 0.18);
    --kendo-color-primary-subtle-hover: rgba(248, 148, 29, 0.28);
    --kendo-color-primary-subtle-active: rgba(248, 148, 29, 0.38);
    --kendo-color-primary: #F8941D;
    --kendo-color-primary-hover: #E0851A;
    --kendo-color-primary-active: #C87010;
    --kendo-color-primary-emphasis: #F29C97;
    --kendo-color-primary-on-subtle: #FDEFEE;
    /* Foreground paired with --kendo-color-primary (#F8941D). White gives only 2.14:1 here
       and fails WCAG AA (4.5:1); this dark neutral gives 7.71:1. Same treatment as
       --kendo-color-on-warning, since the brand primary is a light orange. */
    --kendo-color-on-primary: #1F1F1F;
    --kendo-color-primary-on-surface: #ED736B;
    /* Secondary */
    --kendo-color-secondary-subtle: #525252;
    --kendo-color-secondary-subtle-hover: #666666;
    --kendo-color-secondary-subtle-active: #7A7A7A;
    --kendo-color-secondary: #7A7A7A;
    --kendo-color-secondary-hover: #8F8F8F;
    --kendo-color-secondary-active: #A3A3A3;
    --kendo-color-secondary-emphasis: #333333;
    /* #1A1A1A on secondary's #7A7A7A was 4.05:1, just under WCAG AA's 4.5:1 (k-button-text on
       Silence/Refresh/Clear/Copy to Clipboard etc). #0D0D0D clears it at 4.53:1 -- same
       treatment as --kendo-color-on-primary above. */
    --kendo-color-on-secondary: #0D0D0D;
    --kendo-color-secondary-on-surface: #8F8F8F;
    --kendo-color-secondary-on-subtle: #FFFFFF;
    --kendo-color-tertiary-subtle: #023F5C;
    --kendo-color-tertiary-subtle-hover: #02557A;
    --kendo-color-tertiary-subtle-active: #026999;
    --kendo-color-tertiary: #03A9F4;
    --kendo-color-tertiary-hover: #039AE0;
    --kendo-color-tertiary-active: #028CCB;
    --kendo-color-tertiary-emphasis: #61C9F9;
    --kendo-color-tertiary-on-subtle: #EBF8FE;
    --kendo-color-on-tertiary: white;
    --kendo-color-tertiary-on-surface: #03A9F4;
    --kendo-color-info-subtle: #002C75;
    --kendo-color-info-subtle-hover: #003892;
    --kendo-color-info-subtle-active: #0042AF;
    --kendo-color-info: #0058E9;
    --kendo-color-info-hover: #0052D6;
    --kendo-color-info-active: #0044C2;
    --kendo-color-info-emphasis: #0052D6;
    --kendo-color-info-on-subtle: #E9F0FD;
    --kendo-color-on-info: white;
    --kendo-color-info-on-surface: #4082EF;
    --kendo-color-success-subtle: #1C5A00;
    --kendo-color-success-subtle-hover: #227100;
    --kendo-color-success-subtle-active: #298700;
    --kendo-color-success: #37B400;
    --kendo-color-success-hover: #32A500;
    --kendo-color-success-active: #2D9600;
    --kendo-color-success-emphasis: #32A500;
    --kendo-color-success-on-subtle: #EDF8E9;
    --kendo-color-on-success: white;
    --kendo-color-success-on-surface: #32A500;
    --kendo-color-warning-subtle: #806000;
    --kendo-color-warning-subtle-hover: #A07800;
    --kendo-color-warning-subtle-active: #BF9000;
    --kendo-color-warning: #FFC000;
    --kendo-color-warning-hover: #EAAF00;
    --kendo-color-warning-active: #D49F00;
    --kendo-color-warning-emphasis: #FFC720;
    --kendo-color-warning-on-subtle: #FFFAE9;
    --kendo-color-on-warning: #3D3D3D;
    --kendo-color-warning-on-surface: #EAAF00;
    --kendo-color-error-subtle: #980F00;
    --kendo-color-error-subtle-hover: #B61100;
    --kendo-color-error-subtle-active: #CA1400;
    --kendo-color-error: #F43520;
    --kendo-color-error-hover: #F31700;
    --kendo-color-error-active: #DF1600;
    --kendo-color-error-emphasis: #F43520;
    --kendo-color-error-on-subtle: #FEEEED;
    --kendo-color-on-error: white;
    --kendo-color-error-on-surface: #F65140;
    --kendo-color-light-subtle: #FAFAFA;
    --kendo-color-light-subtle-hover: #F5F5F5;
    --kendo-color-light-subtle-active: #EBEBEB;
    --kendo-color-light: #EBEBEB;
    --kendo-color-light-hover: #E0E0E0;
    --kendo-color-light-active: #D6D6D6;
    --kendo-color-light-emphasis: #D6D6D6;
    --kendo-color-light-on-subtle: #141414;
    --kendo-color-on-light: black;
    --kendo-color-light-on-surface: #E0E0E0;
    --kendo-color-dark-subtle: #C2C2C2;
    --kendo-color-dark-subtle-hover: #ADADAD;
    --kendo-color-dark-subtle-active: #999999;
    --kendo-color-dark: #3D3D3D;
    --kendo-color-dark-hover: #292929;
    --kendo-color-dark-active: #1F1F1F;
    --kendo-color-dark-emphasis: #666666;
    --kendo-color-dark-on-subtle: #1F1F1F;
    --kendo-color-on-dark: white;
    --kendo-color-dark-on-surface: #141414;
    --kendo-color-inverse-subtle: #c2c2c2;
    --kendo-color-inverse-subtle-hover: #9c9c9c;
    --kendo-color-inverse-subtle-active: #757575;
    --kendo-color-inverse: #EBEBEB;
    --kendo-color-inverse-hover: #E0E0E0;
    --kendo-color-inverse-active: #D6D6D6;
    --kendo-color-inverse-emphasis: #575757;
    --kendo-color-inverse-on-subtle: #141414;
    --kendo-color-on-inverse: black;
    --kendo-color-inverse-on-surface: #E0E0E0;
    --kendo-color-series-a: #EA5A51;
    --kendo-color-series-a-bold: #F08C85;
    --kendo-color-series-a-bolder: #F5ADA8;
    --kendo-color-series-a-subtle: #BB4841;
    --kendo-color-series-a-subtler: #8C3631;
    --kendo-color-series-b: #FFE162;
    --kendo-color-series-b-bold: #FFED9D;
    --kendo-color-series-b-bolder: #FFF7D4;
    --kendo-color-series-b-subtle: #D4BC52;
    --kendo-color-series-b-subtler: #A3913F;
    --kendo-color-series-c: #4CD180;
    --kendo-color-series-c-bold: #79DDA0;
    --kendo-color-series-c-bolder: #A6E8C0;
    --kendo-color-series-c-subtle: #3FAE6A;
    --kendo-color-series-c-subtler: #2F834F;
    --kendo-color-series-d: #4B5FFA;
    --kendo-color-series-d-bold: #7887FB;
    --kendo-color-series-d-bolder: #A5AFFD;
    --kendo-color-series-d-subtle: #3F50D1;
    --kendo-color-series-d-subtler: #2F3C9D;
    --kendo-color-series-e: #AC58FF;
    --kendo-color-series-e-bold: #C182FF;
    --kendo-color-series-e-bolder: #D6ACFF;
    --kendo-color-series-e-subtle: #8F49D4;
    --kendo-color-series-e-subtler: #6B37A0;
    --kendo-color-series-f: #EA5186;
    --kendo-color-series-f-bold: #FF82AE;
    --kendo-color-series-f-bolder: #FFACC9;
    --kendo-color-series-f-subtle: #BF426E;
    --kendo-color-series-f-subtler: #802C49;
}

/* Density/typography overrides -- these apply on top of WHICHEVER Kendo theme is active
   (default or otherwise), unlike the colour swatch above. See the ORDER-SENSITIVE note below:
   this only works because style.css loads after the Kendo theme link. */
:root {
    /* Typography */
    --kendo-font-family: 'Inter', sans-serif;
    --kendo-font-size-xs: 10px;
    --kendo-font-size-sm: 12px;
    /* BACK TO 14px. Raised to 16 earlier, reverted because the goal is a MORE COMPACT UI, and a
       larger base works against that -- it also tightens every fixed RowHeight and container the
       app declares in px.
       The conversions that came with that change are kept: the literal font sizes that were
       replaced with var(--kendo-font-size) now follow this token instead of restating a number,
       which is what makes a future density change one edit rather than a hundred. */
    --kendo-font-size: 14px;
    --kendo-font-size-md: var(--kendo-font-size, inherit);
    --kendo-font-size-lg: 17px;
    --kendo-font-size-xl: 20px;
    --kendo-line-height-xs: 1;
    --kendo-line-height-sm: 1.25;
    --kendo-line-height-md: 1.4;
    --kendo-line-height-lg: 1.5;
    --kendo-h1-font-size: 56px;
    --kendo-h2-font-size: 42px;
    --kendo-h3-font-size: 35px;
    --kendo-h4-font-size: 28px;
    --kendo-h5-font-size: 21px;
    --kendo-h6-font-size: 14px;
    --kendo-h1-line-height: calc((var(--kendo-font-size) * 4) * 1.25);
    --kendo-h2-line-height: calc((var(--kendo-font-size) * 3) * 1.2);
    --kendo-h3-line-height: calc((var(--kendo-font-size) * 2.5) * 1.2);
    --kendo-h4-line-height: calc((var(--kendo-font-size) * 2) * 1.2);
    --kendo-h5-line-height: calc((var(--kendo-font-size) * 1.5) * 1.25);
    --kendo-h6-line-height: calc(var(--kendo-font-size) * 1.25);
    --kendo-h1-font-weight: 400;
    --kendo-h2-font-weight: 400;
    --kendo-h3-font-weight: 400;
    --kendo-h4-font-weight: 700;
    --kendo-h5-font-weight: 700;
    --kendo-h6-font-weight: 700;
    /* Border radius */
    --kendo-border-radius-xs: 1px;
    --kendo-border-radius-sm: 2px;
    --kendo-border-radius-md: 6px;
    --kendo-border-radius-lg: 12px;
    --kendo-border-radius-xl: 24px;
    --kendo-border-radius-xxl: 32px;
    --kendo-border-radius-xxxl: 64px;
    --kendo-border-radius-full: 9999px;
    /* ------------------------------------------------------------------
       COMPACT DENSITY. Every Kendo component sizes its internal padding from
       --kendo-spacing-N; the theme ships a 0.25rem-per-step scale and nothing here
       overrode it, which is why density was previously decided in ~130 literal px
       values scattered across the scoped stylesheets instead of in one place.

       Halving steps 1-4 retunes grids, the nav drawer, inputs, buttons, toolbars and
       tabs at once. Steps 5+ are deliberately LEFT AT THEIR DEFAULTS: those carry
       page-level rhythm rather than component internals, and squeezing them removes
       the separation that makes groups legible rather than removing waste.

       ORDER-SENSITIVE: this only applies because style.css is linked AFTER the Kendo
       theme. The theme defines these same tokens at the same :root specificity, so a
       tie is broken by source order. See the note in BlazorHybrid.Web App.razor.

       Token names/defaults: docs/reference/kendo-theme-tokens.reference.css */
    --kendo-spacing-1: 0.125rem; /* 4px -> 2px */
    --kendo-spacing-2: 0.25rem;  /* 8px -> 4px */
    --kendo-spacing-3: 0.5rem;   /* 12px -> 8px */
    --kendo-spacing-4: 0.75rem;  /* 16px -> 12px */
    /* Spacing (4dp base rhythm) */
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 20px;
    --space-6: 24px;
    --space-8: 32px;
    --space-10: 40px;
    --space-12: 48px;
}

/* Light variant of the custom swatch above -- same "default theme only" scoping. */
:root:not([data-kendo-theme])[data-theme="light"],
:root[data-kendo-theme="default"][data-theme="light"] {
    --kendo-color-app-surface: #FFFFFF;
    --kendo-color-surface: #F5F5F5;
    --kendo-color-surface-alt: #FAFAFA;
    --kendo-color-on-app-surface: #1F1F1F;
    --kendo-color-subtle: #666666;
    --kendo-color-border: rgba(0,0,0, .12);
    --kendo-color-border-alt: rgba(0,0,0, .24);
    --kendo-color-base-subtle: #E0E0E0;
    --kendo-color-base-subtle-hover: #D6D6D6;
    --kendo-color-base-subtle-active: #C2C2C2;
    --kendo-color-base: #D6D6D6;
    --kendo-color-base-hover: #C2C2C2;
    --kendo-color-base-active: #ADADAD;
    --kendo-color-base-emphasis: #999999;
    --kendo-color-on-base: #1F1F1F;
    --kendo-color-base-on-surface: #1F1F1F;
    --kendo-color-base-on-subtle: #1F1F1F;
    /* --kendo-color-primary is theme-independent (#F8941D), so the light theme needs the
       same dark foreground as :root -- white would be 2.14:1 here too. */
    --kendo-color-on-primary: #1F1F1F;
    /* White on the dark swatch's #7A7A7A is 4.29:1; #737373 clears WCAG AA at 4.74:1. */
    --kendo-color-secondary: #737373;
    --kendo-color-on-secondary: white;
    --kendo-color-on-tertiary: white;
    --kendo-color-on-info: white;
    --kendo-color-on-success: white;
    --kendo-color-on-warning: #1F1F1F;
    --kendo-color-on-error: white;
    --kendo-color-on-light: #1F1F1F;
    --kendo-color-on-dark: white;
    /* on-inverse is white here, so inverse itself must be dark: the bare :root value is the
       dark theme's light grey, which left every inverse surface (k-tooltip) at 1.19:1. */
    --kendo-color-inverse: #424242;
    --kendo-color-inverse-hover: #4D4D4D;
    --kendo-color-inverse-active: #575757;
    --kendo-color-on-inverse: white;
}

/* Focus indicator — meets WCAG 2.4.7 Focus Visible (AA) */
:focus-visible {
    outline: 3px solid var(--kendo-color-primary, #F8941D);
    outline-offset: 2px;
    border-radius: 2px;
}

/* Suppress outline on mouse/touch; keep it for keyboard navigation */
:focus:not(:focus-visible) {
    outline: none;
}

/* Screen-reader-only utility — visually hidden but announced by AT */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* The [data-theme="light"] overrides for .page-container, .page-content and
   .table-container that used to live here were a workaround for page
   stylesheets that hardcoded dark hex values. Those now use the semantic
   tokens, so the light overrides were redundant — verified by deleting them
   at runtime and re-measuring: .page-container and .page-content resolve to
   the identical colour via --kendo-color-app-surface, and .table-container
   picks up its intended surface tint and token border instead of being
   flattened to white with no border.

   .page-header keeps a rule because ~55 pages use the class without any
   scoped stylesheet of their own, so this was their only source of a header
   tint — and it applied in the light theme only, which is the very asymmetry
   this pass set out to remove. Restated with tokens it now behaves the same
   way in both themes. Specificity stays at a single class so per-page scoped
   rules (Cardholders, Cards, MapView) continue to win. */
.page-header {
    background-color: var(--kendo-color-surface, #252526);
}

.theme-switcher-controls {
    display: flex;
    align-items: center;
    gap: 6px;
}

/* Sized to fit the LONGEST theme name plus its accessibility icon ("Ocean Blue A11Y") -- not
   `width: fit-content`, which shrinks to whatever's currently SELECTED (e.g. "Fluent"): Telerik's
   popup list mirrors the closed control's own width, so a selection-dependent width means picking
   a short theme narrows the popup too, wrapping every longer label inside it onto two lines. A
   fixed width wide enough for the longest label keeps the popup readable no matter what's
   currently selected. */
.theme-dropdown.k-dropdownlist {
    width: 190px;
}

/* Keeps the Ocean Blue A11Y popup item's accessibility icon on the SAME line as its label --
   TelerikDropDownList's ItemTemplate has no default layout of its own, so without this the icon
   span wraps under the text instead of sitting beside it. */
.theme-dropdown-item {
    display: flex;
    align-items: center;
    gap: 6px;
}

.theme-mode-toggle .k-button {
    padding: 2px 6px;
}

/* UI SCALE REMOVED. It set CSS `zoom` on the app root, which fights the definite-height chains
   the shell, the popout window and every virtualized grid depend on -- so scaling the UI broke
   the layout it was scaling.

   Note for anyone reinstating it: root font-size scaling is NOT the alternative. This app is
   px-based (~130 literal font sizes, fixed grid RowHeights, fixed container heights), so it
   would move rem values only and leave the UI half-scaled. Making the UI resizable really means
   making it not px-based first. */

.main-layout {
	display: flex;
	flex-direction: column;
	height: 100vh;
	width: 100%;
	overflow: hidden;
}

.header-component {
	flex-shrink: 0;
	width: 100%;
	background-color: var(--kendo-color-surface);
	border-bottom: 1px solid var(--kendo-color-border);
}

.content-wrapper {
	display: flex;
	flex: 1;
	overflow: hidden;
}

.navbar-container {
	flex-shrink: 0;
/*	width: 250px;*/
	background-color: var(--kendo-color-surface-alt);
	border-right: 1px solid var(--kendo-color-border);
	overflow-y: auto;
	overflow-x: hidden;
	display: flex;
	flex-direction: column;
}

.navbar-container > * {
	width: 100%;
	height: 100%;
}

.body-container {
	flex: 1;
	overflow-y: auto;
	overflow-x: hidden;
	padding: 16px;
	background-color: var(--kendo-color-app-surface);
}

/* TelerikGrid rounded corners */
.k-grid {
    border-radius: 8px;
    overflow: hidden;
}

.k-grid .k-grid-header {
    border-top-left-radius: 8px;
    border-top-right-radius: 8px;
}

.k-grid .k-grid-content {
    border-bottom-left-radius: 8px;
    border-bottom-right-radius: 8px;
}

/* WinForms' RadGridView cells are not text-selectable (selection there means row/cell
   selection, not a text drag) -- Telerik Blazor's grid defaults to normal browser text
   selection on cell content, so a click-drag across cells highlights text instead of
   extending the row selection. Applied globally so every grid matches, not per-screen. */
.k-grid .k-table-td,
.k-grid .k-grid-header th {
    user-select: none;
}

/* Telerik's column resizer is a thin strip mostly covered by the header's menu button: widen and
   raise it, and push the button clear. The selectors must out-rank Telerik's
   ".k-grid .k-cell-inner .k-grid-header-menu { margin-inline-end }". */
.k-grid .k-grid-header .k-column-resizer {
    width: 10px;
    z-index: 2;
}

.k-grid .k-grid-header .k-cell-inner > .k-grid-header-menu,
.k-grid .k-grid-header .k-cell-inner > .k-grid-filter {
    margin-inline-end: 12px;
}

/* TelerikContextMenu styling */
/*.k-context-menu {
    border-radius: 8px;
    overflow: hidden;
}*/

.k-context-menu .k-item {
    transition: background-color 0.2s ease;
}

/* AIU-698 made popups auto-size to their item count so short lists don't reserve a fixed
   empty-row height. Unbounded height="auto" also removed the scroll cap on long lists (e.g.
   the Timezone dropdown), so this now shrinks short lists but still caps and scrolls long ones. */
.k-list-container {
    height: auto !important;
    max-height: 300px !important;
}

/*.k-popup {
    border-style: none;
}

.k-menu-popup {
    border-style: none;
}*/

.k-context-menu .k-item:hover {
    background-color: var(--kendo-color-primary);
    color: var(--kendo-color-on-primary);
}

.k-context-menu .k-item:hover .k-icon {
    color: var(--kendo-color-on-primary);
}

.k-pager-numbers .k-selected {
    border-radius: 8px;
    width: 30px; /* Adjust size as needed */
    height: 30px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
}

/* Responsive adjustments for smaller screens */
@media (max-width: 768px) {
	.navbar-container {
		position: fixed;
		left: 0;
		top: var(--header-height, 60px);
		height: calc(100vh - var(--header-height, 60px));
		z-index: 999;
		transform: translateX(-100%);
		transition: transform 0.3s ease-in-out;
	}

	.navbar-container.nav-open {
		transform: translateX(0);
	}

	.body-container {
		width: 100%;
	}
}

/* Device status row/cell classes, shared by every Hardware grid (SCPs, SIOs, Readers, Inputs,
   Outputs) that ports the rich client's grdMain_CellFormatting colouring (e.g. frmSCPs.vb:165-193).

   Project-owner revision: row SELECTION still highlights the whole row (Telerik's default
   k-selected on <tr>, untouched), but STATUS colouring is PER CELL on all five of these grids, not
   per row, so a selected row's status stays visible instead of being masked by a row-wide
   background. Each grid's own status column(s) assign inAlarmStatus/unknownStatus/empty via
   OnCellRender (SCPModelHelper.GetCommCellClass/GetStatusCellClass, SIOModelHelper.GetCommCellClass/
   GetTamperCellClass/GetPowerCellClass, ReaderModel.DoorStatusCellClass, InputModel/
   OutputModel.StatusCellClass -- all funnelling through GridStatusStyling for the final
   state-&gt;class mapping). notInstalledStatus is the one exception and stays row-wide via
   OnRowRender + *Model.RowCssClass on every one of the five grids: it is a property of the device,
   not of one status column, and legacy never colours a not-installed row's background either way
   (frmSCPs.vb:167-168), only its text.

   The bare ".inAlarmStatus"/".unknownStatus" selectors below already match a coloured <td> directly,
   same as they match a coloured <tr> -- no extra rule needed for that part. Selection is the one
   thing that DOES need an extra rule: Telerik puts "k-selected" on the <tr>, never on a <td>, so
   ".inAlarmStatus.k-selected" (written for the row case) cannot match a cell-level class -- see the
   "tr.k-selected >" rules below, added beside the row rules rather than forking them.

   inAlarmStatus reuses the app's existing --kendo-color-error-* semantic tokens (defined in :root
   above and NOT overridden by [data-theme="light"], so it already resolves identically -- and
   legibly -- in both themes) rather than inventing a parallel palette.

   unknownStatus is the one exception: the --kendo-color-warning-subtle/-on-subtle pair is a dark
   olive background with pale cream text (correct for other UI surfaces like toasts/badges, but
   unreadable as a wide table-cell fill, and not the plain "yellow row, black text" look legacy
   uses for this state -- frmSCPs.vb:176-182, Color.Yellow). It uses the plain
   --kendo-color-warning/--kendo-color-on-warning pair instead (already the app's shared
   solid-yellow-surface token, e.g. Alarms.razor.css:1816) -- still a shared token, just a
   different existing one than the other three statuses use, not a literal colour.

   !important is required on the alarm/warning rules: they have to beat the Telerik theme's own
   alternating-row, hover and selection backgrounds, which is exactly what these are replacing. The
   ">.k-table-td" rule is not redundant with the bare selector -- the theme colours cell text
   directly, so a row-level colour never reaches it on its own (same reasoning as Alarms.razor.css). */
.inAlarmStatus,
.inAlarmStatus:hover {
    background-color: var(--kendo-color-error-subtle) !important;
    color: var(--kendo-color-error-on-subtle) !important;
}

.inAlarmStatus > .k-table-td {
    color: var(--kendo-color-error-on-subtle) !important;
}

/* The rich client dims (not replaces) a coloured row's background while it is selected/current
   (RGBHSL.ModifyBrightness(colour, 0.775), e.g. frmSCPs.vb:189) so selection stays visible without
   losing the status cue. The "-active" step of each token ladder is reused as that dimmed shade. */
.inAlarmStatus.k-selected,
.inAlarmStatus.k-selected:hover {
    background-color: var(--kendo-color-error-subtle-active) !important;
}

/* Cell-level equivalent of the rule above, for every hardware grid's per-column status cells (see
   the block comment at the top of this section) -- "k-selected" lands on the row, not the cell. */
tr.k-selected > td.inAlarmStatus,
tr.k-selected:hover > td.inAlarmStatus {
    background-color: var(--kendo-color-error-subtle-active) !important;
}

.unknownStatus,
.unknownStatus:hover {
    background-color: var(--kendo-color-warning) !important;
    color: var(--kendo-color-on-warning) !important;
}

.unknownStatus > .k-table-td {
    color: var(--kendo-color-on-warning) !important;
}

.unknownStatus.k-selected,
.unknownStatus.k-selected:hover {
    background-color: var(--kendo-color-warning-active) !important;
}

/* Cell-level equivalent of the rule above, for every hardware grid's per-column status cells. */
tr.k-selected > td.unknownStatus,
tr.k-selected:hover > td.unknownStatus {
    background-color: var(--kendo-color-warning-active) !important;
}

/* Not-installed only greys the text (legacy leaves the row background alone -- frmSCPs.vb:167-168)
   so no background/selection rule is needed here, unlike the two states above. */
.notInstalledStatus,
.notInstalledStatus > .k-table-td {
    color: var(--kendo-color-subtle) !important;
}

/* ── Form layout helpers ──────────────────────────────────────── */

/* Toolbar row — Save/Cancel buttons, grid toolbars, dialog footers */
.toolbar {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 8px;
    padding: 8px 0;
}

/* Every GridToolBar search box, app-wide. The class lands on Telerik's wrapper, not the
   <input> that carries the text. */
.toolbar-search-right input.k-input-inner {
    text-align: right;
}

/* Grid page toolbar (search + filter buttons) */
.toolbar-wrapper {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 10px;
    /* GridToolBar is a SINGLE row on every screen that hosts it, so wrapping is off here and not
       per-page. A wrapping flex container shrinks nothing -- it moves items to the next line --
       so any long item (a results count in LeftContent) took its full width and pushed the
       search box and the buttons onto lines of their own. */
    flex-wrap: nowrap;
    padding: 4px 0;
    /* Without an explicit width the toolbar only spans its own content, so a right-group's
       margin-inline-start:auto push (e.g. CardHolders.razor's .spike-toolbar-right-group)
       has no room to move into and the group sits flush against the count on the left
       instead of the grid's right edge. */
    width: 100%;
}

/* GridToolBar's own right-justified cluster (Clear Filters, search box, LeftContent,
   RightContent, Print, chooser). One wrapper carrying the auto-margin push rather than
   marking whichever optional child happens to render first, so the group holds together
   regardless of which of its members a given screen wires up. */
.toolbar-right-group {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: nowrap;
    margin-inline-start: auto;
}

/* Only a truncating results count gives ground when the row runs out of width; the search box
   and the buttons keep their size. */
.toolbar-wrapper > :not(.spike-results-count) {
    flex-shrink: 0;
}

/* ── Global form layout ───────────────────────────────────────────
   These classes are used by ~40 editor pages that previously had NO
   matching CSS, so labels, fields and buttons rendered with zero
   spacing. They live here (not in a .razor.css) for two reasons:
     1. they are shared across many components;
     2. classes handed to a Telerik component via its Class parameter
        are rendered inside that component, so Blazor CSS isolation
        never stamps them and a scoped rule can never match.
   The .channel-editor-container rules further down are more specific
   and keep overriding these for the label-on-the-left editors.       */

/* ── THE EDITOR CHROME CONTRACT ───────────────────────────────────────────────
   One rule set for every editor shell: the tab STRIP and the action bar stay put,
   only the tab BODY scrolls, and the tab panel has a DEFINITE height so a child
   that asks to fill it (the shared NotesTab textarea) actually can.

   Every link of the chain needs `min-height: 0`. A flex child's default
   `min-height: auto` refuses to shrink below its content, so without it the whole
   modal scrolls instead of the body, and the panel has no height to hand down.

   This was previously written out four times -- once scoped to
   `.channel-editor-container` (which is why only that editor's Notes tab filled)
   and once for the other three containers. Same reasoning as the grid height
   contract below: a shared rule set, not per-page handrolling.

   `.editor-window-container` is the shared EditorWindow's own container. The five
   per-page containers below it are the editors that have not been converted yet;
   the direction of travel is ONE class here, not six. */
.editor-window-container,
.edit-form-container,
.server-editor-container,
.site-editor-container,
.macro-editor-container,
.channel-editor-container {
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow: auto;
}

.editor-window-container > .toolbar,
.edit-form-container > .toolbar,
.server-editor-container > .toolbar,
.site-editor-container > .toolbar {
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--kendo-color-border);
    background-color: var(--kendo-color-surface);
    flex-shrink: 0;
}

.editor-window-container .k-tabstrip,
.edit-form-container .k-tabstrip,
.server-editor-container .k-tabstrip,
.site-editor-container .k-tabstrip,
.macro-editor-container .k-tabstrip,
.channel-editor-container .k-tabstrip {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    overflow: auto;
}

/* The pinned tab strip itself */
.editor-window-container .k-tabstrip-items-wrapper,
.edit-form-container .k-tabstrip-items-wrapper,
.server-editor-container .k-tabstrip-items-wrapper,
.site-editor-container .k-tabstrip-items-wrapper,
.macro-editor-container .k-tabstrip-items-wrapper,
.channel-editor-container .k-tabstrip-items-wrapper {
    flex-shrink: 0;
}

.editor-window-container .k-tabstrip-content,
.edit-form-container .k-tabstrip-content,
.server-editor-container .k-tabstrip-content,
.site-editor-container .k-tabstrip-content,
.macro-editor-container .k-tabstrip-content,
.channel-editor-container .k-tabstrip-content,
.editor-window-container .k-tabstrip-content > .k-tabstrip-panel,
.edit-form-container .k-tabstrip-content > .k-tabstrip-panel,
.server-editor-container .k-tabstrip-content > .k-tabstrip-panel,
.site-editor-container .k-tabstrip-content > .k-tabstrip-panel,
.macro-editor-container .k-tabstrip-content > .k-tabstrip-panel,
.channel-editor-container .k-tabstrip-content > .k-tabstrip-panel {
    flex: 1;
    min-height: 0;
    overflow: auto;
    display: flex;
    flex-direction: column;
}

/* THE SHARED EDIT MODAL (EditorWindow).

   Sized to content, never more than 80% of the viewport in either direction. Expressed
   in vw/vh so the browser clamps it during layout: no JS measurement, which is what
   broke the definite-height chains when the removed UI-scale control tried it.

   min-height:0 on .k-window-content is the load-bearing line. The window content box is
   a flex child; its default min-height:auto refuses to shrink below the form's natural
   height, so without it the WHOLE modal scrolls instead of just the active tab body, and
   the tab panel has no definite height to hand down to the NotesTab textarea. */
/* NO width/height/max-* ON THE EDITOR WINDOW. It used to carry max-width:80vw / max-height:80vh,
   which pinned it: Telerik resizes a window by writing an INLINE width/height, and a stylesheet
   rule beats an inline style, so the resize handles moved nothing once the cap was reached -- and
   with the window sizing to content it was already at the cap. The 80% intent moved into
   EditorWindow's Width/Height parameter defaults, which Telerik owns and the operator can drag
   past. Do not reintroduce a size constraint here. */

/* THE 880px READABILITY CAP DOES NOT APPLY INSIDE AN EDITOR WINDOW.

   .form-content carries `max-width: 880px` as a LINE-LENGTH cap, which is right for a full-page
   single-column form and wrong here: the window is now explicitly sized and resizable, so an 880px
   form inside a 1100px window would just leave dead space on the right, and dragging the window
   wider would widen nothing.

   An editor is a dense multi-field dialog, not prose; it takes the width it is given. */
.editor-window .form-content,
.editor-window .form-container {
    max-width: none;
}

/* Programmatic bring-to-front for the non-modal editor stack (OpenEditorWindows). Telerik raises
   whichever window the operator clicks, but re-opening a record that is already open has to raise
   its window from code, and the window component exposes no API for it. The bump is small on
   purpose -- it stays above the sibling editors without clearing a modal dialog opened later. */
.k-window.editor-window-focused {
    z-index: 10010;
}

/* Matches GridPreviewPane's own background (--kendo-color-surface) rather than falling through to
   Telerik's default stark-white .k-window surface (--kendo-color-app-surface) -- EditorWindow is
   the SAME shared component both an Edit modal and a list screen's inline preview pane render
   through, so the two used to look like two different colour schemes for identical content. One
   rule here covers every Edit dialog in the app, since they all go through this one component.

   --kendo-color-border-alt (a darker/more opaque border than the theme's default --kendo-color-
   border every other panel uses) plus a small shadow give the window a visible edge against the
   page behind it -- the default border read as barely-there once the body background above
   stopped being stark white, and the window started blending into the shell instead of reading
   as a distinct floating dialog. */
/* Rounded to match the grid's own 8px (.k-grid below) rather than the theme's square default --
   NOT paired with overflow:hidden on this outer element, unlike .k-grid: Telerik's resize grips
   for a Resizable window are positioned at/past this element's own edges, and clipping here risked
   clipping them along with the square corners. Rounding the titlebar's top corners and the
   window-content's bottom corners individually (below) gets the same rounded look without an
   overflow:hidden on the piece that owns the resize interaction. */
.k-window.editor-window {
    background-color: var(--kendo-color-surface);
    border: 1px solid var(--kendo-color-border-alt);
    border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
}

.k-window.editor-window > .k-window-content {
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
    padding: 0;
    background-color: var(--kendo-color-surface);
    border-bottom-left-radius: 7px;
    border-bottom-right-radius: 7px;
}

/* The title bar sits a shade darker than the window body (--kendo-color-base-subtle vs.
   --kendo-color-surface above) so the dialog reads as chrome + content rather than one flat
   surface -- the same reason the body background needed to stop matching stark white: without
   contrast here the title bar was one more part of the window blending into the rest of it. */
.k-window.editor-window > .k-window-titlebar {
    background-color: var(--kendo-color-base-subtle);
    border-bottom: 1px solid var(--kendo-color-border-alt);
    border-top-left-radius: 7px;
    border-top-right-radius: 7px;
}

/* Inside the window the container is a flex child, so it fills rather than asking for
   100% of an indefinite parent; and it must NOT scroll -- only the tab body does. */
.k-window.editor-window .editor-window-container {
    flex: 1;
    min-height: 0;
    height: auto;
    overflow: hidden;
}

.editor-action-bar {
    flex-wrap: wrap;
}

/* PHONE (the project's one breakpoint, ShellBreakpoint.PhoneMedia). An editor window fills the
   viewport: no drag, no resize, no minimise/maximise, title bar and action bar stay put and the
   body scrolls. !important is deliberate -- Telerik writes inline width/height/top/left (and the
   centring transform) on open, drag and resize, and a stylesheet loses to an inline style
   otherwise. The Width/Height parameters stay the desktop size. */
@media (max-width: 768px) {
    .k-window.editor-window {
        inset: 0 !important;
        transform: none !important;
        min-width: 0 !important;
        width: 100% !important;
        height: 100vh !important;
        height: 100dvh !important;
        max-width: none;
        border: 0;
        border-radius: 0;
        box-shadow: none;
    }

    .k-window.editor-window > .k-resize-handle {
        display: none;
    }

    .k-window.editor-window > .k-window-titlebar {
        border-radius: 0;
        cursor: default;
    }

    .k-window.editor-window > .k-window-content {
        border-radius: 0;
    }

    .k-window.editor-window .k-window-titlebar-action:has(.k-svg-i-window-minimize),
    .k-window.editor-window .k-window-titlebar-action:has(.k-svg-i-window),
    .k-window.editor-window .k-window-titlebar-action:has(.k-svg-i-window-restore) {
        display: none;
    }

    .k-window.editor-window .editor-window-container {
        overflow-y: auto;
    }

    .editor-window-container > .editor-action-bar {
        position: sticky;
        top: 0;
        z-index: 2;
        background-color: var(--kendo-color-surface);
    }

}

/* THE ONE-DOCUMENT EDIT MODAL BODY (EditorSections).

   Two columns: the "Jump to:" pane on the left is a non-shrinking flex child beside a body that
   is the only thing that scrolls with the document. min-height:0 is mandatory on the scrolling
   child -- a flex child defaults to min-height:auto and refuses to shrink below its content,
   which would put the scrollbar on the whole modal instead. */
.editor-sections {
    display: flex;
    flex-direction: row;
    flex: 1;
    min-height: 0;
    overflow: hidden;
}

/* Fixed 200px: wide enough for the section titles in use without taking more than ~18% of the
   1100px editor. flex:0 0 pins it, so a long title can neither widen nor collapse the pane -- it
   wraps inside the marker instead (see .editor-section-marker). The nav itself no longer scrolls
   as a whole -- an optional PaneHeader (e.g. Cardholder's Picture/Signature) and the "Jump to:"
   heading stay put as fixed flex children, and only .editor-section-marker-list below them
   scrolls, so a ~20-section editor (Cards) does not force the whole column, or the pane header,
   taller than the modal. */
.editor-section-nav {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-1);
    flex: 0 0 200px;
    width: 200px;
    min-height: 0;
    overflow: hidden;
    padding: var(--space-2);
    border-right: 1px solid var(--kendo-color-border);
    background-color: var(--kendo-color-surface);
}

/* Cardholder's Picture/Signature (or any future PaneHeader content) sits above the marker list.
   Images inside it should size to the 200px pane's own content width -- see the img rule below --
   rather than overflow it or force horizontal scroll. */
.editor-section-pane-header {
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding-bottom: var(--space-2);
    border-bottom: 1px solid var(--kendo-color-border);
}

.editor-section-pane-header img {
    max-width: 100%;
    height: auto;
}

.editor-section-nav-heading {
    flex-shrink: 0;
    padding: var(--space-1) var(--space-2);
    font-weight: 600;
    color: var(--kendo-color-subtle);
}

.editor-section-marker-list {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-1);
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

.editor-section-marker {
    border: 1px solid transparent;
    border-radius: var(--kendo-border-radius-md, 4px);
    background: none;
    color: inherit;
    cursor: pointer;
    font: inherit;
    flex-shrink: 0;
    text-align: left;
    padding: var(--space-1) var(--space-2);
    overflow-wrap: anywhere;
    user-select: none;
}

.editor-section-marker:hover {
    background-color: var(--kendo-color-base-subtle);
}

.editor-section-marker:focus-visible {
    outline: 2px solid var(--kendo-color-primary);
    outline-offset: -2px;
}

.editor-section-marker-active {
    background-color: var(--kendo-color-primary);
    border-color: var(--kendo-color-primary);
    color: var(--kendo-color-on-primary);
    font-weight: 600;
}

.editor-sections-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
    .editor-sections-body {
        scroll-behavior: auto;
    }
}

/* PHONE: the "Jump to:" pane becomes a strip above the body, markers scrolling sideways. */
@media (max-width: 768px) {
    .editor-sections {
        flex-direction: column;
    }

    .editor-section-nav {
        flex: 0 0 auto;
        width: auto;
        max-height: 40%;
        border-right: 0;
        border-bottom: 1px solid var(--kendo-color-border);
    }

    .editor-section-pane-header {
        max-height: 24vh;
        overflow: auto;
    }

    .editor-section-marker-list {
        flex: 0 0 auto;
        flex-direction: row;
        overflow-x: auto;
        overflow-y: hidden;
    }

    .editor-section-marker {
        white-space: nowrap;
    }
}

/* EditorSections.BodyHeader -- the editor's leading fields (Timezone name + Site, Dynamic User
   Group Name), first in the scrolling body. Divided from the first section the same way sections
   are divided from each other, so it reads as the leading block of the document rather than as a
   separate band of chrome. */
.editor-sections-body-header {
    border-bottom: 1px solid var(--kendo-color-border);
}

/* The read-only body wrapper is a fieldset purely for its disabled semantics, so every UA box it
   would otherwise draw is removed -- including min-inline-size, whose auto value would stop the
   sections shrinking with the window. */
.editor-sections-readonly {
    border: 0;
    margin: 0;
    padding: 0;
    min-inline-size: 0;
}

/* Same deal for the tabbed editors' gate (EditorTabStrip.BodyEnabled): a fieldset only for its
   disabled semantics, so none of the UA box survives and it does not become a second flex/height
   boundary between the editor body and the tab strip. */
.editor-permission-gate {
    border: 0;
    margin: 0;
    padding: 0;
    min-inline-size: 0;
    display: contents;
}

/* With the markers in a left pane there is no longer any chrome overhanging the top of the
   scroller, so this is only the breathing room that keeps the heading off the body's top edge --
   which is what the old value already amounted to. Re-checked, not inherited. */
.editor-section {
    scroll-margin-top: var(--space-2);
    position: relative;
}

/* Deferred loading (EditorSection.Deferred). The sentinel is the load trigger line: it rides the
   ONE scroll-spy observer alongside the sections, so parking it this far above its own section is
   what makes loading start while the section is still below the fold. There is no second
   observer and no second rootMargin -- an IntersectionObserver only has one. */
.editor-section-preload {
    position: absolute;
    top: -480px;
    left: 0;
    width: 1px;
    height: 1px;
    pointer-events: none;
}

/* Reserves height so an unloaded section is not zero-tall. A body of collapsed sections would
   put every marker's scroll target within a few pixels of the next, and the spy would report the
   last section for the whole document. The section still grows when its content lands; that is
   accepted, because a jump loads the section BEFORE it scrolls, so the growth happens ahead of
   the movement rather than under it. */
.editor-section-placeholder {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 160px;
    padding: var(--space-4) var(--space-5);
    color: var(--kendo-color-subtle);
}

.editor-section + .editor-section {
    border-top: 1px solid var(--kendo-color-border);
}

/* Cardholder's Cardholder Status / Access Levels pair reads as ONE section -- Access Levels
   passes ShowHeading="false" (see Cardholder.razor) so it has no <h2> of its own, and Cardholder
   Status's heading above it is the umbrella title. The divider line between them would be the
   only thing left implying a section boundary. Drop it too. */
[data-editor-section="cardholder-status"] + [data-editor-section="access-levels"] {
    border-top: none;
}

/* The section is focused by the jump (tabindex=-1) so focus follows the scroll. That focus is
   programmatic, not a user tab stop, so it gets no ring. */
.editor-section:focus {
    outline: none;
}

.editor-section-heading {
    margin: 0;
    /* Was --space-3 (12px) top -- combined with .form-content's own bottom padding above, this
       stacked into the section-to-section gap called out as too open relative to the row gap.
       --space-2 keeps the heading visually offset from the previous section's last row (it should
       still read as a new section, not just another row) while bringing the total closer to that
       row gap than the old value did. */
    padding: var(--space-2) var(--space-5) 0;
    font-size: var(--kendo-font-size-lg, 1rem);
    font-weight: 600;
}

/* Sections stack; a body inside one is sized by its own content, never by a share of the
   scroller. flex:1 from .form-content would fight that. */
.editor-section > .form-content {
    flex: 0 0 auto;
    overflow-y: visible;
}

/* Tab panel body — the padded, scrollable area a form sits in.
   Top/bottom was --space-4 (16px) on all four sides -- inside an EditorSections document that
   stacks section after section (Cardholder's Name/General/etc.), this bottom padding plus the
   next section's own heading top-padding (--space-3, see .editor-section-heading below) added up
   to ~29px between the last row of one section and the next section's heading, next to an 8px
   row-to-row gap inside each section -- the section boundary read far more "empty" than the rows
   around it, not just intentionally distinct from them. Halved to --space-2 to bring it in line
   with the row density change above; left/right stays --space-5 (the horizontal reading margin,
   unrelated to this). */
.form-content,
.form-container,
.tab-content {
    padding: var(--space-2) var(--space-5);
}

/* A button that opens a hidden InputFile's native picker. Invoking .click() on the input via JS
   interop loses Chromium's transient user-activation across the interop boundary (silent no-op
   in MAUI's WebView2 -- see DeviceControllerTypePicker's "Other..."), so the input is instead
   stacked directly on top of the button and takes the real click itself. */
.file-trigger {
    position: relative;
    display: inline-block;
}

.file-trigger-input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

/* Sub-section headings within a section's own body (e.g. Door Times, Strike Follow, Commands,
   Display) -- normal labels are 14px/600 (--kendo-font-size), .editor-section-heading is
   17px/600 (--kendo-font-size-lg); this sits at 16px/600 in the SAME full-strength text color
   (not --kendo-color-subtle, which read as weaker than a label, not stronger) plus a bottom
   rule so it reads as a heading rather than a slightly-bigger label. */
.form-content h4,
.form-container h4,
.tab-content h4 {
    margin: 0 0 var(--space-2);
    padding-bottom: var(--space-1);
    border-bottom: 1px solid var(--kendo-color-border);
    font-size: 16px;
    font-weight: 600;
    color: var(--kendo-color-on-app-surface);
}

/* Was --space-4 (16px) between rows -- legacy's own field density (WinForms TableLayoutPanel
   rows sit close together with a thin gap, not a full line of whitespace between each label)
   reads noticeably tighter than that once a form runs more than a handful of rows (e.g. the
   Cardholder Fields UserText1-17 block). --space-2 (8px) halves the gap without the rows
   touching. */
.form-content,
.form-container {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    max-width: 880px;
    overflow-y: auto;
    flex: 1;
}

/* READ-ONLY FIELDS MUST LOOK READ-ONLY.

   Telerik ships no visual treatment for a read-only text input: its own `k-readonly` class only
   covers the slider and the rating, so ReadOnly="true" on a TextBox renders identically to an
   editable one. The Auditing tab is the clearest case -- four fields nobody can edit, presented
   exactly like fields they can.

   Keyed off the readonly ATTRIBUTE rather than a Telerik class, so it applies to every component
   that renders one, and to plain inputs, without naming each component.

   Deliberately NOT styled the same as disabled: a read-only field is still focusable and its
   text is still selectable and copyable, which is the whole point of the audit fields. It reads
   as inert, not as unavailable. */
.k-input-inner[readonly],
input[readonly],
textarea[readonly] {
    background-color: var(--kendo-color-base-subtle);
    color: var(--kendo-color-subtle);
    cursor: default;
}

/* The wrapper carries the border, so it has to soften with the field or the control looks
   half-changed. */
.k-input:has(.k-input-inner[readonly]) {
    background-color: var(--kendo-color-base-subtle);
    border-color: var(--kendo-color-border);
}

/* The shared Notes tab fills whatever the tab gives it, at any modal size, instead of being
   sized by a Rows count. .form-content above is already a flex column with flex:1, so the
   textarea only needs to claim the remaining space and stretch its inner element.

   GLOBAL, not in a scoped .razor.css: Telerik renders .k-textarea and the <textarea> inside it
   from its OWN render tree, so a component-scoped selector never carries the scope attribute
   those elements would need and silently matches nothing.

   Rows stays on the component as the fallback that decides the height if this rule's ancestor
   chain has no definite height to resolve against.

   It is also the one form body with nothing but a textarea in it, so it opts OUT of the shared
   .form-content box: no padding, and no 880px cap. Overridden HERE rather than on .form-content,
   which every other editor shares. Same specificity, later in the file, so these win. */
[data-testid="notes-tab"] {
    overflow: hidden;              /* the textarea owns the scrollbar, not the wrapper */
    padding: 0;
    gap: 0;
    max-width: none;
    width: 100%;
    min-height: 0;                 /* flex children refuse to shrink without it */
}

    [data-testid="notes-tab"] .k-textarea {
        flex: 1 1 auto;
        min-height: 0;
    }

        [data-testid="notes-tab"] .k-textarea textarea {
            height: 100%;
            resize: none;          /* the tab decides the size; a drag handle fights it */
        }

/* Label + field pair, SIDE BY SIDE on one row (VES-444).

   This was `flex-direction: column`, which put every label on its own line above its control
   and is why the report reads "All Forms in the Product" -- one rule, every screen. Note the
   name: `.form-row` was stacking too.

   Two columns rather than `flex-direction: row`, because direction alone is only half of it.
   Flowing each control immediately after its own label leaves the controls ragged -- short
   label, control near the left; long label, control further right -- and legacy lines them all
   up on one column. Legacy's forms ARE two-column grids: ServerEdit is a TableLayoutPanel with
   ColumnCount = 2 (ServerEdit.resx:189), labels anchored Left in column 0
   (lblServerName.Anchor / lblCardUpdateInterval.Anchor, ServerEdit.resx:125, :441) -- so the
   label text is left-aligned in its column, not right-aligned against the control.

   The width is a token so a screen with unusually long labels can widen its own column without
   forking the rule. 200px sits inside the range the scoped sheets already chose for exactly
   this layout: 180px (DataExchangePackages, EngageGateway), 200px (SaltoDoor), 220px
   (EngageEdgeDevice, IPLockset). Those five keep their own widths -- this only supplies the
   default for the screens that never had one.

   DataExchangePackages.razor.css:23 called this rule out as miswritten while fixing VES-414 and
   deliberately left it alone ("they belong to other screens and are reported separately").
   This is that follow-up. */
.form-row,
.form-group {
    display: grid;
    grid-template-columns: var(--form-label-col, 200px) minmax(0, 1fr);
    align-items: center;
    gap: var(--space-1) var(--space-4);
    /* Was --space-4 (16px) -- matches the .form-content/.form-container gap reduction above, for
       a .form-row used standalone outside either (the margin-block-end:0 override further down
       zeroes this back out for the common case, letting the parent's own flex gap space rows
       instead; this is the fallback for whatever still doesn't route through that parent). */
    margin-block-end: var(--space-2);
}

/* A row with nothing but its own single child spans both columns, starting at the left edge.
   Two shapes land here and legacy treats both the same way:

     * checkboxes and radios, whose <label> wraps the control AND its own text, so the box comes
       first and the text after -- the opposite order to a text field, and already how this app
       marks them up (AreaEdit.razor:38-40 and ~230 others). Legacy adds every one of them at
       COLUMN 0 with ColumnSpan 2 (ServerEdit.Designer.vb:868-1008, e.g.
       chkEnforceUniquePinNumbers at :883 spanned at :997) -- full width from the left edge, not
       indented into the control column. Putting a separate label before the box, which a plain
       `flex-direction: row` would have done, would be worse than the stacked layout it replaced.
     * a lone button with no label of its own (Server.razor:55-58, :76-86). Legacy spans those
       too (btnSyncAllControlIdDevices, ServerEdit.Designer.vb:1919). */
.form-row > *:only-child,
.form-group > *:only-child {
    grid-column: 1 / -1;
}

.k-window.allowed-commands-window .k-window-content {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

    .k-window.allowed-commands-window .k-window-content > .form-content {
        flex: 1;
        min-height: 0;
    }

.form-row > .k-button:only-child,
.form-group > .k-button:only-child {
    justify-self: start;
}

/* Everything that is not the label belongs in the control column. Without this, a row carrying
   a third child -- a .form-hint (Server.razor:316, :881) or a .validation-message -- would flow
   to the next grid cell, which is the LABEL column, and the hint would sit under the label
   instead of under the field it describes.

   Both rules exclude :only-child so the span above still wins for a lone checkbox label or a
   lone button; a bare :not(label) would out-specify it and pin the button into column 2. */
.form-row > label:not(:only-child),
.form-group > label:not(:only-child),
.form-row > .form-label:not(:only-child),
.form-group > .form-label:not(:only-child) {
    grid-column: 1;
}

.form-row > *:not(label):not(.form-label):not(:only-child),
.form-group > *:not(label):not(.form-label):not(:only-child) {
    grid-column: 2;
}

.form-content > .form-row,
.form-content > .form-group,
.form-container > .form-row,
.form-container > .form-group,
/* .udf-panel (UserDefinedFieldsPanel -- Cardholder's Department/UserText1-17 etc.) is the same
   shape as .form-content/.form-container -- a flex column of .form-row children with its own
   `gap` -- but was missing from this list, so its rows kept their own margin-block-end ON TOP OF
   the parent's flex gap: the two stacked into roughly double the row spacing every other section
   uses, which is why User Defined fields read as noticeably more spaced out than Last Name/First
   Name right above them even after the row-density pass. */
.udf-panel > .form-row,
.udf-panel > .form-group {
    margin-block-end: 0; /* the flex gap on the parent spaces these */
}

.form-row > label,
.form-group > label,
.form-row > .form-label,
.form-group > .form-label {
    display: block;
    margin-block-end: 0; /* neutralises Bootstrap's .form-label margin */
    font-weight: 600;
    line-height: 1.4;
}

.form-field,
.filter-field {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

/* A text field plus its browse/pick button as ONE .form-row control-column child (e.g.
   VideoRecorder.razor's "Viewer .exe location:", the Data Exchange text-file source/destination
   file pickers). Without this wrapper, the field and the button are two separate .form-row
   children that both land in the control column via the ":not(label)" rule but with no shared
   flex/grid context between them, so each grid-auto-flows onto its own row -- the button ends up
   stacked underneath the field instead of beside it. Previously only defined page-scoped in
   License.razor.css; promoted to global since more than one page/component uses the class and a
   shared component (Components/DataExchange) has no scoped stylesheet of its own to carry it. */
.field-with-button {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: var(--space-2);
}

.field-with-button > .k-textbox,
.field-with-button > .k-input {
    flex: 1 1 auto;
    min-width: 0;
}

.filter-field > label {
    font-weight: 600;
    font-size: var(--kendo-font-size-sm);
    color: var(--kendo-color-subtle);
}

/* Required-field marker — colour plus the literal asterisk, so the
   cue is never carried by colour alone */
.required,
.req-star,
.udf-required-star {
    margin-inline-start: var(--space-1);
    color: var(--kendo-color-error);
    font-weight: 700;
}

/* Screen-reader-only twins of the asterisk */
.req-sr-only,
.udf-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Validation message sits under its field, not glued to it */
.form-row .validation-message,
.form-group .validation-message,
.form-field .validation-message {
    margin-block-start: var(--space-1);
    font-size: var(--kendo-font-size-sm);
    color: var(--kendo-color-error);
}

/* Telerik's own form: the label is flush against its input by design.
   The extra element selector outweighs the theme rule it overrides. */
.k-form .k-form-field > label.k-form-label,
.k-form .k-form-field > label.k-label {
    margin-block-end: var(--space-1);
}

/* Bare <label> inside a Telerik form field (no k-label class) */
.k-form .k-form-field > label:not([class]) {
    display: block;
    margin-block-end: var(--space-1);
    font-weight: 600;
}

/* Centred single-column page form (account pages) */
.account-form {
    max-width: 500px;
    margin: var(--space-10) auto;
    padding: var(--space-6);
}

.account-form-title {
    margin: 0 0 var(--space-4) 0;
    font-size: var(--kendo-h5-font-size);
    font-weight: 600;
}

/* ── Global toolbars & button groups ──────────────────────────────
   Every gap here is >= 8px, which keeps adjacent hit targets apart. */

.page-toolbar,
.detail-toolbar,
.button-bar,
.picture-buttons,
.signature-buttons {
    display: flex;
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-block-end: var(--space-3);
}

/* Telerik's own k-grid-toolbar wraps GridToolBarTemplate content in .k-toolbar-items (itself
   full width, flex-grow:1), but .page-toolbar is a flex ITEM inside that and defaults to
   sizing to its own content -- so .page-toolbar (and .toolbar-wrapper/.toolbar-right-group
   inside it) sat in a box narrower than the grid, with no room for the right group's
   auto-margin push to move into. flex-grow gives it the rest of the toolbar's width to push
   against. Descendant selector, not child: .k-toolbar-items sits between the two. */
.k-grid-toolbar .page-toolbar {
    flex: 1 1 auto;
    /* min-width:0, or the automatic minimum size of a flex item holds this at its content's
       max-content width and the whole toolbar overflows the grid instead of its one shrinkable
       item giving ground. Needed on .k-toolbar-items too -- it sits between the two, and a
       shrink chain is only as constrained as its loosest link. */
    min-width: 0;
}

.k-grid-toolbar .k-toolbar-items {
    min-width: 0;
}

/* .toolbar-wrapper carries width:100% because on CardHolders it IS the whole toolbar row.
   Nested inside .page-toolbar (Cards) it is one flex item among several, and that fixed
   width claimed the entire row, pushing every sibling onto a line of its own -- a three-row
   toolbar at every width, pane open or closed. flex-grow (not plain width:auto) replaces
   it: auto alone shrinks .toolbar-wrapper to its own content, which leaves the right
   group's margin-inline-start:auto push with no room to move into and Clear
   Filters/Search/Advanced Find sit flush left instead of at the row's right edge. Growing
   still lets the other .page-toolbar siblings (flex-shrink:0 below) keep their own size. */
.page-toolbar > .toolbar-wrapper {
    width: auto;
    flex: 1 1 auto;
    min-width: 0;
}

/* A toolbar that leads with a results count -- or that hosts a GridToolBar, whose row is
   single by contract -- stays ONE row, and the count is the only item allowed to give ground.
   flex-wrap:nowrap is what makes the count's text-overflow engage at all: a WRAPPING flex
   container shrinks nothing -- it moves items to the next line instead -- so the count kept
   its full width and pushed the search box and the buttons onto lines of their own. */
.page-toolbar:has(> .spike-results-count),
.page-toolbar:has(> .toolbar-wrapper) {
    flex-wrap: nowrap;
}

.page-toolbar:has(> .spike-results-count) > :not(.spike-results-count),
.page-toolbar:has(> .toolbar-wrapper) > :not(.spike-results-count) {
    flex-shrink: 0;
}

.k-grid-toolbar {
    container: gridtoolbar / inline-size;
}

@media (max-width: 767.98px) {
    .toolbar-wrapper,
    .toolbar-right-group,
    .page-toolbar:has(> .spike-results-count),
    .page-toolbar:has(> .toolbar-wrapper) {
        flex-wrap: wrap;
    }

    .toolbar-wrapper > .toolbar-right-group {
        margin-inline-start: 0;
        flex: 1 1 auto;
        min-width: 0;
    }

    .toolbar-wrapper > .spike-results-count {
        flex: 1 1 100%;
    }
}

@container gridtoolbar (max-width: 640px) {
    .toolbar-wrapper,
    .toolbar-right-group,
    .page-toolbar:has(> .spike-results-count),
    .page-toolbar:has(> .toolbar-wrapper) {
        flex-wrap: wrap;
    }

    .toolbar-wrapper > .toolbar-right-group {
        margin-inline-start: 0;
        flex: 1 1 auto;
        min-width: 0;
    }

    .toolbar-wrapper > .spike-results-count {
        flex: 1 1 100%;
    }
}

/* The row count that opens a grid toolbar. Global rather than page-scoped because
   CardsGridComponent renders it too, in two hosts; a page-scoped copy reaches neither. */
.spike-results-count {
    /* Base size, not -lg: this sits directly above the grid's column headers and read as a
       different type scale from them. */
    font-size: var(--kendo-font-size, 14px);
    font-weight: 600;
    color: var(--kendo-color-on-app-surface, #ffffff);
    /* Truncates instead of wrapping. This text is long and unbounded ("Search (Any field
       contains 'x') results: 2500 items (0 currently selected).") and, as the first flex
       item of a wrapping row, at its full width it forces the search box and the buttons
       onto further lines. The full string stays available as the element's title. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
    flex: 0 1 auto;
}

/* Pushes a trailing group of buttons to the far end of a toolbar.
   Only the first one gets the auto margin, so the group stays
   together instead of spreading across the row. */
.right-button {
    margin-inline-start: auto;
}

.right-button + .right-button {
    margin-inline-start: 0;
}

/* Dialog / modal footers */
.dialog-buttons,
.form-actions {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: var(--space-2);
    justify-content: flex-end;
    margin-block-start: var(--space-4);
}

.form-actions-center {
    justify-content: center;
}

/* Column picker popup body (ConfigurableGrid) */
.column-setup-wrapper {
    padding: var(--space-3);
}

/* Section heading above a detail grid (Cards, Medeco Keys, …) */
.detail-grid-header {
    margin: 0 0 var(--space-2) 0;
    font-size: var(--kendo-font-size-lg);
    font-weight: 600;
    color: var(--kendo-color-on-app-surface);
}

/* Buttons stacked in a grid's action cell must not touch */
.k-grid td > .k-button + .k-button {
    margin-inline-start: var(--space-1);
}

/* Row of action buttons inside a grid cell */
.cell-actions {
    display: flex;
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-1);
}

/* Search control cluster inside a grid toolbar (field picker + box +
   button). Was an unstyled <span>, so the three controls touched. */
.search-wrapper {
    display: inline-flex;
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
}

/* Body of a dialog that hosts a whole child component */
.advanced-find-wrapper {
    padding: var(--space-3);
}

/* User-defined-fields panel (cardholder / card UDF tabs) */
.udf-panel {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

/* Class= on TelerikTextArea, so this must be global to take effect */
.notes-textarea {
    width: 100%;
}

/* ── Global fieldset panels & checkbox grids ──────────────────────
   Consolidated from four near-identical inline <style> blocks that
   IntervalEdit, HolidayEdit and RecipientEdit each carried. The
   #ccc borders those used are replaced by the themed border token,
   which was the point of hoisting them into one place. */

.form-fieldset,
.days-panel,
.holiday-groups-panel,
.recipient-section {
    margin-block-start: var(--space-3);
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--kendo-color-border);
    border-radius: var(--kendo-border-radius-md);
}

.form-fieldset > legend,
.days-panel > legend,
.holiday-groups-panel > legend,
.recipient-section > legend {
    padding: 0 var(--space-2);
    font-weight: 700;
}

/* Checkbox matrices (days of the week, holiday groups) */
.checkbox-grid,
.days-grid,
.holiday-groups-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-2) var(--space-4);
}

/* Two side-by-side .form-row checkboxes (Site's Card Options) rather than the 4-column
   .checkbox-grid pattern above -- that one is for the .checkbox-item shape (checkbox+label as one
   inline pair, dense enough for 4 across); a .form-row keeps its own label-left/field-right grid
   column layout, wider per item, so 2 columns rather than 4 was what read right for these. Each
   child stays a full .form-row -- this only lays them out 2-per-line, auto-flowing left-to-right. */
.two-col-checkbox-rows {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    column-gap: var(--space-5);
}

@media (max-width: 768px) {
    .checkbox-grid,
    .days-grid,
    .holiday-groups-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

/* Checkbox + its label. 8px keeps the two hit targets apart. */
.checkbox-item,
.day-item,
.group-item {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: var(--space-2);
}

.day-label,
.group-label {
    cursor: default;
}

/* Global checkbox size fix. ThemeBuilder.css's bare ".k-checkbox" rule (the one every
   TelerikCheckBox actually matches here -- no "-sm"/"-md"/"-lg" modifier class ends up on the
   rendered input) sets no width/height at all, so with -webkit-appearance:none and no other
   intrinsic size the box collapses to 0x0 -- unusable, not just small. Give the bare class an
   explicit size (matching the "lg" scale ThemeBuilder does define) so every checkbox app-wide
   gets a visible, clickable box regardless of which modifier class (if any) is present. */
.k-checkbox {
    width: var(--kendo-spacing-5, 1.25rem);
    height: var(--kendo-spacing-5, 1.25rem);
}

.k-checkbox::before {
    font-size: var(--kendo-spacing-4\.5, 1.125rem);
}

/* Same bare-class-has-no-size bug as .k-checkbox above, but for TelerikRadioGroup/RadioButton --
   the rendered input matches bare ".k-radio" with no "-sm"/"-md"/"-lg" modifier, so it collapses
   to 0x0 without this. */
.k-radio {
    width: var(--kendo-spacing-5, 1.25rem);
    height: var(--kendo-spacing-5, 1.25rem);
}

.k-radio::before {
    font-size: var(--kendo-spacing-4\.5, 1.125rem);
}

.site-switcher-popup .k-list-item.k-selected {
    background-color: transparent;
    color: inherit;
}

.license-paste-btn .k-svg-icon {
    color: #26a69a;
}

.license-auto-update .k-svg-icon {
    color: #4a9dd8;
}

.license-export-loc .k-svg-icon {
    color: #8d6e63;
}

.license-import-lic .k-svg-icon {
    color: #4caf50;
}

.license-close-btn .k-svg-icon {
    color: #e05252;
}

/* Events toolbar icons, in the ribbon's own --msym-color palette (RibbonBar.razor) so a gesture
   is the same colour wherever the operator meets it. The matching context-menu entries carry the
   same five values as GridContextMenuItem.IconColor -- see Events.razor's *IconColor constants.

   GLOBAL, not Events.razor.css: Blazor's CSS isolation never stamps its scope attribute onto a
   third-party component's own root element, so a scoped `::deep` rule cannot reach a
   TelerikButton's icon at all. */
/* Pause and Resume are ONE button in two states, and they must not look alike: amber while the
   feed is running, green (the ribbon's New) once it is paused and the button offers Resume. The
   context-menu twin carries the same two values -- Events.razor's Pause/ResumeIconColor. */
.events-pause-btn .k-svg-icon {
    color: #F59E0B;
}

.events-resume-btn .k-svg-icon {
    color: #4caf50;
}

.events-playback-btn .k-svg-icon {
    color: #7e57c2;
}

.events-view-btn .k-svg-icon {
    color: #26a69a;
}

.events-journal-btn .k-svg-icon {
    color: #4e342e;
}

/* Edit Card / Edit Cardholder draw the NAVIGATION's own material symbol for their subject, whose
   colour is set inline from NavCatalog.IconFor -- so there is no colour rule here on purpose. All
   this does is size the symbol like a button icon and space it off the caption. */
.events-edit-card-btn .msym,
.events-edit-cardholder-btn .msym {
    font-size: 18px;
    line-height: 1;
    margin-right: 4px;
    vertical-align: text-bottom;
}

.events-view-cardholder-btn .k-svg-icon {
    color: #4caf50;
}

/* Telerik's selected toggle paints #3f3f3f on a darkened orange (3.9:1); the primary fill with
   its paired foreground clears WCAG AA. */
.events-view-cardholder-btn.k-selected {
    background-color: var(--kendo-color-primary);
    border-color: var(--kendo-color-primary);
    color: var(--kendo-color-on-primary);
}

/* A disabled Telerik button dims the whole button; leaving the icon at full-strength colour
   would make a greyed Playback/View read as available. */
.events-playback-btn:disabled .k-svg-icon,
.events-view-btn:disabled .k-svg-icon,
.events-edit-card-btn:disabled .msym,
.events-edit-cardholder-btn:disabled .msym {
    color: inherit !important;
}

/* Multi-column checkbox groups (ReaderEdit's grpReaderOptions/grpControlIdDevice and similar
   legacy RadGroupBox panels laid out on a 2-column TableLayoutPanel). Each .checkbox-column is
   a plain vertical stack; the outer grid is what puts two stacks side by side, matching the
   legacy column arrangement instead of one long single-column list. */
.checkbox-columns {
    display: grid;
    grid-template-columns: repeat(2, minmax(260px, 1fr));
    gap: var(--space-2) var(--space-4);
}

@media (max-width: 768px) {
    .checkbox-columns {
        grid-template-columns: 1fr;
    }
}

.checkbox-column {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.checkbox-column label {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

/* A row of buttons side by side, e.g. Test Connection / Request Sync / View Sync Details on
   the Control iD panel. Wrapped as the .form-group's single child so the :only-child span rule
   above applies and the row gets the full width instead of collapsing into one grid cell,
   where multiple buttons would otherwise stack. */
.button-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
}

/* ── Global dual-listbox (Available / Assigned pickers) ──────────── */

.dual-listbox-container {
    display: flex;
    flex-direction: row;
    align-items: stretch;
    gap: var(--space-3);
    padding: var(--space-3);
}

.listbox-column {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    flex: 1;
    min-width: 0;
}

.listbox-label {
    font-weight: 600;
    font-size: var(--kendo-font-size-sm);
    color: var(--kendo-color-subtle);
}

.listbox-transfer-buttons {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: var(--space-2);
    padding: 0 var(--space-1);
}

/* The double-click target. It fills its row so the whole visible item responds, and stops at the
   item so empty space below the last row stays inert, as IsListItemDoubleClick requires. */
.dual-list-item {
    display: block;
    width: 100%;
    user-select: none;
}

/* Scrollable tab/dialog content area — scoped to editor containers only */
.channel-editor-container .form-content {
    display: flex;
    flex-direction: column;
    gap: 0;
    padding: 16px 20px;
    max-width: 800px;
    overflow-y: auto;
    flex: 1;
}

/* Individual label + input row — scoped to editor containers only */
.channel-editor-container .form-group {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 12px;
    padding: 6px 0;
    margin-block-end: 0; /* row padding supplies the rhythm here */
}

.channel-editor-container .form-group > label {
    min-width: 220px;
    flex-shrink: 0;
    font-size: var(--kendo-font-size);
    font-weight: 400;
    color: var(--kendo-color-subtle);
    line-height: 1.4;
}

/* Editor container (SCP edit, Channel edit, etc.) — the flex chain lives in the
   shared editor chrome contract above; only the toolbar padding differs here. */
.channel-editor-container > .toolbar {
    padding: 8px 12px;
    border-bottom: 1px solid var(--kendo-color-border);
    background-color: var(--kendo-color-surface);
    flex-shrink: 0;
}

/* Grid row detail expand panel */
.channel-detail {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 8px 24px;
    padding: 12px 16px;
    background-color: var(--kendo-color-surface-alt);
}

.detail-row {
    display: flex;
    gap: 6px;
    font-size: var(--kendo-font-size);
    padding: 3px 0;
}

.detail-row strong {
    color: var(--kendo-color-subtle);
    min-width: 100px;
    flex-shrink: 0;
}

/* Input editor event panel */
.event-edit-panel {
    margin-top: 16px;
    padding: 16px;
    border: 1px solid var(--kendo-color-border);
    border-radius: 4px;
    background-color: var(--kendo-color-surface-alt);
}

/* Dual-listbox panel theming (AccessLevel, etc.) */
.dual-list-toolbar {
    display: flex;
    gap: 20px;
    align-items: center;
    flex-wrap: wrap;
    margin-bottom: 12px;
    padding: 10px 12px;
    background: var(--kendo-color-surface);
    border: 1px solid var(--kendo-color-border);
    border-radius: 4px;
}

.dual-list-container {
    display: flex;
    align-items: stretch;
    border: 1px solid var(--kendo-color-border);
    border-radius: 4px;
    overflow: hidden;
}

.dual-list-header {
    background: var(--kendo-color-surface);
    padding: 6px 10px;
    border-bottom: 1px solid var(--kendo-color-border);
    font-size: var(--kendo-font-size-sm);
    font-weight: 600;
}

.dual-list-transfer {
    display: flex;
    flex-direction: column;
    gap: 6px;
    justify-content: center;
    padding: 6px 8px;
    background: var(--kendo-color-surface-alt);
    border-left: 1px solid var(--kendo-color-border);
    border-right: 1px solid var(--kendo-color-border);
}

/* ── UI/UX Pro Max: Typography ── */
/* GRID CELLS ARE DELIBERATELY NOT IN THIS LIST any more. 1.5 is prose leading; on a grid row it
   is 21px of line box before any padding, which is 2px MORE than the whole row height budget
   GridVirtualization.RowHeightCompact is built from. Reading leading belongs to reading text —
   a grid row is a scannable line, and it is the density the operator complained about. */
body, p, li, td {
    line-height: 1.5;
}

/* One line per row, at the theme's own md leading (1.4 = 19.6px at the 14px base). Paired with
   GridVirtualization.RowHeightCompact: that constant's arithmetic assumes this value, so the two
   change together or the virtual scrollbar drifts. */
.k-grid .k-table-td,
.k-grid td {
    line-height: var(--kendo-line-height-md, 1.4);
}

/* Telerik.UI.for.Blazor 15.0.1 renders a solid-filled button's fill mode and theme color as TWO
   separate classes (k-button-solid, k-button-primary), but ThemeBuilder.css (generated for an
   older theme package) only has rules for the compound class name that version used to emit
   (.k-button-solid-primary etc as ONE class) -- live-verified: neither matches, so a plain
   <TelerikButton ThemeColor="primary"> (and any FillMode="Solid" button generally) renders with
   none of its intended fill, just background:none and a default border. Bridges the two most-used
   variants (default/base and primary) to the same values ThemeBuilder.css already defines for the
   old compound classes, rather than regenerating that whole generated file for one package bump. */
.k-button-solid:not(.k-button-primary):not(.k-button-secondary):not(.k-button-tertiary):not(.k-button-info):not(.k-button-success):not(.k-button-warning):not(.k-button-error) {
    border-color: var(--kendo-color-border, rgba(255, 255, 255, 0.24));
    color: var(--kendo-color-on-base, #F5F5F5);
    background-color: var(--kendo-color-base, #3D3D3D);
}
.k-button-solid:not(.k-button-primary):not(.k-button-secondary):not(.k-button-tertiary):not(.k-button-info):not(.k-button-success):not(.k-button-warning):not(.k-button-error):hover {
    background-color: var(--kendo-color-base-hover, #4D4D4D);
}
.k-button-solid.k-button-primary {
    border-color: var(--kendo-color-primary, #F8941D);
    color: var(--kendo-color-on-primary, #1F1F1F);
    background-color: var(--kendo-color-primary, #F8941D);
}
.k-button-solid.k-button-primary:hover {
    border-color: var(--kendo-color-primary-hover, #E0851A);
    background-color: var(--kendo-color-primary-hover, #E0851A);
}

/* ── UI/UX Pro Max: Interaction — cursor ── */
.k-button, .k-grid-toolbar .k-button,
.k-pager-nav, .k-columnmenu-item,
[role="button"], [role="tab"],
.k-menu-item, .k-treeview-item {
    cursor: pointer;
}

/* ── UI/UX Pro Max: Animation — 150ms ease-out transitions ── */
.k-button,
.k-input, .k-textbox,
.k-grid tr,
.k-menu-item,
.k-treeview-item,
.k-listview-item {
    transition: background-color 150ms ease-out,
                border-color 150ms ease-out,
                box-shadow 150ms ease-out,
                color 150ms ease-out;
}

/* ── UI/UX Pro Max: Accessibility — reduced motion ── */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* Blazor's unhandled-error bar.
   ------------------------------------------------------------------------------------------------
   NOT in a .razor.css, deliberately. #blazor-error-ui is a framework-defined GLOBAL id, and the div
   is rendered by BOTH MainLayout and ConsoleShellLayout. These rules used to live in
   MainLayout.razor.css, where CSS isolation rewrote them to #blazor-error-ui[b-ldpngdn43l] — that
   layout's scope attribute. ConsoleShellLayout has no .razor.css at all, so its div carries no scope
   attribute, `display: none` could not match, and the MAUI console shell showed "An unhandled error
   has occurred" permanently, with no error behind it. Keeping the rules unscoped is what makes the
   two layouts unable to drift apart again. */
#blazor-error-ui {
    /* Was `lightyellow` with no colour of its own, so the inherited body text
       (#EBEBEB in the dark theme) landed on pale yellow at ~1.1:1 — the error
       bar was effectively unreadable in the default theme. The warning pair
       carries its own foreground, so this holds up in both. */
    background: var(--kendo-color-warning, lightyellow);
    color: var(--kendo-color-on-warning, #3D3D3D);
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

    /* Both controls are <button>, not <a>: an anchor would be intercepted by the SPA router, and
       Reload previously used a javascript: URL, which WebView2 blocks — so it did nothing at all on
       the MAUI head. Reset the chrome so they still read as the link and glyph they replace. */
    #blazor-error-ui .reload,
    #blazor-error-ui .dismiss {
        background: none;
        border: 0;
        color: inherit;
        cursor: pointer;
        font: inherit;
        padding: 0;
    }

    #blazor-error-ui .reload {
        margin-left: 0.5rem;
        text-decoration: underline;
    }

    #blazor-error-ui .dismiss {
        font-size: 1.1rem;
        line-height: 1;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
    }

/* ── THE GRID HEIGHT CONTRACT ─────────────────────────────────────────────────
   Spec: docs/superpowers/specs/2026-08-27-grid-height-contract-design.md

   A virtualized grid must fill a container with a DEFINITE height or Telerik
   cannot size its viewport: it renders skeleton rows, or collapses to nothing.
   Nothing throws when this is wrong, which is why the rules below are stated as
   a contract rather than left to each page to arrange.

   THREE ROLES. Every participant declares which one it plays:

     grid-host        the element that OWNS a definite height
     grid-fill        every wrapper between that host and the grid
     grid-fills-host  the TelerikGrid itself

   Any number of `grid-fill` wrappers may sit between host and grid. That is the
   whole point: the rule this replaces was an exact three-level path
   (".grid-page > .page-content > .k-grid"), so ONE extra wrapper silently matched
   nothing — which is why Pages/CardHolders.razor.css had to restate the chain by
   hand for its own container. Per-page CSS like that is what this removes.

   `grid-host` deliberately does NOT set a height. Establishing one is the host's
   own business and differs per host — `inset:0` in a shell tab pane, `100dvh` in
   a popout window (shell.css), `height:100%` on a page container, the pane's own
   geometry in a dock pane, the window's size in a modal. The contract governs
   only what the chain BELOW does with that height. */
.grid-host {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.grid-fill {
    flex: 1 1 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.k-grid.grid-fills-host {
    flex: 1 1 0;
    min-height: 0;
}

/* GridShell's host div when its grid is Height="100%". Without it the grid grows to its rows and
   the clipping page-content hides the overflow, so no scrollbar. Global because the div is
   rendered by GridDeleteKeyRegion, out of reach of page-scoped CSS. */
.grid-shell-fill {
    height: 100%;
    min-height: 0;
}

/* As a flex item beside a panel (Events' photo) the host would otherwise refuse to shrink below
   its columns' total and push the panel out of view; the grid scrolls sideways instead. */
.grid-shell-fill,
.grid-shell-stretch {
    min-width: 0;
}

/* Locked cells are sticky and painted opaque, while the rest of an alternate or hovered row gets a
   translucent tint; re-mix that tint so a pinned column matches. :where() keeps these below a
   page's own row colouring (Events). */
:where(.k-grid .k-master-row.k-table-alt-row) > .k-grid-content-sticky {
    background-color: color-mix(in srgb, var(--kendo-color-on-app-surface) 5%, var(--kendo-color-surface-alt)) !important;
}

:where(.k-grid .k-master-row:hover) > .k-grid-content-sticky,
:where(.k-grid .k-master-row.k-hover) > .k-grid-content-sticky {
    background-color: color-mix(in srgb, var(--kendo-color-on-app-surface) 11%, var(--kendo-color-surface-alt)) !important;
}

/* The stretch column floor. Header, body and footer tables must carry the SAME min-width or their
   columns drift apart. Child combinators leave a nested detail-row grid alone; lifted during
   auto-fit (.k-autofitting) so the floor does not pad the column being measured. */
.grid-shell-stretch > .k-grid > .k-grid-aria-root > .k-grid-header > .k-grid-header-wrap > table:not(.k-autofitting),
.grid-shell-stretch > .k-grid > .k-grid-aria-root > .k-grid-container > .k-grid-content > table:not(.k-autofitting),
.grid-shell-stretch > .k-grid > .k-grid-aria-root > .k-grid-container > .k-grid-content > .k-virtual-scroller-wrap > .k-virtual-scroller-content > table:not(.k-autofitting),
.grid-shell-stretch > .k-grid > .k-grid-aria-root > .k-grid-footer > .k-grid-footer-wrap > table:not(.k-autofitting) {
    min-width: var(--grid-stretch-min-width);
}

/* A FOURTH participant: a TabStrip standing between the host and the grid.
   `grid-fill` cannot be used for this, and that is not an oversight — a TabStrip is not one
   wrapper, it is three (root, .k-tabstrip-content, .k-tabstrip-panel), and Telerik renders the
   inner two from its OWN render tree, so nothing in the page's markup can carry a class onto
   them. Only the root takes a Class, hence one class that reaches all three by descent.

   THIS IS WHAT MAKES A GRID INSIDE A TAB SHRINK TO FIT instead of standing at a fixed height and
   scrolling inside itself while its host scrolls too (CardHolders' Cards tab: two nested
   scrollbars, and a Cards list that never gave any height back to the cardholders grid above it).

   The items row is left alone deliberately: it must keep its natural height, and only the content
   below it competes for what is left. */
.k-tabstrip.grid-fill-tabs,
.k-tabstrip.grid-fill-tabs > .k-tabstrip-content,
.k-tabstrip.grid-fill-tabs > .k-tabstrip-content > .k-tabstrip-panel {
    flex: 1 1 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

    /* A tab whose content is NOT a grid (an editor pane, a form) still has to be reachable, so
       the panel scrolls rather than clipping. A grid child sizes against the panel's own resolved
       height first, so this never becomes the grid's scrollbar. */
    .k-tabstrip.grid-fill-tabs > .k-tabstrip-content > .k-tabstrip-panel {
        overflow: auto;
    }

/* Attached to the GRID, not to a page, because it is a VIRTUALIZATION
   requirement: a row taller than RowHeight desynchronises the scrollbar, and
   clipping one cell is recoverable where a drifting scrollbar is not.

   The rule this replaces was a descendant selector on the page
   (".grid-page .k-grid td"), so it also reached nested NON-virtual sub-grids that
   would rather wrap. */
.k-grid.grid-virtual td {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ── Virtualized grid pages (SUPERSEDED by the contract above) ────────────────
   Retained so the 51 pages still carrying `grid-page` keep working while they
   are converted. Delete once nothing references it.

   Opt in by adding `grid-page` alongside `page-container`.

   This is deliberately NOT a blanket `.page-container` rule. Edit forms use the
   same class and MUST keep scrolling; a global override would trap their content
   with no way to reach the buttons at the bottom.

   What it buys: a definite-height chain. Without one, `Height="100%"` on a grid
   resolves against an auto-height parent and is ignored — measured at 178px of
   collapsed content height on /configuration/holidays before this rule existed,
   with the outer shell providing the only scrollbar. */
.grid-page {
    height: 100%;
    display: flex;
    flex-direction: column;
}

.grid-page > .page-content {
    flex: 1;
    min-height: 0;    /* lets the pane shrink below its content height */
    overflow: hidden; /* the grid owns the ONLY scrollbar on the page */
    display: flex;
    flex-direction: column;
}

/* flex-basis 0 is what makes the flex size win over the grid's own Height="100%",
   so the grid fills whatever the toolbar and header leave behind. */
.grid-page > .page-content > .k-grid {
    flex: 1 1 0;
    min-height: 0;
}

/* Clipping is intentional. A truncated value is recoverable — widen the column,
   or read the full text in the editor — whereas one wrapped cell exceeds
   RowHeight and desynchronises the scrollbar for the whole grid. */
.grid-page .k-grid td {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ── Every TelerikWindow's WindowFooter button row (VES-469) ───────────────────
   Telerik's k-actions-stretched gives every DIRECT footer child flex:1 0 0%, and most footers
   wrap OK in a data-testid span while Cancel is bare. Actions sit bottom-right, affirmative
   first; min-width rather than width, so a longer caption (Abbrechen, Annuler, İptal) still
   takes the room it needs. */
.k-window > .k-window-actions {
    justify-content: flex-end;
}

    .k-window > .k-window-actions > * {
        flex: 0 0 auto;
    }

    .k-window > .k-window-actions .k-button {
        min-width: 100px;
    }

/* AppDialog's key-handling wrapper must not change the layout of the content it wraps. */
.app-dialog-body {
    display: contents;
}

/* No dialog may outgrow the window: the title bar and footer buttons stay on screen and the content
   scrolls. The editor window is sized by its own parameters (80vh) and is excluded. */
.k-window:not(.editor-window) {
    max-height: calc(100vh - 16px);
    max-width: calc(100vw - 16px);
}

.k-window:not(.editor-window) > .k-window-content {
    min-height: 0;
    overflow: auto;
}

/* Legacy's post-login info messages are multi-line (a reason, then "running in demo mode"). */
.confirm-dialog [data-testid="confirm-dialog"] {
    white-space: pre-line;
}

.k-grouping-header {
    display: none;
}

/* NO PAGER ON GRIDS.

   Grids are paged rather than virtualized because virtual scrolling recomputes its data window on
   every container resize, against every pane at once, and inside TelerikDockManager that is a
   measured performance problem. But the operator should not SEE a pager: the rich client has none.
   It asks the server for TOP 5000 (frmCardholders.vb:20 MAX_ROWCOUNT) and scrolls the result,
   telling the operator to filter further once the cap is hit (:1207).

   Pageable stays TRUE so Telerik renders only GridPresentation.PagedPageSize rows; this hides the
   control that would page through the rest. That makes the page a CAP -- see PagedPageSize -- and
   is why the row-cap notice matters: a truncated list must not be silent. */
.k-grid .k-grid-pager,
.k-grid .k-pager {
    display: none;
}

/* BadgeDesigner.razor, hosted at its own route (BadgeDesignerPage.razor) in its own browser
   tab/window rather than a TelerikWindow modal -- see that component's class-level remarks. Its
   own toolbar reuses ribbon.css (ribbon-header-row/ribbon-group/...), the same way
   MapDesigner.razor's does; see BadgeDesigner.razor.css for the rest of this page's own
   (component-scoped) styling. */
.badge-designer-page {
    display: flex;
    flex-direction: column;
    height: 100%;
    padding: var(--space-4, 16px);
    gap: var(--space-3, 12px);
}


/* PHONE: inside an editor window a form row stacks (label above its field) and a control never
   outgrows the window, whatever fixed Width the page gave it. */
@media (max-width: 768px) {
    .editor-window .form-row,
    .editor-window .form-group {
        grid-template-columns: minmax(0, 1fr);
    }

    .editor-window .form-row > label:not(:only-child),
    .editor-window .form-group > label:not(:only-child),
    .editor-window .form-row > .form-label:not(:only-child),
    .editor-window .form-group > .form-label:not(:only-child),
    .editor-window .form-row > *:not(label):not(.form-label):not(:only-child),
    .editor-window .form-group > *:not(label):not(.form-label):not(:only-child) {
        grid-column: 1;
    }

    .editor-window .k-input,
    .editor-window .k-picker,
    .editor-window .k-dropdownlist,
    .editor-window .k-combobox,
    .editor-window .k-numerictextbox,
    .editor-window .k-textarea {
        max-width: 100%;
    }
}

/* PHONE: a list screen's side preview pane would leave the grid a sliver, so the grid takes the
   whole width. The record still opens in its editor window. */
@media (max-width: 768px) {
    .k-splitter:has(> .k-pane > .grid-preview-pane) > .k-splitbar,
    .k-splitter:has(> .k-pane > .grid-preview-pane) > .k-pane:has(> .grid-preview-pane) {
        display: none;
    }
}
