/*
    SmartERP.Next — application styles
    ----------------------------------
    Depends on tokens.css. Organised as: reset, shell, primitives, components.
    No CSS framework and no build step: the file is served as authored, which
    keeps the whole surface small enough to read in one sitting.
*/

/* ====================================================== View transitions */

/*
    Cross-document transitions, so moving between screens animates instead of
    blinking white and redrawing.

    This is a server-rendered application: every navigation is a full document
    load, and the hard cut between them is the main thing that makes it feel
    like a website rather than an application. Two declarations change that, and
    the browsers that do not support it simply navigate as before.

    The rail and the topbar are given names of their own so the browser treats
    them as the SAME element across the two documents and leaves them alone.
    Without that they are part of the page snapshot and cross-fade along with
    everything else — the menu blinking on every click, which is worse than no
    transition at all.
*/
@view-transition { navigation: auto; }

.sidebar { view-transition-name: app-sidebar; }
.topbar { view-transition-name: app-topbar; }

::view-transition-old(root) {
    animation: vt-out 90ms var(--ease) both;
}
::view-transition-new(root) {
    animation: vt-in 220ms var(--ease) both;
}

@keyframes vt-out {
    to { opacity: 0; }
}
@keyframes vt-in {
    from { opacity: 0; transform: translateY(6px); }
    to { opacity: 1; transform: none; }
}

/*
    Reduced motion gets no transition rather than a faster one. The point of
    the preference is stillness, and a 90ms slide is still a slide.
*/
@media (prefers-reduced-motion: reduce) {
    ::view-transition-old(root),
    ::view-transition-new(root) { animation: none; }
}

/* ================================================================== Reset */

*,
*::before,
*::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

/*
    The page ground: three very soft colour blooms over the base surface.

    This exists to be seen through. Frosted glass over a single flat colour is
    just a grey box with an expensive filter on it — the material only reads as
    material when there is something behind it for the blur to carry. The blooms
    are kept under 10% alpha and fixed to the viewport, so they read as light in
    the room rather than as decoration that scrolls with the data.
*/
/*
    THE GROUND, AND ITS BUDGET.

    Two rules govern this stack, and both are arithmetic:

      1. No bloom may take any channel of the ground below sRGB 0.70 in light
         or above sRGB 0.34 in dark. Those are the numbers --glass-panel's
         alpha was chosen against in tokens.css. Move them and the contrast
         floor moves with them.

      2. The blooms must overlap at DIFFERENT SCALES. The first layer is a
         wide, very faint diagonal sweep rather than a fifth blob, because a
         lens needs a luminance GRADIENT underneath it, not more colour. It is
         what gives the top-left and bottom-right of every pane a real
         luminance difference for the rim to catch — the difference between a
         rectangle with a white line on it and a piece of glass.

    background-attachment: fixed is load-bearing, not decorative: it keeps the
    backdrop static while the page scrolls, which is what lets the browser keep
    the topbar's blur result between frames instead of recomputing it.
*/
body {
    margin: 0;
    background:
        linear-gradient(118deg,
            rgb(255 255 255 / 42%) 0%,
            rgb(255 255 255 / 0%) 34%,
            rgb(255 255 255 / 0%) 66%,
            rgb(255 255 255 / 30%) 100%),
        radial-gradient(1000px 560px at 6% -10%, rgb(var(--accent-rgb) / 26%), transparent 58%),
        radial-gradient(860px 520px at 104% -6%, rgb(var(--bloom-2-rgb) / 22%), transparent 55%),
        radial-gradient(900px 620px at 44% 112%, rgb(var(--bloom-3-rgb) / 18%), transparent 58%),
        radial-gradient(700px 420px at 88% 92%, rgb(var(--bloom-4-rgb) / 12%), transparent 55%),
        var(--surface-app);
    background-attachment: fixed;
    min-height: calc(100vh / var(--zoom, 1));
    color: var(--text);
    font-family: var(--font-sans);
    font-size: var(--text-base);
    line-height: var(--leading-normal);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

/*
    The same blooms, lifted for the dark ground.

    At the light-mode alphas they vanish into a near-black page: 20% violet over
    #0a101c is still very nearly #0a101c. Dark needs both more opacity and more
    reach for the colour to register at all, and it is on dark that the floating
    panes most need something behind them to float on. The sweep inverts too,
    because on dark the light comes from the panes, not from the ground.

    The explicit-dark selector and the OS arm are SEPARATE rules, and the OS arm
    stays inside its media query. :root:not([data-theme="light"]) on its own is
    TRUE on a default light page; written outside the query it would hand every
    light-theme user the dark ground.
*/
:root[data-theme="dark"] body {
    background:
        linear-gradient(118deg,
            rgb(255 255 255 / 6%) 0%,
            rgb(255 255 255 / 0%) 36%,
            rgb(0 0 0 / 14%) 100%),
        radial-gradient(1100px 620px at 4% -12%, rgb(var(--accent-rgb) / 26%), transparent 60%),
        radial-gradient(900px 560px at 106% -8%, rgb(var(--bloom-2-rgb) / 24%), transparent 58%),
        radial-gradient(950px 660px at 42% 114%, rgb(var(--bloom-3-rgb) / 20%), transparent 60%),
        radial-gradient(740px 440px at 90% 94%, rgb(var(--bloom-4-rgb) / 14%), transparent 56%),
        var(--surface-app);
    background-attachment: fixed;
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) body {
        background:
            linear-gradient(118deg,
                rgb(255 255 255 / 6%) 0%,
                rgb(255 255 255 / 0%) 36%,
                rgb(0 0 0 / 14%) 100%),
            radial-gradient(1100px 620px at 4% -12%, rgb(var(--accent-rgb) / 26%), transparent 60%),
            radial-gradient(900px 560px at 106% -8%, rgb(var(--bloom-2-rgb) / 24%), transparent 58%),
            radial-gradient(950px 660px at 42% 114%, rgb(var(--bloom-3-rgb) / 20%), transparent 60%),
            radial-gradient(740px 440px at 90% 94%, rgb(var(--bloom-4-rgb) / 14%), transparent 56%),
            var(--surface-app);
        background-attachment: fixed;
    }
}

h1, h2, h3, h4, h5, h6 {
    margin: 0;
    font-weight: 600;
    line-height: var(--leading-tight);
    letter-spacing: -0.011em;
}

p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

a {
    color: var(--accent);
    text-decoration: none;
    transition: color var(--duration-fast) var(--ease);
}
/* Not a link that is dressed as a button: a:hover (0,1,1) out-ranks .btn--primary (0,1,0),
   so every <a class="btn btn--primary"> turned blue-on-blue under the pointer. */
a:not(.btn):hover { color: var(--accent-strong); text-decoration: underline; }

img, svg { max-width: 100%; vertical-align: middle; }

button, input, select, textarea { font: inherit; color: inherit; }

/* One consistent focus ring everywhere, and only for keyboard users. */
:focus-visible {
    outline: 2px solid var(--border-focus);
    outline-offset: 2px;
    border-radius: var(--radius-xs);
}
:focus:not(:focus-visible) { outline: none; }

.visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* Skip link — visible only once focused. */
.skip-link {
    position: absolute;
    left: var(--space-4);
    top: -60px;
    z-index: 200;
    padding: var(--space-2) var(--space-4);
    background: var(--accent);
    color: var(--accent-contrast);
    border-radius: var(--radius-sm);
    transition: top var(--duration) var(--ease);
}
.skip-link:focus { top: var(--space-4); text-decoration: none; }

/* ================================================================== Shell */

.app {
    display: grid;
    grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
    min-height: calc(100vh / var(--zoom, 1));
    transition: grid-template-columns var(--duration) var(--ease);
}
.app[data-nav="collapsed"] { grid-template-columns: var(--sidebar-width-collapsed) minmax(0, 1fr); }

/*
    The scrim only exists for the mobile overlay rail. It must stay out of the
    grid the rest of the time, or it takes the first column and pushes the
    sidebar into the content track.
*/
.scrim { display: none; }

/* ---------------------------------------------------------------- Sidebar */

/*
    The rail is a dark translucent panel rather than a solid block, so the page
    behind it tints it and it reads as a layer above the application instead of
    a wall beside it.

    The colour is a gradient rather than one value: a lit panel is brighter
    where the light falls on it, and a perfectly even fill is the thing that
    makes a flat interface look flat.
*/
.sidebar {
    position: sticky;
    top: 0;
    z-index: 30;
    height: calc(100vh / var(--zoom, 1));
    display: flex;
    flex-direction: column;
    /*
        On desktop the rail's backdrop is the page ground and nothing else, so
        it takes the fill and the rim and NO filter. Below 901px it becomes an
        overlay with real content sliding under it — and there it earns
        --lens-rail.

        The fill drops to 78% so the ground's colour runs through it, which is
        what breaks the dark-chrome-beside-light-content signature without
        lightening the rail.
    */
    background:
        linear-gradient(180deg,
            rgb(255 255 255 / 8%) 0%,
            rgb(255 255 255 / 2%) 24%,
            rgb(0 0 0 / 8%) 100%),
        color-mix(in srgb, var(--sidebar-bg) 78%, transparent);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    color: var(--sidebar-fg);
    border-right: 1px solid var(--sidebar-border);
    box-shadow: var(--rim-soft), 1px 0 0 rgb(0 0 0 / 12%);
    /* Visible so the module picker menu can open below the brand. The nav
       itself is the scrollport. */
    overflow: visible;
}

/* Without blur the rail goes solid, or the menu sits over whatever scrolls past. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .sidebar { background: var(--sidebar-bg); }
}

/*
    Reduce Transparency flattens the ground too: the blooms exist to be seen
    THROUGH, and with nothing translucent left they are decoration round the
    edges.
*/
@media (prefers-reduced-transparency: reduce) {
    body,
    :root[data-theme="dark"] body,
    :root:not([data-theme="light"]) body,
    .auth { background: var(--surface-app); }
    .sidebar { background: var(--sidebar-bg); }
}

.sidebar__brand-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
    min-height: 30px;
}

/* The link home to the module launcher (the only place modules switch). */
.module-home {
    margin-inline-start: auto;
    display: inline-grid;
    place-items: center;
    width: 28px; height: 28px;
    border-radius: var(--radius-sm);
    color: var(--sidebar-fg-muted);
    border: 1px solid transparent;
    text-decoration: none;
    flex-shrink: 0;
}
.module-home:hover, .module-home:focus-visible {
    color: var(--sidebar-active-fg);
    background: color-mix(in srgb, currentColor 12%, transparent);
    border-color: var(--sidebar-border);
    outline: none;
}

/* The active-module badge — makes the current workspace unmistakable. */
.sidebar__active-module {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    margin-top: var(--space-2);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--accent-rgb) 14%, transparent);
    font-weight: 650;
    font-size: .92rem;
    color: var(--sidebar-active-fg);
}
.sidebar__active-module.tone-blue    { background: color-mix(in srgb, var(--tone-blue) 16%, transparent); }
.sidebar__active-module.tone-green   { background: color-mix(in srgb, var(--tone-green) 16%, transparent); }
.sidebar__active-module.tone-amber   { background: color-mix(in srgb, var(--tone-amber) 18%, transparent); }
.sidebar__active-module.tone-neutral { background: color-mix(in srgb, var(--tone-neutral) 16%, transparent); }
.sidebar__active-icon { display: inline-grid; place-items: center; flex-shrink: 0; }
.sidebar__active-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.app[data-nav="collapsed"] .sidebar__active-name { display: none; }

.sidebar__brand {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-2);
    padding: var(--space-3);
    border-bottom: 1px solid var(--sidebar-border);
    flex-shrink: 0;
    position: relative;
    z-index: 40;
    overflow: visible;
}

.sidebar__mark {
    display: grid;
    place-items: center;
    width: 30px; height: 30px;
    flex-shrink: 0;
    border-radius: var(--radius-sm);
    background: linear-gradient(135deg, rgb(var(--accent-rgb)), rgb(var(--accent-strong-rgb)));
    color: var(--accent-contrast);
    font-weight: 700;
    font-size: var(--text-sm);
    letter-spacing: -0.03em;
}

/*
    The mark inside that tile. It is an SVG drawn in currentColor, so it picks
    up the tile's own `color` -- --accent-contrast, which the theme and the
    active module both change -- and needs no colour of its own.

    display:block kills the baseline gap an inline SVG otherwise leaves under
    itself, which pushed the glyph 2px above centre in the 30px tile.
*/
.sidebar__mark .brand-mark {
    width: 19px;
    height: 19px;
    display: block;
}

.sidebar__name {
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
    color: var(--sidebar-active-fg);
    letter-spacing: -0.02em;
    white-space: nowrap;
    flex-shrink: 0;
}
/* Sidebar is always dark — page accent is too deep to read on it. */
.sidebar__name span { color: var(--rail-accent); }

/*
    ".Next" sits back from "SmartERP" so the eye reads the product first and
    the edition second. Muted rather than smaller: shrinking it misaligned the
    baseline against "ERP" beside it.

    The suffix IS a span inside .sidebar__name, so `.sidebar__name span` above
    also matches it and would paint it accent-coloured -- making the whole
    wordmark read as one word. Two classes (0,2,0) outrank a class plus an
    element (0,1,1), so this wins on specificity and would do so wherever it
    sat in the file. It is placed next to the rule it overrides for reading,
    not for cascade order.
*/
.sidebar__name .sidebar__name-suffix {
    color: var(--sidebar-fg-muted);
    font-weight: var(--weight-medium);
}

.sidebar__nav {
    flex: 1;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--space-3) var(--space-2) var(--space-4);
}
/* A thin scrollbar so the rail does not jump when content overflows. */
.sidebar__nav::-webkit-scrollbar { width: 6px; }
.sidebar__nav::-webkit-scrollbar-thumb { background: rgb(255 255 255 / 12%); border-radius: 3px; }

.nav-group + .nav-group { margin-top: var(--space-2); }

.nav-pin {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-bottom: var(--space-4);
    padding-bottom: var(--space-3);
    border-bottom: 1px solid var(--sidebar-border);
}

.sidebar__module-head {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 0 var(--space-3) var(--space-3);
}

.sidebar__module-icon {
    display: grid;
    place-content: center;
    width: 26px;
    height: 26px;
    border-radius: var(--radius-sm);
    background: rgb(255 255 255 / 8%);
    color: var(--sidebar-fg);
    flex-shrink: 0;
}
.sidebar__module-icon.tone-blue { color: var(--rail-tone-blue); background: color-mix(in srgb, var(--rail-tone-blue) 14%, transparent); }
.sidebar__module-icon.tone-amber { color: var(--rail-tone-amber); background: color-mix(in srgb, var(--rail-tone-amber) 16%, transparent); }
.sidebar__module-icon.tone-neutral { color: var(--rail-tone-neutral); background: rgb(255 255 255 / 8%); }

.sidebar__module-name {
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--sidebar-active-fg);
    letter-spacing: -0.01em;
    white-space: nowrap;
}

.nav-group {
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    margin-bottom: 2px;
}

.nav-group__summary {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    list-style: none;
    cursor: pointer;
    padding: 7px var(--space-3);
    border-radius: var(--radius-sm);
    color: var(--sidebar-fg-muted);
    user-select: none;
}
.nav-group__summary::-webkit-details-marker { display: none; }
.nav-group__summary:hover {
    background: var(--sidebar-hover);
    color: var(--sidebar-active-fg);
}

.nav-group__label {
    flex: 1;
    min-width: 0;
    padding: 0;
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: inherit;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.nav-group__chevron {
    display: grid;
    place-content: center;
    opacity: .55;
    transition: transform .18s var(--ease);
}
.nav-group[open] > .nav-group__summary {
    color: var(--sidebar-active-fg);
}
.nav-group[open] > .nav-group__summary .nav-group__chevron {
    transform: rotate(180deg);
    opacity: .9;
}

.nav-group__body {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 0 0 var(--space-2);
}

.nav-link {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 7px var(--space-3);
    margin-bottom: 1px;
    border-radius: var(--radius-sm);
    color: var(--sidebar-fg);
    font-size: var(--text-base);
    white-space: nowrap;
    transition: background var(--duration-fast) var(--ease),
                color var(--duration-fast) var(--ease);
}
.nav-link:hover { background: var(--sidebar-hover); color: var(--sidebar-active-fg); text-decoration: none; }

/*
    The current item is marked by a quiet fill and the accent carried on its
    icon and its edge marker — not by a saturated blue block. A full-strength
    tint behind the label made the one item you are already looking at the
    loudest thing in the rail, and washed out the label it was meant to pick out.
*/
.nav-link[aria-current="page"] {
    background: rgb(255 255 255 / 9%);
    color: var(--sidebar-active-fg);
    font-weight: var(--weight-medium);
}
.nav-link[aria-current="page"] .nav-link__icon { color: rgb(var(--accent-rgb)); }

/* Active marker on the rail edge. */
.nav-link[aria-current="page"]::before {
    content: "";
    position: absolute;
    left: calc(var(--space-3) * -1 + 2px);
    top: 50%;
    translate: 0 -50%;
    width: 3px; height: 18px;
    border-radius: var(--radius-pill);
    background: rgb(var(--accent-rgb));
}

.nav-link__icon {
    display: grid;
    place-items: center;
    width: 18px; height: 18px;
    flex-shrink: 0;
    opacity: 0.85;
}
.nav-link[aria-current="page"] .nav-link__icon { opacity: 1; }

.nav-link__badge {
    margin-left: auto;
    padding: 1px 7px;
    border-radius: var(--radius-pill);
    background: rgb(255 255 255 / 12%);
    font-size: var(--text-xs);
    font-variant-numeric: tabular-nums;
}

/* Collapsed rail: labels and section headings drop out, icons stay centred. */
.app[data-nav="collapsed"] .sidebar__name,
.app[data-nav="collapsed"] .nav-link__text,
.app[data-nav="collapsed"] .nav-group__label,
.app[data-nav="collapsed"] .nav-group__chevron,
.app[data-nav="collapsed"] .sidebar__module-name,
.app[data-nav="collapsed"] .nav-link__badge,
.app[data-nav="collapsed"] .sidebar__footer-text { display: none; }

.app[data-nav="collapsed"] .nav-link { justify-content: center; padding-inline: 0; }
.app[data-nav="collapsed"] .sidebar__brand {
    align-items: center;
    padding-inline: var(--space-2);
}
.app[data-nav="collapsed"] .sidebar__brand-row {
    justify-content: center;
    flex-direction: column;
    gap: 8px;
}
.app[data-nav="collapsed"] .module-switch { margin-left: 0; }
.app[data-nav="collapsed"] .sidebar__module-head { justify-content: center; padding-inline: 0; }
.app[data-nav="collapsed"] .nav-group__summary {
    justify-content: center;
    padding: 6px 0;
}
.app[data-nav="collapsed"] .nav-group__body { display: none; }
.app[data-nav="collapsed"] .nav-pin {
    border-bottom-color: transparent;
    padding-bottom: 0;
    margin-bottom: var(--space-2);
}

.sidebar__footer {
    flex-shrink: 0;
    padding: var(--space-3);
    border-top: 1px solid var(--sidebar-border);
    font-size: var(--text-xs);
    color: var(--sidebar-fg-muted);
}

/* ----------------------------------------------------------------- Topbar */

.main { display: flex; flex-direction: column; min-width: 0; }

/*
    The bar the content scrolls beneath. Now properly frosted rather than merely
    semi-transparent: the saturation lift is what keeps a coloured badge or an
    accent link passing underneath looking like a colour instead of a grey smear.

    --glass rather than the old relative-colour expression, so there is one
    definition of what the material is and it themes with everything else.
*/
.topbar {
    position: sticky;
    top: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    height: var(--topbar-height);
    padding: 0 var(--space-5);
    background: var(--topbar-fill, var(--glass-panel));
    -webkit-backdrop-filter: var(--lens-bar);
    backdrop-filter: var(--lens-bar);
    border-bottom: 1px solid var(--glass-line);
    box-shadow: var(--rim-soft);
    transition: background var(--duration) var(--ease),
                box-shadow var(--duration) var(--ease),
                border-radius var(--duration) var(--ease);
}

.topbar__row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    width: 100%;
    min-width: 0;
    height: 100%;
}

/*
    The bar thickens when content has gone under it.

    This is the one piece of motion carrying information — it says "there is
    more above" on a screen with no other cue — and it is the payoff for the
    bar being the one real lens in the product. The corners square off at the
    same moment, because by then the pane's own rounded top is above the
    viewport.

    Driven by a data attribute an IntersectionObserver sets, so there is no
    scroll handler and nothing runs per frame. With script off the bar keeps
    its resting material, which is already legible over anything.
*/
.main[data-under="true"] { --topbar-fill: var(--glass-panel); }
.main[data-under="true"] .topbar {
    border-radius: 0;
    box-shadow: var(--rim-soft), 0 12px 22px -20px var(--glass-edge);
    border-bottom-color: var(--border);
}

/* Somebody who asked for less motion gets stillness, not a faster version. */
@media (prefers-reduced-motion: reduce) {
    .btn:active, .btn--primary:active { transform: none; }
    .stat, .stat:hover { transition: none; background-position: 0% 0%; }
    .topbar { transition: none; }
}

.topbar__crumbs {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
    /* Must be allowed to give, but last. .topbar__actions and .cmdk-trigger
       are both flex-shrink: 0 by design -- their controls have to stay
       tappable -- so the crumb is the one child that can absorb a narrow
       viewport. With flex-shrink: 0 here a long breadcrumb pushed the row past
       the bar and scrolled the whole page sideways (measured 413px of content
       in a 390px viewport on Financial/Journal/Create). So it gives LAST: its
       share is a hundred-thousandth of the module shortcuts' and the company
       label's, which is below the layout's own precision until those two have
       nothing left to give, and then it takes the rest alone. It is the
       page's own name and they are conveniences: with the label at
       flex-shrink 2 the crumb was cut mid-letter on most measured pages at
       1024px, and at 0.25 and at 0.01 it still lost a pixel, enough for an
       ellipsis, at 1440px. A plain 0 would let a long trail push the bar
       wider than the window. Pre-existing, not from the module redesign. */
    flex-shrink: 0.00001;
    overflow: hidden;
    font-size: var(--text-sm);
    color: var(--text-muted);
}
/* The trail is a block of inline items, not a flex row, so that when it does
   have to give it ends in an ellipsis: text-overflow does nothing on a flex
   container, and the clip was silent and cut a word in half ("Human Resourc"). */
.topbar__crumbs ol { display: block; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.topbar__crumbs li { display: inline; white-space: nowrap; }
.topbar__crumbs li:not(:last-child)::after { content: "/"; color: var(--text-subtle); margin-inline: var(--space-2); }
.topbar__crumbs a { color: var(--text-muted); }
.topbar__crumbs [aria-current="page"] { color: var(--text); font-weight: var(--weight-strong); }

/*
    Which company this is, at the start of the bar on every page.

    A quiet label rather than a control: it is not a link and has nothing to
    press, so it takes the sunken surface and the muted text colour that the
    breadcrumbs beside it use, with no border, and reads as information about
    the page rather than another thing to click. Flex gap and logical
    properties only, so Urdu and Arabic mirror it with the rest of the bar.

    A long company name must not take the page's own name with it. The label
    is the first thing in the bar to give: flex-shrink 100 against the crumb's
    0.00001 and the shortcuts' 1, down to the building alone (min-width), with a
    ceiling (max-width) for when there is room, an ellipsis, and the full name
    in its title. A phone gets none of this: below 720px there is no room in
    the bar at all, so the name moves to .company-strip under it instead
    (measured: the bar's own controls are 189px of 335 at 375px, and any chip
    made the page 373px wide in a 360px window).
*/
.topbar__company {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    flex: 0 100 auto;
    min-width: 2.25rem;
    max-width: clamp(8rem, 12vw, 16rem);
    overflow: hidden;
    height: 28px;
    padding-inline: 10px;
    border-radius: var(--radius-pill);
    background: var(--surface-sunken);
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-weight: var(--weight-strong);
    line-height: 1;
}
.topbar__company-icon { display: inline-flex; flex-shrink: 0; color: var(--text-subtle); }

/* DEVELOPMENT ONLY (see _Layout.cshtml): this run is on the shared server's
   databases. Loud on purpose, never shrinks, and a phone gets the short form
   because any chip wider than the room left made the page scroll sideways. */
.topbar__db {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    height: 28px;
    padding-inline: 10px;
    border-radius: var(--radius-pill);
    background: var(--danger);
    color: var(--on-danger);
    font-size: var(--text-sm);
    font-weight: var(--weight-strong);
    line-height: 1;
    white-space: nowrap;
}
.topbar__db-short { display: none; }
@media (forced-colors: active) { .topbar__db { border: 1px solid CanvasText; } }
.topbar__company-name {
    min-width: 0;
    /* The name is data, in whatever script the company wrote it, not a word of
       the interface's language: plaintext lets a Latin name inside an Urdu page
       run left to right and ellipsise at its END, where the page's direction
       alone cut its beginning off ("...OPMENT PROGRAMME"). */
    unicode-bidi: plaintext;
    text-align: start;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* Phone: the bar has no room, so the name goes under it, in the strip. Only one
   of the two is ever rendered (display: none leaves the accessibility tree), so
   a screen reader never reads the company twice. */
.company-strip { display: none; }
@media (max-width: 720px) {
    .topbar__company { display: none; }
    .topbar__db { padding-inline: 8px; }
    /* Hidden to the eye, still there for a screen reader: the visible "DB" is
       aria-hidden, so the words are what gets read. */
    .topbar__db-full {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
        white-space: nowrap;
    }
    .topbar__db-short { display: inline; }
    /* With no label to give first, the crumb is again the one child that gives,
       as it was before the label existed. */
    .topbar__crumbs { flex-shrink: 1; }
    .company-strip {
        display: flex;
        align-items: center;
        gap: var(--space-2);
        min-width: 0;
        padding: var(--space-2) var(--space-4) 0;
        color: var(--text-muted);
        font-size: var(--text-sm);
        font-weight: var(--weight-strong);
        line-height: 1.3;
    }
    .company-strip .topbar__company-name { flex: 0 1 auto; min-width: 0; }
}

.topbar__spacer { flex: 1 1 auto; min-width: var(--space-4); }

.topbar__actions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }

/* -------------------------------------------------------- Module switcher */

.module-switch {
    position: relative;
    margin-left: auto;
    flex-shrink: 0;
    z-index: 50;
}

.module-switch__trigger {
    display: grid;
    place-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 1px solid color-mix(in srgb, var(--rail-accent) 35%, transparent);
    border-radius: var(--radius-sm);
    background: rgb(255 255 255 / 8%);
    color: var(--rail-accent);
    cursor: pointer;
    transition: background var(--duration-fast) var(--ease),
                border-color var(--duration-fast) var(--ease),
                color var(--duration-fast) var(--ease);
}
.module-switch__trigger:hover {
    background: rgb(255 255 255 / 14%);
    border-color: color-mix(in srgb, var(--rail-accent) 60%, transparent);
}
.module-switch[data-open="true"] .module-switch__trigger {
    border-color: color-mix(in srgb, var(--rail-tone-blue) 70%, transparent);
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--rail-tone-blue) 28%, transparent);
    background: rgb(255 255 255 / 12%);
}
.module-switch__trigger.tone-blue { color: var(--rail-tone-blue); }
.module-switch__trigger.tone-amber { color: var(--rail-tone-amber); }
.module-switch__trigger.tone-neutral { color: var(--rail-tone-neutral); }
.module-switch__trigger-icon {
    display: grid;
    place-content: center;
}

.module-menu[hidden] { display: none !important; }

.module-menu {
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    right: auto;
    z-index: 80;
    width: 240px;
    padding: 6px;
    background: var(--sidebar-raised);
    border: 1px solid var(--sidebar-line);
    border-radius: var(--radius);
    box-shadow: 0 12px 28px rgb(0 0 0 / 45%), var(--rim-soft);
}
.app[data-nav="collapsed"] .module-menu {
    left: calc(100% + 10px);
    top: 0;
}

.module-menu__item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    min-height: 36px;
    padding: 8px 10px;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--sidebar-fg-strong);
    font: inherit;
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    transition: background var(--duration-fast) var(--ease),
                border-color var(--duration-fast) var(--ease),
                color var(--duration-fast) var(--ease);
}
.module-menu__item:hover {
    background: rgb(255 255 255 / 8%);
    text-decoration: none;
    color: var(--sidebar-fg-strong);
}
.module-menu__item[aria-current="true"] {
    border-color: color-mix(in srgb, var(--rail-tone-blue) 55%, transparent);
    background: rgb(255 255 255 / 10%);
}
.module-menu__item.tone-blue[aria-current="true"] { color: var(--rail-accent); }
.module-menu__item.tone-amber[aria-current="true"] { color: var(--rail-tone-amber); }
.module-menu__item.tone-neutral[aria-current="true"] { color: var(--sidebar-active-fg); }
.module-menu__icon {
    display: grid;
    place-content: center;
    width: 22px;
    height: 22px;
    border-radius: 6px;
    flex-shrink: 0;
    background: rgb(255 255 255 / 8%);
}
.module-menu__item.tone-blue .module-menu__icon { color: var(--rail-tone-blue); background: color-mix(in srgb, var(--rail-tone-blue) 16%, transparent); }
.module-menu__item.tone-amber .module-menu__icon { color: var(--rail-tone-amber); background: color-mix(in srgb, var(--rail-tone-amber) 14%, transparent); }
.module-menu__item.tone-neutral .module-menu__icon { color: var(--rail-tone-neutral); background: color-mix(in srgb, var(--rail-tone-neutral) 14%, transparent); }

/* -------------------------------------------------------- Language switcher */

.lang-switch {
    position: relative;
    flex-shrink: 0;
    z-index: 40;
}
.lang-switch__code {
    font-size: 11px;
    font-weight: var(--weight-strong);
    letter-spacing: 0.04em;
    line-height: 1;
}
.lang-switch[data-open="true"] .topbar__icon {
    border-color: var(--accent);
    color: var(--text);
    background: var(--surface-raised, var(--surface));
}
.lang-menu[hidden] { display: none !important; }
.lang-menu {
    position: absolute;
    top: calc(100% + 8px);
    inset-inline-end: 0;
    z-index: 90;
    width: 180px;
    padding: 6px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-md), var(--rim-soft);
}
.lang-menu form { margin: 0; }
.lang-menu__item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    min-height: 34px;
    padding: 7px 10px;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text);
    font: inherit;
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    text-align: start;
    cursor: pointer;
}
.lang-menu__item:hover {
    background: var(--surface-hover);
}
.lang-menu__item[aria-current="true"] {
    border-color: color-mix(in srgb, var(--accent) 55%, transparent);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.lang-menu__code {
    display: inline-grid;
    place-items: center;
    min-width: 2.2em;
    padding: 2px 4px;
    border-radius: 6px;
    background: var(--surface-sunken);
    color: var(--text-muted);
    font-size: 10px;
    font-weight: var(--weight-strong);
    letter-spacing: 0.04em;
}

/* ------------------------------------------------------------- Notifications */

.notif-bell {
    position: relative;
    flex-shrink: 0;
    z-index: 40;
}
.notif-bell[data-open="true"] .topbar__icon {
    border-color: var(--accent);
    color: var(--text);
    background: var(--surface-raised, var(--surface));
}
.notif-bell__count {
    position: absolute;
    top: -3px;
    right: -3px;
    display: inline-grid;
    place-items: center;
    min-width: 15px;
    height: 15px;
    padding: 0 3px;
    border-radius: 999px;
    background: var(--danger);
    color: var(--on-danger);
    font-size: 9px;
    font-weight: var(--weight-strong);
    line-height: 1;
}
.notif-menu[hidden] { display: none !important; }
.notif-menu {
    position: absolute;
    top: calc(100% + 8px);
    inset-inline-end: 0;
    z-index: 90;
    width: 320px;
    max-height: 420px;
    overflow-y: auto;
    padding: 6px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-md), var(--rim-soft);
}
.notif-menu form { margin: 0; }
.notif-menu__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 6px 8px;
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
}
.notif-menu__mark-all {
    border: 0;
    background: none;
    color: var(--accent);
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    cursor: pointer;
}
.notif-menu__empty {
    margin: 0;
    padding: 16px 8px;
    color: var(--text-muted);
    font-size: var(--text-sm);
    text-align: center;
}
.notif-menu__item {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 8px;
    border-radius: var(--radius-sm);
}
.notif-menu__item--unread {
    background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.notif-menu__item-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    flex: 1;
}
.notif-menu__subject {
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--text);
}
.notif-menu__doc {
    font-size: var(--text-xs);
    color: var(--text-muted);
}
.notif-menu__date {
    font-size: var(--text-xs);
    color: var(--text-muted);
}
.notif-menu__mark {
    display: inline-grid;
    place-items: center;
    flex-shrink: 0;
    width: 24px;
    height: 24px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: transparent;
    color: var(--text-muted);
    cursor: pointer;
}
.notif-menu__mark:hover {
    border-color: var(--accent);
    color: var(--accent);
}

/* -------------------------------------------------------- RTL (ar / ur) */

/*
    html[dir=rtl] already mirrors grid/flex placement. These rules only fix
    physical sides that must sit on the content edge of the rail.
*/
html[dir="rtl"] .sidebar {
    border-right: 0;
    border-left: 1px solid var(--sidebar-border);
    box-shadow: var(--rim-soft), -1px 0 0 rgb(0 0 0 / 12%);
}
html[dir="rtl"] .module-switch {
    margin-left: 0;
    margin-inline-start: auto;
}
html[dir="rtl"] .app[data-nav="collapsed"] .module-menu {
    left: auto;
    right: calc(100% + 10px);
}
html[dir="rtl"] .module-menu__item { text-align: right; }
html[dir="rtl"] .nav-link__badge,
html[dir="rtl"] .card__actions,
html[dir="rtl"] .pager__controls,
html[dir="rtl"] .push-right {
    margin-left: 0;
    margin-inline-start: auto;
}

/* FINSo-style icon toolbar: important forms as icon-only controls. */
.topbar__features {
    display: flex;
    align-items: center;
    flex: 0 1 auto;
    min-width: 0;
    max-width: none;
    margin: 0;
    padding: 0;
    border: 0;
}
.topbar__features [data-module-badges][hidden] { display: none !important; }

.topbar__icons {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: nowrap;
}

.topbar__icon {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    flex-shrink: 0;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--surface);
    color: var(--text-muted);
    text-decoration: none;
    cursor: pointer;
    box-shadow: none;
    transition: background var(--duration-fast) var(--ease),
                border-color var(--duration-fast) var(--ease),
                color var(--duration-fast) var(--ease);
}
.topbar__icon:hover {
    background: var(--surface-hover);
    border-color: var(--border-strong);
    color: var(--text);
    text-decoration: none;
}
.topbar__icon:focus-visible {
    outline: 2px solid rgb(var(--accent-rgb) / 45%);
    outline-offset: 2px;
}
button.topbar__icon {
    font: inherit;
}
/* -------------------------------------------------------------- Icon button */

.icon-button {
    display: grid;
    place-items: center;
    width: 34px; height: 34px;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-muted);
    cursor: pointer;
    transition: background var(--duration-fast) var(--ease),
                color var(--duration-fast) var(--ease),
                border-color var(--duration-fast) var(--ease);
}
.icon-button:hover { background: var(--surface-hover); color: var(--text); border-color: var(--border); }

/* -------------------------------------------------------------- User chip */

.user-chip {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    height: 34px;
    padding: 0 10px 0 4px;
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--text);
    text-decoration: none;
}
.user-chip:hover { background: var(--surface-hover); text-decoration: none; color: var(--text); }
.user-chip__meta { display: flex; flex-direction: column; line-height: 1.15; }
.user-chip__name { font-size: var(--text-sm); font-weight: 600; }
.user-chip__role { font-size: var(--text-xs); color: var(--text-muted); }

.avatar {
    display: grid;
    place-items: center;
    width: 28px; height: 28px;
    flex-shrink: 0;
    border-radius: var(--radius-pill);
    background: rgb(var(--accent-rgb) / 14%);
    color: var(--accent);
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.02em;
}
.avatar--lg { width: 46px; height: 46px; font-size: var(--text-md); }

/* ---------------------------------------------------------------- Content */

.content {
    flex: 1;
    width: 100%;
    max-width: var(--content-max);
    margin-inline: auto;
    padding: var(--space-6) var(--space-5) var(--space-10);
}

/*
    WHEN A FORM AND A SUMMARY CANNOT BOTH HAVE ROOM, STACK THEM.

    .grid--sidebar already stacks, but on a VIEWPORT query at 1100px, and the
    squeeze that matters is about the CONTAINER. Measured on Apply for leave
    at a 1440px viewport: the app's own sidebar takes 314, the document panel
    286, so the grid is handed 797 -- and 797 is still "wide" to a viewport
    query, so it stayed in two columns and gave the form 459px. Every field
    then sat on its own row, because .form-grid's tracks are
    minmax(120px, 1fr) and a field spans two of them.

    So the question is asked of .content, which is the box that actually
    bounds these pages, and asked twice because the answer differs by whether
    a document panel is taking a column of it. The thresholds are what it
    takes to leave the form about 560px:

        no panel:   560 + 16 + 280  = 856   ->  stack below 880
        with panel: 856 + 286 + 16  = 1158  ->  stack below 1180

    :has() is already how this stylesheet detects the panel (see
    .content:has(> .doc-aside) above), so this adds no new dependency.
*/
.content { container: content / inline-size; }

@container content (max-width: 880px) {
    .grid--sidebar,
    .grid--master-detail { grid-template-columns: minmax(0, 1fr); }
}

@container content (max-width: 1180px) {
    .content:has(> .doc-aside) .grid--sidebar,
    .content:has(> .doc-aside) .grid--master-detail { grid-template-columns: minmax(0, 1fr); }
}

.page-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--space-3) var(--space-4);
    margin-bottom: var(--space-5);
}
.page-head__title-row {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    flex: 1 1 100%;
    min-width: 0;
}
.page-head__title-row h1 {
    flex-shrink: 0;
}
.page-head__title-row .page-head__actions {
    flex: 1 1 auto;
    min-width: 0;
}
.page-head__title-row + .page-head__sub {
    flex: 1 1 100%;
    margin: 0;
}
.page-head__text { min-width: 0; flex: 1; }
.page-head h1 {
    font-family: var(--font-sans-display);
    font-size: var(--text-2xl);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-tighter);
    line-height: 1.15;
    margin: 0;
}

/*
    The two ranks the scale was missing.

    --text-3xl is referenced nowhere at all, and --text-lg reaches the page
    only through inline style= in 15 views, so the hierarchy above the body
    size was one jump with a spare size sitting beside it.

    .figure is the number a whole screen exists to show — a payroll run's net,
    a voucher's total. One size above the page title, used once per page or not
    at all. .section-title is what those 15 inline styles were reaching for.
*/
.figure {
    font-family: var(--font-sans-display);
    font-size: var(--text-3xl);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-tighter);
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
    line-height: 1.05;
}
.section-title {
    font-family: var(--font-sans-display);
    font-size: var(--text-lg);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-tight);
    margin-bottom: var(--space-3);
}
/*
    The subtitle drops to the body size. At --text-md it was within a point of
    the heading's own weight-adjusted presence, so the two competed and the page
    had no clear first line.
*/
.page-head__sub {
    margin-top: 4px;
    color: var(--text-muted);
    font-size: var(--text-base);
}
.page-head__actions {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
}

/*
    The return-to-draft confirmation: which document, and which number is about
    to be surrendered, next to the button that does it. Both are read-only —
    they are there so nobody reopens the wrong document, not to be filled in.
*/
.draft-return { display: flex; align-items: center; gap: var(--space-2); }
.draft-return select,
.draft-return input {
    height: 34px;
    padding: 0 var(--space-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-sunken);
    color: var(--text-muted);
    font-size: var(--text-sm);
    max-width: 13ch;
}
@media (max-width: 700px) { .draft-return select { display: none; } }

/* ============================================================== Primitives */

/* ---------------------------------------------------------------- Buttons */

/*
    One height for every control, from --control-h, so a button standing next to
    an input in a filter bar lines up with it. They used to be 34 and 36 and it
    showed on every list screen.
*/
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    height: var(--control-h);
    padding: 0 14px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font-size: var(--text-base);
    font-weight: var(--weight-medium);
    letter-spacing: var(--tracking-tight);
    white-space: nowrap;
    cursor: pointer;
    box-shadow: var(--shadow-xs);
    transition: background var(--duration-fast) var(--ease),
                border-color var(--duration-fast) var(--ease),
                box-shadow var(--duration-fast) var(--ease),
                transform var(--duration-fast) var(--ease);
}
/*
    :where() keeps this rule at the lowest specificity. As `.btn:hover` (0,2,0) it
    out-ranked every variant's own fill (0,1,0): a green or red button took the
    pale hover surface while its label stayed white -- white on white the moment
    the pointer rested on it (Verify and import data after a click). A variant now
    always wins over the plain-button hover, whichever of them is declared first.
*/
.btn:where(:hover) {
    background: var(--surface-hover);
    border-color: var(--border-strong);
    text-decoration: none;
}
/*
    The press is a shadow collapsing rather than a nudge downwards. Translating
    a button moves its neighbours' optical baseline and reads as a jolt; losing
    the lift reads as the thing being pushed.
*/
/*
    A press is the glass compressing: the rim dims, the glow contracts into the
    button, and the fill darkens as the light is squeezed out. A scale, not a
    translate -- moving a button shifts its neighbours' optical baseline, but
    0.985 from the centre moves each edge about a third of a pixel and reads as
    force rather than as a jolt.
*/
.btn:active {
    box-shadow: inset 0 1px 3px var(--glass-edge);
    transform: scale(0.985);
}
.btn:disabled { opacity: 0.5; cursor: not-allowed; box-shadow: none; }

/*
    A lit capsule rather than a flat fill: the gradient is the light falling
    across it and the inset line is the specular edge along the top. The glow
    is the accent's own colour thrown below the button — what a bright surface
    does to the ground it sits on, and the thing that most separates a control
    that looks lit from one that looks printed.
*/
.btn--primary {
    background: linear-gradient(180deg,
        rgb(var(--accent-rgb)) 0%,
        rgb(var(--accent-strong-rgb)) 100%);
    border-color: rgb(var(--accent-strong-rgb));
    color: var(--accent-contrast);
    /* A lit capsule: the gradient is light falling across it, the inset pair is
       the edge catching and returning it, and the glow is the accent thrown on
       the ground below. */
    box-shadow:
        inset 0 1px 0 rgb(255 255 255 / 30%),
        inset 0 -1px 0 rgb(255 255 255 / 12%),
        0 1px 2px rgb(var(--accent-rgb) / 30%),
        0 6px 18px rgb(var(--accent-rgb) / 30%);
}
.btn--primary:hover {
    background: linear-gradient(180deg,
        rgb(var(--accent-rgb) / 92%) 0%,
        rgb(var(--accent-strong-rgb)) 100%);
    border-color: rgb(var(--accent-strong-rgb));
    box-shadow:
        inset 0 1px 0 rgb(255 255 255 / 34%),
        inset 0 -1px 0 rgb(255 255 255 / 14%),
        0 2px 4px rgb(var(--accent-rgb) / 32%),
        0 10px 26px rgb(var(--accent-rgb) / 34%);
}
.btn--primary:active {
    background: linear-gradient(180deg,
        rgb(var(--accent-strong-rgb)) 0%,
        rgb(var(--accent-rgb)) 100%);
    box-shadow:
        inset 0 1px 3px rgb(0 0 0 / 28%),
        inset 0 -1px 0 rgb(255 255 255 / 10%),
        0 1px 2px rgb(var(--accent-rgb) / 22%);
    transform: scale(0.985);
}

.btn--danger {
    background: var(--danger);
    border-color: var(--danger);
    color: var(--on-danger);
    box-shadow: var(--shadow-sm), inset 0 1px 0 rgb(255 255 255 / 14%);
}
.btn--danger:hover { filter: brightness(0.94); }

.btn--ghost {
    background: transparent;
    border-color: transparent;
    color: var(--text-muted);
    box-shadow: none;
}
.btn--ghost:hover { background: var(--surface-hover); color: var(--text); border-color: transparent; }

/* A quiet button that still reads as a button: no fill until hovered. */
.btn--subtle {
    background: var(--surface-sunken);
    border-color: transparent;
    color: var(--text);
    box-shadow: none;
}
.btn--subtle:hover { background: var(--surface-active); border-color: transparent; }

.btn--sm { height: var(--control-h-sm); padding: 0 10px; font-size: var(--text-sm); }
.btn--lg { height: var(--control-h-lg); padding: 0 var(--space-5); font-size: var(--text-md); }
.btn--block { width: 100%; }

/* Legacy Save split-button (Save ▾ → Draft / Submit / Regular) */
.doc-form-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    align-items: center;
    gap: var(--space-2);
    margin-top: 0;
    width: 100%;
}

/* Legacy puts Attach Document on the left of the footer row. */
.doc-form-actions > [data-attach-open] {
    margin-right: auto;
}

.save-split {
    position: relative;
    display: inline-flex;
    align-items: stretch;
}

.save-split__main {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
    padding-inline-end: 12px;
}

.save-split__toggle {
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
    width: 34px;
    padding: 0;
    border-inline-start: 1px solid rgb(255 255 255 / 28%);
}

.save-split__toggle .icon {
    transition: transform var(--duration) var(--ease);
}

.save-split[data-open] .save-split__toggle .icon {
    transform: rotate(180deg);
}

.save-split__menu {
    position: absolute;
    right: 0;
    bottom: calc(100% + 6px);
    /* Popover tier, like .combo__list and .module-menu -- see the layer
       ladder in the animation section. At 30 it was below the module switch
       and only above the topbar by accident of where it opens. */
    z-index: 80;
    min-width: 260px;
    margin: 0;
    padding: 6px;
    list-style: none;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-md);
}

.save-split__menu[hidden] { display: none !important; }

.save-split__item {
    display: block;
    width: 100%;
    text-align: start;
    border: 0;
    background: transparent;
    color: var(--text);
    padding: 8px 12px;
    border-radius: var(--radius-sm);
    font: inherit;
    font-size: var(--text-sm);
    cursor: pointer;
}

.save-split__item:hover {
    background: var(--surface-hover);
}

/* =================================================================== The form footer's buttons
   One look for the buttons at the foot of every form, document or master, in every module:

     [ Save | v ]   [ Clear ]   [ Close ]

   Large, rounded, drawn as outlines: Save in the accent colour with the chevron as its own segment
   opening upward onto Save as Draft / Save and Submit for Approval / Save as Regular, each with its
   icon; Clear, Cancel and Close as quiet grey outlines. Small and icon-only buttons are left alone.
   Applies to the Setup strip (.form-actions), the document footer (.doc-form-actions) and the
   card footers of forms; a card foot that is not a form's (a note, a pager, a bulk confirm) is not touched.
*/
:is(.form-actions, .doc-form-actions, form .card__foot:not(.bulk-confirm, .bpa-pager, .muted, .subtle)) .btn:not(.btn--sm, .btn--icon, .save-split *) {
    height: 44px;
    padding-inline: 20px;
    gap: 8px;
    border-radius: 12px;
    font-size: var(--text-md);
    font-weight: 600;
    box-shadow: none;
    background: rgb(var(--neutral-rgb) / 7%);
    border: 1.5px solid color-mix(in srgb, var(--text-muted) 32%, transparent);
    color: var(--text);
}
:is(.form-actions, .doc-form-actions, form .card__foot:not(.bulk-confirm, .bpa-pager, .muted, .subtle)) .btn:not(.btn--sm, .btn--icon, .save-split *):hover {
    background: rgb(var(--neutral-rgb) / 13%);
    border-color: color-mix(in srgb, var(--text-muted) 50%, transparent);
}
/* A plain Save (a master form with no Draft or Regular) is the accent outline, like the split. */
:is(.form-actions, .doc-form-actions, form .card__foot:not(.bulk-confirm, .bpa-pager, .muted, .subtle)) .btn--primary:not(.btn--sm, .btn--icon, .save-split *) {
    background: var(--surface);
    border-color: var(--accent);
    color: var(--accent);
}
:is(.form-actions, .doc-form-actions, form .card__foot:not(.bulk-confirm, .bpa-pager, .muted, .subtle)) .btn--primary:not(.btn--sm, .btn--icon, .save-split *):hover {
    background: rgb(var(--accent-rgb) / 8%);
    border-color: var(--accent);
}
/* Cancel keeps its warning colour, as an outline. */
:is(.form-actions, .doc-form-actions, form .card__foot:not(.bulk-confirm, .bpa-pager, .muted, .subtle)) .btn--soft-danger:not(.btn--sm, .btn--icon) {
    background: rgb(var(--danger-rgb, 220 38 38) / 7%);
    border-color: rgb(var(--danger-rgb, 220 38 38) / 40%);
    color: var(--danger, #dc2626);
}
/* Mark as Regular and other filled success buttons keep their colour at the same size. */
:is(.form-actions, .doc-form-actions, form .card__foot:not(.bulk-confirm, .bpa-pager, .muted, .subtle)) .btn--success:not(.btn--sm, .btn--icon, .save-split *) {
    background: var(--success, #16a34a);
    border-color: transparent;
    color: #fff;
}

/* The Save split: one outlined control, Save and its chevron divided by a line. */
.save-split {
    border: 1.5px solid var(--accent);
    border-radius: 12px;
    background: var(--surface);
}
.save-split > .btn {
    height: 44px;
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
    color: var(--accent);
    font-size: var(--text-md);
    font-weight: 600;
    gap: 8px;
}
.save-split > .btn:where(:hover) { background: rgb(var(--accent-rgb) / 8%); }
.save-split__main {
    padding-inline: 22px 18px;
    border-start-start-radius: 10px;
    border-end-start-radius: 10px;
}
.save-split__toggle {
    width: 46px;
    border-start-end-radius: 10px;
    border-end-end-radius: 10px;
    border-inline-start: 1.5px solid var(--accent);
}

/* The menu opens upward from the chevron, with a pointer back at it. */
.save-split__menu {
    min-width: 19.5rem;
    padding: 8px;
    /* Anchored at Save's own left edge and opening toward Clear and Close, never toward the form:
       a card that clips its overflow (the Leave request's) would cut off a menu that opened leftward. */
    right: auto;
    inset-inline-start: 0;
    bottom: calc(100% + 14px);
    border-radius: 16px;
    border: 1px solid var(--border);
    box-shadow: 0 14px 34px rgb(15 23 42 / 18%);
}
.save-split__menu::after {
    content: "";
    position: absolute;
    bottom: -7px;
    inset-inline-start: 3.25rem;
    width: 12px;
    height: 12px;
    background: var(--surface);
    border-right: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    transform: rotate(45deg);
}
.save-split__item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 14px;
    border-radius: 10px;
    font-size: var(--text-md);
    font-weight: 500;
    color: var(--text);
}
.save-split__item svg { flex: none; color: var(--accent); }
.save-split__item:hover,
.save-split__item:focus-visible {
    background: rgb(var(--accent-rgb) / 10%);
    outline: none;
}

/* Role Rights — match legacy SystemAdmin/UserRights (ACE tabs-left)

   This screen is a deliberate FACSIMILE of the original's tabs-left rights
   matrix, down to the ACE tab greens and browns, so an administrator moving
   between the two systems is looking at the same object. That is why it does
   not take --surface / --text like every other screen does.

   What it used to do, though, was write those forty-odd ACE hexes inline
   through four hundred lines, so on the dark theme the whole matrix stayed a
   sheet of white with #333 labels on it — a torch in a dark room, and the one
   screen no theme could reach. The values are now a LOCAL palette, stated
   once, and a dark arm restates it. Light is byte-for-byte the legacy look;
   dark is the same object with the lights off.

   Named --rights-* rather than folded into the global tokens on purpose: these
   are the legacy product's colours, not this one's, and a change to the app
   palette must not drag them with it. */
.rights-page {
    --rights-surface: #ffffff;
    --rights-surface-alt: #fafbfc;
    --rights-surface-sunken: #f9f9f9;
    --rights-surface-hover: #f5f5f5;
    --rights-line: #c5d0dc;
    --rights-line-soft: #ececec;
    --rights-line-mid: #e5e5e5;
    --rights-line-box: #d5d5d5;

    --rights-ink: #333333;
    --rights-ink-2: #444444;
    --rights-ink-3: #555555;
    --rights-ink-4: #666666;
    --rights-ink-5: #888888;

    --rights-blue: #438eb9;
    --rights-blue-bg: #ebf8ff;
    --rights-blue-edge: #6fb3e0;
    --rights-blue-2: #478fca;
    --rights-head-bg: #eef3f8;

    --rights-green: #87b87f;
    --rights-green-2: #69aa46;
    --rights-red: #d15b47;
    --rights-brown: #daa458;
    --rights-brown-edge: #e7b979;
    --rights-grey: #908f8f;
    --rights-steel: #abbac3;
    --rights-purple: #9585bf;
    --rights-orange: #f89406;
    --rights-star: #f59e0b;

    max-width: none;
}

/* The same object with the lights off. Both variant arms, and the OS arm
   inside its media query — the arrangement documented in tokens.css. */
:root[data-theme="dark"] .rights-page {
    --rights-surface: var(--surface);
    --rights-surface-alt: var(--surface-hover);
    --rights-surface-sunken: var(--surface-sunken);
    --rights-surface-hover: var(--surface-active);
    --rights-line: var(--border-strong);
    --rights-line-soft: var(--border-subtle);
    --rights-line-mid: var(--border);
    --rights-line-box: var(--border);

    --rights-ink: var(--text);
    --rights-ink-2: var(--text);
    --rights-ink-3: var(--text-muted);
    --rights-ink-4: var(--text-muted);
    --rights-ink-5: var(--text-subtle);

    --rights-blue: #7ec0e0;
    --rights-blue-bg: rgb(126 192 224 / 14%);
    --rights-blue-edge: #7ec0e0;
    --rights-blue-2: #7ec0e0;
    --rights-head-bg: var(--surface-sunken);

    --rights-green: #9ccf94;
    --rights-green-2: #9ccf94;
    --rights-red: #e88a79;
    --rights-brown: #e0bb80;
    --rights-brown-edge: #e0bb80;
    --rights-grey: #b0afaf;
    --rights-steel: #c2ced6;
    --rights-purple: #b7a9dc;
    --rights-orange: #f8ac52;
    --rights-star: #fbbf24;
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .rights-page {
        --rights-surface: var(--surface);
        --rights-surface-alt: var(--surface-hover);
        --rights-surface-sunken: var(--surface-sunken);
        --rights-surface-hover: var(--surface-active);
        --rights-line: var(--border-strong);
        --rights-line-soft: var(--border-subtle);
        --rights-line-mid: var(--border);
        --rights-line-box: var(--border);

        --rights-ink: var(--text);
        --rights-ink-2: var(--text);
        --rights-ink-3: var(--text-muted);
        --rights-ink-4: var(--text-muted);
        --rights-ink-5: var(--text-subtle);

        --rights-blue: #7ec0e0;
        --rights-blue-bg: rgb(126 192 224 / 14%);
        --rights-blue-edge: #7ec0e0;
        --rights-blue-2: #7ec0e0;
        --rights-head-bg: var(--surface-sunken);

        --rights-green: #9ccf94;
        --rights-green-2: #9ccf94;
        --rights-red: #e88a79;
        --rights-brown: #e0bb80;
        --rights-brown-edge: #e0bb80;
        --rights-grey: #b0afaf;
        --rights-steel: #c2ced6;
        --rights-purple: #b7a9dc;
        --rights-orange: #f8ac52;
        --rights-star: #fbbf24;
    }
}

.rights-page__title {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 14px;
}

.rights-page__star {
    color: var(--rights-star);
    font-size: 1.25rem;
    line-height: 1;
}

.rights-page__heading {
    margin: 0;
    font-size: 1.35rem;
    font-weight: 600;
    color: var(--rights-blue);
}

.rights-role-pick {
    margin-bottom: 12px;
}

.rights-role-pick__label {
    display: block;
    font-size: var(--text-sm);
    font-weight: 600;
    margin-bottom: 4px;
    color: var(--text);
}

.rights-role-pick__form { margin: 0; }

.rights-role-pick__select {
    width: min(100%, 220px);
    height: 34px;
    padding: 0 10px;
    border: 1px solid var(--rights-line);
    border-radius: 4px;
    background: var(--rights-surface);
    font: inherit;
    font-size: var(--text-sm);
}

.rights-empty,
.rights-placeholder {
    padding: var(--space-5);
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* The Form / Report / BI strip is drawn with every other strip, see FOLDER
   TABS; only the label is not a click target of its own. */
.rights-top-tabs__btn span { pointer-events: none; }

.rights-top-panel {
    border: 1px solid var(--rights-line);
    border-top: none;
    background: var(--rights-surface);
    min-height: 520px;
}

.rights-shell {
    display: flex;
    align-items: stretch;
    min-height: 920px;
}

.rights-modules {
    flex: 0 0 200px;
    width: 200px;
    margin: 0;
    padding: 0;
    list-style: none;
    border-right: 1px solid var(--rights-line);
    background: var(--rights-surface-alt);
}

.rights-modules__item {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    margin: 0;
    padding: 10px 12px;
    border: none;
    border-bottom: 1px solid var(--rights-line);
    border-left: 3px solid transparent;
    background: var(--rights-surface);
    color: var(--rights-ink-2);
    font: inherit;
    font-size: 13px;
    font-weight: 700;
    text-align: left;
    cursor: pointer;
}

.rights-modules__item:hover {
    background: var(--rights-surface-hover);
}

.rights-modules__item.is-active {
    margin-right: -1px;
    border-right: 1px solid var(--rights-surface);
    background: var(--rights-surface);
    z-index: 1;
}

.rights-modules__item.menu-blue.is-active { color: var(--rights-blue); border-left-color: var(--rights-blue-edge); }
.rights-modules__item.menu-green.is-active { color: var(--rights-green); border-left-color: var(--rights-green); }
.rights-modules__item.menu-red.is-active { color: var(--rights-red); border-left-color: var(--rights-red); }
.rights-modules__item.menu-brown.is-active { color: var(--rights-brown); border-left-color: var(--rights-brown-edge); }
.rights-modules__item.menu-grey.is-active { color: var(--rights-grey); border-left-color: var(--rights-grey); }
.rights-modules__item.menu-black.is-active { color: var(--rights-ink-2); border-left-color: var(--rights-ink-2); }

.rights-modules__icon {
    display: inline-flex;
    flex-shrink: 0;
    opacity: 0.85;
}

.rights-modules__label {
    line-height: 1.2;
}

.rights-workspace {
    flex: 1;
    min-width: 0;
    overflow: auto;
    padding: 0 12px 16px;
}

.rights-panel__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin: 0 -12px;
    padding: 10px 16px;
    border-bottom: 1px solid var(--rights-line);
    background: var(--rights-head-bg);
}

.rights-panel__title {
    margin: 0;
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--rights-blue);
}

.rights-panel__bulk,
.rights-matrix__head-bulk {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

.rights-bulk {
    display: inline-flex;
    padding: 2px;
    border: 0;
    background: transparent;
    color: var(--rights-ink-4);
    cursor: pointer;
    line-height: 0;
}

.rights-bulk:hover { color: var(--rights-blue); }

.rights-acc {
    margin-top: 10px;
    border: 1px solid var(--rights-line-box);
    border-radius: 4px;
    background: var(--rights-surface);
}

.rights-acc__summary {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 10px 12px;
    cursor: pointer;
    list-style: none;
    font-weight: 700;
    font-size: 13px;
    color: var(--rights-ink);
    background: var(--rights-surface-sunken);
    border-bottom: 1px solid var(--rights-line-mid);
}

.rights-acc__summary::-webkit-details-marker { display: none; }

.rights-acc[open] > .rights-acc__summary .rights-acc__chevron {
    transform: rotate(90deg);
}

.rights-acc__chevron {
    display: inline-block;
    font-size: 12px;
    color: var(--rights-ink-5);
    transition: transform 0.15s ease;
}

.rights-acc__body {
    padding: 8px 10px 12px;
}

.rights-matrix {
    --rights-col-w: 30px;
    font-size: 13px;
}

.rights-matrix__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 10px;
    margin-bottom: 0;
    background: var(--rights-head-bg);
    border: 1px solid var(--rights-line);
    border-bottom: none;
}

.rights-matrix__icons,
.rights-matrix__cells {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
}

.rights-matrix__icon,
.rights-matrix__cell {
    flex: 0 0 var(--rights-col-w);
    width: var(--rights-col-w);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.rights-matrix__icon:first-child {
    flex: 0 0 22px;
    width: 22px;
}

.rights-matrix__cell:first-child {
    flex: 0 0 22px;
    width: 22px;
}

.rights-matrix__icon.tone-amber { color: var(--rights-orange); }
.rights-matrix__icon.tone-purple { color: var(--rights-purple); }
.rights-matrix__icon.tone-blue { color: var(--rights-blue-2); }
.rights-matrix__icon.tone-red { color: var(--rights-red); }
.rights-matrix__icon.tone-green { color: var(--rights-green-2); }
.rights-matrix__icon.tone-neutral { color: var(--rights-ink-3); }

.rights-matrix__head-right {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-left: auto;
}

.rights-matrix__form-label {
    font-weight: 700;
    color: var(--rights-blue);
    font-size: 13px;
}

.rights-matrix__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    min-height: 34px;
    padding: 4px 10px;
    border: 1px solid var(--rights-line-soft);
    border-top: none;
    background: var(--rights-surface);
}

.rights-matrix__row:nth-child(even) {
    background: var(--rights-surface-alt);
}

.rights-matrix__name {
    flex: 1;
    min-width: 8rem;
    padding-top: 4px;
    color: var(--rights-ink);
    text-align: left;
}

.rights-matrix__check {
    display: inline-flex;
    margin: 0;
    cursor: pointer;
}

.rights-matrix__check input {
    width: 16px;
    height: 16px;
    margin: 0;
    accent-color: var(--rights-blue);
}

.rights-matrix__empty {
    margin: 0;
    padding: 12px;
    color: var(--text-muted);
    border: 1px solid var(--rights-line-soft);
}

.rights-foot {
    display: flex;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 16px;
    padding-top: 12px;
}

.rights-btn-save {
    background: var(--rights-green) !important;
    border-color: var(--rights-green) !important;
    color: var(--rights-surface) !important;
    min-width: 72px;
}

.rights-btn-save:hover { filter: brightness(0.95); }

.rights-btn-cancel {
    background: var(--rights-surface) !important;
    border: 1px solid var(--rights-line) !important;
    color: var(--rights-ink-3) !important;
}

.rights-btn-close {
    background: var(--rights-steel) !important;
    border-color: var(--rights-steel) !important;
    color: var(--rights-surface) !important;
}

@media (max-width: 900px) {
    .rights-shell {
        flex-direction: column;
        min-height: auto;
    }
    .rights-modules {
        width: 100%;
        flex: none;
        display: flex;
        flex-wrap: wrap;
        border-right: 0;
        border-bottom: 1px solid var(--rights-line);
    }
    .rights-modules__item {
        width: auto;
        flex: 1 1 auto;
        border-right: 1px solid var(--rights-line);
    }
    .rights-matrix__head,
    .rights-matrix__row {
        overflow-x: auto;
    }
}

/* ------------------------------------------------------------------ Cards */

/*
    A card is a lit panel: translucent enough that the ground's colour reaches
    it, with a specular line along its top edge.

    --glass-thick, not --glass. A card usually carries a table or a form, and
    body text needs a surface it can rely on — at 68% the blooms behind it start
    to move under the words. The material still reads; the legibility is not
    spent on it.
*/
/*
    A card is glass: a translucent fill, a hairline, a rim and a shadow. It
    does NOT carry a filter of its own at any width. Its backdrop is the pane's
    fill over the page ground — already smooth. Blurring a smooth gradient
    returns the same smooth gradient, at the price of a filter pass per card.

    It must also stay free of backdrop-filter, filter and transform for a
    second reason: any of the three makes it a containing block for
    position: fixed, and the combobox popover is on position: fixed precisely
    to escape the table's scrollport.
*/
.card {
    background: var(--glass-panel);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    border: 1px solid var(--glass-line);
    border-radius: var(--radius-lg);
    box-shadow: var(--rim), var(--shadow-sm);
}
/*
    The head is divided by a subtle rule, not the card's own border weight. A
    full-strength line here made every card look like two stacked boxes.
*/
.card__head {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5);
    border-bottom: 1px solid var(--border-subtle);
}
.card__title {
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-tight);
}
.card__subtitle { font-size: var(--text-sm); color: var(--text-muted); }
.card__actions { margin-left: auto; display: flex; gap: var(--space-2); align-items: center; }

/*
    A note beside a card's title rather than a button: a count, a period, a
    "last updated". Twenty-six of them set this size in a style attribute; it
    belongs to the pairing of the two classes, which is what the markup already
    writes.
*/
.card__actions.subtle { font-size: var(--text-xs); }
.card__body { padding: var(--space-5); }
.card__body--flush { padding: 0; }

/*
    A card whose body is read-only reference material -- who this document is
    about, a summary that never takes input -- rather than something the user
    is working in. Collapsed by default, same reasoning as the filter panel:
    the space matters most on the page that has actual work to show, and the
    card's own head (a name, a total) already says what is folded away.
*/
.card--collapsible .card__head { cursor: pointer; user-select: none; }
.card--collapsible .card__collapse-chev {
    margin-left: auto;
    display: flex;
    color: var(--text-muted);
    transition: transform 0.15s ease;
}
.card--collapsible[data-collapsed="true"] .card__collapse-chev { transform: rotate(-90deg); }
.card--collapsible[data-collapsed="true"] .card__body { display: none; }
/* The same recess at the foot of a card, so the pager and the Save row read
   plainly as furniture and not as data. */
.card__foot {
    padding: var(--space-3) var(--space-5);
    border-top: 1px solid var(--border-subtle);
    background: rgb(var(--neutral-rgb) / 6%);
    box-shadow: inset 0 2px 5px -2px var(--glass-edge);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}

/*
    Vertical rhythm, declared once.

    246 of the 828 inline style="" attributes across the views are one string —
    margin-bottom: var(--space-4) on a card or an alert — because there was
    nowhere else to say it.

    It is written as margin-BOTTOM on everything but the last child rather than
    as margin-top on an adjacent sibling, because the sibling varies: on the
    chart of accounts the filter card is followed by a conditional block and on
    the employee list by nothing at all, so a `.card + .card` rule would have
    silently dropped their spacing when the attribute came out. Measured rather
    than assumed — the pages were fetched and their shape recorded first.

    Same property and same value as the attribute it replaces, so the two
    collapse rather than doubling, and the attributes can come out view by view.

    Scoped to direct children of .content, so a card inside .stack or .grid —
    where the container already owns the gap — is untouched.
*/
.content > .card:not(:last-child),
.content > .alert:not(:last-child),
.content > .jv-foot:not(:last-child),
/*  ...but not a tab strip joined to the card under it (FOLDER TABS). */
.content > .tabs:not(:last-child):not(:has(+ .card, ~ .tabs-panel)),
.content > .stat-grid:not(:last-child) { margin-bottom: var(--space-4); }

/* The two hooks Journal/Edit.cshtml writes and nothing defines. The foot is a
   two-column grid; naming its halves lets the totals box stop stretching to
   the height of the tax table beside it. */
.jv-foot__tax { min-width: 0; }
.jv-foot__totals { align-self: start; }

/* -------------------------------------------------------------- Stat tiles */

.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: var(--space-4);
    margin-bottom: var(--space-5);
}

/*
    Tiles carry one number each, so they can take the thinner material and let
    more of the ground through than a card does. They are also the surface where
    the effect is most worth having: a row of four is the first thing on a
    dashboard.

    The sheen is a wide, very faint highlight across the top-left, the way light
    falls across a curved pane. It brightens on hover — the panel turning
    slightly towards the light rather than jumping forward, which is what a
    shadow-only hover looks like.
*/
/*
    The sheen is a background layer, not a pseudo-element laid over the top. An
    absolutely-positioned overlay paints above the content as well as the
    surface, so a 22% white wash sat on the number the tile exists to show and
    lifted it towards the background. In the background stack it does what it is
    meant to: light on the pane, nothing on the text.
*/
.stat {
    position: relative;
    padding: var(--space-4) var(--space-5) var(--space-5);
    background-color: var(--glass-panel);
    background-image:
        linear-gradient(135deg,
            rgb(255 255 255 / 26%) 0%,
            rgb(255 255 255 / 7%) 34%,
            transparent 62%);
    background-repeat: no-repeat;
    /* Oversized, so the sheen has somewhere to travel on hover. */
    background-size: 220% 220%;
    background-position: 0% 0%;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    border: 1px solid var(--glass-line);
    border-radius: var(--radius-lg);
    box-shadow: var(--rim), var(--shadow-sm);
    overflow: hidden;
    transition: background-position 420ms var(--ease),
                box-shadow var(--duration) var(--ease),
                border-color var(--duration) var(--ease);
}
/* Turning slightly towards the light, rather than jumping forward. */
/*
    A tile hover is the pane turning slightly towards the light, not jumping
    forward: the sheen slides across it inside its 220% background-size, and the
    rim brightens. Nothing translates, so a row of four does not ripple when the
    pointer crosses it.
*/
.stat:hover {
    background-position: 34% 20%;
    box-shadow: var(--rim), var(--shadow-md);
    border-color: rgb(var(--accent-rgb) / 32%);
}

/*
    On a dark panel the light-mode sheen reads as a smear, so it is halved.

    background-image ONLY. These two arms used to set the `background`
    SHORTHAND, which silently reset background-size and background-position to
    their initial values — so the sheen had nowhere to travel and the hover
    move was dead in dark while working in light.
*/
:root[data-theme="dark"] .stat {
    background-image:
        linear-gradient(135deg,
            rgb(255 255 255 / 10%) 0%,
            rgb(255 255 255 / 2%) 34%,
            transparent 62%);
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .stat {
        background-image:
            linear-gradient(135deg,
                rgb(255 255 255 / 10%) 0%,
                rgb(255 255 255 / 2%) 34%,
                transparent 62%);
    }
}

/*
    The semantic colour is carried by a dot beside the label, not by a painted
    strip down the left edge.

    The strip was the single most dated thing on the dashboard — it is the
    Bootstrap-admin signature — and it also failed at its job: four tiles in a
    row each grew a coloured bar, so the colour stopped distinguishing anything
    and just added four vertical lines to the page. A dot sits with the words it
    qualifies and disappears when the tile is merely neutral.
*/
.stat__label {
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: var(--text-sm);
    color: var(--text-muted);
    font-weight: var(--weight-medium);
}
/*
    Keyed off the rgb triplet rather than the finished colour, so the halo can
    take an alpha without relative-colour syntax, which not every engine this
    has to run on supports.
*/
.stat__label::before {
    content: "";
    flex: none;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: rgb(var(--stat-accent-rgb, var(--accent-rgb)));
    box-shadow: 0 0 0 3px rgb(var(--stat-accent-rgb, var(--accent-rgb)) / 15%);
}
.stat__value {
    font-family: var(--font-sans-display);
    margin-top: var(--space-3);
    font-size: var(--text-2xl);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-tighter);
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
    line-height: 1.05;
}
/*
    A tile whose value is words rather than a number.

    Twenty-five tiles wrote font-size:var(--text-lg) into a style attribute
    because 2xl is a figure's size: a date, a document number or a person's name
    at that size either wraps or runs out of the tile. The step is named here so
    the type scale stays in the stylesheet.
*/
.stat__value--sm { font-size: var(--text-lg); letter-spacing: var(--tracking-tight); }

.stat__meta {
    margin-top: 6px;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

/*
    Both are set: --stat-accent-rgb drives the dot and its halo, --stat-accent
    stays because other components (the meter, the chart list) still read it.
*/
.stat--success { --stat-accent-rgb: var(--success-rgb); --stat-accent: var(--success); }
.stat--warning { --stat-accent-rgb: var(--warning-rgb); --stat-accent: var(--warning); }
.stat--danger  { --stat-accent-rgb: var(--danger-rgb);  --stat-accent: var(--danger); }
.stat--info    { --stat-accent-rgb: var(--info-rgb);    --stat-accent: var(--info); }
.stat--neutral { --stat-accent-rgb: var(--neutral-rgb); --stat-accent: rgb(var(--neutral-rgb)); }

/* ------------------------------------------------------------------ Badges */

/*
    A tinted capsule of the semantic colour, lit from above. The gradient is
    slight — a badge is 11px of text and anything stronger turns into a stripe
    rather than a sheen — but it is what stops a row of status pills reading as
    flat stickers.
*/
.badge {
    display: inline-flex;
    font-family: var(--font-sans-small);
    align-items: center;
    gap: 5px;
    padding: 1px 9px;
    border-radius: var(--radius-pill);
    font-size: var(--text-xs);
    font-weight: 600;
    line-height: 1.7;
    white-space: nowrap;
    background:
        linear-gradient(180deg, rgb(255 255 255 / 34%), transparent 62%),
        rgb(var(--badge-rgb, var(--neutral-rgb)) / 14%);
    color: rgb(var(--badge-rgb, var(--neutral-rgb)));
    border: 1px solid rgb(var(--badge-rgb, var(--neutral-rgb)) / 26%);
    box-shadow: var(--rim-soft);
}
/* Named at last. It has been rendering correctly by accident off the
   --badge-rgb fallback in the views that ask for it. */
.badge--neutral { --badge-rgb: var(--neutral-rgb); }
.badge--success { --badge-rgb: var(--success-rgb); }
.badge--warning { --badge-rgb: var(--warning-rgb); }
.badge--danger  { --badge-rgb: var(--danger-rgb); }
.badge--info    { --badge-rgb: var(--info-rgb); }
.badge--accent  { --badge-rgb: var(--accent-rgb); }

/*
    Dark themes need a stronger tint to stay legible on the deep surface, and a
    fainter sheen — the light-mode white would read as a scuff on a dark pill.
    Both layers are restated, because setting `background` alone would drop the
    gradient the light rule puts in the same property.
*/
:root[data-theme="dark"] .badge {
    background:
        linear-gradient(180deg, rgb(255 255 255 / 10%), transparent 62%),
        rgb(var(--badge-rgb, var(--neutral-rgb)) / 20%);
    box-shadow: var(--rim-soft);
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .badge {
        background:
            linear-gradient(180deg, rgb(255 255 255 / 10%), transparent 62%),
            rgb(var(--badge-rgb, var(--neutral-rgb)) / 20%);
        box-shadow: var(--rim-soft);
    }
}

.badge__dot {
    width: 5px; height: 5px;
    border-radius: 50%;
    background: currentColor;
}

/* ------------------------------------------------------------------ Tables */

/*
    THE HEADER NOW STICKS, AND THE REASON IT DID NOT IS WORTH WRITING DOWN.

    overflow-x: auto computes overflow-y to auto as well, which made
    .table-wrap the sticky scrollport — and it had no height, so it never
    scrolled, so `top: 0` on the header had nothing to stick against. It has
    never once engaged in the shipped product, across every view that uses a
    table. There is no way to have overflow-x: auto and overflow-y: visible;
    the only fix is to bound the box and let it scroll.

    NO overscroll-behavior on the block axis. Containing the wheel turns a list
    screen into a trap: reach the last of 2,058 rows and the page stops
    responding until the pointer is moved off the table. Chaining is the
    browser's default and it is correct here. The inline axis IS contained,
    because a horizontal swipe that escapes triggers back-navigation.

    --table-max is per-screen: a view that wants a different height writes
    style="--table-max:40vh", and a view that wants the document to scroll
    instead writes .table-wrap--page and gives up the frozen header.

    Bounding also removes a z-index problem the old rule would have hit if it
    had ever worked: a sticky header inside a bounded scrollport cannot leave
    that box, so it can never rise behind the topbar at z-index 20.
*/
.table-wrap {
    overflow: auto;

    /*
        A SCROLLPORT MUST BE THE CONTAINING BLOCK FOR WHAT IS INSIDE IT.

        Without this, an absolutely positioned descendant with no positioned
        ancestor is laid out against the INITIAL containing block -- the page
        -- so it is neither clipped nor scrolled by the box it visually sits
        in, and its static position becomes the width of the document.

        Measured on /setup/asm-utility-bills/new, whose line grid is 1,826px
        wide inside a 677px card: a .visually-hidden span landed at
        left: 2031.81px and the page gained 767px of horizontal scroll. The
        whole screen slid sideways, and the table scrolled correctly the
        whole time -- which is why it reads as a mystery. documentElement
        reported scrollWidth 2032 while body reported 1265, and no element
        outside a scrollport could be found: the escapee WAS inside one, and
        being positioned is exactly what let it ignore the clip.

        Safe for popovers: .combo__list is position: fixed, which a relative
        ancestor does not capture (only transform/filter/will-change would).
        Swept the tables across twelve screens: the only absolutely
        positioned descendants are the clipped native <select> behind each
        combobox and this span, and both are things that should be contained
        rather than escape.
    */
    position: relative;

    overscroll-behavior-inline: contain;
    max-height: var(--table-max, min((62dvh / var(--zoom, 1)), 720px));
    background: var(--surface);
    -webkit-overflow-scrolling: touch;
}
.table-wrap--page {
    --table-max: none;
    max-height: none !important;
    height: auto !important;
    /* No overflow-y declaration here. Per spec a visible value computes to
       auto when the other axis is auto, which overflow-x below sets -- the
       browser reports overflow-y: auto either way. Declaring visible read
       as if it did something and never could. max-height: none above is
       what actually hands scrolling back to the document. */
    overflow-x: auto !important;
}

.table-wrap--page,
.results-card .table-wrap,
.filter-card + .card .table-wrap,
.card .table-wrap:not([data-scrollable]),
.card:has(> .card__body > .table-wrap) .table-wrap,
.card:has(> .table-wrap) > .table-wrap {
    --table-max: none;
    max-height: none !important;
    height: auto !important;
    overflow-y: visible !important;
    overflow-x: auto !important;
}

/* The data sheet fills the card's bottom corners when it is the last thing in
   it. */
.card__body--flush > .table-wrap:last-child,
.card > .table-wrap:last-child {
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}

.table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: var(--text-base);
}

/*
    A COLUMN IS AS WIDE AS WHAT IS IN IT, AND THE SCROLLPORT TAKES THE REST.

    width:100% above says the table is exactly as wide as its wrapper, no
    wider -- so when the content wants more, the browser does not scroll, it
    SQUEEZES. The narration column on the General Ledger got 90px, wrapped a
    one-line sentence over six lines, and made a 130px row; meanwhile
    .table-wrap's own overflow-x: auto sat there with nothing to scroll,
    because the table had already agreed never to exceed it. Vertical space
    is the scarce one on a screen: there is one page down, and sideways is a
    gesture.

    So: as wide as the content wants (width: max-content), and never
    narrower than the wrapper (min-width: 100%), which keeps a short table
    filling its card exactly as before.

    MEASURED over 347 screens, against the pages as the client's own data
    fills them, at both widths. Of the 167 that carry a populated table:

        1280px   37 shorter, 11,624px saved, 130 unchanged, 0 taller
         900px   52 shorter, 20,110px saved, 115 unchanged, 0 taller

    Payroll Adjustment loses a third of its height (7,379px to 4,936px),
    User GL Accounts a fifth, Promotion and Leave Quota nearly half at
    900px. NOT ONE PAGE GOT TALLER at either width.

    What it costs: 44 pages gain horizontal scroll at 1280px and 69 at
    900px. Nine of those at 1280px gain it WITHOUT getting any shorter --
    the rule firing on a table that was not actually being squeezed. That
    is the one case where this change is pure cost, so it was measured
    rather than waved at: the median of the nine is 8px and the worst is
    14px (/setup/report-data-source). None exceeds 200px, and none is a
    width a reader would ever have to scroll through.

    THE 80ch CAP is for the column that has no sensible width. The Exception
    log carries a stack trace; uncapped it asks for 1,885px of column and
    1,233px of sideways scroll to read one list. Capped it takes 639px --
    still nearly twice the 347px it gets today -- and the page is shorter
    than it was. 80ch only ever bites prose that long; the narration that
    started this is nowhere near it.

    break-word, NOT anywhere. "anywhere" also lowers the cell's min-content
    contribution, which lets the table algorithm hand the column LESS room
    than it had: it made the Exception log 582px taller and Sales Orders
    492px taller -- the opposite of the point. "break-word" breaks a word
    that cannot fit without telling the sizing algorithm the column may be
    one character wide. (This is the reverse of the choice .checkbox > span
    makes, and for the reverse reason: there, lowering min-content is
    exactly what stops a label forcing a grid track wide.)

    A GRID YOU TYPE INTO IS EXCLUDED, and that is what the :not(:has(...))
    is for. Its width comes from its controls, not from its text, so
    max-content sizing buys no vertical space at all and spends sideways
    scroll for it: the journal line grid went from the 1,092px of scroll it
    already has to 1,568px, and not one row got shorter. A checkbox is not
    an editable grid -- a select-all column is in half the lists here -- so
    it does not count.

    The guard is blunt, and deliberately so: it asks whether a table
    contains a control at all, not whether the controls are what gives it
    its width. A list that merely embeds one is excluded with the real line
    grids and keeps today's layout. That is the side to err on; the
    alternative is a class on every table in the application and a
    judgement call per screen.

    If a browser has no :has(), the whole selector is invalid and dropped,
    and every table below behaves exactly as it does today.
*/
.table-wrap > .table:not(:has(textarea, select, input:not([type="checkbox"]):not([type="hidden"]))) {
    width: max-content;
    min-width: 100%;
}
.table-wrap > .table:not(:has(textarea, select, input:not([type="checkbox"]):not([type="hidden"]))) td,
.table-wrap > .table:not(:has(textarea, select, input:not([type="checkbox"]):not([type="hidden"]))) th {
    max-width: 80ch;
}
.table-wrap > .table:not(:has(textarea, select, input:not([type="checkbox"]):not([type="hidden"]))) td {
    overflow-wrap: break-word;
}

/*
    Rows are separated by --border-subtle, not --border. At full border weight a
    forty-row voucher list is forty hard lines, and the eye reads the rules
    instead of the numbers. The subtle rule is enough to track across a row and
    quiet enough to disappear when you are scanning a column.
*/
/*
    THE ROW IS SIZED BY ITS PADDING, NOT BY A FIXED HEIGHT.

    This read `height: var(--row-h)`. CSS 2.1 calls height on a table cell a
    minimum, but a browser treats it as DEFINITE: a cell whose content needed
    more simply overflowed, and because cells are `overflow: visible` the extra
    line painted on top of the row below. On General types the name cell wanted
    62px, got 40, and dropped "came with the system" across the next row's
    name. --cell-y is set to reproduce the same 40px row for a single line, so
    nothing that already fitted moves.

    .table--dense keeps its own height and padding below: its cells are
    `white-space: nowrap` single lines, so they have nothing to overflow with.
*/
.table th,
.table td {
    padding: var(--cell-y) var(--cell-x);
    text-align: left;
    border-bottom: 1px solid var(--border-subtle);
    vertical-align: middle;
}

/*
    The header is set in the body size with weight and colour doing the work,
    rather than shrunk to 11.5px and spaced out in capitals. Uppercase tracking
    is legible at a glance but it is also the loudest thing on a list screen,
    and it competes with the data for attention.
*/
/*
    The header is OPAQUE, and so is the sheet beneath it. This is the line the
    material does not cross: glass goes on the furniture, never under dense
    data. A column header with forty rows of figures ghosting through it is
    unreadable at any contrast ratio, and a header that matched the card's
    translucent composite would drift against it as the ground's blooms change
    across the page.

    What makes it read as the same material is the rim and the shadow, not a
    second blur — and it costs nothing per frame, which matters here more than
    anywhere else in the product.
*/
.table thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    height: var(--row-h-dense);
    background: var(--surface);
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-tight);
    white-space: nowrap;
    border-bottom: 1px solid var(--border);
    box-shadow: var(--rim-soft);
}

/*
    Always on, rather than waiting for a .table-wrap--scrolled class that
    nothing has ever applied — not app.js, not a view, not another rule. A soft
    fall-off under a frozen header is right whether or not it is being scrolled
    under at this instant, and it needs no script.
*/
.table thead th::after {
    content: "";
    position: absolute;
    inset: auto 0 -6px 0;
    height: 5px;
    pointer-events: none;
    /* Was a fixed rgb(18 27 56 / 6%) — invisible on a dark surface. */
    background: linear-gradient(var(--glass-edge), transparent);
    opacity: 1;
    transition: none;
}

.table tbody tr { transition: background var(--duration-fast) var(--ease); }
.table tbody tr:last-child td { border-bottom: none; }

/* A row the user has picked out, as against merely hovered. */
.table tbody tr[aria-selected="true"],
.table tbody tr.is-selected { background: var(--surface-selected); }

/*
    THE LIT ROW — the row being worked in.

    :focus-within, so the keyboard and the pointer light it identically: they
    land in the same place, there is no state to keep and no script to fall out
    of step with, and it is correct the instant the browser moves focus. It is
    the one "futuristic" gesture a data screen actually earns — light on the
    work rather than haze behind it.

    The cursor is an inset shadow on the first cell rather than a border,
    because a border changes that cell's width and would shift every column in
    the table by three pixels as the cursor moves down it.
*/
.table tbody tr:focus-within { background: var(--surface-selected); }
.table tbody tr:focus-within > td:first-child,
.table tbody tr[aria-selected="true"] > td:first-child,
.table tbody tr.is-selected > td:first-child {
    box-shadow: inset 3px 0 0 rgb(var(--accent-rgb));
}

/* Hover must not beat focus: the pointer crossing a grid you are typing in
   should not take the light off the line you are on. */
.table tbody tr:hover:not(:focus-within) { background: var(--surface-hover); }

/* In an editable grid, the cell holding the caret is picked out a shade
   further, so a thirteen-column row says which of the thirteen you are in. */
.table td:focus-within { background: rgb(var(--accent-rgb) / 6%); }

/* Totals and subtotals are not data rows and should not read as one. */
.table tfoot td,
.table tbody tr.is-total td {
    font-weight: var(--weight-semibold);
    background: var(--surface-sunken);
    border-top: 1px solid var(--border);
    border-bottom: none;
}

/* A denser mode for the grids that carry thirty rows of line items — declared
   once, further down, beside the attendance-cell rules that depend on it. It
   was declared twice, 1,600 lines apart, and the padding here was silently
   discarded by the other one. */

/* Numeric columns: right-aligned, tabular, monospaced for column alignment. */
.table .num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
    white-space: nowrap;
}
.table .num--mono { font-family: var(--font-mono); font-size: var(--text-sm); }

/*  A negative number that the reader has to notice. Added for the cash and
    bank form's Bank balance box, where a minus sign means an overdraft and the
    original's own field clamps it away to zero. --danger is the existing
    token; nothing here invents a colour. */
.num--negative { color: var(--danger); }

/*
    The minus hangs into the gutter.

    The sign occupies one character's width and gives it all back, so a
    negative sits in the column's padding and the digits of every row start on
    the same vertical — which is the whole point: the eye tracks the thousands
    separator down a column, and a leading minus moves it by about a third of a
    digit, putting the positive rows' separators and the negative rows' on two
    different verticals.

    1ch of the mono face at 12.5px is about 7.5px against a cell padding of 16,
    so there is room for it inside the cell's own padding.
*/
.num__sign {
    display: inline-block;
    width: 1ch;
    margin-left: -1ch;
    text-align: left;
}

/* A blank account, sized to one tabular digit rather than to an en dash's own
   width, so it does not widen the column it is in. */
.num__zero {
    display: inline-block;
    width: 1ch;
    text-align: center;
    color: var(--text-muted);
}

.table .col-shrink { width: 1%; white-space: nowrap; }

/*  The employee card on a setup form (Employee contract, Warning, ...) sits in a <form>,
    outside the .content > .card spacing rule above, so the Details card ran into it. */
form > .card[data-employee-summary] { margin-bottom: var(--space-4); }

.table__sort {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: inherit;
    font: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
}
.table__sort:hover { color: var(--text); text-decoration: none; }
/*
    The `i` flag, because the value is a .NET bool.

    The views write data-active="@(Model.SortBy == "name")", and Razor renders
    whatever Boolean.ToString() gives it — "True", with a capital T. CSS
    attribute values are matched case-sensitively in an HTML document, so
    [data-active="true"] matched nothing: on the two views that write it the
    sorted column has never once been accented, and the header gave no sign of
    which column the list was ordered by.

    Fixed here rather than in the views, so it holds for any view that writes
    the attribute later and spells the bool however Razor feels like spelling
    it.
*/
.table__sort[data-active="true" i] { color: var(--accent); }

.table--zebra tbody tr:nth-child(even) { background: rgb(var(--neutral-rgb) / 4%); }
/* Zebra is more specific than the lit-row rules above, so it has to restate
   them or a striped grid loses the light on the line being worked in. */
.table--zebra tbody tr:nth-child(even):hover:not(:focus-within) { background: var(--surface-hover); }

/*
    A table whose cells hold one digit or one word.

    .input, .select and .textarea are width:100% -- right in a form grid,
    where a field owns its row, and wrong in a table cell, where the column
    is as wide as the layout allows. On the Global Options code structures
    that made a "Digits Width" box 166px for a number from 1 to 10, and the
    "Property" and "Operation" dropdowns 355px each for the words "Number"
    and "Auto", at 1440px. Six panes share that partial -- chart of accounts,
    cost centre, fixed assets, warehouse and the two business partner
    structures -- so they all read the same way.

    Capped here rather than by changing the control rule, which every form in
    the application relies on. The columns keep their padding; it is the
    controls that stop stretching.
*/
.table--structure { width: auto; }
.table--structure th,
.table--structure td { white-space: nowrap; }
.table--structure td .input { max-width: 7rem; }
.table--structure td .select { max-width: 12rem; }
.table--zebra tbody tr:nth-child(even):focus-within { background: var(--surface-selected); }

/* Primary identifier in a row. */
.cell-strong { font-weight: 600; }
.cell-sub {
    display: block;
    font-size: var(--text-xs);
    font-family: var(--font-sans-small);
    color: var(--text-muted);
}

/*
    A CODE SITS BESIDE THE NAME IT BELONGS TO, NOT UNDER IT.

    Reported from the Business Partners list: "2 Innovative" on one line and
    "11010035" on the next, which is two lines of row height to carry one
    identifier. Now that a column is as wide as its content (see A COLUMN IS
    AS WIDE AS WHAT IS IN IT above) there is room for both on one line, and
    the row halves.

    SCOPED TO THE PAIR, deliberately. `.cell-sub` has 133 uses across 68
    views and most are NOT a code: "came with the system", "no serial
    recorded", "Manage Cost Centres" -- explanatory lines that belong under
    what they explain, and two of them are <div>s in a tile where inline
    would break the layout outright. Only a .cell-sub that IMMEDIATELY
    FOLLOWS a .cell-strong is the name/code pair the report is about, and
    the adjacent-sibling combinator is what says so.

    The margin, not a gap: these are inline boxes, so the source newline
    between them already collapses to one space; the margin is what makes it
    read as a deliberate pairing rather than a run-on.
*/
.cell-strong + .cell-sub {
    display: inline;
    margin-left: var(--space-2);
}

/* ------------------------------------------------------------- Empty state */

.empty {
    display: grid;
    place-items: center;
    gap: var(--space-3);
    padding: var(--space-12) var(--space-5);
    text-align: center;
    color: var(--text-muted);
}
.empty__icon {
    display: grid;
    place-items: center;
    width: 46px; height: 46px;
    border-radius: var(--radius-pill);
    background: var(--surface-sunken);
    color: var(--text-subtle);
}
.empty__title { font-size: var(--text-md); font-weight: 600; color: var(--text); }
.empty__text { max-width: 40ch; font-size: var(--text-base); }

/* -------------------------------------------------------------- Pagination */

.pager {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
}
.pager__status { font-size: var(--text-sm); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.pager__controls { margin-left: auto; display: flex; gap: var(--space-1); }

/* A report column that can be sorted: the heading is a link that still reads as the heading it was. */
.th-sort { color: inherit; text-decoration: none; display: inline-flex; align-items: center; gap: 4px; }
.th-sort:hover { text-decoration: underline; }
.th-sort__mark { font-size: 0.8em; opacity: 0.7; }
th[aria-sort="ascending"] .th-sort,
th[aria-sort="descending"] .th-sort { font-weight: 700; }

/* "Rows per page", beside the row count: plain links, so the page needs no script to change size. */
.result-size { display: inline-flex; align-items: center; gap: var(--space-1); font-size: var(--text-sm); color: var(--text-muted); }
.result-size__opt { color: inherit; text-decoration: none; padding-inline: 6px; border-radius: 6px; }
.result-size__opt:hover { background: rgb(var(--neutral-rgb) / 10%); }
.result-size__opt[aria-current="true"] { background: rgb(var(--accent-rgb) / 14%); color: var(--accent-strong); font-weight: 600; }

/* The library's "Forms" cell: what a report comes in. Standard is every report's; the original layout and the related dashboard are
   drawn only where there is one to open. The dashboard is a link, so it is a badge that takes a hover of its own. */
.forms { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); }
.forms__link { text-decoration: none; }
.forms__link:hover { background: rgb(var(--accent-rgb) / 24%); }
.forms-note { margin: 0; font-size: var(--text-sm); color: var(--text-muted); max-width: 70ch; }

.pager__link {
    display: grid;
    place-items: center;
    min-width: 30px; height: 30px;
    padding: 0 var(--space-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
}
.pager__link:hover { background: var(--surface-hover); color: var(--text); text-decoration: none; }
.pager__link[aria-current="page"] {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-contrast);
    font-weight: 600;
}
.pager__link[aria-disabled="true"] { opacity: 0.4; pointer-events: none; }

/* The pager under a dashboard list (_DashboardPager): status left, Previous / Next, View all right. */
.dash-pager {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
    margin-top: var(--space-3);
    padding-top: var(--space-2);
    border-top: 1px solid var(--border);
    font-size: var(--text-sm);
}
.dash-pager__status { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.dash-pager__controls { display: inline-flex; align-items: center; gap: var(--space-2); margin-left: auto; }
.dash-pager__page { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.dash-pager__link {
    display: grid;
    place-items: center;
    min-width: 28px; height: 28px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text-muted);
}
.dash-pager__link:hover { background: var(--surface-hover); color: var(--text); text-decoration: none; }
.dash-pager__link[aria-disabled="true"] { opacity: 0.4; pointer-events: none; }
.dash-pager__all { font-weight: 600; white-space: nowrap; }
.dash-pager:not(:has(.dash-pager__controls)) .dash-pager__all { margin-left: auto; }
[data-dash-widget][aria-busy="true"] { opacity: 0.6; transition: opacity 0.15s; }

/* ------------------------------------------------------------------- Forms */

.form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: var(--space-4);
}
.field { display: flex; flex-direction: column; gap: 5px; min-width: 0; grid-column: span 2; }
.field--xs   { grid-column: span 1; }   /* a code, a flag, 1-3 characters   */
.field--sm   { grid-column: span 2; }   /* a date, a number, up to 20       */
.field--md   { grid-column: span 3; }   /* a name, up to 60                 */
.field--lg   { grid-column: span 4; }   /* an address line, up to 200       */
.field--full { grid-column: 1 / -1; }

/*
    THE GENERATED SETUP SCREENS: TWO TO A ROW, ALWAYS, EXCEPT A LONG ONE.

    .form-grid is not Setup/Edit.cshtml's own class -- every hand-built
    document in the application uses it too, Journal and CashBank and Sales
    Order among some ninety others, each with field widths a person chose
    for that one screen. Those stay exactly as written: touching .form-grid
    itself, or the five spans above, would have quietly reflowed every one
    of them.

    .form-grid--uniform is Setup/Edit.cshtml's own, added only to the grid
    it generates. There, a field's width was never a person's choice: it
    was WidthOf() reading MaxLength off the column behind it, so two fields
    on the same screen differ in width for a reason that traces back to a
    database schema, not to anything about the page -- and it showed, as
    rows holding two fields, then three, then two again with nothing to
    explain why. This is what it generates instead: two fields to a row,
    every row, and only a field that cannot honestly be halved -- a
    description, anything long enough that halving it would just wrap the
    text -- takes the full one.
*/
.form-grid--uniform {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}
.form-grid--uniform > .field { grid-column: span 1; }
.form-grid--uniform > .field.field--full { grid-column: 1 / -1; }

/*
    FOUR TO A ROW, for a form that copies the original's own four-column
    layout row for row (its col-md-3 grid) -- the Leave Definition. Each cell
    is one field or one .field-pair; --wide takes two cells, --row the line.
    Two columns where four no longer fit, one on a phone. Its own media rules,
    because `.form-grid--four > .field` outranks the plain `.field` the
    narrow-screen rule below resets.
*/
.form-grid--four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.form-grid--four > .field,
.form-grid--four > .field-pair { grid-column: span 1; }
.form-grid--four > .field--wide { grid-column: span 2; }
.form-grid--four > .field--row { grid-column: 1 / -1; }
@media (max-width: 1100px) {
    .form-grid--four { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
    .form-grid--four { grid-template-columns: minmax(0, 1fr); }
    .form-grid--four > .field,
    .form-grid--four > .field-pair,
    .form-grid--four > .field--wide { grid-column: 1 / -1; }
}

/* Two fields sharing one cell: the original's col-md-6 pairs inside a col-md-3. */
.field-pair {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-2);
    min-width: 0;
}
.field-pair > .field { grid-column: auto; justify-content: flex-end; }
.field-pair > .field-pair__note { grid-column: 1 / -1; }

/* A one-line textarea, as the original's rows="1" remarks: it grows when dragged. */
.textarea.textarea--line { min-height: var(--control-h); }

/* A checkbox in a row of inputs sits on their baseline, not their labels'. */
.field--flag { justify-content: flex-end; }
.field--flag > .checkbox,
.field--flag > .row { min-height: var(--control-h); }

/*
    A NUMBER WITH ITS UNIT: "Days", "Hours", "Times", "Months" fixed to the
    right of the box, as the original's input-group-addon draws them. The
    unit is part of the control, so it is drawn inside its border and greys
    with it when the box is closed.
*/
.input-unit { display: flex; align-items: stretch; min-width: 0; }
.input-unit > .input {
    flex: 1 1 auto;
    min-width: 0;
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}
.input-unit__suffix {
    display: inline-flex;
    align-items: center;
    flex: none;
    padding: 0 var(--space-3);
    border: 1px solid var(--border-strong);
    border-left: 0;
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    background: var(--surface-sunken);
    color: var(--text-muted);
    font-size: var(--text-sm);
    white-space: nowrap;
}
.input-unit > .input:disabled + .input-unit__suffix { opacity: 0.6; }
.field:has(> .input-unit > .input[required]) > .label::after {
    content: "*";
    margin-left: 3px;
    color: var(--danger);
    font-weight: var(--weight-semibold);
}

/* A quiet text button beside a field's label: "All", "None". */
.field__link {
    flex: none;
    border: none;
    background: none;
    padding: 0 2px;
    color: var(--text-muted);
    font-size: var(--text-xs);
    text-decoration: underline;
    cursor: pointer;
}
.field__link:hover { color: var(--text); }

/*
    ALL / NONE ON EVERY MULTI-SELECT (addPickLinks in app.js). In a field with
    no .field__head they sit in its top corner rather than beside a moved label:
    the required asterisk and the focus colour are `.field > .label` rules, so
    the label stays a direct child where it is.
*/
.pick-links {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    line-height: 1.5;
}
.field--pick-links { position: relative; }
.field--pick-links > .pick-links {
    position: absolute;
    top: 0;
    inset-inline-end: 0;
}
.field--pick-links > .label { padding-inline-end: 5.5rem; }
.field__head > .pick-links { margin-inline-start: auto; }
/*  A disabled multi-select posts nothing: links that changed it would be an
    edit nobody saves. */
.field:has(select[multiple]:disabled) .pick-links { display: none; }

/*  "+136 more", after the first ten chips of a long choice. */
.combo__chip.combo__chip--more {
    cursor: pointer;
    background: transparent;
    border-style: dashed;
    font-family: inherit;
}

/* A summary tag picker's count reads as its value, not as a hint. */
.combo--summary.has-value .combo__input::placeholder { color: var(--text); opacity: 1; }

/*
    A FIELD IN A ROW SHARES THE ROW.

    The modifiers above are grid spans, and they only speak for a field inside
    .form-grid. A field inside a .row is in a flex container, where the default
    is to size to content -- so 116 fields across the application declared
    "flex:1" in a style attribute to get the obvious behaviour, and fourteen
    declared "flex:2" for a wider one.

    The basis is 0 rather than auto so the columns come out equal regardless of
    what is in them: with auto, a field holding a long select is wider than its
    neighbours and the row stops lining up. min-width:0 is what lets a field
    shrink at all -- a flex item's floor is its content otherwise, which is how
    a long option pushed a row off the side of its card.
*/
.row > .field { flex: 1 1 0; min-width: 0; }

/* The one that takes twice the share of a row. */
.row > .field--grow { flex: 2 1 0; }

/*
    And the same word inside a filter bar, where the fields size to content
    rather than sharing: the search box on two of the raise forms is the only
    field on its bar and wants the room. The basis is a width rather than 0
    because .filters .field starts from auto, and mixing the two makes the bar
    jump when a second field appears beside it.
*/
.filters .field--grow { flex: 2 1 240px; }

/*
    Below the point where four tracks stop fitting, a spanning field would force
    an implicit column and overflow the card. One column, everything full
    width, which is what a form on a narrow screen wants anyway.

    Do not add a .field--xl spanning 5 without moving this breakpoint.
*/
@media (max-width: 640px) {
    .form-grid { grid-template-columns: minmax(0, 1fr); }
    .field,
    .field--xs, .field--sm, .field--md, .field--lg, .field--full { grid-column: 1 / -1; }
}

.label {
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    color: var(--text-muted);
    letter-spacing: var(--tracking-tight);
    transition: color var(--duration-fast) var(--ease);
}
.label__hint { font-weight: var(--weight-normal); color: var(--text-muted); }

/* The label the field it describes is now the one the caret is in. A quiet
   confirmation for a long form — the row currently being filled in has one
   more thing distinguishing it from the forty already done. */
.field:focus-within > .label { color: rgb(var(--accent-strong-rgb)); }

/*
    A required field says so. Nothing marked one before, so the only way to find
    out which of the twenty fields on a voucher had to be filled was to save and
    read the refusal.
*/
/*
    And it keys off the control's own state, not a class.

    .label--required has been defined and used ZERO times, so on the generated
    setup screens the OPTIONAL fields were marked and required was the unmarked
    default -- which reads backwards. Setup/Edit.cshtml already computes
    `var req = f.IsRequired && Model.CanManage` and writes required="@req" on
    the control, which Razor renders as required="required" or omits entirely,
    so the marker needs no new class and no new Razor.

    :has() is scoped to .field, which never occurs inside a table body. Where it
    is unsupported the asterisk simply does not appear, which is today.
*/
.field:has(> .input[required]) > .label::after,
.field:has(> .select[required]) > .label::after,
.field:has(> .combo > .select[required]) > .label::after,
.field:has(> .textarea[required]) > .label::after,
.label--required::after {
    content: "*";
    margin-left: 3px;
    color: var(--danger);
    font-weight: var(--weight-semibold);
}

.input,
.select,
.textarea {
    width: 100%;
    height: var(--control-h);
    padding: 0 var(--space-3);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font-size: var(--text-base);
    font-family: inherit;
    box-shadow: var(--shadow-xs);
    transition: border-color var(--duration-fast) var(--ease),
                box-shadow var(--duration-fast) var(--ease);
}
.input:hover:not(:disabled),
.select:hover:not(:disabled),
.textarea:hover:not(:disabled) { border-color: var(--n-400); }

.textarea {
    height: auto;
    min-height: 76px;
    padding: var(--space-2) var(--space-3);
    resize: vertical;
    line-height: var(--leading-normal);
}

/*
    The ring keys off :focus-visible, not :focus.

    Clicking an input with a mouse produced the full four-pixel accent ring,
    which is a different rule from every other control on the page and made
    every click on a twenty-field voucher flash blue.
*/
.input:focus,
.select:focus,
.textarea:focus { outline: none; border-color: var(--accent); box-shadow: none; }

.input:focus-visible,
.select:focus-visible,
.textarea:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: var(--ring);
}
.input::placeholder, .textarea::placeholder { color: var(--text-subtle); }
.input:disabled, .select:disabled, .textarea:disabled {
    background: var(--surface-sunken);
    color: var(--text-subtle);
    border-color: var(--border);
    box-shadow: none;
    cursor: not-allowed;
}

/*
    A field the form fills in rather than the user: derived, computed or carried
    from the document. Read-only is not disabled — the value matters and should
    stay legible — so it loses the affordance of an input without losing contrast.
*/
.input[readonly], .textarea[readonly] {
    background: var(--surface-sunken);
    border-color: transparent;
    box-shadow: none;
    color: var(--text-muted);
}

/*
    Money and quantity inputs align on the decimal like the table columns they
    will end up in.
*/
.input--num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
}

/*
    The native date and time pickers. Their indicator is a dark glyph that
    vanishes on a dark ground, and the control sits a pixel or two off the
    others' baseline unless it is told not to.
*/
.input[type="date"],
.input[type="time"],
.input[type="datetime-local"] { padding-right: var(--space-2); }

.input[type="date"]::-webkit-calendar-picker-indicator,
.input[type="time"]::-webkit-calendar-picker-indicator,
.input[type="datetime-local"]::-webkit-calendar-picker-indicator {
    opacity: 0.55;
    cursor: pointer;
    border-radius: var(--radius-xs);
    transition: opacity var(--duration-fast) var(--ease);
}
.input[type="date"]:hover::-webkit-calendar-picker-indicator,
.input[type="time"]:hover::-webkit-calendar-picker-indicator,
.input[type="datetime-local"]:hover::-webkit-calendar-picker-indicator { opacity: 1; }

/*
    Inverted only when the theme really is dark. The OS-preference arm has to
    stay inside its media query: ":root:not([data-theme=light])" on its own is
    true on a default light page as well, and would have inverted the glyph for
    everyone.
*/
:root[data-theme="dark"] .input::-webkit-calendar-picker-indicator { filter: invert(1) brightness(1.6); }

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .input::-webkit-calendar-picker-indicator { filter: invert(1) brightness(1.6); }
}

.select {
    appearance: none;
    padding-right: var(--space-8);
    /* Chevron drawn as a data URI so no icon font or extra request is needed. */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2378839b' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right var(--space-3) center;
}

/*
    A native multiple select is a list box, not a dropdown. The single-line
    rules above — a fixed control height and the chevron on the right — crush
    all its `size` rows into one line's height and paint a pointless arrow over
    them. Let `size` set the height, drop the chevron, and give the option rows
    room to breathe.
*/
.select[multiple] {
    height: auto;
    min-height: var(--control-h);
    padding: var(--space-2);
    background-image: none;
    line-height: var(--leading-normal);
}
.select[multiple] option {
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-sm);
}

/*
    Checkboxes and radios, drawn rather than left native.

    The browser default is a 13px square in the platform's own blue. It ignores
    the accent, it ignores the control height, and on the dark theme it stays
    stubbornly light — so a row of five voucher flags was the one place on the
    screen that still looked like an unstyled form from 2003. accent-color alone
    does not fix the size or the dark ground, so they are drawn.
*/
input[type="checkbox"],
input[type="radio"] {
    appearance: none;
    flex: none;
    width: 16px;
    height: 16px;
    margin: 0;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-xs);
    background: var(--surface);
    box-shadow: var(--shadow-xs);
    cursor: pointer;
    display: inline-grid;
    place-content: center;
    transition: background var(--duration-fast) var(--ease),
                border-color var(--duration-fast) var(--ease),
                box-shadow var(--duration-fast) var(--ease);
}
input[type="radio"] { border-radius: 50%; }

input[type="checkbox"]:hover:not(:disabled),
input[type="radio"]:hover:not(:disabled) { border-color: var(--n-400); }

input[type="checkbox"]::before {
    content: "";
    width: 10px;
    height: 10px;
    transform: scale(0);
    transition: transform var(--duration-fast) var(--ease);
    background: var(--accent-contrast);
    /* A tick, as a mask, so it takes the contrast colour rather than a fixed one. */
    clip-path: polygon(14% 46%, 0 60%, 40% 100%, 100% 22%, 86% 8%, 38% 71%);
}
input[type="radio"]::before {
    content: "";
    width: 7px;
    height: 7px;
    border-radius: 50%;
    transform: scale(0);
    transition: transform var(--duration-fast) var(--ease);
    background: var(--accent-contrast);
}

input[type="checkbox"]:checked,
input[type="radio"]:checked {
    background: var(--accent);
    border-color: var(--accent);
}
input[type="checkbox"]:checked::before,
input[type="radio"]:checked::before { transform: scale(1); }

input[type="checkbox"]:indeterminate {
    background: var(--accent);
    border-color: var(--accent);
}
input[type="checkbox"]:indeterminate::before {
    transform: scale(1);
    clip-path: inset(42% 12% 42% 12%);
}

input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible { outline: none; box-shadow: var(--ring); }

input[type="checkbox"]:disabled,
input[type="radio"]:disabled {
    background: var(--surface-sunken);
    border-color: var(--border);
    box-shadow: none;
    cursor: not-allowed;
}

/*
    A checkbox and its words travel together and are clicked together. Without
    this each flag on a voucher was a 16px target beside unlinked text.
*/
.check {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    cursor: pointer;
    user-select: none;
}

.field__error {
    font-size: var(--text-sm);
    font-family: var(--font-sans-small);
    color: var(--danger);
    font-weight: var(--weight-medium);
}
.field__hint { font-size: var(--text-sm); color: var(--text-muted); }

/* The same hint when it is the reason the form cannot be saved at all. */
.field__hint--empty { color: var(--warning); }

/*
    A value the form shows but does not let you change -- a document number the
    series allocated, a status the approval decided. It sits where an input
    would, so it aligns with the fields around it, but is deliberately not
    styled as a disabled input: a greyed-out box invites people to wonder what
    they have to do to enable it.
*/
/*
    At padding: 7px 0 this was 33px tall against a 36px --control-h, so a readout
    never sat on the baseline of the fields beside it.
*/
.readout {
    margin: 0;
    padding: 0;
    min-height: var(--control-h);
    display: flex;
    align-items: center;
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
}

/*
    Splits a long settings form into readable groups.

    Set in the body size with weight and colour doing the work, matching the
    table headers. Uppercase with letter-spacing was the loudest thing on a
    form: a group label should tell you where you are when you look for it, not
    compete with the fields under it for attention.

    The rule beside it carries the eye across the width, so a group reads as a
    band rather than as a stray line of small text.
*/
.section-heading {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin: var(--space-6) 0 var(--space-4);
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-tight);
    color: var(--text);
}
.section-heading::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--border-subtle);
}
.card__body > .section-heading:first-child { margin-top: 0; }

/*
    An asp-validation-summary renders its container whether or not there is
    anything in it, so without this every form shows an empty red alert box
    before it has been submitted. The tag helper marks the empty case for us.
*/
.validation-summary-valid { display: none; }

/* Times in a grid row need only enough width for hh:mm. */
.input--time { max-width: 8.5rem; }
/*
    A refused field. It was announced by one pixel of red on the border. A wash
    says it at a glance without shouting.
*/
.input.is-invalid,
.select.is-invalid,
.textarea.is-invalid {
    border-color: var(--danger);
    background-color: rgb(var(--danger-rgb) / 7%);
}
.input.is-invalid:focus-visible,
.select.is-invalid:focus-visible,
.textarea.is-invalid:focus-visible {
    box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px rgb(var(--danger-rgb) / 45%);
}

/*
    ONE checkbox class, and it is .checkbox, because that is the one the views
    write — 49 files, against zero for .check.

    The rule no longer touches the control: width: 15px; height: 15px was
    shrinking the drawn 16px control by a pixel at every call site, and
    accent-color was inert anyway because appearance: none had already won.

    .check is kept as an alias rather than deleted, so the .filters .check rule
    written to fix the filter bar's baseline finally has something to apply to.
*/
.checkbox,
.check {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    cursor: pointer;
    user-select: none;
    min-width: 0;
}
.checkbox > span,
.check > span {
    min-width: 0;

    /*
        AND SO THE WORD MUST BE ALLOWED TO BREAK.

        min-width:0 above lets this box shrink below its text -- which is
        what keeps a checkbox from forcing a grid track wide -- but shrinking
        a box does not shorten the word inside it. A label with no space in
        it has no break opportunity, so it simply paints across whatever sits
        to its right.

        Measured on Table Mapping, which labels its checkboxes with the
        column names of the table being mapped: in a 122px track,
        BACK_SCHEDULE_INTERVAL is 195px and ran 73px over the top of
        BACK_SCHEDULE_NAME beside it. Six of sixteen labels collided at
        900px and four still collided at 1280px, so this is not a narrow-
        window case.

        "anywhere" rather than "break-word": it is the one that also lowers
        the min-content contribution, so a track is free to be as narrow as
        the grid asked for instead of being held open by the longest
        identifier. Same choice, and the same reason, as .rp__table in
        report-print.css. Ordinary prose labels are unaffected -- they break
        at their spaces long before this rule has anything to do.
    */
    overflow-wrap: anywhere;
}

/* --------------------------------------------------------------- Filterbar */

/*
    The filter bar reads as a control surface rather than as more content: a
    recessed ground, quieter labels, and every control on one baseline.

    It was the worst thing on a list screen. The fields filled the row, so
    "Apply" and "Reset" wrapped onto a line of their own and sat stranded at the
    right; the standalone checkbox floated at a third height because it has no
    label element above it to align against; and the whole block sat on white,
    so it looked like the first two rows of the table rather than the thing that
    filters it.
*/
/*
    A step cut INTO the sheet, not another box on it.

    --surface-sunken is opaque, so it punched a grey hole through the glass
    card. rgb(var(--neutral-rgb) / 7%) composites over whatever the card is, in
    either theme, so the boundary stays and only the MATERIAL changes: a real
    recess with a shadow falling in at the top and a lit lip at the bottom,
    instead of a flat grey band. The controls keep their own surface, which is
    what makes them read as raised out of it.

    The bottom border stays at --border, not --border-subtle: below it is the
    solid data sheet and above it is glass, and a boundary between two
    materials is a real edge.
*/
/* World-Class Universal Filter Card & Results Card Architecture */
.filter-card,
.card:has(> .filters),
.card:has(> form.filters) {
    margin-bottom: 0.5rem !important;
    border-radius: var(--radius-lg, 12px) !important;
    border: 1px solid var(--border) !important;
    background: var(--surface) !important;
    box-shadow: var(--shadow-sm);
    overflow: hidden !important;
}

.results-card,
.card:has(.table-wrap),
.card:has(.table),
.card:has(> .card__body > .table-wrap),
.card:has(> .card__body > .table),
.filter-card + .card {
    border-radius: var(--radius-lg, 12px) !important;
    border: 1px solid var(--border) !important;
    background: var(--surface) !important;
    box-shadow: var(--shadow-sm);
    overflow: hidden !important;
}

.results-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.35rem 0.85rem !important;
    background: linear-gradient(180deg, var(--surface) 0%, var(--surface-sunken) 100%);
    border-bottom: 1px solid var(--border-subtle);
    flex-wrap: wrap;
    gap: var(--space-2);
}

.results-header__info {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.results-header__title {
    font-size: 0.85rem !important;
    font-weight: 700;
    color: var(--text);
    letter-spacing: -0.01em;
}

.results-header__count {
    font-size: 0.75rem !important;
    color: var(--text-muted);
}

.results-header__actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.results-page-indicator {
    font-size: 0.72rem !important;
    font-weight: 600;
    color: var(--text-subtle);
    background: var(--surface);
    padding: 0.15rem 0.5rem !important;
    border-radius: var(--radius-sm, 6px);
    border: 1px solid var(--border-subtle);
}

.filters {
    display: flex;
    align-items: flex-end;
    gap: 0.5rem !important;
    flex-wrap: wrap;
    padding: 0.45rem 0.75rem !important;
    background: linear-gradient(180deg, var(--surface) 0%, var(--surface-sunken) 100%);
    border-bottom: 1px solid var(--border-subtle);
}

@media (min-width: 1081px) {
    .filters.filters--nowrap {
        flex-wrap: nowrap !important;
    }
}

.card > .filters:only-child,
.card > form.filters:only-child,
.card > .filters:last-child {
    border-bottom: none;
    border-radius: inherit;
}

.filters .field {
    flex: 0 1 auto;
    min-width: 130px;
    margin: 0 !important;
    gap: 2px;
}

.filters .field--search {
    flex: 1 1 200px;
    min-width: 150px;
    max-width: 320px;
}

.filters .label {
    font-family: var(--font-sans-small);
    font-size: 0.75rem !important;
    font-weight: 600 !important;
    color: var(--text-muted);
    margin-bottom: 0.2rem !important;
}

.filters .input,
.filters .select,
.filters .combo__input {
    height: 2.15rem !important;
    min-height: 2.15rem !important;
    font-size: 0.82rem !important;
    border-radius: var(--radius-sm) !important;
    background: var(--surface);
    padding-top: 0 !important;
    padding-bottom: 0 !important;
    line-height: 2.15rem !important;
}

.filters .select--op {
    height: 1.35rem !important;
    line-height: 1.35rem !important;
    min-width: 3.8rem !important;
    font-size: 0.7rem !important;
    padding: 0 1.25rem 0 0.45rem !important;
}

.filters .field__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    margin-bottom: 0.2rem !important;
}

.filters .field__range {
    display: flex;
    align-items: center;
    gap: 0.35rem;
}

.filters .field__range .input {
    min-width: 0;
    flex: 1 1 0;
    height: 2.15rem !important;
    font-size: 0.8rem;
    padding: 0 0.5rem;
}

.filters .search-input {
    position: relative;
    display: flex;
    align-items: center;
    width: 100%;
}

.filters .search-input svg {
    position: absolute;
    left: 0.65rem;
    color: var(--text-subtle);
    pointer-events: none;
}

.filters .search-input .input {
    padding-left: 2.15rem !important;
    padding-right: 2rem !important;
}

.filters__actions {
    display: flex;
    gap: 0.35rem !important;
    margin-left: auto;
    align-items: center;
    height: auto !important;
}

.filters__actions .btn {
    height: 2.15rem !important;
    font-size: 0.82rem !important;
    border-radius: var(--radius-sm) !important;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0 0.75rem !important;
}

/* Only the weight. .btn--primary already paints the accent gradient and sets
   color: var(--accent-contrast), which is the foreground that stays legible on
   the accent in every palette and both themes. This rule used to override both:
   the background read var(--brand), which nothing defines, so that declaration
   was discarded and the gradient survived by accident -- but color: #ffffff is a
   plain value and did apply, replacing the contrast token with hard white on
   whatever the palette's accent happens to be. */
.filters__actions .btn--primary {
    font-weight: 600;
}

/*
    THE ORIGINAL-FORMAT MENU on a report's toolbar --
    Views/Reports/_OriginalFormats.cshtml.

    The shape is the original's own: its report bar is a primary button with a
    caret button joined to it, and the caret opens the export formats. Here the
    button views the report and the menu holds PDF, Excel and the four the
    Crystal viewer's own dropdown offered.

    A FLOATING PANEL AND NOT AN INLINE ONE, unlike .filters__more above. This
    sits at the end of a one-line toolbar at the foot of a card; expanding in
    place would push the card's own border down and shift the grid below it
    every time somebody looked at the menu.

    No script: the <details> opens, closes, closes on Escape and takes focus
    from the keyboard on its own.
*/
.fmt {
    display: inline-flex;
    align-items: center;
}

/*  The caret is welded to the button it belongs to -- square where they meet,
    and pulled one pixel so the two borders are one line rather than two. */
.fmt > .btn {
    border-top-right-radius: 0 !important;
    border-bottom-right-radius: 0 !important;
}

.fmt__more {
    position: relative;
    display: inline-flex;
}

.fmt__toggle {
    border-top-left-radius: 0 !important;
    border-bottom-left-radius: 0 !important;
    margin-left: -1px;
    padding: 0 0.4rem !important;
    cursor: pointer;
    list-style: none;
    user-select: none;
}

/*  BOTH, or the caret carries a disclosure triangle beside the chevron:
    ::marker is the standard one and WebKit still answers only to its own. */
.fmt__toggle::-webkit-details-marker { display: none; }
.fmt__toggle::marker { content: ""; }

.fmt__list {
    position: absolute;
    z-index: 30;
    top: calc(100% + 4px);
    /*  Right-aligned because this control is at the right-hand end of the
        toolbar: opening leftwards keeps the panel on the page instead of
        hanging off it. */
    right: 0;
    min-width: 13.5rem;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 4px;
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lg);
}

/*
    THE CARD MUST NOT CLIP WHILE THE MENU IS OPEN.

    .filter-card is overflow: hidden !important (see the universal filter-card
    rule above), so that a child's square corner cannot show through the card's
    rounded one. The menu hangs below the toolbar, which is at the card's
    bottom edge, so with that in force the panel was laid out, measured and
    painted -- 216x226, six buttons, white background, all of it correct -- and
    then cut off entirely. Measured, not guessed: the DOM said [open] and
    getBoundingClientRect gave a real box while the screen showed nothing.

    A control that opens and shows nothing reads as a control that is broken,
    so this is not cosmetic.

    Only while it is open, only on the card that holds it, and :has is already
    what the filter-card rule itself is written with -- this adds no
    requirement the stylesheet did not already have.
*/
.card:has(.fmt__more[open]) {
    overflow: visible !important;
}

/*  A menu, not a row of chips: each entry fills the width and its label starts
    at the same place as the one above it. */
.fmt__list .btn {
    width: 100%;
    justify-content: flex-start !important;
    white-space: nowrap;
    border-radius: calc(var(--radius-sm) - 4px) !important;
}

/* Active Filter Chips Strip */
.filter-chips-row {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 0.4rem 0.85rem;
    background: var(--surface);
    border-top: 1px solid var(--border-subtle);
    border-bottom: 1px solid var(--border-subtle);
    flex-wrap: wrap;
    font-size: var(--text-xs);
}

.filter-chips-title {
    font-weight: 700;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-size: 0.68rem;
    display: flex;
    align-items: center;
    gap: 0.35rem;
}

.filter-chips-list {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    flex-wrap: wrap;
}

.filter-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.15rem 0.5rem;
    border-radius: 9999px;
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    font-size: 0.72rem;
    color: var(--text);
}

.filter-chip__key {
    color: var(--text-muted);
    font-weight: 600;
}

.filter-chip__val {
    color: var(--text);
    font-weight: 500;
}

/*
    The filters a screen offers beyond the few that answer most questions.

    Their employee list carries twenty criteria and puts them in a sidebar you
    open. Twenty controls laid across a filter bar is a wall; four in view with
    the rest a click away is the same reach without it. The disclosure is a
    <details>, so it needs no script and a keyboard reaches it, and the view
    opens it whenever any filter inside is set — a filtered list must never
    hide what is filtering it.
*/
.filters__more {
    flex: 1 0 100%;
    margin-top: var(--space-2);
    border-top: 1px solid var(--border);
    padding-top: var(--space-3);
}

.filters__more > summary {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    width: fit-content;
    list-style: none;
    cursor: pointer;
    font-family: var(--font-sans-small);
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    color: var(--text-muted);
    user-select: none;
}

.filters__more > summary::-webkit-details-marker { display: none; }

/* A disclosure triangle of our own, so it turns rather than jumps. */
.filters__more > summary::before {
    content: "";
    width: 0;
    height: 0;
    border-left: 5px solid currentColor;
    border-top: 4px solid transparent;
    border-bottom: 4px solid transparent;
    transition: transform 120ms ease;
}

.filters__more[open] > summary::before { transform: rotate(90deg); }
.filters__more > summary:hover { color: var(--text); }
.filters__more > summary:focus-visible {
    outline: 2px solid rgb(var(--accent-rgb));
    outline-offset: 3px;
    border-radius: var(--radius-sm);
}

.filters__more .form-grid { margin-top: var(--space-3); }

/*
    THE UNSTYLED SEARCH BAR. Fleet's 21 Index screens and most of Production's
    render this exact shape -- a search box, a Search button, sometimes a
    Clear link, standing on its own above the results card rather than living
    inside one the way .filters does. .toolbar had never been given a rule of
    its own, so every one of those bars rendered as bare, unspaced <input>/
    <button> tags sitting flush against the page background: no card edge, no
    gap between it and the list below.

    Given the card's own visual language rather than inventing a second one:
    same background, border and radius as every other panel on the page, laid
    out as the same flex row .filters already uses for its own fields.
*/
.toolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5);
    margin-bottom: var(--space-5);
    background: var(--glass-panel);
    border: 1px solid var(--glass-line);
    border-radius: var(--radius-lg);
    box-shadow: var(--rim), var(--shadow-sm);
}
.toolbar .input { flex: 1 1 260px; min-width: 0; }

.search-input { position: relative; display: flex; align-items: center; }
.search-input svg {
    position: absolute;
    left: 11px;
    color: var(--text-subtle);
    pointer-events: none;
}
.search-input .input { padding-left: 34px; }

/* ------------------------------------------------------------------ Alerts */

/*  An alert a script shows and hides with the hidden attribute: display:flex
    below outranks the user agent's [hidden] rule, so without this every such
    alert was drawn from the start -- the Loans warning, the payroll group's
    empty error box, the sort page's "not saved yet". Same fix as
    .save-split__menu[hidden] and the menus above. */
.alert[hidden] { display: none !important; }

.alert {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-sm);
    border: 1px solid rgb(var(--alert-rgb, var(--info-rgb)) / 30%);
    background: rgb(var(--alert-rgb, var(--info-rgb)) / 10%);
    color: var(--text);
    font-size: var(--text-base);
}
/*
    A PARAGRAPH ALERT IS A SENTENCE, NOT AN ICON BESIDE A BODY.

    .alert above is a flex row because almost every one of them is an icon
    followed by its text -- 333 of them in the views, all <div>. Three are <p>,
    and a <p> holds a run of prose: its <code>, <strong> and <a> children then
    become flex ITEMS, each laid out as its own column with the gap between
    them, so the sentence is dealt out across the box in pieces. On the Global
    Options Fixed Assets tab that read:

        This company has no   SCM_ITEM_GROUPS_DETAIL   ), so there is ...
        fixed-asset item groups (   = 6                screen; this tab ...

    Blocking the paragraph puts the words back in order and cannot reach the
    icon layout, which is on <div>. Fixed at the rule rather than by wrapping
    the two affected paragraphs in a span, so the next <p class="alert">
    somebody writes does not have to know this.
*/
p.alert { display: block; }

.alert__icon { color: rgb(var(--alert-rgb, var(--info-rgb))); flex-shrink: 0; margin-top: 1px; }
.alert--success { --alert-rgb: var(--success-rgb); }
.alert--warning { --alert-rgb: var(--warning-rgb); }
.alert--danger  { --alert-rgb: var(--danger-rgb); }

/* -------------------------------------------------------------------- Tabs */

/*
    FOLDER TABS, ONE LOOK FOR EVERY STRIP (2026-10-27).

    The employee detail nav (.emp-nav, further down) was the one strip in the
    application drawn as tabs: a sunken bar, the open tab a raised folder in
    the colour of what it opens, an accent rule along its top, and feet that
    curve its sides into the panel below, so tab and panel read as one shape.
    Every other strip was something else -- an underline (.tabs), a pill
    sliding in a track (.form-tabs__nav), a legacy box (.rights-top-tabs), a
    bordered span (.jv-tabs) -- and the user asked for the employee detail's
    look wherever tabs are used.

    So the look is written ONCE, here, over each strip's own classes. What a
    strip is (links, or a tablist) and how it marks its open tab are unchanged:

      the bar    .tabs  .form-tabs__nav  .jv-tabs  .rights-top-tabs
      a tab      .tab   .form-tabs__tab  .jv-tab   .rights-top-tabs__btn

    TWO SHAPES, by what the strip sits on:

      joined      a card follows (each form-tabs panel's card, a .card right
                  after a .tabs, the rights panel). The bar loses its bottom
                  corners and the card its top border and corners; the open
                  tab is painted in the card's surface and stands 1px over
                  the bar's bottom edge, so the edge breaks exactly under it,
                  and its feet curve into the card's top edge.
      standalone  nothing card-shaped follows (a stat row, an alert). The bar
                  is a closed box and the open tab stands on its bottom edge;
                  a tab opening into bare page background reads as torn off.

    A strip INSIDE a card (a .card__head, or a .card__body) is joined to its
    own card: the card draws the outline, the bar only the edge under its tabs.

    --tabs-line is the card border colour, so the outline keeps one tone where
    bar meets card; the feet's radial-gradient needs it and --surface as exact
    colour stops.
*/
.tabs,
.form-tabs__nav,
.jv-tabs,
.rights-top-tabs {
    --tabs-line: var(--border);
    --tabs-foot: 8px;
    position: relative;
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 2px;
    margin: 0 0 var(--space-4);
    /*  Side padding wider than a foot, so the first and last tab's feet have
        room to draw. */
    padding: 8px calc(var(--tabs-foot) + 6px) 0;
    list-style: none;
    background:
        linear-gradient(180deg, rgb(255 255 255 / 35%) 0%, transparent 100%),
        var(--surface-sunken);
    border: 1px solid var(--tabs-line);
    border-radius: var(--radius-lg);
    box-shadow: none;
}
.rights-top-tabs > li { display: flex; }

/*
    Native <button> chrome is stripped on purpose: form-tabs and the rights
    strip are buttons, and the browser's grey system button would otherwise
    paint over the bar.
*/
.tab,
.form-tabs__tab,
.jv-tab,
.rights-top-tabs__btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
    height: calc(var(--control-h-sm) + 4px);
    margin: 0;
    padding: 0 16px;
    border: 1px solid transparent;
    border-bottom: 0;
    border-radius: var(--radius-md) var(--radius-md) 0 0;
    background: transparent;
    appearance: none;
    -webkit-appearance: none;
    color: var(--text-muted);
    font: inherit;
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-tight);
    line-height: 1.2;
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
    transition: color .15s ease, background .15s ease, border-color .15s ease;
}
.tab:hover,
.form-tabs__tab:hover,
.jv-tab:hover,
.rights-top-tabs__btn:hover {
    color: var(--text);
    background: rgb(var(--neutral-rgb) / 8%);
    text-decoration: none;
}
.tab:focus-visible,
.form-tabs__tab:focus-visible,
.jv-tab:focus-visible,
.rights-top-tabs__btn:focus-visible {
    outline: 2px solid var(--border-focus);
    outline-offset: -2px;
}
/*  A locked tab (the journal voucher's, with GL integration off) is stepped
    over by the keys and must not look reachable to the mouse either. */
.tab:disabled,
.form-tabs__tab:disabled,
.jv-tab:disabled,
.rights-top-tabs__btn:disabled {
    opacity: .45;
    cursor: not-allowed;
    background: transparent;
}
.form-tabs__tab svg,
.rights-top-tabs__btn svg {
    flex-shrink: 0;
    width: 15px;
    height: 15px;
    opacity: .65;
}

/*
    THE OPEN TAB, UNDER EITHER IDIOM -- and it has to be both, because these
    strips are used for both and a tab that marks itself the other way was
    rendering identically to its neighbours.

      aria-current="page"   a link to ANOTHER page.
      aria-selected="true"  a tab in a role="tablist" that switches panels on
                            the SAME page (Outstandings, the work roster, the
                            journal voucher, every form-tabs screen).

    A CLASS IS NOT A STATE HERE. Two screens once marked the live tab with
    `is-active`, a class no rule in this file matched, so the strip was inert:
    measured on Outstandings, the selected tab and the unselected one both
    painted rgb(82,95,117). Styling the ARIA attribute means the thing that
    makes a tab LOOK open is the thing that tells a screen reader it is.
    TabHighlightTests fails on a `.tab` carrying a bare is-active again.
*/
.tab[aria-current="page"],
.tab[aria-selected="true"],
.form-tabs__tab[aria-selected="true"],
.jv-tab[aria-current="page"],
.jv-tab[aria-selected="true"],
.rights-top-tabs__btn[aria-selected="true"] {
    z-index: 1;
    color: var(--accent);
    background: var(--surface);
    border-color: var(--tabs-line);
    box-shadow: inset 0 3px 0 var(--accent);
}
.form-tabs__tab[aria-selected="true"] svg,
.rights-top-tabs__btn[aria-selected="true"] svg {
    opacity: 1;
    color: var(--accent);
}

/* ---- joined: the strip sits on a card ---------------------------------- */

.form-tabs__nav,
.tabs:has(+ .card),
.tabs:has(~ .tabs-panel),
.rights-top-tabs {
    margin-bottom: 0;
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
}

/*  Inside a card the card draws the outline, so the bar keeps only the edge
    under its tabs -- and its top corners follow the card's when it is the
    first thing in it, or its sunken fill pokes out past them. */
.card__head > .jv-tabs,
.card__head > .tabs,
.card__body > .tabs {
    flex: 1 1 auto;
    margin: 0;
    border-width: 0 0 1px;
    border-radius: 0;
}
.card__head:has(> .jv-tabs:only-child),
.card__head:has(> .tabs:only-child) {
    padding: 0;
    border-bottom: 0;
}
.card > .card__head:first-child > .jv-tabs,
.card > .card__head:first-child > .tabs,
.card > .card__body:first-child > .tabs:first-child {
    border-top-left-radius: calc(var(--radius-lg) - 1px);
    border-top-right-radius: calc(var(--radius-lg) - 1px);
}
/*  A card holding nothing but the strip (Attendance exceptions) is a closed
    bar already: the card's own bottom border is the edge the tabs stand on. */
.card > .card__body:only-child > .tabs:only-child {
    border-width: 0;
    border-radius: calc(var(--radius-lg) - 1px);
}

/*  The open tab stands 1px over the bar's bottom edge, so the edge breaks
    under it and it opens into the card. */
:is(.form-tabs__nav, .tabs:has(+ .card), .tabs:has(~ .tabs-panel), .rights-top-tabs,
    .card__head > .jv-tabs, .card__head > .tabs, .card__body > .tabs:not(:only-child),
    .card__body:not(:last-child) > .tabs):not([data-tabs-fit="wrapped"])
    :is(.tab[aria-current="page"], .tab[aria-selected="true"],
        .form-tabs__tab[aria-selected="true"], .jv-tab[aria-current="page"],
        .jv-tab[aria-selected="true"], .rights-top-tabs__btn[aria-selected="true"]) {
    height: calc(var(--control-h-sm) + 5px);
    margin-bottom: -1px;
}
/*  The feet: inverted corners in the card's surface either side of the open
    tab's base, curving its sides into the card the way a browser tab does. */
:is(.form-tabs__nav, .tabs:has(+ .card), .tabs:has(~ .tabs-panel), .rights-top-tabs,
    .card__head > .jv-tabs, .card__head > .tabs, .card__body > .tabs:not(:only-child),
    .card__body:not(:last-child) > .tabs):not([data-tabs-fit="wrapped"])
    :is(.tab[aria-current="page"], .tab[aria-selected="true"],
        .form-tabs__tab[aria-selected="true"], .jv-tab[aria-current="page"],
        .jv-tab[aria-selected="true"], .rights-top-tabs__btn[aria-selected="true"])::before,
:is(.form-tabs__nav, .tabs:has(+ .card), .tabs:has(~ .tabs-panel), .rights-top-tabs,
    .card__head > .jv-tabs, .card__head > .tabs, .card__body > .tabs:not(:only-child),
    .card__body:not(:last-child) > .tabs):not([data-tabs-fit="wrapped"])
    :is(.tab[aria-current="page"], .tab[aria-selected="true"],
        .form-tabs__tab[aria-selected="true"], .jv-tab[aria-current="page"],
        .jv-tab[aria-selected="true"], .rights-top-tabs__btn[aria-selected="true"])::after {
    content: "";
    position: absolute;
    bottom: 0;
    width: var(--tabs-foot);
    height: var(--tabs-foot);
    pointer-events: none;
}
:is(.form-tabs__nav, .tabs:has(+ .card), .tabs:has(~ .tabs-panel), .rights-top-tabs,
    .card__head > .jv-tabs, .card__head > .tabs, .card__body > .tabs:not(:only-child),
    .card__body:not(:last-child) > .tabs):not([data-tabs-fit="wrapped"])
    :is(.tab[aria-current="page"], .tab[aria-selected="true"],
        .form-tabs__tab[aria-selected="true"], .jv-tab[aria-current="page"],
        .jv-tab[aria-selected="true"], .rights-top-tabs__btn[aria-selected="true"])::before {
    left: calc(-1 * var(--tabs-foot) - 1px);
    background: radial-gradient(circle at 0 0,
        transparent calc(var(--tabs-foot) - 1px),
        var(--tabs-line) calc(var(--tabs-foot) - 1px),
        var(--tabs-line) var(--tabs-foot),
        var(--surface) var(--tabs-foot));
}
:is(.form-tabs__nav, .tabs:has(+ .card), .tabs:has(~ .tabs-panel), .rights-top-tabs,
    .card__head > .jv-tabs, .card__head > .tabs, .card__body > .tabs:not(:only-child),
    .card__body:not(:last-child) > .tabs):not([data-tabs-fit="wrapped"])
    :is(.tab[aria-current="page"], .tab[aria-selected="true"],
        .form-tabs__tab[aria-selected="true"], .jv-tab[aria-current="page"],
        .jv-tab[aria-selected="true"], .rights-top-tabs__btn[aria-selected="true"])::after {
    right: calc(-1 * var(--tabs-foot) - 1px);
    background: radial-gradient(circle at 100% 0,
        transparent calc(var(--tabs-foot) - 1px),
        var(--tabs-line) calc(var(--tabs-foot) - 1px),
        var(--tabs-line) var(--tabs-foot),
        var(--surface) var(--tabs-foot));
}

/*  The card under a joined strip: no top border or corners, so the bar's
    bottom edge is its top edge. !important because .results-card and its
    :has() twins pin border and radius with !important, in two stylesheets. */
.form-tabs__nav ~ [data-tab-panel] > .card:first-child,
.form-tabs__nav ~ [data-tab-panel] > .visually-hidden:first-child + .card,
.form-tabs__nav ~ .card[data-tab-panel],
.tabs + .card,
.tabs-panel > .card:first-child,
.rights-top-tabs ~ .rights-top-panel {
    margin-top: 0 !important;
    border-color: var(--border) !important;
    border-top: 0 !important;
    border-top-left-radius: 0 !important;
    border-top-right-radius: 0 !important;
}
/*  A document screen stacks its parts in .doc-reading, a flex column with a
    gap, and no margin can close a gap -- so the panel under a strip there is
    pulled up by the gap itself. Only the shown panel is a flex item: a
    hidden one is display:none and takes no gap of its own. */
.doc-reading > .tabs ~ .tabs-panel,
.doc-reading > .tabs + .card {
    margin-top: calc(-1 * var(--space-4)) !important;
}
/*  And its head is the open tab's colour, so the tab runs straight on into
    it. hr.css paints every card head sunken, which under a white tab read
    as a grey band cutting the tab off from its page. */
:is(.form-tabs__nav ~ [data-tab-panel] > .card:first-child,
    .form-tabs__nav ~ [data-tab-panel] > .visually-hidden:first-child + .card,
    .form-tabs__nav ~ .card[data-tab-panel],
    .tabs + .card,
    .tabs-panel > .card:first-child) > .card__head:first-child {
    background: var(--surface);
}

/*  A strip that would wrap (measured by initTabStripFit in app.js) is made
    compact first; one that wraps even then is "wrapped", and the guards on
    the joined rules above stop its open tab reaching for a card it cannot
    touch from an upper row. */
[data-tabs-fit] { gap: 0; }
[data-tabs-fit] :is(.tab, .form-tabs__tab, .jv-tab, .rights-top-tabs__btn) {
    padding: 0 11px;
    font-size: var(--text-xs);
}

/*  Narrow screens: the bar scrolls sideways rather than wrapping, the way the
    employee detail's group row does. The 1px over the edge is clipped by the
    scroller, so there the open tab closes on the edge instead. */
@media (max-width: 640px) {
    .tabs,
    .form-tabs__nav,
    .jv-tabs,
    .rights-top-tabs {
        flex-wrap: nowrap;
        overflow-x: auto;
        scrollbar-width: thin;
    }
    .tab,
    .form-tabs__tab,
    .jv-tab,
    .rights-top-tabs__btn { padding: 0 12px; }
}

/*  Dark: no white specular on the bar. */
:root[data-theme="dark"] :is(.tabs, .form-tabs__nav, .jv-tabs, .rights-top-tabs) {
    background: var(--surface-sunken);
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) :is(.tabs, .form-tabs__nav, .jv-tabs, .rights-top-tabs) {
        background: var(--surface-sunken);
    }
}

/*
    A long form told as sections rather than one endless scroll.

    Six cards on the Hire screen ran to nine screens of scrolling before
    anyone reached Save, and nothing on the page said which section a field
    with an error belonged to after the page came back. This is a different
    component from .tabs above on purpose: those are links between pages,
    these switch panels of the SAME page, and the ARIA roles the two need are
    not the same.

    Built by initFormTabs() in app.js from plain stacked .card sections — see
    the markup contract there. A panel is hidden with the `hidden` attribute,
    which drops it from layout and from the accessibility tree but not from
    the form a submit collects, so what gets posted is exactly what a single
    long page would have posted. With JavaScript disabled no panel is ever
    hidden and the page is the plain stacked form it always was.
*/
/* A tab whose panel came back from the server holding an error. */
.form-tabs__dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgb(var(--danger-rgb));
    box-shadow: 0 0 0 2px rgb(var(--danger-rgb) / 18%);
    flex-shrink: 0;
}

.form-tabs__panel[hidden] { display: none; }

/*
    A PANEL WHOSE ONLY HEADING IS ITS TAB'S NAME. The generic setup form
    (Views/Setup/Edit.cshtml) gives each section one card, titled with the
    section -- which the tab strip already says, directly above it, so the
    card opened with the same word twice. Hidden only once the strip exists
    (role="tabpanel" is set by setUpFormTabs) and only on screen: without
    JavaScript, and in print, the sections are stacked and need their
    headings. The tab still reads its label from this title: textContent
    does not care about display.
*/
@media screen {
    .form-tabs--headless > [role="tabpanel"] > .card > .card__head { display: none; }
}
/*
    OPACITY ONLY. A translateY in this keyframe was the same mistake fixed
    on .card/.hero earlier: an element animating transform is a position:fixed
    containing block for as long as the animation runs, and a combobox opened
    on a freshly-switched tab -- Stages, right after Template -- placed its
    popover against the panel's mid-animation box instead of the viewport.
    The miscalculated position is a one-time getBoundingClientRect() call, so
    it does not correct itself once the animation ends; the list stays wrong
    for the rest of that popover's life, landing wherever the panel happened
    to be sitting when the 150ms animation was read.
*/
.form-tabs__panel { animation: form-tabs-in .15s ease; }
@keyframes form-tabs-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/*
    Save and Cancel stay reachable from every tab rather than living at the
    foot of whichever section happened to be listed last — sticky, so they
    never fall below the bottom of the viewport the way they did at the end
    of a nine-screen page.
*/
/*
    NOTE: the backdrop-filter below makes this bar a CONTAINING BLOCK for any
    position:fixed descendant, and a stacking context -- so a popover opened
    from inside it (a combobox list, a split-button menu) would be trapped
    here no matter what z-index it carries. Measured 2026-10-03 across the
    five views that use this class: it holds buttons only, 0 combos and
    0 selects, and all popovers on those pages hit-test clean. Keep it that
    way -- if this bar ever needs a dropdown, drop the backdrop-filter (as
    .badge, .combo__list, .main, .sidebar and .topbar already do) rather than
    raising the popover z-index, which cannot work.
*/
.form-tabs__actions {
    position: sticky;
    bottom: var(--space-3);
    z-index: 5;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-3);
    margin-top: var(--space-4);
    padding: var(--space-3) var(--space-4);
    background: var(--glass-panel);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid var(--glass-line);
    border-radius: var(--radius-lg);
    box-shadow: var(--rim), var(--shadow-md);
}
.form-tabs__actions .btn--primary {
    min-width: 8.5rem;
}

/*
    Page-level section switchers that used to be a wrap of small primary/ghost
    buttons (Employee detail and similar). Same visual language as form-tabs,
    but as links between server-rendered pages rather than in-page panels.
*/
.tab-strip {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    padding: 5px;
    margin-bottom: var(--space-4);
    background:
        linear-gradient(180deg, rgb(255 255 255 / 35%) 0%, transparent 100%),
        var(--surface-sunken);
    border: 1px solid var(--glass-line);
    border-radius: var(--radius-lg);
    box-shadow: inset 0 1px 0 rgb(255 255 255 / 40%), var(--shadow-xs);
}
.tab-strip__tab {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: calc(var(--control-h-sm) + 2px);
    padding: 0 12px;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-tight);
    white-space: nowrap;
    text-decoration: none;
    transition: color .15s ease, background .15s ease, border-color .15s ease, box-shadow .15s ease;
}
.tab-strip__tab:hover {
    color: var(--text);
    background: rgb(var(--neutral-rgb) / 8%);
    text-decoration: none;
}
.tab-strip__tab[aria-current="page"] {
    color: var(--accent);
    background: var(--surface);
    border-color: rgb(var(--accent-rgb) / 18%);
    box-shadow:
        inset 0 1px 0 rgb(255 255 255 / 70%),
        0 1px 2px rgb(var(--accent-rgb) / 10%),
        var(--shadow-sm);
}
.tab-strip__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    height: 1.15rem;
    padding: 0 5px;
    border-radius: var(--radius-pill);
    background: rgb(var(--neutral-rgb) / 12%);
    color: var(--text-muted);
    font-size: 11px;
    font-weight: var(--weight-semibold);
    font-variant-numeric: tabular-nums;
    line-height: 1;
}
.tab-strip__tab[aria-current="page"] .tab-strip__count {
    background: rgb(var(--accent-rgb) / 12%);
    color: var(--accent);
}

/* Light-only white speculars on the track and the selected pill. */
:root[data-theme="dark"] .tab-strip {
    background: var(--surface-sunken);
    box-shadow: inset 0 1px 0 rgb(255 255 255 / 4%), var(--shadow-xs);
}
:root[data-theme="dark"] .tab-strip__tab[aria-current="page"] {
    box-shadow:
        inset 0 1px 0 rgb(255 255 255 / 8%),
        0 1px 2px rgb(0 0 0 / 35%),
        var(--shadow-sm);
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .tab-strip {
        background: var(--surface-sunken);
        box-shadow: inset 0 1px 0 rgb(255 255 255 / 4%), var(--shadow-xs);
    }
    :root:not([data-theme="light"]) .tab-strip__tab[aria-current="page"] {
        box-shadow:
            inset 0 1px 0 rgb(255 255 255 / 8%),
            0 1px 2px rgb(0 0 0 / 35%),
            var(--shadow-sm);
    }
}

@media (prefers-reduced-motion: reduce) {
    .form-tabs__panel { animation: none; }
}

/*
    THE EMPLOYEE DETAIL NAV: six groups up top, one group's tabs below.

    Twenty-two flat tabs wrapped to three rows and read as a wall of nouns.
    Six groups of up to seven read as a page: the eye finds a group, then one
    of its items.

    THE TWO ROWS MUST READ AS ONE THING. The first cut had the active group on
    a white tab and its items on a white row, separated by the plinth's grey,
    and in use the items looked like a second, unrelated nav bar. So now:

      * the items row is a PANEL painted in --emp-nav-panel, an accent wash
        over the surface, and the active group tab is painted in the SAME
        colour -- tab and panel are one shape, the way a browser tab and its
        page are;
      * the line under the group row is the panel's own accent edge, and the
        active tab sits 1px over it so the edge breaks exactly under the tab;
      * the tab grows "feet" (::before / ::after) -- inverted corners in the
        panel colour that curve the tab's sides into the panel's top edge.

    NO GROUP LABEL IN THE PANEL. A "PEOPLE ›" lead-in was tried and taken out
    at the user's request: the tab directly above already names the group, so
    the label only said it twice.

    --emp-nav-panel is a single solid colour (color-mix, not a translucent
    overlay) because the feet's radial-gradient needs one colour stop that
    matches the tab and the panel to the pixel.
*/
.emp-nav {
    --emp-nav-panel: color-mix(in srgb, var(--accent) 7%, var(--surface));
    --emp-nav-edge: color-mix(in srgb, var(--accent) 30%, var(--glass-line));
    --emp-nav-foot: 10px;
    display: flex;
    flex-direction: column;
    margin-bottom: var(--space-4);
    border: 1px solid var(--glass-line);
    border-radius: var(--radius-lg);
    overflow: hidden;
    background: var(--surface-sunken);
    box-shadow: var(--shadow-xs);
}

.emp-nav__groups {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 2px;
    /*  Side padding at least as wide as a foot, so the first and last tab's
        inverted corners have room to draw. */
    padding: 8px calc(var(--emp-nav-foot) + 4px) 0;
    background:
        linear-gradient(180deg, rgb(255 255 255 / 35%) 0%, transparent 100%),
        var(--surface-sunken);
    border-bottom: 1px solid var(--emp-nav-edge);
}
.emp-nav__group {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: calc(var(--control-h-sm) + 4px);
    padding: 0 18px;
    border: 1px solid transparent;
    border-bottom: 0;
    border-radius: var(--radius-md) var(--radius-md) 0 0;
    background: transparent;
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-tight);
    white-space: nowrap;
    text-decoration: none;
    position: relative;
    transition: color .15s ease, background .15s ease, border-color .15s ease;
}
.emp-nav__group:hover {
    color: var(--text);
    background: rgb(var(--neutral-rgb) / 8%);
    text-decoration: none;
}
.emp-nav__group[aria-current="page"] {
    z-index: 1;
    color: var(--accent);
    background: var(--emp-nav-panel);
    border-color: var(--emp-nav-edge);
    /*  1px over the group row's bottom edge, so the edge breaks under the tab
        and tab and panel become one surface. */
    margin-bottom: -1px;
    height: calc(var(--control-h-sm) + 5px);
    box-shadow: inset 0 3px 0 var(--accent);
}
/*  The feet: inverted corners in the panel colour either side of the active
    tab's base, curving its sides into the panel the way a browser tab does. */
.emp-nav__group[aria-current="page"]::before,
.emp-nav__group[aria-current="page"]::after {
    content: "";
    position: absolute;
    bottom: 0;
    width: var(--emp-nav-foot);
    height: var(--emp-nav-foot);
    pointer-events: none;
}
.emp-nav__group[aria-current="page"]::before {
    left: calc(-1 * var(--emp-nav-foot) - 1px);
    background: radial-gradient(circle at 0 0,
        transparent calc(var(--emp-nav-foot) - 1px),
        var(--emp-nav-edge) calc(var(--emp-nav-foot) - 1px),
        var(--emp-nav-edge) var(--emp-nav-foot),
        var(--emp-nav-panel) var(--emp-nav-foot));
}
.emp-nav__group[aria-current="page"]::after {
    right: calc(-1 * var(--emp-nav-foot) - 1px);
    background: radial-gradient(circle at 100% 0,
        transparent calc(var(--emp-nav-foot) - 1px),
        var(--emp-nav-edge) calc(var(--emp-nav-foot) - 1px),
        var(--emp-nav-edge) var(--emp-nav-foot),
        var(--emp-nav-panel) var(--emp-nav-foot));
}

.emp-nav__items {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    padding: 10px 12px;
    background: var(--emp-nav-panel);
}
.emp-nav__item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: var(--control-h-sm);
    padding: 0 12px;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-tight);
    white-space: nowrap;
    text-decoration: none;
    transition: color .15s ease, background .15s ease, border-color .15s ease, box-shadow .15s ease;
}
.emp-nav__item:hover {
    color: var(--text);
    background: var(--surface);
    border-color: var(--glass-line);
    text-decoration: none;
}
.emp-nav__item[aria-current="page"] {
    color: var(--accent);
    background: var(--surface);
    border-color: color-mix(in srgb, var(--accent) 35%, transparent);
    box-shadow: var(--shadow-xs);
}
.emp-nav__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    height: 1.15rem;
    padding: 0 5px;
    border-radius: var(--radius-pill);
    background: rgb(var(--neutral-rgb) / 12%);
    color: var(--text-muted);
    font-size: 11px;
    font-weight: var(--weight-semibold);
    font-variant-numeric: tabular-nums;
    line-height: 1;
}
.emp-nav__item[aria-current="page"] .emp-nav__count {
    background: rgb(var(--accent-rgb) / 14%);
    color: var(--accent);
}

/*
    THE THIRD LEVEL: the active sub-tab opens into the card below.

    Group tab -> panel was joined, but the selected sub-tab was still a pill
    floating in the panel with a gap and the card's own border under it, so
    "Bank accounts" did not read as the heading of the form beneath it. With
    .emp-nav--joined the nav sits flush on its card (.emp-nav-body):

      * the nav loses its bottom corners and margin, the card its top border
        and top corners -- one outline from group row to card foot;
      * the panel's bottom edge is drawn in the card's border colour, so it
        reads as the card's top edge;
      * the active sub-tab grows down through the panel's bottom padding and
        1px over that edge, in the card's surface colour, with feet curving
        into it -- the same shape the group tab makes with the panel.

    The stem height (--emp-nav-stem) is the panel's bottom padding plus the
    1px edge; change one and the other must follow.
*/
.emp-nav--joined {
    --emp-nav-stem: 11px;
    --emp-nav-foot-sm: 7px;
    margin-bottom: 0;
    /*  The card's border colour, so the outline does not change tone at the
        seam where nav meets card. */
    border-color: var(--border);
    border-bottom: 0;
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
    box-shadow: none;
}
.emp-nav--joined .emp-nav__items {
    padding-bottom: calc(var(--emp-nav-stem) - 1px);
    border-bottom: 1px solid var(--border);
}
.emp-nav--joined .emp-nav__item[aria-current="page"] {
    position: relative;
    z-index: 1;
    height: calc(var(--control-h-sm) + var(--emp-nav-stem));
    margin-bottom: calc(-1 * var(--emp-nav-stem));
    padding-bottom: var(--emp-nav-stem);
    border-color: var(--border);
    border-bottom: 0;
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    background: var(--surface);
    box-shadow: inset 0 2px 0 var(--accent);
}
.emp-nav--joined .emp-nav__item[aria-current="page"]::before,
.emp-nav--joined .emp-nav__item[aria-current="page"]::after {
    content: "";
    position: absolute;
    bottom: 0;
    width: var(--emp-nav-foot-sm);
    height: var(--emp-nav-foot-sm);
    pointer-events: none;
}
.emp-nav--joined .emp-nav__item[aria-current="page"]::before {
    left: calc(-1 * var(--emp-nav-foot-sm) - 1px);
    background: radial-gradient(circle at 0 0,
        transparent calc(var(--emp-nav-foot-sm) - 1px),
        var(--border) calc(var(--emp-nav-foot-sm) - 1px),
        var(--border) var(--emp-nav-foot-sm),
        var(--surface) var(--emp-nav-foot-sm));
}
.emp-nav--joined .emp-nav__item[aria-current="page"]::after {
    right: calc(-1 * var(--emp-nav-foot-sm) - 1px);
    background: radial-gradient(circle at 100% 0,
        transparent calc(var(--emp-nav-foot-sm) - 1px),
        var(--border) calc(var(--emp-nav-foot-sm) - 1px),
        var(--border) var(--emp-nav-foot-sm),
        var(--surface) var(--emp-nav-foot-sm));
}
/*  !important AND two classes: .results-card pins border, radius and
    background with !important, and a later stylesheet repeats that rule, so
    a single-class selector here lost on load order. */
.emp-nav--joined + .emp-nav-body {
    margin-top: 0 !important;
    border-top: 0 !important;
    border-top-left-radius: 0 !important;
    border-top-right-radius: 0 !important;
    background: var(--surface) !important;
}

/*  Where the sub-tabs can wrap to a second row, a stem from the first row
    would run through the second, so narrow screens keep the pill. */
@media (max-width: 900px) {
    .emp-nav--joined .emp-nav__item[aria-current="page"] {
        height: var(--control-h-sm);
        margin-bottom: 0;
        padding-bottom: 0;
        border-bottom: 1px solid var(--border);
        border-radius: var(--radius-sm);
    }
    .emp-nav--joined .emp-nav__item[aria-current="page"]::before,
    .emp-nav--joined .emp-nav__item[aria-current="page"]::after { content: none; }
}

/*  Dark: a stronger wash, or the panel is indistinguishable from the plinth;
    no white specular on the groups row. */
:root[data-theme="dark"] .emp-nav {
    --emp-nav-panel: color-mix(in srgb, var(--accent) 12%, var(--surface));
    --emp-nav-edge: color-mix(in srgb, var(--accent) 40%, var(--glass-line));
}
:root[data-theme="dark"] .emp-nav__groups { background: var(--surface-sunken); }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .emp-nav {
        --emp-nav-panel: color-mix(in srgb, var(--accent) 12%, var(--surface));
        --emp-nav-edge: color-mix(in srgb, var(--accent) 40%, var(--glass-line));
    }
    :root:not([data-theme="light"]) .emp-nav__groups { background: var(--surface-sunken); }
}

/*  Narrow viewports: the group row scrolls horizontally rather than wrapping,
    since the whole point of the two-level grouping is "the eye takes it in at
    once". On a phone it is a thumb-swipe strip instead. */
@media (max-width: 640px) {
    .emp-nav__groups {
        flex-wrap: nowrap;
        overflow-x: auto;
        scrollbar-width: thin;
    }
    .emp-nav__group { flex: 0 0 auto; padding: 0 12px; }
}

/* ------------------------------------------------------------------ Layout */

.grid { display: grid; gap: var(--space-4); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.grid--sidebar { grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr); }

/*
    MASTER AND DETAIL, THE RIGHT WAY ROUND.

    .grid--sidebar's wide track is its FIRST, which is right when the page's
    own content leads and an aside sits beside it. General types and List
    options are the other shape: a list of things to pick, then the entries of
    whichever was picked. Both were built on .grid--sidebar and then swapped
    with `order`, which moves the CHILDREN but not the TRACKS -- so the picker,
    which is a column of short names, got the 2fr track, and a five-column
    table of entries got the 1fr one.

    Measured on General types at a 1100px viewport: the entries table was left
    with 425px, giving its Name column 92px, which is what wrapped "Five Year
    Award" onto two lines and made the row collision above visible at all.

    So the tracks are narrow-then-wide here and the views keep their `order`.
*/
.grid--master-detail { grid-template-columns: minmax(220px, 1fr) minmax(0, 1fr); }

@media (max-width: 1100px) {
    .grid--sidebar,
    .grid--master-detail { grid-template-columns: minmax(0, 1fr); }
}

.stack { display: flex; flex-direction: column; gap: var(--space-4); }
.row { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.row--tight { gap: var(--space-2); }

/*  Used on two Global Options panels and defined nowhere, so the row it
    names sat left-packed instead of pushing its ends apart. The name says
    exactly one thing and .row is already flex, so this is what it meant. */
.row--between { justify-content: space-between; }

/*
    A row that must stay on one line. Twenty-two rows said so in a style
    attribute; .row--tight itself keeps wrapping, because most rows should.
*/
.row--nowrap { flex-wrap: nowrap; }

/*
    ROW ACTIONS STAY ON ONE LINE.

    A .row wraps by default, which is right for a filter bar and wrong for
    the two or three buttons at the end of a table row: the actions column is
    the narrowest in the table, so Edit and Delete stacked one above the other
    and made every row twice as tall. It only became visible when those
    buttons became icons -- as words they were wide enough to force the column
    open.

    Measured on General types: the cell was 56px and the two 32px buttons need
    72 with the gap. nowrap also fixes the WIDTH, because the cell's
    min-content is then the sum rather than the widest single button, so the
    table gives the column the room instead of squeezing it.

    Scoped to a row that is a table cell's own child, so a .row nested inside
    a cell for some other purpose still wraps.
*/
.table td > .row { flex-wrap: nowrap; }

.push-right { margin-left: auto; }

.muted { color: var(--text-muted); }
.subtle { color: var(--text-muted); }
.num { font-variant-numeric: tabular-nums; }

/*
    A number reads from its last digit, so a box holding one is right-aligned.
    The table cell has always been; the input was not, and ten forms said so in
    a style attribute instead.
*/
.input.num,
.table td.num .input,
.cell-strong.num { text-align: right; }
.nowrap { white-space: nowrap; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 14rem; }

.approval-actions { display: inline-flex; align-items: center; gap: 2px; flex-wrap: nowrap; }
.approval-actions__form { display: inline; margin: 0; }
.btn--icon { min-width: 2rem; padding-inline: var(--space-2); }

a.table-sort { color: inherit; text-decoration: none; font-weight: inherit; }
a.table-sort:hover { color: var(--accent); text-decoration: underline; }

.text-success { color: var(--success); }
.text-danger { color: var(--danger); }
.text-warning { color: var(--warning); }

/* Legacy Approval Center — Forward Document dialog */
.fwd-backdrop {
    min-height: calc(60vh / var(--zoom, 1));
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: var(--space-6) var(--space-4);
}
.fwd-dialog {
    width: min(640px, 100%);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md, 8px);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.18);
}
.fwd-dialog__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--border);
}
.fwd-dialog__title {
    margin: 0;
    font-size: var(--text-lg);
    font-weight: var(--weight-strong);
}
.fwd-dialog__close {
    display: inline-flex;
    color: var(--text-muted);
    padding: var(--space-1);
}
.fwd-dialog__close:hover { color: var(--text); }
.fwd-dialog__body { padding: var(--space-4); }
.fwd-dialog__meta { margin: 0 0 var(--space-4); font-size: var(--text-sm); }
.fwd-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-4);
}
@media (max-width: 560px) {
    .fwd-grid { grid-template-columns: 1fr; }
}
.fwd-dialog__foot {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-2);
    margin-top: var(--space-5);
    padding-top: var(--space-4);
    border-top: 1px solid var(--border);
}
.fwd-dialog .input[readonly] {
    background: var(--surface-sunken);
    color: var(--text-muted);
}

.rights-report-toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-3);
    flex-wrap: wrap;
}
.rights-report-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: var(--space-2);
    padding: var(--space-3);
}
.rights-report-item {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--space-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm, 4px);
    cursor: pointer;
}
.rights-report-item input { margin-right: var(--space-2); align-self: flex-start; }
.rights-report-item__title { font-weight: var(--weight-strong); font-size: var(--text-sm); }
.rights-report-item__code { font-size: var(--text-xs); }

/* ============================================================== Data charts */

/*
    The bar chart stretches horizontally (preserveAspectRatio="none") but must
    not scale its height with the container width, or a wide screen turns a
    150-unit viewBox into a several-hundred-pixel-tall chart.
*/
.chart { display: block; width: 100%; height: 170px; overflow: visible; }

/*
    The grid recedes to the subtle rule. At --border it was the same weight as
    the bars' own edges and the chart read as a grid with bars in it rather than
    as bars with a grid behind them.
*/
.chart__grid { stroke: var(--chart-grid); stroke-width: 1; }
.chart__axis { fill: var(--text-muted); font-size: 10px; }

/*
    Rounded tops. rx on a rect is scaled by preserveAspectRatio="none" along with
    everything else, so a fixed rx would stretch into a wide ellipse on a broad
    screen; 2 is small enough to read as a corner at every width this renders at.
*/
/* The ramp --chart-2 … --chart-6 is declared in both themes and referenced
   nowhere, because this hard-coded --chart-1. One custom property makes it
   reachable from a view — style="--bar: var(--chart-3)" — with no markup
   change, and the fallback keeps every existing chart exactly as it is. */
.chart__bar {
    fill: var(--bar, var(--chart-1));
    rx: 2;
    transition: opacity var(--duration-fast) var(--ease),
                fill var(--duration-fast) var(--ease);
}
.chart__bar:hover { fill: var(--accent-strong); opacity: 1; }

/* Horizontal breakdown bars used by the dashboard distribution panels. */
.meter { display: grid; gap: var(--space-3); }
.meter__row { display: grid; grid-template-columns: minmax(90px, 1fr) 2.2fr auto; gap: var(--space-3); align-items: center; }
.meter__label { font-size: var(--text-sm); color: var(--text-muted); }
/*
    Both of these are spans so they can sit inside a table cell. Width and height
    do not apply to inline elements, so they are blockified explicitly rather than
    relying on being a grid or flex item — which is what made the fill collapse to
    zero width when the same markup was reused inside a table row.
*/
.meter__track {
    display: block;
    height: 7px;
    border-radius: var(--radius-pill);
    background: var(--surface-sunken);
    overflow: hidden;
}
.meter__fill {
    display: block;
    height: 100%;
    border-radius: var(--radius-pill);
    background: var(--meter-color, var(--accent));
}
.meter__value { font-size: var(--text-sm); font-variant-numeric: tabular-nums; font-weight: 600; min-width: 32px; text-align: right; }

/* ========================================================= Navigation bar */

/*
    A progress bar across the top of the window while the next page is being
    built.

    This is a server-rendered application over a database holding eight hundred
    thousand attendance rows and forty thousand ledger lines; a report or the
    attendance register can take several seconds. Until now the browser simply
    sat on the old page with no indication that anything was happening, which
    is indistinguishable from a click that did not register — so people click
    again, and the slow query runs twice.

    It never reaches 100% on its own: it eases towards 90% and waits, because
    it cannot know how long the server will take, and a bar that completes
    while nothing has happened is a lie. The new document landing is what
    finishes it.
*/
.navbar-progress {
    position: fixed;
    inset: 0 auto auto 0;
    z-index: 200;
    width: 100%;
    height: 2px;
    background: none;
    pointer-events: none;
    opacity: 0;
    transition: opacity var(--duration) var(--ease);
}
.navbar-progress[data-active] { opacity: 1; }
.navbar-progress__bar {
    height: 100%;
    width: 0;
    background: linear-gradient(90deg,
        rgb(var(--accent-rgb) / 55%), rgb(var(--accent-rgb)));
    box-shadow: 0 0 8px rgb(var(--accent-rgb) / 55%);
    border-radius: 0 var(--radius-pill) var(--radius-pill) 0;
    transition: width 400ms var(--ease-out);
}

/* ======================================================= Command palette */

/* A key cap, used by the trigger and inside the palette. */
.kbd {
    display: inline-flex;
    align-items: center;
    padding: 1px 6px;
    border: 1px solid var(--border);
    border-bottom-width: 2px;
    border-radius: var(--radius-xs);
    background: var(--surface);
    color: var(--text-muted);
    font-family: inherit;
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    line-height: 1.6;
    white-space: nowrap;
}

.cmdk-trigger {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    height: 34px;
    min-width: 180px;
    padding: 0 10px 0 12px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--surface);
    color: var(--text-muted);
    font-size: var(--text-sm);
    cursor: pointer;
    flex-shrink: 0;
    transition: background var(--duration-fast) var(--ease),
                border-color var(--duration-fast) var(--ease);
}
.cmdk-trigger:hover { background: var(--surface-hover); border-color: var(--border-strong); color: var(--text-muted); }
.cmdk-trigger__text { margin-right: auto; }

/* Below the wide breakpoint the words go and the magnifier stands alone. */
@media (max-width: 1100px) {
    .cmdk-trigger { min-width: 34px; width: 34px; padding: 0; justify-content: center; }
    .cmdk-trigger__text, .cmdk-trigger .kbd { display: none; }
    .user-chip__meta { display: none; }
    .user-chip { padding: 0; width: 34px; justify-content: center; border-radius: 10px; }
}

.cmdk {
    width: min(620px, calc(100vw - var(--space-8)));
    max-height: min((60vh / var(--zoom, 1)), 520px);
    padding: 0;
    border: 1px solid var(--glass-line);
    border-radius: var(--radius-xl);
    /*
        The heaviest material in the application and the place it costs least:
        nothing dense on it, on screen for a second at a time, and the whole
        page behind it is exactly what you want to still see while you jump
        somewhere else.
    */
    background:
        linear-gradient(160deg,
            rgb(255 255 255 / 26%) 0%,
            rgb(255 255 255 / 5%) 40%,
            transparent 70%),
        var(--glass-panel);
    -webkit-backdrop-filter: var(--lens-over);
    backdrop-filter: var(--lens-over);
    color: var(--text);
    box-shadow: var(--rim), var(--shadow-lg);
    overflow: hidden;
    /*
        Sat high rather than centred. A dialog pinned to the middle of the
        viewport jumps as its list grows and shrinks under the filter; anchoring
        the top keeps the input still while the results move beneath it.
    */
    margin: calc(12vh / var(--zoom, 1)) auto auto;
}
/* The backdrop is part of the material: a hard scrim says "a box appeared", a
   shallow lens says "you are looking through something". */
.cmdk::backdrop {
    background: var(--overlay);
    -webkit-backdrop-filter: blur(6px) var(--lens-tone);
    backdrop-filter: blur(6px) var(--lens-tone);
}

/* ------------------------------------------------------------ Attach Document popup
   Legacy parity: toolbar button opens a dialog with the shared attachment list
   + upload form. Kept outside the document <form> (no nested forms). */
.attach-dialog {
    border: none;
    padding: 0;
    max-width: min(720px, calc(100vw - 2rem));
    width: 100%;
    background: transparent;
    color: var(--text);
}
.attach-dialog::backdrop {
    background: var(--overlay);
    -webkit-backdrop-filter: blur(4px) var(--lens-tone);
    backdrop-filter: blur(4px) var(--lens-tone);
}
.attach-dialog__panel {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg, 12px);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
    max-height: min((85vh / var(--zoom, 1)), 900px);
    display: flex;
    flex-direction: column;
}
.attach-dialog__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--border-subtle);
}
.attach-dialog__title {
    margin: 0;
    font-size: var(--text-lg);
    font-weight: 600;
}
.attach-dialog__heading { min-width: 0; }
.attach-dialog__doc {
    margin: 2px 0 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.attach-dialog__doc:empty { display: none; }
.attach-dialog__title { display: flex; align-items: center; gap: var(--space-2); }
.attach-dialog__body {
    padding: var(--space-4);
    overflow: auto;
}
/* The file dialog: wider, the list and form edge to edge, one scroller. */
.attach-dialog--files { max-width: min(860px, calc(100vw - 2rem)); }
.attach-dialog--files .attach-dialog__body {
    padding: 0;
    overflow-x: hidden;
    flex: 1 1 auto;
    min-height: 0;
}
/* The panel is named in the dialog head; its own heading serves the inline (tab) use. */
.attach-dialog--files .attach__bar { display: none; }
.doc-attach {
    display: inline-flex;
    align-items: center;
}

.cmdk__search {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 0 var(--space-4);
    height: 52px;
    border-bottom: 1px solid var(--border-subtle);
    color: var(--text-subtle);
}
.cmdk__input {
    flex: 1;
    min-width: 0;
    height: 100%;
    border: 0;
    background: none;
    color: var(--text);
    font-family: inherit;
    font-size: var(--text-md);
}
.cmdk__input:focus { outline: none; }
.cmdk__input::placeholder { color: var(--text-subtle); }

.cmdk__list {
    max-height: calc(min((60vh / var(--zoom, 1)), 520px) - 52px);
    overflow-y: auto;
    padding: var(--space-2);
    margin: 0;
    list-style: none;
}
.cmdk__item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 0 var(--space-3);
    height: var(--row-h);
    border-radius: var(--radius-sm);
    cursor: pointer;
    scroll-margin: var(--space-2);
}
/*
    A rule of our own beats the browser's [hidden], which is only
    "display: none" at user-agent weight and so loses to the "display: flex"
    above. Without this the filter computed the right matches and highlighted
    the right row while leaving every other row on screen.
*/
.cmdk__item[hidden] { display: none; }
/*
    The highlight follows the keyboard, not the pointer — moving the mouse moves
    the selection, so there is only ever one highlighted row and Enter always
    takes the one you can see.
*/
.cmdk__item[aria-selected="true"] { background: var(--surface-selected); }
.cmdk__icon { display: grid; place-items: center; color: var(--text-subtle); flex: none; }
.cmdk__item[aria-selected="true"] .cmdk__icon { color: var(--accent); }
.cmdk__text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmdk__hit { font-weight: 700; color: var(--text); }
.cmdk__group {
    margin-left: auto;
    padding-left: var(--space-3);
    color: var(--text-muted);
    font-size: var(--text-xs);
    white-space: nowrap;
}
.cmdk__empty {
    padding: var(--space-6);
    text-align: center;
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* ================================================================ Sign-in */

/*
    The sign-in page is the one screen with nothing else on it, so it is where
    the material can be used without reservation — no data to keep legible, and
    a single card to float.

    Its ground carries the same four blooms as the application's, at full
    strength: this is the first thing anyone sees, and it should say what the
    rest of the interface is going to look like.
*/
.auth {
    display: grid;
    place-items: center;
    min-height: calc(100vh / var(--zoom, 1));
    padding: var(--space-6);
    /* The same sweep, one step stronger. It is the first thing anyone sees and
       it should say what the rest is going to look like. */
    background:
        linear-gradient(118deg,
            rgb(255 255 255 / 46%) 0%,
            rgb(255 255 255 / 0%) 36%,
            rgb(255 255 255 / 34%) 100%),
        radial-gradient(1000px 560px at 10% -12%, rgb(var(--accent-rgb) / 30%), transparent 58%),
        radial-gradient(900px 540px at 104% -6%, rgb(var(--bloom-2-rgb) / 26%), transparent 56%),
        radial-gradient(950px 640px at 40% 114%, rgb(var(--bloom-3-rgb) / 22%), transparent 58%),
        radial-gradient(760px 460px at 92% 96%, rgb(var(--bloom-4-rgb) / 14%), transparent 56%),
        var(--surface-app);
    background-attachment: fixed;
}
.auth__card {
    width: min(410px, 100%);
    padding: var(--space-8);
    background:
        linear-gradient(150deg,
            rgb(255 255 255 / 26%) 0%,
            rgb(255 255 255 / 6%) 38%,
            transparent 66%),
        var(--glass);
    -webkit-backdrop-filter: var(--blur-strong);
    backdrop-filter: var(--blur-strong);
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-xl);
    box-shadow: var(--rim), var(--shadow-lg);
}
:root[data-theme="dark"] .auth__card {
    background:
        linear-gradient(150deg,
            rgb(255 255 255 / 10%) 0%,
            rgb(255 255 255 / 2%) 38%,
            transparent 66%),
        var(--glass);
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .auth__card {
        background:
            linear-gradient(150deg,
                rgb(255 255 255 / 10%) 0%,
                rgb(255 255 255 / 2%) 38%,
                transparent 66%),
            var(--glass);
    }
}
.auth__brand { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-6); }
.auth__title { font-size: var(--text-xl); }
.auth__sub { margin-top: 2px; color: var(--text-muted); }
.auth__hint {
    margin-top: var(--space-5);
    padding: var(--space-3);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface-sunken);
    font-size: var(--text-sm);
    color: var(--text-muted);
}
.auth__hint code {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--text);
}

/* ========================================================== Floating shell */

/*
    Above the rail breakpoint the shell separates into two panes floating on the
    ground, with the page's colour visible around and between them.

    This is what makes the material read. Flush to the window edges there is
    nothing behind the glass but more glass, so the translucency has nothing to
    do and the whole thing reads as a flat white app with a blur declaration on
    it. Set in from the edges, the ground's colour runs round both panes and the
    layering becomes the point.

    Scoped to desktop deliberately: below 900px the rail is a fixed overlay that
    slides in over the content, and a floating inset pane would fight it.

    No overflow:hidden on .main, however much it would simplify the corners —
    that makes it a scroll container and breaks position:sticky on the topbar
    inside it. The topbar rounds its own top corners to match instead.
*/
@media (min-width: 901px) {
    .app {
        padding: var(--space-3);
        gap: var(--space-3);
    }

    .sidebar {
        top: var(--space-3);
        height: calc((100vh / var(--zoom, 1)) - var(--space-6));
        border: 1px solid var(--sidebar-border);
        border-radius: var(--radius-xl);
    }

    /* The pane is glass, and it is ALSO not a filter. Same argument as the
       card, one level up: the only thing behind .main is the fixed ground, and
       a fixed ground does not change while the page scrolls. */
    .main {
        border: 1px solid var(--glass-line);
        border-radius: var(--radius-xl);
        background: var(--glass-pane);
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
        box-shadow: var(--rim), var(--shadow-md);
    }

    /*
        THE ONE PANE WITH DETAIL BEHIND IT. Rows really do pass under the
        topbar, so this is where a lens does work no fill can do — and it is
        the only element in the product that re-blurs while the page scrolls.
        It is 56px tall, so the per-frame region is roughly 1400x56, not a
        screenful.
    */
    .topbar {
        border-radius: calc(var(--radius-xl) - 1px) calc(var(--radius-xl) - 1px) 0 0;
        background: var(--topbar-fill, var(--glass-pane));
    }

    /*
        Cards and tiles keep their own fill on the pane.

        The rule that used to live here switched the material OFF indoors —
        `.main .card { background: var(--surface) }` — so above 901px, which is
        every machine this client owns, the desktop product had exactly one
        translucent element. The reason it gave (glass over glass compounds,
        and the blur cost is paid twice) is answered instead by neither of them
        carrying a filter at all.
    */
}

/* =============================================================== Responsive */

@media (max-width: 900px) {
    .app { grid-template-columns: minmax(0, 1fr); }

    /* Below the breakpoint the rail overlays real content, so THERE it is a
       lens — this is the one place in the product where the rail has anything
       behind it worth bending. */
    .sidebar {
        position: fixed;
        inset: 0 auto 0 0;
        z-index: 60;
        width: var(--sidebar-width);
        translate: -100% 0;
        transition: translate var(--duration) var(--ease);
        -webkit-backdrop-filter: var(--lens-rail);
        backdrop-filter: var(--lens-rail);
    }
    .app[data-nav="open"] .sidebar { translate: 0 0; box-shadow: var(--shadow-lg); }

    .topbar { background: var(--topbar-fill, var(--glass-panel)); }

    .app[data-nav="open"] .scrim {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 50;
        /* The one place a token already existed and was not used. */
        background: var(--overlay);
        -webkit-backdrop-filter: blur(3px);
        backdrop-filter: blur(3px);
    }

    .content { padding: var(--space-5) var(--space-4) var(--space-8); }
    .user-chip__meta { display: none; }
    .grid--sidebar,
    .grid--master-detail { grid-template-columns: minmax(0, 1fr); }

    /*
        The page's actions drop below its title rather than sharing the line.
        .page-head is a wrapping flex row, and on a narrow screen the title
        keeps just enough width to stay on the line with two buttons beside it
        — so "Vouchers" was squeezed to a third of the width while "General
        ledger" and "Trial balance" took the rest.
    */
    .page-head__title-row {
        flex-direction: column;
        align-items: stretch;
    }
    .page-head__title-row .page-head__actions,
    .page-head__actions {
        width: 100%;
        justify-content: flex-start;
    }

    /* Filters go one per line; three controls abreast at 375px are unusable. */
    .filters { padding: var(--space-4); }
    .filters .field,
    .filters .field--search { flex: 1 1 100%; min-width: 0; }
    .filters__actions { width: 100%; }
    .filters__actions .btn { flex: 1; }
}

/* ============================================================= Muster roll */

/*
    A month grid: employees down, days across. The name column is sticky so it
    stays readable while the day columns scroll horizontally, which is the only
    way a 31-column grid is usable on a laptop.
*/
.muster { border-collapse: separate; border-spacing: 0; font-size: var(--text-xs); }

.muster th,
.muster td { border-bottom: 1px solid var(--border); padding: 4px 6px; }

.muster thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--surface-sunken);
    font-weight: var(--weight-semibold);
    text-align: center;
    white-space: nowrap;
}

.muster__name {
    position: sticky;
    left: 0;
    z-index: 3;
    min-width: 190px;
    background: var(--surface);
    text-align: left !important;
    border-right: 1px solid var(--border);
}
.muster thead .muster__name { z-index: 4; background: var(--surface-sunken); }
.muster tbody tr:hover .muster__name { background: var(--surface-hover); }

.muster__day {
    width: 30px;
    text-align: center;
    font-variant-numeric: tabular-nums;
}
.muster__day--weekend { background: rgb(var(--neutral-rgb) / 6%); }

/* One cell per day, coloured by what was recorded. */
.muster__cell {
    display: inline-block;
    min-width: 22px;
    padding: 1px 3px;
    border-radius: var(--radius-xs);
    font-weight: 600;
    line-height: 1.5;
}
.muster__cell--present  { background: rgb(var(--success-rgb) / 16%); color: rgb(var(--success-rgb)); }
.muster__cell--absent   { background: rgb(var(--danger-rgb) / 16%);  color: rgb(var(--danger-rgb)); }
.muster__cell--leave    { background: rgb(var(--warning-rgb) / 18%); color: rgb(var(--warning-rgb)); }
.muster__cell--holiday  { background: rgb(var(--info-rgb) / 14%);    color: rgb(var(--info-rgb)); }
.muster__cell--unmarked { color: var(--text-subtle); }

/* A split day (different halves) is narrower text but must stay readable. */
.muster__cell--split { font-size: 10px; letter-spacing: -0.02em; }

.muster__totals td {
    background: var(--surface-sunken);
    font-weight: var(--weight-semibold);
    border-top: 1px solid var(--border-strong);
}

/* ------------------------------------------------------------ Detail list

   A key/value list for the read-only side of a record. Rows collapse to a
   single column on narrow screens rather than squeezing the value.           */

.detail-list {
    margin: 0;
    display: grid;
    gap: var(--space-2);
}

.detail-list__row {
    display: grid;
    grid-template-columns: minmax(0, 11rem) minmax(0, 1fr);
    gap: var(--space-3);
    align-items: baseline;
    padding-bottom: var(--space-2);
    border-bottom: 1px solid var(--border);
}

.detail-list__row:last-child {
    padding-bottom: 0;
    border-bottom: none;
}

.detail-list dt {
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.detail-list dd {
    margin: 0;
    color: var(--text);
    font-weight: var(--weight-strong);
}

@media (max-width: 560px) {
    .detail-list__row { grid-template-columns: minmax(0, 1fr); gap: 0; }
}

/* ------------------------------------------------------------- Colour swatch

   Shifts and day markers carry a colour that the muster roll paints with, so
   the colour itself is part of identifying the row.                          */

.swatch {
    flex: none;
    width: 12px;
    height: 12px;
    border-radius: var(--radius-xs);
    /* The client colour comes out of HRM_ATTN_TYPES.COLOR_CODE, so the edge
       must not be a fixed black: on the dark theme a dark swatch had no edge
       at all. --glass-edge themes; rgba(0,0,0,.15) does not. */
    border: 1px solid var(--glass-edge);
    box-shadow: var(--rim-soft);
    display: inline-block;
}

.swatch--lg {
    width: 16px;
    height: 16px;
    vertical-align: baseline;
}

/* ---------------------------------------------------------------- Calendar

   A month grid: seven columns of equal width, cells that grow to fit the names
   in them. Below the breakpoint the grid keeps its seven columns and scrolls,
   because a calendar reflowed to one column stops being a calendar.           */

.calendar {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 1px;
    background: var(--border);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow: hidden;
}

.calendar__head {
    background: var(--surface-sunken);
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.calendar__cell {
    background: var(--surface);
    min-height: 96px;
    padding: var(--space-2);
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.calendar__cell--blank { background: var(--surface-sunken); }
.calendar__cell--closed { background: var(--surface-sunken); }
.calendar__cell--busy { background: var(--surface); }

.calendar__date {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--text-muted);
}

.calendar__count {
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: var(--radius-pill);
    background: rgba(var(--info-rgb), 0.14);
    color: var(--info);
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.calendar__closed {
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.calendar__chip {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    padding: 2px var(--space-2);
    border-radius: var(--radius-xs);
    font-size: var(--text-xs);
    line-height: 1.4;
    text-decoration: none;
    border-left: 3px solid transparent;
    min-width: 0;
}

.calendar__chip:hover { background: var(--surface-hover); }

.calendar__chip--approved {
    background: rgba(var(--success-rgb), 0.10);
    border-left-color: var(--success);
    color: var(--text);
}

.calendar__chip--pending {
    background: rgba(var(--warning-rgb), 0.12);
    border-left-color: var(--warning);
    color: var(--text);
}

.calendar__chip-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.calendar__chip-type {
    flex: none;
    color: var(--text-muted);
    font-weight: var(--weight-semibold);
}

.calendar__more {
    font-size: var(--text-xs);
    color: var(--text-muted);
    padding-left: var(--space-2);
}

@media (max-width: 860px) {
    .calendar__cell { min-height: 74px; }
    .calendar__chip-type { display: none; }
}

/* ---------------------------------------------------------------- Tree

   The chart of accounts is four levels deep and 358 leaves. Collapsing on
   <details> keeps it navigable without script, and keeps the whole thing
   printable when expanded.                                                  */

.tree { border-bottom: 1px solid var(--border-subtle); }
.tree:last-child { border-bottom: none; }

.tree__summary {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    cursor: pointer;
    list-style: none;
    font-size: var(--text-sm);
    /* A step etched into the sheet: a shadow falling in at the top, a lit lip
       where the light comes back out at the bottom. --surface-sunken is opaque
       and punched a grey hole through the glass card. */
    background: linear-gradient(rgb(var(--neutral-rgb) / 8%), rgb(var(--neutral-rgb) / 3%));
    box-shadow:
        inset 0 1px 2px var(--glass-edge),
        inset 0 -1px 0 var(--lip);
}

.tree__summary::-webkit-details-marker { display: none; }

.tree__summary::before {
    content: "";
    flex: none;
    width: 0;
    height: 0;
    border-left: 5px solid var(--text-subtle);
    border-top: 4px solid transparent;
    border-bottom: 4px solid transparent;
    transition: transform var(--duration-fast) var(--ease);
}

.tree[open] > .tree__summary::before { transform: rotate(90deg); }
.tree__summary:hover { background: var(--surface-hover); }

.tree__label { font-weight: var(--weight-semibold); color: var(--text); flex: 1 1 auto; min-width: 0; }

.tree__meta {
    flex: none;
    min-width: 22px;
    padding: 1px 6px;
    border-radius: var(--radius-pill);
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--text-muted);
    font-size: var(--text-xs);
    text-align: center;
}

.tree__amount { flex: none; color: var(--text); font-weight: var(--weight-semibold); }

/* The third tier, nested inside the second. Indented and unfilled so the two
   rows of headings read as different depths rather than as siblings. */
.tree--nested { border-bottom: none; }

.tree--nested > .tree__summary {
    padding-left: var(--space-8);
    font-size: var(--text-xs);
    /* One level down is a shallower step, offset rather than boxed. */
    background: rgb(var(--neutral-rgb) / 3%);
    box-shadow: inset 3px 0 0 var(--border-subtle);
}

.tree--nested > .tree__summary:hover { background: var(--surface-hover); }
.tree--nested > .tree__summary .tree__label { font-weight: 600; color: var(--text-muted); }
.tree--nested + .tree--nested > .tree__summary { border-top: 1px solid var(--border-subtle); }
.tree--nested .tree__group-head { padding-left: var(--space-10); }

.tree__group { padding: 0 0 var(--space-3); }

.tree__group-head {
    display: flex;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-4);
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--text-muted);
    /* The deepest heading is a marker on the edge, not a band. */
    background: none;
    box-shadow: inset 3px 0 0 rgb(var(--accent-rgb) / 28%);
}

.tree__group .table { font-size: var(--text-sm); }

/* ---------------------------------------- Chart of accounts: tree + panel

   The whole chart on the left, nested <details> so a branch opens and
   closes with no script (see Areas/Financial/Views/Chart/_TreeNode.cshtml);
   the selected node's fields on the right, swapped in by app.js's
   initTreePanels over AJAX, or by a plain page load with scripting off. */

.mtree-layout {
    display: grid;
    grid-template-columns: minmax(280px, 360px) minmax(0, 1fr);
    gap: var(--space-4);
    align-items: start;
}

@media (max-width: 860px) {
    .mtree-layout { grid-template-columns: minmax(0, 1fr); }
}

.mtree-list-card .card__body { max-height: 640px; overflow-y: auto; }

/*
    "mtree" rather than the existing .tree (Ledger/Index.cshtml's nested
    group-and-amount rows, Admin/CompanyAccess/Role.cshtml) -- a different
    component with the same word in mind. Shared by every master-data
    browsing screen built this way (Chart of accounts, Cost centres).
*/
.mtree { font-size: var(--text-sm); }

.mtree__node { border-bottom: 1px solid var(--border-subtle); }
.mtree__node:last-child { border-bottom: none; }

.mtree__summary {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    cursor: pointer;
    list-style: none;
}

.mtree__summary::-webkit-details-marker { display: none; }

.mtree__summary::before {
    content: "";
    flex: none;
    width: 0;
    height: 0;
    border-left: 5px solid var(--text-subtle);
    border-top: 4px solid transparent;
    border-bottom: 4px solid transparent;
    transition: transform var(--duration-fast) var(--ease);
}

.mtree__node[open] > .mtree__summary::before { transform: rotate(90deg); }
.mtree__summary:hover { background: var(--surface-hover); }

.mtree__label {
    flex: 1 1 auto;
    min-width: 0;
    color: var(--text);
    text-decoration: none;
    font-weight: var(--weight-semibold);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mtree__label:hover { text-decoration: underline; }

.mtree__children {
    list-style: none;
    margin: 0;
    padding: 0 0 0 var(--space-4);
}

.mtree__leaf {
    display: block;
    padding: var(--space-2) var(--space-3);
    color: var(--text-muted);
    text-decoration: none;
    font-size: var(--text-sm);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mtree__leaf:hover { background: var(--surface-hover); color: var(--text); }

.mtree__leaf.is-selected,
.mtree__label.is-selected {
    color: var(--accent);
    font-weight: var(--weight-semibold);
}

.mtree__leaf.is-closed,
.mtree__label.is-closed { opacity: 0.6; font-style: italic; }

.panel__head { margin-bottom: var(--space-4); }
.panel__title { font-size: var(--text-md); font-weight: 600; color: var(--text); }
.panel__subtitle { color: var(--text-muted); font-size: var(--text-sm); margin-top: 2px; }
.panel__actions form { display: contents; }

/* -------------------------------------------------- Explanatory blocks */

/*
    A short list of rules or conditions, used where prose would be harder to
    scan than bullets — the payroll run form explains its exclusions this way.
*/
.bullet-list {
    margin: 0 0 var(--space-3);
    padding-left: var(--space-5);
    color: var(--text-muted);
    font-size: var(--text-base);
}
.bullet-list li { margin-bottom: var(--space-2); }
.bullet-list li::marker { color: var(--text-subtle); }

/* A quiet aside inside a card: context for the choice being made, not a warning. */
.callout {
    padding: var(--space-3) var(--space-4);
    border-left: 3px solid var(--border-strong);
    background: var(--surface-sunken);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.callout__title { font-weight: 600; font-size: var(--text-base); color: var(--text); }
.callout__text { margin: 4px 0 0; font-size: var(--text-sm); color: var(--text-muted); }

/*
    A COLLAPSIBLE NOTE.

    Verified reports carry long notes -- what was measured against the client's
    own data, which rows moved, which predicate was wrong. Worth reading once
    and worth keeping; not worth pushing the grid below the fold on every run.
    The Attendance Register note is 1,800 characters.

    <details>/<summary>, so it opens with no JavaScript, takes focus from the
    keyboard and is announced as expandable without an aria- attribute. The
    only work here is removing the default marker and drawing our own, because
    the native triangle sits outside the padding and looks like a mistake.
*/
.callout--note > summary {
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    gap: 8px;
}

/* Safari still draws the marker from the shadow tree. */
.callout--note > summary::-webkit-details-marker { display: none; }

.callout--note > summary::after {
    content: "";
    width: 7px;
    height: 7px;
    border-right: 2px solid var(--text-muted);
    border-bottom: 2px solid var(--text-muted);
    transform: rotate(-45deg);
    transition: transform .15s ease;
}

.callout--note[open] > summary::after { transform: rotate(45deg); }

.callout--note > summary:focus-visible {
    outline: 2px solid var(--focus, #6d28d9);
    outline-offset: 2px;
    border-radius: 3px;
}

/*  How long it is, so a collapsed note says what it is hiding. */
.callout__hint {
    margin-left: auto;
    font-size: var(--text-xs);
    font-weight: 400;
    color: var(--text-muted);
}

/* ========================================================= Balance breakdown */

/*
    Where a figure came from, as a list of the parts that make it. Used for the
    leave balance, which is a subtraction of six things from two and is worth
    showing rather than asserting.
*/
.stat-list { margin: 0; }
.stat-list__row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    padding: 5px 0;
    border-bottom: 1px dashed var(--border);
}
.stat-list__row:last-child { border-bottom: 0; }
.stat-list__row dt { color: var(--text-muted); font-size: var(--text-sm); }
.stat-list__row dd { margin: 0; font-variant-numeric: tabular-nums; }
.stat-list__row--total {
    margin-top: 4px;
    border-top: 1px solid var(--border);
    border-bottom: 0;
    padding-top: 8px;
}
.stat-list__row--total dt { color: var(--text); font-weight: 600; }
.stat-list__row--total dd { font-weight: 700; }

.divider { height: 1px; background: var(--border); }

/*
    Small variants of the two form controls, for a control that sits inside a
    table row rather than on a form of its own — the probation and ad-hoc
    screens act one row at a time, so the input is part of the row.
*/
.input--sm, .select--sm {
    height: 28px;
    padding: 0 var(--space-2);
    font-size: var(--text-sm);
}

/*
    The bulk-attendance calendar: one narrow column per day of the month, and
    a colour key above it. The colours are the client's own, out of
    HRM_ATTN_TYPES.COLOR_CODE, so nothing here picks them.
*/
/* The one declaration, carrying the height that used to live in a second rule
   1,600 lines above this one. */
.table--dense th,
.table--dense td {
    height: var(--row-h-dense);
    padding: var(--space-1) var(--space-2);
    font-size: var(--text-sm);
    white-space: nowrap;
}
/*
    Arbitrary client hex arrives inline on these cells out of
    HRM_ATTN_TYPES.COLOR_CODE, so nothing here picks the fill. What was wrong
    is the INK: the rule set no colour, so the label took --text — which is
    #e9eef7 on the dark theme. Measured against the client's own five colours
    that is 1.25 : 1 on their Leave cyan and 1.63 : 1 on their Holiday green,
    so their attendance grid has been near-unreadable in dark mode.

    Dark ink with a light halo measures 5.05 : 1 on their worst colour and
    12.99 : 1 on their best, in either theme, and it keeps the colour as the
    data channel it is — which a band at the foot of the cell would not.
*/
.table--dense td[style*="background"] {
    text-align: center;
    font-weight: var(--weight-semibold);
    /* !important to beat any inline colour the client's configuration might
       one day supply. It beats nothing today. */
    color: #111 !important;
    text-shadow:
        0 0 2px rgb(255 255 255 / 70%),
        0 1px 1px rgb(255 255 255 / 55%);
}

.chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: 2px var(--space-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-size: var(--text-sm);
    color: var(--text-muted);
}
.chip__swatch {
    width: 14px;
    height: 14px;
    border-radius: 3px;
    border: 1px solid var(--glass-edge);
}

/*
    A list of lists down the side of a screen — general types has 44 of them,
    and which are empty matters as much as which is chosen.
*/
.side-list { display: flex; flex-direction: column; }
.side-list__item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
    border-left: 2px solid transparent;
    color: var(--text-muted);
    font-size: var(--text-sm);
}
.side-list__item:hover { background: var(--surface-hover); color: var(--text); text-decoration: none; }
.side-list__item[aria-current="page"] {
    border-left-color: var(--accent);
    background: var(--surface-sunken);
    color: var(--text);
    font-weight: 600;
}
.side-list__label { flex: 1; }

/* A textarea holding markup rather than prose — the letterhead HTML. */
.textarea.code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: var(--text-xs);
    line-height: 1.5;
    white-space: pre;
    overflow-wrap: normal;
    overflow-x: auto;
}

/* Attendance photos: a plain grid of what is in one folder. */
.thumb-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: var(--space-3);
}
.thumb { margin: 0; }
.thumb img {
    width: 100%;
    aspect-ratio: 3 / 4;
    object-fit: cover;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-sunken);
}
.thumb figcaption {
    margin-top: var(--space-1);
    font-size: var(--text-xs);
    color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Year calendar — twelve small months, the days a holiday covers picked out.
   Used by the holiday calendar; kept here rather than in the view because the
   layout has no per-page style section.
   -------------------------------------------------------------------------- */
.cal-year {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
    gap: var(--space-4);
}

.cal-month__name {
    font-size: var(--text-xs);
    font-weight: 600;
    margin-bottom: var(--space-2);
}

.cal-month__grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
}

.cal-month__dow {
    font-size: 10px;
    text-align: center;
    color: var(--text-muted);
}

.cal-month__day {
    font-size: 11px;
    text-align: center;
    padding: 3px 0;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
}

.cal-month__day--on {
    background: rgb(var(--accent-soft-rgb));
    color: var(--accent-strong);
    font-weight: 600;
}

/* A side list long enough to need its own scroll — four hundred coded lists
   will not fit beside a table otherwise. */
.side-list--scroll {
    max-height: 28rem;
    overflow-y: auto;
}

/* A card whose contents are destructive — used by the attendance delete, so
   the part of the page that removes data does not look like the part that
   reports it. */
.card--danger {
    border-color: rgb(var(--danger-rgb) / 0.4);
}

.card--danger .card__head {
    background: rgb(var(--danger-rgb) / 0.06);
    color: var(--danger);
}

/* A grid of checkboxes — used where a set is chosen from a long list, such as
   the six scopes on a holiday. */
.check-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    gap: var(--space-2) var(--space-4);
}

/* ------------------------------------------------------ Journal voucher

   The original's voucher is two columns: what somebody decides on the left,
   what the document has become on the right, greyed out. Everything here
   exists to hold that shape and the grid beneath it.
*/

.jv-head,
.doc-head {
    display: grid;
    grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
    gap: var(--space-6);
    align-items: start;
}

@media (max-width: 60rem) {
    .jv-head,
    .doc-head { grid-template-columns: minmax(0, 1fr); }
}

.jv-head__right,
.doc-head__right { padding-top: 1.6rem; }

/* The read-only half, and the totals box, which are the same shape: a label
   against a value that nobody types into. Same alignment as legacy's right
   column (Draft Number / Document Number stacked, label left / value right). */
/* ==========================================================================
   Document Identity Box (World-Class Side Panel for All Modules)
   ========================================================================== */
.doc-identity-box {
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-md, 10px);
    padding: var(--space-3) var(--space-4);
    box-shadow: var(--shadow-sm, 0 1px 3px rgba(0,0,0,0.04));
    margin-bottom: var(--space-3);
}

.doc-identity-box__title {
    font-size: var(--text-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-muted);
    margin-bottom: var(--space-2);
    padding-bottom: var(--space-2);
    border-bottom: 1px solid var(--border-subtle);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
}

/* Universal Document Status Badges for All Modules */
.doc-status-badge,
.sales-status-badge,
.proc-status-badge,
.inv-status-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.2rem 0.65rem;
    border-radius: 9999px;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    line-height: 1.3;
}

.doc-status-badge__dot,
.sales-status-badge__dot,
.proc-status-badge__dot,
.inv-status-badge__dot {
    width: 0.45rem;
    height: 0.45rem;
    border-radius: 50%;
    background: currentColor;
    flex-shrink: 0;
}

.doc-status-badge--draft,
.sales-status-badge--draft,
.proc-status-badge--draft,
.inv-status-badge--draft {
    background: var(--surface);
    color: var(--text-muted);
    border: 1px solid var(--border);
}

.doc-status-badge--open,
.sales-status-badge--open,
.proc-status-badge--open,
.inv-status-badge--open {
    background: rgba(59, 130, 246, 0.12);
    color: var(--accent);
    border: 1px solid rgba(59, 130, 246, 0.3);
}

.doc-status-badge--submitted,
.sales-status-badge--submitted,
.proc-status-badge--submitted,
.inv-status-badge--submitted {
    background: rgba(245, 158, 11, 0.12);
    color: #b45309;
    border: 1px solid rgba(245, 158, 11, 0.3);
}

.doc-status-badge--posted,
.sales-status-badge--posted,
.proc-status-badge--posted,
.inv-status-badge--posted {
    background: rgba(16, 185, 129, 0.12);
    color: #047857;
    border: 1px solid rgba(16, 185, 129, 0.35);
}

.doc-status-badge--cancelled,
.sales-status-badge--cancelled,
.proc-status-badge--cancelled,
.inv-status-badge--cancelled {
    background: rgba(239, 68, 68, 0.12);
    color: #b91c1c;
    border: 1px solid rgba(239, 68, 68, 0.3);
}

/* Universal Document Number Code Chip */
.doc-code-chip,
.sales-code-chip,
.proc-code-chip,
.inv-code-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-family: var(--font-mono, monospace);
    font-size: 0.78rem;
    font-weight: 600;
    padding: 0.18rem 0.5rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm, 6px);
    color: var(--text);
    user-select: all;
    transition: all 0.15s ease;
    box-shadow: 0 1px 2px rgba(0,0,0,0.02);
}

.doc-code-chip:hover,
.sales-code-chip:hover,
.proc-code-chip:hover,
.inv-code-chip:hover {
    border-color: var(--accent);
    background: var(--surface-hover, var(--surface));
}

.doc-code-chip__btn,
.sales-code-chip__btn,
.proc-code-chip__btn,
.inv-code-chip__btn {
    background: none;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    padding: 0;
    display: inline-flex;
    align-items: center;
    transition: color 0.15s ease;
}

.doc-code-chip__btn:hover,
.sales-code-chip__btn:hover,
.proc-code-chip__btn:hover,
.inv-code-chip__btn:hover {
    color: var(--accent);
}

/* Universal Document Hero Container */
.doc-hero,
.sales-doc-hero,
.proc-doc-hero,
.inv-doc-hero {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg, 12px);
    box-shadow: var(--shadow-sm);
    margin-bottom: var(--space-4);
    overflow: hidden;
}

.doc-hero__bar,
.sales-doc-hero__bar,
.proc-doc-hero__bar,
.inv-doc-hero__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-5);
    background: var(--surface-sunken);
    border-bottom: 1px solid var(--border-subtle);
}

.doc-hero__badge-group,
.sales-doc-hero__badge-group,
.proc-doc-hero__badge-group,
.inv-doc-hero__badge-group {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.jv-readonly,
.doc-identity,
.doc-identity-list {
    display: grid;
    grid-template-columns: minmax(7rem, auto) minmax(0, 1fr);
    gap: var(--space-2) var(--space-3);
    margin: 0;
    align-items: center;
}

.jv-readonly dt,
.doc-identity dt,
.doc-identity-list dt {
    font-size: var(--text-xs);
    color: var(--text-muted);
    white-space: nowrap;
    font-weight: 500;
}

.jv-readonly dd,
.doc-identity dd,
.doc-identity-list dd {
    margin: 0;
    font-family: var(--font-mono, monospace);
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-sm, 4px);
    padding: 0.2rem 0.5rem;
    font-variant-numeric: tabular-nums;
    font-size: 0.78rem;
    word-break: break-all;
    min-height: 1.75rem;
    display: flex;
    align-items: center;
}

.jv-marks {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3) var(--space-5);
}

/* Their tab strip above the line grid: drawn with every other strip, see
   FOLDER TABS. */

.jv-add,
.jv-row-clear {
    border: 1px solid var(--border);
    background: var(--surface);
    border-radius: var(--radius-sm, 4px);
    cursor: pointer;
    line-height: 1;
    padding: 0.2rem 0.45rem;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.jv-add { font-weight: 700; color: var(--accent); }
.card__head [data-lines-add] { margin-inline-start: auto; }
.jv-row-clear:hover { color: var(--danger); }

/* Tax breakup on the left, totals on the right, as the original foots it. */
.jv-foot {
    display: grid;
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    gap: var(--space-4);
    margin-bottom: var(--space-4);
    align-items: start;
}

@media (max-width: 60rem) {
    .jv-foot { grid-template-columns: minmax(0, 1fr); }
}

.jv-actions {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
    margin: var(--space-4) 0 var(--space-6);
    padding: var(--space-3) var(--space-4);
    position: sticky;
    bottom: 0;
    z-index: 6;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-sm);
}

.jv-actions > .row,
.jv-actions [data-attach-host] {
    margin-top: 0 !important;
    justify-content: flex-end;
    flex-wrap: wrap;
    width: 100%;
}

/* ---------------------------------------------------------------- Combobox
   A searchable dropdown built over a real <select>. The select stays in the
   DOM so the form posts without script; it is hidden only once the script has
   put a text box in front of it, which is why the rule keys off the class the
   upgrade adds rather than the attribute the server renders.

   Every ordinary .select is upgraded at load; multi-select listboxes and
   data-no-combobox are left alone. */
.combo {
    position: relative;
    display: block;
    width: 100%;
}

.combo__native {
    /* Not display:none — a hidden-but-present control keeps its name, its
       value and its :disabled state, which the enhancement reads. */
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

.combo__input {
    width: 100%;
    padding-right: var(--space-8);
    /* Same chevron as .select, so a searchable box still reads as a dropdown. */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2378839b' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right var(--space-3) center;
}

.combo__input:disabled {
    background-color: var(--surface-sunken);
    color: var(--text-muted);
    cursor: not-allowed;
}

/*
    THE MARK OF A DROPDOWN, and why it is written twice more here.

    A closed dropdown read as a plain text box: the chevron was a pale grey 12px drawing at the right, and on every
    page that loaded a module stylesheet it was not there at all -- hr.css and the filter bar restate
    ".input, .select { background: ... }" and a shorthand clears background-image. So the mark is declared again at the
    end of the base rules with a selector that outweighs those (".select:not([multiple])" and ".input.combo__input"
    are two classes' worth), and it carries a hairline divider as well, which is what makes a box with an arrow at its
    edge look like a control that opens.

    The same two classes twice (".select.select", ".input.combo__input.combo__input") is deliberate: it outweighs a
    later ".input:focus", ".input:disabled" or ".filters .select" that restates "background:". hr.css did, on focus and
    on disabled, so the mark vanished the moment a box was clicked -- the state the first complaint was a screenshot of.
    Those are mended at the source too (background-color), but a new stylesheet will do it again.

    A single choice is a chevron. A tag picker (several choices, shown as chips) is a different mark -- two chevrons,
    one up and one down, in the accent colour with a tinted divider -- so "pick one" and "pick several" are told apart
    before the list is opened. Drawn as data URIs so no request is needed; the stroke colours are mid-tones that
    read on the light and the dark ground alike.
*/
.select.select:not([multiple]):not(.select--op),
.input.combo__input.combo__input {
    padding-right: 2.6rem;
    background-image:
        linear-gradient(var(--border-strong), var(--border-strong)),
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364708b' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat, no-repeat;
    background-size: 1px 52%, 16px 16px;
    background-position: right 2.1rem center, right 0.65rem center;
}

.select.select:not([multiple]):not(.select--op):hover:not(:disabled),
.input.combo__input.combo__input:hover:not(:disabled) {
    background-image:
        linear-gradient(var(--n-400), var(--n-400)),
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23475069' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}

.combo--multi > .input.combo__input {
    padding-right: 2.6rem;
    background-image:
        linear-gradient(rgb(var(--accent-rgb) / 40%), rgb(var(--accent-rgb) / 40%)),
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%233b6ef6' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m7 15 5 5 5-5'/%3E%3Cpath d='m7 9 5-5 5 5'/%3E%3C/svg%3E");
}

.combo--multi > .input.combo__input:hover:not(:disabled) {
    background-image:
        linear-gradient(rgb(var(--accent-rgb) / 70%), rgb(var(--accent-rgb) / 70%)),
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%232552d6' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m7 15 5 5 5-5'/%3E%3Cpath d='m7 9 5-5 5 5'/%3E%3C/svg%3E");
}

/* The same field with nobody able to open it: the mark stays, quieter. */
.select.select:not([multiple]):not(.select--op):disabled,
.input.combo__input.combo__input:disabled {
    background-repeat: no-repeat, no-repeat;
    background-size: 1px 52%, 16px 16px;
    background-position: right 2.1rem center, right 0.65rem center;
    background-image:
        linear-gradient(var(--border-strong), var(--border-strong)),
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%238b94a8' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}

/*
    The Confirm / End-adhoc action row (Standing.cshtml) picks an employment
    type one row at a time, in a flex row shared with a date input. .combo's
    own width:100% gives it the same flex-basis as everything else in the
    row, so it lost the fight for space to the date input's own basis and
    shrank to the chevron alone -- "Unpaid Internee" read as an empty box.

    A bounded pixel width rather than min-width: min-width leaves width:100%
    in charge of the flex-basis, which claimed the whole row back the moment
    shrinking was turned off.
*/
.combo:has(> .combo__native[name="employmentType"]) {
    flex: 0 0 150px;
    width: 150px;
}

/*
    Positioned by script, in the viewport, so it is not clipped by the
    scrollport the grid lives in. .table-wrap is now a bounded scroll box, so
    an absolutely-positioned popover in the last row of a journal would be cut
    off by it on every row rather than on the odd one.

    It is OPAQUE, and that is the material's own law being kept rather than
    broken: the person entering a voucher is looking AT the list of 358
    accounts, not through it, and account codes ghosting behind account codes
    is the one place in this application where translucency costs reading speed
    instead of buying it.
*/
/*
    z-index: 80 is the popover tier of the layer ladder documented in the
    animation section near the end of this file. It only means anything while
    no ancestor is a stacking context -- which is why .card, .hero and .stat
    animate with fill-mode backwards rather than both.
*/
.combo__list {
    position: fixed;
    z-index: 80;
    top: 0;
    left: 0;
    min-width: 12rem;
    max-width: 28rem;
    max-height: 16rem;
    overflow-y: auto;
    overscroll-behavior: contain;
    margin: 0;
    padding: var(--space-1);
    list-style: none;
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--rim-soft), var(--shadow-md);
}

.combo__option {
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-sm);
    cursor: pointer;
    white-space: nowrap;
}

.combo__option:hover,
.combo__option.is-active {
    background: rgb(var(--accent-soft-rgb));
    box-shadow: var(--rim-soft);
}

.combo__option[aria-selected="true"] {
    font-weight: 600;
}

.combo__empty {
    padding: var(--space-1) var(--space-2);
    color: var(--text-muted);
}

/*
    A checkmark rather than bold, for an option a tag picker's list offers
    more than once (picking it again removes it) -- aria-selected="true"
    alone reads the same as the single combobox's "this is the value",
    which is not what choosing a second or third value means here.
*/
.combo__option--check {
    position: relative;
    padding-left: var(--space-6);
}
.combo__option--check[aria-selected="true"]::before {
    content: "";
    position: absolute;
    left: var(--space-2);
    top: 50%;
    width: 5px;
    height: 9px;
    margin-top: -6px;
    border: solid rgb(var(--accent-strong-rgb));
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

/*
    The tag picker: upgradeTagPicker's chips row sits above the same search
    box and popover list a single combobox uses, so multi-select columns in
    the advanced-filter panel (Employee, Branch, Document Status, ...) cost a
    couple of lines of chips instead of the three-to-six-row listbox a native
    <select multiple> always was, full or holding one value.
*/
.combo__chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    margin-bottom: var(--space-1);
}
.combo__chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 6px 2px 9px;
    border-radius: var(--radius-pill);
    background: rgb(var(--accent-rgb) / 8%);
    border: 1px solid rgb(var(--accent-rgb) / 20%);
    color: rgb(var(--accent-strong-rgb));
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    white-space: nowrap;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
}
.combo__chip-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 14px;
    height: 14px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: none;
    color: inherit;
    opacity: 0.75;
    font-size: 13px;
    line-height: 1;
    cursor: pointer;
    flex: none;
}
.combo__chip-remove:hover {
    opacity: 1;
    background: rgb(var(--accent-rgb) / 16%);
}

/*
    The label row for an advanced filter that also offers an operator: the
    "is" / "is not" select sits beside the label rather than above the value,
    the original's own layout for a checkbox-gated search field.

    Explicit flex-basis on both sides rather than leaving the select at
    width:auto — .select's own width:100% (app.css:2451) is an equal-
    specificity sibling rule, and an "auto"-width <select> still measures
    against the flex row's available space before shrinking to its options,
    so it rendered near full width with the label squeezed into what was
    left. flex: 0 0 <fixed> takes the select out of that negotiation
    entirely; the label gets flex: 1 1 auto and min-width: 0 so it is what
    actually grows, and truncates with an ellipsis rather than wrapping the
    row onto two lines if a longer label ever sits beside the same control.
*/
.field__head {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}
.field__head .label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.select--op {
    flex: 0 0 6rem;
    width: 6rem;
    height: 1.6rem;
    padding: 0 var(--space-6) 0 var(--space-2);
    background-position: right var(--space-2) center;
    font-size: var(--text-xs);
}

/*  Wide enough for a GL account code and its title. It replaces the inline
    style="min-width:260px" that the Account Code Config select carried before
    it became a fetched picker -- the partial takes a class, not a style, and
    the column should not get narrower for the change. */
.select--wide { min-width: 260px; }

/* The line-level note where the original would raise a warning box. */
.jv-note:empty {
    display: none;
}

.jv-note {
    margin-top: var(--space-1);
    font-size: var(--text-xs);
    color: var(--warning);
}

/* ========================================================= Printed documents

   A voucher, a payslip or a certificate laid out for paper. On screen it sits
   inside the usual shell; on paper the shell goes and only .doc is left, which
   is what the @media print block at the bottom does.

   Sized in millimetres rather than rem: this is the one place in the
   application where the output is a sheet of A4 and not a browser window, and
   a document that reflows with the reader's font size is a document nobody can
   file. */
.doc {
    background: #fff;
    color: #000;
    max-width: 200mm;
    margin: 0 auto;
    padding: 10mm 12mm;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    font-size: 10.5pt;
    line-height: 1.35;
}

.doc__head {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--space-4);
    border-bottom: 1.5pt solid #000;
    padding-bottom: 3mm;
    margin-bottom: 4mm;
}

.doc__company { font-size: 15pt; font-weight: 700; letter-spacing: .01em; }
.doc__legal   { font-size: 10pt; }
.doc__reg     { font-size: 8.5pt; margin-top: 1mm; }
.doc__reg span + span::before { content: " · "; }

/*
    The company logo on a printed letterhead. Bounded in BOTH directions,
    because the file is whatever the client uploaded to the original years ago
    and nothing has ever constrained it: a 2000px banner would push the tax
    numbers off the page, and max-width alone would still let a tall narrow
    image do it. object-fit keeps the aspect ratio inside the box.

    print-color-adjust so a logo is not washed out by a browser's
    "simplify colours" print default -- a letterhead is the one thing on the
    page that has to come out as the client's own.
*/
.doc__logo {
    max-height: 16mm;
    max-width: 55mm;
    object-fit: contain;
    margin-bottom: 1.5mm;
    print-color-adjust: exact;
    -webkit-print-color-adjust: exact;
}

.doc__title { text-align: right; }
.doc__kind  { font-size: 12pt; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; }

/* A draft on paper has to announce itself, or it reads as a posted document. */
.doc__draft {
    margin-top: 1mm;
    font-size: 9pt;
    font-weight: 700;
    color: #b00;
    border: 1pt solid #b00;
    padding: .5mm 2mm;
    display: inline-block;
}

.doc__fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(45mm, 1fr));
    gap: 1.5mm 4mm;
    margin: 0 0 4mm;
}
.doc__fields > div { display: flex; gap: 2mm; }
.doc__fields dt { font-size: 8.5pt; text-transform: uppercase; letter-spacing: .04em; color: #555; }
.doc__fields dd { margin: 0; font-weight: 600; }

.doc__lines { width: 100%; border-collapse: collapse; margin-bottom: 3mm; }
.doc__lines th,
.doc__lines td { border: .5pt solid #999; padding: 1.2mm 2mm; text-align: left; vertical-align: top; }
.doc__lines thead th {
    background: #eee;
    font-size: 8.5pt;
    text-transform: uppercase;
    letter-spacing: .03em;
}
.doc__lines tfoot td { font-weight: 700; background: #f5f5f5; }
.doc__num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.doc__acc { font-family: var(--font-mono); font-size: 9pt; display: block; }
.doc__accname { font-size: 9pt; color: #333; }

.doc__words {
    display: flex;
    gap: 3mm;
    border: .5pt solid #999;
    padding: 1.5mm 2mm;
    margin-bottom: 3mm;
}
.doc__wordslabel { font-size: 8.5pt; text-transform: uppercase; color: #555; white-space: nowrap; }
.doc__wordsvalue { font-weight: 600; }

.doc__narration { margin-bottom: 2mm; }

.doc__period { font-size: 10pt; }

/* Earnings against deductions, which is the comparison a salary slip is for.
   They fall into one column on a narrow screen so the slip is still readable
   on a phone; on paper the sheet is always wide enough for two. */
.doc__cols {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 4mm;
    align-items: start;
}

@media (max-width: 640px) {
    .doc__cols { grid-template-columns: 1fr; }
}

/* Both amount columns the same width, so the two totals line up with each
   other across the page and can be read as the pair they are. */
.doc__cols .doc__lines th:last-child,
.doc__cols .doc__lines td:last-child { width: 28mm; }

.doc__hint { font-size: 8.5pt; color: #555; }

/* The one figure the employee is looking for. */
.doc__net td { font-weight: 700; font-size: 11.5pt; background: #f5f5f5; }

.doc__note {
    border: .5pt solid #999;
    border-left: 2pt solid #000;
    padding: 2mm;
    margin-bottom: 3mm;
    font-size: 9pt;
}

.doc__foot {
    margin-top: 4mm;
    padding-top: 2mm;
    border-top: .5pt solid #999;
    font-size: 8.5pt;
    color: #555;
    text-align: center;
}

/* ---- Letters and certificates

   A letter is read as prose and a voucher is read as a table, so the letter
   variant gives itself more room and larger type. The letterhead the client
   writes under Letter Options lands inside .doc__letterhead as their own
   markup -- decoded, filled in and stripped of anything that would run -- so
   only spacing is imposed on it. */
.doc--letter { font-size: 11pt; line-height: 1.55; }

.doc__head--letter { display: block; text-align: center; padding-bottom: 4mm; }
.doc__letterhead > *:first-child { margin-top: 0; }
.doc__letterhead > *:last-child { margin-bottom: 0; }
.doc__letterhead img { max-height: 25mm; width: auto; }

.doc__ref {
    display: flex;
    justify-content: space-between;
    gap: var(--space-4);
    font-size: 9.5pt;
    margin: 4mm 0;
}

.doc__lettertitle {
    text-align: center;
    font-size: 12pt;
    letter-spacing: .06em;
    text-decoration: underline;
    margin: 6mm 0 5mm;
}

.doc__addressee { margin-bottom: 5mm; }

.doc__para { margin: 0 0 4mm; text-align: justify; }
.doc__para--small { font-size: 9.5pt; }

/* One signature, or two, but never spread across the width of the page the
   way a voucher's four are -- a letter is signed at the left. */
.doc__sign--letter {
    display: flex;
    gap: 20mm;
    justify-content: flex-start;
    margin-top: 14mm;
}
.doc__sign--letter .doc__signbox { min-width: 60mm; max-width: 75mm; }

.doc__letterfoot {
    margin-top: 8mm;
    padding-top: 2mm;
    border-top: .5pt solid #999;
    font-size: 9pt;
    text-align: center;
}

.doc__receiver { border: .5pt solid #999; padding: 2mm; margin-bottom: 4mm; }
.doc__receiverhead {
    font-size: 8.5pt;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: #555;
    margin-bottom: 1.5mm;
}

.doc__sign {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 6mm;
    margin-top: 14mm;
}
.doc__signline { border-top: .5pt solid #000; }
.doc__signlabel { font-size: 8.5pt; text-transform: uppercase; letter-spacing: .04em; padding-top: 1mm; }
.doc__signwho { font-size: 8.5pt; color: #555; }

/* ---- The chart of accounts

   A plain list of the groups under a section. Not the .tree above it, which
   dresses a <details> element: this one is always open, because the point of
   the card is to show what is inside without a click. */
.chart-list { list-style: none; margin: 0; padding: 0; }

.chart-list li {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-4);
    border-bottom: 1px solid var(--border);
}

.chart-list li:last-child { border-bottom: none; }
.chart-list li:hover { background: var(--surface-hover); }

.chart-list__meta {
    font-size: var(--text-sm);
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}


/* ================================================ Windows High Contrast

   The rim, the shadows and the tints are invisible or wrong here, and what
   must survive is STRUCTURE: where a pane ends, which row is selected, which
   control has focus — and, because the muster roll's colour IS the cell value,
   where a cell is.

   Placed before the print block, which stays last in the file.            */
@media (forced-colors: active) {
    .card, .stat, .main, .sidebar, .topbar, .cmdk, .auth__card,
    .filters, .card__foot, .table thead th, .tree__summary, .combo__list {
        box-shadow: none;
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
        border: 1px solid CanvasText;
    }
    .badge { border: 1px solid CanvasText; }

    /* A selected or focused row is a colour tint, which forced colours
       discards — so it needs an outline it cannot discard. */
    .table tbody tr[aria-selected="true"],
    .table tbody tr.is-selected,
    .table tbody tr:focus-within,
    .cmdk__item[aria-selected="true"] { outline: 2px solid Highlight; outline-offset: -2px; }

    /* The muster roll's colour IS the cell value, so it has to be kept. */
    .muster__cell { border: 1px solid CanvasText; }
    .table--dense td[style*="background"] { forced-color-adjust: none; }

    /* The drawn checkbox loses its fill; the border has to say checked. */
    input[type="checkbox"]:checked,
    input[type="radio"]:checked { outline: 2px solid Highlight; outline-offset: 1px; }

    /* The open tab is a fill and an inset accent rule, both discarded here,
       so which tab is open has to be said with an outline instead. */
    .tabs, .form-tabs__nav, .jv-tabs, .rights-top-tabs,
    .tab-strip { border: 1px solid CanvasText; background: Canvas; box-shadow: none; }
    .tab[aria-current="page"], .tab[aria-selected="true"],
    .form-tabs__tab[aria-selected="true"],
    .jv-tab[aria-current="page"], .jv-tab[aria-selected="true"],
    .rights-top-tabs__btn[aria-selected="true"],
    .tab-strip__tab[aria-current="page"] { outline: 2px solid Highlight; outline-offset: -2px; }
}

/* ===================================================== Print, consolidated

   On paper there is no light to catch and no backdrop to bend, so EVERY
   property the material is made of has to go — and the chrome that was never
   hidden (the palette trigger, the user chip, the tabs, the card foot, the
   progress bar) goes with it.

   This replaces three blocks that disagreed with each other: .card got
   `1px solid #ccc` in one and `none` in another, none of them mentioned a
   backdrop-filter, a rim, a fill alpha or a bounded scrollport, and none of
   them neutralised the dark palette — so a dark-theme user pressing Ctrl+P got
   --text #e9eef7 on white paper. White on white, in the shipped product.

   Last in the file, deliberately.                                          */
@media print {

    /* ---------------------------------------------------------------------
       1. The theme goes light, whatever the reader has chosen.

       color-scheme alone does not move a custom property, so the tokens are
       restated. Shadows and rims become transparent rather than `none`,
       because `none` is invalid inside a comma-separated shadow list and would
       kill the whole declaration.

       The SEMANTIC hues stay: a red DRAFT stamp, a red out-of-balance
       difference and the muster roll's four tones are data, not decoration.
       --------------------------------------------------------------------- */
    :root,
    :root[data-theme="dark"],
    :root:not([data-theme="light"]) {
        color-scheme: light;

        --text: #000;
        --text-muted: #333;
        --text-subtle: #555;
        --text-inverse: #fff;

        --surface: #fff;
        --surface-app: #fff;
        --surface-raised: #fff;
        --surface-sunken: #f4f4f4;
        --surface-hover: #fff;
        --surface-active: #fff;
        --surface-selected: transparent;
        --overlay: transparent;

        --border: #999;
        --border-subtle: #ccc;
        --border-strong: #666;

        --glass-pane: #fff;
        --glass-panel: #fff;
        --glass-line: #ccc;
        --glass-border: #ccc;
        --glass-edge: #ccc;
        --lip: transparent;

        --lens-bar: none;
        --lens-rail: none;
        --lens-over: none;
        --blur: none;
        --blur-strong: none;

        --rim: 0 0 #0000;
        --rim-soft: 0 0 #0000;
        --glass-highlight: 0 0 #0000;
        --shadow-xs: 0 0 #0000;
        --shadow-sm: 0 0 #0000;
        --shadow-md: 0 0 #0000;
        --shadow-lg: 0 0 #0000;
        --ring: 0 0 #0000;

        --accent-rgb: 29 78 216;
        --accent-strong-rgb: 29 78 216;
        --accent-contrast: #fff;
        --success-rgb: 21 128 61;
        --warning-rgb: 146 94 5;
        --danger-rgb: 185 28 28;
        --info-rgb: 7 132 161;
        --neutral-rgb: 90 100 115;
    }

    /* ---------------------------------------------------------------------
       2. The application is not part of the document.
       --------------------------------------------------------------------- */
    .sidebar, .topbar, .company-strip, .scrim, .skip-link, .navbar-progress,
    .cmdk, .cmdk-trigger, .user-chip,
    .page-head__actions, .tabs, .jv-tabs, .jv-actions,
    .card__foot, .pager, .btn, .icon-button, .jv-add, .jv-row-clear,
    .filters__actions, .form-tabs__nav, .form-tabs__actions, .tab-strip, .no-print { display: none !important; }

    /*
        Every section prints, not just the one that was on screen. The switcher
        is chrome, hidden above with .form-tabs__nav; what it switches between
        is the document.

        Selected on data-tab-panel, which every panel carries. This rule used
        to name .form-tabs__panel alone -- a class nothing sets -- so the
        hidden attribute kept every other section out of print after all.
    */
    .form-tabs__panel, .form-tabs__panel[hidden],
    .form-tabs > [data-tab-panel], .form-tabs > [data-tab-panel][hidden] { display: block !important; animation: none !important; }

    .app { display: block; padding: 0; gap: 0; }
    .main {
        display: block;
        border: none;
        border-radius: 0;
        background: none;
        box-shadow: none;
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
    }
    .content { max-width: none; margin: 0; padding: 0; }

    body,
    :root[data-theme="dark"] body,
    :root:not([data-theme="light"]) body { background: #fff; color: #000; }

    /* ---------------------------------------------------------------------
       3. No lens, no rim, no fill, no shadow. One rule, everything.
          !important is deliberate: this has to beat the inline style=""
          attributes scattered across the views, and only !important does.
       --------------------------------------------------------------------- */
    .card, .stat, .auth__card, .filters, .tree__summary, .table thead th,
    .badge, .combo__list, .main, .sidebar, .topbar {
        background-image: none !important;
        box-shadow: none !important;
        -webkit-backdrop-filter: none !important;
        backdrop-filter: none !important;
    }
    .card, .stat { background-color: #fff !important; border: none; border-radius: 0; }
    .card__head { border-bottom: .5pt solid #999; padding: 0 0 2mm; margin-bottom: 3mm; }
    .card__body, .card__body--flush { padding: 0; }
    .card + .card { margin-top: 6mm; }
    .badge { border: 1px solid #666; color: #000; background: none !important; }
    .table thead th::after { display: none; }

    /* ---------------------------------------------------------------------
       4. The filter bar is the report's parameter line, not chrome.

       On a trial balance, a ledger or an attendance register it carries the
       date range, the account, the branch and the employee. Hiding it hands
       the auditor a sheet that does not say what it is a register OF. So it
       prints, flattened: the controls lose their boxes and print as the values
       they hold.
       --------------------------------------------------------------------- */
    .filters {
        display: flex;
        flex-wrap: wrap;
        gap: 1.5mm 6mm;
        align-items: baseline;
        padding: 0 0 3mm;
        margin: 0 0 3mm;
        background: none !important;
        border: 0;
        border-bottom: .5pt solid #999;
    }
    .filters .field { flex: 0 0 auto; min-width: 0; flex-direction: row; gap: 2mm; align-items: baseline; }
    .filters .label { font-size: 8.5pt; text-transform: uppercase; letter-spacing: .04em; color: #555; }
    .filters .input,
    .filters .select,
    .filters .textarea {
        width: auto;
        height: auto;
        min-height: 0;
        padding: 0;
        border: 0;
        box-shadow: none;
        background: none !important;
        background-image: none !important;
        -webkit-appearance: none;
        appearance: none;
        color: #000;
        font-weight: 600;
        font-size: 9.5pt;
    }
    .search-input svg { display: none; }
    .search-input .input { padding-left: 0; }
    .filters .field:has(.input[value=""]) { display: none; }

    /* ---------------------------------------------------------------------
       5. THE ONE THAT MATTERS. A bounded .table-wrap lets the header stick on
          screen; on paper it prints one screenful of a register and silently
          drops the rest. Release it, and repeat the header on every sheet.
       --------------------------------------------------------------------- */
    .table-wrap,
    .side-list--scroll,
    .cmdk__list,
    .combo__list {
        max-height: none !important;
        overflow: visible !important;
        background: none !important;
    }
    .table thead { display: table-header-group; }
    .table tfoot { display: table-footer-group; }
    .table thead th { position: static; }
    .table tr, .muster tr, .doc__lines tr { page-break-inside: avoid; }
    .table tbody tr:hover,
    .table tbody tr:focus-within,
    .table td:focus-within,
    .table--zebra tbody tr:nth-child(even) { background: none !important; }

    /* A cell whose fill IS the value keeps it. */
    .table--dense td[style*="background"] { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
    .muster__cell { print-color-adjust: exact; -webkit-print-color-adjust: exact; }

    /* ---------------------------------------------------------------------
       6. The chart of accounts prints expanded or it prints nothing.

       display:block on a closed <details>'s children does NOT work in current
       Chromium or Firefox — the content is hidden by ::details-content, in the
       UA shadow tree, which author CSS on the light-DOM children cannot reach.
       This rule handles the browsers that expose the pseudo; the reliable
       answer with JavaScript off is to render `open` on the server.
       --------------------------------------------------------------------- */
    details::details-content { content-visibility: visible !important; block-size: auto !important; }
    details > summary { list-style: none; }

    /* ---------------------------------------------------------------------
       7. The document IS the page. Every value below belongs to pre-printed
          stationery and none of it may move.
       --------------------------------------------------------------------- */
    .doc {
        background: #fff;
        color: #000;
        border: none;
        border-radius: 0;
        max-width: none;
        margin: 0;
        padding: 0;
    }
    .doc__lines tr, .doc__sign { page-break-inside: avoid; }
    .cheque { border: none; padding: 0; }

    /* Unchanged from the shipped file, deliberately. The muster roll and the
       cheque are not portrait A4, but this rule is already global — changing
       it here would be a regression introduced by the step claiming to be the
       print authority. A view that needs landscape opts in. */
    @page { size: A4 portrait; margin: 12mm; }

    @page landscape-sheet { size: A4 landscape; margin: 10mm; }
    /* Opt in with class="print-landscape" on the page's outermost element.
       Chromium and Safari honour named pages; Firefox ignores them and falls
       back to portrait, which is today's behaviour either way. */
    .print-landscape { page: landscape-sheet; }
}

/*
    A document's own totals, shown beside the item grid. This client's sales
    orders carry the amount on the header and have no lines at all, so without
    this the screen showed nothing where their money is.
*/
.doc-totals {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--space-6);
    margin: 0;
}
.doc-totals > div { display: flex; flex-direction: column; gap: 2px; align-items: flex-end; }
.doc-totals dt { color: var(--text-muted); font-size: var(--text-sm); }
.doc-totals dd { margin: 0; font-weight: 600; }

/*
    A secondary line in a table cell that must not grow the row. The client's
    sales orders carry a description that restates the customer; on one line it
    is context, on three it is noise.
*/
.cell-clip {
    max-width: 22ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   Confirm dialog — the in-app replacement for window.confirm(). A dangerous or
   irreversible action (approve, cancel, remove, reject) shows this instead of
   the browser's native prompt, so the question reads in the application's own
   voice and styling. Driven by data-confirm; see app.js initConfirms.
   --------------------------------------------------------------------------- */
.confirm-overlay {
    position: fixed; inset: 0; z-index: 1000;
    display: grid; place-items: center; padding: 20px;
    background: var(--overlay);
    -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
    opacity: 0; transition: opacity .14s ease;
}
.confirm-overlay.is-open { opacity: 1; }
.confirm-dialog {
    width: min(440px, 100%);
    background: var(--surface); color: var(--text);
    border: 1px solid var(--border); border-radius: 16px;
    box-shadow: 0 24px 60px -20px rgb(var(--shadow-rgb) / 45%);
    padding: 24px; text-align: center;
    transform: translateY(8px) scale(.98); transition: transform .16s ease;
}
.confirm-overlay.is-open .confirm-dialog { transform: none; }
.confirm-dialog__icon {
    width: 48px; height: 48px; border-radius: 50%; margin: 0 auto 14px;
    display: grid; place-items: center;
}
.confirm-dialog__icon svg { width: 24px; height: 24px; }
.confirm-dialog__icon--primary { background: rgb(var(--accent-rgb) / 12%); color: var(--accent); }
.confirm-dialog__icon--danger  { background: rgb(var(--danger-rgb) / 12%); color: var(--danger); }
.confirm-dialog__title { font-size: 1.12rem; font-weight: 700; margin: 0 0 6px; }
.confirm-dialog__text  { font-size: .92rem; color: var(--text-muted); line-height: 1.45; margin: 0 0 22px; }
.confirm-dialog__actions { display: flex; gap: 10px; justify-content: center; }
.confirm-dialog__actions .btn { min-width: 108px; justify-content: center; }
@media (max-width: 420px) {
    .confirm-dialog__actions { flex-direction: column-reverse; }
    .confirm-dialog__actions .btn { width: 100%; }
}

/* ---------------------------------------------------------------------------
   Collapsible filters — a heavy search (a form.filters with a search box and
   several more fields) gets a Filters toggle injected by app.js, and its
   advanced fields collapse behind it so a twelve-field search does not push the
   results off the screen. The fields are hidden in place, never moved, so one
   set before collapsing still submits. The toggle badges how many are set.
   --------------------------------------------------------------------------- */
.filter-toggle {
    display: inline-flex; align-items: center; gap: 8px;
}
.filter-toggle svg { flex: 0 0 auto; }
.filter-toggle__chev { display: inline-flex; transition: transform .15s ease; }
.filter-toggle[aria-expanded="true"] .filter-toggle__chev { transform: rotate(180deg); }
.filter-toggle__badge {
    display: inline-grid; place-items: center; min-width: 18px; height: 18px; padding: 0 5px;
    border-radius: 9px; background: var(--accent); color: var(--accent-contrast);
    font-size: .7rem; font-weight: 700; line-height: 1;
}

/* app.js rebuilds a heavy .filters form into a bar (search + toggle + actions)
   and a panel (the advanced fields, three to a row). The form becomes a block
   so the two stack; it keeps the .filters padding, so the bar and grid sit in
   from the card edges. */
.filters--collapsible { display: block; }

.filter-bar { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }
.filter-bar .field--search { flex: 1 1 260px; margin: 0; }
.filter-bar__spacer { flex: 1 1 auto; }
.filter-bar .filter-toggle { align-self: flex-end; }
.filter-bar .filters__actions { margin: 0; }

.filter-panel {
    overflow: hidden;
    max-height: 0; opacity: 0; margin-top: 0;
    transition: max-height .24s ease, opacity .18s ease, margin-top .24s ease;
}
.filter-panel.is-open { max-height: 900px; opacity: 1; margin-top: 14px; }
.filter-panel__grid {
    display: grid; gap: 14px 18px;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    padding-top: 16px; border-top: 1px solid var(--border-subtle);
}
.filter-panel__grid .field { margin: 0; min-width: 0; grid-column: auto; }
.filter-panel__grid .select,
.filter-panel__grid .input,
.filter-panel__grid .combobox { width: 100%; }
@media (max-width: 860px) { .filter-panel__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) {
    .filter-panel.is-open { max-height: 2000px; }
    .filter-panel__grid { grid-template-columns: 1fr; }
}

/* =================================================== HR module refresh */
/*
    Additive layer, written for the HR screens but scoped to shared classes so
    every screen that already renders .card, .stat, .page-head or .avatar
    picks this up with no markup change and no risk to a binding an earlier
    pass verified. Everything below is either a NEW class (.hero, .chip,
    .stat__icon, .page-head__icon) that a view opts into, or motion layered on
    an existing selector that changes nothing about layout, only how it
    arrives on screen.
*/

/* ---- Entrance: content rises in rather than appearing flat ------------- */
/*
    Staggered by position so a row of stat tiles or a stack of cards cascades
    instead of popping together in one frame. nth-child is used rather than
    nth-of-type because siblings under .content mix .card, .alert and .tabs --
    nth-of-type would count only the divs among them and mis-stagger; nth-child
    counts position, which is what "first on the page" actually means here.
    Capped at a few explicit steps: a ninth tile starting half a second after
    the first would read as the page stalling, not as staging.
*/
/*
    TWO KEYFRAMES, AND THE REASON IS NOT TASTE.

    A CONTAINER MUST NOT ANIMATE transform. .card carries the warning already
    (see the .card rule): anything that gives it a transform, a filter or a
    backdrop-filter makes it the containing block for position: fixed, and the
    combobox popover is fixed precisely so the table's scrollport cannot clip
    it. Break that and every dropdown inside a card opens relative to the card
    instead of the viewport -- which is exactly what happened: the component
    picker on the salary screen opened near the bottom of the window.

    animation-fill-mode: both is what makes it permanent rather than momentary.
    Holding the last keyframe means the computed transform stays a MATRIX after
    the animation finishes -- matrix(1,0,0,1,0,0), the identity -- and the
    identity matrix still creates a containing block. `transform: none` in the
    keyframe does not save it; only never touching the property does.

    So containers fade, and only leaf tiles rise.

    AND THE FILL MODE IS backwards, NOT both -- THE SAME TRAP, ONE STEP ALONG.

    The note above stops at "a transform makes a containing block". It missed
    the other half: an element that is animating OR STILL FILLING a property
    which induces a stacking context -- opacity is one -- is a stacking context
    for as long as that lasts. With `both` it lasts forever: the animation sits
    in its after-phase holding `opacity: 1`, and holding it is enough. The
    value being the harmless one does not matter; the fill does.

    So every .card on every screen was a permanent stacking context at
    z-index: auto, and .combo__list's z-index: 80 was scoped INSIDE the card
    the combobox happened to live in. A popover taller than its own card was
    then painted over by whatever followed that card in the document: on the
    bank-receipt-against-invoices screen, the Invoices/Voucher Preview tab
    strip and the Invoices card head painted across the middle of the open
    partner list, with the list showing through in the gaps between them.
    (Verified by hit-testing down the popover's own rectangle: BUTTON
    .form-tabs__tab and DIV.card__head came back from inside it.)

    backwards fills only BEFORE the animation starts, which is the half the
    staggered animation-delays below actually need -- a card must be invisible
    while it waits its turn. After the 420ms the fill ends, the property goes
    back to the element's own value, the stacking context goes with it, and a
    popover opened inside a card competes in the root stacking context where
    its z-index means what the ladder below says it means. Nothing about the
    animation looks different; the end state was already opacity: 1.

    LAYER LADDER (root stacking context), so a number here is a decision:
        1-10    in-flow sticky furniture: table heads (2-4), the form-tabs
                action bar (5), .jv-actions (6)
        20-60   application chrome: topbar 20, sidebar 30 (60 on mobile),
                topbar triggers 40, module switch and mobile scrim 50
        80-90   floating overlays anchored to a control: .combo__list 80,
                .module-menu 80, .save-split__menu 80, .lang-menu and
                .notif-menu 90. Above all chrome, because an overlay that a
                sticky header cuts in half is worse than one that covers it.
        200     whole-window feedback: .skip-link, .navbar-progress
        1000    .confirm-overlay
    <dialog> (.cmdk, .attach-dialog) is not on this ladder at all -- opened
    with showModal() it is in the browser's top layer, above every z-index.
*/
@keyframes fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@keyframes rise-in {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: none; }
}

/* Containers: opacity only, and only while the animation runs. Opacity makes
   a stacking context; backwards keeps that from outliving the 420ms. */
.card {
    animation: fade-in 420ms var(--ease) backwards;
}

/* Leaf tiles carry a number and nothing that opens. Safe to move -- but the
   held identity matrix was still a containing block for any fixed child a
   tile might grow one day, so this one is backwards for both reasons. */
.stat {
    animation: rise-in 420ms var(--ease) backwards;
}
.stat-grid > .stat:nth-child(1) { animation-delay: 0ms; }
.stat-grid > .stat:nth-child(2) { animation-delay: 55ms; }
.stat-grid > .stat:nth-child(3) { animation-delay: 110ms; }
.stat-grid > .stat:nth-child(4) { animation-delay: 165ms; }
.stat-grid > .stat:nth-child(5) { animation-delay: 220ms; }
.stat-grid > .stat:nth-child(n+6) { animation-delay: 270ms; }
.content > .card:nth-child(1),
.grid > .card:nth-child(1) { animation-delay: 0ms; }
.content > .card:nth-child(2),
.grid > .card:nth-child(2) { animation-delay: 60ms; }
.content > .card:nth-child(3),
.grid > .card:nth-child(3) { animation-delay: 120ms; }
.content > .card:nth-child(n+4),
.grid > .card:nth-child(n+4) { animation-delay: 160ms; }

@media (prefers-reduced-motion: reduce) {
    .card, .stat, .hero, .meter__fill { animation: none; }
}

/* A card that is also a link or a button -- a module tile, a document row
   presented as a card -- turns toward the pointer the way a .stat already
   does, instead of sitting inert under a hand cursor. */
a.card:hover,
button.card:hover,
.card--interactive:hover {
    box-shadow: var(--rim), var(--shadow-md);
    border-color: rgb(var(--accent-rgb) / 30%);
    transform: translateY(-2px);
    transition: box-shadow var(--duration) var(--ease),
                border-color var(--duration) var(--ease),
                transform var(--duration) var(--ease);
}

/* ---- Stat tiles: an optional icon, lit the same way the value is -------- */
/*
    Opt-in: a tile that renders a span.stat__icon before its label gets a small
    glowing badge in the tile's own accent, floated to the top-right so it
    never competes with the number underneath it. A tile that renders no icon
    is unchanged -- this is a slot, not a redesign of the tile itself.
*/
.stat__icon {
    position: absolute;
    top: var(--space-4);
    right: var(--space-4);
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    border-radius: var(--radius-sm);
    background:
        linear-gradient(150deg, rgb(255 255 255 / 30%), transparent 60%),
        rgb(var(--stat-accent-rgb, var(--accent-rgb)) / 14%);
    color: rgb(var(--stat-accent-rgb, var(--accent-rgb)));
    box-shadow: inset 0 1px 0 rgb(255 255 255 / 25%);
}
.stat:has(.stat__icon) .stat__label { max-width: calc(100% - 40px); }

/* ---- Page head: an optional icon chip ahead of the title ---------------- */
.page-head__icon {
    display: grid;
    place-items: center;
    flex: none;
    width: 42px;
    height: 42px;
    border-radius: var(--radius-sm);
    background:
        linear-gradient(150deg, rgb(255 255 255 / 30%), transparent 60%),
        linear-gradient(180deg, rgb(var(--accent-rgb)) 0%, rgb(var(--accent-strong-rgb)) 100%);
    color: var(--accent-contrast);
    box-shadow:
        inset 0 1px 0 rgb(255 255 255 / 30%),
        0 6px 16px rgb(var(--accent-rgb) / 28%);
}

/* ---- Chip: a small fact pill, quieter than a status .badge -------------- */
/*
    Used where a page-head or a hero used to write "Code . Designation .
    Department" as one run-on line of muted text. A chip carries one fact
    each, so the line wraps cleanly on a narrow screen instead of clipping
    mid-word, and an icon can sit against a single fact rather than the whole
    line.
*/
.chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 11px 3px 9px;
    border-radius: var(--radius-pill);
    background: rgb(var(--neutral-rgb) / 10%);
    border: 1px solid var(--glass-line);
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    white-space: nowrap;
}
.chip svg { opacity: 0.7; flex: none; }
.chip--accent {
    background: rgb(var(--accent-rgb) / 10%);
    border-color: rgb(var(--accent-rgb) / 24%);
    color: rgb(var(--accent-strong-rgb));
}

/*
    The advanced-filter panel's applied-filter row: one removable chip per
    column currently narrowing the list, plus a plain-text "clear all". Reuses
    the fact-pill chip above rather than a second component, since that is
    exactly what an applied filter is -- one fact, removable.
*/
.chip-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    padding: 0 var(--space-4) var(--space-4);
}
a.chip--removable {
    background: rgb(var(--accent-rgb) / 8%);
    border-color: rgb(var(--accent-rgb) / 20%);
    color: rgb(var(--accent-strong-rgb));
    text-decoration: none;
    cursor: pointer;
}
a.chip--removable:hover {
    background: rgb(var(--accent-rgb) / 16%);
    border-color: rgb(var(--accent-rgb) / 36%);
}
a.chip--removable svg { opacity: 0.85; }
.chip--clear {
    border: none;
    background: none;
    color: var(--text-muted);
    text-decoration: underline;
    font-size: var(--text-sm);
    padding: 3px 4px;
}
.chip--clear:hover { color: var(--text); }

/* A from/to pair sharing one field's width, for a range filter. */
.field__range {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}
.field__range .input { min-width: 0; flex: 1 1 0; }
.field__range-sep { color: var(--text-muted); flex: none; }

/* ---- Hero: the banner header for a single-record profile screen -------- */
/*
    Opt-in wrapper for a page-head that is about one person or one document
    rather than a list -- the employee profile today, and anywhere else that
    wants the same treatment. It is .page-head's spacing and slot structure
    with a lit backdrop and a larger avatar; a screen that does not use it
    keeps the plain .page-head it already has.
*/
.hero {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-5);
    margin-bottom: var(--space-5);
    border-radius: var(--radius-lg);
    background:
        linear-gradient(120deg, rgb(var(--accent-rgb) / 14%), transparent 55%),
        linear-gradient(300deg, rgb(var(--bloom-2-rgb) / 12%), transparent 50%),
        var(--glass-panel);
    border: 1px solid var(--glass-line);
    box-shadow: var(--rim), var(--shadow-sm);
    overflow: hidden;
    /* Fade, not rise: a hero carries action buttons and can carry a control,
       so it must not become a containing block for a fixed popover. backwards
       rather than both for the same reason .card is -- a filling opacity
       animation is a permanent stacking context, which traps a popover's
       z-index inside the hero. See the note on the two keyframes above. */
    animation: fade-in 420ms var(--ease) backwards;
}
.hero__avatar {
    flex: none;
    display: grid;
    place-items: center;
    width: 68px;
    height: 68px;
    border-radius: var(--radius-pill);
    background: linear-gradient(150deg, rgb(var(--accent-rgb)), rgb(var(--bloom-2-rgb)));
    color: #fff;
    font-family: var(--font-sans-display);
    font-size: var(--text-xl);
    font-weight: 700;
    letter-spacing: 0.02em;
    box-shadow:
        0 0 0 4px var(--surface),
        0 0 0 5px rgb(var(--accent-rgb) / 30%),
        0 10px 24px rgb(var(--accent-rgb) / 24%);
}
.hero__body { min-width: 0; flex: 1 1 320px; }
.hero__eyebrow {
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: rgb(var(--accent-strong-rgb));
    margin: 0 0 2px;
}
.hero h1 {
    font-family: var(--font-sans-display);
    font-size: var(--text-2xl);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-tighter);
    line-height: 1.15;
    margin: 0;
}
.hero__facts {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: var(--space-2);
}
.hero__actions {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
    align-items: center;
    margin-left: auto;
}
@media (max-width: 760px) {
    .hero__actions { margin-left: 0; width: 100%; }
}

/* ---- Avatar: a gradient tint instead of a flat one --------------------- */
/*
    Every avatar in the app -- table rows, the recent-joiners list, the hero
    above -- reads from the same two rules, so this one change carries
    everywhere a person's initials appear.
*/
.avatar {
    background:
        linear-gradient(150deg, rgb(255 255 255 / 30%), transparent 65%),
        rgb(var(--accent-rgb) / 16%);
    box-shadow: inset 0 1px 0 rgb(255 255 255 / 20%);
}

/* ---- Avatar: the person's picture over their initials ------------------ */
/*
    The UserAvatar view component draws the initials always and, for a person who has chosen a picture, the picture on top of them
    (and account-picture.js adds or removes it after a change). The picture is clipped to the circle by the avatar itself, so a
    larger or smaller avatar needs no rule of its own; if it fails to load, its empty alt text leaves the initials showing.
*/
.avatar--photo { position: relative; overflow: hidden; }
.avatar__img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: inherit;
}

/* ---- Meter: the bar grows in rather than appearing already full -------- */
/*
    A transform, not the width the inline style already sets -- animating
    width itself would fight the value the view wrote for the final state.
    scaleX from the left edge reaches the same finished bar without touching
    the property that carries the real number.
*/
@keyframes meter-grow { from { transform: scaleX(0); } }
.meter__fill {
    transform-origin: left center;
    /* backwards, not both: the delay needs the held scaleX(0), the finished
       bar does not need a held matrix -- and a held matrix is a stacking
       context and a fixed-position containing block for good. */
    animation: meter-grow 620ms var(--ease) backwards;
    animation-delay: 120ms;
}
@media (prefers-reduced-motion: reduce) {
    .meter__fill { animation: none; }
}

/* ============================ Document panel: a column, not a band */
/*
    The identity panel -- Draft Number, Document Number, Status, Dates, Branch
    -- sits down the right of the form as an ordinary card in an ordinary
    two-column layout: the same border, the same shadow, the same radius as
    every other card on the page, because it IS another card on the page, not
    a different kind of surface bolted on beside it.

    Two things were tried before this and both made it look like it belonged
    to a different screen: rendered full width it took the whole page for
    eight short fields, and docked as a fixed drawer against the window edge
    it carried its own shadow and z-index and sat in front of the topbar
    rather than inside the content column with everything else. Neither
    looked like part of this application, because nothing else in it is laid
    out that way. A plain grid column is.

    Placed from CSS rather than by restructuring each view, same as before:
    the panel is a direct child of a <form> on the setup screens and of
    .content on the HR ones, so the column is described once here and every
    screen rendering the partial picks it up.
*/
form:has(> .doc-aside),
.content:has(> .doc-aside) {
    display: grid;
    gap: var(--space-4);
    align-items: start;
    /*
        The column's own width is set by which state the panel is in -- see
        the two rules below. Both are declared here as the default, open
        width so nothing is unstyled before either matches.

        NOT animated. grid-template-columns transitions by interpolating the
        track list's minmax() functions frame by frame, and that
        interpolation is not reliably defined between two different max
        terms (26% against auto) -- mid-transition it can compute a track
        narrower than minmax()'s own stated floor, which briefly breaks the
        15rem minimum this is supposed to guarantee. Better an instant,
        correct width than a smooth, wrong one for a few frames.
    */
    grid-template-columns: minmax(0, 1fr) minmax(15rem, 26%);
}

/*
    Where app.js has gathered the reading column into one element
    (initDocPanelColumn), the two sides of the row are told to match height --
    see .doc-reading and the align-self note below. align-items: start stays
    the default above for the no-script case, where the reading "column" is
    really several independent grid items and stretching would size the panel
    off whichever of them the browser happens to auto-place tallest, not the
    true bottom of the form.
*/
.doc-panel-wrapped { align-items: stretch; }

/*
    THE COLUMN ITSELF RESIZES, NOT JUST THE CARD IN IT.

    Collapsing used to shrink the panel's height and leave its column at full
    width, so the reading column beside it never got any wider -- the panel
    folded up but the form did not get the room back. `:has` reads the
    <details> element's own open attribute, so the grid's second column
    tracks it directly: wide while the panel is open, down to the width of
    its collapsed card while it is not. The reading column is minmax(0, 1fr)
    throughout, so it is what expands to take the space the panel gives up.
*/
form:has(> .doc-aside[open]),
.content:has(> .doc-aside[open]) { grid-template-columns: minmax(0, 1fr) minmax(15rem, 26%); }
form:has(> .doc-aside:not([open])),
.content:has(> .doc-aside:not([open])) { grid-template-columns: minmax(0, 1fr) auto; }

/*
    Everything that is not the panel stays in the reading column -- the
    no-script fallback. With app.js running, initDocPanelColumn has already
    gathered these same elements inside one .doc-reading wrapper (below) and
    this rule no longer matches any of them, because they are no longer
    direct children of the grid; it stays for a screen with scripting off,
    which then gets the plain per-card stacking this replaces rather than a
    layout with nothing telling it where column 1 is.
*/
form:has(> .doc-aside) > *:not(.doc-aside),
.content:has(> .doc-aside) > *:not(.doc-aside) { grid-column: 1; }

/*
    The wrapper initDocPanelColumn builds. A plain flex column with its own
    gap, standing in for the grid gap the direct children used to get --
    they are no longer grid items themselves, so grid's own `gap` no longer
    runs between them, and this is what puts the same var(--space-4) back.
*/
.doc-reading {
    grid-column: 1;
    grid-row: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}
/* The flex gap does this job now; a card's own margin would double it. */
.doc-reading > .card,
.doc-reading > .alert { margin-bottom: 0; }

/*
    Row 1 of column 2, for a screen where scripting never ran. NOT STICKY here:
    a sticky grid item is bounded by the whole grid container rather than by its
    own row, so beside the action strip it slid down over Clear, Cancel and
    Close. With scripting the panel is sticky inside .doc-panel-main below.

    align-self: stretch, not the grid's own align-items: start, once the
    panel is shut: shut it carries no body height to size itself by, so
    without this it would be exactly as tall as its own title bar -- a
    narrow tab sitting at the top rather than a strip running the height of
    the form beside it.

    Pinned to row 1 rather than spanning several: with .doc-reading gathering
    the rest of the row into one item, row 1 IS the whole reading column on
    any screen app.js has run on, so a span was never needed to begin with --
    .doc-panel-wrapped's align-items: stretch reaches the true bottom by
    matching the row's own height, the same tool used everywhere else two
    grid items are meant to read as equal height.*/
form:has(> .doc-aside) > .doc-aside,
.content:has(> .doc-aside) > .doc-aside {
    grid-column: 2;
    grid-row: 1;
    margin-bottom: 0;
}
.doc-aside:not([open]) { align-self: stretch; }

/*
    THE TWO COLUMNS, WITH THE PANEL STICKY AND THE STRIP BELOW THEM.

    app.js (initDocPanelColumn) puts the reading column and the panel in this
    grid and leaves the action strip beside it, not in it. The panel is sticky
    here, and a sticky grid item is bounded by its grid container: with the strip
    a row of the same grid, the panel slid down at the end of the page and sat on
    top of Clear, Cancel and Close. In a container that holds only the two
    columns it cannot get below them, so it follows the page while the form is
    on screen and stops where the form stops. The strip runs the full width,
    after both, as on every other document screen.
*/
.doc-panel-main {
    display: grid;
    gap: var(--space-4);
    align-items: start;
    grid-template-columns: minmax(0, 1fr) minmax(15rem, 26%);
}
.doc-panel-main:has(> .doc-aside:not([open])) { grid-template-columns: minmax(0, 1fr) auto; }
.doc-panel-main > .doc-reading { grid-column: 1; grid-row: 1; }
.doc-panel-main > .doc-aside {
    grid-column: 2;
    grid-row: 1;
    position: sticky;
    top: calc(var(--topbar-height) + var(--space-4));
    margin-bottom: 0;
    max-height: calc((100dvh / var(--zoom, 1)) - var(--topbar-height) - var(--space-8));
    overflow-y: auto;
}
.doc-panel-main > .doc-aside:not([open]) { align-self: stretch; }
.doc-panel-wrapped > .form-actions { margin-top: var(--space-4); }

@media (max-width: 1000px) {
    .doc-panel-main { display: block; }
    .doc-panel-main > .doc-aside {
        position: static;
        max-height: none;
        overflow: visible;
        margin-bottom: var(--space-4);
    }
    .doc-panel-main > .doc-aside:not([open]) { align-self: auto; }
}

/* The grid owns the spacing now; the stacking margins would double it. */
form:has(> .doc-aside) > .card:not(:last-child),
.content:has(> .doc-aside) > .card:not(:last-child),
.content:has(> .doc-aside) > .alert:not(:last-child) { margin-bottom: 0; }

/* ---- The panel collapses sideways, not down ----------------------------- */
/*
    A <details>, so the state, the keyboard and the accessible name come from
    the element rather than from script. Nothing floats, nothing docks,
    nothing gets a shadow the rest of the page does not also have -- it is
    the same card, the same border, the same radius, open or shut.

    Shut, it is a narrow strip the height of the form beside it, carrying
    only the handle -- not a title bar at the top of a column still holding
    the full width. The column width is what actually gives the room back
    (see the grid-template-columns rules above); this is what the card looks
    like once that room is gone.
*/
.doc-aside__summary {
    cursor: pointer;
    list-style: none;
    user-select: none;
    display: flex;
    align-items: center;
    height: 100%;
}
.doc-aside__summary::-webkit-details-marker { display: none; }
.doc-aside__summary::marker { content: ""; }
.doc-aside__summary:hover { background: rgb(var(--accent-rgb) / 5%); }
.doc-aside__summary:focus-visible {
    outline: none;
    box-shadow: var(--ring-inset);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.doc-aside:not([open]) .doc-aside__summary {
    border-bottom: 0;
    justify-content: center;
    padding: var(--space-4) var(--space-2);
}
.doc-aside__label { display: flex; flex-direction: column; min-width: 0; }
.doc-aside:not([open]) .doc-aside__label { display: none; }

.doc-aside__handle {
    margin-left: auto;
    display: grid;
    place-items: center;
    flex: none;
    width: 28px;
    height: 28px;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    transition: background var(--duration-fast) var(--ease),
                color var(--duration-fast) var(--ease);
}
.doc-aside__summary:hover .doc-aside__handle {
    background: rgb(var(--neutral-rgb) / 12%);
    color: var(--text);
}
.doc-aside:not([open]) .doc-aside__handle { margin-left: 0; }

/* A narrow value column reads better stacked than as two cramped columns. */
.doc-aside .doc-identity { grid-template-columns: minmax(0, 1fr); gap: 2px; }
.doc-aside .doc-identity dt { margin-top: var(--space-2); }
.doc-aside .doc-identity dt:first-child { margin-top: 0; }

/*
    Below this the two columns stop being two: a 26% panel on a tablet is a
    column of wrapped labels. It goes back to a band, above the form.
*/
@media (max-width: 1000px) {
    form:has(> .doc-aside),
    .content:has(> .doc-aside) { display: block; }
    form:has(> .doc-aside) > .doc-aside,
    .content:has(> .doc-aside) > .doc-aside {
        position: static;
        max-height: none;
        overflow: visible;
        margin-bottom: var(--space-4);
    }

    /*
        The sideways collapse is a column trading width with the form beside
        it, and there is no column here -- the panel is stacked above the
        form instead. Shut, it goes back to being a plain title bar rather
        than the narrow, full-height strip the two-column layout uses.
    */
    .doc-aside:not([open]) { align-self: auto; }
    .doc-aside__summary { height: auto; }
    .doc-aside:not([open]) .doc-aside__summary {
        justify-content: flex-start;
        padding: var(--space-4) var(--space-5);
    }
    .doc-aside:not([open]) .doc-aside__label { display: flex; }
    .doc-aside:not([open]) .doc-aside__handle { margin-left: auto; }

    .doc-aside .doc-identity {
        grid-template-columns: minmax(7.5rem, auto) minmax(0, 1fr);
        gap: var(--space-2) var(--space-4);
    }
    .doc-aside .doc-identity dt { margin-top: 0; }
}

/* ===================================== The form's action strip */
/*
    The row that saves the document, given the weight of a toolbar rather than
    a line of text links. Save is the lit primary it always was; Cancel and
    Close were two identical ghost links, which made the destructive one and
    the harmless one look the same and neither look like a control.
*/
.form-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-3);
    border-top: 1px solid var(--glass-line);

    /*
        NOT PINNED. It was sticky to the viewport bottom, which sounded right
        -- Save always in reach -- but on the document screens it sits in the
        same row-2 as nothing else, below both the form AND the panel, so
        pinning it to the viewport made it hover in front of whichever one was
        still scrolling past, most visibly the bottom of the document panel:
        Branch and Department sat half-covered by a bar that had not actually
        been reached yet. Plain, static flow -- it appears once, after both
        columns, exactly where its position in the grid already puts it.

        The fill stays opaque rather than a tint, because it still sits above
        the panel and the form as the last thing in the column and a 7% wash
        over their bottom edge would be unreadable if it were.
    */
    background:
        linear-gradient(180deg, rgb(255 255 255 / 30%), transparent 70%),
        color-mix(in srgb, var(--surface) 92%, rgb(var(--neutral-rgb)));
    box-shadow: inset 0 1px 0 rgb(255 255 255 / 35%);
}

/*
    THE STRIP RUNS THE WHOLE WIDTH.

    It is rendered as a child of the <form> rather than inside the last card,
    so where the form is the two-column document layout it spans both columns
    instead of stopping at the left one and leaving the save row two thirds
    wide with empty page beside it.

    Standing on its own it needs its own edges: inside a card it inherited the
    card's border and bottom corners, and it has neither to inherit now.
*/
/* Spans both columns of the document grid, same as before: the strip runs
   the full width of the form, not just the reading column beside the panel. */
form:has(> .doc-aside) > .form-actions,
.content:has(> .doc-aside) > .form-actions { grid-column: 1 / -1; }

.form-actions {
    border: 1px solid var(--glass-line);
    border-radius: var(--radius-lg);
    padding: var(--space-3) var(--space-4);
}

/*
    THE SAME STRIP ON THE BESPOKE DOCUMENT SCREENS. Their footer (_DocumentFormActions)
    was a bare row of buttons; it now has the Setup forms' strip around it, so the
    footer looks the same on every document. Not when it is already inside a strip, a
    card's own foot or body, and not when it is empty (a screen that only hosts the
    Attach button until the script adds it).
*/
.doc-form-actions:has(.btn) {
    box-sizing: border-box;
    margin-top: var(--space-4);
    border: 1px solid var(--glass-line);
    border-radius: var(--radius-lg);
    padding: var(--space-3) var(--space-4);
    background:
        linear-gradient(180deg, rgb(255 255 255 / 30%), transparent 70%),
        color-mix(in srgb, var(--surface) 92%, rgb(var(--neutral-rgb)));
    box-shadow: inset 0 1px 0 rgb(255 255 255 / 35%);
}
:is(.form-actions, .card__foot, .card__body) .doc-form-actions:has(.btn) {
    margin-top: 0;
    border: 0;
    border-radius: 0;
    padding: 0;
    background: none;
    box-shadow: none;
}
/*
    RIGHT-ALIGNED. A toolbar of this many buttons read as a ragged left edge
    that trailed off into empty strip; every other action bar in the app (a
    dialog's OK/Cancel, a card's own footer) ends flush with the right edge,
    and this one now matches them instead of being the exception.
*/
.form-actions .row { gap: var(--space-2); flex-wrap: wrap; align-items: center; flex: 1 1 auto; justify-content: flex-end; }

/* A filled but quiet button: reads as a control, does not compete with Save. */
.btn--soft {
    background: rgb(var(--neutral-rgb) / 12%);
    border-color: rgb(var(--neutral-rgb) / 22%);
    color: var(--text);
    box-shadow: var(--rim-soft);
}
.btn--soft:hover {
    background: rgb(var(--neutral-rgb) / 18%);
    border-color: rgb(var(--neutral-rgb) / 30%);
}

/*
    Parking a document is a save, so Draft keeps the accent family -- tinted
    rather than filled, because it sits next to the filled Save and two solid
    blues in a row would read as one wide button.
*/
.btn--soft-accent {
    background:
        linear-gradient(180deg, rgb(255 255 255 / 30%), transparent 70%),
        rgb(var(--accent-rgb) / 11%);
    border-color: rgb(var(--accent-rgb) / 26%);
    color: rgb(var(--accent-strong-rgb));
    box-shadow: var(--rim-soft);
}
.btn--soft-accent:hover {
    background:
        linear-gradient(180deg, rgb(255 255 255 / 34%), transparent 70%),
        rgb(var(--accent-rgb) / 18%);
    border-color: rgb(var(--accent-rgb) / 38%);
    color: rgb(var(--accent-strong-rgb));
}

/*
    Sending it on is the one that leaves your hands, so it gets its own colour
    rather than a third shade of the accent -- lit the same way .btn--primary
    is, since it is just as much a commit.
*/
.btn--success {
    background: linear-gradient(180deg,
        rgb(var(--success-rgb)) 0%,
        rgb(var(--success-rgb) / 82%) 100%);
    border-color: rgb(var(--success-rgb));
    color: var(--on-success);
    box-shadow:
        inset 0 1px 0 rgb(255 255 255 / 28%),
        0 1px 2px rgb(var(--success-rgb) / 30%),
        0 6px 18px rgb(var(--success-rgb) / 28%);
}
.btn--success:hover {
    filter: brightness(1.05);
    color: var(--on-success);
    box-shadow:
        inset 0 1px 0 rgb(255 255 255 / 32%),
        0 2px 4px rgb(var(--success-rgb) / 32%),
        0 10px 26px rgb(var(--success-rgb) / 32%);
}

/* Cancel abandons what was typed, so it carries the warning colour without
   the shout of a filled danger button. */
.btn--soft-danger {
    background: rgb(var(--danger-rgb) / 9%);
    border-color: rgb(var(--danger-rgb) / 24%);
    color: rgb(var(--danger-rgb));
    box-shadow: var(--rim-soft);
}
.btn--soft-danger:hover {
    background: rgb(var(--danger-rgb) / 15%);
    border-color: rgb(var(--danger-rgb) / 36%);
    color: rgb(var(--danger-rgb));
}

/*
    ONE STATE TREATMENT FOR EVERY BUTTON VARIANT.
    hover, keyboard focus and press never change the label's colour or swap the
    fill for the neutral surface: filled variants (primary, success, danger)
    darken, tinted ones (soft, soft-danger, soft-accent, danger-ghost) deepen
    their tint, and every variant shows the same visible focus ring. The
    contrast between label and fill therefore stays the one the base rule set.
    tests/SmartErp.Next.Tests/ButtonStateTests.cs guards this.
*/
.btn:focus-visible {
    outline: 2px solid var(--border-focus);
    outline-offset: 2px;
}
.btn--primary:focus-visible,
.btn--success:focus-visible,
.btn--danger:focus-visible { filter: brightness(1.04); }
.btn--success:active,
.btn--danger:active {
    filter: brightness(0.92);
    transform: scale(0.985);
}
.btn--soft:focus-visible,
.btn--soft-accent:focus-visible,
.btn--soft-danger:focus-visible,
.btn--danger-ghost:focus-visible { outline-offset: 1px; }
.btn--soft:active { background: rgb(var(--neutral-rgb) / 24%); }
.btn--soft-danger:active { background: rgb(var(--danger-rgb) / 22%); }
.btn--ghost:active { background: var(--surface-hover); color: var(--text); }

/*
    One employee's attendance calendar (Register/Employee.cshtml) -- a month
    grid, one cell a day, coloured by that day's own attendance type. Colours
    reuse the badge palette's own custom properties so a day reads the same
    "good/bad" language the rest of the app already uses, and follows dark
    mode for free.
*/
.attn-calendar {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: var(--space-1);
}
.attn-calendar__head {
    text-align: center;
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--text-muted);
    padding-bottom: var(--space-1);
}
.attn-day {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-height: 3.75rem;
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    text-decoration: none;
    color: var(--text);
    background: var(--surface-sunken);
    transition: border-color var(--duration-fast) var(--ease);
}
.attn-day:hover { border-color: var(--border-strong); }
.attn-day--pad { background: transparent; }
.attn-day--selected { border-color: var(--accent); border-width: 2px; }
.attn-day__num { font-size: var(--text-sm); font-weight: 600; }
.attn-day__type { font-size: var(--text-xs); font-weight: 700; text-transform: uppercase; }
.attn-day__shift { font-size: 10px; color: var(--text-muted); }

.attn-day--present { background: rgb(var(--success-rgb) / 14%); }
.attn-day--present .attn-day__type { color: rgb(var(--success-rgb)); }
.attn-day--absent { background: rgb(var(--danger-rgb) / 14%); }
.attn-day--absent .attn-day__type { color: rgb(var(--danger-rgb)); }
.attn-day--holiday { background: rgb(var(--info-rgb) / 14%); }
.attn-day--holiday .attn-day__type { color: rgb(var(--info-rgb)); }
.attn-day--leave { background: rgb(var(--warning-rgb) / 14%); }
.attn-day--leave .attn-day__type { color: rgb(var(--warning-rgb)); }
.attn-day--other { background: rgb(var(--neutral-rgb) / 14%); }
.attn-day--blank { color: var(--text-subtle); }

tr.is-selected td { background: rgb(var(--accent-rgb) / 8%); }

/* A select locked by Auto Calculation: still posts its value, cannot be changed.
   Their grid disables the control instead, which it can afford because jqGrid
   posts from its own row data rather than from the DOM. */
select.is-locked { pointer-events: none; opacity: .6; }

/*
    A titled group inside a card body -- the legacy document forms bracket
    related header fields in a <fieldset> with a <legend> ("Payment/Shipping
    Details", "Custom Fields", "Bill To Party"). A real fieldset draws a box
    that fights the card it sits inside, so the grouping is carried by a rule
    and a small caps heading instead, which reads the same and nests cleanly.
*/
.form-section {
    margin-top: var(--space-5);
    padding-top: var(--space-4);
    border-top: 1px solid var(--border);
    min-width: 0;
}
.form-section__title {
    margin: 0 0 var(--space-3);
    font-size: .75rem;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--text-muted);
}

/* ==========================================================================
   Class names the views use that nothing defined
   --------------------------------------------------------------------------
   Found 2026-10-03 while QA-ing the module redesign. Each of these is written
   in markup across many views and had no rule anywhere, so the element fell
   back to unstyled. Counted with grep over Areas/ and Views/:

       .doc-code-chip--accent    41 views, 11 areas (356x on one GL render)
       .results-header__summary  17 views
       .results-count            17 views
       .results-label            17 views
       .results-header__meta      5 views
       .badge--sm                 2 views

   Defining them here fixes every use at once, which is the right shape of fix
   for a name used in 41 files -- renaming the markup in 41 views would not be.

   Deliberately NOT defined: .badge--pill, written in 100 views. .badge already
   sets border-radius: var(--radius-pill) (999px), so the modifier asks for what
   the base class already gives and its absence changes nothing. Left alone
   rather than adding a rule that does nothing.

   Also deliberately NOT defined, re-checked 2026-10-04: .alert--info, written in
   8 views. .alert declares rgb(var(--alert-rgb, var(--info-rgb))) for its border,
   background and icon, so --alert-rgb already falls back to info and a bare
   .alert IS the info alert. Same reasoning as .badge--pill above.

   ========================================================================== */

/* The accent treatment for a document-code chip: same geometry as the base
   chip, tinted with the palette's accent so a primary code reads as a link.
   Uses --accent-rgb so it tracks all four palettes and both themes; the base
   chip's neutral surface/border is what it was falling back to. */
.doc-code-chip--accent,
.sales-code-chip--accent,
.proc-code-chip--accent,
.inv-code-chip--accent {
    background: rgb(var(--accent-rgb) / 10%);
    border-color: rgb(var(--accent-rgb) / 30%);
    color: rgb(var(--accent-rgb));
}

.doc-code-chip--accent:hover,
.sales-code-chip--accent:hover,
.proc-code-chip--accent:hover,
.inv-code-chip--accent:hover {
    background: rgb(var(--accent-rgb) / 16%);
    border-color: rgb(var(--accent-rgb) / 45%);
}

/* The results-header family. .results-header__info/__title/__count were already
   defined; the redesign also wrote these four names for the same bar, so they
   are aliased onto the same treatment rather than left unstyled. */
.results-header__summary,
.results-header__meta {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.results-count {
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--text);
    letter-spacing: -0.01em;
}

.results-label {
    font-size: 0.75rem;
    color: var(--text-muted);
}

/* A denser badge for inline use in a grid cell. */
.badge--sm {
    padding: 0 7px;
    font-size: calc(var(--text-xs) - 1px);
    line-height: 1.6;
    gap: 4px;
}

/* --------------------------------------------------------------------------
   .empty-state-modern -- alias of the .empty component
   --------------------------------------------------------------------------
   The redesign wrote this name in 13 views. Two (Sales) were converted back to
   .empty; 11 remain in HumanResource. Nothing defined it, so those blocks
   rendered as left-aligned unstyled text. Aliasing is the right shape of fix
   here: it repairs all 11 at once, and the markup stays valid if someone later
   converts them to .empty as the Sales pair were.
   -------------------------------------------------------------------------- */
.empty-state-modern {
    display: grid;
    place-items: center;
    gap: var(--space-3);
    padding: var(--space-12) var(--space-5);
    text-align: center;
    color: var(--text-muted);
}

.empty-state-modern__icon {
    display: grid;
    place-items: center;
    width: 46px; height: 46px;
    border-radius: var(--radius-pill);
    background: var(--surface-sunken);
    color: var(--text-subtle);
}

.empty-state-modern__title { font-size: var(--text-md); font-weight: 600; color: var(--text); }
.empty-state-modern__desc  { max-width: 40ch; font-size: var(--text-base); }

/* --------------------------------------------------------------------------
   Badge modifiers the redesign introduced without rules
   --------------------------------------------------------------------------
   .badge--glow  20 views   .badge--brand  6 views
   .badge--brand is an exact alias of the pre-existing .badge--accent (app.css
   line 2259); kept because 6 views write the --brand name. Both drive off
   --badge-rgb, which .badge already uses, so they compose with
   every existing colour variant instead of fixing a hue.
   -------------------------------------------------------------------------- */
.badge--glow {
    box-shadow: var(--rim-soft), 0 0 0 3px rgb(var(--badge-rgb, var(--neutral-rgb)) / 14%);
}

.badge--brand {
    --badge-rgb: var(--accent-rgb);
}

/* --------------------------------------------------------------------------
   Remaining undefined names from the module redesign
   --------------------------------------------------------------------------
   Reported by the Admin/Fleet sweep. Each is written in markup with no rule.
   The visible one is .filter-chip__remove: without a rule the x inherits the
   link accent and renders bright blue inside an otherwise muted chip, on the
   11 Fleet list views. The rest are quieter, but an undefined class is a
   latent surprise, so they are given the treatment their siblings already use.
   -------------------------------------------------------------------------- */
.filter-chip__remove {
    display: inline-grid;
    place-items: center;
    width: 14px; height: 14px;
    margin-left: 0.15rem;
    border: 0;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--text-subtle);
    font-size: 11px;
    line-height: 1;
    cursor: pointer;
}

.filter-chip__remove:hover {
    background: rgb(var(--danger-rgb) / 14%);
    color: rgb(var(--danger-rgb));
}

/* Same family as .results-header__title / __count, which are already defined. */
.results-header__label  { font-size: var(--text-xs); color: var(--text-muted); }
.results-header__active { font-size: var(--text-xs); color: rgb(var(--accent-rgb)); font-weight: 600; }

.font-mono { font-family: var(--font-mono, ui-monospace, monospace); }

/* A field row whose content is buttons rather than an input. */
.field--actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}

/* .card__meta exists only in inventory.css and sales.css, which _Layout does
   not load on Fleet pages -- 8 Fleet Edit views use it. Defined here so it
   resolves everywhere rather than only in two modules. */
.card__meta {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
    font-size: var(--text-xs);
    color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Document Identity: collapsible
   --------------------------------------------------------------------------
   The panel is a <details> sharing initDocPanel with the document aside (see
   _DocumentIdentity.cshtml). These rules only make the <summary> behave like
   the heading row it replaced -- marker removed, a chevron in its place, and
   the bottom rule dropped while shut so a closed panel reads as one line
   rather than a heading with nothing under it.
   -------------------------------------------------------------------------- */
.doc-identity-box > summary.doc-identity-box__title {
    cursor: pointer;
    list-style: none;
    user-select: none;
}

.doc-identity-box > summary.doc-identity-box__title::-webkit-details-marker { display: none; }

.doc-identity-box > summary.doc-identity-box__title::after {
    content: "";
    width: 7px; height: 7px;
    margin-left: auto;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg) translate(-2px, -2px);
    transition: transform .15s var(--ease, ease);
    opacity: .7;
    flex: none;
}

.doc-identity-box[open] > summary.doc-identity-box__title::after {
    transform: rotate(225deg) translate(-2px, -2px);
}

.doc-identity-box:not([open]) > summary.doc-identity-box__title {
    margin-bottom: 0;
    padding-bottom: 0;
    border-bottom: 0;
}

.doc-identity-box > summary.doc-identity-box__title:focus-visible {
    outline: 2px solid rgb(var(--accent-rgb));
    outline-offset: 2px;
    border-radius: var(--radius-sm, 6px);
}

/* A destructive action that should not shout: ghost geometry, danger colour.
   Written on three Delete buttons (CompanyBonus, HrJournalVoucher and
   OvertimeBulk edit screens) and defined nowhere, so they were rendering as the
   plain neutral .btn -- a delete that did not read as one. Unlike .alert--info
   and .badge--pill the base class gives nothing here, so this one needs a rule.
   Transparent like .btn--ghost, tinted like .btn--soft-danger on hover. */
.btn--danger-ghost {
    background: transparent;
    border-color: transparent;
    color: rgb(var(--danger-rgb));
    box-shadow: none;
}

.btn--danger-ghost:hover {
    background: rgb(var(--danger-rgb) / 12%);
    border-color: rgb(var(--danger-rgb) / 30%);
    color: rgb(var(--danger-rgb));
}

/*
    System / Super Admin Security banner (_SystemSecurityHeader.cshtml).

    Marks the screens that grant and revoke the company's rights, so they do not
    read as ordinary setup forms. Only tokens defined in tokens.css are used
    here: --radius-md does not exist in this app and would have taken the whole
    border-radius declaration with it.
*/
.sys-sec {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    margin-bottom: var(--space-4);
    border: 1px solid rgb(var(--danger-rgb) / 35%);
    border-left: 3px solid rgb(var(--danger-rgb));
    border-radius: var(--radius);
    background: rgb(var(--danger-rgb) / 8%);
}

.sys-sec__icon {
    color: rgb(var(--danger-rgb));
    flex-shrink: 0;
    margin-top: 1px;
}

.sys-sec__title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
    color: var(--text);
    font-weight: var(--weight-semibold);
}

.sys-sec__sub {
    color: var(--text-muted);
    font-size: var(--text-sm);
    margin-top: 2px;
}

/*
    A shared setup in the sidebar (_NavLink.cshtml).

    The screen belongs to another module and is gated on that module's
    permission, so it is marked rather than left to look like one of this
    module's own. Deliberately quiet: a marker, not a warning. Only tokens
    defined in tokens.css are used.
*/
.nav-link__shared {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    color: var(--text-muted);
    opacity: 0.65;
    flex-shrink: 0;
}

.nav-link--shared .nav-link__text {
    font-style: italic;
}

.nav-link--shared:hover .nav-link__shared,
.nav-link--shared[aria-current="page"] .nav-link__shared {
    opacity: 1;
}

/* ------------------------------------------------------ Document layout

   A document form is two columns: the first section beside the document's
   identity panel (top right), the next sections alternating left and right
   beneath it, so no section waits under a short one with a hole beside it.
   A section that needs the whole width takes both columns (doc-layout__wide).
*/
.doc-layout {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-4) var(--space-6);
    align-items: start;
}

.doc-layout > .card { margin: 0; min-width: 0; }

.doc-layout__side {
    grid-column: 2;
    grid-row: 1;
    min-width: 0;
}

.doc-layout > .doc-layout__wide { grid-column: 1 / -1; }


@media (max-width: 60rem) {
    .doc-layout { grid-template-columns: minmax(0, 1fr); }
    .doc-layout__side { grid-column: 1; grid-row: auto; order: -1; }
}

/* ------------------------------------------------------------ Attachments panel
   The shared list + upload form (_AttachmentList.cshtml), in the Attach Document
   dialog and inline on the employee Documents tab. Logical properties throughout
   so it mirrors under dir="rtl"; every grid child has min-width:0 so a long file
   name wraps instead of pushing the dialog into a horizontal scrollbar. */
.attach { display: flex; flex-direction: column; min-width: 0; }
.attach__alert { margin: var(--space-3) var(--space-4) 0; }
.attach__section { padding: var(--space-4); min-width: 0; }
.attach__bar { display: flex; align-items: center; justify-content: space-between; margin-block-end: var(--space-3); }
.attach__heading {
    display: flex; align-items: center; gap: var(--space-2);
    margin: 0; font-size: var(--text-md); font-weight: 600;
}
.attach__empty {
    display: flex; align-items: center; justify-content: center; gap: var(--space-3);
    padding: var(--space-6) var(--space-4);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius);
    color: var(--text-muted);
    background: var(--surface-sunken);
}

.attach-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.attach-item {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "tile main actions" ". meta meta";
    column-gap: var(--space-3);
    row-gap: var(--space-2);
    align-items: start;
    padding: var(--space-3);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    min-width: 0;
}
.attach-item:hover { border-color: var(--border-strong); background: var(--surface-hover); }
.attach-item__tile {
    grid-area: tile;
    display: inline-flex; align-items: center; justify-content: center;
    inline-size: 40px; block-size: 40px;
    border-radius: var(--radius-sm);
    font-size: 10.5px; font-weight: 700; letter-spacing: 0.02em;
    background: rgb(var(--neutral-rgb) / 14%);
    color: var(--text);
    border: 1px solid rgb(var(--neutral-rgb) / 26%);
}
.attach-item__tile--pdf   { background: rgb(var(--danger-rgb) / 12%);  border-color: rgb(var(--danger-rgb) / 30%);  color: rgb(var(--danger-rgb)); }
.attach-item__tile--doc   { background: rgb(var(--accent-rgb) / 12%);  border-color: rgb(var(--accent-rgb) / 30%);  color: rgb(var(--accent-strong-rgb)); }
.attach-item__tile--sheet { background: rgb(var(--success-rgb) / 12%); border-color: rgb(var(--success-rgb) / 30%); color: rgb(var(--success-rgb)); }
.attach-item__tile--image { background: rgb(var(--warning-rgb) / 14%); border-color: rgb(var(--warning-rgb) / 34%); color: var(--text); }
.attach-item__tile--zip   { background: rgb(var(--neutral-rgb) / 18%); }
.attach-item__tile--other { background: rgb(var(--neutral-rgb) / 12%); }
.attach-item__main { grid-area: main; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.attach-item__name { font-weight: 600; overflow-wrap: anywhere; }
.attach-item__tags { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.attach-item__tags .badge { max-inline-size: 100%; overflow: hidden; text-overflow: ellipsis; }
.attach-item__sub { color: var(--text-muted); font-size: var(--text-xs); overflow-wrap: anywhere; }
.attach-item__actions { grid-area: actions; display: flex; align-items: center; gap: 2px; }
.attach-item__actions form { margin: 0; display: inline; }
.attach-item__meta {
    grid-area: meta; margin: 0;
    display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-5);
    font-size: var(--text-xs); color: var(--text-muted);
}
.attach-item__meta > div { display: flex; gap: var(--space-1); min-width: 0; }
.attach-item__meta dt { font-weight: 600; }
.attach-item__meta dt::after { content: ":"; }
.attach-item__meta dd { margin: 0; overflow-wrap: anywhere; }

.attach-form {
    display: flex; flex-direction: column; gap: var(--space-3);
    padding: var(--space-4) var(--space-4) 0;
    border-block-start: 1px solid var(--border-subtle);
    background: var(--surface-sunken);
    min-width: 0;
}
.attach-form__title { font-weight: 600; font-size: var(--text-md); }

.attach-drop {
    position: relative;
    display: flex; align-items: center; gap: var(--space-3);
    padding: var(--space-4);
    border: 2px dashed var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface);
    cursor: pointer;
    transition: border-color var(--duration-fast) var(--ease), background var(--duration-fast) var(--ease);
}
.attach-drop:hover,
.attach-drop.is-dragover,
.attach-drop:focus-within { border-color: var(--accent); background: rgb(var(--accent-rgb) / 6%); }
.attach-drop.has-file { border-style: solid; border-color: rgb(var(--success-rgb) / 55%); }
.attach-drop__icon {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    inline-size: 44px; block-size: 44px; border-radius: 50%;
    background: rgb(var(--accent-rgb) / 12%); color: rgb(var(--accent-strong-rgb));
}
.attach-drop__text { display: flex; flex-direction: column; min-width: 0; gap: 2px; }
.attach-drop__chosen { color: var(--text-muted); font-size: var(--text-sm); overflow-wrap: anywhere; }
.attach-drop.has-file .attach-drop__chosen { color: var(--text); font-weight: 600; }
/* The real input stretches over the whole zone, invisible: click, keyboard and a
   file dragged from the desktop all reach it with no script. */
.attach-drop__input {
    position: absolute; inset: 0; inline-size: 100%; block-size: 100%;
    opacity: 0; cursor: pointer;
}
.attach-drop:has(.attach-drop__input:focus-visible) { outline: 2px solid var(--border-focus); outline-offset: 2px; }

.attach-form__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); }
.attach-form__col { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.attach-form__col .field { grid-column: auto; }
.attach-form__choices { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-4); min-block-size: var(--control-h); align-items: center; }
.attach-form__foot {
    position: sticky; inset-block-end: 0; z-index: 1;
    display: flex; justify-content: flex-end; gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
    margin-inline: calc(var(--space-4) * -1);
    border-block-start: 1px solid var(--border-subtle);
    background: var(--surface);
}
@media (max-width: 640px) {
    .attach-form__grid { grid-template-columns: minmax(0, 1fr); }
    .attach-item { grid-template-areas: "tile main actions" "meta meta meta"; }
}
@media (prefers-reduced-motion: reduce) { .attach-drop { transition: none; } }

.attach-dialog__title .badge[hidden] { display: none; }

/* Income tax statement and EOBI return: labels are sentences, not one word. */
.detail-list--wide .detail-list__row { grid-template-columns: minmax(0, 22rem) minmax(0, 1fr); }
@media (max-width: 560px) { .detail-list--wide .detail-list__row { grid-template-columns: minmax(0, 1fr); } }

/* ============================================================================ Zoom
   Application-wide zoom, 75% to 125%, set by zoom.js through --zoom on <html> (and applied before first
   paint by _ThemeBoot). `zoom` scales everything the way the browser's own zoom does, so no layout is
   rewritten. The one thing it cannot do alone is viewport units: 100vh stays 100vh and is then scaled, so
   at 75% a full-height layout stops three quarters of the way down, at 125% it runs off the bottom. Every
   vh and dvh in the stylesheets therefore divides by --zoom (search for var(--zoom)); a new one must too.
   Printing is never zoomed.
*/
:root { --zoom: 1; }
html { zoom: var(--zoom); }
@media print { html { zoom: 1; } }

/* The control in the top bar: smaller, the level (a button that resets), bigger. */
.zoom-ctl {
    display: inline-flex;
    align-items: center;
    height: 32px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--surface);
    overflow: hidden;
}
.zoom-ctl[hidden] { display: none; }
.zoom-ctl__btn,
.zoom-ctl__value {
    height: 100%;
    border: 0;
    background: transparent;
    color: var(--text-muted);
    font: inherit;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
}
.zoom-ctl__btn { width: 28px; font-size: 16px; line-height: 1; }
.zoom-ctl__value { min-width: 46px; padding: 0 4px; font-size: var(--text-xs); font-weight: 600; color: var(--text); }
.zoom-ctl__btn:where(:hover):not(:disabled),
.zoom-ctl__value:where(:hover) { background: rgb(var(--neutral-rgb) / 10%); color: var(--text); }
.zoom-ctl__btn:disabled { opacity: 0.35; cursor: default; }
@media (max-width: 700px) {
    .zoom-ctl__btn { width: 24px; }
    .zoom-ctl__value { min-width: 40px; }
}

/* Full screen, a small square beside the zoom control and as tall as it. Shown by zoom.js where the browser allows it. */
.fullscreen-btn { width: 32px; height: 32px; }
.fullscreen-btn[hidden], .fullscreen-btn [hidden] { display: none; }
.fullscreen-btn[aria-pressed="true"] { color: var(--accent); border-color: rgb(var(--accent-rgb) / 45%); }
@media (max-width: 700px) {
    .fullscreen-btn { display: none; }
}

/* Media queries measure the real window, not the zoomed one, so at 125% (a window that is a fifth narrower
   to the layout) the top bar needs the same slimming the 1100px breakpoint gives it: the user chip shows the
   avatar only. zoom.js and _ThemeBoot set data-zoom on <html>. */
html[data-zoom="125"] .user-chip__meta { display: none; }

/* The star in the top bar: the same button as its neighbours (it carried a class nothing styled, so it drew as a
   bare browser button), filled and in the warning colour when the screen is a favourite. */
.topbar__star { display: inline-flex; margin: 0; }
.topbar__icon.is-on { color: var(--warning); border-color: color-mix(in srgb, var(--warning) 45%, var(--border)); }
.topbar__icon.is-on svg { fill: currentColor; }

/* Salary privacy (Pay.Money / Pay.Amount): the mask that stands where a figure would be, and the strip that says why. The mask
   keeps the figure's own cell and alignment, so a column of stars lines up where the column of figures would. */
.pay-masked { letter-spacing: .14em; color: var(--text-muted); user-select: none; }
.pay-banner { flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); }
.pay-banner__text { flex: 1 1 18rem; min-width: 0; }
.pay-banner__form { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin: 0; }
.pay-banner__form .input { width: 12rem; max-width: 100%; }
