/* =============================================================================
   SalesMax Hybrid — design system
   -----------------------------------------------------------------------------
   One stylesheet, loaded identically by the web host and the desktop BlazorWebView,
   so both shells look the same offline. Everything is expressed as tokens first;
   components only ever reference tokens, never raw colour values.

   Layout model: a fixed dark navigation rail on the left, a sticky command bar
   across the top of the content, and a scrolling work surface beneath it. The rail
   is dark and the surface light so the eye separates "where am I" from "what am I
   doing" without needing borders everywhere.
   ============================================================================= */

:root {
    /* --- Brand ramp -------------------------------------------------------------------
       Derived from the Office White theme, not from our own hexes. That theme is
       Bootstrap-based and publishes its palette as --bs-*, so referencing those makes our
       mark, rail and progress fills follow the theme rather than sitting beside it.

       This replaced a ramp derived from Fluent's --DS-primary-*. The whole application moved
       onto one theme deliberately: a half-migrated app, where the DevExpress controls take a
       theme and our own chrome keeps its old colour, is worse than either theme on its own.

       The literals are fallbacks for the instant before the theme sheet loads. They are Office
       White's own steps, so the fallback matches rather than flashing a different colour.

       SUPERSEDED — do not edit these six in isolation. A SECOND :root block further down this
       file (search "Brand ramp — FOLLOWS THE ACTIVE DEVEXPRESS THEME") redeclares every one of
       them, and being later with equal specificity it is the one that wins. These survive only
       as the pre-theme fallback. When that block held teal literals instead of this chain, the
       entire application's chrome was pinned to teal while the DevExpress controls recoloured —
       so any change here must be made THERE too, or it does nothing at all. --- */
    --erp-brand-50:  var(--bs-primary-bg-subtle,     #ffe3cd);
    --erp-brand-100: var(--bs-primary-bg-subtle,     #ffd4b3);
    --erp-brand-300: var(--bs-primary-border-subtle, #ffb87f);
    --erp-brand-500: var(--bs-primary, var(--DS-primary-90, #fe7109));   /* the theme's primary */
    --erp-brand-600: var(--bs-link-hover-color, var(--DS-primary-100, #cb5a07));
    --erp-brand-700: var(--bs-primary-text-emphasis, var(--DS-primary-120, #662d04));

    /* --- Neutral ramp: surfaces and text, from the theme's own greys. --- */
    --erp-ink-900: var(--bs-emphasis-color, var(--DS-color-content-neutral-default-rest, #111111));
    --erp-ink-800: var(--bs-body-color, var(--DS-color-content-neutral-default-rest, #212529));
    --erp-ink-700: var(--bs-secondary-color, var(--DS-color-content-neutral-subdued-rest, #495057));
    --erp-ink-500: var(--bs-tertiary-color,  #6c757d);
    --erp-ink-400: var(--bs-gray-400,        #ced4da);
    --erp-ink-200: var(--bs-gray-300,        #e5e5e5);
    --erp-ink-100: var(--bs-gray-200,        #f2f2f2);
    --erp-ink-50:  var(--bs-gray-100,        #f7f8f9);

    /* --- Semantic accents. Money in, money out, warnings.
           Taken from the theme so a success badge is the same green as a success button. --- */
    --erp-positive:      var(--bs-success, var(--DS-color-content-success-default-rest, #10893e));
    --erp-positive-soft: var(--bs-success-bg-subtle,    #cfe7d8);
    --erp-negative:      var(--bs-danger, var(--DS-color-content-danger-default-rest, #e81123));
    --erp-negative-soft: var(--bs-danger-bg-subtle,     #facfd3);
    --erp-warning:       var(--bs-warning, var(--DS-color-content-warning-default-rest, #ff9c00));
    --erp-warning-soft:  var(--bs-warning-bg-subtle,    #ffebcc);
    --erp-info:          var(--bs-info, var(--DS-color-content-info-default-rest, #0078d4));
    --erp-info-soft:     var(--bs-info-bg-subtle,       #cce4f6);

    /* --- Applied roles. Components use these, not the ramps above. --- */
    --erp-surface:        var(--bs-body-bg, var(--DS-color-surface-neutral-default-rest, #ffffff));
    --erp-surface-raised: var(--bs-body-bg, var(--DS-color-surface-neutral-default-rest, #ffffff));

    /* The page behind the cards. COMPUTED, not taken from a theme variable, because no theme
       variable is trustworthy here: Blazing Dark publishes --bs-secondary-bg as #e5e5e5 and
       --bs-tertiary-bg as #f7f8f9 — both near-white — while its body is #37353d. Binding the
       page to either produced a white sheet behind every grid under the dark theme, which is
       exactly what it looked like.

       Mixing a little of the TEXT colour into the BACKGROUND colour is self-correcting: on a
       light theme (white page, dark text) it darkens slightly; on a dark theme (dark page, light
       text) it lightens slightly. The card always sits a step apart from the page, in the right
       direction, without depending on any variable being consistent. */
    --erp-surface-sunken: color-mix(in srgb,
                              var(--bs-body-bg, var(--DS-color-surface-neutral-default-rest, #ffffff)) 94%,
                              var(--bs-body-color, var(--DS-color-content-neutral-default-rest, #212529)));
    --erp-border:         var(--bs-border-color, var(--DS-color-border-neutral-default-rest, #e5e5e5));
    /* --bs-secondary-border-subtle resolves to plain WHITE in this theme, which made every
       "strong" border invisible on a white card. gray-400 is a genuine step up from
       --bs-border-color (#e5e5e5) and is what a heavier rule should be. */
    --erp-border-strong:  var(--bs-gray-400, #ced4da);
    --erp-text:           var(--bs-body-color, var(--DS-color-content-neutral-default-rest, #212529));
    --erp-text-muted:     var(--bs-secondary-color, var(--DS-color-content-neutral-subdued-rest, #6c757d));
    --erp-text-inverse:   #ffffff;

    /* The rail is the largest block of colour on screen. Office White is a light, businesslike
       theme, so the rail takes the theme's dark neutral rather than a tint of the orange
       primary — a full-height orange panel would dominate every screen in the application. */
    /* The rail carries the THEME'S OWN COLOUR, not a shared neutral.
       --bs-primary-text-emphasis is the theme's primary darkened for text, so it is both dark
       enough for white labels and unmistakably that theme's hue: dark orange under Office White,
       deep purple under Blazing Berry, indigo under Purple, violet under Blazing Dark.

       It was --bs-dark, which is the same #212529 in THREE of the four themes — so switching
       theme left the largest block of colour on screen completely unchanged, which is exactly
       what it looked like. A neutral cannot express a theme. */
    --erp-rail-bg: var(--bs-primary-text-emphasis, var(--DS-primary-150, #662d04));
    /* The active item carries the primary, at low alpha so it reads as a highlight rather
       than a solid orange block. color-mix because the step is a variable, not a literal. */
    --erp-rail-bg-active: color-mix(in srgb,
                              var(--bs-primary, var(--DS-primary-80, #fe7109)) 26%, transparent);
    --erp-rail-text: rgba(255, 255, 255, 0.62);
    --erp-rail-text-active: #ffffff;
    /* Hover, and the dimmer label/count text. White at alpha rather than a fixed colour: these
       were #dfe5f3 and #5c6787, blue-greys left over from when the application ran on Fluent
       blue. Against an orange theme they read as a faint blue cast on every nav row. Alpha-white
       is correct on a dark rail under any theme. */
    --erp-rail-text-hover: rgba(255, 255, 255, 0.86);
    --erp-rail-text-dim: rgba(255, 255, 255, 0.45);
    /* The rail's own hairlines and hover wash, tokenised for the same reason. */
    --erp-rail-wash: rgba(255, 255, 255, 0.05);
    --erp-rail-line: rgba(255, 255, 255, 0.08);

    /* --- Shape: from the theme, so our cards match its buttons and inputs.
           Office White is squarer than Fluent; taking these from it is what makes the
           whole page agree rather than rounded cards holding square controls. --- */
    --erp-radius:    var(--bs-border-radius,    0.25rem);
    --erp-radius-sm: var(--bs-border-radius-sm, 0.2rem);
    --erp-radius-lg: var(--bs-border-radius-lg, 0.3rem);

    /* --- Typography: the theme's own family, so our chrome and its controls agree. --- */
    --erp-font: var(--bs-body-font-family, "Segoe UI", system-ui, -apple-system, sans-serif);

    /* --- Shape and depth. --- */
    --erp-radius-sm: 6px;
    --erp-radius: 10px;
    --erp-radius-lg: 14px;
    --erp-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06);
    --erp-shadow: 0 1px 3px rgba(15, 23, 42, 0.08), 0 6px 16px -6px rgba(15, 23, 42, 0.10);
    --erp-shadow-lg: 0 10px 32px -8px rgba(15, 23, 42, 0.20);

    /* --- Rhythm. --- */
    --erp-rail-w: 236px;
    --erp-topbar-h: 60px;
    --erp-modbar-h: 48px;
    --erp-gap: 16px;

    --erp-font: "Segoe UI", -apple-system, "Inter", system-ui, sans-serif;
    /* Tabular figures so money columns align digit-for-digit down a grid. */
    --erp-font-num: "Segoe UI", "SF Mono", Consolas, sans-serif;
}

/* NO DARK PALETTE, deliberately.
   The application runs on one theme — DevExpress Office White — and that theme is light only.
   A dark palette here would darken our surfaces, rail and text while every DevExpress control
   stayed light: dark cards holding white input boxes. That mismatch is exactly what having two
   sets of behaviour produces, and it is why the previous dark blocks were removed rather than
   kept. If dark is wanted later it means moving the whole application to Blazing Dark, not
   adding a second palette here. */

/* ---------------------------------------------------------------- base ---- */

html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    font-family: var(--erp-font);
    font-size: 14px;
    color: var(--erp-text);
    background: var(--erp-surface-sunken);
    -webkit-font-smoothing: antialiased;
}

* { box-sizing: border-box; }

h1, h2, h3, h4 { margin: 0; font-weight: 600; letter-spacing: -0.01em; }

/* --------------------------------------------------------------- shell ---- */

.erp-shell {
    display: flex;
    min-height: 100vh;
}

.erp-rail {
    width: var(--erp-rail-w);
    flex: 0 0 var(--erp-rail-w);
    background: var(--erp-rail-bg);
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    height: 100vh;
    z-index: 30;
}

.erp-rail__brand {
    height: var(--erp-topbar-h);
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 18px;
    flex: 0 0 var(--erp-topbar-h);
    border-bottom: 1px solid var(--erp-rail-line);
}

.erp-rail__mark {
    width: 30px;
    height: 30px;
    border-radius: 8px;
    display: grid;
    place-items: center;
    font-weight: 700;
    font-size: 13px;
    color: var(--erp-text-inverse);
    background: linear-gradient(135deg, var(--erp-brand-500), var(--erp-brand-700));
    flex: 0 0 auto;
}

.erp-rail__name {
    color: var(--erp-rail-text-active);
    font-weight: 600;
    font-size: 14px;
    line-height: 1.1;
}

.erp-rail__sub {
    color: var(--erp-rail-text);
    font-size: 11px;
}

.erp-rail__nav {
    padding: 14px 10px;
    overflow-y: auto;
    flex: 1 1 auto;
}

.erp-rail__section {
    color: var(--erp-rail-text-dim);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    padding: 14px 10px 6px;
}

.erp-nav-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    margin-bottom: 2px;
    border-radius: var(--erp-radius-sm);
    color: var(--erp-rail-text);
    text-decoration: none;
    font-size: 13.5px;
    font-weight: 500;
    cursor: pointer;
    border-left: 2px solid transparent;
    transition: background 0.12s ease, color 0.12s ease;
}

.erp-nav-item:hover {
    background: var(--erp-rail-wash);
    color: var(--erp-rail-text-hover);
}

/* Blazor's NavLink adds .active on the matching route. */
.erp-nav-item.active {
    background: var(--erp-rail-bg-active);
    color: var(--erp-rail-text-active);
    border-left-color: var(--erp-brand-500);
}

.erp-nav-item i { font-size: 15px; width: 18px; text-align: center; }

/* --- Module groups -------------------------------------------------------
   The rail carries all 93 pages, so modules collapse. A group header is a real
   <button> (keyboard/screen-reader accessible), styled to sit flush with the
   links it contains. */

.erp-nav-group {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px 10px;
    margin-bottom: 2px;
    border: 0;
    border-radius: var(--erp-radius-sm);
    background: transparent;
    color: var(--erp-rail-text);
    font: inherit;
    font-size: 13.5px;
    font-weight: 500;
    text-align: left;
    cursor: pointer;
    transition: background 0.12s ease, color 0.12s ease;
}

.erp-nav-group:hover { background: var(--erp-rail-wash); color: var(--erp-rail-text-hover); }
.erp-nav-group--open { color: var(--erp-rail-text-hover); }
.erp-nav-group > i:first-child { font-size: 15px; width: 18px; text-align: center; flex: 0 0 18px; }
.erp-nav-group__name { flex: 1 1 auto; min-width: 0; }

/* How many of the module's pages are actually navigable. */
.erp-nav-group__count {
    font-size: 10px;
    font-weight: 600;
    color: var(--erp-rail-text-dim);
    background: var(--erp-rail-wash);
    border-radius: 999px;
    padding: 1px 6px;
    flex: 0 0 auto;
    font-variant-numeric: tabular-nums;
}

.erp-nav-group__chev {
    font-size: 10px !important;
    width: auto !important;
    flex: 0 0 auto;
    opacity: 0.5;
    transition: transform 0.15s ease;
}

.erp-nav-group--open .erp-nav-group__chev { transform: rotate(180deg); }

/* Children hang off a hairline so the group reads as one block. */
.erp-nav-children {
    margin: 0 0 4px 19px;
    padding-left: 10px;
    border-left: 1px solid var(--erp-rail-line);
}

.erp-nav-item--child {
    padding: 6px 10px;
    font-size: 13px;
    font-weight: 400;
    border-left: 2px solid transparent;
}

/* Pages that exist in SalesMax but are not built in this app: shown, never clickable. */
.erp-nav-item--soon {
    opacity: 0.42;
    cursor: default;
}

.erp-nav-item--soon:hover { background: transparent; color: var(--erp-rail-text); }

.erp-soon {
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    border: 1px solid currentColor;
    border-radius: 3px;
    padding: 0 3px;
    margin-left: 6px;
    opacity: 0.75;
    vertical-align: 1px;
}

.erp-rail__foot {
    padding: 12px 16px;
    border-top: 1px solid var(--erp-rail-line);
    color: var(--erp-rail-text);
    font-size: 11px;
}

/* --------------------------------------------------------------- main ----- */

.erp-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.erp-topbar {
    height: var(--erp-topbar-h);
    flex: 0 0 var(--erp-topbar-h);
    background: var(--erp-surface);
    border-bottom: 1px solid var(--erp-border);
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 0 22px;
    position: sticky;
    top: var(--erp-modbar-h);
    z-index: 20;
}

.erp-topbar__title { font-size: 15px; font-weight: 600; }
.erp-topbar__crumb { color: var(--erp-text-muted); font-size: 12px; }
.erp-topbar__spacer { flex: 1 1 auto; }

.erp-content {
    padding: 22px;
    flex: 1 1 auto;
}

/* ------------------------------------------------ two-level navigation ----- */
/* A dark module bar across the top (primary nav); the rail below shows only the
   pages of the active module. One hamburger drives the rail: collapse on desktop,
   drawer on mobile — see the layout's _navOpen note. */

/* The active-module header that sits above the rail's page list. */
.erp-rail__modhead {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 14px 16px 8px;
    color: var(--erp-rail-text-active);
    font-weight: 600;
    font-size: 13px;
    letter-spacing: 0.01em;
}
.erp-rail__modhead i { font-size: 16px; color: var(--erp-brand-300); }

/* A page link inside the active module — no icon, so the list reads as a clean column. */
.erp-nav-item--flat { padding-left: 26px; }

/* The module bar. */
.erp-modulebar {
    height: var(--erp-modbar-h);
    flex: 0 0 var(--erp-modbar-h);
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 0 10px;
    background: var(--erp-rail-bg);
    border-bottom: 1px solid var(--erp-rail-line);
    position: sticky;
    top: 0;
    z-index: 25;
}

.erp-navtoggle {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    border: none;
    border-radius: 8px;
    background: transparent;
    color: var(--erp-rail-text-hover);
    cursor: pointer;
    display: grid;
    place-items: center;
    font-size: 19px;
    transition: background 0.14s ease, color 0.14s ease;
}
.erp-navtoggle:hover { background: var(--erp-rail-wash); color: var(--erp-rail-text-active); }

/* A compact brand shown in the bar only when the rail (which carries the full brand) is away. */
.erp-modulebar__brand { display: none; align-items: center; }
.erp-modulebar__mark {
    width: 26px; height: 26px;
    border-radius: 7px;
    display: grid; place-items: center;
    font-weight: 700; font-size: 11px;
    color: var(--erp-text-inverse);
    background: linear-gradient(135deg, var(--erp-brand-500), var(--erp-brand-700));
}

.erp-modulebar__tabs {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    gap: 3px;
    height: 100%;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: thin;
    scrollbar-color: var(--erp-rail-line) transparent;
}
.erp-modulebar__tabs::-webkit-scrollbar { height: 5px; }
.erp-modulebar__tabs::-webkit-scrollbar-thumb { background: var(--erp-rail-line); border-radius: 999px; }

.erp-modtab {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    height: 34px;
    padding: 0 13px;
    border: none;
    border-radius: 8px;
    background: transparent;
    color: var(--erp-rail-text);
    font-size: 13px;
    font-weight: 500;
    white-space: nowrap;
    cursor: pointer;
    transition: background 0.14s ease, color 0.14s ease;
}
/* Icons carry the brand accent so the bar reads as colourful rather than a row of grey glyphs.
   The token follows the DevExpress theme, so this stays on-brand if the theme changes. */
.erp-modtab i { font-size: 15px; color: var(--erp-brand-300); }
.erp-modtab:hover { background: var(--erp-rail-wash); color: var(--erp-rail-text-hover); }
.erp-modtab.active {
    background: var(--erp-rail-bg-active);
    color: var(--erp-rail-text-active);
    font-weight: 600;
}
/* On the selected tab the icon goes solid white, for contrast against the filled background. */
.erp-modtab.active i { color: var(--erp-rail-text-active); }

/* The pin (star) at the end of the module bar. */
.erp-hotpin {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    border-radius: 8px;
    color: var(--erp-rail-text);
    font-size: 16px;
    transition: background 0.14s ease, color 0.14s ease;
}
.erp-hotpin:hover { background: var(--erp-rail-wash); color: var(--erp-rail-text-active); }

/* Quick access — the pinned stack near the bottom of the rail, filling bottom-to-top. */
.erp-hot {
    border-top: 1px solid var(--erp-rail-line);
    padding: 10px 10px 6px;
    flex: 0 0 auto;
}
.erp-hot__label {
    color: var(--erp-rail-text-dim);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    padding: 0 6px 6px;
}
.erp-hot__stack {
    display: flex;
    flex-direction: column-reverse;   /* newest is first in the DOM, so it lands at the bottom */
    gap: 2px;
}
.erp-hot__item {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    padding: 7px 10px;
    border-radius: 8px;
    color: var(--erp-rail-text);
    font-size: 12.5px;
    text-align: left;
    transition: background 0.14s ease, color 0.14s ease;
}
.erp-hot__item i { font-size: 14px; width: 16px; text-align: center; flex: 0 0 16px; }
.erp-hot__item:hover { background: var(--erp-rail-wash); color: var(--erp-rail-text-hover); }
.erp-hot__item.active { background: var(--erp-rail-bg-active); color: var(--erp-rail-text-active); }
.erp-hot__name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Theme (skin) switcher — a live dot that always shows the ACTIVE theme's colour, and a per-theme
   swatch in the dropdown list. The live dot uses --erp-brand-500, which erp.css derives from the
   loaded skin, so it re-tints the instant a new theme is chosen. */
.erp-themeswitch { display: flex; align-items: center; gap: 9px; }
.erp-themeswitch__live {
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--erp-brand-500);
    border: 2px solid rgba(255, 255, 255, 0.55);
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
    transition: background 0.15s ease;
}
.erp-themeswitch__combo { flex: 1 1 auto; min-width: 0; }

/* The button that opens the catalogue, in place of the old 150px combo. It carries the CURRENT
   theme's name so the utilities menu still answers "what am I on?" without opening anything. */
.erp-themeswitch__open { display: flex; align-items: center; gap: 8px; flex: 1 1 auto;
                         min-width: 0; padding: 4px 8px; border-radius: 7px;
                         border: 1px solid var(--erp-border); background: var(--erp-surface);
                         color: inherit; font-size: 13px; cursor: pointer; }
.erp-themeswitch__open:hover { border-color: var(--erp-brand-500); }
.erp-themeswitch__name { flex: 1 1 auto; min-width: 0; overflow: hidden;
                         text-overflow: ellipsis; white-space: nowrap; text-align: left; }

/* ── THE THEME CATALOGUE ────────────────────────────────────────────────────────────────────
   Laid out as paint chips rather than a list: the whole point is that a colour is chosen by
   looking, and twenty-six names in a scrolling drop-down is the one shape that prevents it. */
.erp-themecat__hint { font-size: 13px; color: var(--erp-text-muted); margin: 0 0 14px; }
.erp-themecat__group { font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
                       color: var(--erp-text-muted); margin: 16px 0 8px; padding-bottom: 5px;
                       border-bottom: 1px solid var(--erp-border); }
.erp-themecat__group:first-of-type { margin-top: 0; }

/* auto-fill rather than a fixed count, so the same grid works in the popup and on a phone */
.erp-themecat__grid { display: grid; gap: 10px;
                      grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); }

.erp-themecat__tile { display: flex; flex-direction: column; align-items: stretch; gap: 0;
                      padding: 0; overflow: hidden; border-radius: 9px; cursor: pointer;
                      border: 2px solid var(--erp-border); background: var(--erp-surface);
                      transition: border-color .12s, transform .12s; }
.erp-themecat__tile:hover { border-color: var(--erp-brand-500); transform: translateY(-1px); }
.erp-themecat__tile.is-current { border-color: var(--erp-brand-500); }

/* The chip is the theme's own accent. A dark theme gets a darker plate under it so the tile
   shows which WAY the theme goes, not only which hue — the two things people are choosing
   between are rarely the same question. */
.erp-themecat__chip { display: flex; align-items: center; justify-content: center;
                      height: 54px; color: #fff; font-size: 20px; }
.erp-themecat__tile.is-dark .erp-themecat__chip { box-shadow: inset 0 -14px 0 rgba(0,0,0,.35); }
.erp-themecat__label { padding: 7px 9px 8px; font-size: 12.5px; line-height: 1.25;
                       text-align: left; }
.erp-themecat__tile.is-current .erp-themecat__label { font-weight: 600; }

.erp-themecat__foot { display: flex; align-items: center; justify-content: space-between;
                      gap: 14px; width: 100%; }
.erp-themecat__current { font-size: 13px; color: var(--erp-text-muted); }

.erp-themeswitch__swatch {
    display: inline-block;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    margin-right: 8px;
    vertical-align: -1px;
    border: 1px solid rgba(0, 0, 0, 0.18);
}

/* Backdrop is a mobile-only device; never shown on desktop. */
.erp-backdrop { display: none; }

/* Desktop: the hamburger collapses the rail to give the content the full width. With the rail's
   full brand gone, the bar shows the compact one so identity never disappears. */
.erp-shell.nav-open .erp-rail { display: none; }
.erp-shell.nav-open .erp-modulebar__brand { display: flex; }

/* Mobile: the rail becomes an off-canvas drawer the hamburger slides in. */
@media (max-width: 980px) {
    .erp-rail {
        position: fixed;
        left: 0;
        top: 0;
        height: 100vh;
        transform: translateX(-100%);
        transition: transform 0.22s ease;
        z-index: 60;
        box-shadow: var(--erp-shadow-lg);
    }
    /* Override the desktop collapse rule: on mobile, nav-open SHOWS the drawer. */
    .erp-shell.nav-open .erp-rail { display: flex; transform: translateX(0); }

    .erp-backdrop {
        position: fixed;
        inset: 0;
        background: rgba(15, 23, 42, 0.45);
        z-index: 50;
    }
    .erp-shell.nav-open .erp-backdrop { display: block; }

    .erp-modulebar__brand { display: flex; }
    /* Icons-only tabs to fit narrow screens — IN THE MODULE BAR ONLY. .erp-modtab is also
       worn by the Role Centre's group tabs (ModuleActions), which have no icons, so the
       unscoped rule left a row of blank pills on every phone. */
    .erp-modulebar__tabs .erp-modtab span { display: none; }
    .erp-modulebar__tabs .erp-modtab { padding: 0 10px; }
}

/* --------------------------------------------------------------- cards ---- */

/* Why these are CSS and not DevExpress components — checked against 25.1, 2026-07-18.
   Reopening this costs a day, so the findings are here rather than in a commit message.

   .erp-card: DevExpress 25.1 has NO card/panel primitive. DxCardView does not exist at all
   (it was assumed to, and counted as a gap — it is not one). The nearest thing, DxFormLayoutGroup,
   carries form semantics and is right for a form; wrapping grids, dashboards and reports in one
   would misuse it. A styled container div IS the correct primitive here.

   .erp-toolbar: DxToolbar is documented as "an adaptive BUTTON-based interface" and dispatches by
   item Name through a single ItemClick. Of the twelve toolbars in this app, eleven mix buttons
   with labelled date editors, combos and text boxes, which it is not built for; the twelfth is a
   pager, where adaptive overflow — DxToolbar's whole point — would collapse Previous/Next into a
   menu. Converting any of them would be a downgrade.

   The DevExpress adoption that WAS worth doing is done: SizeMode.Small set globally, the Fluent
   light/dark sheet following the theme, and Settings rebuilt on DxFormLayout, which deleted 104
   lines of this file and the whole staircase class of bug. */

.erp-card {
    background: var(--erp-surface-raised);
    border: 1px solid var(--erp-border);
    border-radius: var(--erp-radius-lg);
    box-shadow: var(--erp-shadow-sm);
}

.erp-card__head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px 18px;
    border-bottom: 1px solid var(--erp-border);
}

.erp-card__title { font-size: 13.5px; font-weight: 600; }
.erp-card__hint { font-size: 11.5px; color: var(--erp-text-muted); }
.erp-card__body { padding: 18px; }
.erp-card__body--flush { padding: 0; }

/* --------------------------------------------------------------- tiles ---- */

.erp-kpi-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: var(--erp-gap);
    margin-bottom: var(--erp-gap);
}

.erp-tile {
    background: var(--erp-surface-raised);
    border: 1px solid var(--erp-border);
    border-radius: var(--erp-radius-lg);
    padding: 16px 18px;
    box-shadow: var(--erp-shadow-sm);
    position: relative;
    overflow: hidden;
}

/* A 3px accent stripe carries the tile's meaning without colouring the whole card. */
.erp-tile::before {
    content: "";
    position: absolute;
    inset: 0 auto 0 0;
    width: 3px;
    background: var(--erp-tile-accent, var(--erp-brand-500));
}

.erp-tile--positive { --erp-tile-accent: var(--erp-positive); }
.erp-tile--negative { --erp-tile-accent: var(--erp-negative); }
.erp-tile--warning  { --erp-tile-accent: var(--erp-warning); }
.erp-tile--info     { --erp-tile-accent: var(--erp-info); }

.erp-tile__label {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--erp-text-muted);
    display: flex;
    align-items: center;
    gap: 6px;
}

.erp-tile__value {
    font-family: var(--erp-font-num);
    font-variant-numeric: tabular-nums;
    font-size: 25px;
    font-weight: 650;
    letter-spacing: -0.02em;
    margin-top: 8px;
    line-height: 1.15;
}

.erp-tile__meta {
    margin-top: 5px;
    font-size: 11.5px;
    color: var(--erp-text-muted);
}

.erp-tile__cur {
    font-size: 13px;
    font-weight: 600;
    color: var(--erp-text-muted);
    margin-right: 3px;
}

/* -------------------------------------------------------------- badges ---- */

.erp-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.7;
    white-space: nowrap;
}

.erp-badge--paid      { background: var(--erp-positive-soft); color: var(--erp-positive); }
.erp-badge--unpaid    { background: var(--erp-negative-soft); color: var(--erp-negative); }
.erp-badge--partial   { background: var(--erp-warning-soft);  color: var(--erp-warning); }
.erp-badge--posted    { background: var(--erp-info-soft);     color: var(--erp-info); }
/* Applied roles, not the ink ramp. --erp-ink-* is declared once on :root and is NOT redeclared
   in either dark block, so building these two from it painted pale-grey pills on a dark card —
   the loudest thing on the screen, attached to the least important information. These are the
   most common badges in the app (every unposted document wears one), so they were the most
   visible instance of that bug. A border carries the shape now that the fill recedes. */
.erp-badge--draft {
    background: var(--erp-surface-sunken);
    color: var(--erp-text-muted);
    border: 1px solid var(--erp-border);
}
.erp-badge--cancelled {
    background: var(--erp-surface-sunken);
    color: var(--erp-text-muted);
    border: 1px solid var(--erp-border);
    text-decoration: line-through;
}

/* Settings badges. They live HERE with the other modifiers, not beside the settings rules —
   they were in that block once, and rewriting it deleted them out from under the two pages
   still using them. A modifier belongs with its base class. */

/* "shared default": informational, not a warning. It wore erp-badge--partial (amber) before,
   which is the attention colour, applied to a setting's ordinary resting state. */
.erp-badge--inherit { background: var(--erp-info-soft); color: var(--erp-info); }

/* "default": the quietest thing on the row. Nothing is wrong with an unset setting. */
.erp-badge--quiet {
    background: var(--erp-surface-sunken);
    color: var(--erp-text-muted);
    border: 1px solid var(--erp-border);
}

/* --------------------------------------------------------------- money ---- */

.erp-num {
    font-family: var(--erp-font-num);
    font-variant-numeric: tabular-nums;
    text-align: right;
    display: block;
}

.erp-num--strong { font-weight: 650; }
.erp-num--muted { color: var(--erp-text-muted); }
.erp-num--due { color: var(--erp-negative); font-weight: 600; }

/* --------------------------------------------------------- invoice form --- */

/* Two columns on a wide screen: the document on the left, the running totals on
   the right where they stay visible while lines are entered. Collapses to one
   column under 1200px so the desktop window can be narrow. */
.erp-invoice-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: var(--erp-gap);
    align-items: start;
}

@media (max-width: 1200px) {
    .erp-invoice-grid { grid-template-columns: minmax(0, 1fr); }
}

.erp-totals { position: sticky; top: calc(var(--erp-topbar-h) + 14px); }

.erp-total-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 12px;
    padding: 7px 0;
    font-size: 13px;
}

.erp-total-row__label { color: var(--erp-text-muted); }

.erp-total-row__value {
    font-family: var(--erp-font-num);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

.erp-total-row--grand {
    border-top: 2px solid var(--erp-border-strong);
    margin-top: 8px;
    padding-top: 12px;
    font-size: 15px;
}

.erp-total-row--grand .erp-total-row__label { color: var(--erp-text); font-weight: 600; }

.erp-total-row--grand .erp-total-row__value {
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -0.02em;
}

/* The Sales/Purchase switch — the single most important control on the form, so
   it reads as a segmented control rather than a dropdown. */
.erp-modeswitch {
    display: inline-flex;
    background: var(--erp-surface-sunken);
    border: 1px solid var(--erp-border);
    border-radius: var(--erp-radius);
    padding: 3px;
    gap: 3px;
}

.erp-modeswitch__btn {
    border: 0;
    background: transparent;
    color: var(--erp-text-muted);
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    padding: 6px 16px;
    border-radius: 7px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    transition: background 0.12s ease, color 0.12s ease;
}

.erp-modeswitch__btn:hover:not(:disabled) { color: var(--erp-text); }

.erp-modeswitch__btn--on {
    background: var(--erp-surface-raised);
    color: var(--erp-text);
    box-shadow: var(--erp-shadow-sm);
}

.erp-modeswitch__btn--on.erp-modeswitch__btn--sales { color: var(--erp-positive); }
.erp-modeswitch__btn--on.erp-modeswitch__btn--purchase { color: var(--erp-info); }
.erp-modeswitch__btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* --------------------------------------------------------------- forms ---- */

.erp-field { margin-bottom: 13px; }

.erp-field__label {
    display: block;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--erp-text-muted);
    margin-bottom: 5px;
}

.erp-field__label--req::after {
    content: " *";
    color: var(--erp-negative);
}

.erp-field-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 0 var(--erp-gap);
}

.erp-readout {
    font-family: var(--erp-font-num);
    font-size: 15px;
    font-weight: 650;
    letter-spacing: 0.01em;
    padding: 7px 0;
}

.erp-readout--muted { color: var(--erp-text-muted); font-weight: 500; }

/* ------------------------------------------------------------ messages ---- */

.erp-alert {
    display: flex;
    gap: 10px;
    padding: 11px 14px;
    border-radius: var(--erp-radius);
    font-size: 13px;
    margin-bottom: var(--erp-gap);
    border: 1px solid transparent;
}

.erp-alert--error   { background: var(--erp-negative-soft); border-color: var(--erp-negative); color: var(--erp-negative); }
.erp-alert--success { background: var(--erp-positive-soft); border-color: var(--erp-positive); color: var(--erp-positive); }
.erp-alert--warning { background: var(--erp-warning-soft);  border-color: var(--erp-warning);  color: var(--erp-warning); }
.erp-alert--info    { background: var(--erp-info-soft);     border-color: var(--erp-info);     color: var(--erp-info); }
.erp-alert ul { margin: 4px 0 0; padding-left: 18px; }

/* Validation summary inside an editor popup. Sticks to the top of the scrolling body so it stays
   in view while the operator scrolls down to the offending field — on a 1366x768 screen the tall
   editors scroll, and an error that scrolls away is an error nobody acts on. */
.erp-editor__error {
    position: sticky;
    top: 0;
    z-index: 5;
    /* Opaque: rows scrolling underneath must not show through the banner. */
    background: var(--erp-negative-soft);
    box-shadow: 0 2px 6px rgb(0 0 0 / 12%);
}

/* Marks the field the summary is complaining about. Applied to the DevExpress editor's own
   wrapper, so it outlines whatever control the page used (text box, combo, spin edit) without
   each page needing to know which. */
.erp-invalid .dxbl-edit,
.erp-invalid .dxbl-text-edit,
.erp-invalid.dxbl-edit {
    border-color: var(--erp-negative) !important;
    box-shadow: 0 0 0 2px var(--erp-negative-soft);
}

/* The caption above an invalid field, so the eye lands on it without reading the whole form. */
.erp-field__label--err { color: var(--erp-negative); font-weight: 600; }

.erp-toolbar {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.erp-toolbar__spacer { flex: 1 1 auto; }

.erp-empty {
    padding: 40px 20px;
    text-align: center;
    color: var(--erp-text-muted);
}

.erp-empty i { font-size: 30px; opacity: 0.4; display: block; margin-bottom: 10px; }

/* ---------------------------------------- DevExpress overrides: REMOVED ---- */

/* This block used to restyle the DevExpress grid — 11px uppercase headers with
   !important, and 13px body cells. It has been removed so grids render exactly as the
   native theme draws them, which is what the DevExpress demos show.

   WHAT THIS CHANGES, so it is not a surprise: grid rows get TALLER. The old rules,
   together with SizeMode.Small, produced the compact row height previously approved as
   "perfect size". Native theme density is looser than that.

   TO PUT IT BACK: re-add two rules targeting the grid table — headers at 11px, weight 600,
   uppercase, muted colour; body cells at 13px — and set SizeMode.Small again in
   ServiceRegistration.cs. Written as prose rather than as CSS on purpose: as literal
   declarations it reads as live rules, both to a person skimming and to the guard in
   Tools/ThemeTest that scans this file for exactly that pattern. */

/* Kept: this removes a doubled border where a grid sits inside a card. It is layout
   correction, not restyling — the theme still draws the grid itself. */
.erp-card .dxbl-grid { border: 0; border-radius: 0; }

.erp-chart { width: 100%; }

/* ------------------------------------------------- horizontal bar rows ---- */
/* Used where a total splits across named buckets and the share matters as
   much as the number — Projects' hours-by-job breakdown. Deliberately not a
   chart: the labels are long free text and a legend would hide them. */

.erp-bar-row {
    display: grid;
    grid-template-columns: minmax(160px, 1.4fr) minmax(80px, 2fr) minmax(190px, auto);
    align-items: center;
    gap: 12px;
    padding: 7px 0;
    /* Applied role, not the ink ramp: --erp-ink-* never flips, so this drew a near-white
       hairline across a dark card. */
    border-bottom: 1px solid var(--erp-border);
}
.erp-bar-row:last-child { border-bottom: 0; }

.erp-bar-row__label {
    font-size: 12.5px;
    color: var(--erp-text);
    overflow-wrap: anywhere;
}

.erp-bar-row__track {
    height: 8px;
    border-radius: 999px;
    /* The unfilled part of the meter. --erp-surface-sunken is the role for a recessed
       surface and flips with the theme; --erp-ink-100 stayed near-white on a dark card. */
    background: var(--erp-surface-sunken);
    overflow: hidden;
}

.erp-bar-row__fill {
    height: 100%;
    border-radius: 999px;
    background: var(--erp-brand-500);
    min-width: 2px;
}

.erp-bar-row__value {
    font-size: 12px;
    text-align: right;
    white-space: nowrap;
}

/* Long labels and a right-aligned value fight for room on a phone; stack. */
@media (max-width: 720px) {
    .erp-bar-row { grid-template-columns: 1fr; gap: 4px; }
    .erp-bar-row__value { text-align: left; }
}

/* ------------------------------------------------------ progress meter ---- */
/* Inline percentage inside a grid cell (task progress). */

.erp-progress {
    height: 6px;
    border-radius: 999px;
    /* Same as .erp-bar-row__track: the trough must recede in BOTH themes. */
    background: var(--erp-surface-sunken);
    overflow: hidden;
    min-width: 60px;
}

.erp-progress__bar {
    height: 100%;
    border-radius: 999px;
    background: var(--erp-positive);
}

/* ------------------------------------------------------------- gantt ----- */
/* The project Gantt (Pages/Projects/ProjectTasks.razor, Gantt view).
   DevExpress Blazor 25.1.3 ships no Gantt control — Grid, TreeList, Scheduler
   and Charts, none of which is this — so a task bar is a positioned div inside
   a full-width track, one track per grid row. Every track shares one date
   window computed once per load, which is what makes the bars comparable.
   Dragging is added by wwwroot/js/gantt.js and is OFF until the operator arms
   it; the rules for it are at the end of this block.
   Uses the same theme variables as everything else, so it follows light/dark. */

.erp-gantt-track {
    position: relative;
    height: 14px;
    min-width: 200px;
    border-radius: 3px;
    background: var(--erp-surface-sunken);
    overflow: hidden;
}

.erp-gantt-bar {
    position: absolute;
    top: 2px;
    height: 10px;
    min-width: 3px;
    border-radius: 3px;
    background: var(--erp-info);
    overflow: hidden;
}

/* A task with zero float: any slip on it moves the project end date, so it is
   the one thing on this chart that has to be findable at a glance. */
.erp-gantt-bar--critical {
    background: var(--erp-negative);
}

/* Progress fills from the left of its own bar, darkened rather than recoloured
   so the critical/non-critical distinction survives underneath it. */
.erp-gantt-bar__progress {
    height: 100%;
    border-radius: 3px 0 0 3px;
    background: rgba(0, 0, 0, 0.35);
}

/* --- dependency connectors (§2.4) -----------------------------------------
   A connector is a HORIZONTAL segment inside the successor's own track,
   running along the shared date scale from where the predecessor's bar ends
   to where this bar begins. It is not the elbow arrow of a desktop Gantt:
   every row is its own grid cell with its own clipping box, so nothing here
   can draw from one row down to another — which is why the badge beside the
   bar carries the link type, the lag and, in its tooltip, the names.
   Colours come from the theme variables, so this follows light and dark. */

/* The bar and its badge sit on one line, so adding the badge costs no row
   height. The track keeps the width it had and simply flexes. */
.erp-gantt-cell {
    display: flex;
    align-items: center;
    gap: 6px;
}

.erp-gantt-cell > .erp-gantt-track {
    flex: 1 1 auto;
}

/* Drawn above the track's vertical centre so it reads as a link between bars
   rather than as another bar. The arrowhead marks the direction of the
   dependency: it points at the task that has to wait. */
.erp-gantt-link {
    position: absolute;
    top: 6px;
    height: 2px;
    background: var(--erp-text-muted);
    opacity: 0.8;
}

.erp-gantt-link::after {
    content: "";
    position: absolute;
    right: -1px;
    top: -2px;
    border-top: 3px solid transparent;
    border-bottom: 3px solid transparent;
    border-left: 4px solid var(--erp-text-muted);
}

/* A finish-to-start predecessor that finishes AFTER this task starts. The
   plan as drawn has work beginning before what it waits on is finished, and
   this is the one thing on the chart a planner has to be able to spot. */
.erp-gantt-link--violated {
    background: var(--erp-negative);
    opacity: 1;
    height: 3px;
    top: 5px;
}

.erp-gantt-link--violated::after {
    border-left-color: var(--erp-negative);
    top: -1.5px;
}

/* The badge: link type and lag, with every predecessor named in its title. */
.erp-gantt-linkmark {
    flex: 0 0 auto;
    font-size: 10px;
    line-height: 14px;
    white-space: nowrap;
    padding: 0 4px;
    border: 1px solid var(--erp-border);
    border-radius: 3px;
    color: var(--erp-text-muted);
    cursor: help;
}

.erp-gantt-linkmark--violated {
    color: var(--erp-negative);
    border-color: var(--erp-negative);
}

/* --- drag-and-drop scheduling (§2.4) --------------------------------------
   Only bars the page marked draggable get any of this. A bar the operator
   cannot move must look exactly as inert as it is: no grab cursor, no grips,
   no hover state — an affordance on something that will refuse is worse than
   none. Razor writes data-drag="1" only when dragging is armed, the row is
   not a summary and the operator holds EDIT, so these rules key off the class
   it writes alongside it. Behaviour lives in wwwroot/js/gantt.js. */

.erp-gantt-bar--movable {
    cursor: grab;
    /* Without this a touch drag is claimed by the page's own scrolling and the
       bar never moves at all — the single most common way a pointer-events
       drag "works on a mouse and does nothing on a tablet". */
    touch-action: none;
}

/* The two grips. Drawn as inset highlights rather than child elements: the bar
   already contains the progress fill, and any extra node inside it would be
   re-created by every grid render, which is precisely when a drag is in flight.
   White at low alpha reads on both the info blue and the critical red, and in
   light and dark alike, because it sits on the bar's own colour. */
.erp-gantt-bar--movable::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 3px;
    box-shadow: inset 3px 0 0 rgba(255, 255, 255, 0.55),
                inset -3px 0 0 rgba(255, 255, 255, 0.55);
    pointer-events: none;
}

/* Set by gantt.js while the pointer is within a few pixels of either end. */
.erp-gantt-bar--movable.erp-gantt-bar--edge {
    cursor: ew-resize;
}

/* Held. The outline is what tells the operator which bar the keyboard's Escape
   would put back, on a chart where fifty bars look alike. */
.erp-gantt-bar--dragging {
    cursor: grabbing;
    z-index: 3;
    outline: 1px solid var(--erp-info);
    outline-offset: 1px;
}

.erp-gantt-bar--critical.erp-gantt-bar--dragging {
    outline-color: var(--erp-negative);
}

/* The prospective dates, following the pointer. Appended to <body> by gantt.js,
   OUTSIDE the grid, so a render diff mid-drag cannot delete it. Fixed rather
   than absolute for the same reason the track clips: an absolutely positioned
   read-out inside the row would be cut off by the first overflow it met. */
.erp-gantt-drag-tip {
    position: fixed;
    z-index: 2200;
    pointer-events: none;
    white-space: nowrap;
    font-size: 11.5px;
    line-height: 1.5;
    padding: 3px 8px;
    border: 1px solid var(--erp-border);
    border-radius: 4px;
    background: var(--erp-surface-raised);
    color: var(--erp-text);
    box-shadow: var(--erp-shadow-sm);
}

/* -------------------------------------------------------------- hints ----- */
/* A quiet explanatory line inside an editor — for saying what a field will
   actually do, or that a stored number is typed in rather than measured. */

.erp-hint {
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--erp-text-muted);
}
.erp-hint .bi { margin-right: 4px; }

/* --------------------------------------------- editable line grids ---- */
/* A DxGrid whose cells hold live editors (BOM components). The grid ships with
   cell padding tuned for text, which leaves editors cramped and clipped. */

/* Line-entry grids only (6 of 97). These hold live editors inside their cells, which the
   theme's text-tuned padding clips. This is a functional fix for a specific control, not a
   theme override applied app-wide — every other grid now renders at native density. */
.erp-linegrid .dxbl-grid-table > tbody > tr > td { padding: 4px 6px; vertical-align: middle; }
.erp-linegrid .dxbl-grid-table > thead > tr > th { padding: 6px; }

/* -------------------------------------------------- settings, by row ---- */
/* THE SETTINGS ROW — what SettingsEditor renders, and the shape every settings
   screen in the app should reach for.

   Two earlier attempts are worth remembering, because this is the third:
     1. a hand-built CSS grid PER ROW, whose tracks resolved per row, so the
        editors staircased down the page;
     2. a DxFormLayout of boxed collapsible groups, two settings per row, each a
        caption over an editor with the explanation underneath — every editor a
        different height and every explanation a different length, so the two
        columns never lined up. That is what read as "scattered".

   The rule that fixes both: THE CONTROL COLUMN IS A FIXED WIDTH. Nothing here is
   content-sized, so every control on the page starts on the same vertical line
   whatever kind it is, and no row can push its neighbour out of true. */
.erp-setlist { display: flex; flex-direction: column; }

.erp-setrow {
    display: flex;
    align-items: center;
    gap: 24px;
    padding: 11px 0;
    border-top: 1px solid var(--erp-border);
}
.erp-setrow:first-child { border-top: 0; padding-top: 4px; }
.erp-setrow:last-child { padding-bottom: 4px; }

/* min-width:0 or a long unbroken value (an API base URI) refuses to shrink and
   pushes the control column off the row. */
.erp-setrow__text { flex: 1 1 auto; min-width: 0; }
.erp-setrow__label {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-size: 13px;
    font-weight: 500;
    color: var(--erp-text);
}
.erp-setrow__hint {
    margin-top: 2px;
    font-size: 11.5px;
    line-height: 1.45;
    color: var(--erp-text-muted);
    max-width: 68ch;
}

.erp-setrow__control {
    flex: 0 0 260px;
    display: flex;
    justify-content: flex-end;
}
.erp-setrow__control > * { width: 100%; }
/* A checkbox given the full column would put its box a long way from its tick;
   it sits at the column's right edge instead, where the eye already is. */
.erp-setrow__control .dxbl-checkbox { width: auto; }

/* A number is read by its digits, not its box. */
.erp-settings__num { max-width: 140px; }

/* The tab strip is navigation between groups, not a heading — it sits above the
   list with a hairline under it and no box of its own. */
.erp-settabs .dxbl-tabs { margin-bottom: 2px; }

/* Phones: the row becomes two stacked lines, and the control takes the width it
   was denied. A 260px column beside a label on a 360px screen is two cramped
   columns instead of one clear one. */
@media (max-width: 720px) {
    .erp-setrow {
        flex-direction: column;
        align-items: stretch;
        gap: 8px;
        padding: 12px 0;
    }
    .erp-setrow__control { flex: 1 1 auto; justify-content: flex-start; }
    .erp-setrow__control > * { width: 100%; }
    .erp-setrow__control .dxbl-checkbox { width: auto; }
    .erp-setrow__hint { max-width: none; }
}

/* ------------------------------------------------------- form layouts ---- */
/* DxFormLayout ships with --dxbl-fl-padding-x:0 and --dxbl-fl-padding-y:0, so
   the first and last controls sit flush against whatever contains them — the
   popup edge, the card edge. Every editor in the app wears .erp-form, which
   until now had no rules at all and did nothing.

   Set the variables DevExpress exposes rather than fighting its layout with
   overrides: they are the supported seam, and they survive a theme update. */

.erp-form {
    /* The popup's padding lives HERE, on our own element. The popup's own class never reaches
       the DOM — see the editor-popups note below — so this is the one hook that cannot be
       missed. A card body already pads, and zeroes this again further down. */
    padding: 18px 20px;

    --dxbl-fl-padding-x: 2px;
    --dxbl-fl-padding-y: 4px;

    /* A little more air between rows than the shipped 0.75rem: these forms are
       dense (12-column, up to 30 fields) and the labels need separating. */
    --dxbl-fl-item-spacing-y: 0.9rem;
    --dxbl-fl-item-spacing-x: 1.25rem;

    --dxbl-fl-caption-font-weight: 600;
    --dxbl-fl-caption-color: var(--erp-text-muted);
}

/* NOTE: inside a popup this has no effect, and that is by design — DevExpress zeroes
   --dxbl-fl-padding-* for a form layout that is a direct child of the popup body, because
   the BODY is meant to provide the padding. See .erp-popup below. These values apply to
   form layouts on cards (Company Settings, Preferences). */

/* A group inside an editor is a box within a box; without this its border sits
   hard against the popup edge. */
.erp-form .dxbl-fl-group { margin: 6px 0 2px; }

/* Cards hold form layouts too (Company Settings, Preferences) — the card body
   already pads, so only the top needs releasing. */
.erp-card__body > .erp-form { padding: 2px 0 0; }

/* ----------------------------------------------------- editor popups ---- */
/* NOTHING HERE IS SCOPED TO .erp-popup, AND THAT IS THE POINT.

   Every DxPopup in this app passes CssClass="erp-popup". DevExpress 25.1 does not put it on the
   modal: a probe page rendering a popup with that exact CssClass produced ZERO occurrences of
   "erp-popup" in the markup. A block of rules scoped to it was dead from the day it was written,
   which is why editors sat hard against both edges however carefully those rules were phrased.

   Two fixes were attempted against that dead selector before anyone read the DOM — setting
   --dxbl-popup-body-padding-x/y (never consulted; 25.1 ships
   .dxbl-modal-body{padding:0!important}), then overriding that padding directly (right idea,
   unreachable selector). Both looked correct and changed nothing.

   Everything below hangs off .erp-form instead: ours, present inside every popup body, and
   impossible to miss. If a popup needs something .erp-form cannot express, add a wrapper of our
   own rather than a DevExpress class — theirs move between versions, and this one was never
   applied at all. */

.erp-form .dxbl-fl {
    --dxbl-fl-item-spacing-y: 0.9rem;
    --dxbl-fl-item-spacing-x: 1.25rem;
    --dxbl-fl-caption-font-weight: 600;
    --dxbl-fl-caption-color: var(--erp-text-muted);
}

/* The last field should not sit flush against the footer rule. */
.erp-form .erp-field:last-child,
.erp-form .erp-field-row:last-child .erp-field { margin-bottom: 4px; }

/* The reliable horizontal inset for editor popups. DevExpress zeroes the modal body's padding
   with !important and does not surface the popup's CssClass on the modal, so neither could be
   trusted to hold the labels off the edges — which is why editors read flush left and right.
   This wrapper is OURS, sits directly inside the body, and no DevExpress rule can reach it, so
   the side padding lives here. .erp-form then drops its own left/right padding so the inset is
   applied exactly once. Vertical rhythm is left to .erp-form, unchanged. */
.erp-editor { padding-left: 24px; padding-right: 24px; }
.erp-editor .erp-form { padding-left: 0; padding-right: 0; }

/* The subtitle strip under the editor title — icon + one muted line, above the form. */
.erp-editor__head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 2px 0 12px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--erp-border);
    color: var(--erp-text-muted);
    font-size: 12.5px;
}
.erp-editor__head > .bi { font-size: 15px; color: var(--erp-brand-500); flex: 0 0 auto; }

/* =============================================================================
   Sign-in
   -----------------------------------------------------------------------------
   Login.razor has always carried these class names, but the rules behind them
   were never written, so the page rendered as bare HTML: full-bleed inputs, no
   card, no centring. Tokens only — the card must keep looking like the rest of
   the app when the DevExpress theme underneath it changes.
   ============================================================================= */

/* Sign-in — a split screen: a brand panel on the left, the form on the right. */
.erp-login {
    min-height: 100vh;
    display: grid;
    grid-template-columns: 1.05fr 1fr;
    background: var(--erp-surface-sunken);
}

/* Brand panel. A brand-coloured gradient so sign-in reads as this app, not a generic form. */
.erp-login__aside {
    position: relative;
    overflow: hidden;
    display: flex;
    align-items: center;
    padding: 56px;
    color: #fff;
    background: linear-gradient(150deg, var(--erp-brand-500), var(--erp-brand-700));
}
.erp-login__aside::after {
    content: "";
    position: absolute;
    inset: 0;
    background: radial-gradient(900px 520px at 88% -12%, rgba(255, 255, 255, 0.18), transparent 60%);
    pointer-events: none;
}
.erp-login__aside-inner { position: relative; max-width: 430px; }
.erp-login__logo { display: flex; align-items: center; gap: 12px; margin-bottom: 42px; }
.erp-login__mark {
    background: rgba(255, 255, 255, 0.16) !important;
    color: #fff !important;
    width: 38px; height: 38px; font-size: 14px;
}
.erp-login__brandname { font-size: 18px; font-weight: 700; line-height: 1.1; }
.erp-login__brandsub { font-size: 12px; opacity: 0.82; }
.erp-login__headline {
    font-size: 30px;
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: -0.01em;
    margin-bottom: 28px;
}
.erp-login__points { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 13px; }
.erp-login__points li { display: flex; align-items: center; gap: 10px; font-size: 14px; opacity: 0.95; }
.erp-login__points i { font-size: 15px; opacity: 0.9; }

/* Form panel. */
.erp-login__main { display: grid; place-items: center; padding: 24px; }
.erp-login__card {
    width: 100%;
    max-width: 400px;
    background: var(--erp-surface-raised);
    border: 1px solid var(--erp-border);
    border-radius: var(--erp-radius-lg);
    box-shadow: var(--erp-shadow-lg);
    padding: 32px;
}

/* Compact brand inside the card — shown only when the aside panel is hidden (narrow screens). */
.erp-login__cardbrand { display: none; align-items: center; gap: 10px; margin-bottom: 22px; }
.erp-login__name { font-size: 17px; font-weight: 700; color: var(--erp-text); line-height: 1.2; }
.erp-login__sub { font-size: 11.5px; color: var(--erp-text-muted); }

.erp-login__welcome { margin-bottom: 22px; }
.erp-login__title { font-size: 21px; font-weight: 700; color: var(--erp-text); letter-spacing: -0.01em; }
.erp-login__subtitle { font-size: 13px; color: var(--erp-text-muted); margin-top: 4px; }

.erp-login__field { margin-bottom: 16px; }
.erp-login__field > label {
    display: block;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--erp-text-muted);
    margin-bottom: 6px;
}
.erp-login__box { width: 100%; }
.erp-login__input--empty {
    width: 100%;
    padding: 9px 12px;
    border: 1px solid var(--erp-border);
    border-radius: var(--erp-radius-md, 8px);
    background: var(--erp-surface-sunken);
    color: var(--erp-text-muted);
    font-size: 13px;
}

/* The two remember options, set apart from the credential fields by a hairline. */
.erp-login__checks {
    display: flex;
    flex-direction: column;
    gap: 11px;
    margin: 20px 0;
    padding-top: 18px;
    border-top: 1px solid var(--erp-border);
}

/* Company selection (step 2): one clickable row per company the account may open. */
.erp-login__companies { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; }
.erp-login__company {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding: 14px 16px;
    border: 1px solid var(--erp-border);
    border-radius: var(--erp-radius-md, 10px);
    background: var(--erp-surface-1, var(--erp-surface-raised));
    cursor: pointer;
    text-align: left;
    transition: border-color .12s ease, box-shadow .12s ease;
}
.erp-login__company:hover { border-color: var(--erp-brand-500); box-shadow: var(--erp-shadow-sm, 0 1px 3px rgba(0, 0, 0, .08)); }
.erp-login__company:disabled { opacity: .6; cursor: default; }
.erp-login__company.is-default { border-color: var(--erp-brand-500); }
.erp-login__company-icon { font-size: 18px; color: var(--erp-brand-500); flex: 0 0 auto; }
.erp-login__company-name { flex: 1 1 auto; font-weight: 600; color: var(--erp-text); }
.erp-login__company-tag {
    font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
    color: var(--erp-brand-600); background: var(--erp-brand-50); padding: 2px 7px; border-radius: 999px;
}
.erp-login__company-go { color: var(--erp-text-muted); font-size: 14px; flex: 0 0 auto; }

.erp-login__back {
    border: none; background: transparent; cursor: pointer;
    display: inline-flex; align-items: center; gap: 7px;
    font-size: 13px; font-weight: 600; color: var(--erp-text-muted);
    padding: 6px 2px;
}
.erp-login__back:hover { color: var(--erp-brand-600); }

/* Narrow screens: drop the brand panel, bring back a compact brand and a brand wash. */
@media (max-width: 900px) {
    .erp-login { grid-template-columns: 1fr; }
    .erp-login__aside { display: none; }
    .erp-login__cardbrand { display: flex; }
    .erp-login__main {
        min-height: 100vh;
        padding: 20px;
        background-image: radial-gradient(1000px 500px at 50% -10%, var(--erp-brand-50), transparent 70%);
    }
}

/* Native checkbox styled to match the app — no DevExpress dependency, so it always paints. */

/* Back link at the top of a full-page editor. */
.erp-pageback { margin-bottom: 12px; }
.erp-pageback__link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    font-weight: 600;
    color: var(--erp-text-muted);
    text-decoration: none;
    padding: 4px 2px;
    transition: color .12s ease;
}
/* It became a <button> when it started deciding its destination at click time; these three keep
   it looking exactly like the link it replaced. */
.erp-pageback__link { border: 0; background: transparent; cursor: pointer; font-family: inherit; }
.erp-pageback__link:hover { color: var(--erp-brand-600); }
.erp-pageback__link i { font-size: 15px; }

.erp-login__button { width: 100%; }

.erp-login__note {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    margin-top: 18px;
    font-size: 11.5px;
    line-height: 1.45;
    color: var(--erp-text-muted);
}

/* Shown instead of the sign-in form while a remembered token is redeemed. Sized to roughly the
   height of the form it replaces, so the card does not collapse and rebound as it resolves. */
.erp-login__restoring {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    min-height: 220px;
    font-size: 13px;
    color: var(--erp-text-muted);
}

.erp-login__foot {
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px solid var(--erp-border);
    text-align: center;
    font-size: 11px;
    color: var(--erp-text-muted);
}

/* ---------------------------------------------------------- dashboard ---- */

/* The bar above the tiles: title on the left, period pills on the right. */
.erp-dash-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: var(--erp-gap);
}

.erp-dash-bar__fresh {
    font-size: 11.5px;
    color: var(--erp-text-muted);
    display: flex;
    align-items: center;
    gap: 6px;
}

/* Period selector: a segmented row of pills, the active one carrying the accent. */
/* The wrapper still exists as the DxButtonGroup CssClass; the hand-rolled .erp-seg__btn rules
   that used to style plain <button> elements were removed with them. */
.erp-seg {
    display: inline-flex;
    background: var(--erp-surface-sunken);
    border: 1px solid var(--erp-border);
    border-radius: 999px;
    padding: 3px;
    gap: 2px;
}

/* Change vs the previous comparable period, shown on a tile. */
.erp-delta {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 11.5px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
.erp-delta--up   { color: var(--erp-positive); }
.erp-delta--down { color: var(--erp-negative); }
.erp-delta--flat { color: var(--erp-text-muted); }

/* Aging bar fills carry meaning: current is calm, the late bands warm up. */
.erp-bar-row__fill--ok   { background: var(--erp-positive); }
.erp-bar-row__fill--warn { background: var(--erp-warning); }
.erp-bar-row__fill--late { background: var(--erp-negative); }

.erp-dash-grid {
    display: grid;
    grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
    gap: var(--erp-gap);
    align-items: start;
    margin-bottom: var(--erp-gap);
}
@media (max-width: 980px) {
    .erp-dash-grid { grid-template-columns: 1fr; }
}

/* =============================================================================
   Print — A4 documents (tax invoice, etc.)
   -----------------------------------------------------------------------------
   The document renders the same on screen and on paper: a white A4 sheet on a grey
   backdrop on screen, the sheet alone when printed. The toolbar and the app chrome
   are dropped at print time.
   ============================================================================= */

.erp-print-host { background: #e9edf3; min-height: 100vh; padding: 20px 0 60px; }

.erp-print__toolbar {
    display: flex; gap: 8px; justify-content: center; margin-bottom: 16px;
}

/* PRINT INK IS DELIBERATELY FIXED, not tokenised.
   Everything below uses literal colours on purpose, and that is the one place in this stylesheet
   where a literal is correct. A tax invoice is a legal document: it must look the same on paper
   this year as last, and must not change colour because somebody changed the application theme.
   Deriving these from --bs-* would mean an invoice reprinted after a theme change no longer
   matches the one already sent to the customer.

   One exception worth knowing about: .erp-print__doctype is #0f6cbd, which is Fluent blue and a
   leftover from the previous theme. It is left alone rather than quietly restyled, because that
   would change the appearance of a document already issued to customers. Say the word and it
   becomes the brand colour or plain black. */
.erp-print {
    background: #fff;
    color: #14203a;
    width: 210mm;
    min-height: 297mm;
    margin: 0 auto;
    padding: 16mm 15mm;
    box-shadow: 0 6px 28px -8px rgba(15, 23, 42, 0.30);
    font-family: "Segoe UI", system-ui, sans-serif;
    font-size: 12px;
    line-height: 1.5;
    box-sizing: border-box;
}

.erp-print__head { display: flex; justify-content: space-between; gap: 20px; border-bottom: 2px solid #14203a; padding-bottom: 12px; }
.erp-print__seller { font-size: 20px; font-weight: 700; letter-spacing: -0.01em; }
.erp-print__muted { color: #566; font-size: 11px; }
.erp-print__trn { font-weight: 600; margin-top: 4px; }
.erp-print__title { text-align: right; }
.erp-print__doctype { font-size: 17px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: #0f6cbd; }
.erp-print__meta { margin-left: auto; margin-top: 8px; border-collapse: collapse; font-size: 11.5px; }
.erp-print__meta td { padding: 1px 0 1px 12px; text-align: right; }
.erp-print__meta td:first-child { color: #566; text-align: left; padding-left: 0; }

.erp-print__parties { display: flex; gap: 40px; margin: 16px 0; }
.erp-print__label { font-size: 9.5px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: #8792a8; margin-bottom: 3px; }
.erp-print__party { font-weight: 700; font-size: 13px; }

.erp-print__lines { width: 100%; border-collapse: collapse; margin-top: 6px; }
.erp-print__lines thead th {
    background: #14203a; color: #fff; font-weight: 600; font-size: 10.5px; text-align: left;
    padding: 7px 8px; text-transform: uppercase; letter-spacing: 0.02em;
}
.erp-print__lines tbody td { padding: 7px 8px; border-bottom: 1px solid #e2e8f0; vertical-align: top; }
.erp-print__prod { font-weight: 600; }
.erp-print__c-idx { width: 28px; text-align: center; }
.erp-print__c-num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.erp-print__lines .erp-print__c-num { text-align: right; }

.erp-print__foot { display: flex; justify-content: space-between; gap: 30px; margin-top: 16px; }
.erp-print__foot-left { flex: 1; display: flex; gap: 16px; }
.erp-print__qr { width: 108px; height: 108px; flex: 0 0 auto; }
.erp-print__words { font-weight: 600; max-width: 300px; }
.erp-print__totals { border-collapse: collapse; min-width: 230px; height: fit-content; }
.erp-print__totals td { padding: 5px 0; }
.erp-print__totals td:last-child { text-align: right; font-variant-numeric: tabular-nums; padding-left: 24px; }
.erp-print__totals .erp-print__grand td { border-top: 2px solid #14203a; font-weight: 700; font-size: 15px; padding-top: 8px; }

.erp-print__signoff { display: flex; justify-content: space-between; align-items: flex-end; margin-top: 40px; color: #566; font-size: 10.5px; }
.erp-print__sig { border-top: 1px solid #14203a; padding-top: 4px; min-width: 180px; text-align: center; color: #14203a; }

/* User manuals — see Pages/Help/ManualPage.razor. Set in a narrow measure because this is the
   one part of the application that is read as prose, and prose across a 1600px screen is unreadable
   however good the words are. */
.erp-topbar__help { margin-left: 6px; opacity: .85; }
.erp-topbar__help:hover { opacity: 1; }

/* The header clock (Audit Trail). Tabular figures so the seconds tick in place instead of
   nudging the row a pixel wider every time the digits change width. */
.erp-topbar__clock {
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
    padding: 3px 9px;
    border: 1px solid var(--erp-border);
    border-radius: 999px;
    color: var(--erp-text-muted);
    font-size: 12px;
    line-height: 1.4;
    white-space: nowrap;
    cursor: default;
}
.erp-topbar__clock .bi { font-size: 11px; opacity: .8; align-self: center; }
.erp-topbar__clock-time {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    font-weight: 600;
    color: var(--erp-text);
    letter-spacing: .2px;
}
.erp-topbar__clock-zone { font-size: 10px; text-transform: uppercase; letter-spacing: .6px; opacity: .75; }

.erp-help { max-width: 760px; margin: 0 auto; padding: 8px 4px 60px; line-height: 1.65; }
.erp-help h1 { font-size: 26px; margin: 0 0 10px; }
.erp-help h2 { font-size: 18px; margin: 34px 0 8px; }
.erp-help p { margin: 0 0 12px; }
.erp-help__back { display: inline-block; margin-bottom: 14px; font-size: 13px; color: var(--erp-text-muted); text-decoration: none; }
.erp-help__back:hover { color: var(--erp-brand-500); }
.erp-help__head { border-bottom: 1px solid var(--erp-border); padding-bottom: 16px; margin-bottom: 8px; }
.erp-help__intro { font-size: 15.5px; color: var(--erp-text-muted); }
.erp-help__meta { font-size: 12px; color: var(--erp-text-muted); }

.erp-help__before { background: var(--erp-surface); border: 1px solid var(--erp-border);
                    border-radius: 8px; padding: 14px 18px; margin: 20px 0; }
.erp-help__before h2 { margin-top: 0; font-size: 15px; }
.erp-help__before-why { font-size: 13px; color: var(--erp-text-muted); }
.erp-help__before ul { margin: 0; padding-left: 18px; }
.erp-help__before li { margin-bottom: 6px; }

.erp-help__toc { border-left: 3px solid var(--erp-border); padding: 4px 0 4px 14px; margin: 22px 0; }
.erp-help__toc-label { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: var(--erp-text-muted); margin-bottom: 4px; }
.erp-help__toc ol { margin: 0; padding-left: 18px; font-size: 14px; }
.erp-help__toc a { text-decoration: none; color: var(--erp-brand-500); }

/* A manual long enough to need groups. The rule under the heading is what makes a section read
   as a division of the page rather than as one more topic with a shorter title. */
.erp-help__section { font-size: 12px; text-transform: uppercase; letter-spacing: .09em;
                     color: var(--erp-text-muted); margin: 46px 0 0; padding-bottom: 6px;
                     border-bottom: 1px solid var(--erp-border); }
.erp-help__section:first-of-type { margin-top: 30px; }
.erp-help__toc-group { margin: 12px 0 2px; font-size: 11px;
                       text-transform: uppercase; letter-spacing: .08em; color: var(--erp-text-muted); }
.erp-help__topic { margin-top: 34px; }
.erp-help__topic h2 { display: flex; align-items: baseline; gap: 10px; }
.erp-help__topic-n { display: inline-flex; align-items: center; justify-content: center;
                     min-width: 26px; height: 26px; border-radius: 50%; font-size: 13px;
                     background: var(--erp-brand-500); color: #fff; flex-shrink: 0; }
.erp-help__purpose { color: var(--erp-text-muted); }

/* Numbered steps: the action in the reader's voice, the consequence underneath it in a quieter
   one, so the page can be followed by reading only the bold line. */
.erp-help__steps { padding-left: 22px; margin: 14px 0 6px; }
.erp-help__steps li { margin-bottom: 12px; }
.erp-help__action { display: block; font-weight: 600; }
.erp-help__detail { display: block; color: var(--erp-text-muted); font-size: 14px; margin-top: 2px; }

.erp-help__note { display: flex; gap: 10px; padding: 10px 14px; border-radius: 8px;
                  margin: 10px 0; font-size: 14px; border-left: 3px solid; }
.erp-help__note i { margin-top: 2px; }
.erp-help__note--tip     { background: var(--erp-positive-soft); border-color: var(--erp-positive); }
.erp-help__note--rule    { background: var(--erp-surface);       border-color: var(--erp-brand-500); }
.erp-help__note--warning { background: var(--erp-negative-soft); border-color: var(--erp-negative); }

.erp-help__trouble { margin-top: 40px; border-top: 1px solid var(--erp-border); padding-top: 10px; }
.erp-help__qa { margin-bottom: 16px; }
.erp-help__q { font-weight: 600; }
.erp-help__a { color: var(--erp-text-muted); font-size: 14px; }

.erp-help__index-head { margin-bottom: 18px; }
.erp-help__start { display: flex; align-items: center; justify-content: space-between; gap: 16px;
                   text-decoration: none; color: inherit; padding: 18px 20px; margin-bottom: 22px;
                   border: 1px solid var(--erp-brand-500); border-radius: 10px; background: var(--erp-surface); }
.erp-help__start:hover { box-shadow: 0 2px 10px rgba(0,0,0,.08); }
.erp-help__start-kicker { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: var(--erp-brand-500); }
.erp-help__start-title { font-weight: 700; font-size: 16px; margin: 2px 0 4px; }
.erp-help__start-sub { font-size: 13.5px; color: var(--erp-text-muted); }

.erp-help__start--quiet { border-color: var(--erp-border); }
.erp-help__start--quiet .erp-help__start-kicker { color: var(--erp-text-muted); }
.erp-help__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 12px; }
.erp-help__card { display: block; padding: 14px 16px; border: 1px solid var(--erp-border);
                  border-radius: 8px; text-decoration: none; color: inherit; background: var(--erp-surface); }
.erp-help__card:hover { border-color: var(--erp-brand-500); }
.erp-help__card--empty { opacity: .55; cursor: default; }
.erp-help__card-title { font-weight: 600; }
.erp-help__card-sub { font-size: 12px; color: var(--erp-text-muted); margin-top: 2px; }
.erp-help__card-sub--muted { font-style: italic; }

@media print { .erp-help__toc, .erp-help__back, .erp-topbar__help { display: none; } }

/* Settings reference — a long two-column list, so the name column is fixed and narrow and the
   explanation is given the room, which is the opposite of a grid and correct for reading. */
.erp-help__subhead { font-size: 14px; text-transform: uppercase; letter-spacing: .06em;
                     color: var(--erp-text-muted); margin: 22px 0 6px; }
.erp-help__settings { width: 100%; border-collapse: collapse; font-size: 14px; }
.erp-help__settings td { padding: 10px 12px 10px 0; border-top: 1px solid var(--erp-border); vertical-align: top; }
.erp-help__setting-name { width: 34%; font-weight: 600; }
.erp-help__setting-kind { font-weight: 400; font-size: 11.5px; color: var(--erp-text-muted); margin-top: 2px; }
.erp-help__setting-default { color: var(--erp-text-muted); font-size: 13px; margin-top: 3px; }
.erp-help__count { color: var(--erp-text-muted); font-size: 12px; }

/* Rule builder — see Pages/Settings/Rules.razor. The controls sit on one line so a rule reads
   as a sentence rather than as a stack of form fields. */
.erp-rule__sentence { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 10px 0; }
.erp-rule__word { color: var(--erp-text-muted); }
.erp-rule__pick { min-width: 200px; }
.erp-rule__pick--sm { min-width: 150px; }
.erp-rule__value { max-width: 160px; }
.erp-rule__condition { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 8px; }
.erp-rule__targets { display: grid; gap: 8px; }
.erp-rule__people { max-height: 190px; }
.erp-rule__test { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--erp-border); }
.erp-rule__result { margin-top: 10px; font-size: 13px; }
.erp-rule__samples { margin: 6px 0 0; padding-left: 18px; color: var(--erp-text-muted); font-size: 12px; }

/* Document trail — see Components/DocumentChainPanel.razor. One row per related document,
   clickable across its whole width because the number alone is a small target. */
.erp-chain__row {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 7px 9px; border: 1px solid var(--erp-border); border-radius: 6px;
    margin-bottom: 6px; cursor: pointer; background: var(--erp-surface);
}
.erp-chain__row:hover, .erp-chain__row:focus-visible { border-color: var(--erp-brand-500); outline: none; }
.erp-chain__main { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.erp-chain__type { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--erp-text-muted); }
.erp-chain__number { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.erp-chain__meta { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--erp-text-muted); flex-shrink: 0; }

@page { size: A4; margin: 0; }
@media print {
    .erp-print-host { background: #fff; padding: 0; }
    .erp-print__toolbar { display: none !important; }
    .erp-print { box-shadow: none; margin: 0; width: auto; min-height: auto; }
    .erp-print__lines thead { display: table-header-group; }   /* repeat header across pages */
    .erp-print__lines tr { page-break-inside: avoid; }
}

/* DevExpress sprite icons used on our own buttons (see Components/DxIcon.razor).
   Sized and spaced to match what IconCssClass produces for a bootstrap-icon, so a toolbar
   holding both kinds still lines up — the two sets coexist by necessity, because the
   DevExpress Fluent set has no print, save, delete or export glyph. */
.erp-dxicon {
    display: inline-block;
    vertical-align: -0.125em;
    margin-right: 6px;
    fill: currentColor;   /* inherit the button's text colour, light and dark alike */
    flex: none;
}

/* =============================================================================
   REDESIGN 2026 — Indigo → Violet, light + dark
   -----------------------------------------------------------------------------
   This block is ADDITIVE and lives at the end of the file on purpose: it
   redefines the design tokens and re-skins the chrome WITHOUT touching any of
   the functional CSS above (print, forms, editor popups, login, dashboard
   grid). Everything above is token-driven, so re-pointing the tokens here
   re-colours the whole application — all 90 pages — in one place.

   The brand tokens are now LITERAL indigo/violet rather than chained off the
   DevExpress --bs-* palette, so the chrome is the same indigo under any skin.
   To keep DevExpress's own controls (grids, inputs, charts) in step, run the
   app on the "Purple" skin for light and "Blazing Dark" for dark — the
   light/dark toggle in the shell does exactly that. `theme.js` sets
   html[data-theme="dark"] whenever a dark skin is active, which is what turns
   the tokens below dark; it is never set on its own, so dark chrome and dark
   controls always arrive together.
   ============================================================================= */

/* Plus Jakarta Sans — VENDORED, not fetched from Google Fonts, for the same
   reason the icon font and the DevExpress themes are vendored: the desktop
   BlazorWebView must render identically offline. The files live in the shared
   RCL's wwwroot/fonts, so ../fonts/ resolves under _content/Analysis.Shared/
   in both hosts. font-display: swap keeps text readable (in Segoe UI) for the
   instant before the face arrives — never invisible. Latin subset only, 61KB
   across five weights. */
@font-face { font-family: "Plus Jakarta Sans"; font-style: normal; font-weight: 400; font-display: swap; src: url("../fonts/plus-jakarta-sans-latin-400-normal.woff2") format("woff2"); }
@font-face { font-family: "Plus Jakarta Sans"; font-style: normal; font-weight: 500; font-display: swap; src: url("../fonts/plus-jakarta-sans-latin-500-normal.woff2") format("woff2"); }
@font-face { font-family: "Plus Jakarta Sans"; font-style: normal; font-weight: 600; font-display: swap; src: url("../fonts/plus-jakarta-sans-latin-600-normal.woff2") format("woff2"); }
@font-face { font-family: "Plus Jakarta Sans"; font-style: normal; font-weight: 700; font-display: swap; src: url("../fonts/plus-jakarta-sans-latin-700-normal.woff2") format("woff2"); }
@font-face { font-family: "Plus Jakarta Sans"; font-style: normal; font-weight: 800; font-display: swap; src: url("../fonts/plus-jakarta-sans-latin-800-normal.woff2") format("woff2"); }

:root {
    /* Brand ramp — FOLLOWS THE ACTIVE DEVEXPRESS THEME.
       -----------------------------------------------------------------------
       These were Business Central teal literals, matched from the Role Center
       screenshot. The literals are now the FALLBACK rather than the value, for
       one reason: as literals they silently overrode the identical chain in the
       first :root block of this file (see the top of erp.css), because a later
       rule of equal specificity wins. The whole application's chrome — the
       .erp-cmd strips on every document form, the role-centre actions, the cue
       tiles, the badges, the rail — was therefore pinned to teal no matter
       which of the fifteen themes was chosen, while the DevExpress controls
       beside them recoloured. That is the half-migrated look the note at the
       top of this file argues against, arrived at by accident.

       The chain reads: the classic themes' Bootstrap primary, else the Fluent
       accents' --DS-primary-*, else BC teal for the instant before a theme
       sheet lands. html[data-accent] below still pins a brand colour outright
       for anyone who wants one that does NOT follow the theme. */
    --erp-brand-50:   var(--bs-primary-bg-subtle,     var(--DS-primary-20,  #e9f6f7));
    --erp-brand-100:  var(--bs-primary-bg-subtle,     var(--DS-primary-30,  #d5eced));
    --erp-brand-300:  var(--bs-primary-border-subtle, var(--DS-primary-60,  #66b5ba));
    --erp-brand-500:  var(--bs-primary,               var(--DS-primary-90,  #008089));  /* primary */
    --erp-brand-600:  var(--bs-link-hover-color,      var(--DS-primary-100, #00666d));
    --erp-brand-700:  var(--bs-primary-text-emphasis, var(--DS-primary-120, #005055));
    --erp-violet-500: var(--bs-primary,               var(--DS-primary-80,  #2aa0a8));  /* secondary (name kept for compat) */
    --erp-grad:       linear-gradient(135deg, var(--erp-brand-700) 0%, var(--erp-brand-500) 100%);

    /* Semantic accents — Fluent's message-bar palette */
    --erp-positive:      #107c10;  --erp-positive-soft: #dff6dd;
    --erp-negative:      #d13438;  --erp-negative-soft: #fde7e9;
    --erp-warning:       #9d5d00;  --erp-warning-soft:  #fff4ce;
    --erp-info:          #0078d4;  --erp-info-soft:     #deecf9;

    /* Surfaces & ink (light) — THE THEME'S OWN NEUTRALS WHERE IT PUBLISHES ANY.
       ------------------------------------------------------------------------------------
       These were the Fluent neutral literals, and being literals they did to the SURFACES
       exactly what the teal ones above had done to the brand: pinned them, so a classic
       theme recoloured its buttons and left every card, page and heading the same white.

       The chain is the same one the brand ramp uses and the same one the first :root block
       of this file already used for these: the classic themes' Bootstrap neutrals, else
       Fluent's surface tokens, else the Fluent literals for the instant before a sheet
       lands. A Fluent accent publishes NO page-level surface, so it falls straight through
       to the literal and looks exactly as it does today — which is what a Fluent accent is.
       A classic theme publishes body-bg and body-color as a designed PAIR, so taking both
       from the same theme is what keeps the contrast right rather than mixing one theme's
       paper with another's ink. */
    --erp-surface:        var(--bs-body-bg,         var(--DS-color-surface-neutral-default-rest, #ffffff));
    --erp-surface-raised: var(--bs-body-bg,         var(--DS-color-surface-neutral-default-rest, #ffffff));
    --erp-text:           var(--bs-body-color,      var(--DS-color-content-neutral-default-rest, #242424));
    --erp-text-muted:     var(--bs-secondary-color, var(--DS-color-content-neutral-subdued-rest, #616161));
    --erp-text-inverse:   #ffffff;

    /* EVERY OTHER NEUTRAL IS DERIVED FROM THOSE TWO, and that is not stylistic tidiness — it
       is the only shape that cannot invert. Measured across the themes this application
       ships: --bs-body-bg and --bs-body-color are a designed pair and are always right way
       round, but Blazing Dark publishes a DARK body over a LIGHT --bs-secondary-bg,
       --bs-tertiary-bg and --bs-border-color, because its sheet does not override those
       three. Chaining a surface to one of them therefore produced a light page under white
       text — unreadable, in a theme that is otherwise correct.

       Mixing the ink into the paper instead keeps a sunken surface sunken and a border
       visible in either direction, whatever the theme publishes, and introduces no colour of
       its own: both operands are the theme's. */
    --erp-border:         color-mix(in srgb, var(--erp-text) 14%, var(--erp-surface));
    --erp-border-strong:  color-mix(in srgb, var(--erp-text) 30%, var(--erp-surface));

    /* THE PAGE AND THE SUNKEN AREAS CARRY A LITTLE OF THE THEME'S OWN ACCENT.
       ------------------------------------------------------------------------------------
       A neutral grey page under a green button is an accent sitting ON a design rather than
       a design; what reads as considered is the page agreeing with the button. So both are
       mixed twice: first the INK, which guarantees the page stays a step below the card
       whatever the accent's own lightness is, and then the ACCENT, which is the part you
       see. The card itself stays at the surface colour, so the separation between page and
       card still does its job.

       THE PERCENTAGE IS THE WHOLE CRAFT. At 3-5% this reads as a considered pairing; at 8%
       it reads as a tinted spreadsheet, and beyond that it reads as a mistake. These are
       deliberately at the quiet end — the accent is meant to be noticed on the button, not
       on the paper behind it. Both operands are the theme's own, so no colour is invented
       and every one of the twenty-six pairs itself. */
    --erp-page:           color-mix(in srgb, var(--erp-brand-500) 4%,
                          color-mix(in srgb, var(--erp-text) 3%, var(--erp-surface)));
    --erp-surface-sunken: color-mix(in srgb, var(--erp-brand-500) 5%,
                          color-mix(in srgb, var(--erp-text) 5%, var(--erp-surface)));

    /* Rail — D365's SITEMAP: light grey, dark text, white selection card.
       (The dark chrome moved up to the app bar — see the topnav tokens.) */
    --erp-rail-bg:          #f3f2f1;
    --erp-rail-bg-2:        #f3f2f1;
    --erp-rail-bg-active:   #ffffff;
    --erp-rail-text:        #424242;
    --erp-rail-text-active: #242424;
    --erp-rail-text-hover:  #242424;
    --erp-rail-text-dim:    #707070;
    --erp-rail-wash:        rgba(0, 0, 0, .05);
    --erp-rail-line:        rgba(0, 0, 0, .08);

    /* App bar — Business Central: a WHITE header with dark text and teal
       accents, matched from the Role Center screenshot. (The navy strip was
       the other Dynamics product — corrected against the real reference.) */
    --erp-topnav-bg:          #ffffff;
    --erp-topnav-bg-2:        #ffffff;
    --erp-topnav-text:        #424242;
    --erp-topnav-text-strong: #1b1a19;
    --erp-topnav-wash:        rgba(0, 0, 0, .05);
    --erp-topnav-line:        #e1dfdd;
    /* Follows the DevExpress FLUENT accent when one is loaded, and falls back to the classic
       teal when it is not.

       WHY THE INDIRECTION. theme.js's own accent ramp knows exactly three names — violet,
       magenta and indigo — and removes html[data-accent] for anything else. So picking Fluent
       Orchid (or Mint, Rust, Rose, Moss, Desert, Steel, Storm, Cool Blue, Blue) repainted every
       DevExpress control and left this token on the default teal: the app bar stayed teal while
       the rest of the page went purple.

       --DS-primary-110 is defined by the accent stylesheet itself — same variable name across
       all eleven accents, a different value in each — so reading it means a NEW accent shipped
       by DevExpress works without anyone adding a case here. The classic (non-Fluent) themes
       define nothing of the sort, and var() falls back to the teal they were designed with.

       The -110 rung rather than -100: this is 13px text on a white bar, and the darker rung
       clears contrast comfortably on every accent (orchid #86257c, blue #0b5699, mint #006457,
       rust #982601) where the mid rung is marginal on the lighter ones. */
    /* BOTH FAMILIES, in the order that reads correctly. --bs-primary is what the CLASSIC themes
       publish and --DS-primary-110 is Fluent's; reading only the Fluent one is why picking a
       classic skin (Purple, Office White, Blazing Dark) repainted the grids and editors and left
       this bar on the teal it was designed with — the module tabs, the page tabs and every
       accent that hangs off this token simply did not move. */
    --erp-topnav-accent:      var(--bs-primary, var(--DS-primary-110, #008089));

    /* Shape & depth — Fluent: tight radii, whisper-quiet elevation */
    --erp-radius-sm: 3px;
    --erp-radius:    4px;
    --erp-radius-lg: 8px;
    --erp-radius-md: 4px;
    --erp-shadow-sm: 0 1px 2px rgba(0,0,0,.08);
    --erp-shadow:    0 1.6px 3.6px rgba(0,0,0,.11), 0 0.3px 0.9px rgba(0,0,0,.07);
    --erp-shadow-lg: 0 6.4px 14.4px rgba(0,0,0,.13), 0 1.2px 3.6px rgba(0,0,0,.10);

    /* Chart series — validated colourblind-safe pair (dataviz palette, light) */
    --erp-series-1: #2a78d6;
    --erp-series-2: #eb6834;

    /* Typography — Segoe UI, the Dynamics 365 face. The vendored Plus Jakarta
       Sans @font-face rules above stay harmlessly unused (an unreferenced face
       is never fetched) in case the type direction swings back. */
    --erp-font: "Segoe UI", "Segoe UI Web (West European)", -apple-system, system-ui, sans-serif;
    --erp-font-num: "Segoe UI", Consolas, sans-serif;
}

html[data-theme="dark"] {
    /* Same change as the light ramp above: the dark theme's own primary, with the
       lightened teal kept as the fallback. A dark skin publishes a primary that is
       already legible on its dark surfaces, so following it is what keeps contrast
       right — pinning these to teal is what made a dark theme's buttons stay teal. */
    --erp-brand-50:   var(--bs-primary-bg-subtle,     var(--DS-primary-20,  #06282a));
    --erp-brand-100:  var(--bs-primary-bg-subtle,     var(--DS-primary-30,  #0a3f42));
    --erp-brand-300:  var(--bs-primary-border-subtle, var(--DS-primary-60,  #2e989f));
    --erp-brand-500:  var(--bs-primary,               var(--DS-primary-90,  #4db6bd));
    --erp-brand-600:  var(--bs-link-hover-color,      var(--DS-primary-100, #6cc5cb));
    --erp-brand-700:  var(--bs-primary-text-emphasis, var(--DS-primary-120, #9ddade));
    --erp-violet-500: var(--bs-primary,               var(--DS-primary-80,  #6cc5cb));
    --erp-grad:       linear-gradient(135deg, var(--erp-brand-300) 0%, var(--erp-brand-500) 100%);

    --erp-positive:      #6ccb5f;  --erp-positive-soft: rgba(108,203,95,.15);
    --erp-negative:      #f1707b;  --erp-negative-soft: rgba(241,112,123,.15);
    --erp-warning:       #f2c661;  --erp-warning-soft:  rgba(242,198,97,.14);
    --erp-info:          #479ef5;  --erp-info-soft:     rgba(71,158,245,.15);

    /* Surfaces & ink (dark) — the same chain as the light block, with the dark literals as
       the fallback. A classic DARK theme publishes its own dark body-bg and body-color, so
       it drives them; the eleven Fluent darks publish no page surface and fall through to
       these, which is where the navy chrome comes from. */
    --erp-surface:        var(--bs-body-bg,         var(--DS-color-surface-neutral-default-rest, #201f1e));
    --erp-surface-raised: var(--bs-body-bg,         var(--DS-color-surface-neutral-default-rest, #252423));
    /* SOFTENED, AND ONLY ON THE DARK SIDE. Reported as dark themes not being easy on the
       eyes. The surfaces were not the problem — the ink was. A dark theme that puts white on
       near-black is running at 12:1 and above, and at that distance the light bleeds around
       the letterforms; the comfortable band for dark body text is roughly 7-9:1, which is
       still far above the 4.5 that legibility asks for. Mixing a little of the surface back
       into the ink is how that is done without dimming the page, inventing a grey, or
       touching the light themes, which were reported as fine and are left exactly as they
       were. */
    --erp-text:           color-mix(in srgb,
                            var(--bs-body-color, var(--DS-color-content-neutral-default-rest, #f3f2f1)) 84%,
                            var(--erp-surface));
    --erp-text-muted:     color-mix(in srgb,
                            var(--bs-secondary-color, var(--DS-color-content-neutral-subdued-rest, #a19f9d)) 84%,
                            var(--erp-surface));
    --erp-text-inverse:   #ffffff;

    /* Derived exactly as in the light block, and for the same reason — Blazing Dark is the
       theme that proved it necessary. On a dark surface the mix lightens rather than darkens,
       because the ink being mixed in is the light one. */
    --erp-border:         color-mix(in srgb, var(--erp-text) 14%, var(--erp-surface));
    --erp-border-strong:  color-mix(in srgb, var(--erp-text) 26%, var(--erp-surface));

    /* The same pairing as the light block. A dark page with no hue in it at all is the
       charcoal that reads as cheap; a few per cent of the theme's own accent is what makes
       a dark skin look chosen rather than merely switched off.

       WHY THE SECOND OPERAND IS black AND NOT A THEME VALUE. The page must sit a step BELOW
       the card. On a light theme mixing the ink achieves that; on a dark theme the ink is the
       light one, so the same expression pushes the page ABOVE the card and the relationship
       inverts — measured on Blazing Dark, where the page came out lighter than the cards on
       it. The obvious substitute, the darkest brand rung, is not reliably dark either: in
       Blazing Dark it resolves light and the page came out lighter still.

       There is no theme value that is dependably the dark end, so the level change is made
       achromatically and the HUE still comes from the accent in the outer mix. black adds no
       colour — it only takes light away — which is the one thing being asked of it. */
    --erp-page:           color-mix(in srgb, var(--erp-brand-500) 5%,
                          color-mix(in srgb, black 22%, var(--erp-surface)));
    --erp-surface-sunken: color-mix(in srgb, var(--erp-brand-500) 6%,
                          color-mix(in srgb, var(--erp-text) 6%, var(--erp-surface)));

    --erp-rail-bg:          #252423;
    --erp-rail-bg-2:        #252423;
    --erp-rail-bg-active:   #3b3a39;
    --erp-rail-text:        #c8c6c4;
    /* WHITE ON A DARK SURFACE IS THE ONE THAT TIRES THE EYE, so these take the same softened
       ink the body text uses rather than pure white. White on a BRAND FILL is left alone
       deliberately and is not the same case: measured across the accents this application
       ships, white on the brand colour is already under 4.5:1 on several of them — Mint 2.8,
       Desert 3.0, Rose 3.5 — so dimming it there would take a marginal contrast and make it
       fail. Big dark areas get the softer ink; small coloured buttons keep the bright one. */
    --erp-rail-text-active: var(--erp-text);
    --erp-rail-text-hover:  var(--erp-text);
    --erp-rail-text-dim:    #8a8886;
    --erp-rail-wash:        rgba(255, 255, 255, .06);
    --erp-rail-line:        rgba(255, 255, 255, .09);

    --erp-topnav-bg:          #252423;
    --erp-topnav-bg-2:        #252423;
    --erp-topnav-text:        rgba(255, 255, 255, .72);
    --erp-topnav-text-strong: var(--erp-text);
    --erp-topnav-wash:        rgba(255, 255, 255, .10);
    --erp-topnav-line:        rgba(255, 255, 255, .10);
    /* Same indirection as the light theme, on a LIGHTER rung. The dark bar is #252423, so the
       -110 rung used on white would be almost invisible against it — orchid's #86225c on
       near-black is unreadable. -60 keeps the accent recognisable and legible. */
    /* Same chain as the light block above, on the rung dark chrome needs. */
    --erp-topnav-accent:      var(--bs-primary, var(--DS-primary-60, #4db6bd));

    --erp-series-1: #3987e5;
    --erp-series-2: #d95926;

    --erp-shadow-sm: 0 1px 2px rgba(0,0,0,.45);
    --erp-shadow:    0 1px 3px rgba(0,0,0,.5), 0 10px 28px -12px rgba(0,0,0,.6);
    --erp-shadow-lg: 0 20px 56px -18px rgba(0,0,0,.7);
}

/* ---- Accent variants ---------------------------------------------------------
   Same design, different brand: everywhere the teal appears, these swap the whole
   brand ramp (and the matching gradient and app-bar accent). Nothing else changes —
   neutrals, spacing and depth stay Business Central. theme.js sets
   html[data-accent] from the remembered choice before first paint; no attribute
   (or "teal") means the teal ramp above. Each variant carries its own dark-mode
   ramp, tinted lighter the same way the teal one is. */

/* Violet — a dignified Office purple. */
html[data-accent="violet"] {
    --erp-brand-50:   #f3eefb;
    --erp-brand-100:  #e6d9f7;
    --erp-brand-300:  #a583d9;
    --erp-brand-500:  #6f42c1;
    --erp-brand-600:  #59359c;
    --erp-brand-700:  #432878;
    --erp-violet-500: #8a63ce;
    --erp-grad:       linear-gradient(135deg, #432878 0%, #6f42c1 100%);
    --erp-topnav-accent: #6f42c1;
}
html[data-theme="dark"][data-accent="violet"] {
    --erp-brand-50:   #241b3a;
    --erp-brand-100:  #32254f;
    --erp-brand-300:  #8a6fd0;
    --erp-brand-500:  #a78bfa;
    --erp-brand-600:  #bda5fb;
    --erp-brand-700:  #d6c7fd;
    --erp-violet-500: #bda5fb;
    --erp-grad:       linear-gradient(135deg, #8a6fd0 0%, #a78bfa 100%);
    --erp-topnav-accent: #a78bfa;
}

/* Magenta — the Microsoft family's pink, as on Power BI's palette. */
html[data-accent="magenta"] {
    --erp-brand-50:   #fbeaf8;
    --erp-brand-100:  #f6d4f0;
    --erp-brand-300:  #d267c4;
    --erp-brand-500:  #b4009e;
    --erp-brand-600:  #90007e;
    --erp-brand-700:  #6c005f;
    --erp-violet-500: #c433b1;
    --erp-grad:       linear-gradient(135deg, #6c005f 0%, #b4009e 100%);
    --erp-topnav-accent: #b4009e;
}
html[data-theme="dark"][data-accent="magenta"] {
    --erp-brand-50:   #33122e;
    --erp-brand-100:  #491a42;
    --erp-brand-300:  #c94eb8;
    --erp-brand-500:  #e066d0;
    --erp-brand-600:  #e98adc;
    --erp-brand-700:  #f3b5ea;
    --erp-violet-500: #e98adc;
    --erp-grad:       linear-gradient(135deg, #c94eb8 0%, #e066d0 100%);
    --erp-topnav-accent: #e066d0;
}

/* Indigo — the Teams blue-violet. */
html[data-accent="indigo"] {
    --erp-brand-50:   #f0f0fa;
    --erp-brand-100:  #e2e2f6;
    --erp-brand-300:  #9295d8;
    --erp-brand-500:  #5b5fc7;
    --erp-brand-600:  #4649a8;
    --erp-brand-700:  #383b87;
    --erp-violet-500: #7579d1;
    --erp-grad:       linear-gradient(135deg, #383b87 0%, #5b5fc7 100%);
    --erp-topnav-accent: #5b5fc7;
}
html[data-theme="dark"][data-accent="indigo"] {
    --erp-brand-50:   #1f2040;
    --erp-brand-100:  #2c2d59;
    --erp-brand-300:  #7f83d8;
    --erp-brand-500:  #8b90f0;
    --erp-brand-600:  #a5a9f4;
    --erp-brand-700:  #c5c8f9;
    --erp-violet-500: #a5a9f4;
    --erp-grad:       linear-gradient(135deg, #7f83d8 0%, #8b90f0 100%);
    --erp-topnav-accent: #8b90f0;
}

/* The accent dots that stood here were removed with the app-bar accent picker they
   styled. The html[data-accent] ramps above are deliberately kept: theme.js still
   re-applies a previously stored accent before first paint, so deleting the ramps
   would leave that attribute set with nothing behind it. */

/* ---- App-bar utilities panel -------------------------------------------------
   The one button that replaced the old six-icon cluster (bell, gear, help, pin,
   theme toggle, accent dots), so the module names get the width. A plain
   conditional panel anchored under the button — no overlay, nothing modal; it
   closes on its own button or on any navigation. */
.erp-util { position: relative; display: inline-flex; }
.erp-util__pop {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: 70;
    min-width: 260px;
    background: var(--erp-surface);
    border: 1px solid var(--erp-border-strong);
    border-radius: 4px;
    box-shadow: var(--erp-shadow-lg);
    padding: 6px;
    display: flex;
    flex-direction: column;
}
.erp-util__row {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    text-align: left;
    border: 0;
    background: transparent;
    padding: 8px 10px;
    font: inherit;
    font-size: 13px;
    color: var(--erp-text);
    border-radius: 3px;
    cursor: pointer;
    white-space: nowrap;
}
.erp-util__row:hover { background: var(--erp-brand-50); color: var(--erp-brand-700); }
.erp-util__row i { font-size: 14px; color: var(--erp-brand-500); flex: 0 0 auto; }
.erp-util__row--static { cursor: default; }
.erp-util__row--static:hover { background: transparent; color: var(--erp-text); }
.erp-util__row--soon { color: var(--erp-text-muted); opacity: .55; cursor: default; }
.erp-util__row--soon:hover { background: transparent; color: var(--erp-text-muted); }
.erp-util__spacer { flex: 1 1 auto; }
.erp-util__rule { flex: 0 0 1px; background: var(--erp-border); margin: 4px 6px; }
.erp-hotpin.active { color: var(--erp-brand-600); background: var(--erp-topnav-wash); }

/* ---- Utilities panel: notifications, help, and the theme row -------------------
   The panel now has three faces (main / notifications / help) reached by the rows
   themselves, so it stays one small popup instead of growing a second level of
   menus. Wider than the original because a notification carries two lines. */
.erp-util__pop { max-width: 380px; }
.erp-util__back { font-weight: 600; }
.erp-util__note {
    padding: 10px;
    font-size: 12.5px;
    color: var(--erp-text-muted);
    white-space: normal;
}
.erp-util__note--quiet { font-size: 11.5px; opacity: .85; }
.erp-util__helprow {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 3px 0;
}
.erp-util__helprow span { flex: 0 0 108px; }
.erp-util__helprow strong { font-weight: 600; color: var(--erp-text); overflow-wrap: anywhere; }

/* A notification is two stacked lines, so this row opts out of the panel's nowrap. */
.erp-util__row--note { align-items: flex-start; white-space: normal; }
.erp-util__row--note > span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.erp-util__notetitle { font-size: 13px; }
.erp-util__notesub { font-size: 11.5px; color: var(--erp-text-muted); }

/* The count beside "Notifications", and the dot on the three-dot button itself. */
.erp-util__count {
    min-width: 18px;
    padding: 0 5px;
    border-radius: 9px;
    background: var(--erp-brand-500);
    color: #ffffff;
    font-size: 11px;
    font-weight: 600;
    text-align: center;
}
.erp-util__dot {
    position: absolute;
    top: 4px;
    right: 3px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--erp-negative);
    pointer-events: none;
}

/* The theme picker sits inside a panel row, so it must not carry the page-level
   width the Preferences card gave it. */
.erp-util__row--static .erp-themeswitch { margin: 0; }
.erp-util__row--static .erp-themeswitch__open { max-width: 170px; }

/* The page sheet behind the cards now uses its own role, not a computed mix. */
html, body { background: var(--erp-page); }

/* ------- D365 chrome: navy app bar on top, light sitemap rail on the left ------- */
.erp-modulebar {
    background: linear-gradient(180deg, var(--erp-topnav-bg), var(--erp-topnav-bg-2));
    border-bottom: 1px solid var(--erp-topnav-line);
}
.erp-rail {
    background: var(--erp-rail-bg);
    border-right: 1px solid var(--erp-rail-line);
}

.erp-rail__mark,
.erp-modulebar__mark {
    background: var(--erp-grad);
    box-shadow: none;
    border-radius: 4px;
}
.erp-rail__mark { width: 32px; height: 32px; font-size: 12px; font-weight: 700; }

/* Sitemap items — D365's white selection card with the blue indicator bar. */
.erp-nav-item.active,
.erp-hot__item.active {
    background: var(--erp-rail-bg-active);
    color: var(--erp-rail-text-active);
    box-shadow: var(--erp-shadow-sm);
    font-weight: 400;   /* no bold in the menus — the selection card carries the state */
}
.erp-nav-item.active { border-left: 3px solid var(--erp-brand-500); }
.erp-rail__modhead i { color: var(--erp-brand-500); }

/* App-bar tabs — the module NAME now carries the theme accent, not just the icon, so a
   module reads as one coloured unit instead of a tinted glyph beside grey text.
   --erp-topnav-accent is the theme token, so this follows whichever DevExpress theme and
   accent are selected (teal by default, purple on Purple, and the dark-mode variants) rather
   than pinning a colour here.
   Normal weight throughout: the wash background carries the active state, not boldness.
   Hover and active stay on the accent too — flipping the selected tab back to near-black
   would make the one tab you are actually on the only one that is not on-theme. */
.erp-modtab { color: var(--erp-topnav-accent); font-weight: 400; }
.erp-modtab i { color: var(--erp-topnav-accent); }
.erp-modtab:hover { background: var(--erp-topnav-wash); color: var(--erp-topnav-accent); }
.erp-modtab.active { background: var(--erp-topnav-wash); color: var(--erp-topnav-accent); font-weight: 500; }
.erp-modtab.active i { color: var(--erp-topnav-accent); }
.erp-modulebar .erp-navtoggle,
.erp-modulebar .erp-hotpin { color: var(--erp-topnav-text); }
.erp-modulebar .erp-navtoggle:hover,
.erp-modulebar .erp-hotpin:hover { background: var(--erp-topnav-wash); color: var(--erp-topnav-text-strong); }

/* ------- KPI tiles: icon chip, hover lift ------- */
.erp-tile { transition: transform .16s ease, box-shadow .16s ease; }
.erp-tile:hover { transform: translateY(-2px); box-shadow: var(--erp-shadow); }
.erp-tile::before { width: 4px; }
/* BC Role-Center stats: big THIN figures under small-caps labels — the airy
   "2 / 33" look from the reference, not heavy bold numbers. */
.erp-tile__value { font-size: 34px; font-weight: 300; letter-spacing: -.01em; }
.erp-tile__label { font-size: 10px; letter-spacing: .1em; }

/* The label's leading icon becomes a soft rounded chip tinted to the tile role.
   Targets the existing <i> in the markup, so no page needs editing. */
.erp-tile__label > i:first-child {
    width: 26px; height: 26px;
    display: inline-grid; place-items: center;
    border-radius: 8px;
    font-size: 14px;
    background: var(--erp-brand-50);
    color: var(--erp-brand-500);
}
.erp-tile--positive .erp-tile__label > i:first-child { background: var(--erp-positive-soft); color: var(--erp-positive); }
.erp-tile--negative .erp-tile__label > i:first-child { background: var(--erp-negative-soft); color: var(--erp-negative); }
.erp-tile--warning  .erp-tile__label > i:first-child { background: var(--erp-warning-soft);  color: var(--erp-warning); }
.erp-tile--info     .erp-tile__label > i:first-child { background: var(--erp-info-soft);     color: var(--erp-info); }

/* ------- delta chips: pill background ------- */
.erp-delta { padding: 1px 7px; border-radius: 999px; }
.erp-delta--up   { background: var(--erp-positive-soft); }
.erp-delta--down { background: var(--erp-negative-soft); }

/* ------- posted badge onto the brand, table polish ------- */
.erp-badge--posted { background: var(--erp-brand-50); color: var(--erp-brand-600); }

/* DevExpress grids are deliberately left at native theme density (see the
   "DevExpress overrides: REMOVED" note above and Tools/ThemeTest). Running the
   Purple / Blazing Dark skin is what keeps their headers and selection in step
   with the indigo chrome — no grid restyle here, on purpose. */

/* ------- primary buttons: brand gradient (our own .erp-btn helpers) ------- */
.erp-btn--primary,
.erp-modeswitch__btn--on.erp-modeswitch__btn--sales,
.erp-modeswitch__btn--on.erp-modeswitch__btn--purchase { /* keep semantic colours on the switch */ }

/* ------- headings a touch tighter, cards a touch softer ------- */
.erp-topbar__title { font-size: 17px; font-weight: 700; }
.erp-card { border-radius: var(--erp-radius-lg); }
.erp-card__title { font-size: 14px; font-weight: 700; }

/* ------- accessibility: visible focus + reduced motion ------- */
/* Keyboard focus ring on our own chrome (DevExpress controls carry their own).
   :focus-visible so it shows for keyboard users, not on a mouse click. */
.erp-nav-item:focus-visible,
.erp-nav-heading:focus-visible,
.erp-modtab:focus-visible,
.erp-hot__item:focus-visible,
.erp-navtoggle:focus-visible,
.erp-hotpin:focus-visible,
.erp-seg__btn:focus-visible,
.erp-modeswitch__btn:focus-visible,
.erp-btn:focus-visible,
.erp-login__company:focus-visible {
    outline: 2px solid var(--erp-brand-500);
    outline-offset: 2px;
    border-radius: var(--erp-radius-sm);
}
/* On the navy app bar the blue ring is too dark — brighten it there.
   (The rail is light now, so the standard brand ring works there.) */
.erp-modulebar :focus-visible { outline-color: var(--erp-topnav-accent); }

/* Honour the OS "reduce motion" setting: state changes stay instant, no lift. */
@media (prefers-reduced-motion: reduce) {
    .erp-tile,
    .erp-nav-item,
    .erp-modtab,
    .erp-hot__item,
    .erp-btn,
    .erp-seg__btn,
    .erp-login__company { transition: none !important; }
    .erp-tile:hover { transform: none; }
}

/* Selection / focus accents that DevExpress paints from --bs-* are handled by
   running the Purple / Blazing Dark skin; nothing to override here. */

/* =============================================================================
   ENTRY FORMS — QuickBooks Online idiom
   -----------------------------------------------------------------------------
   The document editors (invoice, expense, journal, voucher, quotation, orders,
   receipts, returns) take QBO's anatomy: quiet grey captions over roomy fields,
   hairline-separated form sections, an airy line grid with an uppercase header
   strip, a large right-aligned "balance due" grand total, and a dark sticky
   action bar across the foot of the screen (see EditorActionBar.razor).

   Every form already carries .erp-formsection / .erp-form / .erp-totals /
   .erp-linegrid markup, so this layer restyles all of them at once; only the
   action bar needs a per-form markup change.
   ============================================================================= */

/* --- form sections: the field zone reads as titled groups split by hairlines --- */
.erp-formsection { padding: 2px 0 18px; }
.erp-formsection + .erp-formsection {
    border-top: 1px solid var(--erp-border);
    padding-top: 18px;
}
/* D365 section headers: sentence case, semibold, dark — not small caps. */
.erp-formsection__head {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 14px;
    font-weight: 600;
    color: var(--erp-text);
    margin-bottom: 10px;
}
.erp-formsection__head i { color: var(--erp-brand-500); font-size: 15px; }

/* --- captions: QBO's quiet grey labels, a touch larger and calmer --- */
.erp-form {
    --dxbl-fl-caption-color: var(--erp-text-muted);
    --dxbl-fl-caption-font-weight: 500;
}
.erp-field__label { font-size: 12px; font-weight: 500; }

/* --- line grid: uppercase header strip, roomier rows, hover highlight ---
   .erp-linegrid is the sanctioned exception to native grid density (see the
   note above it); this extends that exception, it does not create a new one. */
.erp-linegrid .dxbl-grid-table > thead > tr > th {
    padding: 8px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--erp-text-muted);
    background: var(--erp-surface-sunken);
}
.erp-linegrid .dxbl-grid-table > tbody > tr > td { padding: 6px 8px; border-right: 1px solid var(--erp-border); }
.erp-linegrid .dxbl-grid-table > thead > tr > th { border-right: 1px solid var(--erp-border); }
.erp-linegrid .dxbl-grid-table > tbody > tr:hover > td { background: var(--erp-surface-sunken); }

/* --- grand total: QBO's balance-due block — small caps over a large figure --- */
.erp-total-row--grand {
    flex-direction: column;
    align-items: flex-end;
    gap: 2px;
}
.erp-total-row--grand .erp-total-row__label {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--erp-text-muted);
}
.erp-total-row--grand .erp-total-row__value {
    font-size: 30px;
    font-weight: 700;
    letter-spacing: -.02em;
    line-height: 1.1;
}

/* --- the sticky action bar (EditorActionBar.razor) ---
   Dark in BOTH modes, like QBO's: it is command chrome, not document surface,
   so it wears the rail's colour and separates save/post/cancel from the form.
   The negative margins bleed it across .erp-content's 22px padding so it spans
   the full work surface edge to edge. */
/* Restyled for D365: a LIGHT command bar (Dynamics' command surfaces are
   white/grey, never dark) stuck to the foot of the screen, separated from the
   work surface by a hairline and a faint lift. */
.erp-actionbar {
    position: sticky;
    bottom: 0;
    z-index: 18;
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin: 24px -22px -22px;
    padding: 10px 24px;
    background: var(--erp-surface);
    border-top: 1px solid var(--erp-border);
    box-shadow: 0 -2px 8px rgba(0, 0, 0, .07);
}
.erp-actionbar__left,
.erp-actionbar__actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.erp-actionbar__spacer { flex: 1 1 auto; }
.erp-actionbar__hint { color: var(--erp-text-muted); font-size: 12px; }

/* =============================================================================
   FIELD LANGUAGE — one look for every editor
   -----------------------------------------------------------------------------
   The entry forms compose several DxFormLayouts per page, so out of the box
   each editor takes its size and border from the theme and its width from its
   own layout — heights drift, borders shout, nothing shares a rhythm. This
   gives every editor on the work surface ONE deliberate shape:

     resting  — soft-filled (sunken surface), quiet border, 40px tall
     hover    — border firms up
     focused  — surface turns white/raised and a brand ring appears

   Styling rides on the editor ROOT (.dxbl-text-edit/.dxbl-memo) and
   :focus-within, so it needs nothing from DevExpress internals beyond the one
   stable wrapper class, and the inner input simply inherits. Scoped to
   .erp-content so popups, grids and the login page are untouched; the line
   grid then opts back out below into QBO's click-to-edit cell style.
   ============================================================================= */

/* Business Central's field, matched from the real page: a compact BOXED input —
   white fill, thin light-grey border, 2px radius, ~30px tall. Hover firms the
   border; focus draws BC's teal-green ring (an inset shadow doubles the border
   without moving a pixel). */
.erp-content .dxbl-text-edit,
.erp-content .dxbl-memo {
    background: var(--erp-surface);
    border-color: var(--erp-border-strong);
    border-radius: 2px;
    min-height: 30px;
    font-family: var(--erp-font);
    transition: border-color .12s ease, box-shadow .12s ease;
}
.erp-content .dxbl-text-edit input,
.erp-content .dxbl-text-edit select,
.erp-content .dxbl-memo textarea {
    background: transparent;
    font: inherit;
    color: var(--erp-text);
}
.erp-content .dxbl-text-edit:hover,
.erp-content .dxbl-memo:hover {
    border-color: var(--erp-text-muted);
}
.erp-content .dxbl-text-edit:focus-within,
.erp-content .dxbl-memo:focus-within {
    border-color: var(--erp-brand-500);
    box-shadow: inset 0 0 0 1px var(--erp-brand-500);
}

/* --- settings and admin FORMS: a capped reading column ----------------------
   The pages that are a form rather than a list — company details, the mail
   server, two-factor, localisation — all had the same fault: a 12-column
   DxFormLayout across the full width of the screen, so a four-character
   currency code owned a 1,400px box and twenty fields ran past two screens.

   Three opt-in classes, used together, fix it without touching a single field:
     .erp-formpage    caps the page's reading column;
     .erp-form--tight tightens the rhythm between rows (these are forms you fill
                      once a year, not ones you type into all day);
     .erp-fli--wide   on a DxFormLayoutItem, for the few fields that genuinely
                      need the room — an address, a footer, a connection string.

   Widths are capped on the EDITOR (.dxbl-text-edit / .dxbl-memo — the root every
   DevExpress editor renders, date, spin and combo included; verified against the
   live DOM) rather than on the column, so the captions stay ruled up while the
   boxes stop at a sane width. */
.erp-formpage { max-width: 1080px; }
.erp-formpage__bar { margin-bottom: 10px; }

.erp-form.erp-form--tight {
    --dxbl-fl-item-spacing-y: .5rem;
    --dxbl-fl-item-spacing-x: 1.25rem;
}
.erp-form.erp-form--tight label,
.erp-form.erp-form--tight .dxbl-fl-cpt {
    font-size: 12px;
    margin-bottom: 2px;
    padding-bottom: 0;
}
.erp-form.erp-form--tight .dxbl-text-edit,
.erp-form.erp-form--tight .dxbl-memo { width: 100%; max-width: 300px; }
.erp-form.erp-form--tight .erp-fli--wide .dxbl-text-edit,
.erp-form.erp-form--tight .erp-fli--wide .dxbl-memo { max-width: 620px; }

/* Phones: every cap comes off. A 300px box on a 360px screen is the same
   mistake as a 1,400px one on a monitor. */
@media (max-width: 720px) {
    .erp-formpage { max-width: none; }
    .erp-form.erp-form--tight .dxbl-text-edit,
    .erp-form.erp-form--tight .dxbl-memo,
    .erp-form.erp-form--tight .erp-fli--wide .dxbl-text-edit,
    .erp-form.erp-form--tight .erp-fli--wide .dxbl-memo { max-width: none; }
    /* The action row's buttons share the width instead of the primary one
       sliding off the right edge. */
    .erp-formpage__bar { flex-wrap: wrap; gap: 8px; }
    .erp-formpage__bar .erp-toolbar__spacer { display: none; }
    .erp-formpage__bar .dxbl-btn { flex: 1 1 130px; justify-content: center; }
}

/* --- BC FastTab density: captions BESIDE the fields, tight rows ------------
   The arrangement that lets a Dynamics form carry dozens of fields without
   feeling crowded: label and field share a row, rows sit close, two pairs per
   line. Opt-in per form via .erp-form--dense. */
.erp-form--dense {
    --dxbl-fl-item-spacing-y: 0.3rem;
    --dxbl-fl-item-spacing-x: 1.5rem;
}
/* A steady caption column keeps the pairs ruled-up down the page, and the
   ::after draws Business Central's signature DOTTED LEADER from the label to
   its field (Vendor Name ········ [field]). Class confirmed against the live
   DOM (.dxbl-fl-cpt). */
.erp-form--dense .dxbl-fl-cpt {
    flex: 0 0 175px;
    max-width: 175px;
    display: flex;
    align-items: center;
    font-size: 12px;
    color: var(--erp-text-muted);
}
.erp-form--dense .dxbl-fl-cpt::after {
    content: "";
    flex: 1 1 auto;
    min-width: 12px;
    margin: 0 10px 0 8px;
    border-bottom: 1px dotted var(--erp-border-strong);
    transform: translateY(2px);
}
.erp-form--dense .dxbl-fl-group { margin: 2px 0 8px; }

/* Captions: D365's quiet regular-weight grey labels — every form, no drift. */
.erp-form label {
    font-size: 12px;
    font-weight: 400;
    color: var(--erp-text-muted);
    margin-bottom: 3px;
}
.erp-form {
    --dxbl-fl-item-spacing-y: 1.1rem;   /* looser vertical rhythm between fields */
}

/* Line grids: click-to-edit cells, D365-style — plain text at rest, the
   underline appears on hover and turns blue in the cell you are editing. */
.erp-content .erp-linegrid .dxbl-text-edit,
.erp-content .erp-linegrid .dxbl-memo {
    min-height: 30px;
    background: transparent;
    border-color: transparent;
    border-radius: 2px 2px 0 0;
}
.erp-content .erp-linegrid .dxbl-text-edit:hover,
.erp-content .erp-linegrid .dxbl-memo:hover {
    background: transparent;
    border-bottom-color: var(--erp-border-strong);
}
.erp-content .erp-linegrid .dxbl-text-edit:focus-within,
.erp-content .erp-linegrid .dxbl-memo:focus-within {
    background: var(--erp-surface-raised);
    border-bottom-color: var(--erp-brand-500);
    box-shadow: inset 0 -1px 0 0 var(--erp-brand-500);
}

/* Same selector as the base rule and later in the file, so it wins without
   !important — the guard forbids !important aimed at DevExpress markup. */
@media (prefers-reduced-motion: reduce) {
    .erp-content .dxbl-text-edit,
    .erp-content .dxbl-memo { transition: none; }
}

/* --- Business Central top navigation: the pages band -----------------------
   BC's Role Center runs ALL navigation across the top: the dark header, then
   the areas, then their pages in a light horizontal band. This is that band —
   the active module's pages as underline tabs, with pinned quick-access pages
   after a divider. On desktop it REPLACES the left rail entirely; the rail
   remains only as the mobile drawer. */
:root { --erp-pagesbar-h: 42px; }

/* Every page of the module visible at once, flowing one after another and
   wrapping into rows. NATURAL widths packed left — equal columns spread short
   labels apart and the band read as scattered; tight chips read as designed.
   A 12-page module settles into two or three quiet rows. */
.erp-pagesbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 3px 4px;
    padding: 6px 14px 8px;
    background: var(--erp-surface);
    border-bottom: 1px solid var(--erp-border);
}

.erp-pagetab {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    border: none;
    border-radius: 4px;
    background: transparent;
    color: var(--erp-brand-600);   /* BC's teal quick links */
    font-size: 13px;
    font-weight: 400;
    white-space: nowrap;
    cursor: pointer;
    transition: color .12s ease, background .12s ease;
}
.erp-pagetab:hover { color: var(--erp-brand-700); background: var(--erp-surface-sunken); }
.erp-pagetab.active {
    color: var(--erp-brand-600);
    background: var(--erp-brand-50);
    font-weight: 400;   /* no bold anywhere in the menu — colour carries the state */
}
.erp-pagetab--soon { opacity: .45; cursor: default; }
.erp-pagetab--soon:hover { background: transparent; color: var(--erp-text-muted); }
.erp-pagetab--pin i { font-size: 11px; color: var(--erp-warning); }
/* A slim vertical rule separates the module's pages from the pinned
   quick-access items; they share the flow rather than taking a row of
   their own. */
.erp-pagesbar__divider { flex: 0 0 1px; height: 18px; background: var(--erp-border-strong); margin: 0 6px; }

/* Section label inside the band — the small "REPORTS" (or "DASHBOARDS", "FINANCIAL")
   heading printed once where a group of pages begins, with a hairline to its left so
   the group reads as a group without stealing the band's one-row rhythm. */
.erp-pagesbar__sect {
    align-self: center;
    margin-left: 10px;
    padding-left: 12px;
    border-left: 1px solid var(--erp-border-strong);
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--erp-text-muted);
    white-space: nowrap;
}

/* ---- Module dropdown menus — the reference's top row -------------------------
   Each module name in the app bar opens a menu of its pages, one at a time,
   with the same section headings the band uses. The invisible backdrop under
   the open menu makes any outside click close it. */
.erp-modtab-wrap { position: relative; display: inline-flex; }
.erp-modtab__caret { font-size: 9px; margin-left: 2px; opacity: .55; }
.erp-modtab.open { color: var(--erp-brand-600); background: var(--erp-surface-sunken); }
.erp-menu-backdrop { position: fixed; inset: 0; z-index: 60; background: transparent; }
.erp-menu-pop {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    z-index: 61;
    min-width: 250px;
    max-height: 72vh;
    overflow-y: auto;
    background: var(--erp-surface);
    border: 1px solid var(--erp-border-strong);
    border-radius: 4px;
    box-shadow: 0 12px 28px rgba(0, 0, 0, .16);
    padding: 6px;
    display: flex;
    flex-direction: column;
}
.erp-menu-pop__item {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    text-align: left;
    border: 0;
    background: transparent;
    padding: 7px 10px;
    font: inherit;
    font-size: 13px;
    color: var(--erp-text);
    border-radius: 3px;
    cursor: pointer;
    white-space: nowrap;
}
.erp-menu-pop__item:hover { background: var(--erp-brand-50); color: var(--erp-brand-700); }
.erp-menu-pop__item i { font-size: 13px; color: var(--erp-brand-500); }
.erp-menu-pop__item--soon { color: var(--erp-text-muted); opacity: .55; cursor: default; }
.erp-menu-pop__item--soon:hover { background: transparent; color: var(--erp-text-muted); }
.erp-menu-pop__sect {
    padding: 9px 10px 3px;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--erp-text-muted);
}
.erp-menu-pop__rule { flex: 0 0 1px; background: var(--erp-border); margin: 4px 6px; }

/* ---- Rotating Role Center headline ------------------------------------------
   The reference's sliding Headline: messages come and go, each fading up as it
   arrives, with dots to jump between them. Reduced-motion users get the message
   swap with no movement (the global reduced-motion rule quiets the animation). */
.erp-rc__headline--fade { animation: erp-headfade .45s ease; }
@keyframes erp-headfade {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
}
.erp-rc__dots { display: flex; gap: 7px; margin-top: 14px; }
.erp-rc__dot {
    width: 8px;
    height: 8px;
    padding: 0;
    border-radius: 50%;
    border: 1px solid var(--erp-brand-500);
    background: transparent;
    cursor: pointer;
}
.erp-rc__dot.active { background: var(--erp-brand-500); }

/* Desktop: navigation lives on top, Business Central-fashion — no left panel.
   The rail markup stays in the page purely as the mobile drawer, and the
   hamburger goes with it; the compact brand shows in the app bar instead. */
@media (min-width: 981px) {
    .erp-rail { display: none; }
    .erp-shell.nav-open .erp-rail { display: none; }
    .erp-navtoggle { display: none; }
    .erp-modulebar__brand { display: flex; }
}

/* The pages band wraps to whatever height its items need, so it scrolls away
   with the content rather than pinning; only the app bar and the page title
   bar stay stuck. */
.erp-topbar { top: var(--erp-modbar-h); }
.erp-totals { top: calc(var(--erp-modbar-h) + var(--erp-topbar-h) + 14px); }

/* --- the D365 app-bar cluster ---------------------------------------------
   Global search + bell/gear/help + the user's avatar monogram, riding in the
   navy bar exactly where Dynamics puts them. The search and the icon buttons
   are UI-complete but inert for now — behaviours get wired later. */
.erp-appbar-search {
    display: flex;
    align-items: center;
    gap: 8px;
    height: 32px;
    padding: 0 12px;
    border-radius: 4px;
    background: var(--erp-topnav-wash);
    color: var(--erp-topnav-text);
    min-width: 220px;
    flex: 0 1 260px;
}
.erp-appbar-search i { font-size: 13px; }
/* The box is a DxTextBox now, so the rules target its inner input and strip the control's
   own chrome — the pill around it is .erp-appbar-search itself. */
.erp-appbar-search { position: relative; }
.erp-appbar-search__box { flex: 1 1 auto; min-width: 0; }
.erp-appbar-search__box,
.erp-appbar-search__box .dxbl-text-edit {
    background: transparent !important;
    border: 0 !important;
    box-shadow: none !important;
}
.erp-appbar-search input {
    background: transparent;
    border: 0;
    outline: 0;
    color: var(--erp-topnav-text-strong);
    font: inherit;
    font-size: 13px;
    width: 100%;
    min-width: 0;
}
.erp-appbar-search input::placeholder { color: var(--erp-topnav-text); }
@media (max-width: 1180px) { .erp-appbar-search { display: none; } }

/* ---- Global search results ----------------------------------------------------
   A plain conditional block anchored under the box — the same no-overlay, no-timer
   pattern the utilities panel and the Role Center reports panel use, so there is
   nothing that can hang open over the page. */
.erp-searchpop {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    width: 420px;
    max-width: 92vw;
    max-height: 60vh;
    overflow-y: auto;
    z-index: 60;
    padding: 6px;
    border-radius: 6px;
    background: var(--erp-surface-raised);
    border: 1px solid var(--erp-border);
    box-shadow: 0 8px 28px rgba(0, 0, 0, .18);
}
.erp-searchpop__note { padding: 12px; font-size: 12.5px; color: var(--erp-text-muted); }
.erp-searchpop__group {
    padding: 8px 10px 4px;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--erp-text-muted);
}
.erp-searchpop__row {
    display: flex;
    align-items: baseline;
    gap: 8px;
    width: 100%;
    padding: 7px 10px;
    border: 0;
    border-radius: 4px;
    background: transparent;
    color: var(--erp-text);
    font-size: 13px;
    text-align: left;
    cursor: pointer;
}
.erp-searchpop__row:hover { background: var(--erp-surface-sunken); }
.erp-searchpop__row i { color: var(--erp-brand-500); font-size: 13px; flex: 0 0 auto; }
.erp-searchpop__title { font-weight: 500; }
.erp-searchpop__sub {
    margin-left: auto;
    padding-left: 12px;
    font-size: 11.5px;
    color: var(--erp-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.erp-avatar {
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: var(--erp-brand-600);
    color: #ffffff;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: .02em;
    margin-left: 2px;
    /* It opens this user's quick menu now, so it is a button: same size, same colour, same
       monogram - the only change is that it answers when pressed. */
    cursor: pointer;
    border: 0;
    padding: 0;
    font-family: inherit;
    line-height: 1;
}

.erp-avatar:hover,
.erp-avatar.active {
    /* Brightened rather than recoloured: a second brand tone in the bar would read as a
       different control, and this one has to stay recognisable as the person's monogram. */
    filter: brightness(1.12);
}

.erp-avatar:focus-visible {
    outline: 2px solid var(--erp-brand-600);
    outline-offset: 2px;
}

/* --- the monogram's quick menu ---------------------------------------------- */
.erp-userwrap { position: relative; }
.erp-userpop { min-width: 250px; }

.erp-userpop__who { padding: 6px 10px 4px; }
.erp-userpop__name { font-weight: 600; }

.erp-userpop__co {
    font-size: 11px;
    color: var(--erp-text-muted);
}

/* The row is a link plus a discard control, so the two share a line and the link takes
   whatever is left - the name must not be truncated to make room for an x. */
.erp-userpop__row {
    display: flex;
    align-items: center;
    gap: 2px;
}

.erp-userpop__go { flex: 1 1 auto; min-width: 0; }

.erp-userpop__drop {
    flex: 0 0 auto;
    width: 24px;
    height: 24px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 3px;
    background: transparent;
    color: var(--erp-text-muted);
    cursor: pointer;
}

.erp-userpop__drop:hover {
    background: var(--erp-danger-050, rgba(200, 0, 0, .08));
    color: var(--erp-danger, #b3261e);
}

.erp-userpop__count {
    font-size: 11px;
    color: var(--erp-text-muted);
    font-variant-numeric: tabular-nums;
}

/* --- picking the quick menu from the whole menu ------------------------------
   Three hundred and fifty-six tick boxes in one white column is a list, not a
   screen. Every colour here comes from the theme tokens, so the panel wears
   whatever accent the operator picked rather than a palette of its own. */
.erp-qp { display: flex; flex-direction: column; gap: 12px; }

.erp-qp__bar {
    display: flex;
    align-items: center;
    gap: 10px;
}

/* The magnifier sits INSIDE the field, so the search reads as one control. */
.erp-qp__search {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
}

.erp-qp__search > .bi-search {
    position: absolute;
    left: 10px;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
    pointer-events: none;
    color: var(--erp-text-muted);
    font-size: 13px;
}

.erp-qp__searchbox input { padding-left: 24px; }
.erp-qp__searchbox .dxbl-text-edit-input { padding-left: 24px; }

.erp-qp__tally {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 12px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--erp-brand-700);
    background: var(--erp-brand-50);
    border: 1px solid var(--erp-brand-300);
}

.erp-qp__tally span { font-weight: 400; opacity: .75; }

/* Full is a state worth seeing before you hunt for a box that will not tick. */
.erp-qp__tally.is-full {
    color: var(--erp-text-inverse);
    background: var(--erp-brand-500);
    border-color: var(--erp-brand-600);
}

/* --- what is already chosen, as removable chips --- */
.erp-qp__chosen {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding: 10px;
    border-radius: var(--erp-radius);
    background: var(--erp-surface-sunken);
    border: 1px solid var(--erp-border);
}

.erp-qp__chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 8px 4px 10px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    color: var(--erp-text-inverse);
    background: var(--erp-brand-500);
    border: 1px solid var(--erp-brand-600);
}

.erp-qp__chip:hover { background: var(--erp-brand-600); }
.erp-qp__chipx { font-size: 9px; opacity: .8; }

.erp-qp__note {
    display: flex;
    gap: 8px;
    align-items: flex-start;
    padding: 8px 10px;
    font-size: 12px;
    border-radius: var(--erp-radius-sm);
    color: var(--erp-brand-700);
    background: var(--erp-brand-50);
    border-left: 3px solid var(--erp-brand-500);
}

/* --- the menu itself, grouped by module ---
   Columns, because twelve modules stacked vertically is the same endless scroll in
   a different shape; the browser packs the groups and the eye gets whole modules. */
/* GRID, not CSS columns. Multi-column inside a fixed-height scroller lays the
   overflow out SIDEWAYS - it produced a horizontal scrollbar and a third module
   sliced down the middle. Grid flows down and scrolls the way a list should. */
.erp-qp__groups {
    max-height: 54vh;
    overflow-y: auto;
    overflow-x: hidden;
    padding-right: 6px;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
    align-items: start;
    gap: 12px;
}

.erp-qp__group {
    padding: 8px;
    padding: 8px;
    border-radius: var(--erp-radius);
    background: var(--erp-surface);
    border: 1px solid var(--erp-border);
}

.erp-qp__group:hover { border-color: var(--erp-brand-300); }

.erp-qp__grouphead {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 2px 2px 8px;
    border-bottom: 1px solid var(--erp-border);
    margin-bottom: 6px;
}

/* The module's own icon in the theme colour: the group is recognisable before it is read. */
.erp-qp__badge {
    flex: 0 0 auto;
    width: 24px;
    height: 24px;
    display: grid;
    place-items: center;
    border-radius: var(--erp-radius-sm);
    font-size: 12px;
    color: var(--erp-text-inverse);
    background: linear-gradient(135deg, var(--erp-brand-500), var(--erp-brand-700));
}

.erp-qp__groupname {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .01em;
    flex: 1 1 auto;
    min-width: 0;
}

.erp-qp__groupn {
    font-size: 11px;
    font-variant-numeric: tabular-nums;
    color: var(--erp-text-muted);
}

.erp-qp__page {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 6px;
    border-radius: var(--erp-radius-sm);
    font-size: 12px;
    cursor: pointer;
}

.erp-qp__page:hover { background: var(--erp-surface-sunken); }

/* Chosen is tinted, not merely bolder - the ticks have to be findable at a glance
   in a panel this wide. */
.erp-qp__page.is-on {
    font-weight: 600;
    color: var(--erp-brand-700);
    background: var(--erp-brand-50);
}

.erp-qp__pagename { min-width: 0; overflow-wrap: anywhere; }

.erp-qp__empty {
    padding: 28px 12px;
    text-align: center;
    color: var(--erp-text-muted);
    font-size: 13px;
}

.erp-qp__empty .bi { display: block; font-size: 22px; margin-bottom: 8px; opacity: .6; }

/* One column on a phone: three would each be too narrow to read a page name in. */
@media (max-width: 720px) {
    .erp-qp__groups { columns: 1; }
    .erp-qp__bar { flex-wrap: wrap; }
}

/* --- document command strip & party snapshot --------------------------------
   The document page's menus and side panel — the reference's PATTERNS in this
   app's own dress: pill commands on a white strip (not flat text menus), and
   rounded tinted history tiles (not solid squares). Every command is real. */
.erp-cmdbar {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-wrap: wrap;
    background: var(--erp-surface);
    border: 1px solid var(--erp-border);
    border-radius: 8px;
    padding: 6px 10px;
    margin-bottom: 16px;
}
.erp-cmd {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 6px 12px;
    border: none;
    border-radius: 4px;
    background: transparent;
    color: var(--erp-text);
    font-size: 13px;
    cursor: pointer;
    transition: background .12s ease, color .12s ease;
}
.erp-cmd i { color: var(--erp-brand-500); font-size: 13px; }
.erp-cmd:hover { background: var(--erp-surface-sunken); }
.erp-cmd--primary { background: var(--erp-brand-500); color: #ffffff; }
.erp-cmd--primary i { color: #ffffff; }
.erp-cmd--primary:hover { background: var(--erp-brand-600); }
.erp-cmdbar__sep { width: 1px; height: 18px; background: var(--erp-border-strong); margin: 0 6px; }

.erp-snapfacts { display: flex; flex-direction: column; gap: 6px; }
.erp-snapfacts > div { display: flex; gap: 10px; font-size: 12.5px; align-items: baseline; }
.erp-snapfacts span {
    flex: 0 0 62px;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--erp-text-muted);
}
.erp-snapfacts strong { font-weight: 500; overflow-wrap: anywhere; }
.erp-minicues {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
    margin-top: 12px;
}
.erp-minicue {
    border-radius: 6px;
    background: var(--erp-brand-50);
    color: var(--erp-brand-700);
    padding: 8px 10px;
}
.erp-minicue span {
    display: block;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    opacity: .8;
}
.erp-minicue strong { font-size: 20px; font-weight: 300; font-variant-numeric: tabular-nums; }
.erp-minicue--soon { background: var(--erp-surface-sunken); color: var(--erp-text-muted); }

/* The grouped statistics list under the tiles — BC's label→value rows with the
   figures in teal, small-caps group headings between. */
.erp-statlist { margin-top: 12px; }
.erp-statlist__group {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--erp-text-muted);
    margin: 10px 0 3px;
    padding-bottom: 2px;
    border-bottom: 1px solid var(--erp-border);
}
.erp-statlist__row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 12px;
    font-size: 12.5px;
    padding: 3px 0;
}
.erp-statlist__row span { color: var(--erp-text-muted); }
.erp-statlist__row strong {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--erp-brand-600);
    white-space: nowrap;
}

/* --- the ROLE CENTER: Business Central's module home ------------------------
   The page a module OPENS ON: kicker + huge thin insight headline with the
   teal figure, an Actions block of teal links, Activities stats as big thin
   underlined numbers, and the teal cue tiles. Matched from the IC SALES
   Role Center screenshot. */
.erp-rc {
    background: var(--erp-surface);
    border: 1px solid var(--erp-border);
    border-radius: 8px;
    padding: 24px 30px 28px;
}
.erp-rc__top { display: flex; gap: 48px; flex-wrap: wrap; }
.erp-rc__insight { flex: 1 1 420px; min-width: 300px; }
.erp-rc__kicker {
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--erp-text-muted);
    margin-bottom: 10px;
}
.erp-rc__headline {
    font-size: 34px;
    font-weight: 300;
    line-height: 1.3;
    letter-spacing: -.01em;
    color: var(--erp-text);
    max-width: 580px;
}
.erp-rc__headline strong { color: var(--erp-brand-500); font-weight: 300; }
/* The Actions panel: a COLUMN beside the headline, never a band across the page.

   max-width is the whole point. flex-grow alone meant the panel took half of whatever the
   monitor was, so the same launcher that read as three tidy columns on a laptop opened out
   into six or seven on a wide screen — the buttons marching left to right with the eye
   travelling further for every one of them. Capped, the grid tops out at three columns and
   grows DOWNWARD instead, which is the shape Sales has and the one that stays scannable.
   It is a max, not a width: on a narrow screen .erp-rc__top wraps and the panel goes full
   width underneath, as it should. */
.erp-rc__actions { flex: 1 1 360px; max-width: 700px; }
.erp-rc__actions-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
    gap: 6px 24px;
}
.erp-rc__action {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 4px 0;
    border: none;
    background: transparent;
    color: var(--erp-brand-600);
    font-size: 13px;
    text-align: left;
    cursor: pointer;
}
.erp-rc__action:hover { color: var(--erp-brand-700); text-decoration: underline; }
.erp-rc__action i { font-size: 12px; }
/* The toggled state of the Reports action — the panel it opens sits right below. */
.erp-rc__action.active { color: var(--erp-brand-700); font-weight: 600; }
.erp-rc__section { margin-top: 28px; }
.erp-rc__sechead {
    font-size: 15px;
    font-weight: 400;
    color: var(--erp-text);
    padding-bottom: 8px;
    border-bottom: 1px solid var(--erp-border-strong);
    margin-bottom: 16px;
}
/* .erp-rc__actions--wide is GONE, deliberately.

   It dropped the panel under the headline at full page width, on the reasoning that a 53-entry
   module needs the room. Tabs had already solved that — one group shows at a time, so the widest
   group is sixteen buttons, not fifty-three — and the full width bought nothing but a run of
   buttons stretching the whole screen while every other module kept a tidy column. Two launchers
   looking like two different applications is a worse cost than four extra rows.

   Removed rather than left as a no-op so nobody re-adds the class expecting it to do something. */

/* A line of context under a Role Centre figure — "3 left the company", "8 payslips, net".
   The figure alone is a number; this is what makes it a fact. */
.erp-rc-stat__note {
    font-size: 11.5px;
    color: var(--erp-text-muted);
    margin-top: 2px;
}

/* Two short lists side by side, replacing the dashboard's bar charts. A department with eleven
   people and a leave type worth thirty days need a label and a number, not an axis. */
.erp-rc__splits {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 10px 48px;
    align-items: start;
}
.erp-rc__split { min-width: 0; }
.erp-rc__line {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 4px 0;
    border-bottom: 1px solid var(--erp-border);
}
.erp-rc__line:last-child { border-bottom: none; }
.erp-rc__linelabel {
    flex: 1 1 auto; min-width: 0;
    font-size: 13px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.erp-rc__linevalue {
    flex: 0 0 auto;
    font-size: 13px; font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* The group-tab strip.

   The buttons inside it are .erp-modtab — the MODULE buttons' own class, reused rather than
   copied, so Overview/Time/Leave/Pay read exactly like Reports/Masters/Inventory. An earlier
   attempt duplicated only the geometry into a separate .erp-rc__tab and kept page colours; that
   was the wrong half. .erp-modtab is white-on-dark by design because the module bar is a dark
   strip, so the class needs that background to be itself.

   This provides it: the same --erp-rail-bg the module bar uses, as an inset strip. Rounded and
   padded rather than full-bleed, because it sits inside a card rather than across the app. */
.erp-rc__tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding: 6px 8px;
    margin: -2px 0 16px;
    background: var(--erp-rail-bg);
    border: 1px solid var(--erp-rail-line);
    border-radius: 10px;
}

/* Back to the module's Role Centre, in the header on every page that is not already it.
   Replaced the band's "Home" tab — one destination, one control. */
.erp-topbar__home {
    background: none; border: none; cursor: pointer;
    color: var(--erp-text-muted); padding: 4px 8px 4px 0; margin-right: 2px;
    font-size: 15px; line-height: 1;
}
.erp-topbar__home:hover { color: var(--erp-brand-600); }

.erp-rc__stats { display: flex; gap: 56px; flex-wrap: wrap; }
.erp-rc-stat { min-width: 150px; }
.erp-rc-stat__label {
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--erp-text-muted);
    margin-bottom: 4px;
}
.erp-rc-stat__value {
    font-size: 44px;
    font-weight: 300;
    line-height: 1.15;
    padding-bottom: 4px;
    border-bottom: 1px solid var(--erp-border-strong);
    font-variant-numeric: tabular-nums;
}
.erp-rc-stat__more {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-top: 8px;
    font-size: 12.5px;
    color: var(--erp-brand-600);
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
}
.erp-rc-stat__more:hover { color: var(--erp-brand-700); text-decoration: underline; }
.erp-cues { display: flex; gap: 28px; flex-wrap: wrap; }
.erp-cues__grouplabel {
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--erp-text-muted);
    margin-bottom: 8px;
}
.erp-cues__row { display: flex; gap: 4px; flex-wrap: wrap; }
.erp-cue {
    width: 132px;
    min-height: 122px;
    background: var(--erp-brand-500);
    color: #ffffff;
    border: none;
    border-radius: 0;
    text-align: left;
    padding: 10px 12px 8px;
    display: flex;
    flex-direction: column;
    cursor: pointer;
    transition: background .12s ease;
}
.erp-cue:hover { background: var(--erp-brand-600); }
.erp-cue--muted { background: var(--erp-surface-sunken); color: var(--erp-text-muted); cursor: default; }
.erp-cue--muted:hover { background: var(--erp-surface-sunken); }
.erp-cue__label {
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: .05em;
    text-transform: uppercase;
    line-height: 1.35;
}
.erp-cue__value {
    font-size: 36px;
    font-weight: 300;
    margin-top: auto;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
}
.erp-cue__go { font-size: 13px; opacity: .85; }
@media (prefers-reduced-motion: reduce) { .erp-cue { transition: none; } }

/* --- the D365 record header -----------------------------------------------
   Every record form leads with its identity — small-caps record type over the
   record name — and the key facts (balance, credit, status) read out on the
   right, exactly as a Dynamics form header does. */
.erp-recordhead {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 24px;
    flex-wrap: wrap;
    padding-bottom: 14px;
    margin-bottom: 16px;
    border-bottom: 1px solid var(--erp-border);

    /* Sized against ITSELF, not the viewport. This header sits in the right pane of a
       split screen (customer, supplier), so its width is a fraction of the window and no
       viewport media query can tell when it has run out of room. */
    container-type: inline-size;
    container-name: recordhead;
}
.erp-recordhead__type {
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--erp-text-muted);
}
/* BC's record title: large but LIGHT — "21000012 · Tires and Tubes Inc." */
.erp-recordhead__name {
    font-size: 26px;
    font-weight: 400;
    letter-spacing: -.01em;
    line-height: 1.2;
    margin-top: 2px;
}
.erp-recordhead__facts { display: flex; gap: 28px; align-items: flex-end; flex-wrap: wrap; }

/* Below the width where the facts still fit beside the name, they drop under it — and a
   wrapping flex row leaves them scattered: every fact is as wide as its own number, so the
   columns line up with nothing, and the last one is orphaned on a line of its own with a
   gap beside it. On the customer master that reads as four loose labels, not a header.

   Under the name they become an even grid instead, the same auto-fit shape .erp-recgrid
   uses for the fields below, so the facts sit in aligned columns at every width. */
@container recordhead (max-width: 700px) {
    .erp-recordhead__facts {
        width: 100%;
        display: grid;
        grid-template-columns: repeat(4, minmax(0, 1fr));
        gap: 12px 20px;
        align-items: end;
    }
}

/* Four and two, never three: every header here carries two to four facts, and an odd column
   count is what leaves the last one stranded on a row of its own beside a hole. */
@container recordhead (max-width: 460px) {
    .erp-recordhead__facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.erp-recordhead__fact { min-width: 0; }
.erp-recordhead__fact span {
    display: block;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--erp-text-muted);
    margin-bottom: 2px;
}
.erp-recordhead__fact strong {
    font-size: 14.5px;
    font-weight: 650;
    font-variant-numeric: tabular-nums;
}

/* --- wide editors: D365's record surface ----------------------------------
   A record with many fields is edited on a WIDE surface with its sections
   flowing in two columns (D365's fasttab arrangement), not in a tall strip.
   Pages wrap their .erp-formsection blocks in this grid and pass a wide
   EditorWidth; on a narrow screen the columns stack back to one. */
.erp-editor-cols {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 0 32px;
    align-items: start;
}
@media (max-width: 900px) {
    .erp-editor-cols { grid-template-columns: 1fr; }
}

/* A wide editor must still fit a narrow window. Sizing, not styling — the
   guard allows exactly this selector for exactly this reason. */
.dxbl-modal { max-width: 96vw; }

/* --- grids: D365's list anatomy -------------------------------------------
   Grey semibold headers on the white surface with a firm rule beneath,
   hairline row separators, quiet grey hover. Scoped to grids inside our
   cards, reaching DevExpress markup only under our own scope. */
.erp-card .dxbl-grid .dxbl-grid-table > thead > tr > th {
    background: var(--erp-surface);
    color: var(--erp-text-muted);
    font-size: 12px;
    font-weight: 600;
    border-bottom: 1px solid var(--erp-border-strong);
}
.erp-card .dxbl-grid .dxbl-grid-table > tbody > tr > td {
    border-bottom: 1px solid var(--erp-border);
}
.erp-card .dxbl-grid .dxbl-grid-table > tbody > tr:hover > td {
    background: var(--erp-surface-sunken);
}


/* =============================================================================
   L-06 — the reconnect overlay
   -----------------------------------------------------------------------------
   Styling for the #components-reconnect-modal supplied in App.razor. Blazor toggles
   the CLASS on that element — components-reconnect-show / -failed / -rejected /
   -hide — and expects the page to decide what each state looks like. Supplying the
   element without these rules would leave all three messages visible at once, which
   is why this block is not optional.

   It ships in the SHARED stylesheet rather than the web host's own so the desktop
   BlazorWebView gets identical treatment; that host reconnects far less often, but
   when it does it should not look like a different application.

   Hidden by default. The framework adds a state class only when something is wrong,
   so anything not explicitly shown below must stay off the screen.
   ============================================================================= */
.erp-reconnect { display: none; }

/* Any state the framework raises turns the backdrop on. A dim rather than a blackout:
   the operator should still see their work sitting behind it, because the entire
   message is "this is still here, don't panic and don't reload". */
#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 2000;
    align-items: center;
    justify-content: center;
    padding: 20px;
    background: rgba(17, 17, 17, .45);
    backdrop-filter: blur(2px);
}

.erp-reconnect__card {
    width: 100%;
    max-width: 420px;
    padding: 24px;
    border-radius: var(--erp-radius, 8px);
    background: var(--erp-surface-raised, #ffffff);
    border: 1px solid var(--erp-border, #e5e5e5);
    box-shadow: 0 18px 48px rgba(0, 0, 0, .28);
    text-align: center;
}

/* Only the panel matching the current state is shown; the other two stay hidden.
   Each rule is scoped to its own state class, so exactly one can ever be visible. */
.erp-reconnect__show,
.erp-reconnect__failed,
.erp-reconnect__rejected { display: none; }

.components-reconnect-show     .erp-reconnect__show,
.components-reconnect-failed   .erp-reconnect__failed,
.components-reconnect-rejected .erp-reconnect__rejected { display: block; }

.erp-reconnect__title {
    font-size: 16px;
    font-weight: 600;
    color: var(--erp-text, #212529);
    margin-bottom: 8px;
}

.erp-reconnect__text {
    font-size: 13px;
    line-height: 1.5;
    color: var(--erp-text-muted, #6c757d);
}

.erp-reconnect__btn {
    margin-top: 16px;
    padding: 8px 18px;
    font-size: 13px;
    font-weight: 600;
    color: #fff;
    background: var(--erp-brand-500, #fe7109);
    border: none;
    border-radius: var(--erp-radius-sm, 6px);
    cursor: pointer;
}
.erp-reconnect__btn:hover { background: var(--erp-brand-600, #cb5a07); }

/* The reload escape hatch. Deliberately quieter than the reconnect button above it —
   it is the option that costs unsaved work, so it should not be the one the eye
   lands on first. */
.erp-reconnect__fine {
    margin-top: 14px;
    padding-top: 12px;
    border-top: 1px solid var(--erp-border, #e5e5e5);
    font-size: 11px;
    line-height: 1.45;
    color: var(--erp-text-muted, #6c757d);
}

.erp-reconnect__link {
    display: inline;
    padding: 0;
    margin-left: 2px;
    font: inherit;
    color: var(--erp-brand-600, #cb5a07);
    background: none;
    border: none;
    text-decoration: underline;
    cursor: pointer;
}

/* A spinner rather than a progress bar: the wait has no known length, and a bar that
   fills and then keeps waiting reads as a hang. */
.erp-reconnect__spinner {
    width: 28px;
    height: 28px;
    margin: 0 auto 14px;
    border: 3px solid var(--erp-border, #e5e5e5);
    border-top-color: var(--erp-brand-500, #fe7109);
    border-radius: 50%;
    animation: erp-reconnect-spin .9s linear infinite;
}

@keyframes erp-reconnect-spin { to { transform: rotate(360deg); } }

/* Respect a reduced-motion preference: keep the ring, drop the rotation. */
@media (prefers-reduced-motion: reduce) {
    .erp-reconnect__spinner { animation: none; }
}

/* =============================================================================
   DOCUMENT VIEWER (Components/DocumentViewer.razor + js/docviewer.js)
   -----------------------------------------------------------------------------
   The in-app reader. Pages are rasterised onto a canvas this application owns
   rather than handed to the browser's own PDF reader, so all of this styles OUR
   chrome around OUR canvas — there is no third-party viewer UI to fight with.
   ========================================================================== */

.erp-viewer { display: flex; flex-direction: column; height: 100%; min-height: 0; position: relative; }

.erp-viewer__toolbar {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    padding: 8px 12px;
    border-bottom: 1px solid var(--erp-border);
    background: var(--erp-surface-sunken);
    /* Stays put while the page scrolls under it: a 40-page scan otherwise leaves
       the operator scrolling back up to reach Next. */
    position: sticky;
    top: 0;
    z-index: 2;
}

.erp-viewer__pagebox { width: 62px; }
.erp-viewer__of { font-size: 12px; color: var(--erp-text-muted); }
.erp-viewer__zoom { font-size: 12px; min-width: 46px; text-align: center; font-variant-numeric: tabular-nums; }
.erp-viewer__divider { width: 1px; height: 20px; background: var(--erp-border); margin: 0 4px; }

/* The scroll container for the rendered page. Grey rather than white so the edge
   of a white A4 scan is visible — a page that bleeds into its background looks
   cropped, and on a scanned document "is anything missing" is the first question. */
.erp-viewer__stage {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
    padding: 12px;
    text-align: center;
    background: var(--erp-surface-sunken);
}

.erp-viewer__stage--hidden { display: none; }

.erp-viewer__page {
    display: inline-block;
    background: #fff;
    box-shadow: 0 1px 6px rgba(0, 0, 0, .18);
    /* Not a defence — a determined copy is always possible — but it stops the
       accidental drag of a document onto the desktop. */
    user-select: none;
    -webkit-user-drag: none;
}

.erp-viewer__text { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 12px; }

.erp-viewer__trail { padding: 10px 12px; border-bottom: 1px solid var(--erp-border); max-height: 38%; overflow: auto; }

.erp-viewer__busy {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Translucent rather than opaque: the page underneath stays visible while a
       zoom re-renders, so the viewer does not blink white on every button. */
    background: rgba(255, 255, 255, .45);
    pointer-events: none;
}

/* The popup body carries the viewer's own scrolling, so DevExpress must not add
   its own padding around it or the toolbar detaches from the header. */
.erp-viewer-popup .dxbl-modal-body { padding: 0 !important; overflow: hidden !important; }

/* -----------------------------------------------------------------------------
   PRINTING THE DOCUMENT, AND NOTHING ELSE

   docviewer.js appends #erp-viewer-print (one <img> per rendered page, watermark
   already burned in) and puts .erp-printing on <html>. Everything else in the
   body is hidden for the duration, so the application's rail, the toolbar and
   the popup chrome do not reach the paper.
   -------------------------------------------------------------------------- */
#erp-viewer-print { display: none; }

@media print {
    html.erp-printing body > *:not(#erp-viewer-print) { display: none !important; }
    html.erp-printing #erp-viewer-print { display: block !important; }
    html.erp-printing #erp-viewer-print img {
        display: block;
        width: 100%;
        page-break-after: always;
    }
    html.erp-printing #erp-viewer-print img:last-child { page-break-after: auto; }
    html.erp-printing #erp-viewer-print pre { white-space: pre-wrap; word-break: break-word; }
}

/* =============================================================================
   DOCUMENT CONTROL — phase 3 (discussion threads, dashboard, bars)
   ========================================================================== */

/* ---- Discussion (Components/CommentPanel.razor) ---------------------------- */
.erp-thread { display: flex; flex-direction: column; gap: 10px; }

.erp-thread__item {
    border-left: 2px solid var(--erp-border);
    padding: 2px 0 2px 10px;
}

.erp-thread__meta {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    color: var(--erp-text-muted);
}

.erp-thread__meta strong { color: var(--erp-text); }

.erp-thread__body { font-size: 13px; white-space: pre-wrap; word-break: break-word; margin-top: 2px; }

/* A withdrawn remark keeps its row: on a controlled document, the fact that something was said
   and taken back is itself part of how a decision was reached. */
.erp-thread__body--gone { font-style: italic; color: var(--erp-text-muted); }

.erp-thread__compose { margin-top: 14px; border-top: 1px solid var(--erp-border); padding-top: 12px; }

/* ---- Dashboard (Pages/Documents/DocumentDashboard.razor) ------------------- */
.erp-dashgrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: 16px;
    padding: 4px;
}

/* The activity report is a table and needs the full width; the summary cards do not. */
.erp-dashgrid__wide { grid-column: 1 / -1; }

.erp-statlist { display: flex; flex-direction: column; }

.erp-statlist__row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 7px 2px;
    border-bottom: 1px solid var(--erp-border);
    font-size: 13px;
}

.erp-statlist__row:last-child { border-bottom: none; }

/* A count that is also a way in — a dashboard number nobody can click is one somebody has to go
   and find for themselves. */
.erp-statlist__row--action { cursor: pointer; }
.erp-statlist__row--action:hover { background: var(--erp-surface-sunken); }

/* ---- Twelve bars, in CSS ---------------------------------------------------
   A charting library for twelve numbers would be a dependency to keep current for the rest of
   the product's life. */
.erp-bars {
    display: flex;
    align-items: flex-end;
    gap: 6px;
    height: 140px;
    padding-top: 8px;
}

.erp-bars__col {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    align-items: center;
    height: 100%;
    min-width: 0;
}

.erp-bars__bar {
    width: 100%;
    max-width: 34px;
    background: var(--erp-brand-500, #6366f1);
    border-radius: 3px 3px 0 0;
    /* A floor, so a month with one file is still visibly a month rather than nothing. */
    min-height: 3px;
}

.erp-bars__n { font-size: 11px; font-variant-numeric: tabular-nums; margin-top: 3px; }

.erp-bars__label {
    font-size: 10.5px;
    color: var(--erp-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}

/* ---- Drag and drop onto the attachment panel ------------------------------- */
.erp-drop { transition: box-shadow .12s ease, background-color .12s ease; }

/* The whole card highlights, because the whole card is the target. A small dashed rectangle
   somebody has to aim at is a feature people go back to the button for. */
.erp-drop--over {
    background: var(--erp-surface-sunken);
    box-shadow: inset 0 0 0 2px var(--erp-brand-500, #6366f1);
}

/* ---- Comparing two versions side by side ----------------------------------- */
.erp-viewer__stages { display: flex; flex: 1 1 auto; min-height: 0; gap: 1px; background: var(--erp-border); }

.erp-viewer__side { flex: 1 1 50%; display: flex; flex-direction: column; min-width: 0; min-height: 0; }

.erp-viewer__sidehead {
    padding: 5px 10px;
    font-size: 11.5px;
    font-weight: 600;
    background: var(--erp-surface-sunken);
    border-bottom: 1px solid var(--erp-border);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* On a narrow screen two half-width pages are unreadable, so they stack instead. */
@media (max-width: 900px) {
    .erp-viewer__stages { flex-direction: column; }
}

/* =============================================================================
   BADGE MODIFIERS THAT WERE USED BUT NEVER DEFINED
   -----------------------------------------------------------------------------
   BUGFIX. --success, --warning, --danger and --info were written across 46 places
   in the document-control work — every "Expired", "Review overdue", "Confidential",
   "legal hold", "Refused" and acknowledgement badge — and none of them existed in
   this stylesheet. The class matched .erp-badge and nothing else, so all of them
   rendered as a plain grey pill.

   That is the worst way for this particular bug to present: the badge is THERE, it
   is legible, and it is the same colour as the neutral ones beside it. An expired
   trade licence sat in the register looking exactly like a valid one, and nothing
   about the page suggested anything was missing. Found by an agent reviewing its
   own page, not by looking at the register.

   Built from the SAME applied-role tokens as the badges above (--erp-positive,
   --erp-warning, --erp-negative, --erp-info) rather than from the ink ramp, for the
   reason recorded at .erp-badge--draft: those roles are redeclared per theme, and
   anything built from the ramp paints pale grey on a dark card.
   ========================================================================== */
.erp-badge--success { background: var(--erp-positive-soft); color: var(--erp-positive); }
.erp-badge--warning { background: var(--erp-warning-soft);  color: var(--erp-warning); }
.erp-badge--danger  { background: var(--erp-negative-soft); color: var(--erp-negative); }
.erp-badge--info    { background: var(--erp-info-soft);     color: var(--erp-info); }

/* =============================================================================
   WORKFLOW DESIGNER — the approval chain drawn as a diagram
   -----------------------------------------------------------------------------
   Pages/Approvals/WorkflowDesigner.razor. Everything here is DRAWING, not form
   controls: the boxes are divs and the arrows between them are inline SVG, so the
   page needs no diagramming library. That is not a preference — the desktop shell
   can be running with no network, which is why every dependency in this repository
   is vendored, and a chart library fetched from a CDN would leave the designer
   blank on exactly the machines that most need to read a chain.

   BUILT FROM THE APPLIED-ROLE TOKENS (--erp-positive, --erp-warning,
   --erp-negative, --erp-info, --erp-border, --erp-surface, --erp-text-muted)
   rather than from literal colours, for the reason recorded at the badge
   modifiers above: those roles are redeclared per theme, so a diagram built from
   them follows the theme switch, and one built from hex values does not.

   THE PARALLEL FAN IS PURE CSS, deliberately. The cards of a level that is asked
   all at once sit in a flex row; the rail they hang from is the row's own top
   border, and each card grows a short vertical stub up into it. Nothing measures
   anything, so nothing can be one pixel out at a different zoom, font size or
   translation — and there is no JavaScript in the drawing at all.
   ========================================================================== */

/* ---- Page frame: the canvas, and the checks beside it ---------------------- */
.wfd-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: 16px;
    align-items: start;
}

/* Below this the checks panel would squeeze the diagram narrower than one card,
   so it goes underneath instead of beside. */
@media (max-width: 1100px) {
    .wfd-split { grid-template-columns: minmax(0, 1fr); }
}

.wfd-canvas { min-width: 0; }
.wfd-side   { position: sticky; top: 12px; }

.wfd-pick__box  { min-width: 300px; }
.wfd-trial      { display: flex; align-items: center; gap: 8px; }
.wfd-trial__label { font-size: 12px; color: var(--erp-text-muted); white-space: nowrap; }
.wfd-trial__box   { width: 150px; }
.wfd-trial__note  { margin-bottom: 14px; }

.wfd-static {
    padding: 5px 0;
    font-weight: 600;
}

/* ---- The diagram itself ---------------------------------------------------- */
.wfd {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    max-width: 880px;
    margin: 0 auto;
}

/* Start and end of the chain. Deliberately a different shape from a level box:
   these are not things anybody approves. */
.wfd__node {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    padding: 10px 14px;
    border: 1px solid var(--erp-border);
    border-radius: var(--erp-radius-lg);
    background: var(--erp-surface-sunken);
}

.wfd__node > i        { font-size: 16px; line-height: 1.4; color: var(--erp-text-muted); }
.wfd__node--start > i { color: var(--erp-info); }
.wfd__node--end       { border-color: var(--erp-positive); }
.wfd__node--end > i   { color: var(--erp-positive); }
.wfd__node--empty     { border-style: dashed; }

.wfd__node-title { font-weight: 600; font-size: 13px; }
.wfd__node-sub   { font-size: 11.5px; color: var(--erp-text-muted); margin-top: 2px; }

/* ---- The arrow between two levels ------------------------------------------ */
.wfd__link-wrap {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    position: relative;
}

.wfd__link {
    stroke: var(--erp-text-muted);
    fill: var(--erp-text-muted);
    stroke-width: 1.5;
    opacity: .5;
}

.wfd__link-note {
    font-size: 11px;
    color: var(--erp-text-muted);
    font-style: italic;
}

/* ---- One level ------------------------------------------------------------- */
.wfd__band {
    border: 1px solid var(--erp-border);
    border-radius: var(--erp-radius-lg);
    background: var(--erp-surface);
    overflow: hidden;
}

/* A level asked all at once is a genuinely different thing from a level with one
   approver, so it is a different shape rather than the same shape with a label. */
.wfd__band--parallel { border-color: var(--erp-info); }

.wfd__bandhead {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    background: var(--erp-surface-sunken);
    border-bottom: 1px solid var(--erp-border);
}

.wfd__bandno    { font-weight: 600; font-size: 12px; }
.wfd__bandcount { font-size: 11px; color: var(--erp-text-muted); }

.wfd__cards {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    padding: 12px;
}

/* The fan. The rail is this row's own top border and each card hangs a stub from
   it — see the note at the top of this block for why it is done this way. */
.wfd__band--parallel .wfd__cards {
    border-top: 1px dashed var(--erp-info);
    margin-top: 16px;
    padding-top: 20px;   /* must equal the stub height below, or the stubs stop short of the rail */
}

.wfd__card {
    position: relative;
    flex: 1 1 240px;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 5px;
    padding: 10px 12px;
    border: 1px solid var(--erp-border);
    border-left: 3px solid var(--erp-border);
    border-radius: var(--erp-radius);
    background: var(--erp-surface);
}

.wfd__band--parallel .wfd__card::before {
    content: "";
    position: absolute;
    top: -20px;          /* rises exactly to the rail — see the padding-top above */
    left: 50%;
    height: 20px;
    border-left: 1px dashed var(--erp-info);
}

/* A level that is switched off is still part of the picture — it is exactly the
   thing somebody will wonder about — but it is visibly not in the chain. */
.wfd__card--off {
    background: var(--erp-surface-sunken);
    opacity: .75;
    border-style: dashed;
}

/* Not in the chain for the amount being tried. Faded rather than hidden: which
   levels DROP OUT at a given value is half of what the test is for. */
.wfd__card--dim { opacity: .38; }

.wfd__card--error { border-left-color: var(--erp-negative); }
.wfd__card--warn  { border-left-color: var(--erp-warning); }
.wfd__card--info  { border-left-color: var(--erp-info); }

.wfd__card-head  { display: flex; align-items: center; gap: 6px; }
.wfd__card-title { font-weight: 600; font-size: 13px; }

.wfd__row {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    font-size: 12px;
    min-width: 0;
}

.wfd__row > i      { color: var(--erp-text-muted); font-size: 12px; line-height: 1.35; }
.wfd__row > span   { overflow-wrap: anywhere; }
.wfd__row--muted   { color: var(--erp-text-muted); font-size: 11.5px; }

.wfd__card-actions {
    display: flex;
    gap: 4px;
    margin-top: 2px;
    padding-top: 6px;
    border-top: 1px solid var(--erp-border);
}

/* ---- What is wrong, said on the shape that is wrong ------------------------- */
.wfd__flags {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-top: 2px;
}

.wfd__flags--chain { margin: 10px 0 0; }

.wfd__flag {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    padding: 5px 8px;
    border-radius: var(--erp-radius-sm);
    font-size: 11.5px;
    line-height: 1.45;
}

.wfd__flag > i { line-height: 1.45; }

.wfd__flag--error { background: var(--erp-negative-soft); color: var(--erp-negative); }
.wfd__flag--warn  { background: var(--erp-warning-soft);  color: var(--erp-warning); }
.wfd__flag--info  { background: var(--erp-info-soft);     color: var(--erp-info); }

/* The same flag repeated in the side panel, where it needs a little more room. */
.wfd__flag--block { margin-bottom: 6px; }


/* ------------------------------------------------------------------------------------------
   THE CHROME FOLLOWS THE THEME (2026-08-05)

   Reported plainly: "changing the theme changes the main colours, but the headings, the menus
   and the group buttons stay as they were." That was true, and in two different ways.

   ONE — the app bar read only Fluent's accent token, so every CLASSIC skin left it teal. Fixed
   above, in the token itself, where it fixes the module tabs and page tabs at the same time.

   TWO — the headings and menu group labels were painted from the neutral text token and had no
   relationship to the theme at all. Grid text stays neutral deliberately: black on white is what
   a dense figure grid needs and tinting it would hurt. But a HEADING is chrome, not data, and it
   is exactly where somebody looks to see whether their choice took effect.

   Everything below reads --erp-brand-*, which already chains --bs-primary → --DS-primary-90, so
   it moves for both families without another special case.
   ------------------------------------------------------------------------------------------ */

/* Card headings: the leading icon and the rule beneath carry the theme. */
.erp-card__head { border-bottom-color: color-mix(in srgb, var(--erp-brand-500) 22%, var(--erp-border)); }
.erp-card__head > i,
.erp-card__head .bi { color: var(--erp-brand-500); }

/* Menu group labels in the rail — the "Leads", "Pipeline", "Engagement" headings. */
.erp-nav-heading { color: var(--erp-brand-600); }
.erp-nav-heading i { color: var(--erp-brand-500); }

/* The module name at the top of the rail. */
.erp-rail__modhead { color: var(--erp-brand-600); }

/* Section headings inside editors already accented their icon; give the label the same weight
   of colour so a section and a card read as one family. */
.erp-formsection__head { color: var(--erp-brand-700); }

/* The page title in the top bar. Kept to the darker rung: it sits on a white bar at 17px and
   the mid rung is marginal for contrast on the lighter accents (mint, desert, cool blue). */
.erp-topbar__title { color: var(--erp-brand-700); }

/* Dark skins invert the relationship: the brand ramp's dark rungs are too dark to read on a
   dark bar, so headings take the light rung instead of the -600/-700 ones above. */
html[data-theme="dark"] .erp-nav-heading,
html[data-theme="dark"] .erp-rail__modhead,
html[data-theme="dark"] .erp-formsection__head,
html[data-theme="dark"] .erp-topbar__title { color: var(--erp-brand-300); }


/* ==========================================================================================
   ONE BUTTON SIZE, ONE WORD PER WORD, AND HEADINGS THAT FOLLOW THE THEME (2026-08-08)

   Three faults reported together, all of them global, all of them fixed here rather than in
   the ninety-odd screens that show them.
   ========================================================================================== */

/* --- 1. ONE BUTTON SIZE ---------------------------------------------------------------
   DevExpress buttons come in three sizes and the app used all three: 96 buttons ask for
   Small, 9 for Large, and the other ~1,980 take the default. Side by side in one screen —
   a Small "Apply" beside a default "Save" — they read as a mistake, because they are one.

   The height is pinned HERE rather than by editing a hundred markup sites, so a button
   added tomorrow is the same height as the rest without anyone remembering a rule.

   Three exclusions, all necessary:
     .dxbl-btn-tool  is DevExpress's OWN furniture — a spin edit's up/down, an editor's
                     clear cross, the tab strip's scroll arrows. Sizing those to 30px would
                     inflate the editors they live inside.
     .dxbl-lg        the nine deliberately large ones: Shop Floor and the punch clock are
                     used on a touch screen with gloves on, and small targets there are a
                     safety problem, not a style one.
     .dxbl-grid      a button in a grid cell decides that row's height, and the row height
                     in this app is settled. In-grid buttons stay small.
   Scoped to .erp-content (the <main>), so the module bar, the rail and the utilities menu
   keep the sizes they were designed with. */
.erp-content .dxbl-btn:not(.dxbl-btn-tool):not(.dxbl-lg) {
    min-height: 30px;
    padding: 3px 12px;
    font-size: 13px;
    line-height: 1.45;
    white-space: nowrap;
}
.erp-content .dxbl-grid .dxbl-btn:not(.dxbl-btn-tool):not(.dxbl-lg) {
    min-height: 24px;
    padding: 1px 8px;
    font-size: 12px;
}

/* --- 2. NO ONE-WORD-PER-LINE ------------------------------------------------------------
   "Some texts can see five lines with a single word." That is a label in a box narrower
   than its longest word, and it happens where a flex or grid child is allowed to shrink
   below its content: buttons, tabs, badges and column headers.

   Text that a person READS (a description, a cell value, a hint) is deliberately NOT in
   this list — wrapping is correct there, and clipping it would hide data. This covers
   CHROME only, where a wrapped word is always a bug. */
.erp-content .dxbl-tabs-item,
.erp-badge,
.erp-soon,
.erp-setrow__control,
.erp-tile__label,
.erp-formsection__head,
.erp-rc__sechead,
.erp-card__title { white-space: nowrap; }

/* THE REPORTED CASE, found on User Management: the filter checkboxes in a card head
   ("Active only", "With access here") were flex items squeezed by the search box beside
   them, and a checkbox label squeezed below its longest word wraps ONE WORD PER LINE —
   three lines to say "With access here". A label is a label: it does not wrap, it does not
   shrink, and when the row genuinely runs out of room the ROW wraps instead. */
.erp-content .dxbl-checkbox { flex: 0 0 auto; white-space: nowrap; }
.erp-content .dxbl-checkbox label,
.erp-content .dxbl-checkbox span { white-space: nowrap; }
.erp-card__head { flex-wrap: wrap; row-gap: 8px; }

/* A heading that cannot wrap must be allowed to end in an ellipsis rather than spill. */
.erp-formsection__head > span,
.erp-rc__sechead,
.erp-card__title { overflow: hidden; text-overflow: ellipsis; }

/* Grid column headers: one line, ellipsis if the column is narrow — the column can be
   widened, and a five-line header cannot. Cells are untouched. */
.erp-content .dxbl-grid-table > thead > tr > th { white-space: nowrap; }

/* The classic cause: a flex child at its default min-width:auto refuses to shrink and
   squeezes its neighbour instead. These four are the shells everything else sits in. */
.erp-card__head > div,
.erp-setrow__text,
.erp-tile,
.erp-toolbar > * { min-width: 0; }

/* --- 3. GROUP HEADINGS AND TAB STRIPS FOLLOW THE THEME -----------------------------------
   The 2026-08-05 pass above accented the rail headings, the card head rule and the editor
   section heads. These were missed: the card TITLE itself, the Role Centre section
   headings (which the Reports deck also uses), and the selected tab in every DxTabs on the
   screen — including the settings groups. A tab strip is a group button by another name,
   and it is exactly where somebody looks to see whether their theme choice took. */
.erp-card__title { color: var(--erp-brand-700); }
.erp-rc__kicker { color: var(--erp-brand-600); }
.erp-rc__sechead {
    color: var(--erp-brand-700);
    border-bottom-color: color-mix(in srgb, var(--erp-brand-500) 35%, var(--erp-border));
}
.erp-content .dxbl-tabs-item.dxbl-active {
    color: var(--erp-brand-600);
    box-shadow: inset 0 -2px 0 0 var(--erp-brand-500);
    font-weight: 500;
}
html[data-theme="dark"] .erp-card__title,
html[data-theme="dark"] .erp-rc__kicker,
html[data-theme="dark"] .erp-rc__sechead,
html[data-theme="dark"] .erp-content .dxbl-tabs-item.dxbl-active { color: var(--erp-brand-300); }


/* --- 5. THE PLACES THE 2026-08-08 PASS STILL LEFT NEUTRAL (2026-08-29) -------------------
   Reported as "only the module names and some tiles change colour". Two causes, and this
   block is the second of them. (The first was that --erp-accent and --erp-muted were never
   declared: 38 rules referenced names that do not exist, so the manuals' contents links,
   their numbered chips and every muted caption fell back to plain body text. Those are
   repointed at --erp-brand-500 and --erp-text-muted where they are written.)

   What is added here is the chrome that was simply never accented: the manual system, the
   page and card headings, and the links. Everything below takes the BRAND ramp, which every
   one of the twenty-six themes publishes — no literal is introduced, and the dark rung
   follows the same inversion the block above uses, because the -600/-700 rungs are too dark
   to read on a dark surface. */

/* Blazor focuses the page's h1 after every navigation, for screen readers, and gives it
   tabindex="-1" to do so. The side effect is a focus ring drawn round the title of every page
   a mouse user opens — visible on all nineteen manuals and on every other page besides.

   :focus-visible does NOT separate the two cases, which is the obvious fix and was tried
   first: Chrome matches it on a programmatically focused element even when the arrival was a
   mouse click, so the ring stayed. Targeting tabindex="-1" instead is exact and costs nothing.
   An element with a negative tabindex cannot be reached by tabbing, so this ring is only ever
   the one the navigation put there — a keyboard user never sees it and loses nothing, and a
   screen reader is announced the heading either way, which is what the focus is actually for. */
[tabindex="-1"]:focus { outline: none; }

/* The manuals. Nineteen of them, and every heading in them was body-coloured. */
.erp-help h1,
.erp-help__topic h2,
.erp-help__index-head h1 { color: var(--erp-brand-700); }
.erp-help__section,
.erp-help__toc-group,
.erp-help__before h2,
.erp-help__trouble h2 { color: var(--erp-brand-600); }
.erp-help__q { color: var(--erp-brand-700); }

/* Page and card headings elsewhere. .erp-card__title is already accented above; these are
   its siblings — the section heads inside cards and the empty-state icon that fills the
   middle of a card with nothing in it. */
.erp-section__title,
.erp-page__title,
.erp-cardhead__title { color: var(--erp-brand-700); }
.erp-empty i,
.erp-card__head > i { color: var(--erp-brand-300); }

/* Links inside content. They were body-coloured, which is the one thing a link must not be:
   indistinguishable from the sentence around it. */
.erp-content a:not(.erp-cmd):not(.erp-btn):not([class*="dxbl"]) { color: var(--erp-brand-600); }
.erp-content a:not(.erp-cmd):not(.erp-btn):not([class*="dxbl"]):hover { color: var(--erp-brand-700); }

/* The rail's selected page. The active card was white-on-grey and carried no trace of the
   theme at all, which on a 26-theme application is the single largest surface that did not
   answer the question "did my choice take?". A left bar rather than a fill, so the rail
   stays quiet and the reading of the label does not change. */
.erp-rail__link.active { box-shadow: inset 3px 0 0 0 var(--erp-brand-500); }
.erp-rail__link.active i { color: var(--erp-brand-500); }

/* Dividers that separate one group of things from another. Mixed rather than replaced, so
   a rule stays a rule and does not become a coloured stripe. */
.erp-card__head,
.erp-formsection__head { border-bottom-color: color-mix(in srgb, var(--erp-brand-500) 30%, var(--erp-border)); }

html[data-theme="dark"] .erp-help h1,
html[data-theme="dark"] .erp-help__topic h2,
html[data-theme="dark"] .erp-help__index-head h1,
html[data-theme="dark"] .erp-help__section,
html[data-theme="dark"] .erp-help__toc-group,
html[data-theme="dark"] .erp-help__before h2,
html[data-theme="dark"] .erp-help__trouble h2,
html[data-theme="dark"] .erp-help__q,
html[data-theme="dark"] .erp-section__title,
html[data-theme="dark"] .erp-page__title,
html[data-theme="dark"] .erp-cardhead__title { color: var(--erp-brand-300); }
html[data-theme="dark"] .erp-content a:not(.erp-cmd):not(.erp-btn):not([class*="dxbl"]) { color: var(--erp-brand-300); }

/* --- 4. THE MODULE BAR FITS ITS MODULES --------------------------------------------------
   Eleven modules plus the search box overflowed a 1440px bar, so the last two ("Projects &
   Service", "Documents") were cut mid-word and reachable only by scrolling a bar nobody
   thinks to scroll. Moving Settings into the utilities menu bought one tab's width; this
   buys the rest by dropping the ICONS, which decorate, and keeping the NAMES, which
   identify. Below 981px the rule the other way round already applies — icons only, no
   names — so this range stops there rather than leaving tabs with neither. */
@media (max-width: 1500px) and (min-width: 981px) {
    .erp-modtab { padding: 0 9px; gap: 5px; }
    .erp-modtab i { display: none; }
}

/* --- 5. THE PHONE FIX: THE SHELL MUST BE ALLOWED TO SHRINK -------------------------------
   Every page overflowed sideways on a phone — the Role Centre headline cut mid-word, the KPI
   tiles running off the right edge — and it was not the pages. It was this one line missing.

   .erp-modulebar__tabs is a flex child with overflow-x:auto, which reads as "the tabs scroll
   when they do not fit". They did not: a flex item's default min-width is AUTO, meaning its
   min-content size, and its content is eleven tabs that each refuse to shrink. So the bar's
   floor was ~540px, the shell could not go narrower than its widest row, and every page below
   inherited that width on a 390px screen. min-width:0 lets the declared scrolling actually
   happen — the tabs scroll, and the app fits the phone.

   The same trap, same fix, for the two other rows that hold non-shrinking children. */
.erp-modulebar__tabs,
.erp-pagesbar,
.erp-toolbar { min-width: 0; }

@media (max-width: 720px) {
    /* 22px of padding on each side of a 390px screen is a tenth of the page. */
    .erp-content { padding: 14px; }
    /* One tile per row rather than two half-legible ones. */
    .erp-kpi-row { grid-template-columns: 1fr; }
    /* The Role Centre's two panels stack; 300px of insisted-on width is most of a phone. */
    .erp-rc { padding: 16px; }
    .erp-rc__insight { min-width: 0; flex-basis: 100%; }
    .erp-rc__actions { flex-basis: 100%; }
    .erp-rc__top { gap: 20px; }
    .erp-rc__stats { gap: 20px; }
    .erp-rc__actions-grid { grid-template-columns: 1fr; }
    /* Nothing in the working area may be wider than the screen. */
    .erp-content img,
    .erp-content table { max-width: 100%; }
}

/* Last resort on a phone: nothing directly inside the working area may be wider than the
   screen. A block whose CONTENT genuinely cannot fit (a wide table, a long tab strip) then
   scrolls INSIDE its own card, which is a scrollbar in the right place — rather than pushing
   the whole page sideways, which is a scrollbar in the wrong one. */
@media (max-width: 720px) {
    .erp-content > * { max-width: 100%; }
    .erp-rc,
    .erp-card { overflow-x: auto; }
}


/* ==========================================================================================
   THE DOCUMENT PATTERN (2026-08-08) — band, strip, disclosure, lines, rail

   Approved from a mockup as "option A" and built first on the invoice, to become the shape of
   every transaction form: quotation, order, delivery note, receipt, return, voucher, journal.

   What it replaced on the invoice: a card whose title repeated the page header, wrapping ten
   fields in two boxed FastTab groups with three address and terms memos among them — so the
   LINES, which are the work, began below the fold on a laptop, and the fields touched on every
   document sat mixed in with the ones touched on one in twenty.

   Four parts, in this order:
     .erp-docband    who and which — the party as a heading, the number and status opposite
     .erp-docstrip   the five or six fields filled every time, one quiet row
     .erp-docmore    everything else, one click away, with a summary of what is inside
     (then the existing lines card and the totals rail, both unchanged)
   ========================================================================================== */

.erp-docband,
.erp-docstrip,
.erp-docmore { margin-bottom: 10px; }

/* --- the band ---------------------------------------------------------------------------- */
.erp-docband {
    display: flex;
    align-items: flex-start;
    gap: 24px;
    padding: 14px 18px;
}
.erp-docband__party { flex: 1 1 auto; min-width: 0; }
.erp-docband__id { flex: 0 0 auto; text-align: right; }

.erp-docband__label {
    font-size: 10.5px;
    letter-spacing: .08em;
    text-transform: uppercase;
    font-weight: 700;
    color: var(--erp-text-muted);
    margin-bottom: 4px;
    white-space: nowrap;
}
.erp-req { color: var(--erp-negative); }

/* The party picker is the biggest control on the page because it is the first decision and
   the one every other field follows from. Capped so it does not run the width of a monitor. */
.erp-docband__pick { width: 100%; max-width: 420px; }
.erp-docband .erp-docband__pick .dxbl-text-edit { font-size: 15px; font-weight: 600; }

.erp-docband__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 18px;
    margin-top: 8px;
    font-size: 12.5px;
    color: var(--erp-text-muted);
}
.erp-docband__meta strong { color: var(--erp-text); font-weight: 600; }
.erp-docband__meta--warn strong { color: var(--erp-warning); }

.erp-docband__num {
    font-family: var(--erp-font-num);
    font-size: 19px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--erp-brand-700);
    white-space: nowrap;
}
.erp-docband__num--muted { color: var(--erp-text-muted); font-weight: 500; }
.erp-docband__id .erp-badge { margin-top: 5px; }

/* --- the strip --------------------------------------------------------------------------- */
.erp-docstrip {
    display: flex;
    flex-wrap: wrap;
    gap: 12px 22px;
    padding: 12px 18px;
    background: var(--erp-surface-sunken);
}
/* A fixed basis, not content width: the cells line up down the row whatever is in them, which
   is the same rule that settled the settings list. */
.erp-docstrip__cell { flex: 0 1 170px; min-width: 0; }
.erp-docstrip__label {
    display: block;
    font-size: 10.5px;
    letter-spacing: .07em;
    text-transform: uppercase;
    font-weight: 700;
    color: var(--erp-text-muted);
    margin-bottom: 3px;
    white-space: nowrap;
}

/* --- the disclosure ---------------------------------------------------------------------- */
.erp-docmore__toggle {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    padding: 10px 18px;
    border: 0;
    background: transparent;
    color: var(--erp-text);
    font: inherit;
    font-size: 13px;
    text-align: left;
    cursor: pointer;
}
.erp-docmore__toggle:hover { background: var(--erp-surface-sunken); }
.erp-docmore__toggle i { color: var(--erp-brand-500); font-size: 12px; }
.erp-docmore__title { font-weight: 600; white-space: nowrap; }
.erp-docmore__summary {
    color: var(--erp-text-muted);
    font-size: 12.5px;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.erp-docmore__body {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 14px 22px;
    padding: 4px 18px 16px;
    border-top: 1px solid var(--erp-border);
}

@media (max-width: 720px) {
    .erp-docband { flex-direction: column; gap: 14px; }
    .erp-docband__id { text-align: left; }
    .erp-docband__pick { max-width: none; }
    .erp-docstrip__cell { flex: 1 1 140px; }
    .erp-docmore__summary { display: none; }
}


/* --- LINE GRIDS: SHORTER ROWS ---------------------------------------------------------------
   Reported on the invoice: "the height of qty, unit price, disc, VAT, line total is over
   height". Three things were stacking up, and the first was the real cause — the invoice grid
   never wore .erp-linegrid at all, so none of the line-grid density above ever reached it. It
   does now, and these three rules take the rest:

     1. the editors inside a line row are 26px, not the 30px a form field gets — a row of them
        is a table, and a table's job is to show the next row;
     2. the cells give them 3px above and below rather than 6;
     3. the spin arrows are GONE from line grids. They cost width in an already tight column,
        they are the tallest thing in the cell, and nobody clicks an arrow 40 times to reach a
        quantity of 40 — they type it. The keyboard's up/down still works.

   Data grids elsewhere are untouched: this whole block is behind .erp-linegrid, which only the
   document line tables wear. */
/* A fixed height on the single-line editors, because a min-height alone left the combo box
   sizing itself. NOT on .dxbl-memo — a memo is multi-line by definition and pinning it to one
   row would hide what is typed in a line grid that carries one. */
.erp-content .erp-linegrid .dxbl-text-edit:not(.dxbl-memo) { min-height: 24px; height: 24px; }
.erp-content .erp-linegrid .dxbl-memo { min-height: 24px; }
.erp-content .erp-linegrid .dxbl-text-edit .dxbl-edit-btn-dropdown { min-height: 24px; }
.erp-content .erp-linegrid .dxbl-text-edit input { padding-top: 1px; padding-bottom: 1px; }
.erp-content .erp-linegrid .dxbl-grid-table > tbody > tr > td { padding: 2px 6px; }
.erp-content .erp-linegrid .dxbl-spin-btn-inc,
.erp-content .erp-linegrid .dxbl-spin-btn-dec { display: none; }

/* A line of consequence under the field strip — the order type's meaning, a date that will be
   refused on save. It runs the full width beneath the cells rather than under one of them,
   because it is about the document, not about a field. */
.erp-docstrip__note {
    flex: 1 0 100%;
    display: flex;
    flex-wrap: wrap;
    gap: 4px 16px;
    margin-top: 2px;
    font-size: 12px;
    color: var(--erp-text-muted);
}
.erp-docstrip__note--warn { color: var(--erp-warning); }

/* A value inside the disclosure that the document RESOLVED rather than stores — shown, never
   typed into. It reads as text, not as a disabled editor pretending to be one. */
.erp-docmore__readonly {
    font-size: 12.5px;
    color: var(--erp-text-muted);
    padding: 5px 0;
    white-space: pre-line;
}

/* A party the document did not choose — a packing list takes its customer from the delivery
   note it belongs to. It reads at the picker's weight so the band looks the same either way,
   but it is text, not a disabled control pretending to be one. */
.erp-docband__name { font-size: 15px; font-weight: 600; padding: 5px 0; }
.erp-docband__name--muted { color: var(--erp-text-muted); font-weight: 500; }

/* --- MASTER-RECORD EDITORS: ONE BAND AT A TIME ---------------------------------------------
   A product carries 68 fields and an employee 92, and both were nine and four sections stacked
   down a popup — so the person setting a reorder level or a bank account scrolled past every
   other band to reach it, with no idea how much was left. Tabs inside the editor, the shape
   every ERP uses for a master record.

   The tab strip sits flush at the top of the popup body and the pages get a little air under
   it; the rest is the same .erp-tabs treatment the settings screen uses, so a tab looks like a
   tab wherever it appears. */
.erp-edittabs { margin-bottom: 2px; }
.erp-edittabs .dxbl-tabs-content-panel { padding-top: 12px; }
.erp-editor .erp-edittabs .dxbl-tabs-item { font-size: 13px; }

/* --- FIRST RUN: the way out of an empty installation ---------------------------------------
   The login screen used to say "No companies available" and stop, which is a dead end on a
   fresh install — no password will ever get anyone in, because there is nothing to get into.
   The button beneath it is the way out. */
.erp-login__setup { width: 100%; margin-top: 8px; }
.erp-login__setuphint {
    margin-top: 8px;
    font-size: 12px;
    line-height: 1.5;
    color: var(--erp-text-muted);
}

/* --- ONE EDITOR HEIGHT, EVERYWHERE ----------------------------------------------------------
   Reported as "the VAT box is not the same height as the others, and it is like that in plenty
   of places". Measured on Company Settings before touching anything:

       text box                30px
       combo box / date edit   32px
       SPIN EDIT               61px      <-- twice the others

   The spin edit stacks its up and down buttons and each one demands a full row, so any figure
   field — VAT %, decimal places, quantities, discounts, credit limits — stood out as a box twice
   the size of its neighbours on every form in the application.

   The fix is on the BUTTONS, not the editor: they share the control's height instead of setting
   it. The editor itself is then pinned to the same 30px as everything else. Memos are excluded —
   a memo is multi-line by definition and pinning it to one row would hide what is typed. */
.erp-content .dxbl-text-edit .dxbl-spin-btn-inc,
.erp-content .dxbl-text-edit .dxbl-spin-btn-dec {
    min-height: 0;
    height: 14px;
    padding: 0;
    line-height: 1;
}
.erp-content .dxbl-text-edit:not(.dxbl-memo) { height: 30px; }
.erp-content .dxbl-text-edit .dxbl-edit-btn-dropdown { min-height: 0; }

/* Line grids keep their own tighter figure — set after this block so it still wins. */
.erp-content .erp-linegrid .dxbl-text-edit:not(.dxbl-memo) { height: 24px; }

/* --- AND ENOUGH ROOM TO READ A DATE ---------------------------------------------------------
   The other half of the same report: "some date fields have not enough length, we can't see the
   complete text". A field strip cell was free to shrink (flex: 0 1 170px), and a date edit that
   shrinks puts "01/01/2026", a calendar button and a clear button into whatever is left. The
   floor stops that. Deliberately a floor and not a wider fixed width — the instruction was not
   to overdo the length, and a 300px box for eight characters is its own kind of wrong. */
.erp-docstrip__cell { min-width: 150px; }

/* A figure that has crossed a line the operator set — stock at or under its reorder level. Uses
   the same negative token as every other "this needs attention" number in the application. */
.erp-num--negative { color: var(--erp-negative); font-weight: 600; }

/* --- A PICKER NEEDS MORE ROOM THAN A TEXT BOX -----------------------------------------------
   Measured on a live goods receipt: the "Received into warehouse" combo gave its text 70px, so
   "Main Warehouse" was clipped by 48px and "Marina Branch" fared no better. The cell was 150px —
   the same as every other cell — but a picker spends its width on a dropdown button and a clear
   button before any words are shown, while a text box spends none of it.

   So a cell is sized by WHAT IT HOLDS, not uniformly: a code is short, a date is a date, a name
   somebody chose from a list needs to be readable. This is the cheap, safe half of "let me drag
   the field wider" — it cannot break a layout, because the strip already wraps. */
.erp-docstrip__cell--pick { flex: 0 1 260px; min-width: 230px; }

/* --- the quick menu's keyboard hints -----------------------------------------
   A key cap, not a badge: it has to read as something you PRESS. Muted, because
   the shortcut is a shortcut - it must never outshine the page name beside it. */
.erp-userpop__key,
.erp-userpop__keys kbd {
    font-family: inherit;
    font-size: 10px;
    font-weight: 600;
    line-height: 1;
    padding: 2px 5px;
    border-radius: var(--erp-radius-sm);
    color: var(--erp-text-muted);
    background: var(--erp-surface-sunken);
    border: 1px solid var(--erp-border);
    white-space: nowrap;
}

.erp-userpop__keys {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 11px;
}

/* ==========================================================================================
   EACH DARK SKIN TAKES ITS OWN HUE (2026-08-29)

   Reported as: every dark theme is the same navy, and a neutral charcoal reads badly.
   Both are the same fact — a dark surface with no hue in it looks like the lights went out
   rather than like a theme somebody chose.

   WHAT CHANGED, AND WHAT DELIBERATELY DID NOT. The twenty-five steps below are unchanged as
   LUMINANCE: they are the ramp that keeps every step's original lightness so the contrast
   DevExpress designed against still holds, and that is the whole reason this block can exist
   at all. What is new is that each step now carries a sixth of the ACTIVE ACCENT, so the
   hue follows the skin: Mint dark is a green charcoal, Rose dark a plum one, Desert dark a
   warm brown, and Blue and Navy stay as they were, because mixing blue into navy is navy.

   SIXTEEN PER CENT, MEASURED RATHER THAN CHOSEN. Below about ten the hue is not visible on a
   surface this dark; above about twenty-five the steps start to gain saturation faster than
   they keep luminance, and the pale text steps go milky. The white step is exempt — text that
   is meant to be white should be white.

   IT REACHES THE DEVEXPRESS CONTROLS TOO, which is the point of tinting --DS-neutral-* rather
   than only this application's own tokens: the grids, editors and popups read those same
   variables, so the whole screen shifts together instead of our chrome going green against a
   grey grid.

   STILL SCOPED TO A FLUENT DARK. Blazing Dark is a classic theme whose greys are compiled
   into its stylesheet and cannot be re-tinted; theme.js sets this attribute only for a Fluent
   dark skin, so the two remain separate and each stays coherent.
   ========================================================================================== */
html[data-theme="dark"][data-dark-tint="navy"] {
    --DS-neutral-250: color-mix(in srgb, var(--DS-primary-90) 16%, #060a12);  --DS-neutral-240: color-mix(in srgb, var(--DS-primary-90) 16%, #0a1020);  --DS-neutral-230: color-mix(in srgb, var(--DS-primary-90) 16%, #0f172a);
    --DS-neutral-220: color-mix(in srgb, var(--DS-primary-90) 16%, #152036);  --DS-neutral-210: color-mix(in srgb, var(--DS-primary-90) 16%, #1b2942);  --DS-neutral-200: color-mix(in srgb, var(--DS-primary-90) 16%, #22324e);
    --DS-neutral-190: color-mix(in srgb, var(--DS-primary-90) 16%, #293a59);  --DS-neutral-180: color-mix(in srgb, var(--DS-primary-90) 16%, #314365);  --DS-neutral-170: color-mix(in srgb, var(--DS-primary-90) 16%, #3a4d71);
    --DS-neutral-160: color-mix(in srgb, var(--DS-primary-90) 16%, #44587d);  --DS-neutral-150: color-mix(in srgb, var(--DS-primary-90) 16%, #4f6389);  --DS-neutral-140: color-mix(in srgb, var(--DS-primary-90) 16%, #5b6f95);
    --DS-neutral-130: color-mix(in srgb, var(--DS-primary-90) 16%, #6879a1);  --DS-neutral-120: color-mix(in srgb, var(--DS-primary-90) 16%, #7585ad);  --DS-neutral-110: color-mix(in srgb, var(--DS-primary-90) 16%, #8391b9);
    --DS-neutral-100: color-mix(in srgb, var(--DS-primary-90) 16%, #919ec5);  --DS-neutral-90:  color-mix(in srgb, var(--DS-primary-90) 16%, #a0abd0);  --DS-neutral-80:  color-mix(in srgb, var(--DS-primary-90) 16%, #afb9db);
    --DS-neutral-70:  color-mix(in srgb, var(--DS-primary-90) 16%, #bfc7e5);  --DS-neutral-60:  color-mix(in srgb, var(--DS-primary-90) 16%, #ced4ed);  --DS-neutral-50:  color-mix(in srgb, var(--DS-primary-90) 16%, #dde1f4);
    --DS-neutral-40:  color-mix(in srgb, var(--DS-primary-90) 16%, #e9ecf8);  --DS-neutral-30:  color-mix(in srgb, var(--DS-primary-90) 16%, #f1f3fb);  --DS-neutral-20:  color-mix(in srgb, var(--DS-primary-90) 16%, #f7f8fd);
    --DS-neutral-10:  #ffffff;

    /* This application's own surfaces, on the same ramp so the two never diverge. */
    --erp-surface:        color-mix(in srgb, var(--DS-primary-90) 16%, #0f172a);
    --erp-surface-raised: color-mix(in srgb, var(--DS-primary-90) 16%, #152036);
    --erp-surface-sunken: color-mix(in srgb, var(--DS-primary-90) 16%, #0b1220);
    --erp-page:           color-mix(in srgb, var(--DS-primary-90) 16%, #070d17);

    /* Borders as translucent light rather than a flat grey: a fixed border colour reads as a
       seam on a raised card and disappears on a sunken one. */
    --erp-border:        rgba(148, 175, 255, .16);
    --erp-border-strong: rgba(148, 175, 255, .26);

    /* Softened the same way and for the same reason as the block above — the accent tint
       stays, only the brightness comes down. */
    --erp-text:         color-mix(in srgb,
                          color-mix(in srgb, var(--DS-primary-90) 16%, #e8edf7) 84%, var(--erp-surface));
    --erp-text-muted:   color-mix(in srgb,
                          color-mix(in srgb, var(--DS-primary-90) 16%, #9fb0cd) 84%, var(--erp-surface));
    --erp-text-inverse: #ffffff;

    /* The module bar and rail sit one step ABOVE the page, not below it: the navigation should
       read as chrome in front of the content, which is how it reads in the light theme too. */
    --erp-rail-bg:          color-mix(in srgb, var(--DS-primary-90) 16%, #101a2f);
    --erp-rail-bg-2:        color-mix(in srgb, var(--DS-primary-90) 16%, #101a2f);
    --erp-rail-bg-active:   color-mix(in srgb, var(--DS-primary-90) 16%, #22324e);
    --erp-rail-text:        color-mix(in srgb, var(--DS-primary-90) 16%, #c2cee4);
    /* Softened for the same reason as the block above: on a dark rail this is a large area of
       type, not a button label. */
    --erp-rail-text-active: var(--erp-text);
    --erp-rail-text-hover:  color-mix(in srgb, var(--DS-primary-90) 16%, #eaf0fb);
    --erp-rail-text-dim:    color-mix(in srgb, var(--DS-primary-90) 16%, #8598b8);
    --erp-rail-wash:        rgba(148, 175, 255, .10);
    --erp-rail-line:        rgba(148, 175, 255, .14);

    --erp-topnav-bg:          color-mix(in srgb, var(--DS-primary-90) 16%, #101a2f);
    --erp-topnav-bg-2:        color-mix(in srgb, var(--DS-primary-90) 16%, #101a2f);
    --erp-topnav-text:        rgba(233, 240, 252, .78);
    --erp-topnav-text-strong: var(--erp-text);
    --erp-topnav-wash:        rgba(148, 175, 255, .14);
    --erp-topnav-line:        rgba(148, 175, 255, .16);
}

/* --- nature of business and add-ons (super administrator only) ---------------- */
.erp-lic__label {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--erp-text-muted);
}

/* A grid rather than a flex row: eleven cards each carrying a line of examples, and a wrapped
   flex row sized them all to the longest one. */
.erp-lic__natures {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 8px;
    margin-top: 6px;
}

.erp-lic__nature {
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: 10px 14px;
    border-radius: var(--erp-radius);
    background: var(--erp-surface);
    border: 1px solid var(--erp-border);
    cursor: pointer;
    text-align: left;
}

.erp-lic__nature:hover { border-color: var(--erp-brand-300); }

/* The chosen nature is filled, not merely outlined: it decides every tick below it, so it
   has to be unmistakable at a glance. */
.erp-lic__nature.is-on {
    color: var(--erp-text-inverse);
    background: linear-gradient(135deg, var(--erp-brand-500), var(--erp-brand-700));
    border-color: var(--erp-brand-600);
}

.erp-lic__naturename { font-weight: 700; font-size: 13px; }

/* The examples are the point of the card, so they get room to be read rather than being
   truncated into a tooltip nobody opens. */
.erp-lic__natureeg { font-size: 11px; line-height: 1.45; opacity: .8; }

.erp-lic__naturen { font-size: 11px; opacity: .7; font-variant-numeric: tabular-nums; }

.erp-lic__group {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--erp-brand-600);
    margin: 12px 0 5px;
}

.erp-lic__modules {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
    gap: 6px;
}

.erp-lic__module {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 8px;
    border-radius: var(--erp-radius-sm);
    border: 1px solid var(--erp-border);
    font-size: 12px;
    cursor: pointer;
}

.erp-lic__module.is-on { background: var(--erp-brand-50); border-color: var(--erp-brand-300); }

/* Included by the nature: shown as fact, not as a choice. */
.erp-lic__module.is-locked { cursor: default; opacity: .75; }

/* A feature sold as part of a bigger one — Payroll inside HR, Fleet inside Assets. Indented so a
   vendor can see it is a half of something rather than a peer of it. */
.erp-lic__module--child { margin-left: 18px; }

.erp-lic__tag {
    margin-left: auto;
    font-size: 10px;
    padding: 2px 6px;
    border-radius: 999px;
    color: var(--erp-text-muted);
    background: var(--erp-surface-sunken);
    white-space: nowrap;
}

.erp-lic__tag--paid {
    color: var(--erp-text-inverse);
    background: var(--erp-brand-500);
}

/* --- EVERY grid sizes itself to the window, not to a hard-coded total -------------------------
   Grid columns across this app carried fixed pixel Widths - 3,903 of them - so each table was
   laid out at the sum of its columns whatever the screen was: dead space on a wide monitor and a
   squeeze on a laptop. They now carry MinWidth only, which is a FLOOR rather than a size, and
   DevExpress shares the real width between them.

   Two rules make that behave. A grid must be allowed to fill its container, and when the floors
   together exceed the room - a phone, or a user who has dragged columns wider than the window -
   the table has to SCROLL SIDEWAYS rather than compress past legibility. A crushed numeric column
   on an invoice is not a cosmetic problem; it is a misread figure.

   Whatever the operator then drags to is remembered per grid by GridLayoutStore, so "responsive"
   and "the user defines the size" are not in tension: the window decides the starting point, the
   operator overrides it, and the override is what comes back next time. */
.erp-content .dxbl-grid,
.erp-content .dxbl-grid-table { width: 100%; }

.erp-content .dxbl-grid-table-container,
.erp-content .dxbl-grid-scroll-viewer,
.erp-content .dxbl-scroll-viewer { overflow-x: auto; }

/* Line grids keep their denser padding; the sizing above already applies to them. */
.erp-linegrid { width: 100%; }

/* =============================================================================================
   SIDE A | SIDE B - the customer and supplier masters (DxSplitter)

   A real splitter rather than two CSS columns, because where the divider falls is the operator's
   business and it changes through the day: working the list wants side A wide, reading a
   customer's history wants side B wide. DevExpress draws and drags it; this only dresses it.
   ============================================================================================= */
.erp-party-split { border: 1px solid var(--erp-border); border-radius: var(--erp-radius, 8px); }

/* Each pane scrolls on its own. If the page scrolled as one, reading the bottom of a customer's
   invoice tab would scroll the name you clicked off the screen and lose your place in both. */
.erp-party-pane { height: 100%; overflow: auto; padding: 10px 12px; }

/* The list grid fills its pane and drops its own outer border - the splitter draws the frame. */
.erp-party-pane > .dxbl-grid { border: 0; }

.erp-party__prompt {
    padding: 44px 18px;
    text-align: center;
    color: var(--erp-text-muted);
}

.erp-party__prompt i { font-size: 22px; display: block; margin-bottom: 8px; }

/* ---- the record card: a grouped, readable master record -------------------------------------
   The first version listed every field in one flat run - code, contact, phone, mobile, email,
   TRN, type, group, credit days, discount, invoices, last invoice, two addresses - and on a
   record where most are blank it read as fourteen dashes in a column. That is a data dump, not a
   record.

   Fields are therefore GROUPED the way somebody thinks about a party (who they are, how to reach
   them, what the tax and credit terms are, where things go, what they have traded), each group
   under a quiet heading and split by a hairline.

   AND A BLANK IS DRAWN QUIETLY. An unfilled field still appears - on a master record "there is no
   TRN" is information, and hiding it would leave the operator unsure whether it is missing or
   just not shown - but it is dimmed so the eye lands on what is actually there. */
/* GROUP BOXES, SIDE BY SIDE — not one long column of hairline-separated runs.
   Stacked groups meant the four short groups on a customer ran the full height of the pane one
   under another, with a rule between them doing the work a box does better. Each group is now a
   boxed panel, and the panels themselves flow two or three to a row wherever the pane is wide
   enough, which is what actually shortens the record: a customer that needed 478px of height at
   900px wide now needs roughly a third of that. */
.erp-recsecs {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(286px, 1fr));
    gap: 12px;
    /* stretch, not start: boxes on the same row share a bottom edge, which is what makes a row
       of them read as one band rather than as three cards of three different lengths. */
    align-items: stretch;
}

.erp-recsec {
    padding: 10px 13px 12px;
    border: 1px solid var(--erp-border);
    border-radius: var(--erp-radius, 8px);
    background: var(--erp-surface);
    container-type: inline-size;
    container-name: recsec;
}

/* The ledger snapshot and a remarks paragraph are the width of the record, not of a column. */
.erp-recsec--wide { grid-column: 1 / -1; }

.erp-recsec__head {
    display: flex;
    align-items: center;
    gap: 7px;
    margin: 0 0 8px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--erp-text-muted);
}

/* LABEL BESIDE VALUE, NOT ABOVE IT.
   The fields used to be label-over-value cells in stretched columns, so on a wide pane three
   short fields — a code, a type, a group — were spread across the whole width with a hand's
   width of nothing between them, and every pair floated free with nothing to line up against.
   That is what read as scattered.
   Now each field is its own two-column row: a fixed label column and the value beside it. The
   labels form one straight edge and the values form another, so a section reads as a record
   card whatever the pane is doing, and a half-empty record shows its blanks in a column instead
   of as holes. Pairs still flow into two side-by-side columns when there is room for them. */
.erp-recgrid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
}

.erp-recgrid > div {
    display: grid;
    grid-template-columns: 118px minmax(0, 1fr);
    gap: 0 14px;
    align-items: baseline;
    padding: 4px 0;
}

/* A narrow pane keeps the two columns — it is the wrapping that scattered them, not the width —
   but gives the label less of it. */
@container recsec (max-width: 430px) {
    .erp-recgrid > div { grid-template-columns: 104px minmax(0, 1fr); gap: 0 10px; }
}

.erp-recgrid > div { min-width: 0; }
.erp-recgrid__wide { grid-column: 1 / -1; }

/* A value with no label of its own — the remarks block — takes the whole row rather than
   sitting in the narrow label column with nothing beside it. */
.erp-recgrid > div > strong:only-child { grid-column: 1 / -1; }

.erp-recgrid span {
    display: block;
    margin-bottom: 0;
    font-size: 11.5px;
    line-height: 1.4;
    color: var(--erp-text-muted);
}

.erp-recgrid strong {
    display: block;
    font-weight: 600;
    line-height: 1.4;
    overflow-wrap: anywhere;
}

/* Not filled in. Lighter and unbolded, so a record of mostly-blanks does not shout. */
.erp-recgrid strong.is-blank { font-weight: 400; opacity: .38; }

.erp-recactions {
    display: flex;
    gap: 8px;
    align-items: center;
    margin-top: 6px;
    padding-top: 13px;
    border-top: 1px solid var(--erp-border);
}

.erp-partytab__foot {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 4px 2px;
}


/* ==========================================================================
   DevExpress grid ink — dark themes only
   --------------------------------------------------------------------------
   Same request as the rest of the chrome: nothing on a dark ground should be
   pure white, because a full grid of it is the most tiring surface in the
   product. DevExpress publishes the grid's ink as its own custom properties,
   so this is a colour swap on the vendor's own knobs — no rule of theirs is
   overridden, no structure is touched, and the light themes are untouched.

   Every state listed here sits on a DARK background: the grid itself, the
   hover tint, the selected row (#012822 on mint), the row being edited, the
   footers and the column captions. Those all take the softened ink.

   --dxbl-grid-focus-color IS DELIBERATELY ABSENT. Its background is the full
   brand colour (--dxbl-grid-focus-bg resolves to #018574 on mint, #ea005e on
   rose), so the focused row is the one place in the grid where white sits on
   a saturated fill. That contrast is already tight; dimming it would push the
   row you are actually pointing at below the rest. It keeps the bright ink.
   Same reasoning as the buttons and the numbered chips.
   ========================================================================== */
html[data-theme="dark"] .dxbl-grid {
    --dxbl-grid-color:              var(--erp-text);
    --dxbl-grid-header-color:       var(--erp-text);
    --dxbl-grid-footer-color:       var(--erp-text);
    --dxbl-grid-group-footer-color: var(--erp-text);
    --dxbl-grid-hover-color:        var(--erp-text);
    --dxbl-grid-selection-color:    var(--erp-text);
    --dxbl-grid-edit-row-color:     var(--erp-text);
    --dxbl-grid-touch-color:        var(--erp-text);
}

/* ==========================================================================
   Fluent Purple Dark and Fluent Orchid Dark — accent damping
   --------------------------------------------------------------------------
   Reported as still too bright when the other nine darks read as comfortable.
   Measuring them did NOT explain it: their page and surface luminance sit
   mid-pack (purple .0114, orchid .0111, against steel .0126 and cool-blue
   .0124), and by luminance x chroma the accent ink is LESS intense than mint,
   rust or cool-blue. So there is no defect in the numbers to point at.

   What the numbers miss is that both of these are violets. The eye focuses
   short wavelengths less sharply than the middle of the spectrum, so a
   saturated blue-violet on a dark ground shimmers at a luminance where a teal
   or an amber sits still. That is a property of the hue, not of the value,
   which is why the two violet accents are the two that were picked out and
   why no threshold on brightness would have found them.

   So the fix is applied to exactly those two, by hue, and does not touch the
   other nine. Both rungs are re-derived from the SAME DevExpress source they
   already come from, mixed toward the theme's own surface — nothing new is
   introduced and nothing is hardcoded.

   The accent INK is damped harder than the accent FILL: the ink is what
   shimmers, being thin strokes of saturated colour on a dark ground, while the
   fill is a solid block that carries the theme's identity and should not be
   washed out. Damping the fill also darkens it slightly, which RAISES the
   contrast of the white text sitting on it.
   ========================================================================== */
html[data-theme="dark"][data-skin="purple-dark"],
html[data-theme="dark"][data-skin="orchid-dark"] {
    --erp-brand-300: color-mix(in srgb,
                       var(--bs-primary-border-subtle, var(--DS-primary-60, #2e989f)) 86%,
                       var(--erp-surface));
    --erp-brand-500: color-mix(in srgb,
                       var(--bs-primary, var(--DS-primary-90, #4db6bd)) 88%,
                       var(--erp-surface));
    --erp-brand-600: color-mix(in srgb,
                       var(--bs-link-hover-color, var(--DS-primary-100, #6cc5cb)) 84%,
                       var(--erp-surface));
    --erp-brand-700: color-mix(in srgb,
                       var(--bs-primary-text-emphasis, var(--DS-primary-120, #9ddade)) 84%,
                       var(--erp-surface));
}

/* The testing nature on the licensing screen. Set apart from the eleven real trades so it is
   never mistaken for one — a dashed edge reads as "not a normal choice" without needing a
   warning colour, which would suggest something is wrong rather than something is deliberate. */
.erp-lic__nature--all {
    border-style: dashed;
    border-color: var(--erp-border-strong);
}
.erp-lic__nature--all.is-on {
    border-style: solid;
}

/* ==========================================================================
   Editor buttons must not outgrow the editor they sit in
   --------------------------------------------------------------------------
   Reported on the new-expense form: the dropdowns show no bottom line under
   the arrow, and the Amount and VAT % arrows fall outside the field. Both are
   the same defect, and both are ours, not DevExpress's.

   Two earlier rules in this file give buttons a comfortable tap target —
   min-height 30px normally, 24px inside a grid. A dropdown arrow and a spin
   arrow ARE .dxbl-btn, so they were caught by those rules, and MIN-HEIGHT
   BEATS HEIGHT: the later rules that pin these two to the editor's height
   (`height: 14px`, `min-height: 0`) were being overruled and could not win,
   because the general rules carry more classes. Measured before this change:

     dropdown arrow   30px tall inside a 30px bordered box, so it hung 1px
                      past the bottom border and painted over it — which is
                      exactly "no bottom line where the down arrow is", and
                      why the in-grid combos, whose arrow is 28px, looked fine
     spin arrows      24px EACH, two stacked = 48px inside a 28px group, the
                      lower one ending 18px below the field

   The fix is specificity only. Same intent as the rules that were losing,
   written so they win: qualified with .dxbl-btn (and a .dxbl-grid variant to
   out-rank the in-grid rule), and placed last. Nothing about the design
   changes — no new colour, no new spacing, no DevExpress markup touched, and
   the general button sizing that these two are exceptions to is untouched.

   Heights are given as percentages of the button group rather than fixed
   pixels, so one rule is correct in both the 30px form editors and the 24px
   in-grid ones instead of being right in one and wrong in the other. 50% is
   what makes two stacked spin arrows exactly fill their group.
   ========================================================================== */
/* NAMING THE BUTTONS ONE BY ONE WAS THE WRONG SHAPE, and this is the corrected
   form. DevExpress puts three kinds of button inside an editor — dropdown,
   spin, and CLEAR (dxbl-edit-btn-clear, 68 rules in its stylesheet) — and the
   first version of this fix named only the two that had been reported. The
   third has the identical defect for the identical reason and would have
   surfaced later as a new bug report. The rule below therefore lifts the
   minimum off EVERY button inside an editor, so a button type this application
   has not met yet is covered too, and only then sets the heights. */
.erp-content .dxbl-text-edit .dxbl-btn:not(.dxbl-btn-tool),
.erp-content .dxbl-grid .dxbl-text-edit .dxbl-btn:not(.dxbl-btn-tool) {
    min-height: 0;
}
.erp-content .dxbl-text-edit .dxbl-btn.dxbl-edit-btn-dropdown,
.erp-content .dxbl-text-edit .dxbl-btn.dxbl-edit-btn-clear,
.erp-content .dxbl-grid .dxbl-text-edit .dxbl-btn.dxbl-edit-btn-dropdown,
.erp-content .dxbl-grid .dxbl-text-edit .dxbl-btn.dxbl-edit-btn-clear {
    min-height: 0;
    height: 100%;
}
.erp-content .dxbl-text-edit .dxbl-btn.dxbl-spin-btn-inc,
.erp-content .dxbl-text-edit .dxbl-btn.dxbl-spin-btn-dec,
.erp-content .dxbl-grid .dxbl-text-edit .dxbl-btn.dxbl-spin-btn-inc,
.erp-content .dxbl-grid .dxbl-text-edit .dxbl-btn.dxbl-spin-btn-dec {
    min-height: 0;
    height: 50%;
    padding: 0;
    line-height: 1;
}

/* The theme catalogue's body inset.
   ------------------------------------------------------------------------------------------
   Reported as the colour list running edge to edge with no space around it. Same cause as the
   editor popups documented further up: DevExpress does not put a popup's CssClass on the modal,
   and here there is no .dxbl-modal-body element at all — the body template is dropped straight
   into .dxbl-modal-content, which has padding 0. So there was nothing of ours to hang an inset
   on, and no DevExpress padding to override either. The wrapper in ThemeSwitcher.razor gives us
   that something.

   The horizontal value is DEVEXPRESS'S OWN, not a number picked to look right: the header and
   footer of this same window are inset by --dxbl-popup-body-padding-x, so taking that variable
   lines the tiles up with the title above them and the Close button below, and keeps them lined
   up if a theme changes the figure. Measured at this app's 14px root it resolves to the 21px the
   header already used.

   Vertical is small and deliberate — the header and footer already contribute their own spacing,
   so this only stops the first line sitting against the title rule and the last row of tiles
   against the footer. */
/* Now shared. Nine more popups were in the same position — list pickers, confirm dialogs and
   the payment-run line editor, where a DxFormLayout carried no .erp-form and so its captions sat
   on the window edge. They wear .erp-popupbody and take the identical inset, so all of them agree
   with each other and with the popups that were already right.

   NOT APPLIED AS A BLANKET RULE ON .dxbl-modal-content, though that would have been fewer edits.
   Of the 150 popups in the application most already hold an .erp-form, which carries padding of
   its own and would then have been indented twice; and several — the document viewer, the label
   previews — are full-bleed deliberately. A rule that cannot tell those apart would have been
   wrong more often than right, so the inset goes only where it was checked. */
.erp-popupbody,
.erp-themecat__body {
    padding-left:  var(--dxbl-popup-body-padding-x, 1.5rem);
    padding-right: var(--dxbl-popup-body-padding-x, 1.5rem);
    padding-top: 4px;
    padding-bottom: 8px;
}

/* A combo that needs room for its longest option — a dimension name, a record type.
   A CLASS, NOT AN INLINE style="", because DxComboBox does not splat HTML attributes: the inline
   min-width these two pages used to carry was dropped by DevExpress and never applied to
   anything. Found by Tools/ThemeTest, which checks splat support by reflection. */
.erp-combo--wide { min-width: 220px; }

/* The general ledger's account picker.
   DxTagBox.DropDownHeight sets the popup, but the list inside keeps its own ceiling — so the
   default shows about five accounts and finding one in a chart of eighty means scrolling in
   five-row steps. Both are raised together; changing only one leaves a tall popup with a short
   list in it and a band of empty space underneath. */
.erp-gl-accounts__list,
.erp-gl-accounts__list .dxbl-listbox-list { max-height: 420px; min-width: 380px; }

/* A grid whose cells WRAP rather than truncate.
   DevExpress keeps grid cells on one line, so a long narration or contra account is cut off with
   no way to read the rest — and dragging the column wider only moves the problem to the next
   column. Wrapping lets the row grow instead, which is the right trade on a ledger: the text
   matters more than a uniform row height.
   Opt-in by class, never blanket — a grid of figures reads worse ragged, and only the pages that
   carry long text ask for this. */
.erp-grid--wrap .dxbl-grid-table > tbody > tr > td {
    white-space: normal;
    word-break: break-word;
    vertical-align: top;
}

/* A line grid that needs air above it. Was an inline CssStyle="margin-top:10px" on DxGrid — a
   parameter DxGrid does not have, so the margin never applied. A class does. */
.erp-linegrid--spaced { margin-top: 12px; }

/* ---- attachment previews ---------------------------------------------------------------------
   A strip of small pages under the attachment list, each clickable to open full size.

   THE SHAPE IS A5, because that is what the thing being previewed usually is: a receipt, an
   invoice, a certificate. 1:1.414 is the ISO ratio, so a scanned A4 or A5 page fills the frame
   instead of sitting letterboxed inside it. 210px wide is about a thumbnail's worth — big enough
   to recognise a document, small enough that four fit across a form.
   
   object-fit: contain, never cover: cropping a receipt to fill a box hides the total, which is the
   one thing somebody is looking for. */
.erp-thumbs {
    display: flex; flex-wrap: wrap; gap: 14px;
    margin-top: 14px; padding: 4px 2px;
}
.erp-thumb {
    position: relative; margin: 0; cursor: pointer;
    width: 210px;
    border: 1px solid var(--erp-line); border-radius: 8px;
    background: var(--erp-surface); overflow: hidden;
    transition: border-color .12s ease, box-shadow .12s ease, transform .12s ease;
}
.erp-thumb:hover, .erp-thumb:focus-visible {
    border-color: var(--erp-brand);
    box-shadow: 0 4px 14px -6px rgb(0 0 0 / 30%);
    transform: translateY(-1px);
}
.erp-thumb > img,
.erp-thumb > object {
    display: block; width: 100%; height: 297px;   /* 210 x 297 — A5 in the same ratio as A4 */
    object-fit: contain; background: var(--erp-surface-2); border: 0;
}
/* A PDF <object> swallows the click — the plug-in gets it, not the figure. This sits over it so
   the whole tile stays clickable, which is the behaviour the picture promises. */
.erp-thumb__veil { position: absolute; inset: 0 0 26px 0; }
.erp-thumb__fallback {
    display: grid; place-items: center; height: 297px;
    font-size: 34px; opacity: .35; background: var(--erp-surface-2);
}
.erp-thumb figcaption {
    padding: 5px 8px; font-size: 11.5px; line-height: 1.35;
    border-top: 1px solid var(--erp-line); color: var(--erp-text-soft);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
@media (max-width: 560px) {
    .erp-thumb { width: 100%; }
    .erp-thumb > img, .erp-thumb > object, .erp-thumb__fallback { height: 240px; }
}

/* ==================================================================================================
   Documents › Browse — the folder tree beside the file list.

   Two panes with the tree fixed and the list taking the rest, because a file manager whose tree
   reflows as you click through it is disorienting. The tree scrolls independently: a company with
   ten modules and twenty years of history has a taller tree than a screen.
   ================================================================================================== */
.erp-browse {
    display: grid;
    grid-template-columns: minmax(200px, 280px) 1fr;
    gap: 12px;
    align-items: start;
    padding: 12px;
}

.erp-browse__tree {
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: 6px;
    padding: 8px;
    max-height: calc(100vh - 260px);
    overflow: auto;
    background: var(--bs-body-bg, #fff);
}

.erp-browse__files { min-width: 0; }   /* lets the grid scroll instead of stretching the page */

.erp-browse__crumb {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    padding: 6px 2px 10px;
}

.erp-browse__crumb strong { font-family: var(--bs-font-monospace, monospace); }

/* Below a tablet the two panes stack: the tree becomes a short scrollable strip above the files
   rather than squeezing the list into a column too narrow to read a file name in. */
@media (max-width: 900px) {
    .erp-browse { grid-template-columns: 1fr; }
    .erp-browse__tree { max-height: 220px; }
}

/* Folder rows read as folders: heavier than a file name, and the whole row is a click target
   because that is how every file manager behaves. */
.erp-browse__folder { font-weight: 600; }
.erp-browse__grid .dxbl-grid-table > tbody > tr { cursor: default; }
.erp-browse__crumb { gap: 2px; }
.erp-browse__sep { opacity: .45; padding: 0 2px; }


/* ==================================================================================================
   The paperclip on a list row, and the little file picker it opens.

   It sits INSIDE a grid whose rows navigate, so it has to read as its own target rather than as
   decoration on the cell: a hit area, a hover state and a focus ring. Keyboard users reach it with
   Tab like any button, which is why it carries role and tabindex in the markup.
   ============================================================================================== */

.erp-clip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 6px;
    border-radius: 6px;
    cursor: pointer;
    color: var(--erp-text-soft);
    font-size: 12px;
    line-height: 1.6;
}

.erp-clip:hover { background: var(--erp-surface-sunken); color: var(--erp-brand); }
.erp-clip:focus-visible { outline: 2px solid var(--erp-brand); outline-offset: 1px; }
.erp-clip i { font-size: 14px; }

.erp-cliplist__row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 2px;
    border-top: 1px solid var(--erp-line);
}

.erp-cliplist__row:first-child { border-top: 0; }
.erp-cliplist__main { flex: 1 1 auto; min-width: 0; }

.erp-cliplist__name {
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.erp-cliplist__sub { font-size: 11.5px; color: var(--erp-text-muted); }
.erp-cliplist__note { font-size: 11.5px; color: var(--erp-text-muted); white-space: nowrap; }

/* ---------------------------------------------------------------- role centre: locations ----
   "Where the stock is". A row per store, and one bar drawn against the largest store rather
   than against the total — the comparison a warehouse manager makes is store against store.
   Deliberately flat: no card, no shadow, no second accent. The bar IS the emphasis, and if it
   competes with anything the page has two focal points and therefore none.                    */
.erp-loc { display: flex; flex-direction: column; border-top: 1px solid var(--erp-border); }
.erp-loc__row {
    display: grid;
    grid-template-columns: minmax(150px, 1.4fr) minmax(80px, 2fr) 78px 108px;
    gap: 14px; align-items: center;
    width: 100%; padding: 9px 4px; text-align: left;
    background: none; border: 0; border-bottom: 1px solid var(--erp-border);
    color: inherit; font: inherit; cursor: pointer;
}
.erp-loc__row:hover { background: var(--erp-surface-sunken); }
.erp-loc__row:focus-visible { outline: 2px solid var(--erp-brand-600); outline-offset: -2px; }
.erp-loc__name { font-size: 13px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.erp-loc__track { height: 6px; background: var(--erp-surface-sunken); border-radius: 1px; overflow: hidden; }
.erp-loc__fill { display: block; height: 100%; background: var(--erp-brand-600); }
.erp-loc__items { font-size: 11.5px; color: var(--erp-text-muted); text-align: right; }
.erp-loc__value { font-size: 13px; text-align: right; font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- role centre: movement feed -
   Sentences, not a grid. "340 out of Site Store - Pump Station" is read by everybody; a row of
   QtyIn / QtyOut columns is read by an accountant.                                             */
.erp-feed { display: flex; flex-direction: column; border-top: 1px solid var(--erp-border); }
.erp-feed__row {
    display: grid; grid-template-columns: 22px 1fr 56px; gap: 10px; align-items: baseline;
    width: 100%; padding: 8px 4px; text-align: left;
    background: none; border: 0; border-bottom: 1px solid var(--erp-border);
    color: inherit; font: inherit; cursor: pointer;
}
.erp-feed__row:hover { background: var(--erp-surface-sunken); }
.erp-feed__row:focus-visible { outline: 2px solid var(--erp-brand-600); outline-offset: -2px; }
.erp-feed__dir { font-size: 12px; line-height: 1; align-self: center; }
.erp-feed__dir--in  { color: var(--erp-ok, #1f6b4e); }
.erp-feed__dir--out { color: var(--erp-text-muted); }
.erp-feed__text { font-size: 13px; }
.erp-feed__text strong { font-variant-numeric: tabular-nums; }
.erp-feed__where { color: var(--erp-text-muted); }
.erp-feed__when { font-size: 11.5px; color: var(--erp-text-muted); text-align: right; white-space: nowrap; }

.erp-rc__quiet { font-size: 13px; color: var(--erp-text-muted); padding: 10px 2px; }

/* ---------------------------------------------------------------- camera barcode scanning ----
   The sheet covers the page rather than sitting in the form: a phone held up to a carton has no
   room for anything else on screen, and a small preview is a preview the operator cannot aim.  */
.erp-scan__btn {
    display: inline-flex; align-items: center; gap: 6px;
    font: inherit; font-size: 13px; padding: 5px 11px;
    background: var(--erp-surface-sunken); color: inherit;
    border: 1px solid var(--erp-border-strong); border-radius: 3px; cursor: pointer;
}
.erp-scan__btn:hover { background: var(--erp-surface); border-color: var(--erp-brand-600); }
.erp-scan__btn:focus-visible { outline: 2px solid var(--erp-brand-600); outline-offset: 1px; }
.erp-scan__btn i { font-size: 15px; line-height: 1; }

.erp-scan__sheet {
    position: fixed; inset: 0; z-index: 1090;
    background: rgba(8, 12, 14, .82);
    display: flex; align-items: center; justify-content: center; padding: 16px;
}
/* display:flex above is a CLASS rule and the browser's own rule for [hidden] is display:none on
   the element — so the class wins and the panel never hides. It has to be said again, here, with
   the attribute in the selector. This is the bug that made the scanner impossible to close. */
.erp-scan__sheet[hidden] { display: none; }
.erp-scan__panel {
    width: min(560px, 100%); background: var(--erp-surface);
    border: 1px solid var(--erp-border-strong); border-radius: 4px; overflow: hidden;
}
.erp-scan__head {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 11px 14px; border-bottom: 1px solid var(--erp-border); font-size: 14px; font-weight: 600;
}
.erp-scan__close {
    background: none; border: 0; font: inherit; cursor: pointer; color: var(--erp-text-muted);
    padding: 2px 6px; line-height: 1;
}
.erp-scan__close:hover { color: inherit; }
.erp-scan__stage { position: relative; background: #000; aspect-ratio: 4 / 3; }
.erp-scan__video { width: 100%; height: 100%; object-fit: cover; display: block; }
/* A guide for the operator's hands, not a crop — the decoder reads the whole frame. */
.erp-scan__reticle {
    position: absolute; inset: 22% 12%; border: 2px solid rgba(255, 255, 255, .85);
    border-radius: 3px; box-shadow: 0 0 0 9999px rgba(0, 0, 0, .18);
    pointer-events: none;
}
.erp-scan__hint  { padding: 10px 14px; font-size: 12.5px; color: var(--erp-text-muted); }
.erp-scan__error { padding: 10px 14px; font-size: 12.5px; color: var(--erp-danger, #a13b2c); }

/* A refusal shown beside the control that caused it, rather than at the top of the page where
   somebody who just pressed a button at the bottom will never look. */
.erp-inline-error {
    margin-top: 6px; font-size: 12px; line-height: 1.45;
    color: var(--erp-danger, #a13b2c); max-width: 42ch;
}

/* ---------------------------------------------------------------- role centre: today ---------
   The only figures on the page that move while somebody is watching, so they are the only ones
   given size. In and out are separated by colour rather than by a label alone — a warehouse
   manager reads the direction before he reads the number.                                     */
.erp-today {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
    gap: 1px; background: var(--erp-border);
    border: 1px solid var(--erp-border);
}
.erp-today__cell { background: var(--erp-surface); padding: 12px 14px; }
.erp-today__n {
    font-size: 26px; font-weight: 300; line-height: 1.1; letter-spacing: -.02em;
    font-variant-numeric: tabular-nums;
}
.erp-today__n--in  { color: var(--erp-ok, #1f6b4e); }
.erp-today__n--out { color: var(--erp-brand-600); }
.erp-today__l { font-size: 11.5px; color: var(--erp-text-muted); margin-top: 3px; }
.erp-today__v {
    font-size: 12px; margin-top: 6px; color: var(--erp-text-muted);
    font-variant-numeric: tabular-nums;
}
/* The reading time, beside the heading. Small, because it is reassurance and not information. */
.erp-rc__asof {
    font-size: 11px; font-weight: 400; color: var(--erp-text-muted);
    text-transform: none; letter-spacing: 0; margin-left: 8px;
}
