/* MudBlazor loaded into a low-priority cascade layer so unlayered app rules below win on the
 * cascade without needing !important to beat .mud-* specificity. Must stay the first statement
 * in this file (only @charset/@layer may precede an @import). Path is root-relative so it
 * resolves from /css/app.css. See index.html. */
@import url("/_content/MudBlazor/MudBlazor.min.css") layer(mudblazor);

/* =========================================================================
 * 00. MUD PX-CHROME COMPENSATION (density bake, 2026-09-03)
 * MudBlazor states its chrome in px, which the retired --app-zoom used to scale and the
 * 87.5% root font does not (Mud's icons/typography are rem and scale on their own). Each
 * rule below restates one Mud default × 0.875 — ONLY for chrome this app actually renders
 * and does not already restyle.
 * ⚠️ Placement is the contract: this block sits FIRST, directly under the import, so it
 * beats the mudblazor layer (unlayered wins over any layer) while every app rule below
 * still wins ties on source order. Do not move it, and do not add app styling here.
 * ⚠️ A rule here flattens ALL of Mud's contextual variants of that property (a layered
 * higher-specificity variant still loses to unlayered) — check min.css before adding one.
 * Mud originals in the comments; delete this block if MudBlazor ever goes rem. */
.mud-dialog-title { padding: 14px 21px; }                          /* Mud: 16px 24px */
.mud-dialog .mud-dialog-content { padding: 7px 21px; }             /* Mud: 8px 24px */
.mud-dialog .mud-dialog-actions { padding: 7px; gap: 7px; }        /* Mud: 8px / 8px */
.mud-menu-item { padding: 7px 10.5px; }                            /* Mud: 8px 12px */
.mud-menu-item .mud-menu-item-icon { margin-inline-end: 10.5px; }  /* Mud: 12px */
.mud-list-item { padding-top: 7px; padding-bottom: 7px; }          /* Mud: 8px */
.mud-list-item-dense { padding-top: 3.5px; padding-bottom: 3.5px; }/* Mud: 4px */
.mud-list-item-gutters { padding-left: 14px; padding-right: 14px; }/* Mud: 16px */
.mud-alert-message { padding: 7.875px 0; }                         /* Mud: 9px 0 — scaled, never zeroed */
.mud-day { width: 31.5px; height: 31.5px; }                        /* Mud: 36px */
.mud-picker-month { height: 52.5px; }                              /* Mud: 60px */
.mud-picker-content { min-width: 271.25px; min-height: 266.875px; }/* Mud: 310px / 305px */
.mud-picker-static { min-width: 271.25px; }                        /* Mud: 310px */
.mud-picker-month-container { width: 271.25px; }                   /* Mud: 310px */
.mud-picker-calendar-container { width: 271.25px; }                /* Mud: 310px */
.mud-picker-toolbar { height: 87.5px; }                            /* Mud: 100px */
.mud-picker-year-container { height: 262.5px; }                    /* Mud: 300px */
.mud-picker-calendar-transition { margin-top: 10.5px; min-height: 189px; } /* Mud: 12px / 216px */

/* =========================================================================
 * app.css - TABLE OF CONTENTS
 *
 * 01. Base & design tokens .......... html/body reset + :root custom properties
 * 02. Typography & body text ........ font stack + .mud-typography-* sizing
 * 03. Auth: loading splash & login
 * 04. Layout: app shell & sidebar
 * 05. Layout: topbar & user menu
 * 06. Layout: page stack & cards
 * 07. Table toolbar & filters
 * 08. Data tables
 * 09. Table: columns menu, expand rows & pager
 * 10. Cell components: utilization, role pill
 * 11. Buttons: row actions & soft buttons
 * 12. Roles & permissions
 * 13. Forms: grid layout & fields
 * 14. Filter: status select
 * 15. Forms: native controls & input sizing
 * 16. Searchable select & dropdown panel
 * 17. Forms: status toggle & form actions
 * 18. Responsive (@media)
 * 19. Form focus states & date/time pickers
 * 20. Dialogs
 * 21. Blazor error UI
 * 22. Dark mode (token reassignment + global overrides; component-specific dark
 *     overrides are co-located with their component, not gathered here)
 * 23. Features: version, auto-refresh, update overlay & settings drawer
 * 24. Board pane .................... the card recipe /welcome wears
 * 25. (removed 2026-10-23 — the template's master-detail board; no Pulsar page used it)
 * 26. (removed 2026-10-23 — the template's month calendar; no Pulsar page used it)
 *
 * Conventions:
 *   - Design tokens live in :root (section 01) and are reassigned on body.app-dark.
 *   - MudBlazor is imported into the `mudblazor` cascade layer (top of file), so unlayered app
 *     rules here win over .mud-* WITHOUT !important. Only keep !important against MudBlazor when
 *     the competing .mud-* declaration is itself !important, or when Mud sets the value via an
 *     inline style (e.g. drawer width, popover position) — layers beat neither. Always check the
 *     competing .mud-* rule before adding or removing !important.
 *   - Comments tagged "Deploy-window fallback" guard the brief window where a stale
 *     cached WASM bundle renders old markup before the update overlay reloads.
 * ========================================================================= */

/* =========================================================================
 * 01. BASE & DESIGN TOKENS
 * ========================================================================= */

html,
body,
#app {
    min-height: 100%;
}

/* GLOBAL UI DENSITY, rem half [developer-directed 2026-09-03]: 1rem = 14px (16 × 0.875).
   The retired --app-zoom transform is replaced by real lengths — this declaration scales every
   rem in this file AND MudBlazor's rem-based icons/typography, and every px literal below was
   rescaled ×0.875 in the same pass. Full story + the true-space exceptions: the --app-zoom
   comment in §01. Retune the density by changing BOTH this percentage and the px literals —
   or temporarily via --app-zoom, which still works (but compounds with the bake). */
html {
    font-size: 87.5%;
}

/* SCROLLBARS - ONE definition for the whole app and BOTH themes [developer-directed
   2026-09-02]. Every scroller reads the same two tokens, and `body.app-dark` re-points those
   tokens rather than restating the rule, so there is no per-theme scrollbar rule anywhere.
   ⚠️⚠️ THE SELECTORS ARE NOT A STYLE CHOICE - they are the minimum that beats MudBlazor.
   MudThemeProvider injects a <style> INSIDE <body> (so after this file, whatever we do) that
   reads, verbatim:
       ::-webkit-scrollbar {width: 8px; height: 8px; z-index: 1;}
       ::-webkit-scrollbar-track {background: transparent;}
       ::-webkit-scrollbar-thumb {background: #c4c4c4; border-radius: 1px;}
       ::-webkit-scrollbar-thumb:hover {background: #a6a6a6;}
       html, body * {scrollbar-color: #c4c4c4 transparent; scrollbar-width: thin;}
   That last line is why --app-scrollbar-thumb had reached exactly TWO elements in this app
   since the port: `body *` is (0,0,2) and later in the document, so it beat every plain
   `*` / `div` / `body *` rule written here - measured, a scroller reported Chrome's own
   rgb(196, 196, 196) on both themes while <body> itself wore the token. So:
     - `html body *` is (0,0,3), one step past Mud's `body *`;
     - `:root:root` is (0,2,0), past Mud's `html` - this file's existing idiom for
       out-specifying an injected sheet (see the MudBlazor note in §22);
     - and the webkit rules below take the same prefix, since Mud's are bare (0,0,1) pseudos.
   ⚠️ Mud's `scrollbar-width: thin` and its 8px rail are deliberately LEFT ALONE - only the
   colour is ours. Turning the whole block off (`<MudThemeProvider DefaultScrollbar="true" />`)
   would take the width with it and fatten every scrollbar in the app.
   ⚠️ And a scroller using EITHER standard property gets Chrome's STANDARD scrollbar, which
   ignores the ::-webkit-* pseudos - Mud sets `scrollbar-width` on everything, so on Chrome
   the webkit rules are inert and only `scrollbar-color` is doing the work. They are kept for
   Safari, which has no scrollbar-color.
   A scroller that genuinely wants its own colour states it at a higher specificity and wins. */
:root:root,
html body,
html body * {
    scrollbar-color: var(--app-scrollbar-thumb) var(--app-scrollbar-track);
}

html body *::-webkit-scrollbar-thumb {
    background: var(--app-scrollbar-thumb);
    border-radius: var(--app-radius-pill);
}

html body *::-webkit-scrollbar-thumb:hover {
    background: var(--app-scrollbar-thumb-hover);
}

html body *::-webkit-scrollbar-track {
    background: var(--app-scrollbar-track);
}

/* The PAGE bar is painted from <html>, which sits OUTSIDE body.app-dark - so the var() the
   rule above resolves there would pick up the LIGHT palette. Re-point the two tokens on
   <html> itself and that rule does the rest. */
html:has(body.app-dark) {
    --app-scrollbar-thumb: var(--app-scrollbar-dark-thumb);
    --app-scrollbar-thumb-hover: var(--app-scrollbar-dark-thumb-hover);
}

/*
 * SIDEBAR DARK MODE (original) — restore these to revert:
 *   --app-sidebar: #111f44
 *   --app-sidebar-deep: #0c1734
 *   --app-sidebar-muted: #8d9abd
 *   .app-sidebar color: #dbe3ff
 *   .app-sidebar border-inline-end: 0
 *   .sidebar-brand border-bottom: rgba(255,255,255,0.06)
 *   .sidebar-brand-link:hover background: rgba(255,255,255,0.04)
 *   .brand-title color: #ffffff
 *   .sidebar-link color: #b0b7e1
 *   .sidebar-link:hover background: rgba(255,255,255,0.10), color: #ffffff
 *   .sidebar-link.active background: rgba(108,92,231,0.25), color: #ffffff
 */
:root {
    /*
     * Color tokens — light mode defaults.
     * Surfaces: --app-bg, --app-card, --app-surface-raised, --app-surface-subtle
     * Text: --app-text, --app-muted, --app-icon-muted, --app-on-primary
     * Borders: --app-border, --form-control-border
     * Brand: --app-primary, --app-primary-soft
     * States: --app-danger, --app-warning, --app-info, --status-on/off
     * Tables: --app-table-head, --app-table-column-name, --app-table-row-hover
     * Role aliases: --app-color-form-box-*, --app-color-filter-box-*
     * Dark mode reassigns all tokens on body.app-dark.
     */
    --mud-typography-button-text-transform: none;
    --mud-palette-primary: var(--app-primary);
    --mud-palette-primary-rgb: 72, 149, 239;
    --mud-palette-primary-text: var(--app-on-primary);
    --app-bg: #eaedf4;
    --app-sidebar: #131d3f;
    --app-sidebar-deep: #0e152e;
    --app-sidebar-text: #a3aed1;
    --app-sidebar-muted: #a3aed1;
    /* The menu's two body tones. A page (child) is the thing you aim at, so it wears the lifted,
       near-neutral tone. The group header that holds it is NOT simply dimmer — a header darker
       than its own children reads as disabled — it is brand-TINTED instead: a desaturated step
       down from the --app-primary its section header wears, so the three levels read
       saturated → tinted → neutral rather than dark → darker → light. */
    --app-sidebar-text-parent: #85abda;
    --app-sidebar-text-child: #d7e0f5;
    --app-primary: #4895ef;
    --app-primary-soft: #e8f4ff;
    /* The brand at a CONTROL’s depth: the fill a solid control wears (the status toggles’
       saturation), as against --app-primary, which is the brand as TEXT/edge and is lifted in
       dark so it can be read on a dark card. In light the two are the same colour — it already
       carries white ink; only dark needs its own value, or a solid pill paints washed-out.
       ⚠️ Restated in every dark block: a token composed from another token bakes the value it
       saw where it was DECLARED, so inheriting this one into dark would carry the LIGHT brand. */
    --app-primary-solid: var(--app-primary);
    /* The resting EDGE of a control in the solid-brand family: --app-primary-solid at 29%
       [developer-directed 2026-09-05]. A neutral --app-border-muted edge made an unpicked form tab
       read as unrelated furniture beside the picked one; a brand edge at low alpha says "same
       control, not chosen" without becoming a second signal.
       ⚠️ RESTATED in every dark block, exactly like the token it is mixed from: a composed token
       substitutes its var()s on the element that DECLARES it, so a :root-only declaration would
       carry the LIGHT brand under body.app-dark. */
    --app-primary-edge: color-mix(in srgb, var(--app-primary-solid) 29%, transparent);
    --app-border: #e1e6f1;
    --app-border-muted: #b9c2d4;
    --app-text: #1f2937;
    --app-muted: #697386;
    --app-text-disabled: #566176;
    --app-icon-muted: #9ca3af;
    --app-placeholder: #9ca3af;
    --app-on-primary: #f5f7fb;
    /* NOTHING IS PURE WHITE [developer-directed 2026-09-06]. The card and every box on it
       were #ffffff on a near-white ground, so light mode read as one sheet of white and a
       card was raised only by its shadow. The whole ladder came down a notch and the
       RELATIONSHIPS are untouched, which is what keeps this a retune and not a redesign: a
       box (input, dropdown panel) sits a step ABOVE the card, the card a clear step above
       the ground, every subtle/hover/active fill a step below the card. The card-over-ground
       step actually GREW, so a card reads as lifted more plainly than it did while white.
       ⚠️ --app-surface-raised is the highest surface in the app and deliberately stays
       near-white: it paints the boxes you TYPE IN, and taking it further down either merges
       it into the card or collides with the read-out/disabled shades below it. A genuinely
       grey field means INVERTING the ladder (a sunken field) - a different design.
       ⚠️ The SIDEBAR is not in this list: it is navy in both themes here.
       ⚠️ --app-surface-inset has no consumer and no body.app-dark twin; it is retuned only
       so the ladder stays coherent if something ever reads it.
       Ported from ElitaPro, whose own values live in its theme-modern.css layer. */
    --app-card: #f7f8fc;
    --app-surface-raised: #fcfdff;
    --app-surface-subtle: #f1f3f9;
    --app-surface-accent: #ebeefb;
    --app-surface-hover: #e9ecf2;
    --app-surface-active: #dde2ec;
    --app-surface-inset: #ecedf1;
    --app-topbar-bg: var(--app-card);
    --app-topbar-muted: #6b7280;
    --app-topbar-muted-dark: #8a94b8;
    --app-link: #2f7fd4;
    --app-link-hover: #9ecbff;
    --app-danger: #ef4444;
    --app-danger-soft: #fde8ea;
    --app-danger-hover-bg: rgba(239, 68, 68, 0.2);
    /* Mint (avatar palette index 2) — used by the "סינון זהה" filter-by-value button. */
    --app-mint: #00cfa5;
    --app-mint-soft: #d9f6f0;
    /* Resting icon colour: mint softened so it reads less saturated (hover stays solid). */
    --app-mint-muted: rgba(0, 207, 165, 0.85);
    /* --- Tints (alpha overlays on semantic colors) --- */
    --app-primary-tint-subtle: rgba(72, 149, 239, 0.08);
    --app-primary-tint: rgba(72, 149, 239, 0.16);
    --app-primary-tint-strong: var(--app-primary-tint);
    --app-danger-tint-subtle: rgba(239, 68, 68, 0.08);
    --app-danger-tint: rgba(239, 68, 68, 0.12);
    --app-danger-tint-strong: var(--app-danger-hover-bg);
    --app-green-tint-toolbar: rgba(3, 181, 98, 0.22);
    --app-link-tint: rgba(109, 180, 255, 0.12);
    --app-warning-tint: rgba(255, 169, 9, 0.18);
    --app-neutral-soft: rgba(156, 163, 175, 0.2);
    --app-warning: rgb(255, 169, 9);
    --app-warning-soft: #fff3cd;
    --app-banner-warning-bg: #fbf5ee;
    /* Indigo — the impersonate row action; same hue as the roles-form "אינדיגו" swatch
       (UserProfileImages.AvatarColors[11]). */
    --app-indigo: #3d5afe;
    --app-indigo-soft: rgba(61, 90, 254, 0.12);
    --app-info: rgb(15, 188, 249);
    --app-info-soft: rgba(15, 188, 249, 0.1);
    --app-export-excel: #107c41;
    --app-export-pdf: #e53935;
    --app-export-print: #1976d2;
    --app-export-email: #f57c00;
    --app-table-head: rgb(238, 241, 248);
    /* THE column-name ink - every table head in the app reads this ONE token: the list
       pages' MudTable heads, the inline line grids and any hand-rolled column header. Its
       dark twin is in the body.app-dark block, and those TWO declarations are the whole
       control - change a column-name colour in one place per theme.
       [renamed from --app-table-head-text 2026-09-03; the head FILL is --app-table-head] */
    --app-table-column-name: #010101;
    --app-table-row-hover: rgba(72, 149, 239, 0.06);
    /* Zebra stripe for list tables — softer than row-hover so hover still reads
       on striped rows. Retune this token (and its dark twin) to restyle stripes. */
    --app-table-row-stripe: rgba(72, 149, 239, 0.035);
    --app-table-expanded: var(--app-surface-subtle);
    --app-table-sort-active: #4f46e5;
    --app-scrollbar-thumb: rgba(100, 116, 139, 0.4);
    --app-scrollbar-thumb-hover: rgba(100, 116, 139, 0.6);
    /* The scroll TRACK is transparent on both themes - the thumb alone says a box scrolls.
       Tokenised so a theme can paint it without touching a rule. */
    --app-scrollbar-track: transparent;
    --app-brand-gradient-start: #8b5cf6;
    --app-brand-gradient-end: #38bdf8;
    --app-color-form-box-bg: var(--app-surface-raised);
    --app-color-form-box-text: var(--app-text);
    --app-color-form-box-border: var(--form-control-border);
    --app-color-filter-box-bg: var(--app-surface-raised);
    --app-color-filter-label: var(--app-primary);
    --app-color-filter-label-bg: var(--app-card);
    /* Floating field label — the name notched on a box's top border. Shared by the
       SearchableNativeSelect (Label=) pickers AND the AppDatePicker (Label=) date boxes so a
       picker label and a date label in the same parameter row render at the exact same size,
       colour, inset and notch. See .report-date-filters for the row they were built for. */
    --app-field-label-size: var(--app-text-form-label);
    --app-field-label-color: var(--app-muted);
    --app-field-label-color-active: var(--app-primary);
    --app-field-label-bg: var(--app-card);
    --app-field-label-inset: 0.6rem;
    --app-field-label-pad-x: 0.35rem;
    --app-color-page-button-hover-text: var(--app-on-primary);
    --app-radius: 4.375px;
    --app-radius-pill: 874.125px;
    --app-radius-circle: 50%;
    /* Aliases onto --app-radius, so ONE GROUP can be retuned without a 15-rule grep. They are a
       ROUTING layer, not a ladder of values — never give a rung its own number, that fakes a
       scale nobody designed. A group earns an alias when it is retuned INDEPENDENTLY of the
       app's general radius: boxes and buttons are, surfaces are the baseline itself.
       Buttons are TWO tokens on purpose — one value chosen to soften a ~97x32 labelled button
       puts a 25px square icon at ratio ~0.4, a blob.
       Rules + the traps: _dev-rules/ui-conventions.md §Corner radius. */
    --app-radius-form-box: var(--app-radius);
    --app-radius-button: var(--app-radius);
    --app-radius-icon-button: var(--app-radius);
    /* --- Shadows (elevation sm → xl, then inset) --- */
    --app-shadow-ink: 17, 31, 68;
    --app-shadow-ink-alt: 15, 23, 42;
    --app-shadow: 0 3.5px 21px rgba(var(--app-shadow-ink), 0.09);
    --app-shadow-popover: 0 7px 28px rgba(var(--app-shadow-ink), 0.14);
    --app-shadow-dropdown: 0 7px 21px rgba(var(--app-shadow-ink-alt), 0.12);
    --app-shadow-menu: 0 19.25px 48.125px rgba(var(--app-shadow-ink), 0.16);
    --app-shadow-dialog: 0 21px 61.25px rgba(var(--app-shadow-ink), 0.2);
    --app-shadow-login-card: 0 10.5px 35px rgba(var(--app-shadow-ink-alt), 0.12);
    --app-shadow-topbar-user: 0 7px 17.5px rgba(72, 149, 239, 0.08);
    --app-shadow-brand: 0 8.75px 19.25px rgba(72, 149, 239, 0.25);
    --app-shadow-avatar-hover: 0 5.25px 14px rgba(79, 70, 229, 0.28);
    --app-shadow-inset-swatch: inset 1px 1px 2px rgba(var(--app-shadow-ink-alt), 0.18);
    --app-shadow-inset-pressed: inset 1px 1px 2.625px rgba(var(--app-shadow-ink-alt), 0.18);
    /* The toggle inset is stated as GEOMETRY + ink, because two rules need the geometry without
       the ink: a control that FADES this shadow in must start from a shadow of the same size, or
       the offsets ramp up from zero and the shadow reads as arriving late — see .entity-form-tabs
       [developer-reported 2026-09-04]. The geometry is identical in every theme (only the ink
       strength adapts), so it is declared ONCE here and every restatement below reads it. */
    --app-shadow-inset-deep-geometry: inset 2px 2px 4.375px;
    --app-shadow-inset-deep: var(--app-shadow-inset-deep-geometry) rgba(var(--app-shadow-ink-alt), 0.22);
    /* One step deeper than --app-shadow-inset-deep: the "pushed further in" state of a control
       that already sits in a well (the picked form tab under the pointer). Composed off the ink
       like its neighbours, so the theme layer’s own ink wins on :root — and RESTATED literally in
       both dark blocks for the reason spelled out on --app-shadow-inset-deep there. */
    --app-shadow-inset-sunken: inset 3px 3px 7px rgba(var(--app-shadow-ink-alt), 0.32);
    --app-shadow-inset-compact: inset 0.5px 0.5px 1.5px rgba(var(--app-shadow-ink-alt), 0.16);
    --app-shadow-elevation-sm: 0 2px 4.375px rgba(var(--app-shadow-ink-alt), 0.28);
    --app-shadow-switch: 0 1px 2.625px rgba(0, 0, 0, 0.2);
    --app-shadow-error-bar: 0 -1px 2px rgba(0, 0, 0, 0.2);
    --app-shadow-invalid-ring: 0 0 0 2.625px rgba(211, 47, 47, 0.12);
    /* --- Overlays & sidebar glass --- */
    --app-overlay-scrim: rgba(var(--app-shadow-ink-alt), 0.52);
    --app-overlay-dialog: rgba(var(--app-shadow-ink-alt), 0.55);
    --app-overlay-drawer: rgba(0, 0, 0, 0.26);
    --app-sidebar-divider: rgba(255, 255, 255, 0.06);
    /* The drawer's edge toward the content, and the brand row's own vertical divider.
       Both are tokens so a skin can move them; the defaults keep today's look exactly.
       The drawer's full-height edge is the ONLY owner of the menu's content-facing line.
       WARNING: the brand row used to draw `inset -1px 0 0` and call it that seam - an inset
       shadow with a NEGATIVE x-offset paints the RIGHT inner edge, which in this RTL app is the
       VIEWPORT's outer edge, not the menu's. It separated nothing. `none` is the honest
       default; the drawer edge below is where a skin asks for that line. */
    --app-sidebar-edge: 0;
    --app-sidebar-brand-edge: none;
    --app-sidebar-hover-bg: rgba(255, 255, 255, 0.10);
    --app-sidebar-hover-subtle: rgba(255, 255, 255, 0.04);
    /* The sidebar is dark in BOTH themes, so its row states are declared once here and never
       reassigned on body.app-dark. The active row used to be left to MudBlazor, whose default
       IS theme-dependent — a light-grey lift in dark mode but black 6% in light, which is
       invisible on navy, so the current page lost its pill the moment you left dark mode.
       The value is that dark-mode lift, pinned. */
    --app-sidebar-active-bg: rgba(154, 166, 199, 0.08);
    /* The label colour of the current page. Unlike the fill above this one IS theme-dependent —
       mint in light, violet in dark — so it is reassigned on body.app-dark even though the
       sidebar surface itself does not change. */
    --app-sidebar-active-color: #4ac8a5;
    --app-glass-hover: rgba(255, 255, 255, 0.05);
    --app-glass-hover-strong: rgba(255, 255, 255, 0.10);
    --app-glass-border: rgba(255, 255, 255, 0.22);
    --app-glass-subtle: rgba(255, 255, 255, 0.06);
    --app-disabled-bg: rgba(0, 0, 0, 0.12);
    --app-disabled-text: rgba(0, 0, 0, 0.38);
    /* Disabled form boxes: a clearly sunken fill + dimmed text; the stacked label above a
       disabled box reuses the text token. Dark reassigns both on body.app-dark. */
    --app-input-disabled-bg: #e2e7f0;
    --app-input-disabled-text: var(--app-icon-muted);
    /* Informative (read-out) boxes sit a shade off the disabled fill in light
       [developer-directed 2026-08-11]; dark reuses the disabled fill unchanged. */
    --app-input-readout-bg: #eeeef0;
    /* The DARK scrollbar values are declared at :root, not only on body.app-dark, because the
       page scrollbar is painted from <html> - which sits outside body and would resolve a
       var() against this LIGHT palette. body.app-dark aliases --app-scrollbar-thumb to these,
       so the page bar and every in-page bar are one value.
       A SOLID navy step, not the white wash it replaced [developer-directed 2026-09-02]: rgba
       white over the card composited to a pale grey (~#353846) that read LIGHTER than every
       surface it slid over. These sit between --app-card (#151d31) and --app-surface-raised. */
    --app-scrollbar-dark-thumb: #2a3252;
    --app-scrollbar-dark-thumb-hover: #3b456b;
    --status-on: #52b152;
    --status-off: #e84c4c;
    /* Ended without a good end (a document rejected or cancelled): a slate grey at the same
       weight as the green / red above, so white pill text reads the same on all three. */
    --status-ended: #8b95a7;
    --app-green: rgb(3, 181, 98);
    --app-green-soft: rgba(3, 181, 98, 0.15);
    --app-magenta: #d500f9;
    --app-magenta-soft: rgba(213, 0, 249, 0.12);
    --app-tooltip-bg: #616161;
    --app-tooltip-color: var(--app-on-primary);
    --app-tooltip-font-size: 11px; /* true-space: tooltips render outside the shell — px, so the 87.5% root font does not shrink them */
    --app-tooltip-font-weight: 600;
    --app-tooltip-line-height: 1.35;
    --app-tooltip-padding: 4px 8px;
    --app-tooltip-radius: 3.5px;
    /* Subtract from Mud/default sizes (e.g. page titles). Tune in one place. */
    --app-type-shift: -7px;
    /*
     * GLOBAL UI DENSITY, zoom half — RETIRED TO 1 [developer-directed 2026-09-03].
     * The app used to render under a CSS `zoom` transform. The density is now BAKED INTO the
     * stylesheet instead: `html { font-size: 87.5% }` (top of §01) scales every rem — ours and
     * MudBlazor's — and every px literal in this file was rescaled ×0.875 in the same pass, so
     * the rendered size is unchanged from a 0.88 zoom (±0.6%) while a px is once again a real
     * device px.
     *
     * What retiring the zoom buys, permanently:
     *   - ONE coordinate system. getBoundingClientRect, scrollHeight and CSS px agree again,
     *     so <Virtualize> works inside the shell (it was unusable — spacer drift), and the
     *     currentCSSZoom divisions in searchableSelect.js are inert.
     *   - No border flooring: a 3px border paints 3 device px (under the zoom it painted 2).
     *   - Media queries and the visual size agree (the narrow breakpoints used to trip late).
     *
     * The token and every `calc(… / var(--app-zoom))` compensation are KEPT, inert at 1, so
     * the zoom mechanism stays a one-variable change if it is ever wanted back — but note that
     * lowering it now DOUBLE-shrinks (the ×0.875 bake would still be in the file).
     *
     * ⚠️ TRUE-SPACE exceptions — authored against the real viewport (outside the old zoom
     * roots) and therefore NOT rescaled in the bake: @media breakpoint conditions,
     * .mud-popover-relative-width's max-height, .row-dialog-frame's border, and the
     * --app-tooltip-* tokens (pinned in px above). Everything else below is written at face
     * value: what you type is what paints.
     */
    --app-zoom: 1;
    /* In-scope typography groups — shared scale 13 / 12 / 11 px (D / T / M). */
    --app-text-scale: 11.375px;
    --app-text-form-box: var(--app-text-scale);
    --app-text-form-label: var(--app-text-scale);
    --app-text-form-status: var(--app-text-scale);
    --app-text-filter-box: var(--app-text-scale);
    --app-text-page-button: var(--app-text-scale);
    --app-text-menu: var(--app-text-scale);
    --form-field-width: 218.75px;
    --form-label-gap: 1px;
    --toolbar-search-width: 245px;
    --toolbar-filter-width: 175px;
    --toolbar-header-button-width: calc(7.5rem - 8.75px);
    --app-topbar-height: 56px;
    /* Sidebar width — 16rem / 2^8, the standard drawer step on the 8-pt grid. Restated here (rather
       than left to Mud's 240px default) because the tree's indents are measured against it. */
    /* The gap between the shell's floating cards (brand, menu, topbar, page) and around them
       [developer-directed 2026-10-22, from YsKarton] — the page card's own inset from the window
       (--app-content-pad-inline). See THE FLOATING SHELL in §04. */
    --app-shell-gap: 13.125px;
    /* The drawer = the 224px card + the gap on its outer side (see .sidebar-panel), so the
       card keeps the width the tree's indents were measured against. The 56px rail likewise. */
    --app-sidebar-width: calc(224px + var(--app-shell-gap));
    --app-sidebar-mini-width: calc(56px + var(--app-shell-gap));
    --toolbar-date-filter-width: 148.75px;
    --toolbar-status-filter-width: 126.875px;
    --toolbar-filter-search-width: 192.5px;
    --form-control-height: 31.5px;
    --entity-form-control-height: 28.875px;
    --form-control-border: #d1d8e5;
    --form-control-focus-border: var(--app-primary);
    --form-control-focus-shadow: 0 0 0 2.625px rgba(72, 149, 239, 0), 0 0 10.5px rgba(72, 149, 239, 0.2);
    /* Shared invalid-state border for every form box (text, select, numeric,
       avatar, roles) — identical in light and dark mode. */
    --form-control-invalid-border: #d32f2f;
    --form-control-invalid-shadow: 0 0 0 1px rgba(211, 47, 47, 0.15);
    /* px — tight-viewBox SVG so background-size = visible triangle size */
    --dropdown-caret-size: 8.75px;
    --dropdown-caret-icon-size: 19.25px;
    --dropdown-caret-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M0 0l6 8 6-8z' fill='%239ca3af'/%3E%3C/svg%3E");
    /* Tick glyph for native checkboxes styled to match MudBlazor's (see .app-checkbox) —
       white in both themes, since the checked box is filled with --app-primary. */
    --checkbox-tick-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.2 8.4l3.1 3.2 6.5-7' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    /* Clear (×) icon size + click target in SearchableNativeSelect */
    --dropdown-clear-size: 10.5px;
    --dropdown-clear-hit-size: 22.75px;
    --dropdown-clear-gap: 0px;
    --dropdown-clear-offset-right: calc(0.25rem + var(--dropdown-caret-size) + 10.5px + var(--dropdown-clear-gap));
    --dropdown-clear-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M1.5 1.5l9 9M10.5 1.5l-9 9' stroke='%239ca3af' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
    --dropdown-clear-hover-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M1.5 1.5l9 9M10.5 1.5l-9 9' stroke='%23e53935' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
    --toolbar-control-height: 31.5px;
    /* The row-action icon button (edit / delete / add / impersonate / the toolbar's copy,
       filter-by-value and reset). Its own ladder — 26.25 desktop, 24.5 tablet, and 22.75 for a
       data-grid's row-actions cell only — is stated on these two tokens so the square and the
       glyph inside it can never step apart. ⚠️ .resource-row-actions' buttons are a DIFFERENT
       family that happens to share the base value and has no ladder at all; left alone. */
    --action-btn-size: 26.25px;
    --action-btn-icon-size: 1.125rem;
    --toolbar-floating-control-height: 42px;
    --toolbar-band-height: 52.5px;
    --toolbar-label-gap: 0.5rem;
    /* --- Control borders ------------------------------------------------
       The theme-level switch for "does a control have a visible edge?".
       0% = no edge. 100% = every control that opts in draws a 1px edge in ITS OWN colour.
       Anything between is a softened edge, so a quiet outline needs no new colour token.
       Three names, and a fourth is drift:
         --app-control-border-strength  the app-wide SWITCH (this token), a <percentage>
         --control-accent               the colour THIS control speaks in, per variant
         --control-border-color         optional per-part override of the colour itself
       WARNING: THE SWITCH IS A PERCENTAGE, NOT A COLOUR. The obvious version
       (--app-control-border-color: var(--control-accent) on :root) is BROKEN and fails
       SILENTLY: a custom property containing var() is substituted on the element where it
       is DECLARED, and :root has no --control-accent, so the switch computes to the
       guaranteed-invalid value, that value INHERITS, and every border-color using it
       becomes invalid-at-computed-value-time -> currentColor. It looks right for as long
       as every soft button's ink IS its accent; a white-inked status pill exposes it.
       WARNING: the WIDTH is deliberately NOT a token - a width that changes moves geometry
       (a bordered control eats 2px of its content box, and under --app-zoom the browser
       floors a border to whole device pixels while padding pays the exact fraction).
       WARNING: read it through the three LONGHANDS, never `border: 1px solid var(--x)`:
       one undefined var makes the whole SHORTHAND invalid and the border disappears. */
    --app-control-border-strength: 0%;
}

/* The framework paints corners we do not own. MudBlazor rounds its popovers/menus, the MudTable's
   own outer corner + its two head end-cells, and the dialog's title/content/actions sections from
   --mud-default-borderradius (its default 4px). Every one of them is a SURFACE, so it follows
   --app-radius rather than a number that only looks right by coincidence. Measured blast radius
   (retarget the token at runtime and count what moves): ~9 elements on a list page, ~13 with a
   dialog open, all of that kind. Re-run that count on a MudBlazor upgrade.
   ⚠️ `:root:root`, and it is NOT a hack: MudThemeProvider INJECTS a `<style>` block at runtime
   holding `:root { --mud-default-borderradius: … }`. That block is unlayered and lands AFTER
   app.css in the document, so a plain `:root` ties on specificity and loses on source order — the
   declaration then does nothing at all, silently (getComputedStyle is the only thing that catches
   it). Doubling the pseudo-class takes specificity to (0,2,0) and wins wherever the injected block
   sits. The cascade layer that tames the rest of MudBlazor does not help: the injected style is
   not in the layer. */
:root:root {
    --mud-default-borderradius: var(--app-radius);

    /* Mud's own PAPER follows the app card [2026-09-06]. MudBlazor paints a MudTable's
       paper, a dialog, a menu and a popover from --mud-palette-surface, and its injected
       palette leaves that at pure #ffffff in light - so after the surface retune above those
       were still WHITE inside a grey card (measured: .mud-table and .mud-dialog both
       rgb(255,255,255) on a rgb(247,248,252) card). This one line is what carries the retune
       into everything MudBlazor draws. It belongs in THIS block for the same reason the
       border-radius above does: a plain :root loses to Mud's injected :root on source order.
       ⚠️ RESTATED on body.app-dark below, and that is NOT optional: a var() inside a custom
       property resolves on the element that DECLARES it, so this copy alone would hand the
       dark theme the LIGHT card. Dark's rendered value does not change either way -
       AppTheme.cs already sets PaletteDark.Surface to the dark card.
       ⚠️ --mud-palette-background / -background-gray are deliberately NOT mapped: the shell
       paints the page ground itself, so they render nowhere, and AppTheme.cs owns them. */
    --mud-palette-surface: var(--app-card);
}

body,
.mud-layout,
.mud-typography,
.mud-button-root,
.mud-input-root,
.mud-table-cell,
.native-form-control,
.searchable-native-option {
    font-family: "Plus Jakarta Sans", "Segoe UI", Arial, sans-serif;
}

body {
    margin: 0;
    background: var(--app-bg);
    color: var(--app-text);
    font-size: 10.5px;
    font-weight: 400;
    line-height: 1.5;
    letter-spacing: normal;
    -webkit-font-smoothing: auto;
    -moz-osx-font-smoothing: auto;
}

/* =========================================================================
 * 02. TYPOGRAPHY & BODY TEXT
 * ========================================================================= */
/* App-wide Mud typography: 8px smaller than library defaults.
   No !important needed — .mud-typography-* are normal declarations in the layered MudBlazor
   stylesheet, so these unlayered rules win on the cascade. */
.mud-typography-h1 {
    font-size: max(2.5rem, calc(6rem + var(--app-type-shift)));
}

.mud-typography-h2 {
    font-size: max(2rem, calc(3.75rem + var(--app-type-shift)));
}

.mud-typography-h3 {
    font-size: max(1.75rem, calc(3rem + var(--app-type-shift)));
}

.mud-typography-h4:not(.page-title) {
    font-size: max(1.25rem, calc(2.125rem + var(--app-type-shift)));
}

.mud-typography-h5 {
    font-size: max(1.125rem, calc(1.5rem + var(--app-type-shift)));
}

.mud-typography-h6 {
    font-size: 12.25px;
}

.mud-typography-subtitle1 {
    font-size: max(0.875rem, calc(1rem + var(--app-type-shift)));
}

.mud-typography-subtitle2 {
    font-size: max(0.8125rem, calc(0.875rem + var(--app-type-shift)));
}

.mud-typography-body1 {
    font-size: 10.5px;
    font-weight: 400;
    letter-spacing: normal;
}

.mud-typography-body2 {
    font-size: 11.375px;
    font-weight: 400;
    letter-spacing: normal;
}

.mud-typography-button {
    font-size: 11.375px;
}

.mud-typography-caption {
    font-size: max(0.6875rem, calc(0.75rem + var(--app-type-shift)));
}

/* MudAlert: the letters sit on the icon's centre line, in every alert [developer-directed
   2026-10-19]. Mud top-aligns the icon (35px) and the message (31.5px), which left the letters
   1.1–2.6px above the icon, a different amount at each width. Centring the two boxes does not
   fix it: Chromium floors a line's half-leading to whole pixels, so where the letters land inside
   their line box changes with every font size and line height (measured 0.3–0.9px off).
   So the row aligns on the text BASELINE. The icon gets an empty spacer, centred on the svg, whose
   bottom edge is the icon's baseline: icon centre + half the letter height. The text's baseline
   lands there, so the letters are centred on the icon at any size, and on the FIRST line when
   the text wraps. 0.609em = the Hebrew letter body (Segoe UI: ascent 0.625 − descent 0.016,
   measured). The icon inherits the alert's font so that em is the text's; the svg sizes itself.
   A single-line message (31.5px) still fits inside the icon (35px), so box centre = icon centre. */
.mud-alert-position {
    align-items: baseline;
}

.mud-alert .mud-alert-icon {
    font-size: inherit;
}

.mud-alert-icon::before {
    content: "";
    align-self: center;
    height: 0.609em;
}


/* MudAlert: every plain (Variant.Text) alert has an edge in its own severity colour [developer-
   directed 2026-11-01]. currentColor is the alert's ink, so the border follows the severity - red
   error, amber warning, green success, blue info - with no per-severity rule. Outlined alerts keep
   Mud's own border and Filled ones keep none. An alert that is deliberately edgeless (the change
   banner, a page hint) sets `border: none` on `.x.mud-alert` LATER in this file, which wins on
   source order at the same (0,2,0) - so keep this rule up here. */
.mud-alert[class*="mud-alert-text-"] {
    border: 1px solid color-mix(in srgb, currentColor 55%, transparent);
}

/* =========================================================================
 * 03. AUTH: LOADING SPLASH & LOGIN
 * ========================================================================= */
.app-loading {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: "Plus Jakarta Sans", "Segoe UI", Arial, sans-serif;
}

.app-loading-card {
    padding: 1.5rem 2rem;
    border-radius: var(--app-radius);
    background: var(--app-card);
    box-shadow: var(--app-shadow-login-card);
}

.login-page {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    background-color: var(--app-sidebar);
}

/* The form column wears YsKarton's login colour - its deep navy sidebar [developer-directed
   2026-10-22]. A literal, not var(--app-sidebar): the modern theme paints that token pale, which
   is the plain gray page this replaced. Same in both themes - the card already flips. */
.login-page {
    background-color: #131d3f;
}

/* Split screen (Login / Forgot / Reset), copied from YsKarton. DOM order = form first, so in
   RTL the form column sits on the right and the illustration (made with Canva) fills the left;
   in LTR it mirrors. Below 900px the illustration is hidden and only the card shows. */
.login-split {
    display: grid;
    grid-template-columns: minmax(440px, 38%) 1fr;
    min-height: 100vh;
}

.login-split-form {
    min-width: 0;
}

.login-split-image {
    position: relative;
    background: #131d3f url("../images/brand/login-art.jpg") center / cover no-repeat;
}

/* Soft fade from the form panel into the illustration, so the seam is not a hard line. */
.login-split-image::before {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to left, transparent 82%, rgba(0, 0, 0, 0.3));
}

[dir="rtl"] .login-split-image::before {
    background: linear-gradient(to right, transparent 82%, rgba(0, 0, 0, 0.3));
}

@media (max-width: 899.98px) {
    .login-split {
        grid-template-columns: 1fr;
    }

    .login-split-image {
        display: none;
    }
}

.login-card {
    width: 100%;
    max-width: 367.5px;
    border-radius: var(--app-radius);
}

.login-page .user-form-field {
    width: 100%;
}

/* Login fields fill the card (like the Sign In button) instead of the default
   250px (--form-field-width) cap that .form-control-sized applies in dense forms. */
.login-page .form-control-sized {
    width: 100%;
    max-width: 100%;
}

/* Leading icons inside login email & password boxes — DOM icons (MudIcon) so they
   survive -webkit-autofill inset paint and background shorthand on prod browsers. */
.login-input-shell {
    position: relative;
    display: grid;
    align-items: center;
    width: 100%;
}

.login-input-shell .native-form-control {
    grid-column: 1;
    grid-row: 1;
    width: 100%;
    padding-inline-start: 2.45rem;
}

.login-input-shell.login-input-shell--end-adornment .native-form-control {
    padding-inline-end: 2.75rem;
}

.login-input-shell .login-input-leading-icon {
    grid-column: 1;
    grid-row: 1;
    justify-self: start;
    align-self: center;
    margin-inline-start: 0.8rem;
    z-index: 2;
    pointer-events: none;
    display: inline-flex;
    color: var(--app-icon-muted);
}

.login-input-shell .login-input-leading-icon .mud-icon-root {
    font-size: 1.05rem;
}

/* Email/password inputs stay LTR on RTL pages: left-aligned text, leading icons
   on the left, visibility toggle on the right. */
[dir="rtl"] .login-page .login-input-shell .native-form-control {
    direction: ltr;
    text-align: left;
}

[dir="rtl"] .login-page .login-input-shell {
    direction: ltr;
}

/* Re-resolve form-box tokens on the login page (same :root alias pitfall as
   body.app-dark). Without this, --app-color-form-box-border can keep #d8dee9. */
.login-page {
    --app-color-form-box-bg: var(--app-surface-raised);
    --app-color-form-box-text: var(--app-text);
    --app-color-form-box-border: var(--form-control-border);
}

body.app-dark .login-page {
    --app-color-form-box-bg: var(--app-surface-raised);
    --app-color-form-box-text: var(--app-text);
    /* Resting login borders: icon-muted tint reads gray on dark inputs, not white. */
    --app-color-form-box-border: rgba(138, 148, 184, 0.45);
}

/* Dark mode login: pin resting/focus borders with !important so prod browsers
   and the form-box shorthand cannot leave a light resting border or skip the
   focus border-color (focus glow alone is not enough). */
body.app-dark .login-page .login-input-shell .native-form-control:not(:focus):not(.is-invalid) {
    border: 1px solid var(--app-color-form-box-border) !important;
}

/* Non-autofill focus only — autofill focus is handled below so box-shadow does
   not override Chrome's -webkit-box-shadow inset background hack. */
body.app-dark .login-page .login-input-shell .native-form-control:focus:not(.is-invalid):not(:-webkit-autofill) {
    border: 1px solid var(--form-control-focus-border) !important;
    box-shadow: var(--form-control-focus-shadow) !important;
    outline: none;
}

body.app-dark .login-page .login-input-shell .native-form-control:hover:not(:focus):not(:disabled):not(.is-invalid) {
    border-color: var(--app-color-form-box-border) !important;
}

/* Chrome autofill on login (prod/incognito): inset shadow fakes the dark surface;
   transition delay prevents Chrome from repainting a white autofill background. */
body.app-dark .login-page .login-input-shell .native-form-control:-webkit-autofill,
body.app-dark .login-page .login-input-shell .native-form-control:-webkit-autofill:hover,
body.app-dark .login-page .login-input-shell .native-form-control:-internal-autofill-selected {
    -webkit-box-shadow: 0 0 0 875px var(--app-color-form-box-bg) inset !important;
    box-shadow: none !important;
    -webkit-text-fill-color: var(--app-color-form-box-text) !important;
    caret-color: var(--app-color-form-box-text);
    border: 1px solid var(--app-color-form-box-border) !important;
    transition: background-color 600000s 0s, color 600000s 0s;
}

body.app-dark .login-page .login-input-shell .native-form-control:-webkit-autofill:focus,
body.app-dark .login-page .login-input-shell .native-form-control:-internal-autofill-selected:focus {
    -webkit-box-shadow: 0 0 0 875px var(--app-color-form-box-bg) inset, var(--form-control-focus-shadow) !important;
    box-shadow: var(--form-control-focus-shadow) !important;
    -webkit-text-fill-color: var(--app-color-form-box-text) !important;
    caret-color: var(--app-color-form-box-text);
    border: 1px solid var(--form-control-focus-border) !important;
    outline: none;
    transition: background-color 600000s 0s, color 600000s 0s;
}

/* Light-theme login: same autofill pins (Chrome saved credentials on prod). */
.login-page .login-input-shell .native-form-control:-webkit-autofill,
.login-page .login-input-shell .native-form-control:-webkit-autofill:hover,
.login-page .login-input-shell .native-form-control:-internal-autofill-selected {
    -webkit-box-shadow: 0 0 0 875px var(--app-color-form-box-bg) inset !important;
    box-shadow: none !important;
    -webkit-text-fill-color: var(--app-color-form-box-text) !important;
    caret-color: var(--app-color-form-box-text);
    border: 1px solid var(--app-color-form-box-border) !important;
    transition: background-color 600000s 0s, color 600000s 0s;
}

.login-page .login-input-shell .native-form-control:-webkit-autofill:focus,
.login-page .login-input-shell .native-form-control:-internal-autofill-selected:focus {
    -webkit-box-shadow: 0 0 0 875px var(--app-color-form-box-bg) inset, var(--form-control-focus-shadow) !important;
    box-shadow: var(--form-control-focus-shadow) !important;
    -webkit-text-fill-color: var(--app-color-form-box-text) !important;
    caret-color: var(--app-color-form-box-text);
    border: 1px solid var(--form-control-focus-border) !important;
    outline: none;
    transition: background-color 600000s 0s, color 600000s 0s;
}

.login-subtitle {
    color: var(--app-muted);
}

/* Brand strip at the bottom of the login column: the logos in one row, on the page's own navy,
   framed only by a soft border [developer-directed 2026-10-23]. The card takes the free space
   above it (margin-block: auto), so it stays centred and the strip never overlaps it. */
.login-page:has(> .login-brands) {
    flex-direction: column;
    gap: 1.5rem;
}

.login-page:has(> .login-brands) > .login-card {
    margin-block: auto;
}

.login-brands {
    width: 100%;
    max-width: 480px;
    display: flex;
    align-items: center;
    justify-content: space-around;
    gap: 1rem;
    padding: 0.875rem 1.25rem;
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: var(--app-radius);
}

.login-brands img {
    height: 28px;
    width: auto;
    max-width: 30%;
    object-fit: contain;
}

.login-layout-shell {
    position: relative;
}

.login-layout-switcher {
    position: absolute;
    top: 0.75rem;
    inset-inline-start: 0.75rem;
    z-index: 10;
    --topbar-control-size: 42px;
}

.user-form-hint {
    color: var(--app-muted);
}

.login-input-shell .login-password-visibility-btn {
    grid-column: 1;
    grid-row: 1;
    justify-self: end;
    align-self: center;
    z-index: 2;
    margin-inline-end: 2px;
    position: static;
    transform: none;
    padding: 5.25px;
}

.login-input-shell .login-password-visibility-btn .mud-icon-root {
    font-size: 1.25rem;
}

.login-password-control {
    display: flex;
    flex-direction: column;
    gap: 8.75px;
    width: 100%;
}

.login-password-control .form-field-error {
    margin-top: 0;
}

/* =========================================================================
 * 04. LAYOUT: APP SHELL & SIDEBAR
 * ========================================================================= */
/* The app shell is one of the two zoom roots (--app-zoom, §01) — it carries the sidebar, topbar,
   page cards, tables and toolbars, so one declaration sets the density for all of them. The
   viewport heights are divided by the zoom because vh resolves against the real viewport and
   would otherwise render at 88% of it, leaving a gap at the bottom of the screen. */
.app-shell {
    /* Mud's drawer width vars (default 240px) are emitted by MudThemeProvider on :root; restating
       them here — on the layout element the drawer, appbar and main content all live under —
       wins by inheritance, so the sidebar AND the content margin that tracks it move together. */
    --mud-drawer-width-left: var(--app-sidebar-width);
    --mud-drawer-width-right: var(--app-sidebar-width);
    --mud-drawer-width-mini-left: var(--app-sidebar-mini-width);
    --mud-drawer-width-mini-right: var(--app-sidebar-mini-width);
    zoom: var(--app-zoom);
    height: calc(100vh / var(--app-zoom));
    min-height: calc(100vh / var(--app-zoom));
    overflow: hidden;
    background: var(--app-bg);
}

.app-shell-fullscreen .app-sidebar {
    visibility: hidden !important;
    width: 0 !important;
    min-width: 0 !important;
    max-width: 0 !important;
    overflow: hidden;
    pointer-events: none !important;
}

.app-shell-fullscreen .app-main.mud-main-content {
    margin-inline-start: 0 !important;
    width: 100% !important;
    max-width: 100% !important;
}

body:has(.app-shell) {
    overflow: hidden;
}

.app-shell .app-sidebar.mud-drawer-fixed {
    top: 0 !important;
    height: 100% !important;
}

.app-sidebar {
    color: var(--app-sidebar-text);
    /* Level 3's ink [developer-directed 2026-09-02]. Levels 2 and 3 were given the same
       --app-sidebar-text-child earlier the same day and read as one level with a weight
       difference, so the page rows step back: the child ink diluted 30% toward the menu's own
       surface, which dims it in whichever direction that surface lies — darker on a white menu,
       lighter on a navy one — with no per-theme value to keep in step.
       ⚠️ Declared HERE, not in :root: a custom property's var()s are substituted on the element
       that DECLARES it, so from :root this would bake in the light palette's surface and stay
       there under body.app-dark and under any theme-*.css layer (the same trap the note on
       --app-sidebar-tree-path describes). 30% is the measured floor — on ElitaPro's white menu
       it keeps the row at 5.8:1, where a flat --app-sidebar-muted would have shipped 3.8:1. */
    --app-sidebar-text-page: color-mix(in srgb, var(--app-sidebar-text-child) 70%, var(--app-sidebar));
    --mud-palette-primary: var(--app-primary);
    --mud-palette-primary-rgb: 72, 149, 239;
    /* The drawer itself is see-through: it only holds the two floating cards (brand + menu),
       and the page ground shows in the gap around them — see .sidebar-panel. */
    background: transparent;
    border-inline-end: 0;
}

/* THE FLOATING SHELL [developer-directed 2026-10-22, YsKarton's layout]. The brand row, the menu
   and the topbar used to be one joined frame (an L of navy + a bar with shared edges). They are
   now three separate cards with the page's own gap (--app-shell-gap, the same 13.125px the page
   card keeps from the window) between them and around them. The drawer pads its content by
   the gap on three sides; the side facing the page gets none, because the topbar / page
   card already keep the gap on their own side.
   ⚠️ --app-sidebar-width (§01) INCLUDES that gap, so the cards stay 224px wide and the tree's
   indents (measured against the card) did not move. */
.app-sidebar .mud-drawer-content {
    background: transparent;
    gap: var(--app-shell-gap);
    padding: var(--app-shell-gap);
    padding-inline-end: 0;
}

/* On a phone the drawer is a temporary overlay with no page beside it: gap on all four sides. */
.app-sidebar.mud-drawer--open.mud-drawer-temporary .mud-drawer-content {
    padding-inline-end: var(--app-shell-gap);
}

/* The menu card: the level ladder + the tree (MainLayout wraps both). */
.sidebar-panel {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    background: var(--app-sidebar);
    border: var(--app-sidebar-edge);
    border-radius: var(--app-radius);
    overflow: hidden;
}

/* The peek glides open and shut instead of snapping [developer-directed 2026-08-13].
   ⚠ The transition belongs on the ROOT: the element carrying `.app-sidebar` IS `.mud-drawer`, so
   the old `.app-sidebar .mud-drawer` selector matched nothing (verified in the running app) and
   only the inner content was ever transitioned — while the panel, which is what the peek actually
   resizes, snapped to full width the instant the pointer arrived.
   The content is deliberately NOT transitioned: it takes its final width at once so the labels
   inside never reflow mid-movement (see the peek rules further down).
   Material's standard easing — Mud's own drawer curve — at 260ms rather than the 180 the content
   used, so opening reads as the same deliberate movement as collapsing. */
.app-sidebar {
    transition: width 260ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Column layout lets the nav grow/scroll while the foot controls pin to the bottom. */
.app-sidebar .mud-drawer-content {
    display: flex;
    flex-direction: column;
}

.app-sidebar .sidebar-nav {
    flex: 1 1 auto;
    overflow-y: auto;
    /* Scrolled rows fade out over the top 12px instead of being cut flat against the level
       ladder above [developer-directed 2026-11-04]. The mask sits on the scroll box, so it stays
       put while the rows move: unscrolled, those 12px are only the tree's own top padding. */
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 12px);
    mask-image: linear-gradient(to bottom, transparent 0, #000 12px);
}

.app-sidebar .mud-typography,
.app-sidebar .mud-nav-link-text {
    color: var(--app-sidebar-text);
}

/* The +/− sign is the one thing painted in the app primary colour; the section caption itself
   reads like every other row. Needed because .app-sidebar .mud-typography above would otherwise
   repaint the MudIcon inside the sign with --app-sidebar-text. */
.app-sidebar .sidebar-section .sidebar-sign,
.app-sidebar .sidebar-section .sidebar-sign .mud-icon-root {
    color: var(--app-primary);
}

/* The panel itself sets WIDTH ALONE — and that is what lets it animate.
   ⚠ It used to pin min-width and max-width here too, which silently defeated the transition:
   `max-width: none → 256px` is not interpolable so it jumped, and a jumped `min-width` clamps the
   used width to full on the very first frame however smoothly `width` is interpolating behind it.
   Neither was doing any work — Mud leaves the drawer root at `min-width: 0; max-width: none`, so
   there was nothing to override. Measured before and after: 49 → 225px in one frame with them,
   49 → 94 → 189 → 219 → 225 across 260ms without.
   (The third selector was `.app-sidebar-peek .mud-drawer`, which matched nothing: the element
   carrying .app-sidebar IS .mud-drawer, so that was a descendant of itself.) */
.app-sidebar-peek {
    width: var(--app-sidebar-width) !important;
    /* Clip while the reveal is in progress: the content below is already at its full width, so for
       the 260ms the panel is still narrower than its contents the drawer root — which sits on
       `overflow-x: auto` — would otherwise be free to flash a horizontal scrollbar. */
    overflow-x: hidden;
}

/* The content, by contrast, takes its full width at once and keeps it for the whole movement, so
   the widening panel REVEALS a finished menu rather than reflowing one — left to resize with the
   panel, every label would wrap and unwrap on the way open. */
.app-sidebar-peek .mud-drawer-content {
    width: var(--app-sidebar-width) !important;
    min-width: var(--app-sidebar-width) !important;
    max-width: var(--app-sidebar-width) !important;
}

.app-sidebar-peek {
    z-index: 1300;
}

.app-sidebar-mini .sidebar-brand {
    justify-content: center;
    /* 0.5rem, not 0.55: the rail is 56px, and 56 - 2 x 8 is exactly the 40px mark. */
    padding-inline-end: 0.5rem;
    padding-inline-start: 0.5rem;
}

/* The collapsed rail is only 56px wide (Mud's mini-drawer default), so the enlarged chip has
   to shed the difference or it would push against both edges. Restored to full size below,
   once the rail peeks open to the full sidebar width. */

.app-sidebar-mini .brand-title,
.app-sidebar-mini .mud-nav-link-text {
    display: none;
}

/* …except that the rail's own rows keep their label for assistive tech. `display: none` above
   would strip every rail row of its accessible name and leave a screen reader a column of unnamed
   buttons — the rail's whole content. Clipped rather than hidden, and out of flow, so the icon
   still centres on the rail. (The wordmark stays `display: none` — it names nothing. The section
   rows get the same clipped-label treatment further down, in §04.) */
.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-group > .mud-nav-link .mud-nav-link-text,
.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-link .mud-nav-link-text {
    display: block;
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* The ladder is three numbered buttons that only read as buttons beside a menu they visibly move:
   on a 56px rail it would be a row of loose digits. Restored below when the rail peeks open. */
.app-sidebar-mini .sidebar-levels {
    display: none;
}

.app-sidebar-mini .sidebar-nav {
    padding-inline-end: 0.45rem;
    padding-inline-start: 0.45rem;
}

/* The rail is a MIRROR of the open menu, not a fixed column [developer-directed 2026-08-13]: every
   row the menu is showing puts its icon on the rail — level 1, level 2 and the page links — and a
   row the menu has folded away is absent there too. (It used to be one icon per group and nothing
   else, so the pages you had open vanished the moment the menu narrowed.) The levels are told
   apart by their colour, the same three tones the open menu uses, and by the gap between sections;
   the tree lines and indents stay off, the rail being too narrow to draw them in.
   So: the page links only shed the row padding that would push them off-centre — nothing hides
   them. Mud's collapse already keeps a folded group's pages out of the DOM's flow. */
.app-sidebar.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-nav .sidebar-group .mud-collapse-wrapper-inner .sidebar-link .mud-nav-link {
    justify-content: center;
    padding-inline-end: 0;
    padding-inline-start: 0;
}

/* The rail centres the icon on its own row, so it sheds the 0.45rem that separates it from a label
   there is no room for. Written more specific than the level-3 icon rule in §04 — that one is
   `!important` too, and at equal specificity the later rule in the file (the §04 one) would win on
   source order alone. */
.app-sidebar.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-nav .sidebar-link svg.mud-nav-link-icon {
    margin: 0 !important;
}

.app-sidebar-mini.app-sidebar-peek .sidebar-brand {
    justify-content: flex-start;
    padding: 0 1.15rem;
}


.app-sidebar-mini.app-sidebar-peek .brand-title,
.app-sidebar-mini.app-sidebar-peek .mud-nav-link-text {
    display: block;
}

.app-sidebar-mini.app-sidebar-peek .sidebar-levels {
    display: flex;
}

.app-sidebar-mini.app-sidebar-peek .sidebar-nav {
    padding: 0.9rem 0.65rem;
}

/* Mud's mini-drawer wraps each nav item in a padding the docked sidebar does not carry, and peek
   is meant to be geometrically identical to docked — the row inside already carries
   --sidebar-row-pad and the tree carries the indents. Left in place, this second outer padding
   pushed the peeked rows 16px deeper than the docked ones, so every level-3 elbow stopped short of
   its icon (the tree reaches the row's padding, not a padding wrapped around the row) and the
   longest page name wrapped in peek alone. */
.app-sidebar-mini.app-sidebar-peek .sidebar-link {
    padding-inline-end: 0;
    padding-inline-start: 0;
}

/* …and Mud's mini-drawer rule pads the far end of the row, which the docked sidebar does not
   carry. Left in place it stole 16px from the label alone — the row, its icon and its elbow all
   sat where they should, so only the longest page name gave it away by wrapping in peek. */
.app-sidebar.app-sidebar-mini.app-sidebar-peek .sidebar-nav .sidebar-link .mud-nav-link {
    padding-inline-end: 0;
}

/* (A peek rule zeroing the level-3 icon's margin-inline-end used to sit here. It was written when
   the docked value was 0 too, so it never decided anything — same (0,4,1) as the §04 rule that
   states the value, which is later in the file and therefore wins. Now that the value is 0.45rem
   it would have been a peek-only divergence from docked, which peek is meant never to have.) */

/* The brand card: the same height as the topbar card beside it, so the two tops and bottoms
   line up across the shell. */
/* Mark + wordmark centred as one group in the card [developer-directed 2026-10-22]. */
.sidebar-brand {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    gap: 0.75rem;
    width: 100%;
    min-height: var(--app-topbar-height);
    padding: 0 1.15rem;
    box-sizing: border-box;
    background: var(--app-sidebar);
    border: var(--app-sidebar-edge);
    border-radius: var(--app-radius);
    box-shadow: var(--app-sidebar-brand-edge);
}

/* No hover fill [developer-directed 2026-10-22]: the brand is its own card, and a tinted card
   under the pointer read as a state change, not as a link. The pointer cursor is the hint. */
.sidebar-brand-link {
    text-decoration: none;
    color: inherit;
    cursor: pointer;
}

/* The brand mark is the Pulsar mark itself (AppLogo, images/brand/), on a transparent ground -
   the gradient badge with a letter that stood here until 2026-10-22 is gone with it. The
   component renders BOTH theme files and these rules show one: body.app-dark decides, nothing
   else, so the swap rides the same class flip as every other themed surface. */
.brand-mark {
    /* 40px on the desktop, as big as the brand card allows [developer-directed 2026-10-22,
       "increase the size"]: the card is --app-topbar-height tall (56px) with no vertical
       padding, so the mark keeps 8px of air above and below. The min() against the token
       is the responsive step: the smaller topbar tiers (46.5 / 41.25 / 39.5px) shrink it
       with the card instead of growing the card past the topbar beside it. */
    --brand-mark-size: min(40px, calc(var(--app-topbar-height) - 12px));
    width: var(--brand-mark-size);
    height: var(--brand-mark-size);
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
}

.brand-mark .brand-logo {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
}

/* The wordmark (mark + «Pulsar»): a wide image whose height follows its width, so a rule sets
   the width alone. */
.brand-wordmark {
    display: block;
    max-width: 100%;
}

.brand-wordmark .brand-logo {
    display: block;
    width: 100%;
    height: auto;
}

/* The theme switch. Written at the SAME specificity as the two `display: block` rules above
   (container + image, (0,2,0)) and after them, so source order decides - a bare
   `.brand-logo--dark` lost to them and both files showed at once in light mode. */
.brand-mark .brand-logo--dark,
.brand-wordmark .brand-logo--dark {
    display: none;
}

body.app-dark .brand-mark .brand-logo--light,
body.app-dark .brand-wordmark .brand-logo--light {
    display: none;
}

body.app-dark .brand-mark .brand-logo--dark,
body.app-dark .brand-wordmark .brand-logo--dark {
    display: block;
}

/* On the login card: the card is 367.5px with 2rem of padding each side, so 220px leaves the
   wordmark air on both sides without reading small. */
.login-wordmark {
    width: 220px;
}

.brand-title {
    color: var(--app-sidebar-text);
    font-size: 12.25px;
    font-weight: 500;
    line-height: 1.1;
    letter-spacing: 0.01em;
}

/* The level ladder: three segments that open the tree down to level 1, 2 or 3. It sits above
   .sidebar-nav rather than inside it, so it can't scroll out of reach — which also puts it
   outside the scope of --sidebar-tree-line, hence the colour-mix restated here. */
.sidebar-levels {
    display: flex;
    align-items: center;
    /* Centred across the drawer rather than hugging the start edge: the ladder belongs to the
       whole menu, not to the first row under it, so it reads as a control strip instead of a
       stray toolbar. */
    justify-content: center;
    gap: 2.625px;
    padding: 0.7rem 0.85rem 0;
}

.sidebar-level {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    width: 18.375px;
    height: 18.375px;
    padding: 0;
    border: 1px solid color-mix(in srgb, var(--app-primary) 42%, transparent);
    /* 6px on a 21px box (~0.29) — sized to the BOX, not drift off a token. The control
       radius (--app-radius-form-box) is chosen for ~33px form boxes; at 21px a corner is judged
       against its own box, which is why the 16px group caret below takes 4px (~0.25) rather than
       the same number as this one. The RATIO transfers to a future chip, the px does not — so
       promote to a token only when a second chip of the SAME size needs it. */
    border-radius: 5.25px;
    background: none;
    color: var(--app-sidebar-text-parent);
    font-family: inherit;
    font-size: 9.625px;
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
    transition: background 140ms ease, color 140ms ease, border-color 140ms ease;
}

.sidebar-level:hover {
    background: var(--app-sidebar-hover-bg);
    border-color: var(--app-primary);
    color: var(--app-sidebar-text-child);
}

.sidebar-level:focus-visible {
    outline: 2px solid var(--app-primary);
    outline-offset: 2px;
}

/* The rung the tree is actually on. Drops when a section is folded by hand — CurrentNavLevel
   then reports 0 and no segment is filled. */
.sidebar-level.is-active {
    background: var(--app-primary);
    border-color: var(--app-primary);
    color: var(--app-on-primary);
}

.sidebar-nav {
    /* --sidebar-tree-*: the tree "path line" geometry, shared by all three indent levels. Nothing
       here is a row HEIGHT: every vertical landmark is a percentage of the row that owns it, so a
       row is free to grow with its font. What is stated in pixels is only what we set ourselves —
       the indent, the row padding, the sign, the margins between rows.
       --sidebar-tree-bleed is how far a row's trunk segment has to run PAST its own box to reach
       the next row's; it is per-level because only levels 2 and 3 put a margin between their
       rows (see the two rules that set it). */
    --sidebar-tree-line: color-mix(in srgb, var(--app-primary) 42%, transparent);
    /* The path line is the same colour, but pre-blended against the sidebar rather than left
       translucent — the tree is assembled from separate rectangles per row, and a 42%-alpha one
       cannot be joined to its neighbour: overlap them and the alpha composites twice into a
       bright blob, abut them and the shell's `zoom` puts the seam on a fractional device pixel
       whose two partial coverages composite to ~89% and read as a dim notch. Opaque, the pieces
       can simply overlap and every junction is exact. The sidebar is a flat fill behind all of
       them (.app-sidebar + .mud-drawer-content), so the blend is the identical colour. */
    --sidebar-tree-path: color-mix(in srgb, var(--app-primary) 42%, var(--app-sidebar));
    --sidebar-tree-indent: 10.5px;
    /* The inline padding every row carries — level 1's section button, level 2's group header,
       level 3's link — stated once because the tree's drop column is measured off it. 12, not
       Mud's 16: the drop below adds to the indent of every level under it, and 16 pushed the
       longest page names (סטטוסים יום עבודה) onto a second line. */
    --sidebar-row-pad: 10.5px;
    /* Where a child branch's trunk stands, measured from its parent row's start edge: that row's
       padding + half the 16px +/− sign = the sign's centre line. Derived, not picked — the sign
       is what the branch hangs from. */
    --sidebar-tree-drop: calc(var(--sidebar-row-pad) + var(--sidebar-sign-half));
    /* How far an elbow runs from its trunk: out to its row's edge (the indent), then across that
       row's padding to the +/− sign or icon the row leads with, so the line touches what it
       points at. */
    --sidebar-tree-reach: calc(var(--sidebar-tree-indent) + var(--sidebar-row-pad));
    /* Half the 16px +/− box. The drop column above is measured from it, and so is every hang —
       a branch starts where its sign's box ends. */
    --sidebar-sign-half: 7px;
    /* The space around a row's icon, ON BOTH SIDES, and the only thing that states it
       [developer-directed 2026-08-13]. The +/− sign used to carry the gap between itself and the
       icon; owning both sides gives the icon the same air on every level, which is what made a page
       row (no sign in front of it) sit tighter than a group row. Level 1 spends it as the section
       row's flex `gap`, which does the same job between the same pair of boxes. */
    --sidebar-glyph-gap: 0.45rem;
    /* The two row heights the vertical geometry is measured from. They are PINNED on the rows
       themselves (not left as Mud min-heights), because a taller line box silently grows a
       min-height row and every tick measured from it then misses its own sign. */
    --sidebar-section-row: 35px;
    --sidebar-group-row: 28px;
    --sidebar-tree-tick: calc(var(--sidebar-group-row) / 2);
    --sidebar-tree-width: 1px;
    /* How softly an elbow turns [developer-directed 2026-09-02]. The elbow is no longer a filled
       1px strip meeting the trunk at a hard 90°: it is an empty box whose bottom + inline-start
       BORDERS draw the turn, rounded by this radius (border-end-start-radius — in RTL that is the
       bottom-right corner, exactly where vertical becomes horizontal). 6px against the 12px
       indent: half the horizontal run stays straight, so the line still reads as arriving AT the
       sign rather than as a loose arc. Must stay well under --sidebar-tree-tick (16px) — the curve
       climbs this far up the trunk, and past the tick it would start above its own row. On a
       MIDDLE row the trunk runs straight through and the curve peels off it (their 1px verticals
       overlap, which the opaque pre-blended --sidebar-tree-path makes seamless); on the LAST
       row the trunk stops where the curve begins — see the :last-child height rules, which
       subtract this token. */
    --sidebar-tree-curve: 5.25px;
    --sidebar-tree-bleed: 0px;
    /* The vertical breathing room between two nav rows. One token because the tree's bleed is
       derived from it — hard-coding either side lets them drift and re-opens the gap. */
    --sidebar-row-gap: 0.12rem;
    padding: 0.9rem 0.65rem;
}

/* Level 1 — the sections (חשבוניסט / סופטביט / הגדרות): a +/− toggle folding the whole section.
   Identical in SIZE to a group header and a link; WEIGHT and colour are what tell the three
   levels apart [developer-directed 2026-09-02]: levels 1 and 2 are semibold (600) and level 3
   is not (500), while level 1 keeps the brand --app-primary (matching the +/− sign it already
   carries), level 2 the neutral --app-sidebar-text-child — near-black on a light menu,
   near-white on a dark one — and level 3 that same ink stepped back
   (--app-sidebar-text-page, declared on .app-sidebar). So the ladder reads brand-bold →
   neutral-bold → quiet-plain rather than the saturated → tinted → neutral it read before.
   ⚠️ --app-sidebar-text-parent is no longer worn by level 2; it still dresses the level chips.
   justify-content is explicit because MudBlazor's global `button` reset centres flex content,
   which would otherwise push level 1 inward, past its own children. */
.sidebar-section {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    /* The same token levels 2 and 3 put on their icons — here one flex gap covers both of the
       pairs it would otherwise be written between (sign→icon, icon→label). */
    gap: var(--sidebar-glyph-gap);
    width: 100%;
    /* Fixed, not a minimum: the tree's elbow is measured from this number, so the row is not
       allowed to outgrow it on a font whose line box runs taller. 40 against a 14px label leaves
       room to spare either way. */
    height: var(--sidebar-section-row);
    margin: 0;
    padding: 0 var(--sidebar-row-pad);
    border: 0;
    border-radius: var(--app-radius);
    background: none;
    color: var(--app-primary);
    font-family: inherit;
    font-size: 12.25px;
    font-weight: 600;
    letter-spacing: normal;
    text-transform: none;
    text-align: start;
    cursor: pointer;
}

.sidebar-section:not(.sidebar-section--static):hover {
    background: var(--app-sidebar-hover-subtle);
}

/* The section's own icon [developer-directed 2026-08-13]. Same 16px square as the +/− sign beside
   it and as the icons on the two levels below, so all three levels keep one glyph column.
   `color: inherit` takes the row's colour — level 1's --app-primary — rather than restating it, so
   the icon and its label can never drift apart. The row's `gap` supplies the space after it, which
   is why (unlike level 2's Mud-rendered icon) it carries no margin of its own. */
.sidebar-section-icon.mud-icon-root {
    width: 14px;
    height: 14px;
    min-width: 14px;
    flex-shrink: 0;
    font-size: 14px;
    color: inherit;
}

/* …but level 1 wears the sign OR the icon, never both [developer-directed 2026-08-13]: while
   קיפול רמה ראשונה is on, the +/− is what the row leads with and the icon steps aside; the static
   row, which draws no sign, keeps it. Hidden here rather than left unrendered in MainLayout,
   because the collapsed rail draws no sign either and shows the icon in both cases (see §04). */
.app-sidebar .sidebar-section:not(.sidebar-section--static) .sidebar-section-icon.mud-icon-root {
    display: none;
}

/* קיפול רמה ראשונה off: the section is pinned open and no longer a button, so it drops every
   affordance that promised a fold — the hover fill above, the pointer, and the +/− sign. The sign
   goes entirely — the static row in MainLayout renders no .sidebar-sign at all [developer-directed
   2026-08-13]: holding its 16px indented the label past where a heading with no marker belongs,
   and an empty bordered box read as an unchecked checkbox. The label sits on the row's own
   padding. */
.sidebar-section--static {
    cursor: default;
}

/* With the sign gone the stub has nothing left to hang from — and it is drawn by climbing
   --sidebar-tree-hang UP from the branch, which without a sign to stop at lands in the middle of
   the heading's own text. The branch keeps its indent (the groups must not shift), so the trunk
   simply begins at the first group row: the spine of the group list rather than a line dangling
   off a node that no longer exists. */
.sidebar-section--static + .sidebar-branch-wrap::before {
    display: none;
}

/* The one +/− sign, worn by every foldable level: the section button's own span and — via
   MudNavGroup's ExpandIcon — the group header's svg, styled to the identical box. */
.sidebar-sign,
.app-sidebar .sidebar-nav .sidebar-group > .mud-nav-link .mud-nav-link-expand-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    width: 14px;
    height: 14px;
    min-width: 14px;
    flex-shrink: 0;
    border: 1px solid var(--sidebar-tree-line);
    /* 4px on a 16px box (~0.25) — sized to the BOX, like the 21px .sidebar-level chip (6px).
       It equals --app-radius-form-box today by COINCIDENCE, not by reference: this is a 16px
       square, not a form box, so it must follow its own size if the control token moves. */
    border-radius: 3.5px;
    color: var(--app-primary);
    line-height: 0;
}

.sidebar-section .sidebar-sign .mud-icon-root {
    width: 10.5px;
    height: 10.5px;
    font-size: 10.5px;
}

/* Mud renders the expand icon last and rotates it when open; `order` puts it on the leading edge
   next to the trunk, and the rotation is pointless on a +/− glyph.
   No margin of its own: the gap to the icon beside it is the ICON's --sidebar-glyph-gap, stated
   once for both of its sides so a row without a sign spaces its icon identically. */
.app-sidebar .sidebar-nav .sidebar-group > .mud-nav-link .mud-nav-link-expand-icon {
    order: -1;
    padding: 2px;
    margin: 0 !important;
    fill: currentColor;
    transform: none !important;
}

.sidebar-section:not(.sidebar-section--static):hover .sidebar-sign,
.app-sidebar .sidebar-nav .sidebar-group > .mud-nav-link:hover .mud-nav-link-expand-icon {
    border-color: var(--app-primary);
    background: var(--app-sidebar-hover-bg);
}

/* CSS-only fold (grid 1fr → 0fr) rather than MudCollapse: the sections are plain markup, not
   MudNavGroups, so there is no Mud collapse to lean on — and the grid fold animates with no JS.
   (Its original second job — letting the rail force a folded section open in CSS — is gone: the
   rail mirrors the fold now, see §04.) */
.sidebar-branch-wrap {
    display: grid;
    grid-template-rows: 1fr;
    transition: grid-template-rows 180ms ease;
}

.sidebar-branch-wrap.is-collapsed {
    grid-template-rows: 0fr;
}

.sidebar-branch-wrap > .sidebar-branch {
    min-height: 0;
    overflow: hidden;
}

/* The stub that hangs a branch off the +/− sign above it. A branch's own first row cannot draw it
   by climbing out of its box: both folds clip their top edge (this grid fold via the overflow
   above, Mud's collapse via .mud-collapse-wrapper's `overflow: hidden auto`), so the piece is
   drawn by the fold WRAPPER, which sits outside the clip — and, unlike the header rows, carries
   none of Mud's ripple (see the elbow rule for what that costs). Without it every branch starts a
   dozen px below its parent and reads as hanging off nothing.
   --sidebar-tree-hang is the climb from that wrapper's top back to the sign's bottom edge: half
   the row it hangs from, less the half-sign that box already covers, plus the margins in between.
   The row-gap + line width on the height carries the stub back down into the first row's own
   trunk, the same overlap every other junction uses. */
.sidebar-branch-wrap,
.app-sidebar .sidebar-nav .sidebar-group .mud-collapse-container {
    position: relative;
}

.sidebar-branch-wrap {
    --sidebar-tree-hang: calc(var(--sidebar-section-row) / 2 - var(--sidebar-sign-half));
}

.app-sidebar .sidebar-nav .sidebar-group .mud-collapse-container {
    /* One margin further than level 1: the group header row carries a bottom margin of its own,
       and the collapse container starts below it. */
    --sidebar-tree-hang: calc(var(--sidebar-group-row) / 2 - var(--sidebar-sign-half) + var(--sidebar-row-gap));
}

/* ⚠️ Every piece of the tree is drawn as a BORDER, never a background fill, since the elbows
   curved [developer-reported 2026-09-02]: under the shell's zoom (0.88) Chrome rasterises the
   two differently — a border is floored to whole device pixels while a 1px background fill lands
   on 0.88 of one and antialiases — so a fill trunk and a border elbow read as two line weights
   on a 1× screen (invisible at the high dpr the screenshots run at, caught only on the real
   display). A vertical line is therefore `width: 0` + border-inline-start — the total is the
   border in EITHER box model, so nothing depends on a box-sizing reset being loaded. */
.sidebar-branch-wrap::before,
.app-sidebar .sidebar-nav .sidebar-group .mud-collapse-container::before {
    content: "";
    position: absolute;
    inset-inline-start: var(--sidebar-tree-drop);
    top: calc(-1 * var(--sidebar-tree-hang));
    width: 0;
    height: calc(var(--sidebar-tree-hang) + var(--sidebar-row-gap) + var(--sidebar-tree-width));
    border-inline-start: var(--sidebar-tree-width) solid var(--sidebar-tree-path);
}

/* Nothing hangs off a folded sign — the stub goes with the branch it belongs to. Level 2 by our
   own class, level 3 by Mud's (the container is .mud-collapse-entered only while fully open). */
.sidebar-branch-wrap.is-collapsed::before,
.app-sidebar .sidebar-nav .sidebar-group .mud-collapse-container:not(.mud-collapse-entered)::before {
    display: none;
}

/* The path lines. Levels 2 and 3 are the same shape — an indented container whose rows each draw a
   trunk segment (::before) and an elbow into their own header (::after), both from the NODE, plus
   a stub from the fold below it hanging that node's branch off its +/− sign. Level 1 draws none of
   it: the sections are the roots, so a trunk above them would hang off nothing (it read as a bare
   rail down the menu's edge with a stub poking at each section). Drawn per row rather
   than as one border on the container so the last row can stop its trunk at its own elbow instead
   of dangling. Level 3 (the links) repeats the shape further down, on Mud's collapse wrapper.
   Everything vertical is measured from a row height this file PINS (see --sidebar-group-row and
   the section's own height): Mud's rows are min-heights that a taller line box silently grows,
   and a grown row slid every level-2 elbow ~4px above its own sign. */
.sidebar-branch {
    padding-inline-start: var(--sidebar-tree-indent);
}

/* Level 1 is the root: it hangs off nothing, so it draws no trunk and no elbow (see below) and
   therefore needs no indent to draw them in. It sits on the menu's own start edge, and the gap
   before its +/− sign is the section row's own --sidebar-row-pad. */
.sidebar-tree {
    padding-inline-start: 0;
}

/* A branch stands in its parent's SIGN column, not at some hand-picked indent: its trunk is drawn
   one --sidebar-tree-indent outside its own content box, so putting that content
   --sidebar-tree-drop in from the parent's edge lands the trunk exactly on the centre of the
   parent's +/− box and the branch reads as dropping out of the toggle. Level 3 has always done
   this (24px, now the token); level 2 carried 0.55rem instead, which stood its trunk ~15px
   OUTSIDE the section's sign — a branch hanging off nothing. */
.sidebar-branch {
    margin-inline-start: var(--sidebar-tree-drop);
}

.sidebar-branch > .sidebar-group {
    position: relative;
}

.sidebar-tree > .sidebar-node {
    /* Padding, not margin, so two sections cannot collapse their gap together. */
    padding-top: 5.25px;
}

.sidebar-branch > .sidebar-group::before {
    content: "";
    position: absolute;
    inset-inline-start: calc(-1 * var(--sidebar-tree-indent));
    /* Border, not a background fill — see the stub rule above for why the whole tree must use one
       paint mechanism. */
    border-inline-start: var(--sidebar-tree-width) solid var(--sidebar-tree-path);
}

/* The trunk runs past the bottom of its own row, far enough to reach into the next row's segment:
   the margin that separates the two rows (which a row's ::before cannot paint, being confined to
   its own box) plus one line width of deliberate overlap, so the join survives fractional
   rounding rather than depending on two edges landing on the same device pixel. */
.sidebar-branch > .sidebar-group::before {
    top: 0;
    bottom: calc(-1 * (var(--sidebar-tree-bleed) + var(--sidebar-tree-width)));
    width: 0;
}

/* The elbow, drawn by the NODE at --sidebar-tree-tick down from its top — which is its header
   row's centre, because the row's height is pinned (below) rather than left to the font.
   ⚠ It cannot be drawn by the row itself, tempting as `top: 50%` is: MudBlazor gives every
   .mud-nav-link the .mud-ripple class, and Mud ships `.mud-ripple { overflow: hidden }` plus
   `.mud-ripple:after { display: none }` — so a level-2 row clips any line reaching outside its own
   box and drops its ::after entirely. Level 3 (drawn on the nav ITEM, outside Mud's ripple row) is
   free of that; level 2 is not, and the two levels have to be one shape.
   The run is the indent PLUS the row's padding, so the line arrives AT the +/− sign — level 3, at
   the page icon — instead of stopping at the row's edge, pointing at it from a row-padding away. */
.sidebar-branch > .sidebar-group::after {
    content: "";
    position: absolute;
    box-sizing: border-box;
    inset-inline-start: calc(-1 * var(--sidebar-tree-indent));
    /* A curved elbow, not a strip (see --sidebar-tree-curve): the box's TOP is the curve's height
       above the tick, and its bottom border lands on exactly the y the old 1px strip occupied
       ([tick, tick + width]) — so the horizontal line has not moved, only the corner softened.
       box-sizing is pinned because the height counts the border IN, and no global border-box
       reset is guaranteed to cover pseudo-elements. */
    top: calc(var(--sidebar-tree-tick) - var(--sidebar-tree-curve));
    width: var(--sidebar-tree-reach);
    height: calc(var(--sidebar-tree-curve) + var(--sidebar-tree-width));
    border-inline-start: var(--sidebar-tree-width) solid var(--sidebar-tree-path);
    border-bottom: var(--sidebar-tree-width) solid var(--sidebar-tree-path);
    border-end-start-radius: var(--sidebar-tree-curve);
}

/* The last row stops its trunk where its elbow's CURVE begins — carried one line width past it so
   the seam is painted by both pieces (the curve's first --sidebar-tree-width IS still vertical:
   the radius is one width less than the box height, so the arc proper starts below the overlap). */
.sidebar-branch > .sidebar-group:last-child::before {
    bottom: auto;
    height: calc(var(--sidebar-tree-tick) - var(--sidebar-tree-curve) + var(--sidebar-tree-width));
}

.sidebar-branch > .sidebar-group {
    /* Half of this row's pinned height — the elbow lands on the header's centre line. */
    --sidebar-tree-tick: calc(var(--sidebar-group-row) / 2);
    /* Two sibling groups are held apart by the header's own margin, which collapses out of this
       (unpadded) node and so falls outside the trunk's box — the trunk has to bleed across it. */
    --sidebar-tree-bleed: var(--sidebar-row-gap);
}

/* A page that hangs straight off a section with no group in between — a section whose whole content
   is one link, so wrapping it in a group of one would be a fold that hides a single row. It sits at
   level 2's indent but IS a page, so it keeps level 3's colour and takes level 3's geometry: the
   elbow at `top: 50%`, which level 2 cannot use because Mud's .mud-ripple clips the group header's
   pseudo-elements (see the note above) while .sidebar-link — the nav ITEM — sits outside that box.
   Inert in a menu whose sections hold only groups. */
.sidebar-branch > .sidebar-link {
    position: relative;
}

.sidebar-branch > .sidebar-link::before,
.sidebar-branch > .sidebar-link::after {
    content: "";
    position: absolute;
    inset-inline-start: calc(-1 * var(--sidebar-tree-indent));
}

.sidebar-branch > .sidebar-link::before {
    top: 0;
    bottom: calc(-1 * (var(--sidebar-row-gap) + var(--sidebar-tree-width)));
    width: 0;
    border-inline-start: var(--sidebar-tree-width) solid var(--sidebar-tree-path);
}

.sidebar-branch > .sidebar-link:last-child::before {
    bottom: auto;
    height: calc(50% - var(--sidebar-tree-curve) + var(--sidebar-tree-width));
}

/* The curved elbow, level-3 geometry — the level-2 rule above documents the box. */
.sidebar-branch > .sidebar-link::after {
    box-sizing: border-box;
    top: calc(50% - var(--sidebar-tree-curve));
    width: var(--sidebar-tree-reach);
    height: calc(var(--sidebar-tree-curve) + var(--sidebar-tree-width));
    border-inline-start: var(--sidebar-tree-width) solid var(--sidebar-tree-path);
    border-bottom: var(--sidebar-tree-width) solid var(--sidebar-tree-path);
    border-end-start-radius: var(--sidebar-tree-curve);
}

/* Mud 9 ships .mud-nav-link as an inline-flex row with align-items: FLEX-START, so a 16px icon
   hangs at the top of a line-height-1.75 (24.5px) content box while the label fills it — the icon
   read ~4px high on every row. Centre the row's own cross axis; the optical nudge below then has
   a true centre to work against. */
.app-sidebar .sidebar-nav .mud-nav-link {
    align-items: center;
}

/* Optical centring of the row labels against their icons. The boxes are centred by the rule
   above, but a Hebrew label's ink is not centred inside its own line box:
   the letters sit on the baseline with no ascenders, while the box reserves the font's full
   ascent. Measured in-browser at 14px Plus Jakarta Sans — the ink centre of a plain Hebrew
   label lands 1.2px BELOW the icon centre — so the label reads low. One rounded pixel back up
   (integer, not the measured 1.2, so the glyphs stay crisp), on all three levels. */
.app-sidebar .sidebar-nav .mud-nav-link-text,
.app-sidebar .sidebar-section .sidebar-section-label {
    position: relative;
    top: -1px;
}

/* Child level: a page inside a group — the plain, stepped-back row: not bold, and one tone
   quieter than the header above it [developer-directed 2026-09-02]. Both halves matter; on the
   first pass this level shared level 2's ink and weight alone could not carry the difference.
   ⚠️ The 500 below is stated on the nav ITEM and Mud's inner anchor does not inherit it, so the
   row renders at Mud's own 400 (measured). Left as it was — this level is meant to be the plain
   one, and the ink now says so too. */
.sidebar-link {
    margin: var(--sidebar-row-gap) 0;
    border-radius: var(--app-radius);
    color: var(--app-sidebar-text-page);
    font-size: 12.25px;
    font-weight: 500;
}

.sidebar-link .mud-nav-link-text {
    font-size: 12.25px;
}

/* Mud 9: sidebar-link is on .mud-nav-item; icon is svg.mud-nav-link-icon (24px by default).
   16px, not 18: the same square as the +/− sign box, so the two icon columns of a group
   header line up as one.
   --sidebar-glyph-gap on BOTH sides, exactly as level 2's icon wears it [developer-directed
   2026-08-13], so a page row spaces its glyph like a group row does. Two things were wrong before:
   the trailing gap was Mud's own 12px label margin (wider than level 2's, and stated by the label
   rather than by the glyph it clears), and the leading side had no gap at all — the icon sat flat
   against the row's padding while level 2's had the sign's air in front of it. The label is zeroed
   just below, and the level-3 elbow grows by the same token so it still reaches the icon. */
.app-sidebar .sidebar-nav .sidebar-link svg.mud-nav-link-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 14px !important;
    height: 14px !important;
    min-width: 14px !important;
    margin-inline: var(--sidebar-glyph-gap) !important;
    flex-shrink: 0;
    font-size: 14px;
}

/* The level-2 rule, worn by level 3: margin 0 so Mud's 12px label margin cannot stack on the
   gap the icon above now carries. */
.app-sidebar .sidebar-nav .sidebar-link .mud-nav-link-text {
    margin: 0;
}

.sidebar-link .mud-nav-link-text {
    color: inherit;
}

.app-sidebar .sidebar-nav .sidebar-link svg.mud-nav-link-icon {
    color: var(--app-sidebar-text-page);
}

/* Hover is the row background alone — a level keeps its own colour on the way past, exactly as
   levels 1 and 2 do. (Lifting level 3 to the child ink on hover would make the row look like it
   had climbed a level, and would land it on level 2's exact colour.) */
.sidebar-link:hover {
    background: var(--app-sidebar-hover-bg);
    color: var(--app-sidebar-text-page);
}

.sidebar-link:hover svg.mud-nav-link-icon {
    color: var(--app-sidebar-text-page);
}

/* Mud 9: NavLink puts the "active" class on the inner anchor, not on .mud-nav-item — hence the
   two levels in the selector (the hover above rides the item, this rides the link inside it).
   The colour is ours rather than Mud's default, because Mud's is picked from the *page* theme
   while this surface stays dark in both — see --app-sidebar-active-bg. */
.app-sidebar .sidebar-nav .sidebar-link .mud-nav-link.active {
    background: var(--app-sidebar-active-bg);
    /* The label keeps level 3's ordinary ink [developer-directed 2026-09-06] — the row's background
       and the badge below say "current page"; a tinted name on top of them said it a third time. */
    color: var(--app-sidebar-text-page);
}

/* ── Level 3 wears a ROUND badge around its glyph [developer-directed 2026-09-06] ──
   The page icon sits in a 22px circle: the brand's tint under the brand as ink — the estate's own
   pill recipe (a soft fill, the colour as ink), said on a circle, in ONE colour. An eight-hue
   rotation (a colour per row position) and a heavier ringed-and-glowing version were both built
   the same day and reversed: colour by position means nothing and competes with the grid's own
   status pills, so the menu went back to one voice. The glyph keeps its 14px content box: the
   badge is PADDING around it, so the icon's outer edge — where the tree's elbow lands — does not
   move, and only the label steps 8px along to clear the wider circle.
   Every value is an existing token: --app-primary-tint / --app-primary are the pair every soft
   brand pill in the app wears, and both re-point under body.app-dark on their own. Hover keeps
   them: the estate's rule that a level keeps its own colour on the way past. */
.app-sidebar {
    --sidebar-badge-size: 22px;
}

/* Same selector as the 14px rule above, later in the file, so it wins on source order (both are
   `!important` on the size). box-sizing: border-box, so the padding sits INSIDE the 22px. */
.app-sidebar .sidebar-nav .sidebar-link svg.mud-nav-link-icon {
    box-sizing: border-box;
    width: var(--sidebar-badge-size) !important;
    height: var(--sidebar-badge-size) !important;
    min-width: var(--sidebar-badge-size) !important;
    padding: calc((var(--sidebar-badge-size) - 14px) / 2);
    border-radius: 50%;
    background-color: var(--app-primary-tint);
    color: var(--app-primary);
}

/* The current page's badge is the PICKED TAB PILL [developer-directed 2026-09-06] — the same three
   tokens .entity-form-tabs gives its active tab: the brand at a control's strength, white ink, and
   the status toggle's inset well. The row's label keeps level 3's ordinary ink (see the .active
   rule above); the row background and this badge are what say "current page".
   (0,5,1) — the active rule's own specificity, later in the file than the hover pair, so the
   current page keeps this while the pointer is on it. */
.app-sidebar .sidebar-nav .sidebar-link .mud-nav-link.active svg.mud-nav-link-icon {
    background-color: var(--app-primary-solid);
    color: var(--app-on-primary);
    box-shadow: var(--app-shadow-inset-deep);
}

/* ── Level 3 wears YsKarton's full-colour pictures [developer-directed 2026-11-04, kept] ──
   Layout/MenuIcons.cs draws every page as a small colour picture (gradients, dark outline), so
   the tinted circle above is dropped: the picture fills the badge's whole 22px square itself —
   the same outer edge, so the tree's elbow still lands on it — with no padding, fill or ring. The
   current page is said by the row background alone; a solid badge would cover the picture. */
.app-sidebar .sidebar-nav .sidebar-link svg.mud-nav-link-icon,
.app-sidebar .sidebar-nav .sidebar-link .mud-nav-link.active svg.mud-nav-link-icon {
    padding: 0;
    border-radius: 0;
    background-color: transparent;
    box-shadow: none;
}

/* Foldable nav groups: the group header is the parent level — same row shape as a link and,
   since 2026-09-02, the same INK; it is told from its children by its weight (600 against their
   500) rather than by a tone of its own. */
.app-sidebar .sidebar-nav .sidebar-group > .mud-nav-link {
    /* HEIGHT, not min-height: Mud's own 32 is a floor its 4px block padding and the label's line
       box are free to grow past — on a font whose line box runs 24.5px the row silently became
       40.5, and the tree's elbow, halved from the stated 32, then sat ~4px above this row's own
       +/− sign. Fixed here and zeroed below, the row is 32 wherever it renders. */
    height: var(--sidebar-group-row);
    min-height: var(--sidebar-group-row);
    padding-block: 0;
    margin: var(--sidebar-row-gap) 0;
    /* Mud's own 16px restated as the shared row padding — the tree's drop column is measured
       from it, so the two have to move together. */
    padding-inline-start: var(--sidebar-row-pad);
    border-radius: var(--app-radius);
    color: var(--app-sidebar-text-child);
    font-size: 12.25px;
    font-weight: 600;
}

/* margin: 0 — Mud gives the label a 12px inline-start margin of its own to clear the row's icon,
   which would stack on top of the 0.45rem the sign carries. Every gap on this row is stated once,
   by the thing on its leading side — and since the docked menu hides the icon (below), the label
   states its own --sidebar-glyph-gap to the sign, the gap the icon used to carry. */
.app-sidebar .sidebar-nav .sidebar-group > .mud-nav-link .mud-nav-link-text {
    margin: 0;
    margin-inline-start: var(--sidebar-glyph-gap);
    font-size: 12.25px;
    color: inherit;
}

/* Level 2 wears NO icon on the docked menu [developer-directed 2026-09-06] — the +/− sign carries
   the row alone, as it did before 2026-08-13 (when the icon was put on every surface). The svg
   stays in the markup and keeps its 14px box here because the MINI RAIL still shows it (see the
   .app-sidebar-mini rule below): there a row shows nothing but its icon, so a blank row is not an
   option. `color: inherit` rather than a stated tone: on the rail the icon reads as the row, so it
   takes the row's own colour and follows it through hover and the active state. */
.app-sidebar .sidebar-nav .sidebar-group > .mud-nav-link svg.mud-nav-link-icon {
    display: none;
    width: 14px !important;
    height: 14px !important;
    min-width: 14px !important;
    margin-inline: var(--sidebar-glyph-gap) !important;
    flex-shrink: 0;
    font-size: 14px;
    color: inherit;
}

/* Hover is background only — a level keeps its own colour, exactly as levels 1 and 3 do.
   (It used to lift the header to the child tone, which made the row look like it changed
   level on the way past.) */
.app-sidebar .sidebar-nav .sidebar-group > .mud-nav-link:hover {
    background: var(--app-sidebar-hover-bg);
}

/* Level 3 path line: the same drop as level 2 above — the trunk lands on the centre of the group
   header's +/− sign, then ticks across into each child link.
   Mud 9 wraps the group's children in .mud-collapse-container > … > .mud-collapse-wrapper-inner. */
.app-sidebar .sidebar-nav .sidebar-group .mud-collapse-wrapper-inner {
    margin-inline-start: var(--sidebar-tree-drop);
    padding-inline-start: var(--sidebar-tree-indent);
}

.app-sidebar .sidebar-nav .sidebar-group .mud-collapse-wrapper-inner .sidebar-link {
    position: relative;
}

/* Mud indents nested nav links by 36px of its own; the tree already carries the indent, and
   leaving both in place made the level-2→3 step twice the level-1→2 step. */
.app-sidebar .sidebar-nav .sidebar-group .mud-collapse-wrapper-inner .sidebar-link .mud-nav-link {
    padding-inline-start: var(--sidebar-row-pad);
}

.app-sidebar .sidebar-nav .sidebar-group .mud-collapse-wrapper-inner .sidebar-link::before,
.app-sidebar .sidebar-nav .sidebar-group .mud-collapse-wrapper-inner .sidebar-link::after {
    content: "";
    position: absolute;
    inset-inline-start: calc(-1 * var(--sidebar-tree-indent));
}

/* Same trunk overlap as the levels above — here the gap to cross is the link's own row margin. */
.app-sidebar .sidebar-nav .sidebar-group .mud-collapse-wrapper-inner .sidebar-link::before {
    top: 0;
    bottom: calc(-1 * (var(--sidebar-row-gap) + var(--sidebar-tree-width)));
    width: 0;
    border-inline-start: var(--sidebar-tree-width) solid var(--sidebar-tree-path);
}

.app-sidebar .sidebar-nav .sidebar-group .mud-collapse-wrapper-inner .sidebar-link:last-child::before {
    bottom: auto;
    height: calc(50% - var(--sidebar-tree-curve) + var(--sidebar-tree-width));
}

/* The plain reach, like the level above: the elbow stops at the end of the row's padding and the
   icon's own --sidebar-glyph-gap begins where the line ends [developer-directed 2026-08-13]. It
   briefly ran a glyph-gap longer, which put the line through that margin and up against the icon —
   the gap is meant to hold the glyph clear of what precedes it, line included. */
/* Curved like the two sites above (the level-2 rule documents the box). */
.app-sidebar .sidebar-nav .sidebar-group .mud-collapse-wrapper-inner .sidebar-link::after {
    box-sizing: border-box;
    top: calc(50% - var(--sidebar-tree-curve));
    width: var(--sidebar-tree-reach);
    height: calc(var(--sidebar-tree-curve) + var(--sidebar-tree-width));
    border-inline-start: var(--sidebar-tree-width) solid var(--sidebar-tree-path);
    border-bottom: var(--sidebar-tree-width) solid var(--sidebar-tree-path);
    border-end-start-radius: var(--sidebar-tree-curve);
}

/* Mini (collapsed) sidebar: center the header icon and hide the expand chevron. */
.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-group > .mud-nav-link {
    justify-content: center;
    padding-inline-end: 0;
    padding-inline-start: 0;
}

/* The rail centres the icon on its own row, so it sheds BOTH sides of --sidebar-glyph-gap — the
   same move the page-link icon makes in §03, and .app-sidebar-prefixed for the same reason: the
   docked rule above states the gap `!important` too, and at equal specificity the earlier rule
   would lose on source order rather than on intent. */
.app-sidebar.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-nav .sidebar-group > .mud-nav-link svg.mud-nav-link-icon {
    margin: 0 !important;
    /* The docked rule hides the icon; the rail is the one surface that still shows it. */
    display: inline-block;
}

/* Specific enough to beat the +/− sign rule above, which is itself .app-sidebar-scoped. */
.app-sidebar.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-nav .sidebar-group > .mud-nav-link .mud-nav-link-expand-icon {
    display: none;
}

/* Mini (collapsed) sidebar, level 1 [developer-directed 2026-08-13]: the rail carries the SECTION
   icons too. It used to hide the section rows outright and start at level 2, so the eight group
   icons stood in one undivided column with nothing to say which section each belonged to. The row
   keeps its icon alone — centred like the group rows, its label clipped (the accessible name, same
   treatment the group rows get in §03) and its +/− sign dropped: the rail forces every section open
   below, so a sign there would promise a fold that changes nothing on screen. */
.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-section {
    justify-content: center;
    padding: 0;
}

.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-section .sidebar-sign {
    display: none;
}

.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-section .sidebar-section-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* Specific enough to beat the sign-or-icon rule in §04 above, which is .app-sidebar-scoped: on the
   rail the icon is the whole row, folded or not. */
.app-sidebar.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-section .sidebar-section-icon.mud-icon-root {
    display: inline-block;
}

/* The rail used to force every section open (`.sidebar-branch-wrap { grid-template-rows: 1fr }`),
   because it hid the section rows and a folded section would have been groups gone missing with
   nothing on screen to explain it. Both halves of that are now false — the section row is there
   and the rail mirrors the menu — so the fold is left alone and a folded section shows its icon
   and nothing under it, exactly as the open menu does.
   The tree indents and lines do go: they would push the icon column off-centre. */
.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-tree,
.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-branch,
.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-group .mud-collapse-wrapper-inner {
    margin-inline-start: 0;
    padding-inline-start: 0;
}

/* The rail used to zero the gap between sections (.sidebar-tree > .sidebar-node padding-top): with
   the section rows hidden it was dead space in the middle of an unbroken icon column. Now that the
   rail shows the level-1 icons, the gap is what separates one section's block from the next, so the
   docked 6px is kept. */

.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-branch > .sidebar-group::before,
.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-branch > .sidebar-group::after,
.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-branch > .sidebar-link::before,
.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-branch > .sidebar-link::after,
.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-group .mud-collapse-wrapper-inner .sidebar-link::before,
.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-group .mud-collapse-wrapper-inner .sidebar-link::after,
.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-branch-wrap::before,
.app-sidebar-mini:not(.app-sidebar-peek) .sidebar-group .mud-collapse-container::before {
    display: none;
}

/* Path-lines off (settings popup → קווי מבנה בתפריט). Only the drawn lines go: the indentation
   still carries the hierarchy and the +/− signs keep their box, so the menu reads the same,
   quieter. The same pseudo-elements the mini sidebar hides above. */
.app-sidebar-flat .sidebar-branch > .sidebar-group::before,
.app-sidebar-flat .sidebar-branch > .sidebar-group::after,
.app-sidebar-flat .sidebar-branch > .sidebar-link::before,
.app-sidebar-flat .sidebar-branch > .sidebar-link::after,
.app-sidebar-flat .sidebar-group .mud-collapse-wrapper-inner .sidebar-link::before,
.app-sidebar-flat .sidebar-group .mud-collapse-wrapper-inner .sidebar-link::after,
.app-sidebar-flat .sidebar-branch-wrap::before,
.app-sidebar-flat .sidebar-group .mud-collapse-container::before {
    display: none;
}


/* =========================================================================
 * 05. LAYOUT: TOPBAR & USER MENU
 * ========================================================================= */
.app-main {
    background: var(--app-bg);
}

/* Layout uses a custom topbar, not MudAppBar — clear MudBlazor's reserved app-bar gap */
.app-main.mud-main-content {
    display: flex;
    flex-direction: column;
    height: calc(100vh / var(--app-zoom)); /* inside .app-shell's zoom - see section 01 */
    overflow: hidden;
    padding-top: 0;
}

/* Impersonation preview strip — shown above the topbar while an admin is viewing the
   app as another user; the exit button restores their own session. */
.impersonation-banner {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    gap: 0.6rem;
    min-height: 35px;
    padding: 0.3rem 1.5rem;
    background: var(--app-warning-soft);
    /* The card above the topbar, when shown — same gap, same corners (the floating shell). */
    margin: var(--app-shell-gap) var(--app-shell-gap) 0;
    border: 1px solid var(--app-warning);
    border-radius: var(--app-radius);
    color: var(--app-text);
    font-size: 0.875rem;
    z-index: 41;
}

.impersonation-banner-icon {
    color: var(--app-warning);
}

.impersonation-banner-text b {
    font-weight: 700;
}

.topbar {
    position: relative;
    z-index: 40;
    flex-shrink: 0;
    overflow: visible;
    /* The topbar is a floating card of its own [2026-10-22] — see THE FLOATING SHELL in §04. Its
       top edge lines up with the brand card's, its sides with the page card's. */
    margin: var(--app-shell-gap) var(--app-shell-gap) 0;
    background: var(--app-topbar-bg);
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius);
    --topbar-control-size: 42px;
}

/* User menu drops below the bar; lift the whole topbar above page content and stop inner scroll clipping. */
.topbar:has(.topbar-user-menu) {
    z-index: 1360;
    overflow: visible;
}

.topbar-inner {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    /* ⚠️ MINUS THE BAR'S OWN BORDERS (top + bottom = 2px). The topbar card and the brand card
       stand side by side and must end on the same y. .sidebar-brand states the same
       --app-topbar-height but is `box-sizing: border-box`, so its border is INSIDE the token;
       .topbar sets no height at all — it is sized by this element — so its border is ADDED.
       `box-sizing` on .topbar would do nothing, because .topbar has no height of its own. */
    min-height: calc(var(--app-topbar-height) - 2px);
    min-width: 0;
    padding: 0 1.5rem;
    overflow-x: auto;
    overflow-y: visible;
    scrollbar-width: none;
}

.topbar-inner::-webkit-scrollbar {
    display: none;
}

.topbar-inner:has(.topbar-user-menu) {
    overflow: visible;
}

.topbar-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    flex: 0 0 auto;
    order: 2;
    position: relative;
    z-index: 1;
}

.topbar-end {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
    margin-inline-start: auto;
    flex: 0 0 auto;
    width: auto;
}

.topbar-icon {
    color: var(--app-topbar-muted);
}

.topbar-icon.mud-icon-button {
    width: var(--topbar-control-size);
    height: var(--topbar-control-size);
    min-width: var(--topbar-control-size);
    min-height: var(--topbar-control-size);
    padding: 0;
    border-radius: var(--app-radius-circle);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    box-sizing: border-box;
}

.language-switcher-menu .mud-menu-activator {
    display: inline-flex;
}

.language-switcher-flag-wrap {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 0;
}

.language-switcher-button .language-switcher-flag {
    width: 1.35rem;
    aspect-ratio: 3 / 2;
    object-fit: cover;
    /* Kept, and kept at 3px: the box-shadow ring below follows the radius, so this is what
       the corner actually shows. See the note on .language-switcher-item .language-switcher-flag. */
    border-radius: 2.625px;
    box-shadow: 0 0 0 1px rgba(127, 127, 127, 0.22);
    display: block;
}

body.app-dark .language-switcher-button .language-switcher-flag {
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.14);
}

.language-switcher-item {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}

.language-switcher-item .language-switcher-flag {
    width: 1.125rem;
    height: auto;
    /* NOT swept to square with the sibling app's copy: the language switcher is DORMANT
       there (single-language config) and LIVE here, so that call was made where nobody could
       see it. Change this only against a rendered Pulsar switcher. */
    border-radius: 2px;
}

.language-switcher-active {
    font-weight: 600;
}

.language-switcher-menu-item.mud-menu-item {
    padding: 0.35rem 0.65rem;
    box-sizing: border-box;
}

.topbar-logout-button {
    display: inline-flex;
    flex-shrink: 0;
    border-radius: var(--app-radius-circle);
    transition: background-color 160ms ease, color 160ms ease;
}

.topbar-logout-button,
.topbar-logout-button .mud-icon-root,
.mud-button-root.topbar-logout-button,
.mud-button-root.topbar-logout-button .mud-icon-root {
    color: var(--app-danger);
}

.topbar-logout-button:hover:not(.mud-disabled),
.mud-button-root.topbar-logout-button:hover:not(.mud-disabled) {
    background: var(--app-danger-soft);
    color: var(--app-danger);
}

.topbar-logout-button:hover:not(.mud-disabled) .mud-icon-root,
.mud-button-root.topbar-logout-button:hover:not(.mud-disabled) .mud-icon-root {
    color: var(--app-danger);
}

.topbar-user-wrap {
    position: relative;
    flex: 0 0 auto;
    width: auto;
    order: 1;
    z-index: 2;
}

.topbar-user-wrap:has(.topbar-user-menu) {
    z-index: 1360;
}

.topbar-user {
    display: inline-flex;
    align-items: center;
    width: auto;
    flex: 0 0 auto;
    gap: 0.65rem;
    --topbar-user-avatar-inset: 4.375px;
    min-height: var(--topbar-control-size);
    padding-block: 0;
    padding-inline-start: var(--topbar-user-avatar-inset);
    padding-inline-end: calc(0.5rem + 13.125px);
    border-radius: var(--app-radius-pill);
    background: var(--app-surface-subtle);
    cursor: pointer;
    user-select: none;
    transition: background 160ms ease, box-shadow 160ms ease;
    box-sizing: border-box;
}

.topbar-user.topbar-user-compact {
    padding: var(--topbar-user-avatar-inset);
    gap: 0;
}

.topbar-user.topbar-user-compact .mud-avatar {
    margin-inline-end: 0;
}

.topbar-user .mud-avatar {
    width: calc(var(--topbar-control-size) - 2 * var(--topbar-user-avatar-inset));
    height: calc(var(--topbar-control-size) - 2 * var(--topbar-user-avatar-inset));
    min-width: calc(var(--topbar-control-size) - 2 * var(--topbar-user-avatar-inset));
    min-height: calc(var(--topbar-control-size) - 2 * var(--topbar-user-avatar-inset));
    margin-inline-end: 4.375px;
    font-size: calc((var(--topbar-control-size) - 2 * var(--topbar-user-avatar-inset)) * 0.36);
}

.topbar-user .mud-avatar .user-avatar-image,
.topbar-user .mud-avatar .user-avatar-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.topbar-user-text {
    min-width: 0;
    max-width: 12rem;
    overflow: hidden;
}

.topbar-user:hover {
    background: var(--app-primary-soft);
    box-shadow: var(--app-shadow-topbar-user);
}

.topbar-user-name {
    color: var(--app-text);
    line-height: 1.1;
    font-weight: 700;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.topbar-user-role {
    color: var(--app-muted);
    line-height: 1.1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.topbar-user-arrow {
    color: var(--app-topbar-muted-dark);
    font-size: var(--dropdown-caret-icon-size);
    width: var(--dropdown-caret-icon-size);
    height: var(--dropdown-caret-icon-size);
}

.topbar-user-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1340;
    background: transparent;
}

.topbar-user-menu {
    position: absolute;
    top: calc(100% + 0.35rem);
    left: 50%;
    right: auto;
    transform: translateX(-50%);
    z-index: 1360;
    width: 245px;
    padding-bottom: 0.5rem;
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius);
    background: var(--app-card);
    box-shadow: var(--app-shadow-menu);
    font-size: 11.375px;
    line-height: 1.5;
    font-weight: 400;
    color: var(--app-text);
}

.user-menu-header,
.user-menu-row {
    display: flex;
    align-items: center;
}

.user-menu-header {
    gap: 0.75rem;
    align-items: center;
}

.user-menu-identity {
    min-width: 0;
    display: flex;
    flex-direction: column;
    flex: 1;
}

.user-menu-divider {
    margin: 0.65rem 0 0.5rem;
}

.user-menu-name {
    color: var(--app-text);
    font-size: inherit;
    font-weight: 600;
    line-height: 1.5;
}

.user-menu-email {
    color: var(--app-muted);
    font-size: inherit;
    line-height: 1.5;
    word-break: break-all;
}

.user-menu-change-password,
.user-menu-logout {
    display: block;
    width: 100%;
    margin: 0;
    padding: 0.5rem 0;
    border: none;
    border-radius: var(--app-radius);
    background: transparent;
    font-family: inherit;
    font-size: 11.375px;
    font-weight: 600;
    line-height: 1.5;
    text-align: start;
    cursor: pointer;
    transition: color 160ms ease, background 160ms ease;
}

.user-menu-actions {
    margin-top: 0.75rem;
}

.user-menu-actions-divider {
    margin: 0.5rem 0;
}

.user-menu-change-password {
    color: var(--app-primary);
}

.user-menu-change-password:hover {
    color: var(--app-link);
    background: var(--app-primary-tint-subtle);
}

.user-menu-logout {
    color: var(--app-danger);
}

.user-menu-logout:hover {
    background: var(--app-danger-tint-subtle);
}

.avatar-color-select-wrap {
    position: relative;
}

.avatar-color-select-wrap .avatar-color-selected-swatch {
    position: absolute;
    top: 50%;
    inset-inline-start: 0.75rem;
    width: 1rem;
    height: 1rem;
    border-radius: var(--app-radius-circle);
    transform: translateY(-50%);
    box-shadow: var(--app-shadow-inset-swatch);
    pointer-events: none;
}

.avatar-color-select-wrap .avatar-color-select .mud-input-slot {
    padding-inline-start: 2rem;
}

.avatar-color-option {
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
}

.avatar-color-swatch {
    width: 1rem;
    height: 1rem;
    border-radius: var(--app-radius-circle);
    flex-shrink: 0;
    box-shadow: var(--app-shadow-inset-swatch);
}

.avatar-color-select-popover .mud-list-item.mud-list-item-dense.mud-list-item-gutters {
    padding-top: 0;
    padding-bottom: 0;
    min-height: 25.375px;
    height: 25.375px;
    box-sizing: border-box;
}

.avatar-color-select-popover {
    z-index: 1500 !important;
}

.user-menu-label {
    color: var(--app-muted);
    font-weight: 600;
    font-size: inherit;
}

.user-menu-row {
    justify-content: flex-start;
    gap: 0.65rem;
}

.user-menu-roles {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem 0.65rem;
    margin-top: 0.9rem;
}

.user-menu-roles .role-chip-list {
    margin-top: 0;
}

.topbar-user-menu .user-status-pill,
.topbar-user-menu .role-pill {
    font-size: 11.375px;
}

.logout-button {
    color: var(--app-topbar-muted);
    text-transform: none;
    font-weight: 600;
}

/* =========================================================================
 * 06. LAYOUT: PAGE STACK & CARDS
 * ========================================================================= */
.app-content {
    width: 100%;
    max-width: 100%;
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    padding-top: var(--app-shell-gap);
    padding-bottom: var(--app-shell-gap);
    /* Tokenized so surfaces that space themselves the way the page does read the SAME value
       at every tier — the responsive blocks reassign it. */
    --app-content-pad-inline: 13.125px;
    padding-inline-start: var(--app-content-pad-inline);
    padding-inline-end: var(--app-content-pad-inline);
    box-sizing: border-box;
}

.app-content > div {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* The board pane (§24) joins the flat-card rule for a visible reason: its 24px-blur shadow
   bleeds into the gaps between cards and tints them darker than --app-bg, while .page-stack
   clips the same shadow at the outer edges — so the gap strips read as a different colour
   than the page margins around them. */
.app-content .data-card,
.app-content .content-card,
.app-content .page-hero,
.app-content .board-pane {
    box-shadow: none;
}

.app-content .data-card:hover,
.app-content .content-card:hover,
.app-content .page-hero:hover {
    box-shadow: none;
}

.page-stack {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    overflow: hidden;
}

.page-stack:not(:has(.users-table-shell)):not(:has(.permission-matrix)) {
    overflow-y: auto;
    overflow-x: hidden;
}

/* A page of natural-height cards only (the Wave tenant page: details, phones, API keys, usage; the
   connected-devices screen): no card takes "the rest" and scrolls its own table, so the page itself
   scrolls. Without it the cards below the screen were cut off and could not be reached [2026-11-01]. */
.page-stack:not(:has(> .data-card:not(.data-card--natural))) {
    overflow-y: auto;
    overflow-x: hidden;
}

.page-stack > .data-card,
.page-stack > .mud-paper.data-card {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* A form card stacked ABOVE a table card (the periodic-VAT page): it keeps its own height and the
   table card below takes the rest. Without it the two cards share the column and the form is cut. */
.page-stack > .data-card.data-card--natural {
    flex: 0 0 auto;
}

.page-stack > .data-card.data-card--natural > .page-card-body {
    overflow: visible;
}

/* The two-card tax pages (מע"מ תקופתי, מקדמות מס הכנסה, מספרי הקצאה): the gap between the cards equals the gap
   above the first card, so the page reads as even strips [developer-directed 2026-10-29]. */
.page-stack.page-stack--shell-gap {
    gap: var(--app-shell-gap);
}

/* A list with one or two short columns (תחומי הוצאות): the card keeps the full height but not
   the full width — a one-column table stretched across a wide screen leaves the name and its row
   buttons a screen apart. It sits at the inline start (right in RTL) and still fills a narrow
   screen [developer-directed 2026-10-23]. */
.page-stack > .data-card.data-card--narrow {
    --data-card-narrow-width: 50rem;
    align-self: flex-start;
    width: min(100%, var(--data-card-narrow-width));
}

/* The trial balance (מאזן בוחן): each group is a title row, its accounts, and a bold total row with a
   rule above it; the grand total sits in the sticky footer. Theme tokens only, so dark mode follows. */
.users-table-shell .mud-table-body .trial-balance-section-row > td.mud-table-cell {
    font-weight: 600;
    background: var(--app-table-head);
    padding-top: 6px;
}

/* Compact rows: a report whose rows are one short figure each (the trial balance) keeps the
   list-table 43.75px row height only in its header; the data rows drop to 30px so the whole
   ledger fits one screen. Specificity (0,4,0) beats the generic (0,3,0) body-cell rule.
   [developer-directed 2026-10-29] */
.data-card .users-table-shell--compact .mud-table-body .mud-table-cell {
    height: 30px;
}

/* A money figure inside an RTL cell: without this a negative reads «346,446.48-», the minus at the
   wrong end. An LTR isolate on the INLINE (never on the cell — that flips the cell's border side,
   see .import-cell-text) keeps the minus on the left while the cell stays aligned by the page
   [developer-reported 2026-10-29, trial balance]. */
.ltr-number {
    direction: ltr;
    unicode-bidi: isolate;
}

.users-table-shell .mud-table-body .trial-balance-total-row > td.mud-table-cell {
    font-weight: 600;
    border-top: 1px solid var(--app-border);
}

.users-table-shell .mud-table-foot .trial-balance-grand-cell {
    position: sticky;
    bottom: 0;
    z-index: 1;
    white-space: nowrap;
    font-weight: 700;
    background: var(--app-table-head);
    border-top: 2px solid var(--app-border);
}

.page-card-body {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    gap: var(--page-card-inset, 0.75rem);
    overflow: hidden;
}

.page-card-body.pa-4 {
    --page-card-inset: 1rem;
}

.page-card-body.pa-4:has(> .table-toolbar-shell) {
    padding-top: 0;
    gap: 0;
}

.page-card-body > .table-toolbar,
.page-card-body > .table-toolbar-shell,
.page-card-body > .role-header-toolbar,
.page-card-body > .permissions-section-header {
    flex-shrink: 0;
}

.table-toolbar-shell {
    flex-shrink: 0;
    box-sizing: border-box;
    padding-block: var(--page-card-inset, 1rem);
}

/* ⚠️ THE HEADER IS THE SCROLL BOX — not the actions strip inside it
   [developer-directed 2026-09-02]. The overflow rail used to live on
   .page-card-header-actions, so a row too narrow for its buttons drew a scrollbar under the
   BUTTONS ALONE — a stub rail part-way across the section, with the title beside it standing
   still. The whole header row scrolls as one now, so the rail runs the section's full width.
   Three consequences follow, each stated where it lives: the actions strip no longer shrinks
   (flex: 0 0 auto), the title keeps a floor so it cannot be squeezed away before the row
   starts scrolling, and pageCardHeader.js measures THIS element's scrollWidth — which is also
   the RTL-correct test, the row overflowing at its LEFT where the old actions-rect comparison
   could not see it. */
.page-card-header {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: 1rem;
    padding: 1.1rem 1.35rem;
    min-height: calc(2.2rem + var(--toolbar-control-height, 31.5px));
    box-sizing: border-box;
    border-bottom: 1px solid var(--app-border);
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    /* Colour comes from the global scrollbar rule in §01; only the RAIL's size is this
       header's own. */
    scrollbar-width: thin;
}

.page-card-header::-webkit-scrollbar {
    height: 3.5px;
}

/* Never shrinks: the buttons keep their natural width and push the HEADER into its scroll,
   which is what puts the rail under the whole section rather than under this strip alone. */
.page-card-header-actions {
    display: flex;
    align-items: center;
    flex: 0 0 auto;
    gap: 0.5rem;
    flex-wrap: nowrap;
}

.page-card-header-actions > * {
    flex-shrink: 0;
}

.page-card-header-actions .toolbar-header-button,
.page-card-header-actions .mud-button-root.toolbar-header-button,
.page-card-header-actions .table-new-entry-button,
.page-card-header-actions .mud-button-root.table-new-entry-button {
    min-width: var(--toolbar-header-button-width);
    width: var(--toolbar-header-button-width);
    justify-content: center;
}

.page-card-header.header-actions-compact .page-card-header-actions .toolbar-header-button,
.page-card-header.header-actions-compact .page-card-header-actions .mud-button-root.toolbar-header-button {
    min-width: var(--toolbar-control-height, 31.5px);
    width: var(--toolbar-control-height, 31.5px);
    padding: 0;
    justify-content: center;
}

.page-card-header.header-actions-compact .page-card-header-actions .toolbar-header-button .mud-button-label {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0;
    line-height: 0;
    gap: 0;
    /* letter-spacing inherits as a COMPUTED px from the button, so font-size: 0 does NOT kill
       it — the hidden label's characters still each took ~0.325px and pushed the icon off the
       button's centre. Measured in ElitaPro [developer-reported 2026-09-02]. */
    letter-spacing: 0;
}

.page-card-header.header-actions-compact .page-card-header-actions .toolbar-header-button .mud-button-icon-end {
    display: none;
}

.page-card-header.header-actions-compact .page-card-header-actions .toolbar-header-button .mud-button-icon-start {
    display: inline-flex;
    margin-inline-end: 0;
    margin-inline-start: 0;
}

.page-card-header.header-actions-compact .page-card-header-actions .mud-button-root.table-new-entry-button,
.page-card-header.header-actions-compact .page-card-header-actions .table-new-entry-button {
    min-width: var(--toolbar-control-height, 31.5px);
    width: var(--toolbar-control-height, 31.5px);
    padding: 0;
}

.toolbar-header-action {
    display: inline-flex;
    flex-shrink: 0;
}

.toolbar-header-tooltip .mud-tooltip-root {
    display: inline-flex;
}

/* Topbar icon buttons wrapped in MudTooltip (styled tooltip instead of the browser's native title
   box): the wrapper must not break the flex row they sit in. Matched structurally as well as by
   class - MudTooltip puts `Class` on the popover and `RootClass` on this wrapper. */
.topbar-icon-tooltip.mud-tooltip-root,
.topbar-actions > .mud-tooltip-root,
.language-switcher-menu .mud-tooltip-root {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
}

/* App-wide Mud tooltip look (toolbar, tables, the settings popup, etc.). */
.mud-popover.mud-tooltip {
    font-size: var(--app-tooltip-font-size);
    font-weight: var(--app-tooltip-font-weight);
    line-height: var(--app-tooltip-line-height);
    padding: var(--app-tooltip-padding);
    border-radius: var(--app-tooltip-radius);
    background-color: var(--app-tooltip-bg);
    color: var(--app-tooltip-color);
    box-shadow: none;
}

/* A long cell text's tooltip (the leads' message, 2026-11-05): wraps, keeps the visitor's line
   breaks, and stays a readable width instead of one screen-wide line. On the inner box, because the
   popover carries an inline max-width:none. */
.lead-text-tooltip-body {
    max-width: min(480px, 90vw);
    white-space: pre-wrap;
    text-align: start;
}

/* Deploy-window fallback: page subtitles were removed; hide any stale markup that a
   cached WASM bundle may still render before the update overlay reloads. */
.page-card-header > .mud-typography-body2,
.page-hero > .mud-typography-body2 {
    display: none;
}

.page-card-section {
    border-top: 1px solid var(--app-border);
}

.page-hero {
    padding: 1.1rem 1.35rem;
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius);
    background: var(--app-card);
    box-shadow: var(--app-shadow);
    transition: box-shadow 180ms ease;
}

.page-hero:hover {
    box-shadow: var(--app-shadow-popover);
}

.page-title {
    margin: 0;
    /* NOTHING IN THE HEADER SHRINKS — the row SCROLLS [developer-reported 2026-09-06,
       ElitaPro /monthly-hours]. This was `flex: 1 1 auto` with a `min-width: 5rem` floor, so a
       narrow header was answered by crushing the title into a stub instead of by the rail the
       section already owns — and because that squeeze made the row FIT, pageCardHeader.js's fit
       test also stopped collapsing the buttons to icons, so the page lost its title while
       keeping its labels. Grow but never shrink (`1 0 auto`): the row overflows at its natural
       width, the buttons go compact first, and the rail takes it from there. */
    flex: 1 0 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--app-text);
    font-size: 15.75px;
    font-weight: 600;
    letter-spacing: 0;
    line-height: 1.3;
}

.muted-text {
    color: var(--app-muted);
    font-size: 11.375px;
}

.content-card,
.data-card {
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius);
    background: var(--app-card);
    box-shadow: var(--app-shadow);
    transition: box-shadow 180ms ease;
    overflow: hidden;
}

.content-card:hover,
.data-card:hover {
    box-shadow: var(--app-shadow-popover);
}

.card-title {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    color: var(--app-text);
    font-size: 12.25px;
    font-weight: 700;
}


/* =========================================================================
 * 07. TABLE TOOLBAR & FILTERS
 * ========================================================================= */
.table-toolbar {
    --toolbar-search-width: 245px;
    display: flex;
    align-items: center;
    gap: 0.65rem;
    min-height: var(--toolbar-band-height);
    padding-top: 0.4rem;
    padding-bottom: 1rem;
    flex-shrink: 0;
    box-sizing: border-box;
}

.table-toolbar-shell .table-toolbar {
    flex-direction: column;
    align-items: stretch;
    gap: 0.35rem;
    min-height: 0;
    padding-top: 0;
    padding-bottom: 0;
}

.table-toolbar .toolbar-filter-field,
.role-header-toolbar .toolbar-filter-field {
    position: relative;
    display: inline-flex;
    align-items: stretch;
    flex-shrink: 0;
    box-sizing: border-box;
    padding-top: var(--toolbar-label-gap, 0.5rem);
}

.table-toolbar .toolbar-filter-field .searchable-native-select.table-search-select,
.role-header-toolbar .toolbar-filter-field .searchable-native-select.table-search-select {
    width: var(--toolbar-filter-width);
    min-width: var(--toolbar-filter-width);
    height: var(--toolbar-control-height);
    min-height: var(--toolbar-control-height);
    max-height: var(--toolbar-control-height);
}

.table-toolbar .toolbar-filter-label,
.role-header-toolbar .toolbar-filter-label {
    position: absolute;
    top: var(--toolbar-label-gap, 0.5rem);
    inset-inline-start: 0.55rem;
    transform: translateY(-50%);
    z-index: 1;
    padding: 0 0.25rem;
    background: var(--app-color-filter-label-bg);
    color: var(--app-color-filter-label);
    font-size: 0.6875rem;
    font-weight: 500;
    line-height: 1;
    pointer-events: none;
    white-space: nowrap;
}

.table-toolbar-shell .table-toolbar-actions {
    padding-top: 0;
}

.table-toolbar-shell .table-search-group-track {
    align-items: flex-end;
}

.table-toolbar-shell .table-search-group {
    padding-bottom: 0.5rem;
}

.table-toolbar .toolbar-filter-field:focus-within .toolbar-filter-label,
.role-header-toolbar .toolbar-filter-field:focus-within .toolbar-filter-label {
    color: var(--app-primary);
}

.table-toolbar .toolbar-filter-field > .form-control-sized,
.role-header-toolbar .toolbar-filter-field > .form-control-sized {
    display: contents;
}

.table-search-group {
    width: 100%;
    min-width: 0;
    overflow-x: auto;
    overflow-y: visible;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    box-sizing: border-box;
    padding-inline-end: 4.375px;
}

.table-search-group-track {
    display: inline-flex;
    align-items: center;
    gap: 0.65rem;
    flex-wrap: nowrap;
    width: max-content;
    min-width: 100%;
    box-sizing: border-box;
}

/* Deploy-window fallback: legacy toolbar rows rendered before the inner track wrapper
   (only matches stale cached WASM; current markup always renders the track). */
.table-search-group:not(:has(.table-search-group-track)) {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    flex-wrap: nowrap;
}

.table-toolbar .table-search-group-track > .toolbar-filter-field,
.table-toolbar .table-search-group:not(:has(.table-search-group-track)) > .toolbar-filter-field,
.table-toolbar .table-search-group-track > .table-toolbar-actions,
.table-toolbar .table-search-group:not(:has(.table-search-group-track)) > .table-toolbar-actions {
    flex-shrink: 0;
}

.table-toolbar .table-search-group:not(:has(.table-search-group-track)) > .table-toolbar-actions {
    margin-inline-end: 4.375px;
}

/* Drop wrapper box from flex layout for native select only */
.table-toolbar .table-search-group-track > .form-control-sized,
.table-toolbar .table-search-group:not(:has(.table-search-group-track)) > .form-control-sized {
    display: contents;
}

.table-toolbar .table-search-group-track > .table-filter-status,
.table-toolbar .table-search-group:not(:has(.table-search-group-track)) > .table-filter-status {
    flex-shrink: 0;
}

.table-toolbar-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.65rem;
    flex-shrink: 0;
    box-sizing: border-box;
    padding-top: var(--toolbar-label-gap, 0.5rem);
}

.table-toolbar .toolbar-reset-button.mud-icon-button {
    width: var(--toolbar-control-height);
    height: var(--toolbar-control-height);
    flex-shrink: 0;
}

/* Clear-filters button in the page header actions row matches the toolbar height. */
.page-card-header-actions .toolbar-reset-button.mud-icon-button,
.page-card-header-actions .toolbar-copy-button.mud-icon-button,
.page-card-header-actions .toolbar-back-button.mud-icon-button,
.page-card-header-actions .toolbar-filter-button.mud-icon-button {
    width: var(--toolbar-control-height);
    height: var(--toolbar-control-height);
}

.table-filter-field {
    display: inline-flex;
    flex-direction: row;
    align-items: center;
    gap: 0.5rem;
    flex-shrink: 0;
    width: auto;
    min-width: 0;
}

.table-filter-field .user-form-label {
    margin: 0;
    white-space: nowrap;
}

.table-filter-field.table-search,
.table-filter-field.table-search-select,
.table-filter-field.table-search-date {
    width: auto;
    min-width: 0;
}

.table-filter-field.table-search-select .searchable-native-select {
    width: var(--toolbar-filter-width);
    min-width: var(--toolbar-filter-width);
}

.table-filter-field.table-search-date .mud-input-control {
    width: var(--toolbar-date-filter-width);
    min-width: var(--toolbar-date-filter-width);
    margin: 0;
}

.table-search {
    width: var(--toolbar-filter-search-width);
    min-width: var(--toolbar-filter-search-width);
}

.table-search-select {
    width: var(--toolbar-filter-width);
    min-width: var(--toolbar-filter-width);
}

.table-search-select .searchable-native-select {
    width: 100%;
}

.table-toolbar .searchable-native-select.table-search-select .searchable-native-input,
.table-toolbar .table-search-select .searchable-native-select .searchable-native-input,
.role-header-toolbar .searchable-native-select.table-search-select .searchable-native-input,
.role-header-toolbar .table-search-select .searchable-native-select .searchable-native-input {
    height: var(--toolbar-control-height);
    min-height: var(--toolbar-control-height);
    max-height: var(--toolbar-control-height);
    padding: 0 2.25rem 0 0.65rem;
    border: 1px solid var(--form-control-border);
    border-radius: var(--app-radius-form-box);
    background: var(--app-color-filter-box-bg);
    color: var(--app-text);
    font-size: var(--app-text-filter-box);
    line-height: calc(var(--toolbar-control-height) - 2px);
    box-sizing: border-box;
    box-shadow: none;
}

.table-toolbar .searchable-native-select.has-color.table-search-select .searchable-native-input,
.role-header-toolbar .searchable-native-select.has-color.table-search-select .searchable-native-input {
    padding-inline-start: 2.25rem;
}

.table-toolbar .table-search-select .searchable-native-input::placeholder,
.role-header-toolbar .table-search-select .searchable-native-input::placeholder {
    color: var(--app-placeholder);
    opacity: 1;
}

.table-toolbar .table-search-select .searchable-native-caret,
.role-header-toolbar .table-search-select .searchable-native-caret {
    background-size: var(--dropdown-caret-size) auto;
}

.table-toolbar .searchable-native-select.has-clear .searchable-native-input,
.table-toolbar .table-search-select .searchable-native-select.has-clear .searchable-native-input,
.role-header-toolbar .searchable-native-select.has-clear .searchable-native-input,
.role-header-toolbar .table-search-select .searchable-native-select.has-clear .searchable-native-input {
    padding-inline-end: calc(var(--dropdown-clear-offset-right) + var(--dropdown-clear-hit-size) + 0.2rem);
}

.table-search-date {
    width: var(--toolbar-date-filter-width);
    min-width: var(--toolbar-date-filter-width);
}

.page-loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 16rem;
    padding: 2rem;
}

.table-toolbar .table-search-group-track > .mud-input-control.table-search:not(.table-search-floating),
.table-toolbar .toolbar-filter-field > .mud-input-control.table-search:not(.table-search-floating) {
    width: var(--toolbar-filter-search-width);
    min-width: var(--toolbar-filter-search-width);
    flex-shrink: 0;
    box-sizing: border-box;
}

.table-toolbar .toolbar-filter-field .mud-picker.table-search-date-picker {
    width: var(--toolbar-date-filter-width);
    min-width: var(--toolbar-date-filter-width);
    height: var(--toolbar-control-height);
    min-height: var(--toolbar-control-height);
    max-height: var(--toolbar-control-height);
    display: inline-flex;
    align-items: stretch;
    margin: 0;
    flex-shrink: 0;
    box-sizing: border-box;
}

.table-toolbar .toolbar-filter-field .mud-picker.table-search-date-picker > .mud-input-control,
.table-toolbar .toolbar-filter-field .mud-picker.table-search-date-picker .mud-input-control-input-container {
    height: var(--toolbar-control-height);
    min-height: var(--toolbar-control-height);
    max-height: var(--toolbar-control-height);
    margin: 0;
    padding: 0;
    display: flex;
    align-items: stretch;
}

.table-toolbar .toolbar-filter-field .mud-picker.table-search-date-picker .mud-input.mud-input-outlined {
    flex: 1 1 auto;
    width: 100%;
    height: var(--toolbar-control-height);
    min-height: var(--toolbar-control-height);
    max-height: var(--toolbar-control-height);
    margin: 0;
    padding: 0;
    align-items: stretch;
    box-sizing: border-box;
}

.table-toolbar .toolbar-filter-field .mud-picker.table-search-date-picker .mud-input-root,
.table-toolbar .toolbar-filter-field .mud-picker.table-search-date-picker .mud-input-slot {
    height: var(--toolbar-control-height);
    min-height: var(--toolbar-control-height);
    max-height: var(--toolbar-control-height);
    margin: 0;
    padding: 0 0.5rem;
    background: var(--app-color-filter-box-bg);
    font-size: var(--app-text-filter-box);
    line-height: calc(var(--toolbar-control-height) - 2px);
    display: flex;
    align-items: center;
    box-sizing: border-box;
}

.table-toolbar .toolbar-filter-field .mud-picker.table-search-date-picker .mud-input-slot-input {
    pointer-events: none;
    cursor: default;
    user-select: none;
}

.table-toolbar .toolbar-filter-field .mud-picker.table-search-date-picker .mud-input-label {
    display: none;
}

.table-toolbar .toolbar-filter-field .mud-picker.table-search-date-picker .mud-input-outlined-border {
    border-radius: var(--app-radius-form-box);
    border-color: var(--form-control-border);
    top: 0;
    bottom: 0;
    height: auto;
}

.table-toolbar .toolbar-filter-field .mud-picker.table-search-date-picker .mud-input-helper-text {
    display: none;
}

.table-toolbar .toolbar-filter-field .mud-picker.table-search-date-picker .mud-input-adornment-end {
    display: inline-flex;
    align-items: center;
    align-self: center;
    height: auto;
    margin-top: 0;
    margin-bottom: 0;
    flex-shrink: 0;
}

.table-toolbar .toolbar-filter-field .mud-picker.table-search-date-picker .mud-input-adornment-end .mud-icon-button:not(.mud-input-clear-button),
.table-toolbar .toolbar-filter-field .mud-picker.table-search-date-picker .mud-input-adornment-end .mud-icon-button-edge-end:not(.mud-input-clear-button) {
    width: 1.5rem;
    height: 1.5rem;
    padding: 0.15rem;
    margin-inline-end: 4.375px;
    margin-inline-end: 4.375px;
}

.table-toolbar .toolbar-filter-field .mud-picker.table-search-date-picker .mud-input-clear-button,
.table-toolbar .toolbar-filter-field .mud-picker.table-search-date-picker .mud-input-clear-button.mud-icon-button-edge-end {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    align-self: center;
    flex: 0 0 auto;
    margin: 0;
    margin-inline: 0;
    padding: 0;
    min-height: 0;
    line-height: 0;
    vertical-align: middle;
    width: var(--dropdown-clear-hit-size);
    height: var(--dropdown-clear-hit-size);
    min-width: var(--dropdown-clear-hit-size);
    background-position: center center !important;
    background-size: var(--dropdown-clear-size) auto !important;
}

.table-toolbar .table-search:not(.table-search-floating).mud-input-control {
    margin: 0;
    padding: 0;
    height: var(--toolbar-control-height);
    min-height: var(--toolbar-control-height);
    max-height: var(--toolbar-control-height);
}

.table-toolbar .table-search:not(.table-search-floating) .mud-input-control-input-container {
    margin-top: 0;
    margin-bottom: 0;
    height: var(--toolbar-control-height);
}

.table-toolbar .table-search:not(.table-search-floating) .mud-input-root,
.table-search:not(.table-search-floating) .mud-input-root {
    height: var(--toolbar-control-height);
    min-height: var(--toolbar-control-height);
    max-height: var(--toolbar-control-height);
    background: var(--app-color-filter-box-bg);
}

.table-toolbar .table-search:not(.table-search-floating) .mud-input-slot,
.table-search:not(.table-search-floating) .mud-input-slot {
    padding-top: 0;
    padding-bottom: 0;
    min-height: var(--toolbar-control-height);
    display: flex;
    align-items: center;
    font-size: var(--app-text-filter-box);
}

.table-toolbar .table-search:not(.table-search-floating) .mud-input-outlined-border {
    top: 0;
    bottom: 0;
    height: auto;
}

.table-toolbar .table-search:not(.table-search-floating) .mud-input-adornment-start,
.table-toolbar .table-search:not(.table-search-floating) .mud-input-adornment-end {
    align-self: center;
    margin-top: 0;
}

.table-search .mud-input-outlined-border {
    border-radius: var(--app-radius-form-box);
    border-color: var(--form-control-border);
}

.table-search input::placeholder {
    color: var(--app-placeholder);
    opacity: 1;
}

.table-search .mud-input-adornment-start .mud-icon-root {
    color: var(--app-icon-muted);
}

.page-card-header-actions .toolbar-header-button,
.page-card-header-actions .mud-button-root.toolbar-header-button,
.table-toolbar .toolbar-header-button,
.table-toolbar .mud-button-root.toolbar-header-button,
.toolbar-header-button,
.mud-button-root.toolbar-header-button {
    min-height: var(--toolbar-control-height, 31.5px);
    height: var(--toolbar-control-height, 31.5px);
    max-height: var(--toolbar-control-height, 31.5px);
    padding: 0 1.25rem;
    box-sizing: border-box;
    border-radius: var(--app-radius-button);
    /* Border - the switch above; OFF at 0%. Three longhands, never the shorthand.
       The 1px is permanent: with box-sizing: border-box the button's OUTER size never moves,
       so the bordered and borderless states are geometrically identical. --control-accent is
       read off --toolbar-btn-color, which the variant selectors below supply per button. */
    --control-accent: var(--toolbar-btn-color);
    border-width: 1px;
    border-style: solid;
    border-color: color-mix(in srgb,
        var(--control-border-color, var(--control-accent, currentColor)) var(--app-control-border-strength),
        transparent);
    background-color: var(--toolbar-btn-soft);
    color: var(--toolbar-btn-color);
    font-size: var(--app-text-page-button);
    text-transform: none;
    font-weight: 600;
    box-shadow: none;
    transition: background-color 160ms ease, color 160ms ease, border-color 160ms ease;
    flex-shrink: 0;
}

.table-new-entry-button,
.mud-button-root.table-new-entry-button {
    font-size: var(--app-text-page-button);
}

.page-card-header-actions .table-filters-button,
.page-card-header-actions .mud-button-root.table-filters-button,
.page-card-header-actions .table-columns-button,
.page-card-header-actions .mud-button-root.table-columns-button,
.page-card-header-actions .table-autorefresh-button,
.page-card-header-actions .mud-button-root.table-autorefresh-button,
.page-card-header-actions .table-report-button,
.page-card-header-actions .mud-button-root.table-report-button,
.page-card-header-actions .table-export-button,
.page-card-header-actions .mud-button-root.table-export-button,
.table-export-button,
.mud-button-root.table-export-button {
    --toolbar-btn-color: var(--app-primary);
    --toolbar-btn-soft: var(--app-primary-soft);
}

.page-card-header-actions .table-search-button,
.page-card-header-actions .mud-button-root.table-search-button,
.table-toolbar .table-search-button,
.table-toolbar .mud-button-root.table-search-button,
.table-search-button,
.mud-button-root.table-search-button,
.mud-button-root.toolbar-header-button.toolbar-button--green {
    --toolbar-btn-color: var(--app-green);
    --toolbar-btn-soft: var(--app-green-soft);
}

/* A destructive header action (TaxAuthority «ניתוק») - red at rest, solid red on hover. */
.mud-button-root.toolbar-header-button.toolbar-button--red {
    --toolbar-btn-color: var(--app-danger);
    --toolbar-btn-soft: var(--app-danger-soft);
}

.toolbar-header-button:hover:not(.mud-disabled),
.mud-button-root.toolbar-header-button:hover:not(.mud-disabled),
.toolbar-header-button:focus-visible:not(.mud-disabled),
.mud-button-root.toolbar-header-button:focus-visible:not(.mud-disabled) {
    /* The primary fallback: a button with no variant class has no --toolbar-btn-color, and
       without it the hover fill went transparent under white text - the button vanished. */
    background-color: var(--toolbar-btn-color, var(--app-primary));
    /* The edge follows the FILL - a hovered button is one solid block. Derived, never
       declared: no -hover / -focus / -active colour tokens per variant. */
    border-color: var(--toolbar-btn-color, var(--app-primary));
    color: var(--app-color-page-button-hover-text);
}

/* Disabled - the edge follows the INK, exactly as the hover edge follows the FILL. MudBlazor
   greys a disabled button's label and its fill and never touches its border, so an accented
   control keeps a full-strength coloured outline as the only colour left on it (reported in
   ElitaPro, whose theme has the switch on; dormant here while the strength is 0%, and correct
   the moment it is raised).
   Keyed on :disabled, NOT on .mud-disabled - a disabled MudButton carries the native ATTRIBUTE
   and no such class, so every .mud-disabled selector in this file matches nothing on a
   MudButton. The :not(.mud-disabled) guards on the hover rule above are therefore no-ops, and
   harmless only because MudBlazor also sets pointer-events: none on a disabled button. */
.toolbar-header-button:disabled,
.mud-button-root.toolbar-header-button:disabled {
    --control-border-color: var(--app-disabled-text);
}

.mud-button-root.toolbar-header-button .mud-button-label,
.mud-button-root.toolbar-header-button .mud-icon-root {
    color: inherit;
    transition: color 160ms ease;
}

/* A toolbar button is one line tall, so its label never wraps. A two-word label («דוח שעות»,
   61px) needs a hair more than the fixed width leaves after padding (59px) and broke onto a
   second line that hung out of the 31.5px button; on one line it eats ~2px of the padding, as
   «ייצוא» with its caret already does. */
.mud-button-root.toolbar-header-button .mud-button-label {
    white-space: nowrap;
}

.mud-button-root.toolbar-header-button:hover:not(.mud-disabled) .mud-button-label,
.mud-button-root.toolbar-header-button:hover:not(.mud-disabled) .mud-icon-root,
.mud-button-root.toolbar-header-button:focus-visible:not(.mud-disabled) .mud-button-label,
.mud-button-root.toolbar-header-button:focus-visible:not(.mud-disabled) .mud-icon-root {
    color: var(--app-color-page-button-hover-text);
}

.mud-button-root.toolbar-header-button .mud-icon-root {
    font-size: 14px;
    width: 14px;
    height: 14px;
}

.mud-button-root.toolbar-header-button .mud-button-icon-end .mud-icon-root {
    font-size: var(--dropdown-caret-icon-size);
    width: var(--dropdown-caret-icon-size);
    height: var(--dropdown-caret-icon-size);
}

.mud-button-root.toolbar-header-button .mud-button-icon-start {
    margin-inline-end: 0.35rem;
}

.mud-button-root.toolbar-header-button .mud-button-icon-end {
    margin-inline-start: 0.15rem;
}

.export-menu-item-excel .mud-icon-root {
    color: var(--app-export-excel);
}

.export-menu-item-pdf .mud-icon-root {
    color: var(--app-export-pdf);
}

.export-menu-item-print .mud-icon-root {
    color: var(--app-export-print);
}

.export-menu-item-email .mud-icon-root {
    color: var(--app-export-email);
}

.table-toolbar-button {
    min-height: 31.5px;
    height: 31.5px;
}

.table-new-entry-button,
.mud-button-root.table-new-entry-button {
    min-width: 9.5rem;
    padding: 0 1.5rem;
    white-space: nowrap;
}

/* ── Report / parameter filter row ────────────────────────────────────────────
   A tight, leading-aligned row of FIXED-width boxes that PARAMETERISE the page below it (pick a
   customer, pick a date) rather than filtering a column from its header. Each box wears its field
   name notched on its own top border — see .floating-native-field (pickers) and
   .native-datetime-host--labeled (date boxes), which share the --app-field-label-* tokens so the
   two label kinds are pixel-identical. Live example: the WorkTimes page.
   The row never wraps: when the boxes overrun the card it scrolls sideways instead of dropping a
   box onto a second line. flex-shrink: 0 is not optional — the row is a child of
   .page-card-body's column flex and the table below it grows, so without this the parameters get
   squeezed flat the moment the table has rows. */
.report-date-filters {
    /* These boxes stand beside the toolbar's controls, not inside a dialog, so they take the
       toolbar's 36px ladder rather than the 33px one --entity-form-control-height carries at
       :root for form fields and inline-edit grid cells [developer-directed 2026-08-17]. Set as
       the token — not as a height — so every rule consuming it below (the picker's height plus
       its line-height calc, and the date box's five MudBlazor elements) follows from this one
       declaration, while the token's other consumers (dialog form boxes, inline-edit grid cells)
       stay at 33px. At --app-zoom a 33px box painted ~3px shorter than the toolbar buttons. */
    --entity-form-control-height: 31.5px;
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    flex-wrap: nowrap;
    flex-shrink: 0;
    overflow-x: auto;
    /* overflow-x:auto unavoidably forces overflow-y to CLIP, and the floating labels sit on each
       box's TOP border — so reserve 8px above them and cancel it with a negative margin, which
       keeps the row exactly where it was and stops the labels being sliced in half. The scrollbar
       is hidden for the same reason (a visible bar draws over the boxes); the row still scrolls
       by trackpad / shift-wheel. */
    overflow-y: hidden;
    padding-top: 7px;
    margin-top: -7px;
    scrollbar-width: none;
}

/* The row's two responsive steps, deliberately CO-LOCATED with the component instead of being
   added to the :root ladders in the 960px/600px blocks. Co-location is the safe form: a
   `.report-date-filters` selector inside those blocks ties on specificity with the base rule
   above, so which one wins is decided purely by which sits later in the file — and that went the
   wrong way silently in ElitaPro, where the :root blocks precede this component. Values are
   --form-control-height's own ladder. */
@media (max-width: 960px) {
    .report-date-filters {
        --entity-form-control-height: 29.75px;
    }
}

@media (max-width: 600px) {
    .report-date-filters {
        --entity-form-control-height: 28px;
    }
}

.report-date-filters::-webkit-scrollbar {
    display: none;
}

.report-date-filters > * {
    flex: 0 0 auto;
}

/* A button standing in the filter row (the tax pages' «קיבוע תקופה» beside «סוג רישום»): the boxes'
   own height, so the row reads as one strip [developer-directed 2026-10-29]. */
.report-date-filters > .report-filter-action {
    height: var(--entity-form-control-height);
    min-height: 0;
}

/* Date boxes are narrower than the name pickers — they hold a date plus two icons. */
.report-date-filters > .form-control-sized {
    width: 145.25px;
}

/* Report-filter date boxes live outside .user-form-field, so without these they keep MudBlazor's
   default input height inside our compact host and overhang it — the pickers beside them would
   then sit a few px shorter. */
.report-date-filters .native-datetime-host .mud-input-control,
.report-date-filters .native-datetime-host .mud-input-control-input-container,
.report-date-filters .native-datetime-host .mud-input.mud-input-outlined,
.report-date-filters .native-datetime-host .mud-input-root,
.report-date-filters .native-datetime-host .mud-input-slot {
    min-height: var(--entity-form-control-height);
    height: var(--entity-form-control-height);
    max-height: var(--entity-form-control-height);
    margin: 0;
    padding-top: 0;
    padding-bottom: 0;
    box-sizing: border-box;
}

/* Fixed-width picker boxes — a long name scrolls inside its input rather than widening the row. */
.report-date-filters .report-filter-picker {
    flex: 0 0 auto;
    width: 210px;
}

.report-date-filters .report-filter-picker .form-control-sized {
    width: 100%;
}

.report-date-filters .report-filter-picker--wide {
    width: 280px;
}

/* =========================================================================
 * 08. DATA TABLES
 * ========================================================================= */

/*
 * ⚠️ RTL: MudBlazor rounds the STICKY header's outer cells in PHYSICAL corners, and it is
 *   written for LTR only:
 *       .mud-table-sticky-header * .mud-table-root .mud-table-head * .mud-table-cell:first-child
 *           { border-radius: var(--mud-default-borderradius) 0 0 0 }   — top-LEFT
 *       …:last-child { border-radius: 0 var(--mud-default-borderradius) 0 0 }   — top-RIGHT
 *   (and the same pair again for .mud-simple-table, which our line grids are).
 *   In an RTL app the FIRST cell sits at the table's right end and the LAST at its left, so
 *   both radii land on the cells' INNER corners: an arc carved out of the header fill at a
 *   column boundary in the middle of the row, with the paper's darker surface showing through
 *   it — most visible on the actions column, whose header then reads as a rounded card corner
 *   floating mid-table.
 *   Nothing is lost by zeroing them: the table's OWN corners are rounded by .mud-table (radius
 *   + overflow: hidden), which clips the header at whatever the scroll position is — so the
 *   cell radii were never what drew the visible corner, in either direction.
 *   Unlayered, so it beats the `mudblazor` cascade layer with no !important and no specificity
 *   games; stated once for both table kinds rather than per page.
 */
.mud-table-head .mud-table-cell,
.mud-simple-table thead th {
    border-radius: 0;
}

/*
 * Data tables: one 1px line per edge.
 * Outer frame = table border; cells only draw bottom + right (no left on :first-child).
 */
.data-card .mud-table.users-table,
.data-card .mud-table.data-table-grid {
    box-shadow: none;
}

.data-card .mud-table-container > .mud-table-root {
    border-collapse: collapse;
    border-spacing: 0;
    border: 1px solid var(--app-border);
    border-top: none; /* header row draws the top edge (visible with sticky + overflow) */
}

.data-card .mud-table-container > .mud-table-root .mud-table-cell {
    border: none;
    border-bottom: 1px solid var(--app-border);
    border-inline-end: 1px solid var(--app-border);
    border-inline-start: none;
}

.data-card .mud-table-container > .mud-table-root .mud-table-head .mud-table-cell {
    border-top: none;
    border-bottom: none;
}

.data-card .mud-table-container > .mud-table-root .mud-table-cell:last-child {
    border-inline-end: none;
}

.data-card .mud-table-head .mud-table-cell {
    color: var(--app-table-column-name);
    background: var(--app-table-head);
    font-size: 11.375px;
    font-weight: 600;
    letter-spacing: normal;
    line-height: 1.4;
    height: 43.75px;
    box-sizing: border-box;
    padding: 0 15.75px;
    vertical-align: middle;
}

.data-card .mud-table-body .mud-table-cell {
    color: var(--app-text);
    font-size: 11.375px;
    font-weight: 400;
    letter-spacing: normal;
    line-height: 1.4;
    height: 43.75px;
    box-sizing: border-box;
    padding: 0 15.75px;
    vertical-align: middle;
}

.data-card .mud-table-body .mud-table-cell,
.data-card .mud-table-body .mud-typography {
    font-weight: 400;
    letter-spacing: normal;
}

.data-card .mud-table-row:hover {
    background: var(--app-table-row-hover);
}

/* Zebra striping for list tables, toggled from the settings drawer
   (ZebraStripePreferenceStore adds .zebra-off on .app-content when disabled).
   `of` keeps parity counted over data rows only, so opening a .table-expanded-row
   detail row never shifts the stripes (and the detail row itself is never striped).
   :not(:hover) lets the row-hover rule win. Project row-wash classes (rows tinted
   by RowClassFunc) should be added as :not(...) exclusions here so the wash wins.
   A shell carrying .users-table-shell--no-stripes opts its one table out whatever the
   setting says (the trial balance, a ledger that reads as one block) [developer-directed
   2026-10-29]. */
.app-content:not(.zebra-off) .data-card .users-table-shell:not(.users-table-shell--no-stripes) .mud-table-body tr:nth-child(even of .mud-table-row:not(.table-expanded-row)):not(:hover):not(.time-row--open) {
    background: var(--app-table-row-stripe);
}

/* A time entry with no end yet — still running, or forgotten — is washed in the warning tint
   (TimeEntries list, RowClassFunc) [developer-directed 2026-11-05]. Hover still wins. */
.data-card .users-table-shell .mud-table-body tr.time-row--open:not(:hover) {
    background: var(--app-warning-tint);
}

.users-table-shell {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}

.data-card .mud-table-head .mud-table-cell.users-table-photo-header,
.data-card .mud-table-body .mud-table-cell.users-table-photo-cell {
    --users-photo-gap: 0.3125rem;
    --users-photo-avatar: var(--app-compact-user-avatar-size, 2.5rem);
    width: calc(var(--users-photo-avatar) + 2 * var(--users-photo-gap));
    min-width: calc(var(--users-photo-avatar) + 2 * var(--users-photo-gap));
    max-width: calc(var(--users-photo-avatar) + 2 * var(--users-photo-gap));
    padding: var(--users-photo-gap);
    box-sizing: border-box;
    text-align: center;
    vertical-align: middle;
    line-height: 0;
}

.data-card .mud-table-body .mud-table-cell.users-table-photo-cell .mud-avatar {
    display: inline-grid;
    place-items: center;
    margin: 0;
    width: var(--users-photo-avatar);
    height: var(--users-photo-avatar);
    min-width: var(--users-photo-avatar);
    min-height: var(--users-photo-avatar);
    max-width: var(--users-photo-avatar);
    max-height: var(--users-photo-avatar);
    border-radius: var(--app-radius-circle);
    overflow: hidden;
    line-height: 1;
    padding: 0;
    font-size: 11.375px;
}

.data-card .mud-table-body .mud-table-cell.users-table-photo-cell .user-avatar-initials {
    display: grid;
    place-items: center;
    width: 100%;
    height: 100%;
    line-height: 1;
    font-weight: 600;
    letter-spacing: 0.01em;
    text-box-trim: trim-both;
    text-box-edge: cap alphabetic;
    text-box: trim-both cap alphabetic;
}

.data-card .mud-table-body .mud-table-cell.users-table-photo-cell .mud-avatar .mud-image,
.data-card .mud-table-body .mud-table-cell.users-table-photo-cell .mud-avatar .user-avatar-image,
.data-card .mud-table-body .mud-table-cell.users-table-photo-cell .mud-avatar .user-avatar-image img {
    border-radius: var(--app-radius-circle);
}

/* A face INSIDE an ordinary data cell, beside the name it belongs to (Employees' משתמש מנהל) —
   as opposed to users-table-photo-cell above, which is a photo COLUMN of its own. The wrapper is
   inline-flex, so the gap is the separator (no comma, no bullet) and it flips with RTL for free;
   min-width:0 keeps it from widening the column past the text it wraps. The same pair is used by
   the expanded-row detail line, so the pairing survives hiding the column. */
.cell-user {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-width: 0;
    vertical-align: middle;
}

/* ⚠️ The size is the whole point of this rule. MudBlazor's Size.Small is 2rem (32px), which on a
   50px row leaves almost no air above and below and reads as a photo column rather than as a mark
   on a name. 24px is the industry's "small avatar" step (Atlassian, Ant Design and Salesforce all
   ladder 16 / 24 / 32 / 40) and the one this app uses in a grid cell.
   0.45, where every other avatar in the app sits near 0.33, because THIS avatar — and only this
   one — draws a SINGLE letter (`SingleInitial`): two glyphs blur together at 24px, and one glyph
   has to fill more of the circle to carry the same weight two did. Every larger avatar (topbar,
   photo column, profile picker) keeps both initials at its own factor; do not sync them. */
.data-card .mud-table-body .mud-table-cell .mud-avatar.cell-user-avatar,
.table-expanded-details .mud-avatar.cell-user-avatar {
    --cell-user-avatar: 21px;
    flex: 0 0 auto;
    margin: 0;
    padding: 0;
    width: var(--cell-user-avatar);
    height: var(--cell-user-avatar);
    min-width: var(--cell-user-avatar);
    min-height: var(--cell-user-avatar);
    max-width: var(--cell-user-avatar);
    max-height: var(--cell-user-avatar);
    border-radius: var(--app-radius-circle);
    overflow: hidden;
    line-height: 1;
    font-size: calc(var(--cell-user-avatar) * 0.45);
}

.data-card .mud-table-body .mud-table-cell .mud-avatar.cell-user-avatar .mud-image,
.data-card .mud-table-body .mud-table-cell .mud-avatar.cell-user-avatar .user-avatar-image,
.data-card .mud-table-body .mud-table-cell .mud-avatar.cell-user-avatar .user-avatar-image img,
.table-expanded-details .mud-avatar.cell-user-avatar .mud-image,
.table-expanded-details .mud-avatar.cell-user-avatar .user-avatar-image,
.table-expanded-details .mud-avatar.cell-user-avatar .user-avatar-image img {
    border-radius: var(--app-radius-circle);
}

.data-card .mud-table-head .mud-table-cell.users-table-actions-header,
.data-card .mud-table-body .mud-table-cell.users-table-actions-cell {
    --users-actions-btn: 26.25px;
    --users-actions-inner-gap: 0.4rem;
    --users-actions-gap: 0.625rem;
    --users-actions-gap-x: calc(var(--users-actions-gap) + 8.75px);
    --users-actions-width: calc(2 * var(--users-actions-btn) + var(--users-actions-inner-gap) + 2 * var(--users-actions-gap-x));
    width: var(--users-actions-width);
    min-width: var(--users-actions-width);
    max-width: var(--users-actions-width);
    padding: var(--users-actions-gap) var(--users-actions-gap-x);
    box-sizing: border-box;
    text-align: center;
    vertical-align: middle;
}

.data-card .mud-table-body .mud-table-cell.users-table-actions-cell {
    line-height: 0;
}

/* Admins get a third row action (impersonate) — widen the fixed actions column to fit. */
.data-card .users-actions-3 .mud-table-head .mud-table-cell.users-table-actions-header,
.data-card .users-actions-3 .mud-table-body .mud-table-cell.users-table-actions-cell {
    --users-actions-width: calc(3 * var(--users-actions-btn) + 2 * var(--users-actions-inner-gap) + 2 * var(--users-actions-gap-x));
}

/* Four row actions (Excel periods: customer report, cancel device, delete device, resend email). */
.data-card .users-actions-4 .mud-table-head .mud-table-cell.users-table-actions-header,
.data-card .users-actions-4 .mud-table-body .mud-table-cell.users-table-actions-cell {
    --users-actions-width: calc(4 * var(--users-actions-btn) + 3 * var(--users-actions-inner-gap) + 2 * var(--users-actions-gap-x));
}

/* Five row actions (hours statements: customer report, time report, entries, send, delete). */
.data-card .users-actions-5 .mud-table-head .mud-table-cell.users-table-actions-header,
.data-card .users-actions-5 .mud-table-body .mud-table-cell.users-table-actions-cell {
    --users-actions-width: calc(5 * var(--users-actions-btn) + 4 * var(--users-actions-inner-gap) + 2 * var(--users-actions-gap-x));
}

/* Six row actions (hourly agreements: customer report, time, gather hours, move an order, edit, delete). */
.data-card .users-actions-6 .mud-table-head .mud-table-cell.users-table-actions-header,
.data-card .users-actions-6 .mud-table-body .mud-table-cell.users-table-actions-cell {
    --users-actions-width: calc(6 * var(--users-actions-btn) + 5 * var(--users-actions-inner-gap) + 2 * var(--users-actions-gap-x));
}

.user-avatar-image,
.user-avatar-image .mud-image,
.user-avatar-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.mud-avatar.user-avatar-has-image {
    overflow: hidden;
    padding: 0;
    background: transparent;
}

.mud-avatar.user-avatar-has-image .user-avatar-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.mud-avatar-filled.user-avatar-material {
    color: var(--app-on-primary);
    box-shadow: var(--app-shadow-inset-pressed);
    align-items: center;
    justify-content: center;
    line-height: 1;
    padding: 0;
}

.user-avatar-initials {
    display: grid;
    place-items: center;
    width: 100%;
    height: 100%;
    line-height: 1;
    font-weight: 600;
    letter-spacing: 0.01em;
    text-box-trim: trim-both;
    text-box-edge: cap alphabetic;
    text-box: trim-both cap alphabetic;
}

@supports not (text-box-edge: cap alphabetic) {
    /* Browsers without text-box trimming (e.g. Firefox): nudge the caps down to
       their optical center. em-based so it scales with the avatar at every size. */
    .user-avatar-initials {
        transform: translateY(0.06em);
    }
}

.profile-image-picker {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
}

.profile-image-picker-trigger {
    display: inline-flex;
    margin: 0;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: default;
    border-radius: var(--app-radius-circle);
}

.profile-image-picker-trigger.is-clickable {
    cursor: pointer;
}

.profile-image-picker-avatar {
    position: relative;
    display: inline-flex;
    flex-shrink: 0;
    overflow: hidden;
    border-radius: var(--app-radius-circle);
    line-height: 0;
    transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.profile-image-picker-avatar.is-small {
    width: 1.5rem;
    height: 1.5rem;
}

.profile-image-picker-avatar.is-medium {
    width: 2.5rem;
    height: 2.5rem;
}

.profile-image-picker-avatar.is-large {
    width: 3.5rem;
    height: 3.5rem;
}

.profile-image-picker-avatar .mud-avatar {
    width: 100%;
    height: 100%;
    min-width: 0;
    min-height: 0;
}

.profile-image-picker-trigger.is-clickable:not(:has(.profile-image-picker-overlay--delete)):hover .profile-image-picker-avatar,
.profile-image-picker-trigger.is-clickable:not(:has(.profile-image-picker-overlay--delete)):focus-within .profile-image-picker-avatar {
    transform: scale(1.04);
    box-shadow: var(--app-shadow-avatar-hover);
}

.profile-image-picker-trigger.is-clickable:has(.profile-image-picker-overlay--delete):hover .profile-image-picker-avatar {
    transform: scale(1.04);
    box-shadow: var(--app-shadow-avatar-hover);
}

.profile-image-picker-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--app-radius-circle);
    background: var(--app-overlay-scrim);
    color: var(--app-on-primary);
    opacity: 0;
    transition: opacity 0.2s ease;
    pointer-events: none;
}

.profile-image-picker-overlay .mud-icon-root {
    font-size: 1.125rem;
}

.profile-image-picker-avatar.is-medium .mud-avatar {
    font-size: 0.8125rem;
}

.profile-image-picker-avatar.is-large .profile-image-picker-overlay .mud-icon-root {
    font-size: 1.5rem;
}

.profile-image-picker-avatar.is-small .profile-image-picker-overlay .mud-icon-root {
    font-size: 0.875rem;
}


.profile-image-picker-overlay--delete {
    pointer-events: auto;
    cursor: pointer;
    border: 0;
}

.profile-image-picker-overlay--delete .mud-icon-root {
    font-size: 1.125rem;
}

.profile-image-picker-avatar.is-large .profile-image-picker-overlay--delete .mud-icon-root {
    font-size: 1.5rem;
}

.profile-image-picker-avatar.is-small .profile-image-picker-overlay--delete .mud-icon-root {
    font-size: 0.875rem;
}

.profile-image-picker-trigger.is-clickable:not(:has(.profile-image-picker-overlay--delete)):hover .profile-image-picker-overlay,
.profile-image-picker-trigger.is-clickable:not(:has(.profile-image-picker-overlay--delete)):focus-within .profile-image-picker-overlay {
    opacity: 1;
}

.profile-image-picker-trigger.is-clickable:has(.profile-image-picker-overlay--delete):hover .profile-image-picker-overlay {
    opacity: 1;
}

.profile-image-picker-input {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.profile-image-picker-remove {
    border: 0;
    background: transparent;
    color: var(--app-table-sort-active);
    font-size: 0.8125rem;
    padding: 0;
    cursor: pointer;
}

.profile-image-picker-remove:hover {
    text-decoration: underline;
}

.user-form-field--photo {
    align-items: flex-start;
}

.user-form-field--photo .profile-image-picker {
    align-items: flex-start;
}

.user-menu-header .profile-image-picker {
    align-items: center;
    align-self: stretch;
    flex-shrink: 0;
}

.user-menu-header .profile-image-picker-trigger {
    display: flex;
    align-items: center;
}

.user-menu-header .profile-image-picker-avatar.is-medium {
    width: 3.5rem;
    height: 3.5rem;
}

.user-menu-header .profile-image-picker-avatar.is-medium .mud-avatar {
    font-size: 1.125rem;
}

.users-table-shell > .users-table,
.users-table-shell > .mud-table {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    height: auto;
    overflow: hidden;
}

.users-table-shell .mud-table-container {
    /*
     * Grow to fill space above pager; scroll when rows overflow.
     * Table root margin-bottom keeps a fixed gap above the pager separator.
     */
    flex: 1 1 auto;
    min-height: 0;
    overflow-x: auto;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/*
 * The list-table header stays put by making the whole HEAD ROW GROUP sticky, not each cell
 * [developer-reported 2026-11-03 and 2026-11-04 — the third mechanism, the one that holds].
 * Under border-collapse the 1px column strip between two head cells is the TABLE's, not a cell's,
 * so with sticky CELLS it did not travel: the scrolled body showed through it (2026-09-04), and
 * every patch over it — an outline, then ::after lines — was a SECOND painter of a line the table
 * already draws, landing a device pixel off or doubling the 9% white (a bright or 2px divider
 * beside the body's 1px one; at 125% scaling no offset fixes it, the cell itself is snapped first).
 * A sticky <thead> carries its own collapsed borders with it, so the header's column dividers are
 * the SAME borders as the body's: measured at 125% in a headed browser, all 8 dividers exactly
 * 1 device px on the body rule's own pixel, unscrolled and scrolled, nothing showing through.
 * Do not go back to sticky cells or pseudo-element lines.
 * The header's top and bottom rules are inset shadows on the cells: the border between the head
 * and the first body row stays behind when the head sticks, so it cannot be the bottom rule.
 */
.data-card .users-table-shell .mud-table-head {
    position: sticky;
    top: 0;
    z-index: 3;
}

.data-card .users-table-shell .mud-table-head .mud-table-cell {
    position: relative;
    top: auto;
    background: var(--app-table-head);
    box-shadow: inset 0 1px 0 var(--app-border), inset 0 -1px 0 var(--app-border);
}

/*
 * The column line of a sticky header / totals row is DRAWN, on the body line's own pixel
 * [developer-reported 2026-11-04, the «שער» / «תאריך שער» edge on /expenses]. With the row group
 * sticky, the table's own border is painted UNDER the row, and showed only through the hair-thin
 * gap between two cells' fills — and on some fractional edges the two fills round shut over it:
 * measured 2 of 11 edges with no header line on /expenses. So each cell draws its end-side line
 * itself, opaque (the border colour over the table fill, so it reads exactly like the body line
 * whether or not the border shows beside it), one device pixel wide.
 * WHERE — measured, not tuned: the body's 1px line lands on the pixel ceil(edge) − 1 (edge = the
 * cell's end-side rect edge), and a positioned box's edge is ROUNDED. A box whose start sits at
 * edge − 0.502 device px rounds onto that same pixel for every fraction (all but edges within
 * 0.002px past a whole pixel). The cell's padding box sits --line-half inside its rect, hence
 * the offset below. Measured 1×: 79 of 79 edges match the body on /expenses, /items, /customers
 * and /services at four window widths, unscrolled and scrolled; other scales use the selection
 * mark's per-scale values (§selection) and are NOT re-measured.
 * ⚠️ The cells must not be stacking contexts (MudBlazor's sticky header gives every head cell a
 * z-index): a later cell would then paint its fill over this cell's line. z-index: auto on the
 * cells, z-index: 1 on the line. The end-most cell draws none: the table's side frame is there.
 */
.data-card .users-table-shell .mud-table-head .mud-table-cell,
.form-child-table thead th {
    --line-devpx: 1px;     /* one device pixel, in CSS px */
    --line-half: 0.5px;    /* half of the floored grid rule = floor(dpr) / dpr / 2 */
}

@media (min-resolution: 1.2dppx) and (max-resolution: 1.3dppx) {  /* 125% */
    .data-card .users-table-shell .mud-table-head .mud-table-cell,
    .form-child-table thead th { --line-devpx: 0.8px; --line-half: 0.4px; }
}

@media (min-resolution: 1.45dppx) and (max-resolution: 1.55dppx) { /* 150% */
    .data-card .users-table-shell .mud-table-head .mud-table-cell,
    .form-child-table thead th { --line-devpx: 0.6667px; --line-half: 0.3333px; }
}

@media (min-resolution: 1.7dppx) and (max-resolution: 1.8dppx) {   /* 175% */
    .data-card .users-table-shell .mud-table-head .mud-table-cell,
    .form-child-table thead th { --line-devpx: 0.5714px; --line-half: 0.2857px; }
}

@media (min-resolution: 1.95dppx) and (max-resolution: 2.05dppx) { /* 200% */
    .data-card .users-table-shell .mud-table-head .mud-table-cell,
    .form-child-table thead th { --line-devpx: 0.5px; --line-half: 0.5px; }
}

.data-card .users-table-shell .mud-table-head .mud-table-cell,
.form-child-table thead th {
    z-index: auto;
}

.data-card .users-table-shell .mud-table-head .mud-table-cell::after,
.form-child-table thead th::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    inset-inline-end: calc(-1 * (var(--line-half) + 0.502 * var(--line-devpx)));
    width: var(--line-devpx);
    background: linear-gradient(var(--app-border), var(--app-border)), var(--app-card);
    z-index: 1;
    pointer-events: none;
}

.data-card .users-table-shell .mud-table-head .mud-table-cell:last-child::after,
.form-child-table thead th:last-child::after {
    content: none;
}

/*
 * …and the body is CUT at the header's lower edge while a cell is selected [developer-reported
 * 2026-11-04]. The column dividers seen in a scrolled header are the BODY's own grid lines, seen
 * through the 1px strip the table owns (a red header border was measured NOT travelling with the
 * sticky thead) — which is right, they sit on the exact pixel. But the selection mark is drawn ON
 * the grid pixel (§selection), so a selected cell scrolled under the header showed its frame
 * through every strip as a blue line (measured: (132,157,255) on the divider pixel). Covering the
 * strip from the header side cannot be done: a line placed by position lands a device pixel off
 * on fractional columns, and a header-colour cover hides the dividers too — six variants measured.
 * So nothing of the body is painted under the header at all: cellDragSelect.js measures how much
 * of the body is hidden under the sticky header (--table-clip-top) and under a sticky footer
 * (--table-clip-bottom: AllGoods / ElitaPro totals rows leaked the same way, measured 2026-11-04),
 * on every scroll and every press, and the body clips that much off. -8px on a side = no cut, so
 * the outermost cells' marks are not clipped. Only while a cell is selected — nothing else of the
 * body shows through, so an unselected table is not touched.
 * ⚠️ Not verified: one stale frame while scrolling fast (the scroll event lands a frame after the
 * compositor moved), which could show the mark through the strip, or cut the row under the
 * header, for that one frame.
 */
.data-card .users-table-shell:has(.cell-selected) .mud-table-body {
    clip-path: inset(var(--table-clip-top, 0px) -8px var(--table-clip-bottom, -8px) -8px);
}

/*
 * List tables: one bottom line from the last row cells (default cell border-bottom).
 * Side frame on table-root; no bottom on root/pager/container (prevents double lines).
 *
 * Fit-to-card column widths (THE app-wide mechanism — ui-conventions §Tables): the table
 * shrinks to the card instead of holding a max-content floor. Data cells are nowrap — a
 * cell's one-line width is the hard floor, cells never wrap — while header labels may wrap
 * to AT MOST 2 lines (1–2-word labels have a single break opportunity; 3+ word labels render
 * via <HeaderLabel> as two nowrap halves whose separating space is the only break point).
 * Short-value columns are pinned to their minimum with th-col-pin; the container's horizontal
 * scrollbar appears only when the sum of the column floors exceeds the card.
 */
.data-card .users-table-shell .mud-table-container > .mud-table-root {
    height: auto;
    min-width: 0;
    width: 100%;
    margin-bottom: 17.5px;
    border: 1px solid var(--app-border);
    border-top: none;
    border-bottom: none;
}

.data-card .users-table-shell .mud-table-body .mud-table-cell {
    white-space: nowrap;
}

/* Expanded child row (hidden-column details) spans the table and must keep wrapping. */
.data-card .users-table-shell .table-expanded-row > td {
    white-space: normal;
}

/* Headers absorb the squeeze: labels wrap (≤2 lines, see above); balance evens the split. */
.data-card .users-table-shell .mud-table-head .mud-table-cell {
    white-space: normal;
    text-wrap: balance;
}

/* A dense column (.col-dense on its header AND its cells): the side padding drops from 15.75px to
   6px. On a pinned number column the HEADER is the floor (title + sort arrow + filter button), so
   the padding is the one part left to cut — ~19px a column, no per-page width. Put on the short
   columns the developer picks; first on /time-entries' pinned columns [developer-directed
   2026-11-05]. (0,4,0) beats the generic (0,2,0)/(0,3,0) cell rules. */
.data-card .users-table-shell .mud-table-head .mud-table-cell.col-dense,
.data-card .users-table-shell .mud-table-body .mud-table-cell.col-dense {
    padding-inline: 6px;
}

/* Generic pinned-compact column for list tables. width:1px (NOT 1%) is the auto-layout
   shrink-to-min-content trick — a percentage inflates the whole table until 1% of it fits
   the content. Put it on short-value columns — number / date / time / bool / status / short
   code — so each always sits at its TRUE minimum: the wider of the one-line cell value and
   the ≤2-row title (the title folds to a second row whenever that makes the column narrower —
   same wrap rules as every list-table header). The width they give up flows to the unpinned
   text columns. A table whose columns are ALL short pins NOTHING (the surplus then spreads
   evenly instead of ballooning one column). Pin rule per column type: ui-conventions §Tables. */
.users-table-shell th.th-col-pin {
    width: 1px;
}

/* HeaderLabel two-line split: the wrapper guarantees the split works in any header context —
   the ONLY break point a long title ever has is the space between its two nowrap halves. */
.th-label-split {
    white-space: normal;
    text-wrap: balance;
}

.th-label-seg {
    white-space: nowrap;
}

/* =========================================================================
 * 09. TABLE: COLUMNS MENU, EXPAND ROWS & PAGER
 * ========================================================================= */
.table-columns-menu-row {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    width: 100%;
    height: 19.25px;
    min-height: 19.25px;
}

.table-columns-menu-row.is-clickable {
    cursor: pointer;
}

.table-columns-menu-row.is-disabled {
    cursor: default;
}

.table-columns-checkbox-wrap {
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
}

.table-columns-menu-item.mud-menu-item {
    min-height: 19.25px !important;
    height: 19.25px !important;
    max-height: 19.25px !important;
    padding: 0 0.45rem;
    box-sizing: border-box;
}

.table-columns-menu-item.mud-menu-item .mud-list-item {
    min-height: 19.25px !important;
    height: 19.25px !important;
    max-height: 19.25px !important;
    padding: 0;
}

.table-columns-checkbox {
    margin: 0;
    padding: 0;
}

.table-columns-checkbox.mud-checkbox {
    height: 19.25px;
    min-height: 19.25px;
    max-height: 19.25px;
}

.table-columns-checkbox .mud-button-root.mud-icon-button {
    width: 15.75px;
    height: 15.75px;
    min-width: 15.75px;
    min-height: 15.75px;
    padding: 0;
}

.table-columns-checkbox .mud-icon-root {
    font-size: 0.9375rem;
}

.table-columns-label {
    font-size: var(--app-text-menu);
    line-height: 19.25px;
    flex: 1 1 auto;
    min-width: 0;
    user-select: none;
}

.table-columns-reset-item.mud-menu-item {
    min-height: 19.25px !important;
    height: 19.25px !important;
    max-height: 19.25px !important;
    padding: 0 0.45rem;
    font-size: 0.8125rem;
    line-height: 19.25px;
    color: var(--app-primary);
    box-sizing: border-box;
}

.mud-menu-list:has(.table-columns-menu-item) {
    padding-top: 0.15rem;
    padding-bottom: 0.15rem;
}

/* Columns menu — center below the Columns button (same approach as Refresh menu). */
.table-columns-menu {
    display: inline-flex;
}

.table-columns-popover.mud-popover-anchor-bottom-right,
.mud-popover.mud-popover-anchor-bottom-right:has(.table-columns-menu-item) {
    transform: translateX(calc((100% - var(--toolbar-header-button-width)) / 2));
}

.table-columns-divider {
    margin: 0.1rem 0;
}

/* The row's hidden-column panel opens from its +/− button only (RowExpandToggle), never from a
   row click [developer-directed 2026-10-24] — so no row wears the pointer cursor. */
.users-table .mud-table-body .mud-table-row {
    cursor: default;
}

/* The +/− column at the row's leading edge: as narrow as its button. The button is the sidebar's
   +/− sign (.sidebar-sign) grown a little for a table row — a bordered square in the primary color. */
.data-card .mud-table-head .mud-table-cell.table-expand-header,
.data-card .mud-table-body .mud-table-cell.table-expand-cell {
    width: 1px;
    padding-inline: 0.5rem;
    text-align: center;
    vertical-align: middle;
}

.data-card .mud-table-body .mud-table-cell.table-expand-cell {
    line-height: 0;
}

.table-expand-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    width: 18px;
    height: 18px;
    padding: 0;
    border: 1px solid var(--app-border);
    border-radius: 4px;
    background: transparent;
    color: var(--app-primary);
    cursor: pointer;
}

.table-expand-btn:hover,
.table-expand-btn.is-expanded {
    border-color: var(--app-primary);
}

.table-expand-btn .mud-icon-root {
    width: 14px;
    height: 14px;
    font-size: 14px;
}

.table-expanded-row > td {
    padding: 0;
    border-bottom: 1px solid var(--app-border);
    background: var(--app-table-expanded);
}

.table-expanded-details {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    padding: 0.65rem 0.85rem;
}

.table-expanded-field {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 0.35rem;
    min-width: 0;
}

.table-expanded-label {
    color: var(--app-table-column-name);
    font-size: 0.8125rem;
    font-weight: 700;
    line-height: 1.35;
    flex-shrink: 0;
}

.table-expanded-label::after {
    content: ":";
}

.table-expanded-value {
    font-size: 0.8125rem;
    line-height: 1.35;
    word-break: break-word;
    min-width: 0;
}

/* Empty filter result: MudBlazor renders th.mud-table-empty-row, not td.mud-table-cell */
.data-card .users-table-shell .mud-table-container > .mud-table-root .mud-table-empty-row {
    height: 43.75px;
    box-sizing: border-box;
    border-bottom: 1px solid var(--app-border);
    border-inline-start: none;
    border-inline-end: none;
}

.users-table-shell .mud-table-pagination {
    flex-shrink: 0;
    margin-top: auto;
    padding: 0.35rem 0 0.15rem;
    background: var(--app-card);
    border-top: 1px solid var(--app-border);
}

.users-table-shell .mud-table-pagination .app-pager {
    border-top: none;
}

.app-pager {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0.6rem 1rem;
    border-top: none;
    background: var(--app-card);
    font-size: 11.375px;
}

.app-pager-left {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.app-pager-rows-label {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--app-muted);
    font-size: 11.375px;
    white-space: nowrap;
}

.app-pager-rows-select {
    height: 24.5px;
    padding: 0 calc(var(--dropdown-caret-size) + 0.85rem) 0 0.5rem;
    border: 1px solid var(--form-control-border);
    border-radius: var(--app-radius-form-box);
    background-color: var(--app-surface-raised);
    background-image: var(--dropdown-caret-svg);
    background-repeat: no-repeat;
    background-position: right 0.35rem center;
    background-size: var(--dropdown-caret-size) auto;
    color: var(--app-text);
    font-size: 11.375px;
    font-family: inherit;
    cursor: pointer;
    outline: none;
    appearance: none;
    -webkit-appearance: none;
}

.app-pager-rows-menu {
    display: inline-flex;
}

.app-pager-rows-popover {
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius);
    min-width: 56px !important;
    width: auto !important;
}

.app-pager-rows-list {
    padding: 0.25rem;
    min-width: 56px;
    box-sizing: border-box;
}

.app-pager-rows-item.mud-menu-item {
    min-height: 21.875px !important;
    height: 21.875px !important;
    max-height: 21.875px !important;
    padding: 0 0.5rem;
    font-size: 11.375px;
    line-height: 21.875px;
    justify-content: center;
    text-align: center;
    box-sizing: border-box;
    border-radius: var(--app-radius);
}

.app-pager-rows-item.mud-menu-item .mud-list-item {
    min-height: 21.875px !important;
    height: 21.875px !important;
    max-height: 21.875px !important;
    padding: 0;
}

.app-pager-rows-item:hover,
.app-pager-rows-item:focus-visible {
    background: var(--app-primary-soft);
}

.app-pager-rows-item.is-selected {
    background: var(--app-primary-soft);
    color: var(--app-primary);
    font-weight: 600;
}

.app-pager-info {
    color: var(--app-muted);
    font-size: 11.375px;
    white-space: nowrap;
}

.app-pager-total {
    color: var(--app-primary);
    font-weight: 600;
}

.app-pager-nav {
    display: flex;
    align-items: center;
    gap: 2.625px;
}

.app-pager-text {
    padding: 0 0.55rem;
    height: 26.25px;
    border: none;
    background: transparent;
    color: var(--app-text);
    font-size: 11.375px;
    font-weight: 500;
    cursor: pointer;
    border-radius: var(--app-radius);
    transition: background-color 140ms ease, color 140ms ease;
    font-family: inherit;
}

.app-pager-text:hover:not(:disabled) {
    background: var(--app-primary-soft);
    color: var(--app-primary);
}

.app-pager-text:disabled {
    color: var(--app-text-disabled);
    cursor: default;
}

.app-pager-num {
    width: 26.25px;
    height: 26.25px;
    border: none;
    background: transparent;
    color: var(--app-text);
    font-size: 11.375px;
    font-weight: 500;
    cursor: pointer;
    border-radius: var(--app-radius-icon-button);
    transition: background-color 140ms ease, color 140ms ease;
    font-family: inherit;
}

.app-pager-num:hover:not(.app-pager-active) {
    background: var(--app-primary-soft);
    color: var(--app-primary);
}

.app-pager-active {
    background: var(--app-primary);
    color: var(--app-color-page-button-hover-text);
}

/* =========================================================================
 * 10. CELL COMPONENTS: UTILIZATION, ROLE PILL
 * ========================================================================= */
.utilization-bar {
    width: 63px;
    height: 7px;
    border-radius: var(--app-radius-pill);
    overflow: hidden;
    flex-shrink: 0;
}

.utilization-bar-fill {
    height: 100%;
    border-radius: var(--app-radius-pill);
}

.utilization-bar--success {
    background: var(--app-green-soft);
}

.utilization-bar--success .utilization-bar-fill {
    background: var(--app-green);
}

.utilization-bar--warning {
    background: var(--app-warning-soft);
}

.utilization-bar--warning .utilization-bar-fill {
    background: var(--app-warning);
}

.utilization-bar--error {
    background: var(--app-danger-soft);
}

.utilization-bar--error .utilization-bar-fill {
    background: var(--app-danger);
}

.utilization-bar--empty {
    background: var(--app-neutral-soft);
}

.utilization-bar--empty .utilization-bar-fill {
    background: var(--app-icon-muted);
}

.row-actions {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
}

/* An empty slot for a row button that only some rows have: it keeps the place, so the
   other buttons stay in one column on every row. */
.row-actions .action-btn-slot {
    visibility: hidden;
}

.role-chip-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}

.role-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 4.5rem;
    padding: 0.22rem 0.8rem;
    border-radius: var(--app-radius-pill);
    color: var(--app-on-primary);
    font-size: 0.8125rem;
    font-weight: 400;
    letter-spacing: normal;
    line-height: 1.2;
    box-shadow: var(--app-shadow-inset-pressed);
}

/* =========================================================================
 * 11. BUTTONS: ROW ACTIONS & SOFT BUTTONS
 * ========================================================================= */
.action-btn.mud-icon-button {
    width: var(--action-btn-size);
    height: var(--action-btn-size);
    border-radius: var(--app-radius-icon-button);
    /* Border - the same switch the toolbar buttons ride. Declared ONCE here for every
       variant; each supplies its own --control-accent in its own rule below.
       WARNING: box-sizing is stated rather than inherited - this is a fixed square, so the
       1px must come OUT of it and never be added to it. MudBlazor's own icon button already
       computes border-box, so this is a documented no-op that pins the guarantee. */
    box-sizing: border-box;
    border-width: 1px;
    border-style: solid;
    border-color: color-mix(in srgb,
        var(--control-border-color, var(--control-accent, currentColor)) var(--app-control-border-strength),
        transparent);
    transition: background-color 160ms ease, color 160ms ease, border-color 160ms ease;
}

.action-btn.mud-icon-button .mud-icon-root {
    font-size: var(--action-btn-icon-size);
    transition: color 160ms ease;
}

.action-btn-edit.mud-icon-button {
    --control-accent: var(--app-warning);
    background: var(--app-warning-soft);
    color: var(--app-warning);
}

.action-btn-edit.mud-icon-button .mud-icon-root {
    color: var(--app-warning);
}

.action-btn-edit.mud-icon-button:hover:not(:disabled) {
    border-color: var(--app-warning);
    background: var(--app-warning);
    color: var(--app-color-page-button-hover-text);
}

.action-btn-edit.mud-icon-button:hover:not(:disabled) .mud-icon-root {
    color: var(--app-color-page-button-hover-text);
}

.action-btn-delete.mud-icon-button {
    --control-accent: var(--app-danger);
    background: var(--app-danger-soft);
    color: var(--app-danger);
}

.action-btn-delete.mud-icon-button .mud-icon-root {
    color: var(--app-danger);
}

.action-btn-delete.mud-icon-button:hover:not(:disabled) {
    border-color: var(--app-danger);
    background: var(--app-danger);
    color: var(--app-color-page-button-hover-text);
}

.action-btn-delete.mud-icon-button:hover:not(:disabled) .mud-icon-root {
    color: var(--app-color-page-button-hover-text);
}

/* Impersonate ("התחבר כמשתמש") row action — indigo twin of the edit/delete buttons. */
.action-btn-impersonate.mud-icon-button {
    --control-accent: var(--app-indigo);
    background: var(--app-indigo-soft);
    color: var(--app-indigo);
}

.action-btn-impersonate.mud-icon-button .mud-icon-root {
    color: var(--app-indigo);
}

.action-btn-impersonate.mud-icon-button:hover:not(:disabled) {
    border-color: var(--app-indigo);
    background: var(--app-indigo);
    color: var(--app-color-page-button-hover-text);
}

.action-btn-impersonate.mud-icon-button:hover:not(:disabled) .mud-icon-root {
    color: var(--app-color-page-button-hover-text);
}

/* Open-and-read row action (the expenses list's view button) — the info-blue twin of
   the edit/delete buttons. Its own colour because it is its own kind of act: amber says "you are
   about to change this row", and a button that only OPENS a reading must not borrow that. */
.action-btn-view.mud-icon-button {
    --control-accent: var(--app-info);
    background: var(--app-info-soft);
    color: var(--app-info);
}

.action-btn-view.mud-icon-button .mud-icon-root {
    color: var(--app-info);
}

.action-btn-view.mud-icon-button:hover:not(:disabled) {
    border-color: var(--app-info);
    background: var(--app-info);
    color: var(--app-color-page-button-hover-text);
}

.action-btn-view.mud-icon-button:hover:not(:disabled) .mud-icon-root {
    color: var(--app-color-page-button-hover-text);
}

/* Link-documents action (the documents list's «קשר מסמכים», the linked-documents box's link
   button) — green, like the legacy link icon. The document lines' / payments' «+» header button
   (.action-btn-add) wears the same green: green is the app's "add" colour. */
.action-btn-link.mud-icon-button,
.action-btn-add.mud-icon-button {
    --control-accent: var(--app-green);
    background: var(--app-green-soft);
    color: var(--app-green);
}

.action-btn-link.mud-icon-button .mud-icon-root,
.action-btn-add.mud-icon-button .mud-icon-root {
    color: var(--app-green);
}

.action-btn-link.mud-icon-button:hover:not(:disabled),
.action-btn-add.mud-icon-button:hover:not(:disabled) {
    border-color: var(--app-green);
    background: var(--app-green);
    color: var(--app-color-page-button-hover-text);
}

.action-btn-link.mud-icon-button:hover:not(:disabled) .mud-icon-root,
.action-btn-add.mud-icon-button:hover:not(:disabled) .mud-icon-root {
    color: var(--app-color-page-button-hover-text);
}

/* Order-adjustment action (the orders list's «עדכון הזמנה») — orange: an amount being changed
   after issue, like the in-progress status pill's "still moving" colour. */
.action-btn-adjust.mud-icon-button {
    --control-accent: var(--app-warning);
    background: var(--app-warning-soft);
    color: var(--app-warning);
}

.action-btn-adjust.mud-icon-button .mud-icon-root {
    color: var(--app-warning);
}

.action-btn-adjust.mud-icon-button:hover:not(:disabled) {
    border-color: var(--app-warning);
    background: var(--app-warning);
    color: var(--app-color-page-button-hover-text);
}

.action-btn-adjust.mud-icon-button:hover:not(:disabled) .mud-icon-root {
    color: var(--app-color-page-button-hover-text);
}

/* Clear-filters (toolbar reset) button: keyboard focus matches hover. */
.action-btn-delete.toolbar-reset-button.mud-icon-button:focus-visible:not(:disabled) {
    border-color: var(--app-danger);
    background: var(--app-danger);
    color: var(--app-color-page-button-hover-text);
}

.action-btn-delete.toolbar-reset-button.mud-icon-button:focus-visible:not(:disabled) .mud-icon-root {
    color: var(--app-color-page-button-hover-text);
}

/* Drill-down / "this button acts on the record" — the brand blue of the set. Worn by the
   copy-selection button; the same soft->solid chip scheme as edit / delete / mint. */
.action-btn-primary.mud-icon-button {
    --control-accent: var(--app-primary);
    background: var(--app-primary-soft);
    color: var(--app-primary);
}

.action-btn-primary.mud-icon-button .mud-icon-root {
    color: var(--app-primary);
}

.action-btn-primary.mud-icon-button:hover:not(:disabled) {
    border-color: var(--app-primary);
    background: var(--app-primary);
    color: var(--app-color-page-button-hover-text);
}

.action-btn-primary.mud-icon-button:hover:not(:disabled) .mud-icon-root {
    color: var(--app-color-page-button-hover-text);
}

/* Filter-by-value ("סינון זהה") button — same shape as the clear-filters button, mint colour. */
.action-btn-mint.mud-icon-button {
    --control-accent: var(--app-mint-muted);
    background: var(--app-mint-soft);
    color: var(--app-mint-muted);
}

.action-btn-mint.mud-icon-button .mud-icon-root {
    color: var(--app-mint-muted);
}

.action-btn-mint.mud-icon-button:hover:not(:disabled) {
    border-color: var(--app-mint);
    background: var(--app-mint);
    color: var(--app-color-page-button-hover-text);
}

.action-btn-mint.mud-icon-button:hover:not(:disabled) .mud-icon-root {
    color: var(--app-color-page-button-hover-text);
}

/* Filter-by-value button: keyboard focus matches hover. */
.action-btn-mint.toolbar-filter-button.mud-icon-button:focus-visible:not(:disabled) {
    border-color: var(--app-mint);
    background: var(--app-mint);
    color: var(--app-color-page-button-hover-text);
}

.action-btn-mint.toolbar-filter-button.mud-icon-button:focus-visible:not(:disabled) .mud-icon-root {
    color: var(--app-color-page-button-hover-text);
}

/* Copy-selection button: keyboard focus matches hover, the same pairing the reset and
   filter-by-value buttons beside it carry. */
.action-btn-primary.toolbar-copy-button.mud-icon-button:focus-visible:not(:disabled) {
    border-color: var(--app-primary);
    background: var(--app-primary);
    color: var(--app-color-page-button-hover-text);
}

.action-btn-primary.toolbar-copy-button.mud-icon-button:focus-visible:not(:disabled) .mud-icon-root {
    color: var(--app-color-page-button-hover-text);
}

/* Legacy "סינון זהה": click a cell to select it, then the toolbar filter button filters by its
   value. Selectable cells wear Excel's thick cross (`cursor: cell`) [developer-directed
   2026-10-24], not the hand: the hand says "a link, click to go somewhere", the cross says "a
   cell — click to select, drag to sweep", which is what every data cell does. The selected one
   is tinted + outlined. */
.selectable-cell {
    cursor: cell;
}

/* Excel's data bar (`DataBar`): a bar behind a number cell's value, as long as the value is
   against the biggest one on the page. It starts at the cell's inline start, where the value
   sits. The box reaches 6px into the cell's padding on both sides and pads the value back by
   the same 6px, so the value stays where every other cell has it and keeps a gap from the
   bar's edge. The fill is mixed in the rule (not a token) so it follows the theme's
   --app-primary in light and dark. */
.data-bar {
    position: relative;
    display: block;
    margin-inline: -6px;
    padding-inline: 6px;
}

.data-bar__fill {
    position: absolute;
    inset-block: -6px;
    inset-inline-start: 0;
    border-radius: 3px;
    background: linear-gradient(to left,
        color-mix(in srgb, var(--app-primary) 45%, transparent),
        color-mix(in srgb, var(--app-primary) 10%, transparent));
    border-inline-start: 2px solid var(--app-primary);
    pointer-events: none;
}

[dir="ltr"] .data-bar__fill {
    background: linear-gradient(to right,
        color-mix(in srgb, var(--app-primary) 45%, transparent),
        color-mix(in srgb, var(--app-primary) 10%, transparent));
}

.data-bar__value {
    position: relative;
}

/* While a drag is selecting CELLS (it left its start cell, or Ctrl is held) no text may be
   highlighted anywhere — cellDragSelect.js sets the class and removes it on release. A drag that
   stays inside one cell never gets it, so a word in a cell can still be highlighted and copied. */
html.cell-drag-active,
html.cell-drag-active * {
    user-select: none;
}


/* The selection mark OVERRIDES the grid rule rather than sitting inside it
   [developer-directed 2026-09-04]. It used to be one `inset 0 0 0 2px` ring, i.e. 2px painted
   wholly INSIDE the cell — so between two selected neighbours the reader saw purple, the grey
   grid rule, then purple again, and a swept block read as a row of loose boxes instead of the
   one shape the Ctrl+drag gesture means.
   ⚠️ A box-shadow CANNOT do this, and the reason is the one §08 already states one rule up: these
   tables are `border-collapse: collapse`, so a shared edge belongs to the TABLE, not to the cell,
   and the table paints it with the non-positioned content. An outward `0 0 0 1px` was measured
   painting UNDER that grey rule and under the next cell's background — the rendered result was
   blue·grey·blue, i.e. the grid line still there and now framed. Do not re-try it.
   An absolutely-positioned overlay is the mechanism that works: a positioned descendant paints in
   a LATER layer than every non-positioned sibling and than the table's own collapsed borders, so
   the mark lands on top of the grid rule instead of beside it. Its 2px border is centred on the
   cell's EDGE (see the insets below), which under border-collapse is the centre of the grid rule,
   so it straddles the rule with no offset. `position: relative` on the cell is the overlay's containing block
   (load-bearing, not decoration). No z-index: the overlay must stay under the sticky header (2 on
   an inline grid, 3 on a list page), which has to keep covering the rows that scroll beneath it.
   ⚠️ What DID show through the header was not the mark but a HOLE in the header — the 1px column
   strip under border-collapse belongs to the table, not to the sticky cell, so the scrolled body
   showed through it, and this mark sits exactly on that strip. Closed on the header side (§08,
   the head cells' OUTLINE), never here; measured 164 stray px → 1 on a real list page. */
.cell-selected {
    background-color: color-mix(in srgb, var(--app-primary) 16%, transparent) !important;
    position: relative;
}

/* ⚠️ WHERE THE BAND SITS IS COMPUTED, NOT TUNED [fitted 2026-09-04 against 40 boundaries on two
   pages in a GPU-rendered browser at 125%, then re-measured at 1× and 2×]. Two facts, both
   measured, decide every pixel:
   (1) The GRID rule is floored to one device pixel and painted at floor(edge) — and under
       border-collapse each of the two cells takes HALF of that floored rule, so the cell's padding
       box (this overlay's containing block) sits `--selection-half` inside the cell's rect on
       every side, declared-border side or not (0.5px at 1×, 0.4px at 125%, 0.333px at 150%).
   (2) The overlay's edge is ROUNDED to a device pixel (round(edge), not floor), and the 2-device-px
       border is painted inward from that rounded edge.
   So round(rect_edge + a) == floor(rect_edge) + 1 for EVERY fractional row position exactly when
   `a` — how far the overlay reaches past the cell's rect — is half a device pixel. Then the band
   is always [grid pixel, plus one pixel INWARD]: the block's contour is the grid contour itself,
   a grey rule arriving from outside the block runs straight into the band's outer pixel, and a
   shared line is the rule plus one pixel into the cell that draws it. Any other `a` puts the extra
   pixel on a side that flips row by row (measured: 0/1/1/0/0/1 down one block at -1.5px), which
   read as one-pixel steps wherever a rule met the block. Hence
   inset = -(half + devpx/2), the two tokens stepping with the scaling below; a browser zoom that
   lands between the tiers takes the 1× values, which still keeps the rule INSIDE the band — only
   the side of the extra pixel is then no longer guaranteed.
   ⚠️ MEASURED at 125% (GPU, 40 boundaries: every band = rule pixel + 1 inward) and at 1×; the
   150% / 175% / 200% / 250% / 300% rows are the same formula, not re-measured — headless Chromium
   rasterises in software and does not snap like the GPU, so it cannot verify them, and a headed
   window on a 125% desktop renders at 125% whatever --dpr says. */
.cell-selected {
    --selection-devpx: 1px;     /* one device pixel, in CSS px */
    --selection-half: 0.5px;    /* half of the floored grid rule = floor(dpr) / dpr / 2 */
}

@media (min-resolution: 1.2dppx) and (max-resolution: 1.3dppx) {  /* 125% */
    .cell-selected { --selection-devpx: 0.8px; --selection-half: 0.4px; }
}

@media (min-resolution: 1.45dppx) and (max-resolution: 1.55dppx) { /* 150% */
    .cell-selected { --selection-devpx: 0.6667px; --selection-half: 0.3333px; }
}

@media (min-resolution: 1.7dppx) and (max-resolution: 1.8dppx) {   /* 175% */
    .cell-selected { --selection-devpx: 0.5714px; --selection-half: 0.2857px; }
}

@media (min-resolution: 1.95dppx) and (max-resolution: 2.05dppx) { /* 200% */
    .cell-selected { --selection-devpx: 0.5px; --selection-half: 0.5px; }
}

@media (min-resolution: 2.45dppx) and (max-resolution: 2.55dppx) { /* 250% */
    .cell-selected { --selection-devpx: 0.4px; --selection-half: 0.4px; }
}

@media (min-resolution: 2.95dppx) and (max-resolution: 3.05dppx) { /* 300% */
    .cell-selected { --selection-devpx: 0.3333px; --selection-half: 0.5px; }
}

.cell-selected::after {
    content: "";
    position: absolute;
    inset: calc(-1 * (var(--selection-half) + var(--selection-devpx) / 2));
    border: 2px solid var(--app-primary);
    pointer-events: none;
}

/* ⚠️ EVERY SHARED EDGE HAS EXACTLY ONE PAINTER [measured 2026-09-04, in a GPU-rendered browser].
   Two selected neighbours used to each draw the edge between them, centred on the same boundary —
   the same 2px in CSS, but NOT the same 2px on screen: the rows are 43.75px tall, so a boundary
   sits on a fraction of a device pixel on three rows out of four, and the GPU snaps each overlay
   to whole device pixels ON ITS OWN. Two overlays that differ by a fraction snap to bands one
   pixel apart, and the union read as a 3px line beside 2px ones (at 125%: 2/3/2/2/3 down one
   block; a 1.6px "whole device pixel" width was tried and did not help, it only moved which rows
   were thick). So the cell to the inline START draws the vertical edge it shares with the next
   sibling, and the UPPER cell draws the horizontal edge it shares with the cell below; the other
   side drops that border. One band, one snap: 11 of 11 rules measured exactly 2 device px, no
   soft pixels, at 125% in the GPU render, and 2 / 4 whole device px at 1× / 2× (Chrome floors a
   border to whole DEVICE pixels, which is what makes a plain 2px border uniform at any scaling
   with no per-DPR width; 1.5× was only measured in the software rasteriser, which snaps nothing).
   The vertical case is the `+` sibling. The horizontal case needs the SAME COLUMN in the next row,
   which CSS can only say per column index — hence the enumerated `:nth-child(n)` list (48 covers
   the widest grid in the estate, 32 columns; a column past it would merely draw its top edge twice).
   Headless Chromium rasterises in software and does NOT show the 3px lines — verify this in a
   headed browser (UISHOTS_HEADED=1), or you will measure a defect that is not there. */
.cell-selected + .cell-selected::after {
    border-inline-start: none;
}

tr:has(> :nth-child(1).cell-selected) + tr > :nth-child(1).cell-selected::after,
tr:has(> :nth-child(2).cell-selected) + tr > :nth-child(2).cell-selected::after,
tr:has(> :nth-child(3).cell-selected) + tr > :nth-child(3).cell-selected::after,
tr:has(> :nth-child(4).cell-selected) + tr > :nth-child(4).cell-selected::after,
tr:has(> :nth-child(5).cell-selected) + tr > :nth-child(5).cell-selected::after,
tr:has(> :nth-child(6).cell-selected) + tr > :nth-child(6).cell-selected::after,
tr:has(> :nth-child(7).cell-selected) + tr > :nth-child(7).cell-selected::after,
tr:has(> :nth-child(8).cell-selected) + tr > :nth-child(8).cell-selected::after,
tr:has(> :nth-child(9).cell-selected) + tr > :nth-child(9).cell-selected::after,
tr:has(> :nth-child(10).cell-selected) + tr > :nth-child(10).cell-selected::after,
tr:has(> :nth-child(11).cell-selected) + tr > :nth-child(11).cell-selected::after,
tr:has(> :nth-child(12).cell-selected) + tr > :nth-child(12).cell-selected::after,
tr:has(> :nth-child(13).cell-selected) + tr > :nth-child(13).cell-selected::after,
tr:has(> :nth-child(14).cell-selected) + tr > :nth-child(14).cell-selected::after,
tr:has(> :nth-child(15).cell-selected) + tr > :nth-child(15).cell-selected::after,
tr:has(> :nth-child(16).cell-selected) + tr > :nth-child(16).cell-selected::after,
tr:has(> :nth-child(17).cell-selected) + tr > :nth-child(17).cell-selected::after,
tr:has(> :nth-child(18).cell-selected) + tr > :nth-child(18).cell-selected::after,
tr:has(> :nth-child(19).cell-selected) + tr > :nth-child(19).cell-selected::after,
tr:has(> :nth-child(20).cell-selected) + tr > :nth-child(20).cell-selected::after,
tr:has(> :nth-child(21).cell-selected) + tr > :nth-child(21).cell-selected::after,
tr:has(> :nth-child(22).cell-selected) + tr > :nth-child(22).cell-selected::after,
tr:has(> :nth-child(23).cell-selected) + tr > :nth-child(23).cell-selected::after,
tr:has(> :nth-child(24).cell-selected) + tr > :nth-child(24).cell-selected::after,
tr:has(> :nth-child(25).cell-selected) + tr > :nth-child(25).cell-selected::after,
tr:has(> :nth-child(26).cell-selected) + tr > :nth-child(26).cell-selected::after,
tr:has(> :nth-child(27).cell-selected) + tr > :nth-child(27).cell-selected::after,
tr:has(> :nth-child(28).cell-selected) + tr > :nth-child(28).cell-selected::after,
tr:has(> :nth-child(29).cell-selected) + tr > :nth-child(29).cell-selected::after,
tr:has(> :nth-child(30).cell-selected) + tr > :nth-child(30).cell-selected::after,
tr:has(> :nth-child(31).cell-selected) + tr > :nth-child(31).cell-selected::after,
tr:has(> :nth-child(32).cell-selected) + tr > :nth-child(32).cell-selected::after,
tr:has(> :nth-child(33).cell-selected) + tr > :nth-child(33).cell-selected::after,
tr:has(> :nth-child(34).cell-selected) + tr > :nth-child(34).cell-selected::after,
tr:has(> :nth-child(35).cell-selected) + tr > :nth-child(35).cell-selected::after,
tr:has(> :nth-child(36).cell-selected) + tr > :nth-child(36).cell-selected::after,
tr:has(> :nth-child(37).cell-selected) + tr > :nth-child(37).cell-selected::after,
tr:has(> :nth-child(38).cell-selected) + tr > :nth-child(38).cell-selected::after,
tr:has(> :nth-child(39).cell-selected) + tr > :nth-child(39).cell-selected::after,
tr:has(> :nth-child(40).cell-selected) + tr > :nth-child(40).cell-selected::after,
tr:has(> :nth-child(41).cell-selected) + tr > :nth-child(41).cell-selected::after,
tr:has(> :nth-child(42).cell-selected) + tr > :nth-child(42).cell-selected::after,
tr:has(> :nth-child(43).cell-selected) + tr > :nth-child(43).cell-selected::after,
tr:has(> :nth-child(44).cell-selected) + tr > :nth-child(44).cell-selected::after,
tr:has(> :nth-child(45).cell-selected) + tr > :nth-child(45).cell-selected::after,
tr:has(> :nth-child(46).cell-selected) + tr > :nth-child(46).cell-selected::after,
tr:has(> :nth-child(47).cell-selected) + tr > :nth-child(47).cell-selected::after,
tr:has(> :nth-child(48).cell-selected) + tr > :nth-child(48).cell-selected::after {
    border-block-start: none;
}

/* The FIRST row under a sticky column-name row [developer-reported 2026-09-04]: the mark's top
   edge straddles the boundary above the row, and there that boundary is the header's last pixel
   plus its outline's lower edge (the header's bottom rule, §08) — both painted OVER the overlay,
   which has to stay under the header. So a block starting on that row lost its top edge and read
   purple on three sides. On that one row the top edge moves 1px DOWN, fully inside the row: the
   header's rule stays where it is and the mark's full 2px stand directly beneath it. Any row
   scrolled under the header still loses the edge — that is what "under the header" means.
   ⚠️ `tbody tr:first-child` is NOT that row on a list page — MudTable's tbody opens with a hidden
   row — and IS that row on an inline grid; `1 of :has(.selectable-cell)` names the first row that
   carries cells at all, on both. */
tbody > tr:nth-child(1 of :has(.selectable-cell)) > .cell-selected::after {
    inset-block-start: var(--selection-devpx);   /* = the outline pixel + 1, at every scaling */
}

/* The table's OUTERMOST columns [measured 2026-09-04]: there the mark's outward half lands on the
   scroll box's own border and is clipped by it (1 device px of blue where every other edge has 2).
   Nothing lives outside a first/last column to straddle into, so on those two cells the vertical
   edge sits wholly INSIDE the table: with the overlay ending ON the rect edge its band is the two
   pixels before the box's own border pixel (measured adjacent at 1× and 125%). Only the FIRST
   column needs it — the last column of every grid here is פעולות, not selectable, and the general
   anchor above already keeps a last-column band inside. */
td:first-child.cell-selected::after {
    inset-inline-start: 0;
}


/* Excel-style selection stats. Ctrl/Cmd+click or Ctrl/Cmd+drag multi-selects cells; this text
   shows ספירה / סכום of the selection in the table footer, styled exactly like the pager's own
   info text (muted labels, primary bold values, 13px). pointer-events:none so it can never
   block pager clicks. */
.users-table-shell .mud-table-pagination {
    position: relative;
    /* Size container so the stats placement reacts to the CARD's width, not the viewport —
       a media query misses the sidebar-open / DevTools-docked cases where the window is wide
       but the footer is narrow. */
    container-type: inline-size;
}

/* Default (MudTablePager / report footers): the stats line is its own centered row above the
   pager — those pagers have controls near the middle, so there is no safe spot to float over. */
.selection-stats {
    width: fit-content;
    margin: 0.4rem auto 0;
    display: flex;
    align-items: center;
    gap: 1.1rem;
    color: var(--app-muted);
    font-size: 11.375px;
    white-space: nowrap;
    pointer-events: none;
}

/* AppPager footers have a known empty middle — float the line over it (no layout shift).
   +2px: the pagination container's top border (1px) + asymmetric padding (5.6/2.4px)
   put its 50% line above the pager row's visual middle — compensate so the stats text
   sits on the same line as the pager's own text. */
.users-table-shell .mud-table-pagination:has(> .app-pager) .selection-stats {
    position: absolute;
    inset-inline: 0;
    top: 50%;
    transform: translateY(calc(-50% + 2px));
    margin: 0 auto;
    z-index: 2;
}

.selection-stats-item b {
    color: var(--app-primary);
    font-weight: 600;
}

/* Narrow footer: the pager's middle is too tight for the stats line (the pager may even wrap
   into stacked rows) — drop it back into normal flow as its own centered row above the pager.
   Threshold ≈ pager nav (~360px) + rows/info group (~300px) + stats (~200px) + gaps/slack. */
@container (max-width: 875px) {
    .users-table-shell .mud-table-pagination:has(> .app-pager) .selection-stats {
        position: static;
        transform: none;
        margin: 0.4rem auto 0;
    }
}

.action-btn.mud-icon-button:disabled {
    opacity: 0.45;
}

/* Border - the switch in :root, for the dialog / form button family. Declared ONCE for the
   five soft variants PLUS the two page-local families that copy their look: the /roles
   action buttons (.role-action-button) and the permissions Save (.permissions-save-button).
   Each supplies its own --control-accent in its own rule below - except the role BASE, which
   carries none on purpose: a disabled delete drops its -delete modifier entirely, so it falls
   back to currentColor and its edge follows the grey ink.
   WARNING: .soft-outline-button is deliberately NOT in this list. Its border IS its identity
   - it is the estate's one genuinely outlined button - so the switch must not be able to take
   it away. A control whose edge is part of what it IS never joins the switch. */
.soft-primary-button,
.mud-button-root.soft-primary-button,
.soft-brand-button,
.mud-button-root.soft-brand-button,
.soft-save-button,
.mud-button-root.soft-save-button,
.soft-update-button,
.mud-button-root.soft-update-button,
.soft-delete-button,
.mud-button-root.soft-delete-button,
.dialog-cancel,
.mud-button-root.dialog-cancel,
.role-action-button,
.mud-button-root.role-action-button,
.permissions-save-button,
.mud-button-root.permissions-save-button {
    box-sizing: border-box;
    border-width: 1px;
    border-style: solid;
    border-color: color-mix(in srgb,
        var(--control-border-color, var(--control-accent, currentColor)) var(--app-control-border-strength),
        transparent);
}

/* The padding is compensated by the 1px, so this family is the SAME SIZE it was before the
   border existed. These five are sized by their CONTENT, so unlike the fixed-size toolbar and
   icon buttons box-sizing cannot hold their outer box - without this they ship 2px larger,
   with the switch off as well as on.
   WARNING: the two page-local families above are deliberately NOT here. Each states a FIXED
   width and height with box-sizing: border-box, so the edge costs them no size at all - and a
   padding-block stated here would outrank their own zero padding and re-space them. */
.soft-primary-button,
.mud-button-root.soft-primary-button,
.soft-brand-button,
.mud-button-root.soft-brand-button,
.soft-save-button,
.mud-button-root.soft-save-button,
.soft-update-button,
.mud-button-root.soft-update-button,
.soft-delete-button,
.mud-button-root.soft-delete-button,
.dialog-cancel,
.mud-button-root.dialog-cancel {
    padding-block: 4.375px;
}

/* Disabled - the toolbar rule above, said for this family: a dialog disables its confirm button
   while it saves, which would otherwise draw a full-strength red or green outline around grey
   text. :disabled, not .mud-disabled - see the toolbar rule for why. */
.mud-button-root.soft-primary-button:disabled,
.mud-button-root.soft-save-button:disabled,
.mud-button-root.soft-update-button:disabled,
.mud-button-root.soft-delete-button:disabled,
.mud-button-root.dialog-cancel:disabled,
.mud-button-root.role-action-button:disabled,
.mud-button-root.permissions-save-button:disabled {
    --control-border-color: var(--app-disabled-text);
}

.soft-primary-button {
    --control-accent: var(--app-green);
    /* Inline half of the compensation - MudBlazor's own 16px, less the 1px border. */
    padding-inline: 13.125px;
    border-radius: var(--app-radius-button);
    background: var(--app-green-soft);
    color: var(--app-green);
    text-transform: none;
    font-weight: 700;
    box-shadow: none;
    transition: background-color 160ms ease, color 160ms ease;
}

.soft-primary-button:hover:not(.mud-disabled),
.soft-primary-button:focus-visible:not(.mud-disabled) {
    border-color: var(--app-green);
    background: var(--app-green);
    color: var(--app-color-page-button-hover-text);
    box-shadow: none;
}

/* The soft family's member in the app's PRIMARY token [developer-directed 2026-09-07] — for a
   dialog action that is neither a create (green), a save (info), an update (amber) nor a delete
   (red): the import dialog's «הנחיות לקובץ». Same recipe as its siblings — soft fill at rest, the
   token solid under the pointer — on --app-primary / --app-primary-soft, both restated per theme. */
.soft-brand-button {
    --control-accent: var(--app-primary);
    padding-inline: 13.125px;
    border-radius: var(--app-radius-button);
    background: var(--app-primary-soft);
    color: var(--app-primary);
    text-transform: none;
    font-weight: 700;
    box-shadow: none;
    transition: background-color 160ms ease, color 160ms ease, border-color 160ms ease;
}

.soft-brand-button:hover:not(.mud-disabled),
.soft-brand-button:focus-visible:not(.mud-disabled) {
    border-color: var(--app-primary-solid);
    background: var(--app-primary-solid);
    color: var(--app-on-primary);
    box-shadow: none;
}

.soft-save-button {
    --control-accent: var(--app-info);
    /* Inline half of the compensation - MudBlazor's own 16px, less the 1px border. */
    padding-inline: 13.125px;
    border-radius: var(--app-radius-button);
    background: var(--app-info-soft);
    color: var(--app-info);
    text-transform: none;
    font-weight: 700;
    box-shadow: none;
    transition: background-color 160ms ease, color 160ms ease;
}

.soft-save-button:hover:not(.mud-disabled),
.soft-save-button:focus-visible:not(.mud-disabled) {
    border-color: var(--app-info);
    background: var(--app-info);
    color: var(--app-color-page-button-hover-text);
    box-shadow: none;
}

.soft-update-button,
.mud-button-root.soft-update-button {
    --control-accent: var(--app-warning);
    /* Inline half of the compensation - MudBlazor's own 16px, less the 1px border. */
    padding-inline: 13.125px;
    border-radius: var(--app-radius-button);
    background: var(--app-warning-soft);
    color: var(--app-warning);
    text-transform: none;
    font-weight: 700;
    box-shadow: none;
    transition: background-color 160ms ease, color 160ms ease;
}

.soft-update-button:hover:not(.mud-disabled),
.soft-update-button:focus-visible:not(.mud-disabled),
.mud-button-root.soft-update-button:hover:not(.mud-disabled),
.mud-button-root.soft-update-button:focus-visible:not(.mud-disabled) {
    border-color: var(--app-warning);
    background: var(--app-warning);
    color: var(--app-color-page-button-hover-text);
    box-shadow: none;
}

.soft-delete-button,
.mud-button-root.soft-delete-button {
    --control-accent: var(--app-danger);
    /* Inline half of the compensation - MudBlazor's own 16px, less the 1px border. */
    padding-inline: 13.125px;
    border-radius: var(--app-radius-button);
    background: var(--app-danger-soft);
    color: var(--app-danger);
    text-transform: none;
    font-weight: 700;
    box-shadow: none;
    transition: background-color 160ms ease, color 160ms ease;
}

.soft-delete-button:hover:not(.mud-disabled),
.soft-delete-button:focus-visible:not(.mud-disabled),
.mud-button-root.soft-delete-button:hover:not(.mud-disabled),
.mud-button-root.soft-delete-button:focus-visible:not(.mud-disabled) {
    border-color: var(--app-danger);
    background: var(--app-danger);
    color: var(--app-color-page-button-hover-text);
    box-shadow: none;
}

.soft-outline-button {
    border-color: var(--app-border);
    color: var(--app-text-disabled);
    text-transform: none;
    font-weight: 700;
    transition: background-color 160ms ease, border-color 160ms ease;
}

.soft-outline-button:hover:not(.mud-disabled) {
    background: var(--app-surface-hover);
    border-color: var(--app-border-muted);
}

.dialog-cancel,
.mud-button-root.dialog-cancel {
    --control-accent: var(--app-muted);
    /* Inline half of the compensation - MudBlazor's own 8px on this variant, less the 1px. */
    padding-inline: 6.125px;
    color: var(--app-muted);
    background: var(--app-surface-hover);
    text-transform: none;
    font-weight: 600;
    border-radius: var(--app-radius-button);
    transition: background-color 160ms ease;
}

.dialog-cancel:hover:not(.mud-disabled),
.dialog-cancel:focus-visible:not(.mud-disabled),
.mud-button-root.dialog-cancel:hover:not(.mud-disabled),
.mud-button-root.dialog-cancel:focus-visible:not(.mud-disabled) {
    background: var(--app-surface-active);
}

.dialog-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.75rem;
    margin-top: 1rem;
}

/* =========================================================================
 * 12. ROLES & PERMISSIONS
 * ========================================================================= */
.permission-matrix {
    flex: 1 1 auto;
    min-height: 0;
    overflow-x: auto;
    overflow-y: auto;
    overscroll-behavior: contain;
}

.permission-matrix table {
    width: 100%;
    border-collapse: collapse;
    border-spacing: 0;
    min-width: 630px;
    border: 1px solid var(--app-border);
    border-top: none; /* header row draws the top edge (visible with sticky + overflow) */
}

.permission-matrix th,
.permission-matrix td {
    height: 43.75px;
    box-sizing: border-box;
    padding: 0 15.75px;
    border: none;
    border-bottom: 1px solid var(--app-border);
    border-inline-end: 1px solid var(--app-border);
    text-align: center;
    vertical-align: middle;
    letter-spacing: normal;
    line-height: 1.4;
}

.permission-matrix th:last-child,
.permission-matrix td:last-child {
    border-inline-end: none;
}

.permission-matrix th {
    border-bottom: none;
}

.permission-matrix tbody tr:last-child td {
    border-bottom: none;
}

.permission-matrix th:first-child,
.permission-matrix td:first-child {
    text-align: start;
    font-weight: 600;
}

.permission-matrix th {
    position: sticky;
    top: 0;
    z-index: 3;
    height: 52.5px;
    color: var(--app-table-column-name);
    font-size: 11.375px;
    font-weight: 600;
    /*
     * Collapsed borders detach from the cell when it's sticky, so the bottom
     * line would scroll away with the body. Draw the bottom as an inset shadow.
     * The top line uses a background gradient offset 1px below the cell edge so
     * it is not clipped by the scroll container when the header is pinned.
     */
    background-color: var(--app-table-head);
    background-image: linear-gradient(var(--app-border), var(--app-border));
    background-size: 100% 1px;
    background-position: 0 1px;
    background-repeat: no-repeat;
    box-shadow: inset 0 -1px 0 var(--app-border);
}

.permission-matrix td {
    color: var(--app-text);
    font-size: 11.375px;
    font-weight: 400;
}

.permission-matrix tbody tr:hover {
    background: var(--app-surface-accent);
}

.resource-row-actions,
.permission-checkbox-cell {
    display: flex;
    align-items: center;
    height: 100%;
}

.role-header-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 0.65rem;
    flex-shrink: 0;
    box-sizing: border-box;
}

.role-action-buttons {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: 0.5rem;
    flex-shrink: 0;
    box-sizing: border-box;
    padding-top: var(--toolbar-label-gap, 0.5rem);
}

.role-action-button,
.role-action-button .mud-button-label {
    text-transform: none;
}

.role-action-button,
.permissions-save-button {
    flex: 0 0 auto;
    min-height: 31.5px;
    height: 31.5px;
    border-radius: var(--app-radius-button);
    font-size: 0.8rem;
    font-weight: 600;
    letter-spacing: normal;
    white-space: nowrap;
    justify-content: center;
    box-shadow: none;
    text-transform: none;
    transition: background-color 160ms ease;
}

.role-action-button {
    --role-action-button-width: calc(6.125rem - 8.75px);
    flex: 0 0 var(--role-action-button-width);
    width: var(--role-action-button-width);
    min-width: var(--role-action-button-width);
    max-width: var(--role-action-button-width);
    padding: 0;
}

.permissions-save-button {
    width: 6.75rem;
    min-width: 6.75rem;
    max-width: 6.75rem;
    padding: 0 0.5rem;
}

.role-action-button .mud-button-icon-start {
    margin-inline-end: 0.35rem;
}

.role-action-button .mud-icon-root {
    font-size: 1.05rem;
}

/* --control-accent - the switch composes each edge against it; the border longhands
   themselves are declared once, with the dialog / form button family above. */
.role-action-button-add {
    --control-accent: var(--app-green);
    background: var(--app-green-soft);
    color: var(--app-green);
}

.role-action-button-update {
    --control-accent: var(--app-warning);
    background: var(--app-warning-soft);
    color: var(--app-warning);
}

.role-action-button-delete {
    --control-accent: var(--app-danger);
    background: var(--app-danger-soft);
    color: var(--app-danger);
}

.mud-button-root.role-action-button-delete.mud-disabled {
    background: var(--app-disabled-bg);
    color: var(--app-disabled-text);
    box-shadow: none;
    opacity: 1;
}


.role-action-button-save {
    background: var(--app-info-soft);
    color: var(--app-info);
}

/* Hover - the edge goes full strength with the fill, exactly as the soft family's own hover
   rules do: the solid accent behind it makes it read as the button's outer edge, not as a
   second ring inside it. */
.role-action-button-add:hover:not(.mud-disabled) {
    border-color: var(--app-green);
    background: var(--app-green);
    color: var(--app-color-page-button-hover-text);
}

.role-action-button-save:hover:not(.mud-disabled) {
    border-color: var(--app-info);
    background: var(--app-info);
    color: var(--app-color-page-button-hover-text);
}

.role-action-button-update:hover:not(.mud-disabled) {
    border-color: var(--app-warning);
    background: var(--app-warning);
    color: var(--app-color-page-button-hover-text);
}

.role-action-button-delete:hover:not(.mud-disabled) {
    border-color: var(--app-danger);
    background: var(--app-danger);
    color: var(--app-color-page-button-hover-text);
}

/* Change Password (soft-save-button) and permissions Save (permissions-save-button)
   buttons adopt the columns/filters/export primary color scheme. Uses the
   --app-primary tokens, so it follows light and dark mode automatically. */
.mud-button-root.soft-save-button,
.mud-button-root.permissions-save-button {
    background-color: var(--app-primary-soft);
    color: var(--app-primary);
}

.mud-button-root.soft-save-button:hover:not(.mud-disabled),
.mud-button-root.soft-save-button:focus-visible:not(.mud-disabled),
.mud-button-root.permissions-save-button:hover:not(.mud-disabled),
.mud-button-root.permissions-save-button:focus-visible:not(.mud-disabled) {
    background-color: var(--app-primary);
    color: var(--app-color-page-button-hover-text);
}

.mud-button-root.soft-save-button .mud-button-label,
.mud-button-root.soft-save-button .mud-icon-root,
.mud-button-root.permissions-save-button .mud-button-label,
.mud-button-root.permissions-save-button .mud-icon-root {
    color: inherit;
}

/* --control-accent for the Save variants - each names the colour it is actually FILLED with
   two rules up, never the one its own base class declares, or the edge is composed off a
   colour the button does not wear.
   ⚠️ .soft-save-button belongs in this list: its own base rule's accent (--app-info) never
   applies to a button this app renders, because the primary-fill rule two rules up is (0,2,0)
   and reaches EVERY .soft-save-button here (Change Password, the settings About copy). Left on
   the info accent it draws a sky-blue edge - and on hover a solid sky-blue ring - around an
   indigo button, wherever --app-control-border-strength is switched on. The base rule keeps
   its own accent, for a consumer that wears the info fill. */
.mud-button-root.soft-save-button,
.mud-button-root.permissions-save-button {
    --control-accent: var(--app-primary);
}

.mud-button-root.soft-save-button:hover:not(.mud-disabled),
.mud-button-root.soft-save-button:focus-visible:not(.mud-disabled),
.mud-button-root.permissions-save-button:hover:not(.mud-disabled),
.mud-button-root.permissions-save-button:focus-visible:not(.mud-disabled) {
    border-color: var(--app-primary);
}

/* Unsaved-changes dialog Save button: the app's add-green instead of the primary save
   color, so the safe action stands out from the red discard. Must sit after the
   permissions-save-button color rules (same specificity — order decides). */
.mud-button-root.unsaved-save-button {
    background-color: var(--app-green-soft);
    color: var(--app-green);
}

.mud-button-root.unsaved-save-button:hover:not(.mud-disabled),
.mud-button-root.unsaved-save-button:focus-visible:not(.mud-disabled) {
    background-color: var(--app-green);
    color: var(--app-color-page-button-hover-text);
}

.mud-button-root.unsaved-save-button {
    --control-accent: var(--app-green);
}

.mud-button-root.unsaved-save-button:hover:not(.mud-disabled),
.mud-button-root.unsaved-save-button:focus-visible:not(.mud-disabled) {
    border-color: var(--app-green);
}

.permissions-section-header {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: center;
    width: 100%;
    gap: 1rem;
    margin-bottom: 0;
    padding-top: 1rem;
    border-top: 1px solid var(--app-border);
}

.permissions-section-header > .mud-typography {
    flex: 0 0 auto;
    width: auto;
}

.permissions-save-button {
    margin-inline-start: auto;
}

.permissions-save-button .mud-button-label {
    text-transform: none;
}

.resource-row-actions {
    gap: 0.5rem;
}

.resource-row-actions .mud-icon-button {
    width: 26.25px;
    height: 26.25px;
    flex-shrink: 0;
}

.permission-checkbox-cell {
    justify-content: center;
    /* Checkbox glyph size — single knob, re-pointed per breakpoint below.
       Desktop 20px; tablet 18px; mobile 16px. */
    --permission-checkbox-size: 17.5px;
}

.permission-checkbox-cell .mud-checkbox {
    margin: 0;
    padding: 0;
}

.permission-checkbox-cell .mud-checkbox .mud-icon-root {
    font-size: var(--permission-checkbox-size);
    width: var(--permission-checkbox-size);
    height: var(--permission-checkbox-size);
}

/* Re-point the size token per breakpoint (the rule above consumes it). */
@media (min-width: 601px) and (max-width: 960px) {
    .permission-checkbox-cell {
        --permission-checkbox-size: 15.75px;
    }
}

@media (max-width: 600px) {
    .permission-checkbox-cell {
        --permission-checkbox-size: 14px;
    }
}

/* =========================================================================
 * 13. FORMS: GRID LAYOUT & FIELDS
 * ========================================================================= */
.user-form-title {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    color: var(--app-primary);
    font-size: 14.875px;
    font-weight: 750;
}

.user-form-grid {
    margin-top: 0.25rem;
}

.user-form-roles-frame {
    display: flex;
    align-items: center;
    box-sizing: border-box;
    width: 100%;
    min-height: var(--form-control-height);
    padding: 0 0.55rem;
    border: 1px solid var(--form-control-border);
    border-radius: var(--app-radius-form-box);
    background: var(--app-color-form-box-bg);
}

.user-form-roles-options {
    display: flex;
    flex-flow: row wrap;
    align-items: center;
    gap: 0.85rem;
    width: 100%;
    min-height: var(--form-control-height);
}

.user-form-roles-frame .user-form-role-check {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    margin: 0;
    padding: 0;
    font-size: 0.85rem;
    line-height: 1.1;
    color: var(--app-text);
    cursor: pointer;
    user-select: none;
}

/* Native checkbox painted like MudBlazor's Color.Primary checkbox (the column-filter
   checklist), so a plain <input type="checkbox"> in a form reads as the same control:
   primary-colored outline when off, primary fill + white tick when on. The browser's own
   box (accent-color) renders white-on-dark and does not match. */
.app-checkbox {
    box-sizing: border-box;
    flex: none;
    width: 1.05rem;
    height: 1.05rem;
    margin: 0;
    border: 2px solid var(--app-primary);
    /* Tracks MudBlazor's OWN checkbox box, not our scale — deliberately not --app-radius,
       and deliberately not a token: the value is not ours to tune. */
    border-radius: 2.625px;
    background-color: transparent;
    background-repeat: no-repeat;
    background-position: center;
    background-size: 100% 100%;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
}

.app-checkbox:checked {
    background-color: var(--app-primary);
    background-image: var(--checkbox-tick-svg);
}

.app-checkbox:focus-visible {
    outline: 2px solid var(--app-primary);
    outline-offset: 2px;
}

.app-checkbox:disabled,
.app-radio:disabled {
    opacity: 0.45;
    cursor: default;
}

/* Same deal for a native radio (a roles frame in single-role mode): round box, primary
   ring, primary dot. The dot is a gradient rather than an inset shadow so it works over
   any container background. */
.app-radio {
    box-sizing: border-box;
    flex: none;
    width: 1.05rem;
    height: 1.05rem;
    margin: 0;
    border: 2px solid var(--app-primary);
    border-radius: var(--app-radius-circle);
    background-color: transparent;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
}

.app-radio:checked {
    background-image: radial-gradient(circle, var(--app-primary) 0 45%, transparent 47%);
}

.app-radio:focus-visible {
    outline: 2px solid var(--app-primary);
    outline-offset: 2px;
}

@media (max-width: 600px) {
    .entity-form-grid.mud-grid > .mud-grid-item {
        padding-top: 0.08rem;
        padding-bottom: 0.08rem;
    }

    .user-form-field {
        gap: 0;
    }

    .login-page .user-form-field.mb-4 {
        margin-bottom: 0.35rem;
    }

    .login-page .user-form-field.mb-6 {
        margin-bottom: 0.45rem;
    }

    .login-password-control {
        gap: 3.5px;
    }
}

/* 12-column form grid: every entity form is a MudGrid whose MudItems carry md spans out of 12
   (each logical row sums to exactly 12 — see AGENTS.md §Forms). The field and its control fill
   the item's cell, so the span alone decides the width. */
.entity-form-grid .user-form-field,
.entity-form-grid .form-control-sized {
    width: 100%;
    max-width: 100%;
    min-width: 0;
}

/* Rows anchor to the TOP: every box sits directly under its (one-line) label, so an inline
   validation message below one box extends that row downward — the whole next row is pushed
   down as a unit — and the sibling boxes on the same row never move ("no broken boxes").
   Corollary: a field label must stay on ONE line — if a label wraps at its span, widen the
   field's span; never let it wrap. */
.entity-form-grid.mud-grid {
    align-items: flex-start;
}

/* Hint row under a field pair (e.g. an "enter one of the two" note) hugs the row above
   (≈6px gap); when a validation message shows on the pair, the flex line above grows, so the
   hint slides down after the message automatically. */
.entity-form-grid .form-hint-item {
    margin-top: -8.75px;
}

.entity-form-grid .form-control-sized .native-form-control,
.entity-form-grid .form-control-sized .searchable-native-select,
.entity-form-grid .form-control-sized .native-datetime-host,
.entity-form-grid .form-control-sized .native-form-textarea,
.entity-form-grid .form-control-sized .form-mud-select {
    width: 100%;
    max-width: 100%;
}

/* Section divider inside a 12-col form: a small primary-colored label with a hairline on
   both sides, spanning its MudItem. */
.form-section-divider {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    color: var(--mud-palette-primary);
    font-size: 0.8rem;
    font-weight: 600;
    white-space: nowrap;
}

.form-section-divider::before,
.form-section-divider::after {
    content: "";
    flex: 1 1 auto;
    height: 1px;
    background: var(--mud-palette-lines-default);
}

/* A divider with no label is one unbroken hairline. */
.form-section-divider:empty {
    gap: 0;
}

/* Small hint line under a group of fields, centered across the MudItem that carries it. */
/* Tabbed form sections (EmployeeFormDialog) — a pill segmented control, not MudBlazor's
   underline bar: the sections are peers of one record, and pills say "pick one of these" more
   plainly than a moving underline does. Same shapes the rest of the app already uses — the
   toolbar buttons' pill radius, the primary fill of the page's own primary button.
   Every color is a token, so the strip follows light/dark with the rest of the dialog. */
.entity-form-tabs {
    /* The space between the pills, as a TOKEN so its responsive steps live in one place
       [developer-directed 2026-09-05]. It steps down hard on a narrow screen: the strip is one
       control, and on a phone the gap was costing more width than the labels had to spare — but it
       never reaches zero, or the pills read as one welded bar. */
    --entity-form-tab-gap: 1.25rem;
    /* The pill’s own inline padding, on the same ladder as the gap [developer-directed 2026-09-05].
       It is the bigger lever of the two on a phone: at 430px a pill is ~57px wide and 1.1rem either
       side spent 30.8 of them on air, which is what pushed the longest two-word label onto a second
       line while the gap between the pills was already down to 4.9px. */
    --entity-form-tab-pad-inline: 1.1rem;
    margin-top: 1.5rem;
    /* The rule the section dividers draw, promoted to a full-width separator: the fields above
       the strip are the record, the strip switches its sub-sections — the line marks the seam. */
    border-top: 1px solid var(--mud-palette-lines-default);
    padding-top: 1.25rem;
}

/* MudBlazor's bar carries a surface background and hosts the sliding indicator; both are in the
   way here. The slider is hidden rather than removed — it is the component's own element and
   still gets positioned, it just never paints. */
.entity-form-tabs .mud-tabs-tabbar {
    background: transparent;
    border: 0;
    min-height: 0;
}

.entity-form-tabs .mud-tab-slider {
    display: none;
}

/* The pills' actual flex row is the sliding wrapper INSIDE .mud-tabs-tabbar-content (the
   content box itself is inline-block, where gap is inert) — the spacing must go here.
   transform is MudBlazor's own paging offset, written inline on this element (hence
   !important): with the arrows gone it would only fight the native scroll below. */
.entity-form-tabs .mud-tabs-tabbar-wrapper {
    gap: var(--entity-form-tab-gap);
    transform: none !important;
    transition: none;
}

/* ⚠️ The gap ladder is CO-LOCATED with the strip, never added to the big :root @media blocks near
   the top of the file: those sit thousands of lines EARLIER, so a .entity-form-tabs selector there
   ties on specificity with the base rule above and then LOSES on source order — the tier would
   silently never apply. Same trap the parameter row and the board banner already document. */
@media (max-width: 960px) {
    .entity-form-tabs {
        --entity-form-tab-gap: 0.6rem;
        --entity-form-tab-pad-inline: 0.7rem;
    }
}

@media (max-width: 600px) {
    .entity-form-tabs {
        --entity-form-tab-gap: 0.35rem;
        --entity-form-tab-pad-inline: 0.4rem;
    }
}

/* Narrow screens: the strip scrolls sideways instead of paging behind MudBlazor's < > buttons
   [developer-directed 2026-08-11]. The buttons only appear once the pills overflow, and a
   two-pill-at-a-time pager is a poor trade for a strip the finger (or the wheel) can just push.
   Nothing changes on a wide dialog — the pills fit, so no rail is drawn. The rail matches the
   toolbar header's own overflow rail (.page-card-header): thin, tokened, contained. */
.entity-form-tabs .mud-tabs-scroll-button {
    display: none;
}

.entity-form-tabs .mud-tabs-tabbar-content {
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    /* Colour comes from the global scrollbar rule in §01; only the RAIL's size is this
       strip's own. */
    /* Room for the rail, so it rides under the pills instead of across their bottom border. */
    padding-bottom: 3.5px;
}

.entity-form-tabs .mud-tabs-tabbar-content::-webkit-scrollbar {
    height: 3.5px;
}


/* width:100% is MudBlazor's default and would stretch four pills across the dialog — each one
   should be as wide as its own label plus the padding. */
.entity-form-tabs .mud-tab {
    /* Width comes from the component's MinimumTabWidth (inline style, outranks CSS): one fixed
       floor for every pill — equal-sized segments read as one control. width:auto only stops
       MudBlazor's width:100% stretch. */
    width: auto;
    min-height: 2.375rem;
    padding: 0 var(--entity-form-tab-pad-inline);
    /* An unpicked pill is edged in the brand at 29%, not in the neutral --app-border-muted
       [developer-directed 2026-09-05]: the strip is one control, and a grey edge beside the picked
       pill read as a different kind of thing. The picked pill zeroes this width; .tab-has-error
       overrides the colour. */
    border: 1px solid var(--app-primary-edge);
    border-radius: var(--app-radius-pill);
    background: transparent;
    color: var(--mud-palette-text-secondary);
    /* The shared page-button size, not a literal [developer-reported 2026-09-05]: 0.8rem is 11.2px
       flat at EVERY width, so on a phone the pill label stood a whole step larger than the toolbar
       and form text around it. --app-text-page-button rides --app-text-scale (11.375 / 10.5 /
       9.625px), which also gives the labels back the room they were wrapping for. */
    font-size: var(--app-text-page-button);
    font-weight: 600;
    letter-spacing: normal;
    text-transform: none;
    /* An invisible inset of the PICKED pill’s exact size. It paints nothing (fully transparent),
       and it is what makes the press land in ONE moment: transitioning from `none` interpolates the
       offsets and the blur up from ZERO too, so at the half-way frame the fill was already 54% blue
       while the shadow was a 1px smudge at 0.27 alpha — measured, and exactly the "the shade comes a
       split second after the background" the developer reported [2026-09-04]. Starting from the same
       geometry leaves only the INK to animate, so the well darkens in step with the fill. */
    box-shadow: var(--app-shadow-inset-deep-geometry) transparent;
    transition: background-color 160ms ease, color 160ms ease, border-color 160ms ease, box-shadow 160ms ease;
}

/* An unpicked pill answers the pointer with its FILL and INK only [developer-directed 2026-09-04].
   Neither its edge nor its depth moves: the edge changing colour read as a second signal competing
   with the fill, and an inset here made an unpicked section look pressed — which is the one thing
   this strip must say about exactly one pill at a time. The WELL belongs to the picked pill alone. */
.entity-form-tabs .mud-tab:hover:not(.mud-tab-active) {
    background: var(--app-primary-soft);
    color: var(--app-primary);
}

/* The picked section wears the brand as a SOLID control, the depth the status toggles wear
   [developer-directed 2026-09-04]: --app-primary is the brand as text/edge and is LIFTED in
   dark so it can be read on a dark card, so filling a pill with it painted a pale lavender
   beside the saturated toggles two rows above. --app-primary-solid is that same brand at a
   control’s strength in both themes, and the ink follows it — --app-on-primary is white in
   both, where --mud-palette-primary-text is the DARK ink the pale fill needed. */
.entity-form-tabs .mud-tab.mud-tab-active {
    /* NO rim [developer-directed 2026-09-04]. A border is painted FLAT while an inset shadow is
       clipped to the PADDING box, so a bordered pill wears a 1px ring of undarkened fill around a
       darkened middle — which is what read as a border on a pill whose border was already its own
       fill colour. Zeroing the WIDTH (not the colour: a transparent border still shows the
       background through that same unshadowed strip) lets the inset reach the pill’s own edge.
       ⚠️ box-sizing is border-box, so the outer box does not move — measured 33.25px either way.
       ⚠️ .tab-has-error puts the width back, or an errored pill would lose its red frame. */
    border-width: 0;
    background: var(--app-primary-solid);
    color: var(--app-on-primary);
    /* … and the inset the status toggles wear, so the picked pill reads as a PRESSED physical
       control rather than as a coloured rectangle [developer-directed 2026-09-04].
       --app-shadow-inset-deep is the toggle TRACK’s own token, restated per theme (dark needs a
       black at its own alpha, or it paints nothing — see the ⚠️ on the token in §22).
       ⚠️ The PICKED pill is the only one with a well, hovered or not: an unpicked pill is transparent
       (an inset there would paint the dialog surface behind it, not the pill), and even hovered — where
       it does have a fill — it stays flat, so "pressed in" says exactly one thing on this strip. */
    box-shadow: var(--app-shadow-inset-deep);
}

/* … and under the pointer it presses FURTHER in: a deeper well plus a fill darkened the way a
   real surface darkens as it goes down. Both ride the pill’s 160ms transition, so the press
   flows rather than snapping [developer-directed 2026-09-04]. */
.entity-form-tabs .mud-tab.mud-tab-active:hover {
    background: color-mix(in srgb, var(--app-primary-solid) 90%, black);
    box-shadow: var(--app-shadow-inset-sunken);
}

/* A tab holding rejected fields (MudTabPanel.Class="tab-has-error"): the pill borders red and
   the messages wait inside for the user — the dialog no longer switches tabs on its own. The
   border must survive all three pill states, including the active fill. */
.entity-form-tabs .mud-tab.tab-has-error,
.entity-form-tabs .mud-tab.tab-has-error:hover:not(.mud-tab-active),
.entity-form-tabs .mud-tab.tab-has-error.mud-tab-active {
    /* The ACTIVE pill zeroes its width above, so the frame has to be put back here. */
    border-width: 1px;
    border-color: var(--app-danger);
}

/* The panels hold their own MudGrid, and its first row's item padding already separates the
   fields from the strip — the panel box itself adds nothing [developer-directed 2026-08-11].
   The height is FIXED [developer-directed 2026-08-11]: every panel gets the same box and a
   panel taller than it (the שכר grid with history) scrolls inside, so the dialog never
   resizes on a tab switch — which is what lets the dialog sit centered by default again
   (the top-pinned tier that used to mask the jump is gone). Panel
   dropdowns are safe to clamp: SearchableNativeSelect reparents its list to the body
   (elitaproPositionSearchDropdown) and the date pickers open through the popover provider. */
.entity-form-tabs .mud-tabs-panels {
    padding: 0;
    height: var(--entity-form-tabs-height, 280px);
    overflow-y: auto;
    overflow-x: hidden;
}


.form-field-hint {
    text-align: center;
    color: var(--mud-palette-text-secondary);
    font-size: 0.75rem;
}

.user-form-field .form-control-sized .native-form-textarea,
.mud-dialog .form-control-sized .native-form-textarea {
    width: 100%;
    min-width: 0;
}

@media (max-width: 600px) {
    .user-form-field .form-control-sized,
    .mud-dialog .form-control-sized {
        width: 100%;
    }
}

.user-form-field {
    display: flex;
    flex-direction: column;
    gap: 0;
    width: min(100%, var(--form-field-width));
}

.user-form-field > .user-form-label {
    margin-bottom: var(--form-label-gap);
}

.form-control-sized {
    width: min(100%, var(--form-field-width));
    max-width: 100%;
}

.user-form-field .form-control-sized .native-form-control,
.mud-dialog .form-control-sized .native-form-control,
.user-form-field .form-control-sized .searchable-native-select,
.mud-dialog .form-control-sized .searchable-native-select,
.user-form-field .form-control-sized .native-datetime-host,
.mud-dialog .form-control-sized .native-datetime-host,
.user-form-field .form-control-sized .form-mud-select,
.mud-dialog .form-control-sized .form-mud-select {
    width: 100%;
    min-width: 0;
    max-width: 100%;
    box-sizing: border-box;
}

.user-form-field .form-control-sized .native-datetime-host .mud-input-control,
.mud-dialog .form-control-sized .native-datetime-host .mud-input-control {
    width: 100%;
    max-width: 100%;
}

.user-form-field .mud-input-control,
.mud-dialog .user-form-field .mud-input-control {
    width: 100%;
    max-width: 100%;
    margin: 0;
}

.user-form-field .mud-input-control-helper-container,
.mud-dialog .user-form-field .mud-input-control-helper-container {
    display: none;
    height: 0;
    min-height: 0;
    margin: 0;
    padding: 0;
}

.user-form-label {
    margin: 0;
    color: var(--app-text);
    font-size: var(--app-text-form-label);
    font-weight: 700;
}

.form-required {
    color: var(--status-off);
    margin-inline-start: 0.15rem;
}

/* A locked field must read as locked at a glance: when the box inside a form field is
   disabled, its stacked label (and the required *) grays out with it. The :has() markers
   are enumerated per control type so a disabled adornment button (e.g. a picker's clear ×)
   inside an enabled field can't false-positive. Add project-specific field wrappers (page
   headers etc.) to the :is() list as the project grows. */
:is(.user-form-field):has(.native-form-control:disabled) > .user-form-label,
:is(.user-form-field):has(.searchable-native-select.is-disabled) > .user-form-label,
:is(.user-form-field):has(.mud-input.mud-input-disabled) > .user-form-label,
:is(.user-form-field):has(.mud-input.mud-disabled) > .user-form-label,
:is(.user-form-field):has(.mud-switch input:disabled) > .user-form-label {
    color: var(--app-input-disabled-text);
}

:is(.user-form-field):has(.native-form-control:disabled) > .user-form-label .form-required,
:is(.user-form-field):has(.searchable-native-select.is-disabled) > .user-form-label .form-required,
:is(.user-form-field):has(.mud-input.mud-input-disabled) > .user-form-label .form-required,
:is(.user-form-field):has(.mud-input.mud-disabled) > .user-form-label .form-required,
:is(.user-form-field):has(.mud-switch input:disabled) > .user-form-label .form-required {
    color: var(--app-input-disabled-text);
}

/* Informative (read-out) fields — the exception to the two rules above [developer-directed
   2026-08-11]. A box the user can NEVER type into — a value the form composes or derives, or
   one it reads off another record (the email a reset link is for) — is not a disabled input,
   it is live data drawn in a box. It keeps a sunken fill (that is what says "you don't type
   here"), but its value and its label read at full strength like an enabled field, so
   current data stops looking stale. A *temporarily* locked input — one a business rule shut,
   which the user can reopen — keeps the plain disabled look, so the two stop sharing one
   signal. Rule of thumb for picking: is there any state of the app in which the user types
   here? No → readout. Yes → plain disabled. Specificity is deliberately above the disabled
   label rules (0,4,0) and the disabled box rule further down, so this wins on merit, not on
   source order; the -webkit-text-fill-color + opacity pair is there because browsers dim a
   disabled input through those, not through `color` alone. */
.user-form-field.user-form-field--readout:has(.native-form-control:disabled) > .user-form-label {
    color: var(--app-text);
}

body.app-dark .user-form-field.user-form-field--readout:has(.native-form-control:disabled) > .user-form-label {
    color: var(--app-muted);
}

.user-form-field.user-form-field--readout .native-form-control:disabled {
    background: var(--app-input-readout-bg);
    color: var(--app-color-form-box-text);
    -webkit-text-fill-color: var(--app-color-form-box-text);
    opacity: 1;
}

/* =========================================================================
 * 14. FILTER: STATUS SELECT
 * ========================================================================= */
.table-filter-status {
    min-width: auto;
}

.table-toolbar .toolbar-filter-field .status-filter-select.mud-select {
    width: var(--toolbar-status-filter-width);
    min-width: var(--toolbar-status-filter-width);
    height: var(--toolbar-control-height);
    min-height: var(--toolbar-control-height);
    max-height: var(--toolbar-control-height);
    display: inline-flex;
    align-items: stretch;
    margin: 0;
    flex-shrink: 0;
    box-sizing: border-box;
}

.table-toolbar .toolbar-filter-field .status-filter-select.mud-select > .mud-input-control,
.table-toolbar .toolbar-filter-field .status-filter-select.mud-select .mud-input-control-input-container {
    height: var(--toolbar-control-height);
    min-height: var(--toolbar-control-height);
    max-height: var(--toolbar-control-height);
    margin: 0;
    padding: 0;
    display: flex;
    align-items: stretch;
}

.table-toolbar .toolbar-filter-field .status-filter-select.mud-select .mud-input.mud-input-outlined {
    flex: 1 1 auto;
    width: 100%;
    height: var(--toolbar-control-height);
    min-height: var(--toolbar-control-height);
    max-height: var(--toolbar-control-height);
    margin: 0;
    padding: 0;
    align-items: stretch;
    box-sizing: border-box;
}

.table-toolbar .toolbar-filter-field .status-filter-select.mud-select .mud-input-root,
.table-toolbar .toolbar-filter-field .status-filter-select.mud-select .mud-input-slot,
.table-toolbar .toolbar-filter-field .status-filter-select.mud-select input.mud-input-slot {
    height: var(--toolbar-control-height);
    min-height: var(--toolbar-control-height);
    max-height: var(--toolbar-control-height);
    margin: 0;
    padding: 0 0.5rem;
    background: var(--app-color-filter-box-bg);
    font-size: var(--app-text-filter-box);
    line-height: calc(var(--toolbar-control-height) - 2px);
    display: flex;
    align-items: center;
    justify-content: flex-start;
    text-align: start;
    box-sizing: border-box;
}

.status-filter-select-popover .mud-list-item.mud-list-item-dense.mud-list-item-gutters {
    min-height: 1.75rem;
    height: auto;
    padding-top: 0.25rem;
    padding-bottom: 0.25rem;
    padding-inline-start: 0.65rem;
    padding-inline-end: 0.65rem;
    box-sizing: border-box;
}

.status-filter-select-popover .mud-list-item,
.status-filter-select-popover .mud-list-item-text {
    justify-content: flex-start;
    text-align: start;
}

.status-filter-select-popover .mud-list-item-text .mud-typography {
    text-align: start;
    color: var(--app-text);
    font-size: var(--app-text-filter-box);
    line-height: 1.1;
}

.status-filter-select-popover .mud-selected-item,
.status-filter-select-popover .mud-selected-item.mud-primary-text {
    color: var(--app-text);
}

.status-filter-select-popover .mud-list-item-icon {
    min-width: 1rem !important;
    width: 1rem !important;
    margin-inline-end: 0.35rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.status-filter-select-popover .mud-list-item-icon .mud-icon-root {
    width: 1rem !important;
    height: 1rem !important;
    font-size: 1rem;
    color: var(--app-primary);
    fill: var(--app-primary);
}

.table-toolbar .toolbar-filter-field .status-filter-select.mud-select .mud-input-label {
    display: none;
}

.table-toolbar .toolbar-filter-field .status-filter-select.mud-select .mud-input-outlined-border {
    border-radius: var(--app-radius-form-box);
    border-color: var(--form-control-border);
    top: 0;
    bottom: 0;
    height: auto;
}

.table-toolbar .toolbar-filter-field .status-filter-select.mud-select .mud-input-helper-text {
    display: none;
}

.table-toolbar .toolbar-filter-field .status-filter-select.mud-select .mud-input-adornment-end {
    display: inline-flex;
    align-items: center;
    align-self: center;
    height: auto;
    margin-top: 0;
    margin-bottom: 0;
    padding-inline-end: 0.15rem;
}

.table-toolbar .toolbar-filter-field .status-filter-select.mud-select .mud-input-adornment-end .mud-icon-root {
    font-size: 1.125rem;
    color: var(--app-icon-muted);
}

/* =========================================================================
 * 15. FORMS: NATIVE CONTROLS & INPUT SIZING
 * ========================================================================= */
.user-form-field .mud-input-root {
    margin-top: 0;
}

.user-form-field .mud-input-outlined-border {
    border-color: var(--form-control-border);
    border-radius: var(--app-radius-form-box);
    top: 0;
    bottom: 0;
    height: auto;
}

/* ⚠️ A box outside a form field or a dialog (an editable table cell) falls through to the
   BROWSER default and paints white on the dark card unless its scope joins this list. Join the
   list rather than restate the values, so surface, ink, border, hover, focus ring, invalid
   state and both themes all arrive from the one place every other box in the app reads. */
.user-form-field .native-form-control,
.mud-dialog .native-form-control,
.editable-lines-table tbody td .native-form-control {
    width: 100%;
    box-sizing: border-box;
    padding: 0.45rem 0.65rem;
    border: 1px solid var(--app-color-form-box-border);
    border-radius: var(--app-radius-form-box);
    background: var(--app-color-form-box-bg);
    color: var(--app-color-form-box-text);
    font-size: var(--app-text-form-box);
    line-height: 1.35;
    outline: none;
}

/* Chrome autofill inside form fields/dialogs (e.g. the email box in the user form): the same
   inset-shadow trick as the login page — fakes the themed surface so an autofilled field
   doesn't stay white in dark mode. The transition delay stops Chrome repainting it. */
.user-form-field .native-form-control:-webkit-autofill,
.user-form-field .native-form-control:-webkit-autofill:hover,
.user-form-field .native-form-control:-internal-autofill-selected,
.mud-dialog .native-form-control:-webkit-autofill,
.mud-dialog .native-form-control:-webkit-autofill:hover,
.mud-dialog .native-form-control:-internal-autofill-selected {
    -webkit-box-shadow: 0 0 0 875px var(--app-color-form-box-bg) inset;
    -webkit-text-fill-color: var(--app-color-form-box-text);
    caret-color: var(--app-color-form-box-text);
    transition: background-color 600000s 0s, color 600000s 0s;
}

/* Focused autofill keeps the inset surface AND the app focus ring (the generic
   .native-form-control:focus rule's plain box-shadow would otherwise wipe the inset). */
.user-form-field .native-form-control:-webkit-autofill:focus,
.user-form-field .native-form-control:-internal-autofill-selected:focus,
.mud-dialog .native-form-control:-webkit-autofill:focus,
.mud-dialog .native-form-control:-internal-autofill-selected:focus {
    -webkit-box-shadow: 0 0 0 875px var(--app-color-form-box-bg) inset, var(--form-control-focus-shadow);
    box-shadow: 0 0 0 875px var(--app-color-form-box-bg) inset, var(--form-control-focus-shadow);
}

/* Login auth fields: padding shorthand above resets inline-start; restore room
   for the leading icon (must follow the rule immediately above). */
.login-page .user-form-field .login-input-shell .native-form-control {
    padding-inline-start: 2.45rem;
}

.login-page .user-form-field .login-input-shell.login-input-shell--end-adornment .native-form-control {
    padding-inline-end: 2.75rem;
}

.mud-select .mud-input-control-input-container,
.mud-select .mud-input-adornment-end {
    overflow: visible;
}

/* Entity dialogs: pin all single-line native controls to the same height as MudSelect. */
.entity-form-grid .user-form-field .native-form-control:not(.native-form-textarea),
.mud-dialog .entity-form-grid .user-form-field .native-form-control:not(.native-form-textarea) {
    height: var(--entity-form-control-height);
    min-height: var(--entity-form-control-height);
    max-height: var(--entity-form-control-height);
    padding-top: 0;
    padding-bottom: 0;
    line-height: calc(var(--entity-form-control-height) - 2px);
}

/* Numeric inputs render LTR (dir="ltr" on the element) so the minus always sits
   to the left of the digits in both LTR and RTL pages. On RTL pages keep them
   flush to the right edge like the surrounding fields. */
[dir="rtl"] .native-form-control.native-form-number {
    text-align: right;
}

.user-form-field .native-form-textarea,
.mud-dialog .native-form-textarea {
    min-height: 4.5rem;
    resize: vertical;
    font-size: var(--app-text-form-box);
}

/* Disabled boxes — EVERY box in the app dims the same way: sunken token fill + dimmed
   text, themed light + dark via the two --app-input-disabled-* tokens. The bare selector
   is the app-wide default; the scoped ones keep the win against higher-specificity
   context rules (dialog forms, floating-label filters). */
.native-form-control:disabled,
.user-form-field .native-form-control:disabled,
.mud-dialog .native-form-control:disabled,
.floating-native-field .searchable-native-input:disabled {
    background: var(--app-input-disabled-bg);
    color: var(--app-input-disabled-text);
}

.user-form-field .form-mud-select,
.mud-dialog .form-mud-select {
    width: 100%;
    margin: 0;
}

.user-form-field .form-mud-select .mud-input-control,
.mud-dialog .form-mud-select .mud-input-control {
    margin: 0;
    min-height: var(--form-control-height);
}

.user-form-field .form-mud-select .mud-input-slot,
.mud-dialog .form-mud-select .mud-input-slot {
    min-height: var(--form-control-height);
}

.user-form-field .form-mud-select .mud-input-outlined-border,
.mud-dialog .form-mud-select .mud-input-outlined-border {
    border-radius: var(--app-radius-form-box);
}

/* Match the multi-select (MudSelect) height to the single-select native
   controls (33px). MudBlazor's dense outlined input is ~54px tall because the
   content-box input adds vertical padding, so pin every layer to 33px and zero
   the input padding. */
.user-form-field .mud-select .mud-input-control,
.mud-dialog .mud-select .mud-input-control,
.user-form-field .mud-select .mud-input-control-input-container,
.mud-dialog .mud-select .mud-input-control-input-container,
.user-form-field .mud-select .mud-input.mud-input-outlined,
.mud-dialog .mud-select .mud-input.mud-input-outlined,
.user-form-field .mud-select .mud-input-outlined-border,
.mud-dialog .mud-select .mud-input-outlined-border {
    min-height: var(--entity-form-control-height);
    height: var(--entity-form-control-height);
}

.user-form-field .mud-select input.mud-input-slot,
.mud-dialog .mud-select input.mud-input-slot {
    height: var(--entity-form-control-height);
    min-height: var(--entity-form-control-height);
    box-sizing: border-box;
    padding-top: 0;
    padding-bottom: 0;
    font-size: var(--app-text-form-box);
}

.mud-select .mud-input-adornment-end .mud-icon-root,
.mud-select .mud-input-adornment-end.mud-icon-button .mud-icon-root,
.mud-select .mud-icon-button.mud-input-adornment-end .mud-icon-root,
.mud-select .mud-input-adornment-end svg,
.mud-autocomplete .mud-input-adornment-end .mud-icon-root,
.mud-autocomplete .mud-input-adornment-end svg {
    font-size: var(--dropdown-caret-icon-size);
    width: var(--dropdown-caret-icon-size) !important;
    height: var(--dropdown-caret-icon-size) !important;
}

/* Invalid state — one shared style for every form box, in light and dark mode.
   The dark-mode selectors repeat `body.app-dark` so they outrank the resting
   `body.app-dark .native-form-control { border-color: ... !important }` rule. */
.native-form-control.is-invalid,
/* A grid cell's box rule (`.editable-lines-table tbody td .native-form-control`, joined to the
   shared box list) carries two classes AND two elements, so it outranks the plain pair above and
   a rejected cell kept its normal border. Restated at the cell's own weight. */
.editable-lines-table tbody td .native-form-control.is-invalid,
.searchable-native-select.is-invalid .searchable-native-input,
.form-mud-select.is-invalid .mud-input-outlined .mud-input-outlined-border,
.avatar-color-select-wrap.is-invalid .native-form-control,
.user-form-roles-frame.is-invalid,
.file-drop.is-invalid,
body.app-dark .native-form-control.is-invalid,
body.app-dark .searchable-native-select.is-invalid .searchable-native-input,
body.app-dark .form-mud-select.is-invalid .mud-input-outlined .mud-input-outlined-border,
body.app-dark .avatar-color-select-wrap.is-invalid .native-form-control,
body.app-dark .user-form-roles-frame.is-invalid,
body.app-dark .file-drop.is-invalid {
    border-color: var(--form-control-invalid-border);
    box-shadow: var(--form-control-invalid-shadow);
}

/* Multi-select dropdown panel (e.g. project Employees) aligned with the app's
   other select panels (mirrors .status-filter-select-popover). */
.form-mud-select-popover .mud-list-item {
    /* Match the native select rows (.searchable-native-option): 1.8rem tall. */
    min-height: 1.8rem !important;
    height: 1.8rem !important;
    padding-top: 0;
    padding-bottom: 0;
    padding-inline-start: 0.75rem;
    padding-inline-end: 0.75rem;
    box-sizing: border-box;
}

.form-mud-select-popover .mud-list-item,
.form-mud-select-popover .mud-list-item-text {
    justify-content: flex-start;
    text-align: start;
}

.form-mud-select-popover .mud-list-item-text .mud-typography {
    text-align: start;
    color: var(--app-text);
    font-size: var(--app-text-form-box);
    line-height: 1.2;
}

.form-mud-select-popover .mud-selected-item,
.form-mud-select-popover .mud-selected-item.mud-primary-text {
    color: var(--app-text);
}

.form-mud-select-popover .mud-list-item-icon {
    min-width: 1rem !important;
    width: 1rem !important;
    margin-inline-end: 0.5rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.form-mud-select-popover .mud-list-item-icon .mud-icon-root {
    width: 1rem !important;
    height: 1rem !important;
    font-size: 1rem;
    color: var(--app-primary);
    fill: var(--app-primary);
}

/* Zoom root 4 of 4 (--app-zoom, section 01). Mud popovers - column filter menus, pickers,
   the pager menu - carry the app density too, or every one of them would sit at full size
   against a scaled page.
   The zoom goes on the popover's CONTENT, never on .mud-popover itself: MudBlazor writes the
   popover's top/left in px onto that element, and zooming it would rescale those offsets as
   well, dragging each panel off its anchor by a margin that grows the further down the page it
   opens. The positioned box is left alone and shrink-wraps the scaled child, so Mud's own math
   - which reads a getBoundingClientRect() in true viewport px - keeps landing it correctly.
   Relative-width popovers need the second rule: MudBlazor sizes those from the anchor's
   rendered width, so the content must be widened by 1/zoom to still fill the box once scaled. */
.mud-popover > * {
    zoom: var(--app-zoom);
}

.mud-popover.mud-popover-relative-width > * {
    width: calc(100% / var(--app-zoom));
}

/* MudSelect / MudAutocomplete dropdowns: show up to 15 rows, then scroll.
   The popover itself is the scroll container (default max-height 300px ≈ 7
   rows). Rows are ~42px tall, so cap the popover at 15 × 42px = 630px. Also
   cap to viewport height so lists near the screen edge stay scrollable. Scoped
   to .mud-popover-relative-width, which MudBlazor applies to select/autocomplete
   popovers (not menus or date pickers).
   The 100dvh here is deliberately NOT divided by --app-zoom: Mud popovers render in
   .mud-popover-provider, which is outside every zoom root (section 01), so it resolves against
   the real viewport already.
   overflow-x is pinned to hidden on purpose. Setting overflow-y alone makes the other axis
   compute from `visible` to `auto`, and the 1/zoom-widened child two rules up measures at its
   pre-zoom width (a 211px box reporting scrollWidth 240 at --app-zoom .88), so every select
   dropdown draws a useless horizontal scrollbar. Inert while --app-zoom is 1 — kept so lowering
   it stays a one-variable change. These lists never scroll sideways. */
.mud-popover.mud-popover-relative-width {
    max-height: min(calc(42px * 15), calc(100dvh - 16px)) !important; /* true-space: this popover box renders outside the shell; not rescaled in the density bake */
    overflow-y: auto;
    overflow-x: hidden;
}

.native-form-control.is-invalid:focus,
.searchable-native-select.is-invalid .searchable-native-input:focus {
    border-color: var(--form-control-invalid-border);
    box-shadow: var(--app-shadow-invalid-ring);
}

.form-field-error {
    display: block;
    margin-top: 0.35rem;
    color: var(--form-control-invalid-border);
    font-size: 0.75rem;
    line-height: 1.3;
}

.native-datetime-host {
    width: 100%;
}

.native-datetime-host .mud-picker,
.user-form-field .native-datetime-host .mud-input-control,
.mud-dialog .user-form-field .native-datetime-host .mud-input-control,
.user-form-field .native-datetime-host .mud-input-control-input-container,
.mud-dialog .user-form-field .native-datetime-host .mud-input-control-input-container,
.user-form-field .native-datetime-host .mud-input.mud-input-outlined,
.mud-dialog .user-form-field .native-datetime-host .mud-input.mud-input-outlined,
.user-form-field .native-datetime-host .mud-input-root,
.mud-dialog .user-form-field .native-datetime-host .mud-input-root,
.user-form-field .native-datetime-host .mud-input-slot,
.mud-dialog .user-form-field .native-datetime-host .mud-input-slot {
    min-height: var(--entity-form-control-height);
    height: var(--entity-form-control-height);
    max-height: var(--entity-form-control-height);
    margin: 0;
    padding-top: 0;
    padding-bottom: 0;
    box-sizing: border-box;
}

.native-datetime-host .mud-input-control {
    margin-top: 0;
}

.native-datetime-host .mud-input-root {
    margin: 0;
}

.native-datetime-host .mud-input-outlined-border,
.user-form-field .native-datetime-host .mud-input-outlined-border,
.mud-dialog .user-form-field .native-datetime-host .mud-input-outlined-border {
    border-color: var(--form-control-border);
    border-radius: var(--app-radius-form-box);
    top: 0;
    bottom: 0;
    height: auto;
}

/* Empty/invalid date & time boxes — same red treatment as every other form box.
   Specificity matches the forced resting rule above (and beats it via order). */
.native-datetime-host.is-invalid .mud-input-outlined-border,
.user-form-field .native-datetime-host.is-invalid .mud-input-outlined-border,
.mud-dialog .user-form-field .native-datetime-host.is-invalid .mud-input-outlined-border,
body.app-dark .native-datetime-host.is-invalid .mud-input-outlined-border,
body.app-dark .user-form-field .native-datetime-host.is-invalid .mud-input-outlined-border,
body.app-dark .mud-dialog .user-form-field .native-datetime-host.is-invalid .mud-input-outlined-border {
    border-color: var(--form-control-invalid-border);
    box-shadow: var(--form-control-invalid-shadow);
}

/* MudBlazor hard-codes the picker's text input to 14px via `.mud-input input`; align it with
   every other form box's size token. Target the REAL input element (`.native-datetime-host
   .mud-input input`, specificity 0-2-1) — the older `.mud-input-slot-input` selector matched
   nothing (MudBlazor renders no such class), so this rule was silently dead and every date/time
   box shipped at 14px. Padding stays MudBlazor's default so box metrics don't shift. */
.native-datetime-host .mud-input input {
    font-size: var(--app-text-form-box);
}

.native-datetime-host .mud-input-label {
    display: none;
}

/* …and close the border hole the hidden label leaves behind. MudBlazor 9 draws the outlined
   box as a <fieldset> whose <legend> holds the label text: the legend is invisible but takes
   width, cutting the "notch" in the top border a floating label sits in. Our label is the
   one-space HiddenLabel with ShrinkLabel always on, so the notch was permanently open — a ~12px
   gap at the border's inline start — with nothing in it. The rule above hides the label
   ELEMENT but the legend is a different node, so it must be closed by itself. Scoped off the
   --labeled variant, whose real label needs the notch. Specificity note: MudBlazor re-opens
   the legend at (0,4,1) on .mud-shrink, :focus-within and autofill — this selector's five
   classes beat all three, so the gap cannot re-open on focus. */
.native-datetime-host:not(.native-datetime-host--labeled) .mud-input.mud-input-outlined .mud-input-outlined-border legend {
    width: 0;
    padding: 0;
}

/* Opt-in variant (AppDatePicker Label=…): the field name floats on the box border instead of a
   separate stacked FormLabel above it — used by report/parameter filter fields, not data-entry
   forms. MudBlazor's own notched label sits at a scale(0.75)+translate transform that renders
   slightly bigger and offset from the SearchableNativeSelect picker's label next to it, so it is
   overridden to the SAME shared --app-field-label-* tokens and the same
   translateY(-50%)-on-the-border geometry as .floating-native-label — the two are then
   pixel-identical in a parameter row. Killing the scale means the font-size is the plain token
   (no /0.75 pre-divide the transform used to require). */
.native-datetime-host--labeled .mud-input-label {
    display: block;
    top: 0;
    left: auto;
    right: auto;
    inset-inline-start: var(--app-field-label-inset);
    transform: translateY(-50%);
    transform-origin: center;
    padding: 0 var(--app-field-label-pad-x);
    background: var(--app-field-label-bg);
    color: var(--app-field-label-color);
    font-size: var(--app-field-label-size);
    line-height: 1;
    white-space: nowrap;
}

.native-datetime-host--labeled .mud-input-control.mud-input-focused .mud-input-label,
.native-datetime-host--labeled .mud-input-control:focus-within .mud-input-label {
    color: var(--app-field-label-color-active);
}

.native-datetime-host .mud-input-helper-text {
    display: none;
}

.native-datetime-host .mud-input-adornment-end {
    align-self: center;
    margin-top: 0;
    margin-bottom: 0;
}

/* MudBlazor reserves 14px on the adornment side of the outlined box (.mud-input-adorned-end) so
   free-floating text never runs under the icon; our compact boxes don't need that much — drop it
   so the calendar icon sits close to the border, matching the other form controls. */
.native-datetime-host .mud-input-adorned-end {
    padding-inline-end: 0;
}

/* Compact the calendar/clock toggle so the clear (×) sits right next to it,
   matching the toolbar date filters. */
.native-datetime-host .mud-input-adornment-end .mud-icon-button:not(.mud-input-clear-button),
.native-datetime-host .mud-input-adornment-end .mud-icon-button-edge-end:not(.mud-input-clear-button) {
    width: 1.5rem;
    height: 1.5rem;
    padding: 0.15rem;
    margin-inline-end: 4.375px;
    margin-inline-end: 4.375px;
}

/* MudBlazor marks a disabled input `.mud-disabled` and its text box `.mud-input-slot`; the
   `.mud-input-disabled` / `.mud-input-slot-input` these rules used to wait for are never
   rendered, so no disabled date box in the app got its fill. The fill sits on the input itself,
   not on the outline fieldset: the fieldset is painted after the text and would cover it. */
.native-datetime-host .mud-input.mud-disabled,
.form-mud-select .mud-input.mud-disabled {
    background: var(--app-input-disabled-bg);
    border-radius: var(--app-radius-form-box);
}

.native-datetime-host .mud-input.mud-disabled .mud-input-slot,
.form-mud-select .mud-input.mud-disabled .mud-input-slot {
    color: var(--app-input-disabled-text);
    -webkit-text-fill-color: var(--app-input-disabled-text);
}

/* Opt-in floating-label variant of SearchableNativeSelect AND SearchableMultiSelect (Label=…):
   the field name floats on the box border — the same look as AppDatePicker's outlined label —
   used by report-parameter pickers that sit on a card, NOT data-entry forms which stack a
   FormLabel above the control. Without this the picker falls back to an unstyled native input
   beside the themed date box.
   The wrapper is deliberately bare (position + width): it draws nothing itself, so it fits
   whichever control it wraps — the single picker, whose border is on .searchable-native-input
   and is styled just below, or the chip box, which already carries its own border on
   .searchable-multi-control. The label anchors to the WRAPPER's top edge, which is the control's
   top border in both cases, so a chip box that grows several chip rows tall keeps its label
   exactly where a one-line picker puts it. */
.floating-native-field {
    position: relative;
    width: 100%;
}

.floating-native-field .searchable-native-input {
    height: var(--entity-form-control-height);
    min-height: var(--entity-form-control-height);
    box-sizing: border-box;
    padding-block: 0;
    padding-inline-start: 0.65rem;
    border: 1px solid var(--form-control-border);
    border-radius: var(--app-radius-form-box);
    background: var(--app-color-form-box-bg);
    color: var(--app-color-form-box-text);
    font-size: var(--app-text-form-box);
    line-height: calc(var(--entity-form-control-height) - 2px);
}

/* Floats over the top border and "notches" it with a card-colored background pad. Drives the
   shared --app-field-label-* tokens so the AppDatePicker date label (see
   .native-datetime-host--labeled .mud-input-label) lands in the exact same spot. z-index keeps it
   above the input; pointer-events:none lets clicks fall through to the field.
   Anchored to the box's TOP BORDER (top:0 + translateY(-50%)) and NOT to any height, so the box
   can be re-sized — as .report-date-filters does — with no label change. */
.floating-native-label {
    position: absolute;
    top: 0;
    inset-inline-start: var(--app-field-label-inset);
    transform: translateY(-50%);
    padding: 0 var(--app-field-label-pad-x);
    background: var(--app-field-label-bg);
    color: var(--app-field-label-color);
    font-size: var(--app-field-label-size);
    line-height: 1;
    white-space: nowrap;
    pointer-events: none;
    z-index: 1;
}

.floating-native-field.is-open .floating-native-label,
.floating-native-field:focus-within .floating-native-label {
    color: var(--app-field-label-color-active);
}

.floating-native-field.is-invalid .floating-native-label {
    color: var(--form-control-invalid-border);
}

/* Floating-label variants (report/filter fields): the floating label dims with its
   disabled box, same token as the stacked-label fields. */
.floating-native-field.is-disabled .floating-native-label,
.native-datetime-host--labeled:has(.mud-input.mud-disabled) .mud-input-label {
    color: var(--app-input-disabled-text);
}

/* =========================================================================
 * 16. SEARCHABLE SELECT & DROPDOWN PANEL
 * ========================================================================= */
.searchable-native-select {
    position: relative;
    display: block;
    width: 100%;
}

.searchable-native-select .searchable-native-input {
    display: block;
    width: 100%;
    padding-inline-end: calc(var(--dropdown-caret-size) + 1.1rem);
}

.searchable-native-select.has-clear .searchable-native-input {
    padding-inline-end: calc(var(--dropdown-clear-offset-right) + var(--dropdown-clear-hit-size) + 0.2rem);
}

/* 2.25rem = swatch start 0.75rem + swatch 1rem + a 0.5rem gap before the text. */
.searchable-native-select.has-color .searchable-native-input {
    padding-inline-start: 2.25rem;
}

.searchable-native-selected-swatch {
    position: absolute;
    top: 50%;
    inset-inline-start: 0.75rem;
    width: 1rem;
    height: 1rem;
    border-radius: var(--app-radius-circle);
    transform: translateY(-50%);
    box-shadow: var(--app-shadow-inset-swatch);
    pointer-events: none;
    z-index: 1;
}

.searchable-native-caret {
    position: absolute;
    inset-inline-end: 0.25rem;
    top: 0;
    bottom: 0;
    margin-top: auto;
    margin-bottom: auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: calc(var(--dropdown-caret-size) + 10.5px);
    height: calc(var(--dropdown-caret-size) + 10.5px);
    padding: 0;
    border: none;
    background-color: transparent;
    background-image: var(--dropdown-caret-svg);
    background-repeat: no-repeat;
    background-position: center;
    background-size: var(--dropdown-caret-size) auto;
    color: transparent;
    font-size: 0;
    line-height: 0;
    cursor: pointer;
}

.searchable-native-caret:disabled {
    cursor: default;
    opacity: 0.5;
}

/* Shared clear (×) — SearchableNativeSelect + Mud clearable fields (date pickers, column
   filters). */
.searchable-native-clear,
.mud-input-clear-button {
    box-sizing: border-box;
    padding: 0;
    border: none;
    background-color: transparent;
    background-image: var(--dropdown-clear-svg);
    background-repeat: no-repeat;
    background-position: center;
    background-size: var(--dropdown-clear-size) auto;
    color: transparent;
    font-size: 0;
    line-height: 0;
    cursor: pointer;
}

.searchable-native-clear {
    position: absolute;
    inset-inline-end: var(--dropdown-clear-offset-right);
    top: 0;
    bottom: 0;
    margin-top: auto;
    margin-bottom: auto;
    width: var(--dropdown-clear-hit-size);
    height: var(--dropdown-clear-hit-size);
}

.mud-input-clear-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    align-self: center;
    flex: 0 0 auto;
    margin: 0;
    min-height: 0;
    width: var(--dropdown-clear-hit-size);
    height: var(--dropdown-clear-hit-size);
    min-width: var(--dropdown-clear-hit-size);
    padding: 0;
    border-radius: 0;
    line-height: 0;
    vertical-align: middle;
    background-image: var(--dropdown-clear-svg) !important;
    background-repeat: no-repeat !important;
    background-position: center center !important;
    background-size: var(--dropdown-clear-size) auto !important;
}

.mud-input-clear-button .mud-icon-root,
.mud-input-clear-button .mud-icon-button-label,
.mud-input-clear-button svg {
    display: none;
}

.mud-input-adornment-end:has(.mud-input-clear-button) {
    display: inline-flex;
    align-items: center;
    align-self: center;
    margin-block: 0;
}

.searchable-native-clear:hover,
.mud-input-clear-button:hover {
    background-image: var(--dropdown-clear-hover-svg) !important;
    opacity: 1;
}

.searchable-native-clear:hover {
    background-color: transparent;
}

.mud-input-clear-button:hover,
.mud-input-clear-button.mud-ripple:hover,
.mud-input-clear-button:hover:not(.mud-button-disabled) {
    background-color: transparent;
    box-shadow: none;
}

.searchable-native-select.is-disabled .searchable-native-caret {
    opacity: 0.5;
}

.searchable-native-dropdown {
    position: absolute;
    left: 0;
    right: 0;
    top: calc(100% + 3.5px);
    z-index: 1300;
    box-sizing: border-box;
    /* Show at most 15 options (1.8rem each) + dropdown padding, then scroll. */
    max-height: calc(1.8rem * 15 + 0.5rem);
    overflow-y: hidden;
    background: var(--app-surface-raised);
    border: 1px solid var(--form-control-border);
    border-radius: var(--app-radius-form-box);
    box-shadow: var(--app-shadow-dropdown);
    padding: 0.25rem 0;
}

/* Portaled under document.body for dialogs; top/left/max-height set in JS from input rect */
/* Zoom root 3 of 4 (--app-zoom, section 01): leaving <body> also leaves the other zoom roots,
   so the panel carries the density itself - otherwise a full-size list would hang off a
   scaled-down input. searchableSelect.js divides the input's rect by the panel's currentCSSZoom
   to land it correctly in its own coordinate space. */
.searchable-native-dropdown.is-fixed-overlay {
    zoom: var(--app-zoom);
    position: fixed !important;
    right: auto !important;
    margin: 0;
}

.searchable-native-dropdown.is-scrollable {
    overflow-y: auto;
}

.searchable-native-option {
    display: block;
    width: 100%;
    text-align: start;
    /* Fixed 1.8rem row height so exactly 15 fit before scrolling. */
    height: 1.8rem;
    line-height: 1.8rem;
    padding: 0 0.75rem;
    border: none;
    background: transparent;
    color: var(--app-text);
    cursor: pointer;
    font-size: var(--app-text-form-box);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.table-toolbar .searchable-native-option,
.role-header-toolbar .searchable-native-option {
    font-size: var(--app-text-filter-box);
}

/* Mirror the selected input exactly: swatch absolutely centered at 0.75rem,
   text padded to 2.25rem, so list rows and the selected value line up pixel-for-pixel. */
.searchable-native-option.has-color {
    position: relative;
    padding-inline-start: 2.25rem;
}

.searchable-native-option.has-color .avatar-color-swatch {
    position: absolute;
    inset-inline-start: 0.75rem;
    top: 50%;
    transform: translateY(-50%);
}

/* ItemIcon — an icon right AFTER the text (in RTL: on its left), on the option and on the selected
   value. The selected one is an overlay that starts where the input's text starts (border 1px +
   the box's 0.65rem inline padding) and carries an invisible copy of the text, so the icon follows
   the real text exactly; same font size as the box. */
.searchable-native-option-icon {
    display: inline-flex;
    vertical-align: middle;
    margin-inline-start: 0.375rem;
}

/* An option with an icon: a flex row centres the icon on the text's middle (inline `middle` sits on
   the x-height, a pixel or two low), and the label holds one fixed slot so every option's icon
   stands in one column, whatever the label's width (ILS is narrower than USD)
   [developer-directed 2026-10-31]. A longer label simply pushes its own icon on. */
.searchable-native-option.has-icon {
    display: flex;
    align-items: center;
}

.searchable-native-option.has-icon .searchable-native-option-label {
    min-width: 2.4em;
}

.searchable-native-selected-icon {
    position: absolute;
    inset-inline-start: calc(1px + 0.65rem);
    top: 50%;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    font-size: var(--app-text-form-box);
    white-space: pre;
    pointer-events: none;
    z-index: 1;
}

.searchable-native-text-ghost {
    visibility: hidden;
}

/* CurrencyIcon — the currency's sign, coloured per currency: ₪ blue, $ green, € amber, text only
   (no circle) [developer-directed 2026-10-30, after a filled circle and a grey sign]. It sits after
   the code (.currency-label in a table cell; ItemIcon in the pickers). Arial for the sign: Plus
   Jakarta Sans draws ₪ tall and narrow — a box at this size. */
:root {
    --currency-ils: #2563eb;
    --currency-usd: #15803d;
    --currency-eur: #c2410c;
}

body.app-dark {
    --currency-ils: #6b9bff;
    --currency-usd: #34c26b;
    --currency-eur: #f0a04b;
}

.currency-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    color: var(--app-muted);
    font-family: Arial, "Segoe UI", sans-serif;
    font-weight: 700;
    /* Larger than the code beside it [developer-directed 2026-10-30], relative so the table cell,
       the picker box and its list all grow alike. */
    font-size: 1.35em;
    line-height: 1;
    /* One width for every sign (₪ is wider than $), centred, so the signs share one column. */
    width: 0.8em;
}

.currency-icon--ils { color: var(--currency-ils); }
.currency-icon--usd { color: var(--currency-usd); }
.currency-icon--eur { color: var(--currency-eur); }

.currency-label {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
}

/* The code holds one slot in a table cell too, so the signs line up down the column. */
.currency-label__code {
    min-width: 2.4em;
}

.searchable-native-option:hover,
.searchable-native-option.is-selected {
    background: var(--app-primary-tint-subtle);
}

/* Keyboard-highlighted option (Arrow Up/Down) — stronger than hover/selected. */
.searchable-native-option.is-active {
    background: var(--app-primary-tint-strong);
}

.searchable-native-empty {
    /* Single option-row height, centered both ways, so the box hugs the message
       without leaving a scrollable sliver. */
    height: 1.8rem;
    line-height: 1.8rem;
    padding: 0 0.75rem;
    text-align: center;
    color: var(--app-icon-muted);
    font-size: 0.85rem;
}

/* The hidden-matches line closing a capped list (Select.MoreItems): the empty state's
   muted voice, ruled off from the options above it, and not a button — it is not
   pickable, so it takes no hover and the keyboard never highlights it. */
.searchable-native-more {
    padding: 0.35rem 0.75rem;
    border-top: 1px solid var(--app-border);
    text-align: center;
    color: var(--app-icon-muted);
    font-size: 0.85rem;
}

.table-search input::placeholder {
    color: transparent;
    opacity: 0;
}

/* -------------------------------------------------------------------------
 * Chip multi-select (SearchableMultiSelect) — the box holds one chip per picked
 * item and wraps to as many rows as it needs; the dropdown panel/options are the
 * shared .searchable-native-* ones above.
 * ------------------------------------------------------------------------- */
.searchable-multi-select {
    position: relative;
    display: block;
    width: 100%;
}

.searchable-multi-control {
    display: flex;
    align-items: flex-start;
    gap: 0.25rem;
    box-sizing: border-box;
    width: 100%;
    /* One empty row matches a single-line form control; extra chip rows grow it. */
    min-height: var(--entity-form-control-height);
    padding: 0.2rem 0.4rem;
    border: 1px solid var(--app-color-form-box-border);
    border-radius: var(--app-radius-form-box);
    background: var(--app-color-form-box-bg);
    color: var(--app-color-form-box-text);
    font-size: var(--app-text-form-box);
    cursor: text;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.searchable-multi-values {
    display: flex;
    flex: 1 1 auto;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem;
    min-width: 0;
    /* Fill the control's inner height so a chipless box still centers its caret row. */
    min-height: calc(var(--entity-form-control-height) - 0.4rem - 2px);
}

.searchable-multi-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    max-width: 100%;
    padding: 0.1rem 0.45rem;
    border: 1px solid var(--form-control-border);
    border-radius: var(--app-radius-form-box);
    background: var(--app-primary-tint-subtle);
    color: var(--app-text);
    line-height: 1.5;
}

.searchable-multi-chip-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.searchable-multi-chip-remove {
    flex: 0 0 auto;
    width: 0.9rem;
    height: 0.9rem;
    padding: 0;
    border: none;
    background-color: transparent;
    background-image: var(--dropdown-clear-svg);
    background-repeat: no-repeat;
    background-position: center;
    background-size: 0.6rem auto;
    color: transparent;
    font-size: 0;
    line-height: 0;
    cursor: pointer;
}

.searchable-multi-chip-remove:hover {
    background-image: var(--dropdown-clear-hover-svg);
}

.searchable-multi-input {
    flex: 1 1 4rem;
    min-width: 3rem;
    box-sizing: border-box;
    height: 1.5rem;
    padding: 0;
    border: none;
    outline: none;
    background: transparent;
    color: inherit;
    font-family: inherit;
    font-size: inherit;
}

.searchable-multi-actions {
    display: flex;
    align-items: center;
    flex: 0 0 auto;
    gap: 0.1rem;
    /* Stay level with the first chip row instead of drifting down as the box grows.
       Height is FIXED, not just a floor: the clear button's hit box
       (--dropdown-clear-hit-size, 26px) is taller than a chip row, and as the tallest
       flex child it would set the control's content height — leaving the chips, pinned
       up by align-items: flex-start, with dead space underneath (uneven top/bottom
       padding). Capping the row lets the button overflow its 0.7px harmlessly instead. */
    height: calc(var(--entity-form-control-height) - 0.4rem - 2px);
}

.searchable-multi-clear {
    width: var(--dropdown-clear-hit-size);
    height: var(--dropdown-clear-hit-size);
    padding: 0;
    border: none;
    background-color: transparent;
    background-image: var(--dropdown-clear-svg);
    background-repeat: no-repeat;
    background-position: center;
    background-size: var(--dropdown-clear-size) auto;
    color: transparent;
    font-size: 0;
    line-height: 0;
    cursor: pointer;
}

.searchable-multi-clear:hover {
    background-image: var(--dropdown-clear-hover-svg);
}

.searchable-multi-caret {
    width: calc(var(--dropdown-caret-size) + 10.5px);
    height: calc(var(--dropdown-caret-size) + 10.5px);
    padding: 0;
    border: none;
    background-color: transparent;
    background-image: var(--dropdown-caret-svg);
    background-repeat: no-repeat;
    background-position: center;
    background-size: var(--dropdown-caret-size) auto;
    color: transparent;
    font-size: 0;
    line-height: 0;
    cursor: pointer;
}

.searchable-multi-select.is-disabled .searchable-multi-control {
    background: var(--app-input-disabled-bg);
    color: var(--app-input-disabled-text);
    cursor: default;
}

.searchable-multi-select.is-disabled .searchable-multi-caret {
    opacity: 0.5;
}

.searchable-multi-select:not(.is-disabled):not(:focus-within):hover .searchable-multi-control {
    border-color: var(--form-control-border);
}

.searchable-multi-select:focus-within:not(.is-invalid) .searchable-multi-control {
    border-color: var(--form-control-focus-border);
    box-shadow: var(--form-control-focus-shadow);
}

.searchable-multi-select.is-invalid .searchable-multi-control,
body.app-dark .searchable-multi-select.is-invalid .searchable-multi-control {
    border-color: var(--form-control-invalid-border);
    box-shadow: var(--form-control-invalid-shadow);
}

/* =========================================================================
 * 17. FORMS: STATUS TOGGLE & FORM ACTIONS
 * ========================================================================= */
.user-form-status-row {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    box-sizing: border-box;
    min-height: var(--form-control-height);
}

.status-light-toggle {
    --toggle-track-height: calc(var(--form-control-height) - 5.25px);
    --toggle-inset: 2px;
    --toggle-knob-size: calc(var(--toggle-track-height) - (var(--toggle-inset) * 2));
    --toggle-track-width: calc(var(--toggle-track-height) * 1.78);
    position: relative;
    flex-shrink: 0;
    width: var(--toggle-track-width);
    height: var(--toggle-track-height);
    padding: 0;
    border: none;
    border-radius: var(--app-radius-pill);
    cursor: pointer;
    box-shadow: var(--app-shadow-inset-deep);
    transition: background-color 180ms ease;
}

.status-light-toggle.is-on {
    background-color: var(--status-on);
}

.status-light-toggle.is-off {
    background-color: var(--status-off);
}

.user-status-pill {
    /* The pill is the one control whose edge may NOT be its own accent: it is SOLID-filled,
       so an edge in the fill's colour paints nothing. --control-border-color is the per-part
       override for exactly this case - a darker shade OF the fill, so the rim stays in the
       pill's own hue and needs no colour token of its own, in either theme. It still rides
       the switch: at 0% strength the rim disappears with every other edge.
       WARNING: the mix and the STRENGTH multiply - a rim judged at 100% is half gone at 55%,
       so the two numbers have to be chosen together.
       WARNING: the padding is pre-compensated by the 1px. Unlike a fixed-size control this
       pill is sized by its CONTENT, so box-sizing alone cannot hold its outer box (only
       min-width goes through it), and it sits in a dense table row. */
    --control-border-color: color-mix(in srgb, var(--control-accent) 50%, black);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 4.5rem;
    padding: calc(0.22rem - 1px) calc(0.8rem - 1px);
    box-sizing: border-box;
    border-width: 1px;
    border-style: solid;
    border-color: color-mix(in srgb,
        var(--control-border-color, var(--control-accent, currentColor)) var(--app-control-border-strength),
        transparent);
    border-radius: var(--app-radius-pill);
    color: var(--app-on-primary);
    font-size: 0.8125rem;
    font-weight: 400;
    letter-spacing: normal;
    line-height: 1.2;
    box-shadow: var(--app-shadow-inset-pressed);
}

.user-status-pill.is-active {
    --control-accent: var(--status-on);
    background-color: var(--status-on);
}

.user-status-pill.is-inactive {
    --control-accent: var(--status-off);
    background-color: var(--status-off);
}

/* A document in progress (בתהליך) — the app's orange, which says "still going". NOT the red
   of is-inactive: a state, not a fault. */
.user-status-pill.is-in-progress {
    --control-accent: var(--app-warning);
    background-color: var(--app-warning);
}

/* A document that ended without a good end (נדחה / בוטל) — grey: nothing is waiting on it any
   more, so it must not shout like the red of is-inactive. */
.user-status-pill.is-ended {
    --control-accent: var(--status-ended);
    background-color: var(--status-ended);
}

.status-light-toggle:focus-visible {
    outline: 2px solid var(--app-primary);
    outline-offset: 2px;
}

.status-light-knob {
    position: absolute;
    top: var(--toggle-inset);
    inset-inline-start: var(--toggle-inset);
    width: var(--toggle-knob-size);
    height: var(--toggle-knob-size);
    border-radius: var(--app-radius-circle);
    background: var(--app-color-filter-box-bg);
    box-shadow: var(--app-shadow-elevation-sm);
    transition: transform 180ms ease;
}

.status-light-toggle.is-on .status-light-knob {
    transform: translateX(calc(var(--toggle-track-width) - var(--toggle-knob-size) - (var(--toggle-inset) * 2)));
}

[dir="rtl"] .status-light-toggle.is-on .status-light-knob {
    transform: translateX(calc(-1 * (var(--toggle-track-width) - var(--toggle-knob-size) - (var(--toggle-inset) * 2))));
}

/* Primary-colored variant (dark-mode / notifications / invite toggles): neutral track
   when off, the app's main color when on. Same shape/knob as the record-status toggle. */
.status-light-toggle.status-light-toggle--primary.is-off {
    background-color: var(--app-border-muted);
}

.status-light-toggle.status-light-toggle--primary.is-on {
    background-color: var(--app-primary);
}

/* Disabled toggle: dim and non-interactive, matching MudSwitch's disabled feel. */
.status-light-toggle:disabled {
    cursor: not-allowed;
    opacity: 0.5;
}

.user-form-status-label {
    color: var(--app-text);
    font-size: var(--app-text-form-status);
    font-weight: 400;
    line-height: 1.5;
}

.user-form-actions {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-top: 1.25rem;
}

.user-form-submit,
.mud-button-root.user-form-submit {
    min-height: 33.25px;
    /* Inline compensation for this size override; the block half is absorbed by min-height. */
    padding: 0 calc(1.35rem - 1px);
    border-radius: var(--app-radius-button);
}

.mud-button-root.soft-primary-button.user-form-submit {
    background-color: var(--app-green-soft);
    color: var(--app-green);
    box-shadow: none;
    text-transform: none;
    font-weight: 700;
}

.user-form-cancel {
    text-transform: none;
    font-weight: 600;
    color: var(--app-muted);
}

/* =========================================================================
 * 18. RESPONSIVE (@media)
 * ========================================================================= */
/* Breakpoint legend (CSS vars cannot be used in @media conditions, so values are literal):
   - phone   : max-width 600px  (a few edge tweaks at 430px / 599px)
   - tablet  : 601px - 960px
   - desktop : min-width 961px
   The bulk of responsive rules live here; a few component-specific @media blocks are
   intentionally co-located with their component (forms, dialogs, settings drawer). */
@media (max-width: 960px) {
    :root {
        --form-control-height: 29.75px;
        --entity-form-control-height: 27.125px;
        --dropdown-caret-icon-size: 17.5px;
        --app-compact-user-avatar-size: 21px;
        --app-text-scale: 10.5px;
    }

    body {
        font-size: 9.625px;
    }

    .mud-typography-body1 {
        font-size: 9.625px;
    }

    .mud-typography-body2 {
        font-size: 10.5px;
    }

    .mud-typography-h6,
    .card-title {
        font-size: 11.375px;
    }

    .user-form-title {
        font-size: 10.5px;
    }

    .page-title {
        font-size: 14px;
        line-height: 1.25;
    }

    .muted-text {
        font-size: 10.5px;
    }

    /*
     * Keep the same viewport-locked flex scroll model as desktop so list tables
     * scroll inside .mud-table-container (horizontal + vertical), not the page.
     */
    body:has(.app-shell-narrow) {
        overflow: hidden;
    }

    /* Viewport heights divided by --app-zoom, as on the desktop shell. The vh-then-dvh pair
       is the usual mobile-toolbar fallback and both halves need the divisor. */
    .app-shell-narrow {
        height: calc(100vh / var(--app-zoom));
        height: calc(100dvh / var(--app-zoom));
        min-height: calc(100vh / var(--app-zoom));
        min-height: calc(100dvh / var(--app-zoom));
        overflow: hidden;
    }

    .app-shell-narrow .app-main.mud-main-content {
        height: calc(100vh / var(--app-zoom));
        height: calc(100dvh / var(--app-zoom));
        overflow: hidden;
    }

    .app-shell-narrow .app-content,
    .app-shell-narrow .app-content > div {
        overflow: hidden;
        min-height: 0;
        flex: 1 1 auto;
        min-width: 0;
        max-width: 100%;
    }

    .app-shell-narrow .page-stack,
    .app-shell-narrow .page-stack > .data-card,
    .app-shell-narrow .page-card-body,
    .app-shell-narrow .users-table-shell,
    .app-shell-narrow .users-table-shell > .users-table,
    .app-shell-narrow .users-table-shell > .mud-table {
        min-width: 0;
        max-width: 100%;
    }

    .app-shell-narrow .users-table-shell {
        overflow: hidden;
        width: 100%;
        flex: 1 1 auto;
        min-height: 0;
    }

    .app-shell-narrow .users-table-shell .mud-table-container {
        overflow-x: auto;
        overflow-y: auto;
        max-width: 100%;
        min-width: 0;
        flex: 1 1 auto;
        min-height: 0;
        overscroll-behavior: contain;
        -webkit-overflow-scrolling: touch;
    }

    .app-sidebar-overlay .mud-drawer-content,
    .app-sidebar-overlay.mud-drawer {
        width: var(--app-sidebar-width) !important;
        min-width: var(--app-sidebar-width) !important;
        max-width: min(calc(85vw / var(--app-zoom)), 245px) !important;
    }

    /* The topbar and the sidebar's brand row are the two halves of ONE horizontal rule
       (see .topbar-inner §05) — so this tier steps the SHARED token and both consumers follow.
       The min-height literal that used to sit on .topbar-inner here left .sidebar-brand on the
       base token, and the seam broke the moment browser zoom pushed the viewport under the
       breakpoint [developer-reported 2026-09-02]. */
    :root {
        --app-topbar-height: 46.5px;
    }

    .topbar-inner {
        gap: 0.4rem;
        padding: 0 0.7rem;
    }

    /* Both scopes DECLARE this token (the login page's switcher is outside .topbar and
       carries its own 42px), so both have to step it — .topbar-icon reads the token and would
       otherwise stay 42px on the login page. Repeated in the 600px and 430px blocks below. */
    .topbar,
    .login-layout-switcher {
        --topbar-control-size: 29.75px;
    }

    .topbar-user-menu {
        width: min(236.25px, calc(100vw / var(--app-zoom) - 1rem));
        max-width: calc(100vw / var(--app-zoom) - 1rem);
    }

    .topbar-icon.mud-icon-button {
        width: var(--topbar-control-size);
        height: var(--topbar-control-size);
        padding: 4.375px;
    }

    .topbar-icon.mud-icon-button .mud-icon-root {
        font-size: 1.15rem;
    }

    .topbar-user:not(.topbar-user-compact) {
        gap: 0.45rem;
        padding-block: 0;
        padding-inline-start: var(--topbar-user-avatar-inset);
        padding-inline-end: 0.5rem;
    }

    .topbar-user-role,
    .logout-button {
        display: none;
    }

    /* The shell's card gap follows the page inset at this width (see --app-shell-gap). */
    :root {
        --app-shell-gap: 0.75rem;
    }

    .app-content {
        padding-top: var(--app-shell-gap);
        padding-bottom: var(--app-shell-gap);
        --app-content-pad-inline: 0.75rem;
        padding-inline-start: var(--app-content-pad-inline);
        padding-inline-end: var(--app-content-pad-inline);
    }

    .page-card-header {
        --toolbar-control-height: 29.75px;
        flex-wrap: nowrap;
        gap: 0.6rem;
        padding: 0.85rem 1rem;
        min-height: calc(1.7rem + var(--toolbar-control-height, 29.75px));
    }

    .page-card-header-actions {
        gap: 0.35rem;
        --toolbar-control-height: 29.75px;
    }

    .page-card-body.pa-4 {
        --page-card-inset: 0.9rem;
        padding: 0.9rem;
    }

    .page-hero,
    .content-card.pa-5 {
        padding: 0.85rem 1rem;
    }

    .permissions-section-header {
        flex-wrap: wrap;
        gap: 0.45rem;
        padding-bottom: 0.5rem;
    }

    .table-toolbar,
    .role-header-toolbar {
        --toolbar-control-height: 29.75px;
        --toolbar-floating-control-height: 36.75px;
        --toolbar-band-height: 45.5px;
        --toolbar-label-gap: 0.45rem;
        gap: 0.45rem;
    }

    .table-toolbar {
        min-height: var(--toolbar-band-height);
        padding-top: 0.35rem;
        padding-bottom: 0.65rem;
    }

    .table-toolbar-shell .table-toolbar {
        min-height: 0;
        padding-top: 0;
        padding-bottom: 0;
    }

    .table-search-group-track,
    .table-search-group:not(:has(.table-search-group-track)) {
        gap: 0.45rem;
    }

    .table-toolbar .toolbar-filter-label,
    .role-header-toolbar .toolbar-filter-label {
        font-size: 0.625rem;
        inset-inline-start: 0.45rem;
    }

    .data-card .mud-table-head .mud-table-cell,
    .data-card .mud-table-body .mud-table-cell {
        height: 35px;
        padding: 0 10.5px;
        font-size: 10.5px;
    }

    .data-card .mud-table-head .mud-table-cell.users-table-actions-header,
    .data-card .mud-table-body .mud-table-cell.users-table-actions-cell {
        --users-actions-btn: 24.5px;
        --users-actions-inner-gap: 0.25rem;
        --users-actions-gap: 0.375rem;
    }

    .data-card .mud-table-head .mud-table-cell.users-table-photo-header,
    .data-card .mud-table-body .mud-table-cell.users-table-photo-cell {
        --users-photo-gap: 0.25rem;
    }

    .data-card .mud-table-body .mud-table-cell.users-table-photo-cell .mud-avatar {
        font-size: calc(var(--users-photo-avatar) * 0.36);
    }

    .data-card .mud-table-body .mud-table-cell.users-table-photo-cell .mud-avatar.user-avatar-material {
        box-shadow: var(--app-shadow-inset-compact);
    }

    .data-card .users-table-shell .mud-table-container > .mud-table-root {
        margin-bottom: 10.5px;
    }

    .data-card .users-table-shell .mud-table-container > .mud-table-root .mud-table-empty-row {
        height: 35px;
    }

    /* A second :root block in this tier, co-located with the row-action rules it belongs to
       rather than added to the big one at the top — the ladder reads as one thing here. */
    :root {
        --action-btn-size: 24.5px;
        --action-btn-icon-size: 1rem;
    }

    .row-actions {
        gap: 0.25rem;
    }

    .app-pager {
        gap: 0.35rem;
        padding: 0.4rem 0.65rem;
        font-size: 10.5px;
    }

    .app-pager-left {
        gap: 0.45rem;
    }

    .app-pager-rows-label,
    .app-pager-info {
        font-size: 10.5px;
    }

    .app-pager-rows-select {
        height: 22.75px;
        font-size: 10.5px;
    }

    .app-pager-text,
    .app-pager-num {
        height: 22.75px;
        font-size: 10.5px;
        padding: 0 0.4rem;
    }

    .user-status-pill,
    .role-pill {
        min-width: 3.75rem;
        padding: 0.15rem 0.55rem;
        font-size: 0.75rem;
    }

    .role-chip-list {
        gap: 0.25rem;
    }

    .app-sidebar-overlay .sidebar-link {
        min-height: 31.5px;
        font-size: 11.375px;
    }

    .app-sidebar-overlay .sidebar-brand {
        padding: 0 0.9rem;
    }

    .table-search {
        max-width: none;
    }

    .role-header-toolbar {
        flex-wrap: nowrap;
    }

    .role-header-toolbar .toolbar-filter-field {
        flex: 0 0 auto;
        width: auto;
    }

    .role-header-toolbar .toolbar-filter-field .searchable-native-select.table-search-select {
        width: var(--toolbar-filter-width);
        min-width: var(--toolbar-filter-width);
    }

    .permission-matrix th,
    .permission-matrix td {
        height: 33.25px;
        padding: 0 8.75px;
        font-size: 9.625px;
    }

    .role-header-toolbar .role-action-buttons {
        flex: 0 0 auto;
        flex-wrap: nowrap;
        width: auto;
    }

    .role-header-toolbar .role-action-buttons .role-action-button {
        --role-action-button-width: var(--toolbar-control-height, 29.75px);
        flex: 0 0 var(--role-action-button-width);
        width: var(--role-action-button-width);
        min-width: var(--role-action-button-width);
        max-width: var(--role-action-button-width);
        min-height: var(--toolbar-control-height, 29.75px);
        height: var(--toolbar-control-height, 29.75px);
        padding: 0;
        justify-content: center;
        font-size: 0.75rem;
    }

    .role-header-toolbar .role-action-buttons .role-action-button .mud-button-label {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        font-size: 0;
        line-height: 0;
        gap: 0;
    }

    .role-header-toolbar .role-action-buttons .role-action-button .mud-button-icon-start {
        margin-inline-end: 0;
        margin-inline-start: 0;
    }

    .role-header-toolbar .role-action-buttons .role-action-button .mud-icon-root {
        font-size: 0.95rem;
    }

    .permissions-save-button {
        --role-action-button-width: calc(5.75rem - 8.75px);
        flex: 0 0 var(--role-action-button-width);
        width: var(--role-action-button-width);
        min-width: var(--role-action-button-width);
        max-width: var(--role-action-button-width);
        min-height: 28px;
        height: 28px;
        font-size: 0.75rem;
    }

    .table-toolbar .searchable-native-select.table-search-select .searchable-native-input,
    .table-toolbar .table-search-select .searchable-native-select .searchable-native-input {
        padding: 0 2rem 0 0.5rem;
    }

    .mud-dialog .mud-dialog-title {
        font-size: 13.125px;
        padding: 0.75rem 1rem 0.35rem;
    }

    .mud-dialog-container .mud-dialog.entity-form-dialog .mud-dialog-title,
    .mud-dialog-container .mud-dialog:has(.entity-form-grid) .mud-dialog-title {
        font-size: 11.375px;
    }

    .mud-dialog-container .mud-dialog.entity-form-dialog .mud-dialog-title .user-form-title,
    .mud-dialog-container .mud-dialog:has(.entity-form-grid) .mud-dialog-title .user-form-title {
        font-size: 10.5px;
    }

    .mud-dialog .mud-dialog-content {
        padding: 0.65rem 1rem;
    }

    .mud-dialog .mud-dialog-actions {
        padding: 0.5rem 1rem 0.75rem;
        gap: 0.45rem;
    }
}

@media (max-width: 600px) {
    :root {
        --form-control-height: 28px;
        --entity-form-control-height: 26.25px;
        --dropdown-caret-icon-size: 15.75px;
        --dropdown-caret-size: 7.875px;
        --app-compact-user-avatar-size: 19.25px;
        --app-text-scale: 9.625px;
    }

    body {
        font-size: 9.625px;
        line-height: 1.4;
    }

    .page-title {
        font-size: 13.125px;
    }

    .user-status-pill,
    .role-pill {
        font-size: 9.625px;
    }

    /* The shell's card gap follows the page inset at this width (see --app-shell-gap). */
    :root {
        --app-shell-gap: 0.5rem;
    }

    .app-content {
        --app-content-pad-inline: 0.5rem;
        padding-inline-start: var(--app-content-pad-inline);
        padding-inline-end: var(--app-content-pad-inline);
        padding-top: var(--app-shell-gap);
        padding-bottom: var(--app-shell-gap);
    }

    .page-card-header {
        --toolbar-control-height: 29.75px;
        flex-wrap: nowrap;
        flex-direction: row;
        align-items: center;
        gap: 0.45rem;
        padding: 0.6rem 0.7rem;
        min-height: calc(1.2rem + var(--toolbar-control-height, 29.75px));
    }

    .page-card-header-actions {
        flex: 0 0 auto;
        width: auto;
        flex-wrap: nowrap;
        gap: 0.3rem;
    }

    .page-card-body.pa-4 {
        --page-card-inset: 0.6rem;
        padding: 0.6rem;
    }

    .page-hero,
    .content-card.pa-5 {
        padding: 0.6rem 0.7rem;
    }

    /* The topbar and the sidebar's brand row are the two halves of ONE horizontal rule
       (see .topbar-inner §05) — so this tier steps the SHARED token and both consumers follow.
       The min-height literal that used to sit on .topbar-inner here left .sidebar-brand on the
       base token, and the seam broke the moment browser zoom pushed the viewport under the
       breakpoint [developer-reported 2026-09-02]. */
    :root {
        --app-topbar-height: 41.25px;
    }

    .topbar-inner {
        gap: 0.25rem;
        padding: 0 0.5rem;
    }

    .topbar,
    .login-layout-switcher {
        --topbar-control-size: 28px;
    }

    .topbar-icon.mud-icon-button {
        width: var(--topbar-control-size);
        height: var(--topbar-control-size);
        padding: 3.5px;
    }

    .topbar-icon.mud-icon-button .mud-icon-root {
        font-size: 1.05rem;
    }

    .topbar-user-name,
    .topbar-user-arrow {
        display: none;
    }

    .topbar-logout-button {
        display: inline-flex;
    }

    .topbar-actions {
        gap: 0.25rem;
    }

    .topbar-end {
        gap: 0.25rem;
    }

    .topbar-user:not(.topbar-user-compact) {
        padding: var(--topbar-user-avatar-inset);
    }

    .topbar-user > div:not(:first-child) {
        display: none;
    }

    .topbar-user-menu {
        padding: 0.75rem;
        padding-bottom: 0.4rem;
        font-size: 10.5px;
    }

    .user-menu-header {
        gap: 0.5rem;
    }

    .user-menu-header .profile-image-picker-avatar.is-medium {
        width: 2.875rem;
        height: 2.875rem;
    }

    .user-menu-header .profile-image-picker-avatar.is-medium .mud-avatar {
        font-size: 0.925rem;
    }

    .user-menu-change-password,
    .user-menu-logout {
        font-size: 10.5px;
        padding: 0.4rem 0;
    }

    .user-menu-actions {
        margin-top: 0.5rem;
    }

    .user-menu-actions-divider {
        margin: 0.4rem 0;
    }

    .user-menu-divider {
        margin: 0.5rem 0 0.35rem;
    }

    .table-toolbar,
    .role-header-toolbar {
        --toolbar-control-height: 28px;
        --toolbar-floating-control-height: 33.25px;
        --toolbar-band-height: 42px;
        --toolbar-label-gap: 0.4rem;
        gap: 0.35rem;
    }

    .table-toolbar {
        padding-top: 0.3rem;
        padding-bottom: 0.5rem;
    }

    .table-search-group-track,
    .table-search-group:not(:has(.table-search-group-track)) {
        gap: 0.35rem;
    }

    .role-action-buttons {
        flex-wrap: nowrap;
        width: auto;
    }

    /* Keep the Save button a fixed-size icon-only button on small screens
       instead of stretching to full width. */
    .permissions-section-header .permissions-save-button {
        flex: 0 0 var(--toolbar-control-height, 28px);
        width: var(--toolbar-control-height, 28px);
        min-width: var(--toolbar-control-height, 28px);
        max-width: var(--toolbar-control-height, 28px);
        min-height: var(--toolbar-control-height, 28px);
        height: var(--toolbar-control-height, 28px);
        padding: 0;
        justify-content: center;
    }

    .permissions-save-button .mud-button-label {
        font-size: 0;
        line-height: 0;
        gap: 0;
    }

    .permissions-save-button .mud-button-icon-start {
        margin-inline-start: 0;
        margin-inline-end: 0;
    }

    .permissions-save-button .mud-icon-root {
        font-size: 0.95rem;
    }

    .mud-dialog-container .mud-dialog.entity-form-dialog.change-password-dialog {
        width: calc(100vw / var(--app-zoom) - 1.5rem);
        min-width: 0;
        max-width: calc(100vw / var(--app-zoom) - 1.5rem);
    }

    .role-header-toolbar .role-action-buttons .role-action-button {
        --role-action-button-width: var(--toolbar-control-height, 28px);
        flex: 0 0 var(--role-action-button-width);
        width: var(--role-action-button-width);
        min-width: var(--role-action-button-width);
        max-width: var(--role-action-button-width);
        min-height: var(--toolbar-control-height, 28px);
        height: var(--toolbar-control-height, 28px);
        padding: 0;
        justify-content: center;
        font-size: 0.7rem;
    }

    .role-header-toolbar .role-action-buttons .role-action-button .mud-icon-root {
        font-size: 0.85rem;
    }

    .permission-matrix th,
    .permission-matrix td {
        height: 29.75px;
        padding: 0 7px;
        font-size: 8.75px;
    }

    .mud-dialog .mud-dialog-title {
        font-size: 12.25px;
        padding: 0.6rem 0.75rem 0.25rem;
    }

    .mud-dialog-container .mud-dialog.entity-form-dialog .mud-dialog-title,
    .mud-dialog-container .mud-dialog:has(.entity-form-grid) .mud-dialog-title {
        font-size: 11.375px;
    }

    .mud-dialog-container .mud-dialog.entity-form-dialog .mud-dialog-title .user-form-title,
    .mud-dialog-container .mud-dialog:has(.entity-form-grid) .mud-dialog-title .user-form-title {
        font-size: 10.5px;
    }

    .mud-dialog .mud-dialog-content {
        padding: 0.5rem 0.75rem;
    }

    .mud-dialog .mud-dialog-actions {
        padding: 0.4rem 0.75rem 0.6rem;
    }

    .user-form-submit,
    .mud-button-root.user-form-submit {
        min-height: 29.75px;
        padding: 0 1rem;
        font-size: 10.5px;
    }

    .dialog-actions {
        gap: 0.45rem;
        margin-top: 0.65rem;
    }
}

@media (max-width: 430px) {
    :root {
        --app-compact-user-avatar-size: 17.5px;
    }

    /* The topbar and the sidebar's brand row are the two halves of ONE horizontal rule
       (see .topbar-inner §05) — so this tier steps the SHARED token and both consumers follow.
       The min-height literal that used to sit on .topbar-inner here left .sidebar-brand on the
       base token, and the seam broke the moment browser zoom pushed the viewport under the
       breakpoint [developer-reported 2026-09-02]. */
    :root {
        --app-topbar-height: 39.5px;
    }

    .topbar-inner {
        gap: 0.15rem;
        padding: 0 0.4rem;
    }

    .topbar,
    .login-layout-switcher {
        --topbar-control-size: 26.25px;
    }

    .topbar-icon.mud-icon-button {
        width: var(--topbar-control-size);
        height: var(--topbar-control-size);
        padding: 2.625px;
    }

    /* The shell's card gap follows the page inset at this width (see --app-shell-gap); the
       600px block's padding-top/bottom read it, so they step down too. */
    :root {
        --app-shell-gap: 0.4rem;
    }

    .app-content {
        --app-content-pad-inline: 0.4rem;
        padding-inline-start: var(--app-content-pad-inline);
        padding-inline-end: var(--app-content-pad-inline);
    }

    .page-card-header,
    .page-card-body.pa-4,
    .page-hero,
    .content-card.pa-5 {
        padding-inline-start: 0.55rem;
        padding-inline-end: 0.55rem;
    }

    .page-card-body.pa-4 {
        --page-card-inset: 0.55rem;
    }
}

@media (max-width: 599px) {
    /* Scoped, not :root — only a data grid's row-actions cell takes this third step. */
    .data-card .mud-table.data-table-grid .row-actions {
        --action-btn-size: 22.75px;
        --action-btn-icon-size: 0.9rem;
        gap: 0.2rem;
    }

    /* The floor stays stated here, as it always was: the base rule sets width/height only. */
    .data-card .mud-table.data-table-grid .row-actions .action-btn.mud-icon-button {
        min-width: var(--action-btn-size);
        min-height: var(--action-btn-size);
    }

    .data-card .mud-table-head .mud-table-cell.users-table-actions-header,
    .data-card .mud-table-body .mud-table-cell.users-table-actions-cell {
        --users-actions-btn: 22.75px;
        --users-actions-inner-gap: 0.2rem;
        --users-actions-gap: 0.4375rem;
    }

    /* The header's third and last step — and it is stated on the TOKEN, never as a literal on
       one button family. The actions row holds TWO of them: the labelled
       `.toolbar-header-button`s (squares once compact) and the `.action-btn` icon buttons
       (copy / filter-by-value / reset), and both size off --toolbar-control-height. Hard-coding
       28px here stepped only the first family down while the icon buttons kept the 600px tier's
       29.75px, so under 600px the right-hand group stood 1.75px taller than the left one.
       ⚠️ It must be declared on `.page-card-header-actions` as well as on the header: the row
       carries its own declaration in the 960px tier, which wins over inheritance from its
       parent. */
    .page-card-header,
    .page-card-header-actions {
        --toolbar-control-height: 28px;
    }

    .page-card-header.header-actions-compact .page-card-header-actions .toolbar-header-button,
    .page-card-header.header-actions-compact .page-card-header-actions .mud-button-root.toolbar-header-button {
        min-width: var(--toolbar-control-height);
        width: var(--toolbar-control-height);
        min-height: var(--toolbar-control-height);
        height: var(--toolbar-control-height);
        max-height: var(--toolbar-control-height);
    }

    .page-card-header {
        min-height: calc(1.2rem + var(--toolbar-control-height));
    }

    .app-pager {
        padding: 0.3rem 0.5rem;
        gap: 0.25rem;
    }

    .app-pager-text,
    .app-pager-num {
        height: 21px;
        min-width: 21px;
        font-size: 9.625px;
    }

    .app-pager-rows-select {
        height: 21px;
        font-size: 9.625px;
    }

    .user-status-pill,
    .role-pill {
        min-width: 3.25rem;
        padding: 0.1rem 0.45rem;
        font-size: 8.75px;
    }
}

/* =========================================================================
 * 19. FORM FOCUS STATES & DATE/TIME PICKERS
 * ========================================================================= */
/* App-wide form control hover and focus */
.mud-input-control .mud-input-outlined-border,
.native-form-control,
.searchable-native-select .searchable-native-input,
.app-pager-rows-select {
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.mud-input-control:hover:not(.mud-input-focused):not(:focus-within) .mud-input-outlined-border {
    border-color: var(--form-control-border);
    box-shadow: none;
}

.native-form-control:hover:not(:focus):not(:disabled),
.searchable-native-select:not(.is-disabled):not(:focus-within):hover .searchable-native-input {
    border-color: var(--form-control-border);
    box-shadow: none;
}

.native-form-control:focus:not(.is-invalid),
.searchable-native-select:focus-within:not(.is-invalid) .searchable-native-input,
.app-pager-rows-select:focus {
    border-color: var(--form-control-focus-border);
    box-shadow: var(--form-control-focus-shadow);
    outline: none;
}

.mud-input-control.mud-input-focused .mud-input-outlined-border,
.mud-input-control:focus-within .mud-input-outlined-border {
    border-color: var(--form-control-focus-border);
    box-shadow: var(--form-control-focus-shadow);
    border-width: 1px;
}

/* High-specificity focus override so every outlined control (selects, date/time
   pickers, toolbar filters) matches the role-form Color field on focus. The
   resting border rules for those hosts use higher-specificity `!important`
   selectors (up to 0,5,0), so this focus rule repeats classes to outrank them. */
.mud-input-control.mud-input-focused.mud-input-focused .mud-input.mud-input-outlined.mud-input-outlined .mud-input-outlined-border,
.mud-input-control:focus-within .mud-input.mud-input-outlined.mud-input-outlined.mud-input-outlined .mud-input-outlined-border {
    border-color: var(--form-control-focus-border);
    box-shadow: var(--form-control-focus-shadow);
    border-width: 1px;
}

/* Toolbar filter boxes (e.g. Work Times: Employee/Customer/Project selects and
   the From/To date pickers) have their own high-specificity resting border +
   `box-shadow: none`, so on focus they didn't pick up the shared focus color.
   These selectors outrank those resting rules and match the role-form Color
   field's focus (primary border + glow). */
.table-toolbar .toolbar-filter-field .searchable-native-select:focus-within:not(.is-invalid) .searchable-native-input,
.role-header-toolbar .toolbar-filter-field .searchable-native-select:focus-within:not(.is-invalid) .searchable-native-input {
    border-color: var(--form-control-focus-border);
    box-shadow: var(--form-control-focus-shadow);
}

.table-toolbar .toolbar-filter-field .mud-picker .mud-input-control.mud-input-focused .mud-input-outlined-border,
.table-toolbar .toolbar-filter-field .mud-picker .mud-input-control:focus-within .mud-input-outlined-border,
.role-header-toolbar .toolbar-filter-field .mud-picker .mud-input-control.mud-input-focused .mud-input-outlined-border,
.role-header-toolbar .toolbar-filter-field .mud-picker .mud-input-control:focus-within .mud-input-outlined-border {
    border-color: var(--form-control-focus-border);
    box-shadow: var(--form-control-focus-shadow);
    border-width: 1px;
}

.mud-picker-toolbar.mud-theme-primary,
.mud-picker-datepicker-toolbar.mud-theme-primary {
    background-color: var(--app-primary);
    color: var(--app-color-page-button-hover-text);
}

.mud-picker-toolbar.mud-theme-primary .mud-button-root,
.mud-picker-toolbar.mud-theme-primary .mud-icon-button,
.mud-picker-datepicker-toolbar.mud-theme-primary .mud-button-root,
.mud-picker-datepicker-toolbar.mud-theme-primary .mud-icon-button {
    color: var(--app-color-page-button-hover-text);
}

/* The month picker's popup — the one a MonthOnly AppDatePicker opens (no toolbar, see the
   component). Twelve month BUTTONS on a 3 × 4 grid under the year row, the shape of a printed
   year planner [developer-directed 2026-09-06, from an Excel mock, then restyled the same day]:
   each month is a small outlined button with the estate's button radius, the grid breathes off
   the popup's edge, the picked month is a FILLED button wearing the picked-tab pill's own fill
   and ink, and the labels read the app's text tokens — the year and the twelve months at the
   same size, the picked one included (Mud printed it as an h5, a size and a half larger).
   ⚠️ SCOPE: the popover is portaled to <body>, so nothing of ours is on it. What singles the
   month-only popup out is the toolbar it does NOT have — `:not(:has(.mud-picker-toolbar))` —
   so a day picker's own month view (toolbar on) keeps Mud's look and Mud's size, and the two
   never swap width when that picker flips between its day and month views.
   SIZE is one ladder of four tokens: the popup is the old Mud size minus ~20% on desktop, ~30%
   under 960 and ~40% under 600 [developer-directed 2026-09-06] — 3 cells + 2 gaps + 2 pads =
   218 / 188 / 160px against Mud's 271. ⚠️ The two @media steps are CO-LOCATED with the base
   rule, never in the big early tiers: same specificity, and the later base rule would win. */
.mud-picker-popover:not(:has(.mud-picker-toolbar)) {
    --month-picker-cell-w: 62px;
    --month-picker-cell-h: 36px;
    --month-picker-gap: 6px;
    --month-picker-pad: 10px;
}

@media (max-width: 960px) {
    .mud-picker-popover:not(:has(.mud-picker-toolbar)) {
        --month-picker-cell-w: 54px;
        --month-picker-cell-h: 32px;
        --month-picker-gap: 5px;
        --month-picker-pad: 8px;
    }
}

@media (max-width: 600px) {
    .mud-picker-popover:not(:has(.mud-picker-toolbar)) {
        --month-picker-cell-w: 46px;
        --month-picker-cell-h: 28px;
        --month-picker-gap: 4px;
        --month-picker-pad: 7px;
    }
}

/* Mud's popup floors (§00 restates them at the app density) are lifted here — the grid is the
   size now, and the floors would hold a 160px grid open at 271. */
.mud-picker-popover:not(:has(.mud-picker-toolbar)) .mud-picker-content {
    min-width: 0;
    min-height: 0;
}

.mud-picker-popover:not(:has(.mud-picker-toolbar)) .mud-picker-calendar-transition {
    margin-top: 0;
    min-height: 0;
}

/* …and the outer calendar box states a fixed width of its own (Mud 310px, §00 271.25px), which
   would keep the popup at the old width with the grid floating in it — measured 271 with an
   182-wide grid before this rule. */
.mud-picker-popover:not(:has(.mud-picker-toolbar)) .mud-picker-calendar-container {
    width: auto;
}

/* THE YEAR ROW IS OURS. Mud's own header (arrows + a year label that swaps the popup for a
   scrolling year list) is hidden, and AppDatePicker renders its replacement — arrows + a native
   year DROPDOWN — through PickerActions, so the grid never leaves the screen
   [developer-directed 2026-09-06]. PickerActions renders at the FOOT of the popup, under the
   content, and it holds two rows that belong at opposite ends (the year row above the grid, the
   חודש קודם / החודש / חודש הבא row below it). So: the block that holds content + actions becomes
   a flex column, `.mud-picker-actions` gives up its own box (display: contents), and the three
   pieces take their place by `order`. The parent is addressed as "whatever holds the actions"
   rather than by Mud's class name, so a rename there cannot silently undo this. */
.mud-picker-popover:not(:has(.mud-picker-toolbar)) .mud-picker-calendar-header-switch {
    display: none;
}

.mud-picker-popover:not(:has(.mud-picker-toolbar)) :has(> .mud-picker-actions) {
    display: flex;
    flex-direction: column;
}

.mud-picker-popover:not(:has(.mud-picker-toolbar)) .mud-picker-actions {
    display: contents;
}

.mud-picker-popover:not(:has(.mud-picker-toolbar)) .mud-picker-content {
    order: 0;
}

.month-picker-year-row {
    order: -1;
    display: flex;
    align-items: center;
    gap: var(--month-picker-gap);
    padding: var(--month-picker-pad) var(--month-picker-pad) 0;
}

.month-picker-quick-row {
    order: 1;
    display: flex;
    gap: var(--month-picker-gap);
    margin: 0 var(--month-picker-pad) var(--month-picker-pad);
    padding-top: var(--month-picker-pad);
    /* The separator between the grid and the quick picks — the estate's ordinary rule. */
    border-top: 1px solid var(--app-border);
}

/* The two arrows: bare icon buttons at the cell's height, the estate's icon-button radius. */
.month-picker-year-step {
    flex: 0 0 auto;
    width: var(--month-picker-cell-h);
    height: var(--month-picker-cell-h);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    border-radius: var(--app-radius-icon-button);
    background: transparent;
    color: var(--app-icon-muted, var(--mud-palette-text-secondary));
    cursor: pointer;
    transition: background-color 160ms ease, color 160ms ease;
}

.month-picker-year-step:hover {
    background: var(--app-primary-soft);
    color: var(--app-primary);
}

.month-picker-year-step .mud-icon-root {
    font-size: 1.1rem;
    width: 1.1rem;
    height: 1.1rem;
}

/* ⚠️ A date picker's popover element is SQUARE while the paper inside it is rounded — Mud gives
   `.mud-popover` an elevation shadow of its own with no radius, so that shadow's square outline
   showed past the paper's four corners as notches [developer-reported 2026-09-06, on the month
   picker; every date picker's popup had it]. The popover takes the paper's own radius. */
.mud-picker-popover {
    border-radius: var(--mud-default-borderradius);
}

/* The year dropdown IS the pager's שורות לעמוד menu (`.app-pager-rows-*`, one recipe — a native
   <select>'s open list is OS-drawn and took neither the app's border nor its alignment). Its
   activator keeps the pager button's own height and stretches to the row [developer-directed
   2026-09-06: shorter than the cells]; the list caps itself and scrolls (101 years, and the
   popover would otherwise run past a short viewport). The POPOVER class is this menu's own,
   because the pager's pins `min-width`/`width` with !important and the year list must be
   exactly as wide as its button (Mud's RelativeWidth=Adaptive writes that as an inline
   min-width, which a !important rule would override). */
.month-picker-year-menu {
    flex: 1 1 auto;
    min-width: 0;
}

.month-picker-year-menu .month-picker-year-button {
    width: 100%;
    font-size: var(--app-text-form-box);
    text-align: center;
}

.month-picker-year-popover {
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius);
    box-sizing: border-box;
}

.month-picker-year-list {
    max-height: min(40vh, 15rem);
    overflow-y: auto;
}

/* The grid itself. Mud lays the months out as a wrapping flex row (`flex: 1 0 33.33%`); a real
   grid gives the three columns and the gaps, and a grid item ignores its flex-basis. */
.mud-picker-popover:not(:has(.mud-picker-toolbar)) .mud-picker-month-container {
    display: grid;
    grid-template-columns: repeat(3, var(--month-picker-cell-w));
    gap: var(--month-picker-gap);
    padding: var(--month-picker-pad);
    width: auto;
}

/* A month, and a quick pick, is the Employee form's TAB PILL [developer-directed 2026-09-06,
   from that strip's screenshot]: brand-edged at 29%, muted ink at rest, the brand's soft wash
   under the pointer, and — on the picked month alone — the solid brand fill with the status
   toggles' inner shade. Same tokens as `.entity-form-tabs .mud-tab`, not a copy of its numbers;
   and no bold anywhere [developer-directed 2026-09-06]: the fill says "picked". The corner is
   the app's BUTTON radius, not the strip's full pill [developer-directed 2026-09-06] — twelve
   full pills read as a different family from every other button on the page. The resting
   transparent inset is the strip's own trick — it keeps the press landing in one moment. */
.mud-picker-popover:not(:has(.mud-picker-toolbar)) .mud-picker-month-container .mud-picker-month,
.month-picker-quick {
    border: 1px solid var(--app-primary-edge);
    border-radius: var(--app-radius-button);
    background: transparent;
    color: var(--mud-palette-text-secondary);
    box-shadow: var(--app-shadow-inset-deep-geometry) transparent;
    transition: background-color 160ms ease, color 160ms ease, box-shadow 160ms ease;
}

.mud-picker-popover:not(:has(.mud-picker-toolbar)) .mud-picker-month-container .mud-picker-month {
    height: var(--month-picker-cell-h);
    padding: 0 2px;
}

.month-picker-quick {
    flex: 1 1 0;
    min-width: 0;
    height: var(--month-picker-cell-h);
    padding: 0 4px;
    font-family: inherit;
    font-size: var(--app-text-page-button);
    font-weight: 400;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: pointer;
}

/* The step buttons carry a long and a short label; the tier picks. ⚠️ Co-located with the size
   ladder above for the same reason its steps are. */
.month-picker-quick-short {
    display: none;
}

@media (max-width: 960px) {
    .month-picker-quick-long {
        display: none;
    }

    .month-picker-quick-short {
        display: inline;
    }
}

.mud-picker-popover:not(:has(.mud-picker-toolbar)) .mud-picker-month .mud-typography {
    font-size: var(--app-text-page-button);
    font-weight: 400;
    line-height: 1.2;
    white-space: nowrap;
}

/* Stated at (0,4,0)+, above Mud's own hover (0,3,0) — which paints its grey wash otherwise. */
.mud-picker-popover:not(:has(.mud-picker-toolbar)) .mud-picker-month-container .mud-picker-month:hover:not(:has(.mud-picker-month-selected)),
.month-picker-quick:hover {
    background-color: var(--app-primary-soft);
    color: var(--app-primary);
}

/* Mud marks the picked month on the LABEL (`.mud-picker-month-selected` + `.mud-primary-text`
   on the <p>), so the cell is reached through :has(). Border WIDTH zeroed, not its colour — the
   inset is clipped to the padding box and a border would ring the well in undarkened fill (the
   tab strip's own finding). box-sizing is border-box, so the pill does not move.
   ⚠️ The INK is stated INSIDE the `mudblazor` layer, and that is the only place it can win:
   Mud's colour utility (`.mud-primary-text { color … !important }`) is an IMPORTANT declaration
   in a LAYER, and for important declarations the cascade inverts — a layered !important beats
   an unlayered one, whatever its specificity. So an ordinary rule here lost, and an unlayered
   `!important` lost too (both were tried: the label stayed PRIMARY ink on the PRIMARY fill — an
   empty blue cell in light — caught by the clip, since the measurement read "primary" and looked
   right). Appending to the same layer puts the two important declarations side by side, where
   specificity decides: (0,6,0) against (0,1,0). The fill needs no such thing — nothing layered
   sets it with !important. */
.mud-picker-popover:not(:has(.mud-picker-toolbar)) .mud-picker-month-container .mud-picker-month:has(.mud-picker-month-selected) {
    border-width: 0;
    background-color: var(--app-primary-solid);
    box-shadow: var(--app-shadow-inset-deep);
}

@layer mudblazor {
    .mud-picker-popover:not(:has(.mud-picker-toolbar)) .mud-picker-month-container .mud-picker-month:has(.mud-picker-month-selected) .mud-typography {
        color: var(--app-on-primary) !important;
        font-weight: 400;
    }
}


.mud-button-root,
.mud-button-label {
    text-transform: none;
}

/* =========================================================================
 * 20. DIALOGS
 * ========================================================================= */
/* Zoom root 2 of 4 (--app-zoom, section 01). Dialogs live outside .app-shell - the dialog
   provider sits at the app root (App.razor) - so they need their own declaration or every form
   would render full size against a scaled-down page. Safe to zoom, unlike .mud-popover-provider,
   because Mud centres a dialog with flexbox and never writes a pixel position for it.
   Mud's own width/height:100% here resolves against the viewport; restating it in zoom-divided
   viewport units keeps the centring box exactly one screen whatever the zoom. */
.mud-dialog-container {
    zoom: var(--app-zoom);
    width: calc(100vw / var(--app-zoom));
    height: calc(100vh / var(--app-zoom));
    padding: 1.5rem;
}

/* 12-col form dialogs need a definite width (the MudItem spans are percentages of it), so the
   dialog is a fixed width per tier — standard 600px, overridable per dialog via the CSS var
   (wide calculators e.g. 760px, 1-2-field forms 420px via .entity-form-dialog--narrow). */
.mud-dialog-container .mud-dialog.entity-form-dialog,
.mud-dialog-container .mud-dialog:has(.entity-form-grid) {
    width: min(calc(100vw / var(--app-zoom) - 3rem), var(--entity-form-dialog-width, 525px));
    max-width: min(calc(100vw / var(--app-zoom) - 3rem), var(--entity-form-dialog-width, 525px));
}

.entity-form-dialog--narrow {
    --entity-form-dialog-width: 367.5px;
}

/* Wide tier — a form running two field groups side by side, so each half still holds a full row of
   boxes at the standard span widths; also the floor for a dialog holding a TABLE rather than a
   form grid (such a dialog usually widens past this on its own class). */
.entity-form-dialog--wide {
    --entity-form-dialog-width: 787.5px;
}

.mud-dialog-container .mud-dialog.entity-form-dialog.change-password-dialog {
    width: calc(var(--toolbar-search-width) + 87.5px);
    min-width: calc(var(--toolbar-search-width) + 87.5px);
    max-width: min(calc(100vw / var(--app-zoom) - 3rem), calc(var(--toolbar-search-width) + 87.5px));
}

.mud-dialog.change-password-dialog .mud-dialog-content {
    width: 100%;
}

.change-password-form {
    padding-inline-start: 43.75px;
    padding-inline-end: 43.75px;
    box-sizing: border-box;
    width: 100%;
}

.mud-dialog-container .mud-dialog {
    margin: 0;
    border-radius: var(--app-radius);
    border: 1px solid var(--app-border);
    box-shadow: var(--app-shadow-dialog);
    max-height: calc(100vh / var(--app-zoom) - 3rem);
    overflow: hidden;
}

.mud-dialog .mud-dialog-content {
    padding-top: 0.25rem;
    overflow-y: auto;
}

.mud-dialog-container .mud-dialog.entity-form-dialog .mud-dialog-title .user-form-title.mud-typography-h6,
.mud-dialog-container .mud-dialog:has(.entity-form-grid) .mud-dialog-title .user-form-title.mud-typography-h6 {
    font-size: 14.875px;
}

@media (max-width: 960px) {
    .mud-dialog-container .mud-dialog.entity-form-dialog .mud-dialog-title .user-form-title.mud-typography-h6,
    .mud-dialog-container .mud-dialog:has(.entity-form-grid) .mud-dialog-title .user-form-title.mud-typography-h6 {
        font-size: 10.5px;
    }
}

/* Tablet (601-960px): form title 15px — between desktop 17px and phone 12px. */
@media (min-width: 601px) and (max-width: 960px) {
    .mud-dialog-container .mud-dialog.entity-form-dialog .mud-dialog-title .user-form-title.mud-typography-h6,
    .mud-dialog-container .mud-dialog:has(.entity-form-grid) .mud-dialog-title .user-form-title.mud-typography-h6,
    .user-form-title {
        font-size: 13.125px;
    }

    /* Shrink the dialog Add/Update and Cancel buttons by 2px (14px -> 12px). */
    .mud-button-root.soft-primary-button,
    .mud-button-root.soft-update-button,
    .mud-button-root.soft-save-button,
    .mud-button-root.dialog-cancel {
        font-size: 10.5px;
    }
}


/* Mud MaxWidth.Small is 600px; validation dialog is 170px narrower than Confirm layout */
.mud-dialog-container .mud-dialog.validation-message-dialog {
    max-width: min(calc(100vw / var(--app-zoom) - 3rem), 376.25px);
}

/* Delete confirmation — 100px narrower than default Small (600px) */
.mud-dialog-container .mud-dialog.confirm-delete-dialog {
    max-width: min(calc(100vw / var(--app-zoom) - 3rem), 437.5px);
}

.mud-dialog-container .mud-dialog.confirm-delete-dialog .mud-dialog-title .confirm-delete-title,
.mud-dialog-container .mud-dialog.confirm-delete-dialog .mud-dialog-title .mud-typography-h6 {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    color: var(--app-danger);
    font-size: 12.25px;
    font-weight: 750;
}

.validation-message-list {
    margin: 0;
    padding-inline-start: 1.25rem;
}

.validation-message-list li {
    margin-bottom: 0.35rem;
}

.mud-dialog .mud-dialog-actions {
    justify-content: flex-start;
    padding-inline-start: 1.5rem;
    padding-inline-end: 1.5rem;
    padding-bottom: calc(7px + 8.75px);
}

/* Small breathing room between the last form row and the Add/Update bar (entity forms only —
   confirmation popups keep the tighter default): 16px total, the app-standard gutter. */
.mud-dialog-container .mud-dialog.entity-form-dialog .mud-dialog-actions,
.mud-dialog-container .mud-dialog:has(.entity-form-grid) .mud-dialog-actions {
    padding-top: calc(7px + 7px);
}

/* Form info button — sits at the leading edge of the dialog header, next to the title.
   Click opens a popover listing the fields whose values must be unique. */
.entity-form-title-bar {
    display: flex;
    align-items: center;
    gap: 0.15rem;
}

.entity-form-title-bar .form-info-button {
    color: var(--app-muted);
    padding: 3.5px;
}

.entity-form-title-bar .form-info-button:hover,
.entity-form-title-bar .form-info-button:focus-visible {
    color: var(--app-primary);
}

/* A field's own rules (i), right after its label (FieldInfoButton via FormLabel InfoLines). Negative
   block margins keep the label row exactly as tall as a plain label, so the box under it lines up
   with its neighbours on the row. */
.form-label-with-info {
    display: flex;
    align-items: center;
    gap: 0.2rem;
}

/* The same gap under the label as a plain label (.user-form-field > .user-form-label), so the box
   sits on the same line as its neighbours; and the label dims with a disabled box like a plain one. */
.user-form-field > .form-label-with-info {
    margin-bottom: var(--form-label-gap);
}

:is(.user-form-field):has(.native-form-control:disabled, .searchable-native-select.is-disabled, .mud-input.mud-input-disabled, .mud-input.mud-disabled) > .form-label-with-info .user-form-label,
:is(.user-form-field):has(.native-form-control:disabled, .searchable-native-select.is-disabled, .mud-input.mud-input-disabled, .mud-input.mud-disabled) > .form-label-with-info .form-required {
    color: var(--app-input-disabled-text);
}

.form-label-with-info .field-info-button {
    color: var(--app-muted);
    padding: 1px;
    margin-block: -4px;
}

.form-label-with-info .field-info-button .mud-icon-root {
    font-size: 15px;
}

.form-label-with-info .field-info-button:hover,
.form-label-with-info .field-info-button:focus-visible {
    color: var(--app-primary);
}

.form-info-popover .form-info-panel {
    padding: 13.125px;
    max-width: 17rem;
    text-align: start;
}

/* Uniform look across the title and field names — letter-spacing and font-family set on each
   text element directly (not via inheritance) so MudBlazor's popover-list defaults can't make the
   field names differ from the title. Font-size is set per element below. */
.form-info-panel__title,
.form-info-panel__list li {
    line-height: 1.5;
    letter-spacing: normal;
    font-family: inherit;
    font-weight: 400;
    color: var(--app-text);
    opacity: 1;
}

/* Field names: 11px desktop, 10px tablet, 10px phone. */
.form-info-panel__list li {
    font-size: 9.625px;
}

@media (min-width: 601px) and (max-width: 960px) {
    .form-info-panel__list li {
        font-size: 8.75px;
    }
}

@media (max-width: 600px) {
    .form-info-panel__list li {
        font-size: 8.75px;
    }
}

/* Title: 13px desktop, 12px tablet, 11px phone — plus the app accent
   (--app-primary carries its own light/dark values). */
.form-info-panel__title {
    margin-bottom: 0.25rem;
    color: var(--app-primary);
    font-size: 11.375px;
    /* Bold so the light accent colour reads as the dominant title — without weight it optically
       recedes next to the darker, higher-contrast field names despite being 1px larger. */
    font-weight: 600;
}

@media (min-width: 601px) and (max-width: 960px) {
    .form-info-panel__title {
        font-size: 10.5px;
    }
}

@media (max-width: 600px) {
    .form-info-panel__title {
        font-size: 9.625px;
    }
}

.form-info-panel__list {
    margin: 0;
    padding-inline-start: 1.15rem;
}

.form-info-panel__list li {
    margin-bottom: 0.1rem;
}


/* ── In-dialog table (`.form-child-table`) ────────────────────────────────────────────────────
   The estate's ONE table skin for a grid that lives inside a dialog — a child-rows section under
   a form, or a read-only drill-down that IS the dialog. Ported from ElitaPro,
   where the same class dresses both, so a dialog grid reads the same in every app built from this
   template. Wrap a `MudSimpleTable` in it; never hand-style a dialog table per screen.
   Auto layout on purpose, NOT table-layout:fixed — fixed splits the width evenly and wraps a date
   column mid-number; auto squeezes only the column that actually has long text. */
.form-child-table table {
    width: 100%;
    max-width: 100%;
    border-collapse: collapse;
    border-spacing: 0;
}

/* Held as variables because the padding is measured FROM elsewhere (an inline-edit cell offsets
   its text by half the control height). One source, so the two cannot drift. MudBlazor's dense
   16px side padding is too generous at a dialog's width. */
.form-child-table {
    --form-child-cell-pad-block: 5.25px;
    --form-child-cell-pad-inline: 7px;
}

.form-child-table.mud-table-dense * tr td,
.form-child-table.mud-table-dense * tr th {
    padding: var(--form-child-cell-pad-block) var(--form-child-cell-pad-inline);
}

/* break-word, NOT anywhere: `anywhere` lowers a cell's min-content width, so auto layout squeezes
   the date/amount columns and wraps "15/08/2026" mid-number to give a long free-text column more
   room. `break-word` leaves min-content at the longest word, so short columns hold their width and
   only the free-text column wraps — at its spaces. */
.form-child-table td {
    overflow-wrap: break-word;
}

/* A long child list must scroll INSIDE the dialog rather than stretch it past the viewport.
   MudSimpleTable already renders .mud-table-container as the scroll element — it just has no
   height cap, so it never scrolls vertically. overscroll-behavior stops a flick past the last row
   from carrying on into the page behind the dialog. The vh term keeps the box proportional on a
   short screen; the rem term stops it growing into a wall of rows on a tall one. A dialog whose
   grid IS its content raises this cap on its own class. */
.form-child-table .mud-table-container {
    max-height: min(45vh, 26rem);
    overflow: auto;
    overscroll-behavior: contain;
    border: 1px solid var(--app-border);
}

/* The header ROW stays put while the body scrolls under it — column names are the only thing
   telling same-shaped columns apart, so losing them mid-scroll makes the grid unreadable. The whole
   <thead> is sticky, not each cell [developer-reported 2026-11-04, the list tables' fix (Pulsar
   82f2ccf) carried over]: the cells used to be sticky one by one with an OUTLINE as their rules,
   and an outline rings all four sides, so every divider was drawn by both neighbours and read as a
   2px line beside the body's 1px one (measured: 3 of 6). Now the dividers are the table's own grid
   lines — the head's own while it sits still, the body's seen through the 1px strip once it scrolls,
   on the same pixel either way — and the cells draw only the bottom rule, as an inset. The top
   edge is the scroll container's frame. A child grid has no cell selection, so nothing but the
   grid lines lies under that strip. z-index clears the body cells, which are in flow. */
.form-child-table thead {
    position: sticky;
    top: 0;
    z-index: 2;
}

.form-child-table thead th {
    position: relative;
    color: var(--app-table-column-name);
    background: var(--app-table-head);
    font-weight: 600;
    text-align: start;
    white-space: nowrap;
    border-bottom: none;
    box-shadow: inset 0 -1px 0 var(--app-border);
}

/* Full grid lines — an outline plus a border on EVERY cell, the same --app-border token the list
   tables rule their cells with, so the two kinds of table cannot drift apart. MudSimpleTable's
   Bordered draws row separators only; the vertical rules come from here.
   The outline sits on the scroll CONTAINER rather than on the table: on the table it would be part
   of the scrolled content and slide out of view at both ends, leaving the box unframed exactly
   when it is scrolled. On the container it stays as the frame the content moves inside. */
.form-child-table th,
.form-child-table td {
    border: none;
    border-bottom: 1px solid var(--app-border);
    border-inline-end: 1px solid var(--app-border);
}

/* The trailing cell and the final row would otherwise double up against the container's outline. */
.form-child-table tr > *:last-child {
    border-inline-end: none;
}

.form-child-table tbody tr:last-child > * {
    border-bottom: none;
}

.form-child-table tbody tr:hover {
    background: var(--app-table-row-hover);
}

/* :not(:hover) lets the hover rule win. The opt-out class goes on the TABLE — a dialog is
   portaled out of `.app-content`, so the app-wide stripe preference's own flag cannot reach it. */
.form-child-table:not(.zebra-off) tbody tr:nth-child(even):not(:hover):not(.mud-selected-item) {
    background: var(--app-table-row-stripe);
}


/* ── FormChildRows (ported from ElitaPro 2026-10-02, together with the component) ──────────── */
.form-child-table th.entity-table-actions-header,
.form-child-table td.entity-table-actions-cell {
    width: 77px;
}

/* Proportional columns (FormChildRows ColumnWidths): the colgroup only binds under fixed layout —
   auto layout sizes from content and ignores it. The actions column takes its share from the
   colgroup too, so the 88px above has to give way.
   min-width is the floor the proportions are applied to: narrower than this the 3/12 date column
   drops under the width of "01.06.2026" plus its two icons and the value clips, so the table stops
   shrinking there and the container scrolls sideways instead. Only fixed-layout (input-bearing)
   grids get it — an auto-layout read-only table has no such floor and is meant to wrap, not
   scroll. */
.form-child-table--fixed table {
    table-layout: fixed;
    min-width: 525px;
}

.form-child-table--fixed th.entity-table-actions-header,
.form-child-table--fixed td.entity-table-actions-cell {
    width: auto;
}

/* The column width is declared here rather than inline on the <col>, which only carries the
   values as custom properties: an inline width would outrank every stylesheet rule and the narrow
   override below could never take effect. */
.form-child-table--fixed col {
    width: var(--fcw);
}

/* Narrow screens re-proportion instead of widening. Down here the table is at its 600px floor,
   where a 3/12 column measures 150px — under the ~157px a full date plus its calendar and clear
   icons need, so the value clips. The caller's NarrowColumnWidths move that space off a column
   that can wrap (a free-text description) and onto the one that cannot; where no narrow set is
   given the standard proportions carry through unchanged. Widening the table instead would just
   trade a clipped date for more sideways scrolling. */
@media (max-width: 720px) {
    .form-child-table--fixed col {
        width: var(--fcw-sm, var(--fcw));
    }
}

/* The row being edited. MudBlazor's .mud-selected-item only styles list items, so a <tr>
   carrying it gets no highlight of its own. */
.form-child-table tbody tr.mud-selected-item {
    background: var(--mud-palette-action-default-hover);
}



/* The "nothing here" line when the head is kept (ShowHeaderWhenEmpty) — the list pages'
   NoRecordsContent said inside a simple table. Centred over the full width and quiet, so it reads
   as an answer about the rows rather than as a row. */
.form-child-table tbody tr.form-child-empty-row > td {
    text-align: center;
    padding: 1rem 0.5rem;
    color: var(--mud-palette-text-secondary);
}

/* No hover and no stripe: it is not a record. */
.form-child-table tbody tr.form-child-empty-row:hover {
    background: none;
}

/* ── Inline-editable child grid (FormChildRows InlineEdit) ──────────────────────────────────
   The row is the editor, so every cell holds a real input. Two failure modes to avoid, and the
   resting style is the compromise between them: full form-box chrome on every cell turns the grid
   into a cage of nested boxes, while fully transparent inputs make an EMPTY row (the state right
   after "add row") render as blank space with nothing to aim at. So at rest each cell carries the
   table's own hairline — quiet enough to read as a grid, present enough to show where to type —
   and hover/focus promotes it to the normal form-box border + fill.

   The date cell is included explicitly: MudDatePicker's host draws its own border, so leaving it
   out would give the date column a box while its neighbours had none. */
/* Cells anchor to the TOP, exactly like the form grid's rows (`.entity-form-grid` §12-column
   form grid): a validation message under one box has to extend that cell DOWNWARD, growing the
   whole row as a unit. Centered cells would instead re-center the box+message stack and lift the
   flagged box out of line with its neighbours in the same row — the "no broken boxes" rule.
   Every control in a row is pinned to one height (below), so with no message on screen top and
   middle render identically.
   ⚠️ The padding below does NOT apply: `.form-child-table.mud-table-dense * tr td` (two classes
   + two elements) outranks this selector (one class + two), so a grid cell measures the shared
   `--form-child-cell-pad-block/-inline` (6px 8px), not 2px 4px — measured in the running app
   2026-08-17. Left as-is rather than promoted: the app has shipped at 6px throughout and
   tightening it would re-space every inline grid. Anything that needs the real padding (as
   `.line-text-cell` does) must read those variables, never this 2px. */
.editable-lines-table tbody td {
    padding: 2px 3.5px;
    vertical-align: top;
}

.editable-lines-table tbody td .user-form-field,
.editable-lines-table tbody td .form-control-sized {
    width: 100%;
}

/* :not(.is-invalid) on all three states, and it is not optional: these selectors carry two
   classes plus two elements, so they outrank the global `.native-form-control.is-invalid` and a
   rejected cell would quietly keep the resting hairline — the one place in the app where a red
   box failed to look red. Excluded rather than re-stated so the invalid rule stays the single
   source of the red border. */
.editable-lines-table tbody td .native-form-control:not(.is-invalid),
.editable-lines-table tbody td .native-datetime-host:not(.is-invalid) {
    border-color: var(--mud-palette-lines-default);
    background-color: transparent;
}

/* …except for a SearchableNativeSelect, where the exclusion above cannot do the job: that
   component puts `is-invalid` on its WRAPPER and the red border on the inner input, which
   therefore never carries the class the `:not()` tests — so the resting rule matched a rejected
   select and won on specificity, and the box stayed hairline-grey under its own "שדה חובה".
   (Visible in any inline grid with a select cell; found on the שכר grid 2026-08-14.) The value
   is still the invalid rule's — only the specificity is restated here. */
.editable-lines-table tbody td .searchable-native-select.is-invalid .searchable-native-input,
body.app-dark .editable-lines-table tbody td .searchable-native-select.is-invalid .searchable-native-input {
    border-color: var(--form-control-invalid-border);
    box-shadow: var(--form-control-invalid-shadow);
}

/* Every control in a row is pinned to the one shared control height so the boxes line up as a
   single band. This has to be restated here: the app's height rules for the date picker are all
   scoped to `.user-form-field`, and these cells deliberately don't use that wrapper (a grid cell
   has no stacked label, and its width cap would fight the colgroup). Without this the picker keeps
   MudBlazor's taller natural input height and the date column sits proud of its neighbours. */
.editable-lines-table tbody td .native-form-control:not(.native-form-textarea) {
    height: var(--entity-form-control-height);
    min-height: var(--entity-form-control-height);
    max-height: var(--entity-form-control-height);
    padding-top: 0;
    padding-bottom: 0;
    line-height: calc(var(--entity-form-control-height) - 2px);
    box-sizing: border-box;
}

.editable-lines-table tbody td .native-datetime-host,
.editable-lines-table tbody td .native-datetime-host .mud-picker,
.editable-lines-table tbody td .native-datetime-host .mud-input-control,
.editable-lines-table tbody td .native-datetime-host .mud-input-control-input-container,
.editable-lines-table tbody td .native-datetime-host .mud-input.mud-input-outlined,
.editable-lines-table tbody td .native-datetime-host .mud-input-root,
.editable-lines-table tbody td .native-datetime-host .mud-input-slot {
    min-height: var(--entity-form-control-height);
    height: var(--entity-form-control-height);
    max-height: var(--entity-form-control-height);
    margin: 0;
    padding-top: 0;
    padding-bottom: 0;
    box-sizing: border-box;
}

.editable-lines-table tbody td .native-form-control:hover:not(.is-invalid),
.editable-lines-table tbody td:hover .native-datetime-host:not(.is-invalid) {
    border-color: var(--app-color-form-box-border);
    background-color: var(--app-color-form-box-bg);
}

.editable-lines-table tbody td .native-form-control:focus:not(.is-invalid),
.editable-lines-table tbody td:focus-within .native-datetime-host:not(.is-invalid) {
    border-color: var(--mud-palette-primary);
    background-color: var(--app-color-form-box-bg);
}

/* A disabled cell keeps the app-wide sunken fill. The resting and hover rules above carry two
   classes plus two elements, so they outrank the global `.native-form-control:disabled` and a
   disabled cell stayed transparent — it read as an open box (the payment row's bank / branch /
   account / check boxes under a cash payment). Restated at the same weight, after them, so the
   disabled look wins in the grid too; the date box gets its fill from the shared
   `.native-datetime-host .mud-input.mud-disabled` rule, and only its host's hover is held here. */
.editable-lines-table tbody td .native-form-control:disabled,
.editable-lines-table tbody td .native-form-control:disabled:hover:not(.is-invalid) {
    background: var(--app-input-disabled-bg);
    color: var(--app-input-disabled-text);
}

.editable-lines-table tbody td:hover .native-datetime-host:has(.mud-input.mud-disabled):not(.is-invalid) {
    background: transparent;
}

/* A rejected cell says so where it stands — the box goes red and its message prints under it,
   the same pair (and the same leading-edge alignment) a required field on the parent form shows.
   Only the gap is grid-specific: 2px instead of the form's 0.35rem, so the row grows by as
   little as the note needs. */
.editable-lines-table tbody td .form-field-error {
    margin-top: 2px;
}

/* A read-only cell in an otherwise input-bearing row (the שכר grid's "נוכחי" marker). The row's
   controls are pinned to the shared control height and the cells anchor to the top, so bare text
   would sit a few px proud of the boxes beside it — this lines it up with them without giving it
   a box of its own. Single-line only: the full control height as a LINE height is what centres
   the one word, and it would double the row the moment the cell wrapped — a cell that can wrap
   takes `.line-text-cell` below instead. */
.editable-lines-table tbody td.line-note-cell {
    line-height: var(--entity-form-control-height);
    white-space: nowrap;
}

/* A read-only TEXT cell in an input-bearing row — the roster's agency / employee / identity /
   contract / rate columns (/work-hours is the one grid whose row is mostly read-out, and its
   text sat ~7px above the text inside the boxes beside it). Same job as `.line-note-cell` above,
   done as an OFFSET rather than as a line height: the control starts at the cell's content top
   and centres its own text 16.5px down, so pushing this text down by half the difference between
   the control and this cell's line box lands the two centre lines on each other — measured at
   0.02px apart in the running app. Only the FIRST line is placed; the rest of the cell keeps its
   ordinary leading, so a cell that wraps (a long agency name in the 100px column) grows by one
   plain line rather than by a whole control height, and its lines stay evenly spaced.
   Top-anchored like every other cell, deliberately: `vertical-align: middle` here would
   re-centre the text in a row grown by a neighbour's validation message and drift out of line
   with the very boxes it is aligning to. */
.editable-lines-table tbody td.line-text-cell {
    padding-top: calc(var(--form-child-cell-pad-block) + (var(--entity-form-control-height) - 1lh) / 2);
}

/* A BOOLEAN cell in an input-bearing row — the roster's האם עובד בקבלנות on a hand-added line.
   The estate says yes/no with the status toggle plus the word (YesNoFormField), and this is that
   pair with the form field's label and MudItem stripped: the column header is the label here.
   The toggle keeps the form's own proportion — track = control height − 6px — but measures from
   the GRID's control height, so it stands the same height as the hours box in the same row.
   The word is what the read-only rows in this very column print, so the two read as one column. */
.editable-lines-table tbody td.line-toggle-cell .line-toggle {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    min-height: var(--entity-form-control-height);
}

.editable-lines-table tbody td.line-toggle-cell .status-light-toggle {
    --toggle-track-height: calc(var(--entity-form-control-height) - 5.25px);
}

/* Add-row button above the grid, leading-edge aligned with the table. */
.form-child-add-row {
    display: flex;
    justify-content: flex-start;
    margin-bottom: 0.5rem;
}

/* A section caption titles the block DIRECTLY under it, so it hugs it: the form grid's gutter
   would otherwise leave a full row's gap between the caption and the thing it captions, which
   reads as an empty band rather than as a heading [developer-directed 2026-08-10]. Only the
   bottom padding goes — the gap ABOVE the caption is what separates the section from the fields
   before it, and that one is doing its job. */
.entity-form-grid.mud-grid > .form-child-section-item {
    padding-bottom: 0;
}

/* =========================================================================
 * 21. BLAZOR ERROR UI
 * ========================================================================= */
#blazor-error-ui {
    background: var(--app-warning-soft);
    bottom: 0;
    box-shadow: var(--app-shadow-error-bar);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

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

/* =========================================================================
 * 22. DARK MODE
 * Activated by the `app-dark` class on <body> (toggled via ThemeState/JS).
 * Re-points the design tokens and overrides the hard-coded light spots.
 * ========================================================================= */
/* A theme swap must not animate. The tokens flip in one frame when `app-dark` lands, but any
   element with a hover `transition` on a themed property animates from the old theme's RESOLVED
   value to the new one — and a light border is opaque near-white while dark's is translucent
   white, so for the transition's duration an opaque bright line sits on the already-dark card
   (every transitioned themed color has the trap; borders are where it shows).
   theme.js stamps this class for the flip's own frame only, so hover behavior is untouched.
   The ONE exemption is the theme toggle's own thumb: its transition is on `transform` alone —
   geometry, not a themed color, so it cannot flash — and the settings dialog re-renders it
   inside this class's two-frame window, so without the exemption the slide that IS the theme
   gesture snaps instead of travelling. */
body.theme-switching *:not(.theme-choice-thumb),
body.theme-switching *::before,
body.theme-switching *::after {
    transition: none !important;
}

body.app-dark {
    --mud-palette-primary: var(--app-primary);
    --mud-palette-primary-rgb: 124, 131, 255;
    --mud-palette-primary-text: #0b1020;
    /* See the :root:root note above - restated so the substitution re-resolves against the
       DARK --app-card instead of carrying the light one down here. */
    --mud-palette-surface: var(--app-card);
    --app-bg: #0a0f1c;
    --app-sidebar: #0d1322;
    --app-sidebar-deep: #070b16;
    --app-sidebar-text: #a3aed1;
    --app-sidebar-muted: #7e89ad;
    /* Same tinted-header idea as :root, re-mixed against the dark theme's periwinkle primary
       (these aliases do NOT re-resolve on their own). */
    --app-sidebar-text-parent: #b1b8ff;
    /* The current page's label — the light theme's mint restated as the dark theme's violet
       (see --app-sidebar-active-color in :root). */
    --app-sidebar-active-color: #ae6cf2;
    --app-primary: #7c83ff;
    --app-primary-soft: rgba(124, 131, 255, 0.16);
    /* The deep periwinkle behind the lifted --app-primary above: white ink reads 5.7:1 on it. */
    --app-primary-solid: #4a52e8;
    --app-primary-edge: color-mix(in srgb, var(--app-primary-solid) 29%, transparent);

    /* ⚠️ RESTATED PURE WHITE on purpose. The light block softens --app-on-primary off white
       with the surfaces [2026-09-06], and this token had NO body.app-dark declaration - so
       without this line the light value would reach the dark theme through :root and every
       solid control (the picked form tab, the brand/green/danger buttons, the sidebar's
       active row, the tooltip) would wear it. Dark keeps white on its #4a52e8 fill. */
    --app-on-primary: #ffffff;
    /* The menu's content-facing line, DARK ONLY [developer-directed]: the navy drawer stands
       clear of a light page on its own, but in dark mode the two surfaces are close enough to
       merge, so the edge is drawn there and nowhere else. */
    --app-sidebar-edge: 1px solid var(--app-border);
    --app-border: rgba(255, 255, 255, 0.10);
    --app-border-muted: rgba(255, 255, 255, 0.18);
    --app-text: #e6eaf5;
    --app-muted: #9aa6c7;
    --app-text-disabled: rgba(230, 234, 245, 0.30);
    --app-icon-muted: #8a94b8;
    --app-placeholder: #8a94b8;
    --app-card: #151d31;
    --app-surface-raised: #1b2440;
    --app-surface-subtle: rgba(124, 131, 255, 0.08);
    --app-surface-accent: rgba(124, 131, 255, 0.10);
    --app-surface-hover: rgba(255, 255, 255, 0.05);
    --app-surface-active: rgba(124, 131, 255, 0.12);
    /* Re-resolve the role aliases against the dark surfaces above. Custom properties
       that reference other custom properties resolve on the element where they are
       DECLARED (:root => light values) and inherit unchanged, so re-pointing the base
       tokens for dark mode does NOT update aliases unless they are re-declared here.
       Without this, form/filter boxes stay white in dark mode. */
    --app-color-form-box-bg: var(--app-surface-raised);
    --app-color-form-box-text: var(--app-text);
    --app-color-form-box-border: var(--form-control-border);
    --app-color-filter-box-bg: var(--app-surface-raised);
    --app-color-filter-label: var(--app-primary);
    --app-color-filter-label-bg: var(--app-card);
    /* Re-resolve the floating field-label colours against the DARK base tokens — without this the
       :root aliases keep their light values (white --app-card notch, dark --app-muted text). */
    --app-field-label-color: var(--app-muted);
    --app-field-label-color-active: var(--app-primary);
    --app-field-label-bg: var(--app-card);
    --app-topbar-bg: var(--app-sidebar);
    --app-topbar-muted: #aeb8d6;
    --app-topbar-muted-dark: #8a94b8;
    --app-link: #6db4ff;
    --app-link-hover: #9ecbff;
    --app-warning-soft: var(--app-warning-tint);
    --app-danger-soft: var(--app-danger-tint-strong);
    --app-indigo-soft: rgba(61, 90, 254, 0.28);
    --app-mint-soft: rgba(0, 207, 165, 0.2);
    --app-green-soft: var(--app-green-tint-toolbar);
    --app-magenta-soft: rgba(213, 0, 249, 0.24);
    --app-table-head: #0f1221;
    /* The dark half of THE column-name ink - see the :root block for what reads it. */
    --app-table-column-name: #847bfa;
    --app-danger-hover-bg: rgba(239, 68, 68, 0.2);
    --app-primary-tint-subtle: var(--app-surface-subtle);
    --app-primary-tint: var(--app-primary-soft);
    --app-primary-tint-strong: rgba(124, 131, 255, 0.30);
    --app-danger-tint-subtle: rgba(239, 68, 68, 0.08);
    --app-danger-tint: rgba(239, 68, 68, 0.12);
    --app-green-tint-toolbar: rgba(3, 181, 98, 0.22);
    --app-link-tint: rgba(109, 180, 255, 0.12);
    --app-warning-tint: rgba(255, 169, 9, 0.18);
    --app-table-row-hover: rgba(124, 131, 255, 0.10);
    /* Dark-mode zebra stripe: same hue as dark row-hover, half strength. */
    --app-table-row-stripe: rgba(124, 131, 255, 0.05);
    --app-table-expanded: var(--app-surface-subtle);
    /* Scrollbars: the value lives at :root (see the note there - <html> cannot read this
       block); these lines only point the generic tokens at it, so the webkit thumb, the
       standard `scrollbar-color` and the page bar can never drift apart. */
    --app-scrollbar-thumb: var(--app-scrollbar-dark-thumb);
    --app-scrollbar-thumb-hover: var(--app-scrollbar-dark-thumb-hover);
    --app-scrollbar-track: transparent;
    --app-shadow-ink: 0, 0, 0;
    --app-shadow-ink-alt: 0, 0, 0;
    /* ⚠️ The toggle shadows (tracks' inset + knobs' drop — their only consumers) must be
       RESTATED here, not left to ride --app-shadow-ink-alt: the composed tokens are declared
       on :root, where var() substitutes the LIGHT ink and bakes it — reassigning the ink on
       body.app-dark re-resolves nothing (the resolves-where-declared trap). And they need
       their own alpha: black at the light values' ~0.2 paints nothing on the dark tracks,
       so every toggle read flat in dark [developer-reported 2026-09-02, ElitaPro]. Same
       geometry as light — only the ink strength adapts. */
    --app-shadow-inset-deep: var(--app-shadow-inset-deep-geometry) rgba(0, 0, 0, 0.5);
    --app-shadow-inset-sunken: inset 3px 3px 7px rgba(0, 0, 0, 0.62);
    --app-shadow-elevation-sm: 0 2px 4.375px rgba(0, 0, 0, 0.55);
    --app-shadow: 0 3.5px 21px rgba(0, 0, 0, 0.45);
    --app-shadow-popover: 0 7px 28px rgba(0, 0, 0, 0.35);
    --app-shadow-dropdown: 0 7px 21px rgba(0, 0, 0, 0.5);
    --app-shadow-menu: 0 19.25px 48.125px rgba(0, 0, 0, 0.55);
    --app-shadow-dialog: 0 21px 61.25px rgba(0, 0, 0, 0.6);
    --app-shadow-login-card: 0 10.5px 35px rgba(0, 0, 0, 0.5);
    --app-shadow-topbar-user: 0 7px 17.5px rgba(0, 0, 0, 0.35);
    /* Sunken below the raised box surface (#1b2440) so a locked box clearly reads as off. */
    --app-input-disabled-bg: #10182c;
    --app-input-disabled-text: rgba(230, 234, 245, 0.45);
    /* Dark keeps one sunken fill for both lock kinds — the light-mode #f5f5f5 shade
       would read as a raised box here, not a sunken one. */
    --app-input-readout-bg: var(--app-input-disabled-bg);
    --app-disabled-bg: rgba(255, 255, 255, 0.08);
    --app-disabled-text: rgba(230, 234, 245, 0.38);
    --form-control-border: rgba(255, 255, 255, 0.18);
    /* Form focus stays app blue in dark mode (not the violet --app-primary accent). */
    --form-control-focus-border: #4895ef;
    --form-control-focus-shadow: 0 0 0 2.625px rgba(72, 149, 239, 0), 0 0 10.5px rgba(72, 149, 239, 0.2);
    --dropdown-caret-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M0 0l6 8 6-8z' fill='%238a94b8'/%3E%3C/svg%3E");
    --dropdown-clear-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M1.5 1.5l9 9M10.5 1.5l-9 9' stroke='%238a94b8' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");

    background: var(--app-bg);
    color: var(--app-text);
}

/* Topbar */
body.app-dark .topbar {
    background: var(--app-sidebar);
    border-color: var(--app-sidebar-divider);
}

body.app-dark .topbar-icon {
    color: var(--app-topbar-muted);
}

body.app-dark .topbar-logout-button,
body.app-dark .topbar-logout-button .mud-icon-root,
body.app-dark .mud-button-root.topbar-logout-button,
body.app-dark .mud-button-root.topbar-logout-button .mud-icon-root {
    color: var(--app-danger);
}

body.app-dark .topbar-logout-button:hover:not(.mud-disabled),
body.app-dark .mud-button-root.topbar-logout-button:hover:not(.mud-disabled) {
    background: var(--app-danger-tint-strong);
    color: var(--app-danger);
}

body.app-dark .topbar-logout-button:hover:not(.mud-disabled) .mud-icon-root,
body.app-dark .mud-button-root.topbar-logout-button:hover:not(.mud-disabled) .mud-icon-root {
    color: var(--app-danger);
}

body.app-dark .topbar-user {
    background: var(--app-glass-hover);
}

body.app-dark .topbar-user:hover {
    background: var(--app-primary-soft);
    box-shadow: var(--app-shadow-topbar-user);
}

body.app-dark .topbar-user-arrow {
    color: var(--app-topbar-muted-dark);
}

body.app-dark .topbar-user-menu {
    background: var(--app-card);
    border-color: var(--app-border);
    box-shadow: var(--app-shadow-menu);
    color: var(--app-text);
}

body.app-dark .user-menu-change-password {
    color: var(--app-link);
}

body.app-dark .user-menu-change-password:hover {
    color: var(--app-link-hover);
    background: var(--app-link-tint);
}

body.app-dark .user-menu-logout {
    color: var(--app-danger);
}

body.app-dark .user-menu-logout:hover {
    background: var(--app-danger-tint);
}

body.app-dark .logout-button {
    color: var(--app-topbar-muted);
}

/* App loading splash */
body.app-dark .app-loading-card {
    background: var(--app-card);
    color: var(--app-text);
    box-shadow: var(--app-shadow-login-card);
}

/* Cards / heroes / sections rely on tokens, but pin Mud paper surfaces too */
body.app-dark .content-card,
body.app-dark .data-card,
body.app-dark .page-hero {
    background: var(--app-card);
    border-color: var(--app-border);
}

/* Data tables */
body.app-dark .data-card .mud-table-head .mud-table-cell {
    color: var(--app-table-column-name);
    background: var(--app-table-head);
}

body.app-dark .data-card .mud-table-head .mud-table-cell .mud-table-sort-label,
body.app-dark .data-card .mud-table-head .mud-table-cell .mud-typography {
    color: var(--app-table-column-name);
}

body.app-dark .data-card .users-table-shell .mud-table-head .mud-table-cell {
    color: var(--app-table-column-name);
    background: var(--app-table-head);
}

body.app-dark .users-table-shell .mud-table-pagination {
    background: var(--app-card);
}

/* Pager */
body.app-dark .app-pager,
body.app-dark .app-pager-rows-select {
    background-color: var(--app-card);
    color: var(--app-text);
}

body.app-dark .app-pager-rows-select {
    border-color: var(--form-control-border);
    background-color: var(--app-card);
    background-image: var(--dropdown-caret-svg);
}

body.app-dark .user-form-label {
    color: var(--app-muted);
}

body.app-dark .table-toolbar .toolbar-filter-label,
body.app-dark .role-header-toolbar .toolbar-filter-label {
    background: var(--app-color-filter-label-bg);
    color: var(--app-muted);
}

body.app-dark .table-search-button,
body.app-dark .mud-button-root.table-search-button,
body.app-dark .mud-button-root.toolbar-header-button.toolbar-button--green {
    --toolbar-btn-color: var(--app-green);
    --toolbar-btn-soft: var(--app-green-tint-toolbar);
}

body.app-dark .mud-button-root.toolbar-header-button .mud-button-label,
body.app-dark .mud-button-root.toolbar-header-button .mud-icon-root {
    color: inherit;
}

body.app-dark .toolbar-header-button:hover:not(.mud-disabled),
body.app-dark .mud-button-root.toolbar-header-button:hover:not(.mud-disabled),
body.app-dark .toolbar-header-button:focus-visible:not(.mud-disabled),
body.app-dark .mud-button-root.toolbar-header-button:focus-visible:not(.mud-disabled) {
    background-color: var(--toolbar-btn-color);
    color: var(--app-color-page-button-hover-text);
    box-shadow: none;
}

body.app-dark .mud-button-root.toolbar-header-button:hover:not(.mud-disabled) .mud-button-label,
body.app-dark .mud-button-root.toolbar-header-button:hover:not(.mud-disabled) .mud-icon-root,
body.app-dark .mud-button-root.toolbar-header-button:focus-visible:not(.mud-disabled) .mud-button-label,
body.app-dark .mud-button-root.toolbar-header-button:focus-visible:not(.mud-disabled) .mud-icon-root {
    color: var(--app-color-page-button-hover-text);
}

body.app-dark .table-toolbar .toolbar-filter-field:has(.table-filter-status) .toolbar-filter-label {
    background: var(--app-color-filter-box-bg);
}

body.app-dark .table-toolbar .toolbar-filter-field:focus-within .toolbar-filter-label,
body.app-dark .role-header-toolbar .toolbar-filter-field:focus-within .toolbar-filter-label {
    color: var(--app-primary);
}

body.app-dark .native-form-control::placeholder,
body.app-dark .searchable-native-input::placeholder,
body.app-dark .searchable-multi-input::placeholder {
    color: var(--app-placeholder);
}

/* Permission matrix */
body.app-dark .permission-matrix table {
    border-color: var(--app-border);
}

body.app-dark .permission-matrix th,
body.app-dark .permission-matrix td {
    border-color: var(--app-border);
}

body.app-dark .permission-matrix th {
    background-color: var(--app-table-head);
    background-image: linear-gradient(var(--app-border), var(--app-border)) !important;
}

/* Dialogs (Mud surface comes from the dark palette; just align the frame) */
body.app-dark .mud-dialog-container .mud-dialog {
    border-color: var(--app-border);
    box-shadow: var(--app-shadow-dialog);
}

body.app-dark .soft-outline-button {
    color: var(--app-muted);
}

body.app-dark .soft-outline-button:hover:not(.mud-disabled) {
    background: var(--app-glass-subtle);
    border-color: var(--app-glass-border);
}

body.app-dark .dialog-cancel,
body.app-dark .mud-button-root.dialog-cancel {
    background: var(--app-glass-hover);
}

body.app-dark .dialog-cancel:hover:not(.mud-disabled),
body.app-dark .dialog-cancel:focus-visible:not(.mud-disabled),
body.app-dark .mud-button-root.dialog-cancel:hover:not(.mud-disabled),
body.app-dark .mud-button-root.dialog-cancel:focus-visible:not(.mud-disabled) {
    background: var(--app-glass-hover-strong);
}

body.app-dark .mud-button-root.role-action-button-delete.mud-disabled {
    background: var(--app-disabled-bg);
    color: var(--app-disabled-text);
}

/* Scrollbars: no dark RULE any more - the dark block above re-points the two tokens and the
   one global rule in §01 reads them. */

/* App Version dialog */
/* =========================================================================
 * 23. FEATURES: VERSION, AUTO-REFRESH, UPDATE OVERLAY & SETTINGS DRAWER
 * ========================================================================= */
.app-version-grid {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    min-width: 18rem;
}

.app-version-section {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.app-version-section-title {
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    opacity: 0.6;
}

.app-version-divider {
    margin: 0.25rem 0;
}

.app-version-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
}

.app-version-label {
    font-size: 0.85rem;
    opacity: 0.7;
}

.app-version-value {
    font-size: 0.9rem;
    font-weight: 500;
    text-align: end;
}

.app-version-value--mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.app-version-note {
    font-size: 0.85rem;
    opacity: 0.7;
}

.app-version-note--error {
    color: var(--mud-palette-error);
    opacity: 1;
}

/* Refresh-mode menu (AutoRefreshModeToggle) */
.table-autorefresh-menu {
    display: inline-flex;
}

.topbar-settings-popover,
.table-autorefresh-popover {
    border-radius: var(--app-radius);
    width: 131.25px !important;
    max-width: 131.25px !important;
}

/* Mud may anchor right when flipped; shift so 150px menu centers under the Refresh button. */
.topbar-settings-popover.mud-popover-anchor-bottom-right,
.table-autorefresh-popover.mud-popover-anchor-bottom-right {
    transform: translateX(calc((131.25px - var(--toolbar-header-button-width)) / 2));
}

.topbar-settings-menu-list {
    padding: 0.25rem;
    width: 131.25px;
    min-width: 131.25px;
    max-width: 131.25px;
    box-sizing: border-box;
}

.topbar-settings-menu-list .mud-menu-item {
    border-radius: var(--app-radius);
    font-size: 9.625px;
    font-weight: 400;
}

@media (min-width: 961px) {
    .topbar-settings-menu-list .mud-menu-item {
        font-size: 10.5px;
    }
}

/* Refresh menu text follows the shared D/T/M type scale (13/12/11). Scoped to the
   refresh popover so the settings (gear) menu, which shares the rule above, is
   unaffected. Specificity (0,3,0) outranks the base + desktop override (0,2,0). */
.table-autorefresh-popover .topbar-settings-menu-list .mud-menu-item {
    font-size: var(--app-text-menu);
}

.topbar-settings-menu-list .mud-menu-item:hover,
.topbar-settings-menu-list .mud-menu-item:focus-visible {
    background: var(--app-primary-soft);
}

.topbar-settings-menu-list .mud-menu-item .mud-icon-root {
    font-size: 1.1rem;
    color: var(--app-primary);
}

/* CUD auto-refresh banner (EntityChangeBanner) */
/* Vertically center the (i) icon against the taller button row. */
.entity-change-banner.mud-alert {
    align-items: center;
    /* Light mode: super-soft orange fill. (Dark mode keeps the soft app-blue below.) */
    background-color: var(--app-banner-warning-bg);
    border: none;
    border-radius: var(--app-radius);
}
body.app-dark .entity-change-banner.mud-alert {
    background-color: var(--app-primary-soft);
}
.entity-change-banner .mud-alert-icon {
    display: flex;
    align-items: center;
    align-self: center;
    padding-block: 0;
}
/* Info icon in the update-record amber; +1px on desktop only. */
.entity-change-banner .mud-alert-icon,
.entity-change-banner .mud-alert-icon .mud-icon-root {
    font-size: 1.375rem;
    color: var(--app-warning);
}
@media (min-width: 961px) {
    .entity-change-banner .mud-alert-icon,
    .entity-change-banner .mud-alert-icon .mud-icon-root {
        font-size: 21px;
    }
}
.entity-change-banner-content {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    flex: 1;
    min-width: 0;
}
/* Message text: 15px desktop (14px tablet, 13px mobile below), normal weight,
   update-record amber. */
.entity-change-banner-content > span {
    flex: 1;
    min-width: 0;
    text-align: start;
    font-size: 13.125px;
    font-weight: 400;
    color: var(--app-warning);
}
@media (min-width: 601px) and (max-width: 960px) {
    .entity-change-banner-content > span {
        font-size: 12.25px;
    }
}
@media (max-width: 600px) {
    .entity-change-banner-content > span {
        font-size: 11.375px;
    }
}
/* Make Refresh look like the Columns/Filters toolbar buttons (primary soft → fill on hover),
   matching their fixed width. (The width/height vars live on .page-card-header, which isn't an
   ancestor here, so use the literals.) */
.mud-button-root.entity-change-refresh-button {
    --toolbar-btn-color: var(--app-warning);
    --toolbar-btn-soft: var(--app-warning-soft);
    min-width: 7.5rem;
    width: 7.5rem;
    justify-content: center;
}

body.app-dark .mud-button-root.entity-change-refresh-button {
    --toolbar-btn-soft: var(--app-warning-soft);
}

/* Mobile: collapse Refresh to an icon-only square so it never wraps to a new line. */
@media (max-width: 600px) {
    .mud-button-root.entity-change-refresh-button {
        min-width: 31.5px;
        width: 31.5px;
        padding: 0;
    }
    .mud-button-root.entity-change-refresh-button .mud-button-label {
        font-size: 0;
        line-height: 0;
        gap: 0;
    }
    .mud-button-root.entity-change-refresh-button .mud-button-icon-start {
        margin-inline-end: 0;
    }
}

/* New-version blocking overlay (MainLayout) — forces a reload onto the latest build */
.app-update-overlay {
    position: fixed;
    inset: 0;
    z-index: 3000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    background: var(--app-overlay-dialog);
    backdrop-filter: blur(2px);
}
.app-update-overlay-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0.75rem;
    max-width: 332.5px;
    width: 100%;
    padding: 2rem 1.75rem;
    border-radius: var(--app-radius);
}
.app-update-overlay-icon {
    width: 2.75rem;
    height: 2.75rem;
    color: var(--mud-palette-primary);
}
.app-update-overlay-title {
    margin: 0;
    font-weight: 600;
}
.app-update-overlay-text {
    margin: 0;
    color: var(--mud-palette-text-secondary);
}
.app-update-overlay-button {
    margin-top: 0.5rem;
}

.preferences-card {
    display: flex;
    flex-direction: column;
    width: 100%;
    box-sizing: border-box;
    padding: 0 1rem;
    /* Softer than --app-radius (5px): this is a content panel, not a control. */
    /* NOT a token here: --app-radius-panel exists in sibling apps because TWO panels must
       agree there; this copy has exactly one consumer, and a token for one rule is a rung that
       carries its own value — the thing ui-conventions §Corner radius says not to add. Promote
       it the day a second panel needs the same softer corner. */
    border-radius: 8.75px;
    border: 1px solid var(--app-border);
    background: var(--app-surface-raised);
}
.preferences-card-divider {
    margin: 0;
    border-color: var(--app-border);
    opacity: 1;
}

/* Preferences row: title + the muted line that says what the switch does, switch on the far side */
.preferences-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.85rem 0;
}
.preferences-row-text {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    flex: 1;
    min-width: 0;
}
.preferences-row-title {
    font-size: 12.25px;
    font-weight: 600;
    line-height: 1.3;
}
.preferences-row-description {
    margin: 0;
    font-size: 10.938px;
    line-height: 1.45;
    color: var(--app-muted);
}
.preferences-row-switch {
    flex-shrink: 0;
}

/* Theme switch (the ערכת נושא row) — the on/off switch the other preference rows wear, with the
   topbar's own sun/moon icons in place of the plain knob. One button, not two: a press anywhere
   on the track swaps the theme, and the filled pill slides to the icon now in force. The track
   reuses the switch's shape and inset shadow so the four rows read as one column of controls. */
.theme-choice {
    --theme-choice-segment: 35px;
    --theme-choice-height: 22.75px;
    --theme-choice-gap: 2px;
    --theme-choice-inset: 2.625px;
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--theme-choice-gap);
    padding: var(--theme-choice-inset);
    border: none;
    border-radius: var(--app-radius-pill);
    background: var(--app-border-muted);
    box-shadow: var(--app-shadow-inset-deep);
    cursor: pointer;
}

/* The filled pill, slid rather than repainted: same 180ms ease the switch knob travels on, so
   picking a theme reads as the same gesture as flipping one of the switches below it. */
.theme-choice-thumb {
    position: absolute;
    top: var(--theme-choice-inset);
    inset-inline-start: var(--theme-choice-inset);
    width: var(--theme-choice-segment);
    height: var(--theme-choice-height);
    border-radius: var(--app-radius-pill);
    background: var(--app-primary);
    box-shadow: var(--app-shadow-elevation-sm);
    transition: transform 180ms ease;
}

.theme-choice.is-dark .theme-choice-thumb {
    transform: translateX(calc(var(--theme-choice-segment) + var(--theme-choice-gap)));
}

[dir="rtl"] .theme-choice.is-dark .theme-choice-thumb {
    transform: translateX(calc(-1 * (var(--theme-choice-segment) + var(--theme-choice-gap))));
}

/* The two icons ride above the sliding thumb and paint no background of their own —
   position+z-index, or the thumb would cover them. */
.theme-choice-option {
    position: relative;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--theme-choice-segment);
    height: var(--theme-choice-height);
    color: var(--app-muted);
    transition: color 180ms ease;
}

.theme-choice-option .mud-icon-root {
    font-size: 1.05rem;
}

/* Hovering anywhere on the switch lifts the idle icon — the whole track is the target now. */
.theme-choice:hover .theme-choice-option:not(.is-selected) {
    color: var(--app-text);
}

.theme-choice-option.is-selected {
    color: var(--app-on-primary);
}

.theme-choice:focus-visible {
    outline: 2px solid var(--app-primary);
    outline-offset: 2px;
}

/* Refresh-mode menu (AutoRefreshModeToggle): small round green dot marks the selected option */
.refresh-mode-item {
    display: flex;
    align-items: center;
    gap: 0.55rem;
}
.refresh-mode-menu-item.mud-menu-item {
    padding: 0.35rem 0.55rem;
    box-sizing: border-box;
}
.refresh-mode-dot {
    /* The fill of the selected option, as its own token so the mark has ONE knob. It reads
       --app-green (the estate's "on" colour); it painted --app-primary before, which said
       nothing about the option being the chosen one.
       It is declared HERE and never in :root - a custom property's var() is substituted on the
       element that DECLARES it, so an alias in :root would bake the LIGHT value and keep it
       under body.app-dark. Same rule --toolbar-btn-color follows on its own buttons. */
    --refresh-mode-dot-color: var(--app-green);
    display: inline-block;
    width: 10.5px;
    height: 10.5px;
    border-radius: var(--app-radius-circle);
    /* The RING is painted, not a border - see the .is-selected rule under this one for why. */
    background-image: radial-gradient(circle at 50% 50%,
        transparent 0 3.75px, var(--app-muted) 3.75px 5.25px, transparent 5.25px);
    box-sizing: border-box;
    flex-shrink: 0;
}
/* The RING AND THE DOT ARE ONE GRADIENT, so they cannot land off each other
   [developer-reported 2026-09-04 on ElitaPro: the dot was not sitting in the middle of its ring].
   The dot used to be an ::after centred by flex, which centres it EXACTLY in LAYOUT - the defect
   is in the PAINT. A box is snapped to whole device pixels independently of its parent, and this
   mark sits on a fractional x (331.578 measured in the running app), so on a 1.25x display the dot
   came out 2 device px from one edge of the ring and 3 from the other. Measured, dot-vs-ring, off
   a magnified capture: 1x square, 1.25x OFF BY HALF A DEVICE PIXEL, 1.5x square, 2x square - a
   rounding artefact, not a wrong number, which is why no size alone fixes it.
   ⚠️ Painting only the DOT as a gradient is NOT enough, and was tried first: the ring is then still
   a border, its rendered width rounds on its own, and a dot whose pixel width has the other parity
   cannot sit square in it (it moved the defect from 1.25x to 1.5x). Painting BOTH from the same
   radial-gradient makes them concentric BY CONSTRUCTION - one rasterisation, one centre, symmetric
   anti-aliasing on either side - at every ratio. Same reason .app-radio paints its checked dot as a
   gradient rather than as an inset shadow.
   Geometry, unchanged from the border version except the dot: outer radius 5.25px (the 10.5px box),
   ring 1.5px thick so it starts at 3.75px, dot radius 3px (it was 6.125px across, now 6px - a hair
   smaller, and a whole number at 1x and 2x). Keep the two rules in step: the resting ring and the
   selected one repeat the same two outer stops. */
.refresh-mode-dot.is-selected {
    background-image: radial-gradient(circle at 50% 50%,
        var(--refresh-mode-dot-color) 0 3px, transparent 3px 3.75px,
        var(--app-muted) 3.75px 5.25px, transparent 5.25px);
}

.settings-dialog {
    --entity-form-dialog-width: 490px;
    /* The two panels are not equally tall (five toggles vs. two version blocks). Pinning the panel
       box keeps the popup from resizing under the pointer on a tab switch — the same reason the
       entity forms pin theirs. It is measured off the TALLER panel (תצוגה, 370px at 560px wide —
       it was 407px at 520px, where two descriptions still wrapped to a second line), not picked by
       eye: one row short and the last card loses its bottom edge to a scroll, which is exactly what
       the pin exists to prevent. Re-measure when a preference row is added or removed — or when the
       dialog width moves, since that is what decides how many descriptions wrap. */
    --entity-form-tabs-height: 328.125px;
}

.settings-dialog-heading {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
}

.settings-dialog-title-icon {
    color: var(--app-primary);
    fill: currentColor;
    flex-shrink: 0;
}

/* The tab strip IS this dialog's content — nothing sits above it, so it needs neither the seam
   line nor the gap that separates it from an entity form's main field area. */
.settings-dialog-tabs {
    margin-top: 0;
    border-top: 0;
}

/* The gap is what separates one preferences card from the next — the תצוגה tab stacks three of
   them (look / sidebar / notifications), the אודות tab has one, where the gap costs nothing. */
.settings-dialog-panel {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding-top: 0.25rem;
}

/* The אודות tab's two version blocks sit in the same card as the preference rows; each takes the
   row's vertical padding so both tabs breathe alike (the העתקה button rides the dialog's actions
   bar, or it would scroll out of the panel). */
.preferences-card .app-version-section {
    padding: 0.85rem 0;
}

/* Excel-style per-column filter (ColumnFilterMenu) */
/* The span that carries the right-click-clear handler must not add box height around the icon. */
.column-filter-trigger-wrap {
    display: inline-flex;
}

.column-filter-trigger {
    opacity: 0.55;
    margin-inline-start: 0.1rem;
}

.column-filter-trigger:hover {
    opacity: 1;
}

.column-filter-trigger.is-active {
    opacity: 1;
    color: var(--app-primary);
}

.column-filter-panel {
    width: 16rem;
    padding: 0.5rem;
    box-sizing: border-box;
}

.column-filter-search {
    width: 100%;
    margin-bottom: 0.35rem;
}

.column-filter-selectall {
    padding: 0.1rem 0.25rem;
}

.column-filter-list {
    max-height: 14rem;
    overflow-y: auto;
    padding: 0.1rem 0.25rem;
}

.column-filter-item {
    display: flex;
}

/* (ריק) is pinned under the scrolling list, so it takes that list's own inline padding to sit
   in the same column as the values above it; the muted label says it is a STATE of the cell,
   not one of the column's values. Same rule serves the search filter's "blanks only" tick. */
.column-filter-blank {
    padding: 0.1rem 0.25rem;
}

.column-filter-blank .mud-typography {
    color: var(--app-muted);
}

.column-filter-divider {
    margin: 0.25rem 0;
}

.column-filter-status {
    display: flex;
    justify-content: center;
    padding: 0.75rem 0;
}

.column-filter-actions {
    display: flex;
    justify-content: space-between;
    gap: 0.5rem;
    padding-top: 0.15rem;
}

.sortable-th-content {
    display: inline-flex;
    align-items: center;
    gap: 0.1rem;
}

.sortable-th-actions {
    display: inline-flex;
    align-items: center;
}

/* While a row's update/delete dialog is open, a frame of one even width runs around that
   whole row (orange = update, red = delete) so the operator sees which record the dialog
   targets [developer-directed 2026-10-23: replaced the gutter dot]. Cells paint their own
   borders over a <tr> border and every wrapper up to the card clips, so js/rowDialogMark.js
   appends the frame to body as position:fixed (also outside the zoom roots, letting client
   rects drive the placement 1:1) and sizes it to the row's visible part.
   .row-dialog-edit/.row-dialog-delete (RowClassFunc) are the JS hooks, deliberately styleless.
   Wired via the RowDialogMark service (Shared/Services) — three touchpoints per page,
   documented on the class — on every list page with per-row edit/delete dialogs. */
.row-dialog-frame {
    position: fixed;
    box-sizing: border-box;
    border: 2px solid var(--app-warning);
    border-radius: 3px;
    pointer-events: none;
    z-index: 1350;
}

.row-dialog-frame--delete {
    border-color: var(--app-danger);
}

/* =========================================================================
 * 24. BOARD PANE (the card recipe /welcome wears)
 * ========================================================================= */
/* `.board-pane`: head = transparent caption, body = framed box standing --board-pane-inset off
   the card's edges. It came from the template's boards — the /lists lookup board (replaced by
   list pages /departments and /expense-areas) and the master-detail board (never used in
   Pulsar) — whose rules, title band and «+ add» button were removed [developer-directed
   2026-10-23]; /welcome is the one page left on it. Everything here is token-driven, so dark
   mode falls out of §22's reassignment with no rules of its own. */

/* The shared board-pane card: border, radius, card surface, shadow — the .data-card recipe
   minus its hover lift (several cards breathing under one pointer move would flicker). */
.board-pane {
    /* ONE inset for everything the card holds off its own edges: the head's text, the framed
       body's four sides. Stated once so the frame and the caption above it share a start edge. */
    --board-pane-inset: 0.85rem;
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius);
    background: var(--app-card);
    box-shadow: var(--app-shadow);
    overflow: hidden;
}

/* Everything in a pane EXCEPT its head is the pane's BODY, stated as "every child but the
   head" rather than by naming the bodies, so a pane can gain a body without gaining a rule.
   The body is a FRAMED box standing --board-pane-inset off the card's edges — a list run
   flush into the card's own border reads as touching it — so the card is the surface and the
   frame is the list's. ⚠️ No overflow here: the framed element decides its own clipping
   (a scroller body clips itself). */
.board-pane > :not(.board-pane-head) {
    flex: 1 1 auto;
    min-height: 0;
    margin-block: 0 var(--board-pane-inset);
    margin-inline: var(--board-pane-inset);
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius);
}

/* The head is a CAPTION, not a band, on the card's own surface. It states its own height
   rather than taking it from its contents, so two cards side by side line their heads up. */
.board-pane-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-shrink: 0;
    min-height: 2.6rem;
    padding: 0.35rem var(--board-pane-inset);
    background: transparent;
}

/* THE START CORNER, ONE GAP OFF IT. The card sits at the content area's start (the RIGHT, on an
   RTL app) with the SAME space over it as beside it — --welcome-gap, one value read three times,
   so the two can never drift apart. The corner itself comes free: `.page-stack` is a flex column,
   so an item with a definite width is placed at its cross-start. It also STOPS at 88rem — on a wide
   screen the rows stretch far past anything in them — and that cap does not bite at 1440 and under,
   where the content area is already narrower.
   ⚠️ The gap is subtracted from the WIDTH TWICE, not only added as a margin
   (`100% - 2 * var(--welcome-gap)`). The first subtraction pays for the start margin — with a
   plain `100%` the margin would push the board past the stack's end edge and the page would
   scroll sideways at every width under the cap. The second is the FAR side
   [developer-reported 2026-09-04]: below the 88rem cap the card used to run flush into the
   stack's end edge, so the space beside it was one gap on the start and nothing on the end, and
   the card read as pushed off to one side rather than as inset. Both sides are now the same
   value by construction. Above the cap nothing changes — the width is 88rem and whatever is
   left over stays on the end side, which is what the start-corner rule is for.
   ⚠️ The space above is the board's own PADDING, not a margin on the card, so on a viewport too
   short for the card it scrolls away with the content instead of pinning it down.
   ⚠️ CENTRING WAS TRIED TWICE AND REVERSED TWICE in ElitaPro: a landing page is read from its
   start corner, and centring only MOVES the empty space — down the page it bought a ~230px band
   above the card while the page under it stayed empty anyway. Two things learned there, if it is
   ever asked for here: an `auto` inline margin OVERRIDES a flex item's `stretch`, so a `max-width`
   must become `width: min(...)` in the same breath or the board falls back to its own max-content
   (a third of the page) and centres THAT; and `justify-content`/`align-content: center` need their
   `safe` form as a second declaration, or a card taller than the box is pushed off both ends with
   its top unreachable by scrolling. */
.welcome-board {
    /* ZERO IN PULSAR, ON EVERY WIDTH [developer-directed 2026-10-22: "close the gap"]. The estate
       value is 2rem (framework/ui-conventions.md); here the card sits exactly where every other
       page's card sits — flush at the stack's start and top, with `.app-content`'s own padding as
       the whole of the space around it. The token and the three places that read it stay, so
       retuning it is still one number; the old `max-width: 960px` zeroing went with it. */
    --welcome-gap: 0px;
    flex: 1 1 auto;
    min-height: 0;
    /* A board scrolls when its card outgrows it. The card itself is block-level, so it simply sits
       at the top of it. */
    overflow-y: auto;
    overflow-x: hidden;
    width: min(100% - 2 * var(--welcome-gap), 88rem);
    margin-inline-start: var(--welcome-gap);
    padding-block-start: var(--welcome-gap);
}

/* NO BRAND STRIP [developer-directed 2026-10-22: "remove the colorful line"]. The card used to
   carry the brand gradient as a 3px top edge (a `::before` on a `welcome-pane` class); it is gone,
   so the card's top edge is the same plain border as every other board's. */

/* NO FRAME: the body gives back the border, the radius and the --board-pane-inset margins that
   `.board-pane`'s shared rule hands every body. A framed box is what a TABLE needs — the lookup
   board's rows would otherwise run flush into the card's own border — and this body holds none:
   the rows draw their own boxes and the mark is a picture, so the frame would be a second
   rectangle around content that already has its own edges. The inset survives as the body's own
   PADDING.
   ⚠️ Stated at (0,3,0): the shared rule is `.board-pane > :not(.board-pane-head)` = (0,2,0), which
   a bare `.welcome-body` would lose to. */
.welcome-board .board-pane > :not(.board-pane-head) {
    margin: 0;
    border: 0;
    border-radius: 0;
}

/* The card's body: the mark at the inline start (the RIGHT), the rows taking the rest. The two
   columns are not peers. The mark column STRETCHES to the rows' height and centres the artwork
   inside it, so the two sides share a top and a bottom edge however many rows there are.
   BOTH COLUMNS ARE FRACTIONS, AND THAT IS THE POINT [developer-reported 2026-09-04]: the mark's
   was a FIXED `minmax(0, 20rem)`, so as the window narrowed the whole loss came out of the ROWS —
   the cards shrank, the mark did not, and the card's two halves changed proportion at every width.
   `0.34fr` is the ratio the fixed pair actually rendered at 1440 (280px of mark against ~816px of
   rows), so the desktop look is unchanged and every narrower width now keeps it.
   ⚠️ `minmax(0, …)` on BOTH, never a bare `0.34fr` / `1fr`: a bare fr is `minmax(auto, Nfr)`,
   whose auto min is the track's own content and would stop it shrinking — the very behaviour being
   removed here. */
.welcome-body {
    display: grid;
    grid-template-columns: minmax(0, 0.34fr) minmax(0, 1fr);
    gap: var(--board-pane-inset);
    padding: var(--board-pane-inset);
}

/* A CONTAINER [developer-reported 2026-09-04], so the mark's own font-size can be a share of the
   column it stands in (see .welcome-logo below). Safe here: this is a grid item whose inline size
   comes from its track, never from its contents, which is what `container-type: inline-size`
   requires. */
.welcome-brand {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 0;
    container-type: inline-size;
}

/* The wordmark, sized as a SHARE of its column, with a rem CAP [developer-reported 2026-09-04,
   restated for the wordmark 2026-10-22]: a rem width alone held the mark at full size while
   everything around it narrowed, so the width is a percentage of the column and the rem only
   stops it growing once the board reaches its own 88rem cap. The height follows the image's own
   ratio (.brand-wordmark), so nothing here fixes one. */
.brand-wordmark.welcome-logo {
    width: min(85%, 24rem);
}

/* A GRID, not a flex column, for one reason: `grid-auto-rows: 1fr` makes every row as tall as the
   TALLEST of them — the date row carries a third line (the weekday) and would otherwise stand a
   line taller than its three neighbours. In a grid whose height is its own content's, an `fr` row
   resolves to the largest row's max-content, so the four come out equal with no measured number to
   keep in step. The row's own `align-items: center` is then what centres a short row's text in the
   height it is given. */
.welcome-rows {
    display: grid;
    grid-auto-rows: 1fr;
    gap: 0.75rem;
    min-width: 0;
}

/* One fact per row, full width: a badge pinned to the row's inline start and the text centred in
   everything it leaves. The identity is a row like the others — its avatar simply stands where
   their badge does, at the same size and in the same filled shape. */
.welcome-row {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.8rem 0.9rem;
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius);
    background: var(--app-surface-subtle);
}

.welcome-row-badge {
    flex: none;
    /* 40px is MudAvatar's Size.Medium own diameter — the identity row's avatar stands in this
       slot, so the badge is measured against it rather than chosen. */
    width: 40px;
    height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--app-radius);
    background: var(--app-primary);
    /* The icon inherits, so the badge states its ink once for whatever it holds. */
    color: var(--app-on-primary);
}

.welcome-row-badge .mud-icon-root {
    font-size: 1.35rem;
    color: inherit;
}

/* Centred in what the badge leaves, not pinned to it: a row is the full width of its column, so
   text held against the badge would leave most of the row empty. */
.welcome-row-text {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.15rem;
    text-align: center;
}

.welcome-row-label {
    font-size: calc(var(--app-text-scale) + 1px);
    font-weight: 400;
    color: var(--app-muted);
    line-height: 1.3;
}

.welcome-row-value {
    font-size: calc(var(--app-text-scale) + 6px);
    font-weight: 600;
    color: var(--app-text);
    line-height: 1.25;
    overflow-wrap: break-word;
}

.welcome-row-value--empty {
    color: var(--app-muted);
    font-weight: 400;
}

.welcome-row-note {
    font-size: calc(var(--app-text-scale) + 1px);
    color: var(--app-muted);
    line-height: 1.2;
}

/* A date and a clock read inside an RTL line otherwise put their separators and digit groups the
   wrong way round. ElitaPro states this recipe once as `.occupancy-count` and the welcome page
   reuses it; this app has no such class, so the two declarations live here. */
.welcome-ltr {
    direction: ltr;
    unicode-bidi: isolate;
}

/* The pill sizes itself to the role name here — the floor it carries in a table cell is for a
   column, and this row has no column to line up with. */
.welcome-row-roles {
    justify-content: center;
    margin-top: 0.15rem;
}

.welcome-row-roles .role-pill {
    min-width: 0;
    font-size: calc(var(--app-text-scale) + 1px);
}

@media (max-width: 900px) {
    /* The two columns stack, the mark's first — it is the page's masthead. */
    .welcome-body {
        grid-template-columns: minmax(0, 1fr);
    }

    .brand-wordmark.welcome-logo {
        width: min(100%, 16rem);
    }
}

@media (max-width: 600px) {
    .welcome-rows {
        gap: 0.6rem;
    }

    .welcome-row {
        gap: 0.7rem;
        padding: 0.7rem;
    }
}

/* --- File drop field (FileDropField) ------------------------------------
   The app's file control: a drop zone, not the browser's grey "choose file" button.
   Every colour here is a theme token, so dark mode needs no rules of its own.
   The invalid state is NOT restated — `.file-drop.is-invalid` is a selector on the
   shared invalid block (§ "Invalid state — one shared style for every form box"),
   so the zone wears the exact red frame every other box does. */
.file-drop {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 84px;
    padding: 0.75rem 1rem;
    box-sizing: border-box;
    border: 1px dashed var(--form-control-border);
    border-radius: var(--app-radius-form-box);
    background-color: var(--app-surface-subtle);
    transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
}

/* Stretched and transparent, NOT the sr-only clip recipe .profile-image-picker-input
   uses: that one has no area, and area is exactly what a drop target needs. This is
   what makes the whole zone both the click target and the browser's own drop target. */
.file-drop-input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

.file-drop:not(.is-disabled):hover {
    border-color: var(--form-control-border);
    background-color: var(--app-surface-hover);
}

.file-drop:focus-within:not(.is-invalid) {
    border-color: var(--form-control-focus-border);
    box-shadow: var(--form-control-focus-shadow);
}

/* Dragging over: the dashes close into a solid accent frame, so the zone reads as armed.
   The fill is `--app-primary-tint`, not `-tint-subtle` — the subtle one resolves to the very
   same value as the resting `--app-surface-subtle` in dark mode, which left the state change
   carried by the border alone there. */
.file-drop.is-dragover {
    border-style: solid;
    border-color: var(--app-primary);
    background-color: var(--app-primary-tint);
}

.file-drop.is-disabled {
    background-color: var(--app-input-disabled-bg);
    pointer-events: none;
}

/* The colour comes from the shared invalid block; only the style is the zone's own —
   dashes read as "you may drop here", and a rejection has to read as a frame. */
.file-drop.is-invalid {
    border-style: solid;
}

.file-drop-prompt {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.15rem;
    text-align: center;
    pointer-events: none;
}

.file-drop-icon {
    font-size: 1.75rem;
    color: var(--app-muted);
    margin-bottom: 0.15rem;
}

.file-drop.is-dragover .file-drop-icon {
    color: var(--app-primary);
}

.file-drop-title {
    font-size: var(--app-text-form-box);
    color: var(--app-text);
}

.file-drop-hint {
    font-size: 0.75rem;
    color: var(--app-muted);
}

/* HANDLE mode (Chrome / Edge): there is no native input, the zone itself is the click target —
   so it has to say so with the cursor the input used to bring. */
.file-drop--handle:not(.is-disabled) {
    cursor: pointer;
}

/* The card plus, in NATIVE mode only, the one-line note under it saying the file was copied
   once. A column so the note sits under the card rather than beside it. */
.file-drop-picked {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    width: 100%;
    min-width: 0;
    pointer-events: none;
}

.file-drop-note {
    font-size: 0.75rem;
    color: var(--app-muted);
    text-align: center;
}

/* The picked file, as a card. min-width:0 on the name so a long file name ellipses
   instead of widening the 600px dialog it sits in. */
.file-drop-file {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    min-width: 0;
    pointer-events: none;
}

.file-drop-file-icon {
    font-size: 1.5rem;
    color: var(--app-green);
    flex-shrink: 0;
}

.file-drop-file-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--app-text-form-box);
    color: var(--app-text);
}

.file-drop-file-size {
    flex-shrink: 0;
    font-size: 0.75rem;
    color: var(--app-muted);
}

/* The one thing in the card that IS clickable — it has to re-arm its own pointer events,
   the card above having switched them off so the input behind takes the click. */
.file-drop-clear {
    flex-shrink: 0;
    pointer-events: auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26.25px;
    height: 26.25px;
    padding: 0;
    border: 0;
    border-radius: var(--app-radius-icon-button);
    background: transparent;
    color: var(--app-muted);
    cursor: pointer;
}

.file-drop-clear:hover {
    background-color: var(--app-surface-hover);
    color: var(--form-control-invalid-border);
}

.file-drop-clear .mud-icon-root {
    font-size: 1.125rem;
}

/* Roomy tier — the one step between the 600px form default and the 900px wide one: a form whose
   boxes are too narrow at the default (a full name, an agency, a 12-column row of small spans)
   but whose field COUNT would leave the wide tier as whitespace either side of the boxes.
   720px = 45rem, the one round rem step in that gap (the default is 37.5rem, wide is 56.25rem),
   so the tier sits on the same 16px grid as its neighbours. Consumers: שעות' add-a-worker dialog
   [developer-directed 2026-08-17] and שעות חודשי's row dialog [developer-directed 2026-08-17]. */
.entity-form-dialog--roomy {
    --entity-form-dialog-width: 630px;
}

/* The generic import dialog [2026-09-03]. Step 1 is a form and sits on the default tier; step 2
   holds the preview TABLE and takes the widest tier — keyed on CONTENT (:has) rather than on a
   class the dialog would have to swap mid-life, the same :has() idiom the tier rule itself uses.
   The table cap is the חסרים day dialog's, a step shorter: this dialog spends a summary line, a
   policy line and a toolbar above the table where that one spends a single summary line. */
.mud-dialog-container .mud-dialog:has(.import-preview) {
    --entity-form-dialog-width: 1085px;
}

.import-preview .form-child-table .mud-table-container {
    max-height: min(calc(52vh / var(--app-zoom)), 60rem);
}

.import-dialog-step {
    font-weight: 400;
    color: var(--app-muted);
    margin-inline-start: 0.35rem;
}

.import-preview-summary {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.75rem;
    margin-bottom: 0.25rem;
}

.import-preview-count {
    font-weight: 600;
}

.import-preview-policy {
    margin-bottom: 0.5rem;
}

.import-preview-toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}

.import-preview-toggle.is-on {
    color: var(--app-primary);
}

/* The legacy's pink cell: the offending cell tinted and inked in the danger colour, its message
   on the cell's own title and in the row's הערת מערכת column. */
.import-preview-table td.import-cell--invalid {
    background-color: var(--app-danger-tint-subtle);
    color: var(--app-danger);
}

.import-preview-table td.import-note-cell,
.import-issues-table td.import-note-cell {
    color: var(--app-danger);
}

/* The column's Excel letter before its title — where the values were READ from, in every mode.
   Isolated LTR: a Latin letter before a «·» in an RTL head cell would otherwise trade places with
   it, and read as «· B» at the wrong end of the title. It wears a small GREEN badge — the verdict
   pills' own recipe (1px edge at 45%, 14% fill, the colour as ink) on the button radius, since a
   letter is a rectangle and not a word [developer-directed 2026-09-07: "borders and color, green"] —
   so the address stands apart from the name without outweighing it (weight 400 against the head's
   600). Sized in em off the head's own text, so it steps with the text scale. */
.import-preview-table th .import-col-letter {
    display: inline-block;
    direction: ltr;
    unicode-bidi: isolate;
    padding: 0 0.4em;
    border: 1px solid color-mix(in srgb, var(--app-green) 45%, transparent);
    border-radius: var(--app-radius-button);
    background: color-mix(in srgb, var(--app-green) 14%, transparent);
    color: var(--app-green);
    font-weight: 400;
    line-height: 1.4;
}

/* A row number sits at its column's end, whatever the page's direction. ⚠️ NOT `direction: ltr`
   [developer-reported 2026-09-06]: the shared child-table rule draws the column rule as
   `border-inline-end`, which a flipped cell paints on the OTHER physical side — the שורה column
   drew its rule on the outer edge (doubling the frame) and none against סטטוס. `unicode-bidi:
   plaintext` resolves the TEXT's direction from its content (a «-5» still reads LTR) and leaves
   the box's `direction`, and with it the border side and `text-align: end`, on the page's own. */
.import-preview-table .import-cell--number,
.import-issues-table .import-cell--number {
    text-align: start;
    white-space: nowrap;
}

/* ⚠️ The plaintext goes on an INLINE inside the cell, never on the cell [developer-reported
   2026-09-06]: on a block, `unicode-bidi: plaintext` also makes `text-align: start` resolve against
   the CONTENT's direction, so every numeric cell aligned LEFT in the RTL table. On the span it is
   an isolate whose base direction is its own text (a «-5» still reads LTR) while the cell's line is
   aligned by the cell's direction — the page's start, right, like every other table in the app. */
.import-cell-text {
    unicode-bidi: plaintext;
}

/* The value cells, same trap one column over: they carried dir="auto", and an EMPTY or numeric
   cell resolves LTR — its rule jumped sides and it read as merged with its neighbour. The content
   keeps its own direction through plaintext; the cell aligns to the page's start like every other
   table in the app [developer-directed 2026-09-06: Hebrew app = right]. */
.import-preview-table td.import-cell--value,
.import-issues-table td.import-cell--value {
    text-align: start;
}

/* The verdict column shrinks to its pill: auto layout otherwise hands it a share of the table's
   free width as if it held text. 1% + nowrap is the standard "as wide as its content" cell. */
.import-preview-table .import-cell--status {
    width: 1%;
    white-space: nowrap;
}


/* ── Document form (Features/Documents) ─────────────────────────────────────────────────────
   One dialog for all seven document types: a header, the customer, the linked documents, two
   inline child grids (lines, payments) and the totals. The payments grid has seven input
   columns, which do not fit the shared --wide tier, hence the document's own width. */
.document-form-dialog {
    --entity-form-dialog-width: 1050px;
}

/* The form's icon-only buttons — «יבוא פרטי לקוח» (sharing the שם לקוח cell) and «נקה תוכן»
   (the header's last cell) — the size of a row delete button (--action-btn-size). The cell keeps
   its own width (a name box beside it takes the rest); the cell's blank label does the vertical
   alignment, and the tooltip wrapper is a control-height flex box that centres the smaller button
   on the boxes beside it. align-items keeps that wrapper as wide as the button: a cell alone in
   its grid item (the bin) is as wide as the item, and a stretched wrapper centres the tooltip on
   the item instead of on the button. */
.entity-form-grid .user-form-field.document-icon-cell {
    width: auto;
    flex: 0 0 auto;
    align-items: flex-start;
}

.document-icon-tip.mud-tooltip-root {
    display: flex;
    align-items: center;
    height: var(--entity-form-control-height);
}

/* The tax-year form's bracket delete button sits in a column-flex .user-form-field, which would
   stretch the tooltip wrapper across the whole cell and centre the tooltip over empty space —
   the wrapper shrinks to the button so the tooltip points at it. */
.tax-bracket-remove-tip.mud-tooltip-root {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    height: var(--entity-form-control-height);
}

.mud-button-root.document-icon-btn {
    min-width: 0;
    min-height: 0;
    width: var(--action-btn-size);
    height: var(--action-btn-size);
    padding: 0;
}

.mud-button-root.document-icon-btn .mud-icon-root {
    font-size: var(--action-btn-icon-size);
}

/* «עדכון הזמנה» (DocumentOrderAdjustmentDialog): one row of read-outs plus the five-column list
   of adjusted orders under it — wider than the default 600px, narrower than the document form. */
.order-adjustment-dialog {
    --entity-form-dialog-width: 900px;
}

/* The legacy footnote under the list ("* טבלה זו משמשת רק לתצוגה…"): at the start, under the box. */
.order-adjustment-note {
    margin-top: 6px;
    text-align: start;
}

/* The customer-list window (DocumentCustomerPickerDialog, legacy Frm_LakohotHipus): six text
   columns — name, ת.ז, street, city, phone, email — need the document's own width, not --wide. */
.customer-picker-dialog {
    --entity-form-dialog-width: 1050px;
}

/* Every row is a choice: pointer, and no text selection on the double-click that confirms. */
.customer-picker-table tbody tr:not(.form-child-empty-row) {
    cursor: pointer;
    user-select: none;
}

/* The box keeps its full height whatever the filter leaves in it [developer-directed 2026-10-20]:
   the shared rule only CAPS it, so a search that narrows the list to a few rows — or to none —
   shrank the whole window under the user's hands while typing. Same value as the cap, so a full
   list looks exactly as before. */
.customer-picker-table .mud-table-container {
    height: min(45vh, 26rem);
}

/* With that fixed height a short list ends mid-box, not on the frame, so the final row keeps its
   rule — the shared form-child-table rule drops it only because the frame normally draws it. */
.customer-picker-table tbody tr:last-child:not(.form-child-empty-row) > * {
    border-bottom: 1px solid var(--app-border);
}

/* The «הצג פעילים בלבד» tick beside the search box: it has no label row, so it sits on the box's
   line instead of the grid's flex-start top. Its control is 45px against the box's ~29px, so the
   -8px pulls its centre onto the box's centre (measured: 253.1 → 261.2 = the box's). */
.customer-picker-active-only {
    align-self: flex-end;
}

.customer-picker-active-only .mud-checkbox {
    margin-bottom: -8px;
}

/* The status column (shown once «הצג פעילים בלבד» is cleared) stays on one line: «לא פעיל» wrapped
   to two lines in the narrow width the six text columns left it [developer-directed 2026-10-21]. */
.customer-picker-table .customer-picker-status {
    white-space: nowrap;
    min-width: 5.5rem;
}

/* The "found N of M" line under the box: the pager-info text, hugging the box's start edge. */
.customer-picker-count {
    margin-top: 6px;
    text-align: start;
}

/* The item-list window (DocumentItemPickerDialog, legacy Frm_Parit) shares the customer window's
   table rules; its four columns need less width. */
.item-picker-dialog {
    --entity-form-dialog-width: 900px;
}

/* The note beside the invoice totals: its grid item stretches to the totals' height (the grid
   otherwise anchors items to the top), and the textarea fills it, so the note's bottom edge is the
   bottom of the totals' last row — the אחוז מע"מ box [developer-directed 2026-10-22]. */
.entity-form-grid > .mud-grid-item.document-note-fill {
    align-self: stretch;
    display: flex;
    flex-direction: column;
}

.document-note-fill > .user-form-field,
.document-note-fill .form-control-sized {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
}

.document-note-fill .native-form-textarea {
    flex: 1 1 auto;
}

/* A document's lines / payments table: the small «+» add-row button sits UNDER the last row, where
   the next row will appear [developer-directed 2026-10-25]. It hugs the table (half the grid
   gutter above it) and starts where the row's first box starts: the cell's 8px inline padding. */
.entity-form-grid.mud-grid > .document-child-add-item {
    padding-top: 8px;
}

.document-child-add-row {
    display: flex;
    padding-inline-start: 8px;
}

.document-child-add-row > .mud-tooltip-root {
    display: inline-flex;
}

/* A document line's item cell: the search button, then the dropdown filling the rest. The time
   form's order cell (.time-order-cell) is the same shape: the order-list button, then the order
   dropdown [developer-directed 2026-10-24]. */
.document-line-item-cell,
.time-order-cell,
.form-select-with-helper {
    display: flex;
    align-items: center;
    gap: 6px;
}

.document-line-item-cell > .mud-tooltip-root,
.time-order-cell > .time-helper,
.form-select-with-helper > .form-select-helper {
    display: inline-flex;
    flex: 0 0 auto;
}

/* In a form, the box may carry an error line under it: the row anchors to the top and each helper
   button is centred on the box's own height, so a red message never pulls the buttons down. */
.form-select-with-helper {
    align-items: flex-start;
}

.form-select-with-helper > .form-select-helper {
    align-items: center;
    height: var(--entity-form-control-height);
}

.document-line-item-cell > :last-child,
.time-order-cell > :last-child,
.form-select-with-helper > :last-child {
    flex: 1 1 auto;
    min-width: 0;
}

/* The linked-documents box (DocumentLinksPanel, legacy l_MismahimMekusharim): an x on each row;
   under the list, the link button at the start side and the total box at the end side. */
.document-links-table .mud-table-container {
    max-height: 12rem;
}

/* The empty row stands exactly as tall as a filled one: a data row's cell padding, and — when rows
   carry an x — one button's height of line [developer-directed 2026-10-21]. */
.document-links-table tbody tr.form-child-empty-row > td {
    padding: var(--form-child-cell-pad-block) var(--form-child-cell-pad-inline);
}

.document-links-panel:has(.entity-table-actions-header) .document-links-table tbody tr.form-child-empty-row > td {
    line-height: var(--action-btn-size);
}

.document-links-panel .document-links-total {
    width: 12rem;
    margin-top: 8px;
    margin-inline-start: auto;
}

/* The link picker (DocumentLinkPickerDialog, legacy Frm_MismahMekushar): the seven type radios in
   one wrapping row, the "showing only …" line, the list at a fixed height so switching types does
   not make the window jump. */
.document-link-picker-dialog {
    --entity-form-dialog-width: 900px;
}

.document-link-picker-types {
    display: flex;
    flex-wrap: wrap;
    column-gap: 4px;
}

.document-link-picker-info {
    margin-bottom: 6px;
    text-align: start;
}

/* Every row is a choice (a double-click confirms), and a double-click on the "none" line must not
   select its text either. */
.document-link-picker-table tbody tr {
    user-select: none;
}

.document-link-picker-table tbody tr:not(.form-child-empty-row) {
    cursor: pointer;
}

.document-link-picker-table .mud-table-container {
    height: min(45vh, 24rem);
}

/* Issued document (Phase 2c): Update + Cancel keep the place they have on every dialog (the start
   edge); print / PDF / email go to the far end of the bar [developer-directed 2026-10-22] — they
   act on the paper, not on the record. The auto margin pushes them there; the 16px padding keeps
   the groups apart when the bar is too narrow to leave a gap. One row of equal columns, so the
   three buttons take the widest one's width; their icons carry the export menu's colors. */
.document-print-actions {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: 8px;
    margin-inline-start: auto;
    padding-inline-start: 16px;
}

/* Same block padding as the dialog's own Update / Cancel family, so the five buttons on the bar
   are one height. */
.document-print-actions .mud-button-root {
    padding-block: 4.375px;
}

/* A plain outlined button (Variant.Outlined, no Color) wears a form box's edge, not MudBlazor's
   text-colored ring, which reads near-white on dark [developer-directed 2026-11-05]. App-wide:
   the document print / PDF / email bar, «הצג מקור» / «טעינת חשבונית» on the expense form, the
   allocation request, the tax-authority login. The toolbar buttons and «/4» keep their own edge. */
.mud-button-root.mud-button-outlined.mud-button-outlined-default:not(.toolbar-header-button):not(.expense-div4-btn) {
    border-color: var(--app-color-form-box-border);
}

/* A disabled button (while a print runs) greys its icon with its label. */
.document-print-actions .mud-button-root:disabled .mud-icon-root {
    color: inherit;
}

/* ===== Document reports (Phase 3): מסמכים פתוחים, דוח לקוח ===== */

/* PageCardHeader with a TitleSuffix: the title and its live subject on one line. The group takes
   the title's own never-shrink flex (the row scrolls, see .page-title). */
.page-title-group {
    display: flex;
    align-items: baseline;
    gap: 0.9rem;
    flex: 1 0 auto;
    min-width: 0;
}

.page-title-group > * {
    flex: 0 0 auto;
}

/* The picked customer beside the דוח לקוח title — regular weight, app text size, so it reads as
   the report's live subject, not a heading. */
.customer-report-title-detail {
    font-size: var(--app-text-scale);
    font-weight: 400;
    color: var(--app-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Document-type pills (DocumentRoutes.TypePillClass): soft tint + the type's hue as text, so a
   row's type and its linked documents match at a glance and never look like the SOLID status
   pill. The text mixes the hue with --app-text: darker on light, lighter on dark — one token
   per type serves both themes. */
.doc-type-pill {
    --doc-type-hue: var(--status-ended);
    display: inline-flex;
    align-items: center;
    padding: 0.12rem 0.55rem;
    border: 1px solid color-mix(in srgb, var(--doc-type-hue) 45%, transparent);
    border-radius: var(--app-radius-pill);
    background: color-mix(in srgb, var(--doc-type-hue) 16%, transparent);
    color: color-mix(in srgb, var(--doc-type-hue) 70%, var(--app-text));
    font-size: 0.8125rem;
    line-height: 1.2;
    white-space: nowrap;
}

.doc-type-pill--quote { --doc-type-hue: #64748b; }
.doc-type-pill--order { --doc-type-hue: #f59e0b; }
.doc-type-pill--proforma { --doc-type-hue: #8b5cf6; }
.doc-type-pill--taxinvoice { --doc-type-hue: #3b82f6; }
.doc-type-pill--creditnote { --doc-type-hue: #ef4444; }
.doc-type-pill--receipt { --doc-type-hue: #10b981; }
.doc-type-pill--invoicereceipt { --doc-type-hue: #06b6d4; }

/* Several linked documents in one cell: a gap instead of a comma. */
.doc-type-pill-row {
    display: inline-flex;
    gap: 0.3rem;
}

/* Prompt shown before a customer is picked — sits clear of the filter row. */
.report-empty-hint {
    margin-top: 14px;
}

/* The uniform-structure files page: the business line between the date row and the table. */
.uniform-files-business {
    margin: 12px 0 8px;
}

/* ===== Time entries (Phase 5): זמן ===== */

/* The form's recent orders | recent agreements tables [developer-directed 2026-11-03]: every row
   is a pick, one click — pointer, and no text selection. */
.time-recent-table tbody tr:not(.form-child-empty-row) {
    cursor: pointer;
    user-select: none;
}

/* «שיוך» as radio buttons [developer-directed 2026-11-03]: the recent tables' titles, or the short
   line that stands in for them (an edit, no recent time). The text is the form label's own size and
   weight; the picked one takes the brand colour. */
.time-target-radio {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0;
    margin-bottom: var(--form-label-gap);
    border: 0;
    background: none;
    color: var(--mud-palette-text-secondary);
    font: inherit;
    font-size: 0.8125rem;
    font-weight: 600;
    cursor: pointer;
}

.time-target-radio.is-on {
    color: var(--mud-palette-primary);
}

.time-target-radio:disabled {
    cursor: default;
    opacity: 0.6;
}

.time-target-line {
    display: flex;
    align-items: center;
    gap: 1.25rem;
}

.time-target-line .time-target-radio,
.time-target-line .user-form-label {
    margin-bottom: 0;
}

/* The form (TimeEntryFormDialog, legacy Frm_Zman): one row per section, as the legacy laid it out —
   [rate] [start] [end] [hours] [amount] [note] = 2+3+3+1+1+2 — so the dialog takes the legacy
   window's own width (~1,100px) rather than the --wide tier [developer-directed 2026-10-24]. The
   order box shares its cell with the order-list button (.time-order-cell, on the document line's
   item-cell rules). */
.time-entry-form-dialog {
    --entity-form-dialog-width: 1240px;
}

/* The tallest form in the app, so the space under its title is cut: the grid's own +0.25rem plus the
   first row's 16px top padding left 37px between the title and the recent tables; pulled up, 22px
   [developer-directed 2026-11-03 — this form only]. */
.time-entry-form-dialog .user-form-grid {
    margin-top: -0.75rem;
}

/* A start / end group (DateTimeSpinField — the time form and the estimate form [developer-directed
   2026-11-03]), laid out as the legacy's (right to left): the "now" button and the day
   spin, ONE locked date-time box, the hour and minute spins, and a clear on an optional box — all
   on one line under one label [developer-directed 2026-10-24]. The box takes what the buttons
   leave; the "now" and clear buttons are row-action-sized in a control-height .document-icon-tip
   wrapper. The error message prints once, under the row. */
.time-datetime-row {
    display: flex;
    align-items: flex-start;
    gap: 6px;
}

/* The label line starts over the box, not over the "now" button and minute spin before it
   [developer-directed 2026-11-05]: indented by exactly their widths and the two gaps. */
.time-datetime-label {
    padding-inline-start: calc(var(--action-btn-size) + 1.15rem + 12px);
}

.time-datetime-row > .time-datetime-box {
    flex: 1 1 0;
    min-width: 0;
    /* "25/10/2026 18:07" is ONE left-to-right run (Excel shows a date-time so): in the RTL form the
       bidi algorithm had put the hour before the date. The value still hugs the box's right edge. */
    direction: ltr;
}

[dir="rtl"] .time-datetime-box .native-form-control {
    text-align: right;
}

/* A helper button's wrapper (the "now", clear and order-list buttons): a control-height flex box
   that centres the smaller button on the boxes beside it. No tooltip wrappers on this form
   [developer-directed 2026-10-24]. */
.time-helper {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    height: var(--entity-form-control-height);
}

.time-datetime-row > .time-spin {
    flex: 0 0 auto;
}

/* The legacy's spin buttons (sp_*_Minute / _Hour / _Day) [developer-directed 2026-10-24]: one
   column per unit, each a box-high outlined column cut into an up and a down half, in the legacy's
   own colours — minute green, hour orange, day red — so a unit is told by its colour, as it was.
   A disabled column dims like the boxes beside it. */
.time-spin {
    display: flex;
    flex-direction: column;
    width: 1.15rem;
    height: var(--entity-form-control-height);
    border: 1px solid var(--form-control-border);
    border-radius: var(--app-radius-form-box);
    overflow: hidden;
    background: var(--spin-soft);
}

.time-spin--minute {
    --spin-accent: var(--app-green);
    --spin-soft: var(--app-green-soft);
}

.time-spin--hour {
    --spin-accent: var(--app-warning);
    --spin-soft: var(--app-warning-soft);
}

.time-spin--day {
    --spin-accent: var(--app-danger);
    --spin-soft: var(--app-danger-soft);
}

.time-spin-btn {
    flex: 1 1 0;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 0;
    padding: 0;
    margin: 0;
    border: 0;
    background: transparent;
    color: var(--spin-accent);
    cursor: pointer;
    line-height: 1;
}

.time-spin-btn + .time-spin-btn {
    border-top: 1px solid var(--form-control-border);
}

.time-spin-btn:hover:not(:disabled) {
    background: var(--spin-accent);
    color: #fff;
}

.time-spin-btn:disabled {
    cursor: default;
    opacity: 0.45;
}

.time-spin-btn .mud-icon-root {
    font-size: 1.05rem;
}

/* The order cell anchors to the top and centres its button on the BOX's height, so an error line
   under the dropdown does not drag the button down to the middle of box + message. */
.time-order-cell {
    align-items: flex-start;
}

/* The grid's "paid" toggle: the form toggle, shrunk to the dense row height. */
.users-table-shell .user-form-status-row {
    min-height: 0;
}

/* Expenses — the form runs rows of four boxes (3+3+3+3), so it is wider than the standard 600px:
   a quarter row still fits «אחוז מע"מ מוכר» on one line. */
.expense-form-dialog {
    --entity-form-dialog-width: 960px;
}

/* Estimates — the prospect's four boxes on one row (3+3+3+3), and the meeting's start / end groups
   at about the time form's width [developer-directed 2026-11-03]. */
.estimate-form-dialog {
    --entity-form-dialog-width: 1100px;
}

/* «ריכוז שעות» — the candidates table keeps every cell and title on one line (the «שעות לחיוב» span
   «16:16–16:22» too), so each entry is one thin row. The dialog is the standard wide tier
   (entity-form-dialog--wide, 787.5px): the note column takes what the short columns leave. */
.hours-statement-candidates th,
.hours-statement-candidates td {
    white-space: nowrap;
}

/* The short columns shrink to their content, so the in-form table's 7px sides left them cramped;
   twice that gives each column air. The token is declared on .form-child-table, so it is set there. */
.hours-statement-candidates .form-child-table {
    --form-child-cell-pad-inline: 14px;
}

/* The table is the app's in-form table (form-child-table), outside users-table-shell, so the shared
   pin rule does not reach it: the short columns shrink to their content here, the note takes the rest. */
.hours-statement-candidates th.th-col-pin {
    width: 1px;
}


/* The agreement's edit form carries its statements on a tab strip under the fields (ElitaPro's employee
   form shape) [developer-directed 2026-11-03]. Wider than the wide tier so the history's eight columns
   and four row buttons hold one line each; the tab panel is taller than the strip's 280px default, so
   the gather table shows several entries before it scrolls (the panel scrolls, the dialog never resizes). */
.hourly-agreement-form-dialog--statements {
    --entity-form-dialog-width: 1000px;
    --entity-form-tabs-height: min(calc(46vh / var(--app-zoom)), 420px);
}

/* Its «עדכון» / «ביטול» sit under the fields, above the strip [developer-directed 2026-11-03]: the dialog
   foot's own bar look (start-aligned, 7px gap) and the 16px gutter above it. */
.entity-form-inline-actions {
    display: flex;
    justify-content: flex-start;
    gap: 7px;
    padding-top: 1rem;
}

.hourly-agreement-form-dialog--statements .mud-dialog-actions:empty {
    display: none;
}

/* The retainer's edit form carries its items and its periods the same way (retainers v2, 2026-11-04):
   «עדכון» / «ביטול» above the strip, the empty foot bar hidden. The panel is cut to what the tabs
   hold [developer-directed 2026-11-04]: measured 206px for one item line with its totals and 239px
   for seven periods, so 280px fits two item lines or eight periods before the panel scrolls. */
.retainer-form-dialog--periods {
    --entity-form-tabs-height: min(calc(36vh / var(--app-zoom)), 280px);
}

.retainer-form-dialog--periods .mud-dialog-actions:empty {
    display: none;
}

/* A period whose invoice was cancelled no longer counts: shown, but dimmed. */
.retainer-period-cancelled td {
    color: var(--mud-palette-text-secondary);
}

/* The panel holds no MudGrid when it shows only a line: the grid's own top padding is what parts the
   fields from the strip elsewhere, so the line takes the same space. */
.hours-history-message {
    padding-top: 1rem;
}

/* «ריכוזי שעות» tab: the count of statements not sent yet, a small red pill inside the label. The menu's
   counts on «ריטיינרים» / «הסכמים שעתיים» (NavCountBadge) are the same pill. */
.tab-count-badge,
.nav-count-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.15rem;
    height: 1.15rem;
    padding: 0 0.3rem;
    margin-inline-start: 0.45rem;
    border-radius: var(--app-radius-pill);
    background-color: var(--app-danger);
    color: var(--app-on-primary);
    font-size: 0.7rem;
    font-weight: 700;
    line-height: 1;
}

/* The menu's count sits on the icon's top corner, the one toward the label: beside the label it did not
   fit — «הסכמים שעתיים» + the pill is wider than the menu, and the card cut it. The label
   (.mud-nav-link-text, position: relative) is the positioned box, and the icon begins 0.45rem
   before it: -1.4rem puts the pill's start edge on the icon's middle, so a wider count grows over
   the icon's far half and the gap, not over the label. A ring in the menu's own background parts it
   from the icon. Smaller than the tab's pill: it sits on a 22px icon. */
.sidebar-link .nav-count-badge {
    position: absolute;
    top: -0.55rem;
    inset-inline-start: -1.4rem;
    margin: 0;
    min-width: 1rem;
    height: 1rem;
    padding: 0 0.22rem;
    font-size: 0.62rem;
    box-shadow: 0 0 0 2px var(--mud-palette-drawer-background);
    pointer-events: none;
}

/* «ריכוז» sits at the gather tab's start (the right, in RTL), as the form's own «עדכון» above it
   [developer-directed 2026-11-03]: that one is the agreement's, this is the tab's. */
.hours-statement-gather-bar {
    display: flex;
    justify-content: flex-start;
    align-items: center;
    gap: 8px; /* «ריכוז» and «ייצוא» side by side [developer-directed 2026-11-05] */
}

/* «ריכוזי שעות» of one agreement (ElitaPro's חסרים day window look) [developer-directed 2026-11-03]:
   a summary line, then the in-form table. */

.hours-history-table th,
.hours-history-table td {
    white-space: nowrap;
}

.hours-history-summary {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin-bottom: 0.75rem;
}

/* The status as a dot + its word: a dense table of pills would outweigh the rows. Open = orange
   (waiting to be sent), sent = green, its document cancelled = grey. */
.hours-history-status,
.hours-history-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.hours-history-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex: none;
    background-color: var(--hours-history-dot, var(--status-ended));
}

/* Not sent yet = red, as the agreement row's «ריכוזי שעות» button [developer-directed 2026-11-03]. */
.hours-history-status.is-open,
.hours-history-pill.is-open {
    --hours-history-dot: var(--app-danger);
}

.hours-history-status.is-sent,
.hours-history-pill.is-sent {
    --hours-history-dot: var(--status-on);
}

/* The summary's verdict wears a soft frame in its own colour, as ElitaPro's «חסר» chip. */
.hours-history-pill {
    padding: 0.15rem 0.7rem;
    border: 1px solid color-mix(in srgb, var(--hours-history-dot) 55%, transparent);
    border-radius: var(--app-radius-pill);
    background-color: color-mix(in srgb, var(--hours-history-dot) 12%, transparent);
    font-size: 0.8125rem;
}

/* The issued document opens from its cell: a text button in the link colour. */
.hours-history-doc-link {
    padding: 0;
    border: 0;
    background: none;
    color: var(--mud-palette-primary);
    font: inherit;
    cursor: pointer;
}

.hours-history-doc-link:hover {
    text-decoration: underline;
}

/* The agreement row's sign for a statement not sent yet: a red dot before the agreement's name, the
   colour the row's «ריכוזי שעות» button wore before the statements moved into the edit form
   [developer-directed 2026-11-03]. The count is in its tooltip and on the form's tab. */
.agreement-unsent-root {
    display: inline-flex;
    vertical-align: middle;
    margin-inline-end: 0.45rem;
}

.agreement-unsent-sign {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background-color: var(--app-danger);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--app-danger) 22%, transparent);
}

/* A kind-of-row dot in a list cell (/time-entries, 2026-11-04 [developer-directed]): two NEUTRAL
   colours — blue and fuchsia, never red / green / orange, which already mean a status here. In
   «הזמנה / הסכם» blue = order, fuchsia = hourly agreement; in «שעתי» fuchsia = כן, blue = לא. (Violet was
   too close to the blue, and grey was refused.) */
:root {
    --type-dot-first: #4a7dff;
    --type-dot-second: #d946ef;
}

.type-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    margin-inline-end: 0.45rem;
    vertical-align: middle;
    background-color: var(--type-dot-first);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--type-dot-first) 22%, transparent);
}

.type-dot.is-second {
    background-color: var(--type-dot-second);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--type-dot-second) 22%, transparent);
}

/* A STATUS dot, the same shape: green = on, red = off — the toggle's own two colours (--status-on /
   --status-off). «שולם» on /time-entries, where the value is shown as text (2026-11-04 [developer-directed]). */
.type-dot.is-on {
    background-color: var(--status-on);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--status-on) 22%, transparent);
}

.type-dot.is-off {
    background-color: var(--status-off);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--status-off) 22%, transparent);
}

/* The same dot marks a retainer period waiting for renewal (/retainers, 2026-11-04): red once the
   period ended, orange while it still runs. */
.agreement-unsent-sign.is-upcoming {
    background-color: var(--app-warning);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--app-warning) 22%, transparent);
}

/* «N ממתינים לחידוש» beside the /retainers title: a switch that filters the grid to the pending
   periods. Soft orange tint when off, solid orange when on. */
.retainer-pending-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.2rem 0.75rem;
    border: 1px solid color-mix(in srgb, var(--app-warning) 55%, transparent);
    border-radius: var(--app-radius-pill);
    background-color: color-mix(in srgb, var(--app-warning) 12%, transparent);
    color: var(--app-text);
    font: inherit;
    font-size: var(--app-text-scale);
    white-space: nowrap;
    cursor: pointer;
}

.retainer-pending-chip:hover {
    background-color: color-mix(in srgb, var(--app-warning) 22%, transparent);
}

.retainer-pending-chip:focus-visible {
    outline: 2px solid var(--app-primary);
    outline-offset: 2px;
}

.retainer-pending-chip.is-on {
    background-color: var(--app-warning);
    color: var(--app-on-primary);
}

.retainer-pending-chip.is-on .agreement-unsent-sign {
    background-color: var(--app-on-primary);
    box-shadow: none;
}

/* «טעינת חשבונית» — what the reading found, under the button: right-aligned (start), the status line
   first, then the notes as a bulleted list. */
.expense-reading-lines {
    text-align: start;
    color: var(--mud-palette-text-secondary);
    font-size: 0.8rem;
    line-height: 1.6;
}

.expense-reading-lines ul {
    margin: 0.25rem 0 0;
    padding-inline-start: 1.25rem;
    list-style: disc;
}

/* Expenses (Phase 4) — the form's ÷4 button: a square the height of the boxes beside it (like the
   time form's "now" button), and the supplier ledger's totals row. */
.expense-div4-btn.mud-button-root {
    height: var(--entity-form-control-height);
    min-width: var(--entity-form-control-height);
    width: 100%;
    padding: 0;
    font-weight: 400;
}

/* «/4» wears a form box's edge and fill [developer-directed 2026-10-31] — not MudBlazor's outlined
   ring — and takes the primary tint on hover. Table and form alike. */
.expense-div4-btn.mud-button-root.mud-button-outlined {
    border: 1px solid var(--app-color-form-box-border);
    border-radius: var(--app-radius-form-box);
    background: var(--app-color-form-box-bg);
    color: var(--app-text);
    transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease;
}

.expense-div4-btn.mud-button-root.mud-button-outlined:hover:not(:disabled) {
    background: var(--app-primary-soft);
    border-color: var(--app-primary);
    color: var(--app-primary);
}

.users-table-shell .mud-table-foot .expense-ledger-total-cell {
    position: sticky;
    bottom: 0;
    z-index: 1;
    white-space: nowrap;
    background: var(--app-table-head);
    border-top: 1px solid var(--app-border);
}

/* ---------------------------------------------------------------- business logo (עסקים form)
   The saved logo beside the drop zone: the same height and corner as the zone, a solid edge
   where the zone's is dashed (this is what IS saved, the zone is what COULD be), the image
   scaled to fit, and a small remove x in the corner. */
.company-logo-preview {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 84px;
    padding: 0.5rem 1.75rem;
    box-sizing: border-box;
    border: 1px solid var(--form-control-border);
    border-radius: var(--app-radius-form-box);
    background-color: var(--app-surface-subtle);
}

.company-logo-preview img {
    display: block;
    max-width: 100%;
    max-height: 64px;
    object-fit: contain;
}

.company-logo-preview .company-logo-remove-tip {
    position: absolute;
    top: 2px;
    inset-inline-end: 2px;
}

/* The hint under the default switch reads with the switch, from the start edge. */
.user-form-status .form-field-hint {
    text-align: start;
    margin-top: 0.25rem;
}

/* The preview is as tall as the drop zone beside it, whatever the zone holds (its prompt, a picked
   file, an error line): its grid item stretches to the row, and the box fills what the label leaves. */
.entity-form-grid > .company-logo-item {
    align-self: stretch;
    display: flex;
    flex-direction: column;
}

.company-logo-item > .user-form-field {
    flex: 1;
    display: flex;
    flex-direction: column;
}

.company-logo-item .company-logo-preview {
    flex: 1;
}

/* The incoming-invoice upload dialog (IncomingUploadDialog) holds a TABLE — file name, size,
   state — so it sits on the wide tier (entity-form-dialog--wide). The size and state cells stay
   on one line (a state chip can be a whole sentence: «אחסון הקבצים לא זמין כרגע. הקובץ לא נשמר.»),
   and the file name takes the leftover width and wraps, so a long name never pushes the table
   into a horizontal scroll [developer-directed 2026-10-30]. */
.incoming-upload-table td:nth-child(2),
.incoming-upload-table td:nth-child(3),
.incoming-upload-table th {
    white-space: nowrap;
}

.incoming-upload-table td:first-child {
    width: 100%;
    overflow-wrap: anywhere;
}

/* /expenses/drafts: a draft is corrected in its own row [developer-directed 2026-10-30]. Each box
   gets a definite width from its cell (--table-edit-w, set per column) — a percentage-wide input in
   an auto-sized table cell has no width of its own and would collapse to the header. Thinner block
   padding so a row of boxes is not much taller than a row of text. */
.users-table-shell td.table-edit-cell {
    padding-block: 3px;
}

.table-edit-cell .user-form-field {
    width: var(--table-edit-w, 8rem);
}

.table-edit-cell .user-form-field .form-control-sized {
    width: 100%;
}

/* A box with a small button beside it in a drafts cell (the total's ÷4): one row, the button a
   square the box's height. */
.table-edit-with-button {
    display: flex;
    align-items: center;
    gap: 4px;
}

/* The form's total: the box takes the cell, the ÷4 square sits beside it, as in the table. Rows
   anchor to the top, so a validation line under the box does not move the button. */
.expense-form-total {
    align-items: flex-start;
}

.expense-form-total > :first-child {
    flex: 1 1 auto;
    min-width: 0;
}

.expense-form-total > .expense-div4-btn.mud-button-root {
    flex: 0 0 auto;
    margin-top: calc((var(--entity-form-control-height) - 24px) / 2);
}

/* Smaller than the box beside it in the table [developer-directed 2026-10-31]. */
.table-div4-btn.expense-div4-btn.mud-button-root {
    width: 24px;
    min-width: 24px;
    height: 24px;
    font-size: 0.75rem;
}

/* The tick column of the drafts table («הפקת מנה» posts the ticked rows). The header holds the
   tick-all box; a row that cannot be posted shows a disabled box whose tooltip lists what is missing.
   Shrink-to-content, like the +/- expand column. */
.users-table-shell .mud-table-cell.draft-tick-header,
.users-table-shell .mud-table-cell.draft-tick-cell {
    width: 1px;
    padding-inline: 4px;
    text-align: center;
}

/* The V button: green with a V = the row is chosen for the batch, an EMPTY neutral box = not
   (no red, no V — developer-directed 2026-10-30 after seeing a red one), grey = cannot be posted
   yet (its tooltip says why). The status-on token, so it reads like every other on in the app. */
.draft-tick-btn.mud-icon-button {
    width: 26px;
    height: 26px;
    padding: 0;
    border-radius: var(--app-radius-icon-button);
    border: 1px solid transparent;
    transition: background-color 160ms ease, color 160ms ease, border-color 160ms ease;
}

.draft-tick-btn.mud-icon-button .mud-icon-root {
    font-size: 18px;
}

.draft-tick-btn.is-on.mud-icon-button {
    background: var(--status-on);
    border-color: var(--status-on);
    color: #fff;
}

.draft-tick-btn.is-off.mud-icon-button {
    background: var(--app-card);
    border-color: var(--form-control-border);
    color: transparent;
}

.draft-tick-btn.is-on.mud-icon-button:hover:not(:disabled) {
    filter: brightness(0.92);
}

.draft-tick-btn.is-off.mud-icon-button:hover:not(:disabled) {
    border-color: var(--status-on);
    background: var(--app-surface-subtle);
}

.draft-tick-btn.is-blocked.mud-icon-button,
.draft-tick-btn.mud-icon-button:disabled {
    background: var(--app-input-disabled-bg);
    border-color: var(--app-border);
    color: var(--app-icon-muted);
    opacity: 1;
}

/* The same V, smaller, before a form label (DateTimeSpinField Synced: the time form's billable
   start / end follow the worked ones while it is on). */
.time-sync-label-row {
    display: flex;
    align-items: center;
    gap: 6px;
}

/* The title beside the V flips it too: a pointer and no text selection on a double click. */
.time-sync-title.is-clickable,
.time-sync-title.is-clickable .user-form-label {
    cursor: pointer;
    user-select: none;
}

.time-sync-label-row .draft-tick-btn.time-sync-tick.mud-icon-button {
    width: 18px;
    height: 18px;
}

.time-sync-label-row .draft-tick-btn.time-sync-tick.mud-icon-button .mud-icon-root {
    font-size: 14px;
}

.draft-tick-blocked {
    display: inline-flex;
}

.draft-blockers div + div {
    margin-top: 2px;
}

/* The (i) beside the batch's value month. The row's reading-notes (i) in «פעולות» was removed
   [developer-directed 2026-10-31]. */
.draft-notes-icon {
    color: var(--app-muted);
}

/* The batch's value month above the drafts table — one box for every ticked row [developer-directed
   2026-10-30], in the report pages' filter-row shape, with its hint behind an (i) beside it. */
.batch-value-date {
    align-items: center;
    margin-bottom: 0.75rem;
}

/* The intake strip above the drafts table: the uploaded supplier invoices not yet read (waiting /
   reading / failed), one line each, inside a bordered box in the card's tone. Hidden when empty. */
.intake-strip {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-bottom: 12px;
    padding: 6px 10px;
    border: 1px solid var(--app-border);
    border-radius: 8px;
    background: var(--app-surface-subtle);
}

.intake-row {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 32px;
    font-size: 0.875rem;
}

.intake-row-icon {
    flex: 0 0 auto;
    width: 18px !important;
    height: 18px !important;
    color: var(--app-muted);
}

.intake-row--failed .intake-row-icon {
    color: var(--mud-palette-error);
}

.intake-row-name {
    font-weight: 600;
    overflow-wrap: anywhere;
}

.intake-row-when,
.intake-row-state {
    white-space: nowrap;
}

.intake-row-state .muted-text {
    white-space: normal;
}

.intake-row-actions {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    margin-inline-start: auto;
}

/* ---- ניירות (2026-10-31) ------------------------------------------------------------------- */

/* The paper's page (PaperEditor, /papers/new and /papers/{id}): the headers in ONE row, like
   AllGoods' order header, and the rich text body under them taking the rest of the card's height
   (the card body is a column that hides overflow, so the editor's page scrolls inside itself and
   its bottom edge is always on screen). Each header field has its own width; the row wraps only on
   a narrow screen. The back button is the shared blue icon (.toolbar-back-button). */

.paper-header-row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 0.75rem 1rem;
}

.paper-header-field {
    min-width: 0;
}

/* Each box fills its field, as in .entity-form-grid (the row is not one, so without this a text
   box keeps the 250px dense-form cap and a wide screen leaves a gap after it — and the type box's
   caret, which sits at the field's end, floats away from the box). */
.paper-header-row .form-control-sized,
.paper-header-row .form-control-sized .native-form-control,
.paper-header-row .form-control-sized .native-datetime-host {
    width: 100%;
    max-width: 100%;
    min-width: 0;
}

.paper-header-field--date { flex: 0 0 9.5rem; }
.paper-header-field--type { flex: 1.2 1 10rem; }
.paper-header-field--customer { flex: 1.6 1 13rem; }
.paper-header-field--id { flex: 0 0 8.5rem; }
.paper-header-field--phone { flex: 0 0 9.5rem; }
.paper-header-field--email { flex: 1.3 1 11rem; }

/* The picker button beside the name: its own width, the name box the rest (the document form
   has the same rule, scoped to its grid). */
.paper-header-row .user-form-field.document-icon-cell {
    width: auto;
    flex: 0 0 auto;
    align-items: flex-start;
}

.paper-header-field--customer > .user-form-field.flex-grow-1 {
    flex: 1 1 0;
    min-width: 0;
}

.paper-editor-body > .paper-editor {
    flex: 1 1 auto;
    min-height: 12rem;
    display: flex;
    flex-direction: column;
}

.paper-editor-body .paper-editor-page {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
}

/* The bank window: category, text, row actions; the text wraps (a bank paragraph runs to 330 characters). */
.paper-bank-picker-dialog {
    --entity-form-dialog-width: 900px;
}

.paper-bank-picker-dialog .paper-bank-picker-text {
    white-space: normal;
}

/* The paper's rich text editor (PaperParagraphsEditor + js/paperEditor.js): a formatting toolbar
   over one editable page, like the legacy Word body. Each paragraph is a .paper-p; the script sets
   its number (data-num) and its level (--paper-level, 0 for level 1). Word's list: 0.63 cm per
   level — in rem, so it follows the app's zoom. */
.paper-editor {
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius-form-box);
    background: var(--app-color-form-box-bg);
    overflow: hidden;
}

.paper-editor:focus-within {
    border-color: var(--app-primary);
}

.paper-editor-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2px;
    padding: 4px 6px;
    border-bottom: 1px solid var(--app-border);
    background: var(--app-surface-subtle);
}

.paper-editor-toolbar .paper-editor-button.is-active {
    background: var(--app-primary-soft);
    color: var(--app-primary);
}

.paper-editor-separator {
    width: 1px;
    align-self: stretch;
    margin: 4px 6px;
    background: var(--app-border);
}

.paper-paragraphs-hint {
    color: var(--app-muted);
    font-size: 0.8rem;
    margin-inline-start: 0.5rem;
}

.paper-editor-page {
    min-height: 16rem;
    max-height: min(52vh, 34rem);
    overflow-y: auto;
    padding: 0.75rem 1rem;
    color: var(--app-color-form-box-text);
    font-size: 11pt;   /* PaperMarkup.DefaultFontSize: a size picked in points reads true */
    line-height: 1.6;
    outline: none;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.paper-editor.is-disabled .paper-editor-page {
    background: var(--app-input-disabled-bg);
}

.paper-editor-page .paper-p {
    position: relative;
    margin: 0 0 0.25rem;
    padding-inline-start: calc(var(--paper-level, 0) * 1.6rem + 2.8rem);
}

/* The number in the gutter before the text, out of the selection. Wrapped in left-to-right marks
   (a bare «1.2.» in a right-to-left line shows «.1.2»); not direction: ltr, which would also turn
   the logical inset to the left side. */
.paper-editor-page .paper-p::before {
    content: "\200E" attr(data-num) "\200E";
    position: absolute;
    inset-inline-start: calc(var(--paper-level, 0) * 1.6rem);
    width: 2.5rem;
    color: var(--app-muted);
    font-variant-numeric: tabular-nums;
    user-select: none;
    pointer-events: none;
}

/* Word's numbering off: no number, and no gutter for it — the text starts at its level's edge
   (level 1 = the margin), as Word does when a paragraph leaves the list. */
.paper-editor-page .paper-p[data-numbered="false"]::before {
    content: none;
}

.paper-editor-page .paper-p[data-numbered="false"] {
    padding-inline-start: calc(var(--paper-level, 0) * 1.6rem);
}

/* The font size list (points, as the PDF): a small button showing the size at the caret, and its
   list right under it. The list lives inside the toolbar, whose presses never take the focus, so
   the selection stays in the page while a size is picked; the script opens it (.is-size-open). */
.paper-size-picker {
    position: relative;
}

.paper-size-button {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    min-width: 3.4rem;
    height: 28px;
    padding: 0 4px 0 8px;
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius-form-box);
    background: var(--app-color-form-box-bg);
    color: var(--app-color-form-box-text);
    font: inherit;
    font-size: 0.85rem;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
}

.paper-size-value {
    flex: 1;
    text-align: start;
}

.paper-size-list {
    display: none;
    position: absolute;
    inset-inline-start: 0;
    top: calc(100% + 2px);
    z-index: 20;
    min-width: 100%;
    max-height: 16rem;
    overflow-y: auto;
    padding: 4px 0;
    border: 1px solid var(--app-border);
    border-radius: var(--app-radius-form-box);
    background: var(--app-color-form-box-bg);
    box-shadow: var(--mud-elevation-8);
}

.paper-editor.is-size-open .paper-size-list {
    display: flex;
    flex-direction: column;
}

.paper-size-option {
    padding: 4px 12px;
    border: 0;
    background: none;
    color: var(--app-color-form-box-text);
    font: inherit;
    font-size: 0.85rem;
    text-align: start;
    cursor: pointer;
}

.paper-size-option:hover {
    background: var(--app-surface-subtle);
}

.paper-size-option.is-active {
    background: var(--app-primary-soft);
    color: var(--app-primary);
}

/* A free-text box with a suggestions caret (FormTextWithSuggestions): the caret sits INSIDE the
   box at its end, where every dropdown in the app draws its own, and the text stops short of it.
   The whole box is the menu's activator (so the list opens under the box), and the activator is a
   block as wide as the field. The caret centres on the box itself, not on box + error line. */
.form-text-suggestions.mud-menu,
.form-text-suggestions > .mud-menu-activator {
    display: block;
    width: 100%;
}

.form-text-suggestions > .mud-menu-activator {
    position: relative;
}

.form-text-suggestions .native-form-control {
    padding-inline-end: 2rem;
}

.form-text-suggestions-caret {
    position: absolute;
    inset-inline-end: 2px;
    top: calc(var(--entity-form-control-height, 2.1rem) / 2);
    transform: translateY(-50%);
}

.form-text-suggestions-btn.mud-icon-button {
    padding: 2px;
}

/* ===== Connected devices (WhatsApp bot Phase 3): מכשירים מחוברים ===== */
/* The «יצירת קוד» button lines up with the phone box, not its label (the grid anchors rows to the top). */
.connected-devices-create {
    align-self: flex-end;
}

/* The link code, shown once: large, spaced, monospace - read off the screen and typed into WhatsApp. */
.connected-devices-code {
    font-family: ui-monospace, Consolas, monospace;
    font-size: 1.75rem;
    font-weight: 600;
    letter-spacing: 0.25em;
    margin-bottom: 0.25rem;
}

/* The Wave "new API key" dialog (WaveApiKeyShownDialog) [developer-directed 2026-11-01].
   The copy button sits on the key box's own row, so it lines up with the box, not with its label.
   The warning is two lines, so its icon is centred on the whole message instead of the app-wide
   first-line baseline (.mud-alert-position above). */
.wave-key-shown-row {
    display: flex;
    align-items: center;
    gap: 8px;
}

.wave-key-shown-row > :first-child {
    flex: 1 1 auto;
    min-width: 0;
}

.wave-key-shown-alert .mud-alert-position {
    align-items: center;
}
