
:root {
    --modal-duration-open: 200ms;
    --modal-duration-close: 150ms;
    --modal-ease-out: cubic-bezier(.22, 1, .36, 1);
    --modal-ease-in: cubic-bezier(.4, 0, 1, 1);
}


/* Cards */
.card-kpi {
    display: flex;
    align-items: center;
    justify-content: space-between
}


.alert {
    border-radius: var(--radius);
    padding: 12px 14px;
    border: 1px solid var(--border);
    font-weight: 500;
}


.alert-danger {
    background: var(--surface-danger);
    color: var(--danger);
    border: 1px solid var(--border-danger-subtle);
}


/* Modals */
.modal {
    position: fixed;
    inset: 0;
    /* Keep it mounted so transitions can run */
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 2000;
    background: var(--scrim-modal);
    /* IPAD-DIALOG-FIT 2026-10-18: THE CARD'S ROOM IS THIS BOX, NOT A VIEWPORT UNIT. position: fixed with inset: 0 is the VISIBLE area on iPad Safari (the toolbar
       takes its share), but 100vh is the LARGE viewport, so a card capped at calc(100vh - 32px) was taller than the box it is centred in and its top and bottom
       edges (border, padding, buttons) fell outside the screen. The padding is the 16px margin the old cap left, and never less than a device's safe-area inset;
       .modal-content takes max-height: 100% of what is left. */
    box-sizing: border-box;
    padding: 16px;
    padding: max(16px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right)) max(16px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
    /* Hidden state (animatable) */
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    /* Backdrop transition */
    transition: opacity var(--modal-duration-open) var(--modal-ease-out), visibility 0s linear var(--modal-duration-open);
    /* Optional: prevents "scroll chaining" behind the modal */
    overscroll-behavior: contain;
}

    /* Visible state */
    .modal.is-visible {
        opacity: 1;
        visibility: visible;
        pointer-events: auto;
        /* Make visibility immediate on open */
        transition: opacity var(--modal-duration-open) var(--modal-ease-out), visibility 0s linear 0s;
    }

/* The on-screen keyboard is up (JavaScript/keyboard-reveal.js sets kb-open and the two variables from the VISUAL viewport): a dialog is laid out in what the
   person can still see, so its fields and buttons are not under the keyboard. Only ever set on a touch device with a keyboard showing. */
html.kb-open .modal {
    top: var(--vvt, 0px);
    bottom: auto;
    height: var(--vvh, 100%);
}

.modal-content {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow-2);
    max-width: 560px;
    width: min(560px, calc(100vw - 32px));
    padding: 18px;
    border-left-color: color-mix(in srgb, var(--brand-primary) 75%, var(--surface));
    border-left-width: 2px;
    /* Enter/exit animation (subtle) */
    opacity: 0;
    transform: translateY(10px) scale(0.98);
    will-change: opacity, transform;
    transition: opacity var(--modal-duration-open) var(--modal-ease-out), transform var(--modal-duration-open) var(--modal-ease-out);
    /* Optional: keep tall forms usable on small screens */
    max-height: calc(100vh - 32px);
    max-height: calc(100dvh - 32px);
    max-height: 100%; /* IPAD-DIALOG-FIT: of the .modal box above (the visible area less its padding); the two lines before are for a browser without it */
    overflow: auto;
}

.modal.is-visible .modal-content {
    opacity: 1;
    transform: translateY(0) scale(1);
}

.modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-bottom: 1px solid var(--border);
    padding-bottom: .75rem;
    margin-bottom: .75rem;
}

    .modal-header h2 {
        font-size: 24px;
        line-height: 1.15;
        margin: 0;
        letter-spacing: -0.2px;
    }

.modal-body {
    padding: .25rem 0;
}

.modal-footer {
    display: flex;
    justify-content: flex-end;
    gap: .5rem;
    margin-top: 14px;
}

/* =================================================================================================
   TL-134: A MODAL THAT SHRINKS TO A SHORT VIEWPORT INSTEAD OF OVERFLOWING IT, AND WHOSE INLINE
   SUMMARY CANNOT SQUEEZE THE LIST BEING PICKED FROM.

   Opt in by adding modal-fit beside modal-content. Currently used by the services modal
   (Controls/ucEROCServicesPicker.ascx, TurnedDownServicesWorksheet) and the categories modal
   (Controls/ucJLIItemCategoriesPicker.ascx, InventoryAdjustmentReport), which have the identical
   shape: header, scrolling body, .report-params-summary, footer.

   -------------------------------------------------------------------------------------------------
   WHY THIS IS NOT THE TL-113 MECHANISM, AND WHY IT NEEDS NO BREAKPOINT AT ALL.

   The report-page summary float keys on max-width because WIDTH is genuinely what runs out there:
   below 1600 the pane cannot hold two columns. None of that is true in a modal. .modal-content is
   width: min(560px, 100vw - 32px), so it is 560px on every screen these users have and the summary
   and the list are stacked, never side by side. Width never squeezes them.

   What runs out is HEIGHT, against .modal-content's max-height: calc(100vh - 32px). So a max-width
   query would fire at exactly the wrong moments - silent on the wide, short window where the fault
   actually appears, and firing on a narrow, tall one where nothing is wrong.

   BUT A max-height QUERY IS ALSO THE WRONG TOOL, and this is the part worth keeping. The constraint
   is not the viewport's height, it is the height left INSIDE the card once the header, the summary
   and the footer have taken theirs. A breakpoint would have to guess that remainder and would be
   wrong for any modal with a different header or a longer summary. Flex expresses the remainder
   directly: the body takes what is left, whatever that turns out to be, continuously and at every
   height. There is no number to choose, so there is no number to get wrong.

   THE ONE PIECE THAT IS EASY TO OMIT is min-height: 0 on the body. A flex item's default
   min-height: auto refuses to shrink below its content, so without it the card overflows exactly as
   it does today and the whole rule appears to do nothing.
   ================================================================================================= */
.modal-content.modal-fit {
    display: flex;
    flex-direction: column;

    /* TL-155: THIS WAS overflow: hidden AND IT HAD TO GO, because the summary below now escapes the
       card and .modal-content IS the summary's containing block (it carries transform and
       will-change, see the base rule above) - so the card's own overflow applies to the float and
       any value other than visible clips it. `auto` was worse than `hidden`: it gave the horizontal
       scrollbar the developer reported instead of simply hiding the summary.

       WHAT overflow: hidden WAS FOR, AND WHY DROPPING IT IS SAFE. It was belt-and-braces against
       the card scrolling and taking the footer with it. The flex chain below already prevents that
       on its own: header and footer are flex: 0 0 auto, the body is the only flexible item and it
       owns the scroll with min-height: 0, so the card's content cannot exceed the card and there is
       nothing for the card to scroll. The in-flow summary fallback is bounded at 25vh with its own
       scroll, so it cannot exceed it either. */
    overflow: visible;
}

    /* Pinned. These are what must stay on screen at any height. */
    .modal-content.modal-fit > .modal-header,
    .modal-content.modal-fit > .modal-footer {
        flex: 0 0 auto;
    }

    /* The flexible part, and the scroll owner. */
    .modal-content.modal-fit > .modal-body {
        flex: 1 1 auto;
        min-height: 0;
        overflow: auto;
    }

    /* THE NARROW FALLBACK, and on a narrow window this is the whole behaviour. The summary sits
       between the body and the footer and is read before committing, so it stays out of the body's
       scroll - scrolling the list must not scroll the summary away. Bounded at a quarter of the
       viewport with its own scroll so that a long selection can never take the space the list needs;
       flex-shrink is 1 so it gives ground before the list does. */
    .modal-content.modal-fit > .report-params-summary {
        flex: 0 1 auto;
        min-height: 0;
        max-height: 25vh;
        overflow: auto;
    }

/* =================================================================================================
   TL-190: ON A SHORT VIEWPORT THE SUMMARY BECOMES ONE LINE, WITH THE DETAIL IN A FLYOUT.

   THE DEFECT: the rule above is the in-flow fallback, and on a short viewport 25vh is very little.
   flex-shrink is 1 by design so the summary yields before the parameter list does - TL-155 set it
   that way deliberately, so that a long selection can never starve the list. The consequence when
   height is ALSO short is a box tall enough for "Summary", "monthly in ..." and "Next runs" and
   nothing underneath them: three labels and no content.

   WHY THE FLOAT DOES NOT ALREADY COVER THIS: the float below only engages above 1100px, so it
   handles short WIDTH and does nothing about short HEIGHT.

   TWO ANSWERS THE DEVELOPER REJECTED, recorded so they are not re-proposed: hiding the summary
   entirely reads as poor, and an internal scroll inside the summary card reads as poor. Note that
   this rule sets overflow: VISIBLE on the summary, so the digest adds no scroll of its own.

   KEYED ON HEIGHT ONLY, not on height and width together. The reported case was both constrained,
   but a short wide viewport squeezes the card the same way - the card cannot be taller than the
   viewport, so the float is bounded by the same shortage. Keying on width as well would leave that
   case broken for no reason. 700px is the threshold: a 25vh summary is about 175px there, which is
   roughly where the second line of content starts to be lost.

   NOTHING HERE TOUCHES .modal-content.modal-fit's OWN overflow, which must stay visible - TL-155
   removed overflow: hidden from it because the card is the float's containing block and any other
   value clips the float, and auto was worse still (a horizontal scrollbar).
   ================================================================================================= */
.scheduler-digest {
    display: none;
}

@media (max-height: 700px) {

    .modal-content.modal-fit > .report-params-summary > .scheduler-digest {
        display: block;

        /* TL-195(b): THE DIGEST CARRIES THE SAME SURFACE AS THE CARD IT REPLACES.
           It was a bare div, so it rendered on the modal's own background - white, unshaded and easy
           to miss, which is exactly what was reported. These are the tokens .report-summary itself
           uses (reportShell.summaryPane.css), not approximations of them, so the one-line version
           and the full card read as the same component at two sizes.
           ON overflow, STATED ACCURATELY BECAUSE IT MATTERS: this rule DOES set overflow: hidden
           further down, and it has to - it is what -webkit-line-clamp needs in order to clamp. That
           is a CLIP, not a scroll: nothing is scrollable and nothing is reachable only by scrolling,
           because whatever the two lines cannot hold is in the tooltip instead. The answer the
           developer rejected was a SCROLLING summary card, and this is not one. */
        border: 1px solid var(--border);
        border-radius: var(--radius);
        background: var(--surface-subtle);

        /* Smaller than the card's 12px padding and smaller type - it is one line, and he asked for
           the smaller, better-styled treatment specifically because it makes the one-liner read
           better rather than merely fit. */
        padding: 8px 10px;
        font-size: 0.8125rem;
        line-height: 1.35;
        /* The sentence can be long on a many-months Monthly selection. Two lines is the ceiling:
           past that it is competing with the parameter list again, which is the problem this rule
           exists to solve. The flyout holds whatever does not fit. */
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
        cursor: help;
    }

        /* Focus must be visible - the digest is tabbable so the flyout is reachable without a
           pointer, and an invisible focus ring on a tabbable element is a defect of its own. */
        .modal-content.modal-fit > .report-params-summary > .scheduler-digest:focus-visible {
            outline: 2px solid var(--focus-ring-color, currentColor);
            outline-offset: 2px;
        }

    /* The full card is replaced, not hidden alongside the digest - they are never both shown. */
    .modal-content.modal-fit > .report-params-summary > .report-summary {
        display: none;
    }

    /* One line needs no share of the flex space and no scroll of its own. */
    .modal-content.modal-fit > .report-params-summary {
        flex: 0 0 auto;
        max-height: none;
        overflow: visible;
    }
}

/* =================================================================================================
   TL-155: ABOVE 1100px THE MODAL'S SUMMARY FLOATS JUST OUTSIDE THE CARD'S RIGHT EDGE.

   THIS REPLACES TL-134's REASONING, WHICH WAS WRONG ABOUT THE ONE THING THAT MATTERED. TL-134 said a
   560px modal "has no neighbour to float over" and therefore bounded the summary inside the card. The
   modal is CENTRED on the viewport, so on any normal screen there is empty overlay either side of it -
   there is room, it is simply not room that belongs to another element. Bounding the summary inside
   the card is what produced the reported defect: the summary needs more width than a 560px card has
   left, so it overflowed and forced horizontal scroll.

   WHY THE ANCHOR IS left: 100% AND NOT A CALCULATION OFF THE VIEWPORT CENTRE. .modal-content carries
   transform and will-change (the base rule above), so it IS the containing block for an absolutely
   positioned descendant - the same property that made this a trap in TL-113 and TL-135 is exactly what
   is wanted here. left: 100% resolves against the card's own padding box, so the summary sits against
   the card's right edge and stays tight to it at any card width, at any viewport width, and while the
   open transition is still animating the card's transform. Nothing here repeats the 560px.

   AND THE CARD'S overflow HAD TO BECOME visible FOR THIS TO BE VISIBLE AT ALL - see the note on
   .modal-content.modal-fit above. Because the card is the containing block, the card's own overflow
   clips the float; no ancestor further up can rescue it.

   WHY 1100px AND WHY A min-width QUERY HERE WHEN TL-134 ARGUED AGAINST ONE. TL-134's argument was
   about HEIGHT - the fit behaviour has no number to choose, so it has no breakpoint. This rule is
   about WIDTH, and width genuinely runs out: the card is 560px centred, so its right edge is at
   50vw + 280, and the summary needs another 216px plus a 12px gap. That fits inside the viewport when
   50vw >= 508, i.e. from about 1016px up. 1100 is the same threshold the narrow left-nav rail uses
   (layout.css, TL-148), so the two agree rather than each having their own number, and it leaves ~42px
   of margin at the breakpoint itself. Below it the fallback above applies unchanged.

   THE SUMMARY MUST NOT BE REACHED BY THE REPORT-PANE FLOAT EITHER. All three of these modals render
   inside the parameters pane, so reportShell.summaryPane.css's float used to match them; it is now
   scoped to a direct child of .report-params-layout. If a modal summary ever appears at the top-right
   INSIDE the card again, that is the rule to check first.
   ================================================================================================= */
/* TL-195(a) 2026-10-02: THE HEIGHT CONDITION IS THE FIX, AND IT IS ONE LINE.
   REPORTED: short AND wide, the summary is squeezed and cut off inside its floating card.
   WHY: this block used to be (min-width: 1100px) alone, so on a short wide viewport it applied AT
   THE SAME TIME as the (max-height: 700px) digest block above - and it came later in the file, so on
   the properties they share it won. The result was the digest rendered INSIDE an absolutely
   positioned box with max-height: 100% of a card that is itself short, and overflow: auto. That is
   both the squeeze that was reported and an internal scroll inside the summary, which is one of the
   two answers the developer explicitly rejected.
   THE FLOAT ONLY MAKES SENSE WHEN THERE IS HEIGHT TO FLOAT INTO. It is bounded by the card, the card
   cannot exceed the viewport, so on a short viewport the float has no more room than the in-flow
   version did - it just hides the shortage behind a scrollbar.
   SO ABOVE 700px TALL: float, exactly as before. AT OR BELOW 700px TALL: no float, and the digest
   takes over in flow above the CTAs - which makes short-and-wide behave like short-and-thin, the
   behaviour he described as correct for the thin case.
   701px rather than 700px because the digest block uses max-height: 700px, and the two must not
   both apply at exactly 700. */
@media (min-width: 1100px) and (min-height: 701px) {
    .modal-content.modal-fit > .report-params-summary {
        position: absolute;
        top: 0;
        left: 100%;
        margin-left: 12px;

        /* The same token the report pane's summary uses, so the two are the same width. */
        width: var(--report-summary-width);

        /* Against the card's height, not the viewport's - the card is the containing block. */
        max-height: 100%;
        overflow: auto;

        /* Out of the flex flow entirely: an absolutely positioned child takes no space from the
           header/body/footer chain, so the list gets the whole card back. flex: none makes that
           explicit against the fallback rule above rather than relying on position alone. */
        flex: none;

        /* It is over the overlay now, not over the card's own background, so it has to draw its own
           surface. Inside the modal's stacking context, so a low z-index is enough. */
        z-index: 1;
        background: var(--surface);
        border-radius: var(--radius);
        box-shadow: var(--shadow-2);
    }

        /* .report-summary draws its own border and background and sets overflow: hidden, which would
           clip the summary now that the float is the scroll owner. The float scrolls; the card fills
           it. Same treatment as the report pane's floating summary, for the same reason. */
        .modal-content.modal-fit > .report-params-summary > .report-summary {
            overflow: visible;
            flex: 1 1 auto;
            border: 0;
            background: transparent;
        }
}

/* Modal close button (used in modal-header) */
.modal-close {
    background: none;
    border: none;
    font-size: 22px;
    cursor: pointer;
    opacity: .6;
    transition: opacity .25s ease;
}

    .modal-close:hover:not(:where(:disabled, [disabled], [aria-disabled="true"])) {
        opacity: 1;
    }

    .modal-close .icon {
        width: 18px;
        height: 18px;
        display: block;
    }

    /* Optional: better keyboard affordance */
    .modal-close:focus-visible {
        outline: 2px solid color-mix(in srgb, var(--brand-primary) 60%, var(--lift-toward));
        outline-offset: 2px;
    }

/* Accessibility: respect reduced motion */
@media (prefers-reduced-motion: reduce) {
    .modal,
    .modal-content {
        transition: none !important;
        transform: none !important;
    }
}


.toggle-row {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 12px;
}

.toggle-label {
    font-weight: 600;
    line-height: 1.2;
    /* allow wrap, but not word-by-word */
    white-space: normal;
}

.form-group {
    margin: 16px 0;
}

/* On narrow screens, align at the top so it doesn't look cramped */
@media (max-width: 480px) {
    .toggle-row {
        align-items: flex-start;
    }
}

/* Payment summary (invoice count + total) */
.payment-summary {
    border: 1px solid var(--border);
    background: var(--surface-subtle);
    border-radius: 12px;
    padding: 12px 14px;
    margin-bottom: 10px;
}

.payment-summary-row {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    padding: 4px 0;
}

.payment-summary-label {
    color: var(--muted);
    font-weight: 600;
}

.payment-summary-value {
    color: var(--ink);
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

/* Toggle control (checkbox-based) */
.toggle {
    display: inline-block;
    position: relative
}

    .toggle input[type="checkbox"] {
        position: absolute;
        opacity: 0;
        width: 1px;
        height: 1px
    }

        .toggle input[type="checkbox"] + label {
            position: relative;
            display: inline-flex;
            align-items: center;
            min-height: 20px;
            line-height: 20px;
            padding-left: 46px;
            cursor: pointer
        }

            .toggle input[type="checkbox"] + label::before {
                content: "";
                position: absolute;
                left: 0;
                top: 0;
                width: 36px;
                height: 20px;
                background: var(--toggle-off);
                border-radius: 999px;
                box-shadow: var(--shadow-3);
                transition: background .2s
            }

            .toggle input[type="checkbox"] + label::after {
                content: "";
                position: absolute;
                left: 2px;
                top: 2px;
                width: 16px;
                height: 16px;
                background: var(--toggle-knob);
                border-radius: 50%;
                box-shadow: var(--shadow-2);
                transition: transform .2s
            }

        .toggle input[type="checkbox"]:checked + label::before {
            background: var(--toggle-on)
        }

        .toggle input[type="checkbox"]:checked + label::after {
            transform: translateX(16px)
        }

/* Toggle chip (multi-select) */
.chip-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 84px; /* fits “Wednesday” comfortably */
    height: 32px; /* taller = less dense */
    padding: 0 8px;
    margin: 4px;
    border-radius: 16px;
    border: 1px solid var(--border);
    background: var(--surface-subtle);
    cursor: pointer;
    transition: all .15s ease;
}

    .chip-toggle.active {
        background: color-mix(in srgb, var(--brand-primary) 25%, var(--tint-toward));
        border-color: color-mix(in srgb, var(--brand-primary) 80%, var(--tint-toward));
        color: var(--brand-ink);
        transition: background-color 150ms ease, border-color 160ms ease;
    }



    .chip-toggle label,
    .chip-toggle-all label {
        margin: 0 !important;
        font-size: 13px !important;
        font-weight: 500 !important;
        line-height: 1 !important;
        cursor: pointer !important;
    }

    .chip-toggle.active label,
    .chip-toggle-all.active label {
        margin: 0 !important;
        font-size: 13px !important;
        font-weight: 600 !important;
        line-height: 1 !important;
    }


    .chip-toggle input[type="checkbox"] {
        display: none;
    }

/* -------------------------------------- */
/*  ALL CHIP — visually distinct, higher  */
/* -------------------------------------- */

.chip-toggle-all {
    width: 72px; /* slightly narrower = visually special */
    font-weight: 600;
    border-width: 1px; /* stronger presence */
    border-color: color-mix(in srgb, var(--brand-primary) 40%, var(--tint-toward));
    background: color-mix(in srgb, var(--brand-primary) 8%, var(--tint-toward));
    color: var(--brand-ink);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--brand-primary) 25%, var(--tint-toward));
}

    /* Hover: subtle command-like emphasis */
    .chip-toggle-all:hover {
        background: color-mix(in srgb, var(--brand-primary) 14%, var(--tint-toward));
        border-color: color-mix(in srgb, var(--brand-primary) 55%, var(--tint-toward));
    }

    /* Active state: authoritative, decisive */
    .chip-toggle-all.active {
        background: color-mix(in srgb, var(--brand-primary) 70%, var(--tint-toward));
        border-color: color-mix(in srgb, var(--brand-primary) 90%, var(--tint-toward));
        color: var(--brand-on-primary);
        box-shadow: 0 0 0 2px color-mix(in srgb, var(--brand-primary) 40%, transparent);
    }

        .chip-toggle-all.active:hover {
            background: color-mix(in srgb, var(--brand-primary) 24%, var(--tint-toward));
            border-color: color-mix(in srgb, var(--brand-primary) 55%, var(--tint-toward));
        }

/* Global page loader (UI_STANDARDS 4.8 / Loaders, PROPOSED 2026-10-17: no bounding box).
   The MT mark floats freely (no visible words; the hidden status text names it for screen readers), centred over a dimmed, softly blurred scrim. There is no card: no border,
   background, shadow or radius on .box, at any size. The scrim blocks clicks (fixed, full screen). Colours are tokens only. */
.mg-loader {
    position: fixed;
    inset: 0;
    display: none; /* hidden by default */
    align-items: center;
    justify-content: center;
    /* Dimmed scrim that carries the legibility on its own (this is also the fallback tint where backdrop-filter is unsupported) */
    background: color-mix(in srgb, var(--bg) 78%, transparent);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    z-index: 3000;
}

    /* No blur available: a slightly stronger tint so the mark still reads over a busy page */
    @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
        .mg-loader {
            background: color-mix(in srgb, var(--bg) 90%, transparent);
        }
    }

    /* Reduced transparency or forced colours: no blur, a solid-enough scrim */
    @media (prefers-reduced-transparency: reduce), (forced-colors: active) {
        .mg-loader {
            background: color-mix(in srgb, var(--bg) 94%, transparent);
            backdrop-filter: none;
            -webkit-backdrop-filter: none;
        }
    }

    /* Treat both states as active */
    .mg-loader.is-visible,
    .mg-loader.is-active {
        display: flex;
    }

    /* Loader content: free-floating, no card */
    .mg-loader .box {
        background: none;
        border: 0;
        border-radius: 0;
        box-shadow: none;
        padding: 0;
        color: var(--ink);
        display: inline-flex;
        align-items: center;
        gap: 10px;
    }

    /* The mark, centred. */
    .mg-loader .box.box--mark {
        flex-direction: column;
        gap: 8px;
        font-weight: 500;
        text-align: center;
        text-shadow: 0 0 8px var(--bg), 0 0 2px var(--bg);
    }

    /* the developer chose the plain brand colour in dark (2026-10-17); it measures 3.73:1 on the dark page, above the 3:1 needed for a mark */
    .mg-loader .box--mark .mt-loader {
        color: var(--brand-primary);
    }


/* Content-area loader (UI_STANDARDS 4.4.8 / Loaders, PROPOSED 2026-10-17: contextual feedback, not a blocking scrim).
   For a page whose CONTENT arrives after the app chrome (the dashboard). The MT mark (no visible words; the hidden status line carries the label) sits centred in the
   content region only: no scrim, no blur, no dimming, no pointer capture, so the nav, header and rail stay usable. Script:
   JavaScript/mt-content-loader.js (150 ms show delay, 400 ms minimum, aria-busy and a polite status line).
   Markup:  <div class="content-loader-host" data-content-loader data-content-loader-label="Loading your dashboard">
                ...the region's content (it keeps its layout: the loader is absolutely positioned, nothing shifts)...
                <div class="content-loader" aria-hidden="true"><span class="mt-loader mt-loader--md" data-mt-loader></span></div>
                <span class="visually-hidden content-loader-status" role="status" aria-live="polite" aria-atomic="true"></span></div>
   Add content-loader--lg to the loader to get the 160 px mark where there is room (641 px wide and 600 px tall and up). Add
   content-loader-content to a child that is EMPTY until the data arrives: it fades in as the loader fades out. Colours are tokens only. */
.content-loader-host {
    position: relative;
}

    /* While loading, keep a minimum height so the loader has room and the region does not jump when the content lands */
    .content-loader-host.is-loading {
        min-height: 9rem;
    }

        .content-loader-host.is-loading:has(> .content-loader--lg) {
            min-height: 12rem;
        }

.content-loader {
    display: none; /* hidden until the script shows it after 150 ms, so a fast load never flashes it */
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 100%;
    min-height: 9rem;
    max-height: 28rem; /* centred in the first screenful of a tall region, never below the fold */
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    color: var(--ink);
    font-weight: 500;
    text-align: center;
    pointer-events: none; /* never blocks a click: the nav, the header and the controls stay live */
    z-index: 1;
    opacity: 1;
    transition: opacity 200ms ease;
}

    .content-loader.is-visible {
        display: flex;
        animation: contentLoaderIn 200ms ease;
    }

    /* Leaving: fades while the content fades in (the transition is off under reduced motion, so it simply goes) */
    .content-loader.is-visible.is-leaving {
        opacity: 0;
    }

    .content-loader .mt-loader {
        color: var(--brand-primary);
    }

/* the 160 px mark only where it fits; the md (64 px) mark everywhere else */
@media (min-width: 641px) and (min-height: 600px) {
    .content-loader--lg .mt-loader {
        width: 160px;
        height: 160px;
    }
}

/* a short screen (a phone on its side): the region starts low on the screen, so the mark sits near the top of the region, not in its middle */
@media (max-height: 500px) {
    .content-loader {
        justify-content: flex-start;
        padding-top: 1.5rem;
        box-sizing: border-box;
    }
}

/* A child that stays empty until the data lands: hidden while the region is busy, fades in the moment busy ends */
.content-loader-content {
    transition: opacity 200ms ease;
}

.content-loader-host[aria-busy="true"] > .content-loader-content {
    opacity: 0;
}

@keyframes contentLoaderIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

@media print {
    .content-loader {
        display: none !important;
    }
}

/* Accessibility helper for the hidden text inside the loader */
.visually-hidden {
    position: absolute !important;
    height: 1px;
    width: 1px;
    overflow: hidden;
    clip: rect(1px,1px,1px,1px);
    white-space: nowrap;
    border: 0;
    padding: 0;
    margin: -1px;
}

/* MT toasts from Site.master (moved from inline) */
#mt-toast-root {
    position: fixed;
    top: 24px;
    right: 24px;
    z-index: 9999; /* higher than loader */
    isolation: isolate; /* ensures toasts stack above other content without z-index conflicts */
    display: flex;
    flex-direction: column;
    gap: 8px;
    pointer-events: none;
}

.mt-toast {
    min-width: 280px;
    max-width: 420px;
    background: var(--inverse-surface);
    color: var(--inverse-ink);
    border-radius: var(--radius);
    box-shadow: var(--shadow-3);
    padding: 12px 14px;
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 8px;
    opacity: 0;
    transform: translateY(-8px);
    transition: opacity 300ms ease, transform 300ms ease;
    pointer-events: auto;
}

    .mt-toast.show {
        opacity: 1;
        transform: translateY(0);
    }

    .mt-toast .mt-toast-body {
        grid-column: 1 / span 1;
    }

    .mt-toast .mt-title {
        font-weight: 600;
        margin-bottom: 2px;
    }

    .mt-toast .mt-msg {
        opacity: .95;
        white-space: pre-line;
    }

    .mt-toast .mt-close {
        cursor: pointer;
        background: transparent;
        border: 0;
        padding: 0 4px;
        color: inherit;
        /* icon button sizing/alignment */
        width: 28px;
        height: 28px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        /* subtle affordance (matches your rail close behavior) */
        opacity: 0.6;
        transition: opacity 0.25s ease;
    }

        .mt-toast .mt-close:hover {
            opacity: 1;
        }

        /* SVG inside the close button */
        .mt-toast .mt-close .icon {
            width: 20px;
            height: 20px;
            display: block;
        }

        /* Optional: keyboard focus ring (accessible, not distracting) */
        .mt-toast .mt-close:focus-visible {
            outline: 2px solid color-mix(in srgb, var(--brand-primary) 35%, var(--shade-toward));
            outline-offset: 2px;
        }

    .mt-toast.info {
        background: var(--toast-info);
    }

    .mt-toast.success {
        background: var(--toast-success);
    }

    .mt-toast.warning {
        background: var(--toast-warning);
        color: var(--toast-warning-ink);
    }

    .mt-toast.error {
        background: var(--toast-error);
    }
/* ============================================================
   INPUT PREFIX (for CB + BAID search)
   ============================================================ */

.input-prefix {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.input-prefix-label {
    display: inline-flex;
    align-items: center;
    height: 44px; /* match .input height */
    padding: 0 12px; /* visually matches .input padding */
    background: var(--prefix-bg);
    border: 1px solid var(--prefix-border);
    border-radius: 8px;
    font-weight: 600;
    color: var(--muted);
    box-sizing: border-box; /* ensures height includes border */
}

.input-prefix .input {
    width: 140px;
}

/* ============================================================
   REPORT PAGE
   ============================================================ */





/* StorePicker wrapper (component-level) */
.store-picker-header {
    margin-bottom: 8px;
}

.store-picker-title {
    font-weight: 700;
}

.store-picker-body {
    /* no hard styling yet; keeps structure stable */
}


/* DateRange control (component-level) */
.date-range {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.date-range-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    align-items: end;
}

.date-range-field {
    min-width: 0;
}

/* TL-52: shown while the Range dropdown's postback is resolving From/To. The control
   suppresses the global loader for that postback on purpose, so without this there is nothing
   on screen to say the dates are about to change under the user.
   TL-92: applied only once the lookup has run longer than SLOW_MS (ucDateRange.js). A typical
   resolve is ~100ms and is now completely silent - no dimming, no microtext, and no validation
   error on drpPeriod either. These rules are for the genuinely slow case only. */
/* TL-96: THE SPACE IS RESERVED PERMANENTLY AND THE ELEMENT IS NEVER REMOVED.
   The previous version toggled the [hidden] attribute, which is display:none - so the line appeared
   and disappeared from the layout and everything below it jumped. THAT JUMP WAS THE BLINK the
   developer was seeing; it was never the text being too fast. min-height reserves the line whether
   or not anything is showing, so nothing shifts.

   THE TWO DURATIONS ARE DELIBERATELY DIFFERENT, AND THE ASYMMETRY IS THE WHOLE DESIGN.
   The fade-IN duration lives on .is-visible and the fade-OUT duration on the base rule, so adding
   the class transitions in at 55ms and removing it transitions out at 160ms.
     55ms IN  - fast enough that a lookup which resolves in ~100ms only ever GHOSTS in. It is never a
                thing that appears; it is a thing that was almost there.
     160ms OUT - slow enough to notice, read, and watch settle AFTER the dates have populated. The
                text is at its most legible on the way out, which is the opposite of what it was
                doing before.
   Softness is the point: after a few times people stop seeing it, which is what reassurance should
   do and what an alarm must not. */
.date-range-busy {
    display: block;
    min-height: 1.2em;
    font-size: 12px;
    color: var(--muted);
    opacity: 0;
    transition: opacity 160ms ease-out;
}

    .date-range-busy.is-visible {
        opacity: 1;
        transition: opacity 55ms ease-in;
    }

/* The inputs dim while their values are about to change. Transitioned on the same principle - a snap
   to 60% and back is another flicker, and this is meant to be barely noticeable. */
.date-range-resolving .date-range-input {
    opacity: 0.6;
}

.date-range-input {
    transition: opacity 160ms ease-out;
}

.date-range-label {
    display: block;
    margin-bottom: 6px;
    font-weight: 600;
    color: var(--ink);
}

.date-range-range .date-range-field {
    grid-column: 1 / -1;
}

.date-range-range select {
    width: 100%;
}

@media (max-width: 520px) {
    .date-range-grid {
        grid-template-columns: 1fr;
    }
}

/* Format picker (component-level) */
.format-picker {
    min-width: 0;
}

    /* Optional: tighten radio list spacing later if needed */
    .format-picker .aspNetDisabled,
    .format-picker input,
    .format-picker label {
        /* keep neutral for now */
    }


/* Scheduler (component-level) */
.scheduler {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.scheduler-presets {
    display: flex;
    justify-content: space-between;
    gap: 12px;
}

.scheduler-presets-right {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1; /* allows the select to grow */
    margin-bottom: 12px;
}

#schedPreset {
    flex: 1;
    min-width: 0;
}

.scheduler-edit-btn,
.jli-item-categories-edit-btn {
    width: 24px;
    height: 24px;
    padding: 0;
}

    .scheduler-edit-btn svg.icon,
    .jli-item-categories-edit-btn svg.icon {
        width: 24px;
        height: 24px;
    }

.scheduler-row {
    display: flex;
    flex-direction: column;
    gap: 0px;
}

.scheduler-label {
    font-size: 14px;
    font-weight: 600;
    line-height: 1.15;
    color: var(--ink);
    margin-bottom: 4px;
}

.scheduler-panel {
    /* border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 12px 14px;
    background: var(--surface-subtle); */
}

.scheduler-subtitle {
    font-weight: 500;
    font-size: 13px;
    line-height: 1.15;
    color: var(--ink);
    margin-bottom: 6px;
    margin-top: 10px;
}

.scheduler-subtitle-inline {
    font-weight: 500;
    font-size: 13px;
    line-height: 1.15;
    color: var(--ink);
    margin-bottom: 6px;
}

.scheduler-choice {
    /*display: flex;
    gap: 2px;
    flex-wrap: wrap;*/
    display: flex;
    flex-wrap: wrap;
    column-gap: 0px;
    row-gap: 12px;
    margin-bottom: 8px;
}

/* TL-18 (G1): these radios used to be pushed off screen for EVERY .scheduler-choice, because the report
   scheduler replaced its own radio list with a segmented control and hid the list this way. CBS AutoPay
   uses the same class for radios the user is meant to see, so its whole Schedule choice (Daily / Weekly /
   Bi-weekly / Semi-monthly / Monthly) was invisible under its heading. Hiding now needs this modifier,
   which only the segmented control's backing list carries. */
.scheduler-choice--offscreen {
    position: absolute;
    left: -9999px;
    top: -9999px;
}

    .scheduler-choice input[type="radio"] {
        top: -2px;
    }

        .scheduler-choice input[type="radio"] + label {
            margin-right: 16px; /* adjust to taste */
        }

.scheduler-choice-vertical {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.scheduler-choice-horizontal {
    display: flex;
    flex-direction: row;
    gap: 6px;
}

/*.scheduler-choice label {
    margin-right: 16px;
}*/

/* Remove hard margins; rely on gap so spacing is predictable */
.scheduler-choice label {
    margin-right: 0;
    white-space: nowrap; /* keep each option as a single “chip” */
    margin-left: 4px;
    font-size: 14px;
    font-weight: 500;
    color: var(--ink);
}


.scheduler-inline {
    display: inline-flex;
    gap: 10px;
    align-items: baseline;
    flex-wrap: wrap;
}

.scheduler-label-inline {
    font-weight: 600;
}

.scheduler-muted {
    margin-bottom: 10px;
    color: var(--muted);
    font-size: 12px;
}

.scheduler-divider {
    margin-top: 4px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--brand-primary);
}

.scheduler-days label {
    margin-right: 10px;
}

.scheduler-months label {
    margin-right: 10px;
}

.input.small {
    width: 44px;
}

.scheduler-ampm label {
    margin-left: 2px;
    margin-right: 16px;
}

.scheduler-radio-inline {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.radio-inline {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 14px;
}

.radio-option {
    font-size: 14px;
}

.inline-number {
    width: 50px;
    text-align: center;
}


/* Embedded report viewer frame */
.report-frame {
    width: 100%;
    min-height: 720px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
}

/* --- Payment summary: single-line layout --- */
.payment-summary--singleline {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px; /* controls row 12px gap */
}

.payment-summary-line {
    display: inline-flex;
    align-items: baseline;
    /* PAY-75: at 360px "Pay $1,284.50 for 12 invoices" is wider than the card minus the More options button, and
       with nowrap on the tail it ran UNDER the button. It wraps as two units instead: "Pay $1,284.50" / "for 12 invoices". */
    flex-wrap: wrap;
    flex: 1 1 auto;
    gap: 0 6px;
    min-width: 0;
    font-size: 15px;
    line-height: 1.2;
}

.payment-summary-verb {
    font-weight: 900; /* bold amount */
    font-size: 18px; /* 16–18px */
}

.payment-summary-amount {
    font-weight: 900; /* bold amount */
    font-size: 18px; /* 16–18px */
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.2px;
}

.payment-summary-tail {
    color: var(--ink);
    font-weight: 650;
    white-space: nowrap;
}

/* =========================================================================================
   PAY-75: PAYMENT MODALS, LAID OUT ONCE
   Review & pay (#authorizeProfilePayment, also the receipt after a skipped payment) and the
   More options disclosure inside Review & pay. Classes and var(--...) tokens only: a theme
   or dark mode restyles all of it, and there are no inline styles in the markup.

   WHAT CHANGED FROM PAY-74'S STATE, AND WHY
   More options was position:absolute inside .modal-content. .modal-content has a transform
   (so it is the flyout's containing block) AND overflow:auto, so the flyout was clipped by the
   card and scrolled with it; PAY-74 hid that with a 340px min-height. It is now IN THE FLOW:
   the card grows when it opens (grid-template-rows 0fr -> 1fr), so nothing is positioned over
   anything, nothing is clipped, and no height has to be guessed. On a short screen the card is
   .modal-fit (flex column, body scrolls, footer pinned), so the opened disclosure scrolls with
   the body instead of pushing Pay off the screen.

   WHERE THE GRADIENT IS: not on the modal. It is the two pseudo-elements of
   .pay-moreoptions-card when the disclosure is open - ::before a soft radial halo and ::after a
   diagonal sheen, both color-mix(--brand-primary). The modal itself only has the 2px brand
   border-left from .modal-content. ONE @keyframes (it used to be defined twice, the later one
   silently winning).
   ========================================================================================= */

/* Supporting text = muted 12-13px */
.pay-pref-help,
.pay-moreoptions-note {
    font-size: 12px;
    color: var(--muted);
    line-height: 1.35;
}

/* The method being charged, by name */
.pay-method-line {
    margin-top: 6px;
    color: var(--muted);
    font-size: 14px;
    line-height: 1.3;
}

    .pay-method-line strong {
        color: var(--ink);
        font-weight: 800;
        font-variant-numeric: tabular-nums;
    }

/* --- More options trigger (anchored to the summary line) --- */
.pay-moreoptions-trigger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    padding: 0;
    border-radius: 10px;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--muted);
    cursor: pointer;
}

    .pay-moreoptions-trigger:hover {
        color: var(--ink);
        background: color-mix(in srgb, var(--surface-subtle) 70%, var(--surface));
    }

    /* Open: brand-tinted, and calm on hover (no lift jitter) */
    .pay-moreoptions-trigger[aria-expanded="true"] {
        background: color-mix(in srgb, var(--brand-primary) 8%, var(--surface));
        border-color: color-mix(in srgb, var(--brand-primary) 35%, var(--border));
        color: var(--brand-ink);
        box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--brand-primary) 22%, transparent);
    }

        .pay-moreoptions-trigger[aria-expanded="true"]:hover {
            background: color-mix(in srgb, var(--brand-primary) 10%, var(--surface));
        }

/* --- The disclosure: in flow, grows the card --- */
.pay-moreoptions {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 200ms ease;
}

    .pay-moreoptions.is-open {
        grid-template-rows: 1fr;
    }

/* The row's content must be allowed to shrink to 0, or 0fr does nothing */
.pay-moreoptions-clip {
    min-height: 0;
    overflow: hidden;
    /* closed: out of the tab order and the accessibility tree; opens immediately, closes after the slide */
    visibility: hidden;
    transition: visibility 0s linear 200ms;
}

.pay-moreoptions.is-open > .pay-moreoptions-clip {
    visibility: visible;
    transition-delay: 0s;
}

.pay-moreoptions-card {
    position: relative;
    overflow: hidden;                 /* clips the halo and sheen to the rounded corners */
    margin-top: 12px;
    padding: 12px;
    display: grid;
    gap: 10px;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--surface);
    box-shadow: var(--shadow-2);
}

.pay-moreoptions.is-open .pay-moreoptions-card {
    box-shadow: var(--shadow-2), 0 0 0 1px color-mix(in srgb, var(--brand-primary) 18%, var(--border));
}

    /* Soft halo: a very subtle radial glow near the top right */
    .pay-moreoptions.is-open .pay-moreoptions-card::before {
        content: "";
        position: absolute;
        inset: -30%;
        pointer-events: none;
        z-index: 0;
        background: radial-gradient(60% 80% at 85% 20%, color-mix(in srgb, var(--brand-primary) 18%, transparent) 0%, transparent 60%);
        opacity: 0.95;
    }

    /* Diagonal sheen, swept across once on open */
    .pay-moreoptions.is-open .pay-moreoptions-card::after {
        content: "";
        position: absolute;
        inset: 0;
        pointer-events: none;
        z-index: 0;
        background: linear-gradient(115deg, transparent 0%, color-mix(in srgb, var(--brand-primary) 10%, transparent) 45%, transparent 70%);
        background-repeat: no-repeat;
        background-size: 220% 100%;
        background-position: 40% 0;
        opacity: 0.9;
    }

@media (prefers-reduced-motion: no-preference) {
    .pay-moreoptions.is-open .pay-moreoptions-card::after {
        animation: paySheenSweep 900ms ease 1;
    }

    @keyframes paySheenSweep {
        0% {
            background-position: 120% 0;
            opacity: 0;
        }

        35% {
            background-position: 40% 0;
            opacity: 0.9;
        }

        100% {
            background-position: -30% 0;
            opacity: 0.85;
        }
    }
}

/* Content above the glow layers */
.pay-moreoptions-card > * {
    position: relative;
    z-index: 1;
}

.pay-moreoptions-header {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding-bottom: 10px;
}

    /* One glass divider under the header: a 2px highlight-to-shadow gradient */
    .pay-moreoptions-header::after {
        content: "";
        position: absolute;
        left: -12px;
        right: -12px;
        bottom: 0;
        height: 2px;
        pointer-events: none;
        background: linear-gradient(to bottom, color-mix(in srgb, var(--surface) 70%, transparent), color-mix(in srgb, var(--border) 70%, transparent));
        opacity: 0.9;
    }

.pay-moreoptions-title {
    font-weight: 900;
    font-size: 13px;
    color: var(--muted);
}

.pay-moreoptions-close {
    width: 32px;
    height: 32px;
    padding: 0;
    border-radius: 10px;
    border: 1px solid var(--border);
    background: var(--surface-subtle);
    color: var(--muted);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}

    .pay-moreoptions-close:hover {
        color: var(--ink);
    }

.pay-moreoptions-body {
    display: grid;
    gap: 10px;
}

/* --- "Always use my saved method" --- */
.pay-pref {
    margin-top: 14px;
}

.pay-pref-row {
    margin-top: 0;
}

/* --- One-time modal: the three inline styles that used to be in the markup --- */
.pay-save-group {
    margin-top: 12px;
    margin-bottom: 12px;
}

/* The payment amount: a number, so right-aligned like the other money fields. 16px comes from .input (no iOS zoom). */
.pay-amount-input {
    text-align: right;
}

.pay-field-label {
    display: block;
    font-weight: 700;
    margin-bottom: 6px;
}

/* --- Payment modal method row --- */
.pay-method-row {
    display: flex;
    gap: 14px;
    align-items: center;
    flex-wrap: wrap;
}

.pay-method-option {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

/* --- Busy overlay for WebForms buttons (prevents double-submit) --- */
.btn-wrap {
    position: relative;
    width: 100%;
}

/* In a dialog footer the wrapper is content width, like the buttons beside it (D02/D03). */
.modal-footer .btn-wrap {
    width: auto;
}

    .btn-wrap .btn-overlay {
        position: absolute;
        inset: 0;
        display: none;
        align-items: center;
        justify-content: center;
        gap: 8px;
        border-radius: var(--radius);
        background: color-mix(in srgb, var(--brand-primary) 10%, var(--surface));
        border: 1px solid color-mix(in srgb, var(--brand-primary) 30%, var(--border));
        color: var(--ink);
        font-weight: 800;
    }

    .btn-wrap.is-busy .btn-overlay {
        display: inline-flex;
    }

    /* Hide underlying control visually, keep layout */
    .btn-wrap.is-busy .btn,
    .btn-wrap.is-busy input[type="submit"],
    .btn-wrap.is-busy input[type="button"] {
        opacity: 0;
        pointer-events: none;
    }

/* DISABLED AND LOCKED BUTTONS (UI_STANDARDS 3 and 4.4; found on the developer's iPhone 2026-10-18: a dimmed "Move invoices" still took the
   brand highlight on a tap, because :hover sticks after a touch and no hover rule excluded a disabled button). The rule: a button that is
   disabled, or locked with aria-disabled="true" (the busy lock, which must stay clickable so the script can cancel the click), does NOT react
   to hover or press: every hover rule above and in theme.css, rightrail.css and reportShell.css carries :not(:where(:disabled, [disabled],
   [aria-disabled="true"])), which adds no specificity. This block adds the rest: a not-allowed cursor, no tap flash, and a dim for the
   controls that .btn[disabled] (theme.css) does not cover. No pointer-events: none, so a title on a disabled button still shows on desktop.
   A locked button that is busy keeps cursor: progress (.btn.is-submitting below). Checked by docs/tools/disabledbuttonscheck.ps1. */
:where(.btn, .icon-btn, .tab, .modal-close, button.link):is(:disabled, [disabled], [aria-disabled="true"]) {   /* :where keeps this below .btn.is-submitting (cursor: progress) */
    cursor: not-allowed;
    -webkit-tap-highlight-color: transparent;
}

:is(.icon-btn, .modal-close, button.link):is(:disabled, [disabled]) {
    opacity: 0.7;
}

.tab:is(:disabled, [disabled]) {
    opacity: 0.75;   /* its label starts muted, so it needs a little more to keep 3:1 */
    cursor: not-allowed;   /* .tab sets pointer later in this file; this rule is more specific */
}

/* Busy submit button (JavaScript/busy-submit.js; UI_STANDARDS 4.4). The button is NOT disabled, because a disabled submit
   button's name is not posted and ASP.NET would not see the click. It is made inert with aria-disabled + .is-submitting. */
.btn.is-submitting {
    opacity: 0.85;
    cursor: progress;
    pointer-events: none;
    transform: none;
}

/* The spinner inside a busy button or link (added by busy-submit.js): uses the text colour so it reads on a primary, secondary or
   danger button in both themes; a faint track plus an arc, which is still a clear ring when reduced motion stops the spin.
   A button that NAVIGATES (a full-page postback) gets no spinner at all (UI_STANDARDS 4.4.9): it is label only, so none of the spinner
   rules below (or the data-busy-spin padding) apply to it. */
.is-submitting .spinner {
    border-color: color-mix(in srgb, currentColor 30%, transparent);
    border-top-color: currentColor;
    margin-right: 8px;
    flex: none;
}

/* An input button cannot hold a spinner: busy-submit.js lays one over its left padding (a sibling) and the input reserves the room. */
.spinner.busy-spinner {
    position: absolute;
    left: var(--busy-x, 0);
    top: var(--busy-y, 0);
    z-index: 1;
    margin: 0;
    color: var(--busy-c, inherit);
    border-color: color-mix(in srgb, currentColor 30%, transparent);
    border-top-color: currentColor;
    pointer-events: none;
}

.btn.is-submitting[data-busy-spin] {
    padding-left: 40px;
}

/* The MT mark loader in a button (mt-loader--btn, mt-loader.css): 12px + 36px + 8px of room on the left of an input button. A compact
   button (under 36px high) gets the smaller mark: 12px + 28px + 8px. */
.btn.is-submitting[data-busy-spin="mark"] {
    padding-left: 56px;
}

.btn.is-submitting[data-busy-spin="mark-sm"] {
    padding-left: 48px;
}

.is-submitting > .mt-loader--btn {
    margin-right: 8px;
}

.mt-loader.mt-loader--btn.busy-spinner {
    position: absolute;
    left: var(--busy-x, 0);
    top: var(--busy-y, 0);
    z-index: 1;
    margin: 0;
    color: var(--busy-c, inherit);
    opacity: 0.85;   /* the busy input has the same opacity, so the mark and its label match */
    pointer-events: none;
}

/* The busy button never gets narrower than it was (busy-submit.js records its width in --busy-minw), so a shorter label shifts nothing. */
.btn.is-submitting:not([data-busy-icon]) {
    min-width: max(96px, var(--busy-minw, 0px));
}

.is-submitting[data-busy-icon] .spinner,
.is-submitting[data-busy-icon] .mt-loader--btn {
    margin-right: 0;
}

/* Make footer stack cleanly */
.pay-footer {
    margin-top: 12px;
}

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
    .pay-moreoptions,
    .pay-moreoptions-clip {
        transition: none !important;
    }
}

/* Table sort like MyScheduledReports */
th.sortable {
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}

    th.sortable .sort-icon {
        display: inline-block;
        width: 14px;
        height: 14px;
        margin-left: 4px;
        opacity: 0.6;
    }

        th.sortable .sort-icon::before {
            content: "\25B2";
            font-size: 10px;
            color: var(--muted);
        }

    th.sortable.sorted-asc .sort-icon::before {
        content: "\25B2";
        font-size: 10px;
    }

    th.sortable.sorted-desc .sort-icon::before {
        content: "\25BC";
        font-size: 10px;
    }

/* ---------------------------------------------------------
   Empty states
   TL-11 2026-09-18: moved here from table.app.css (page-linked)
   so any page can use .empty-state without that stylesheet.
   --------------------------------------------------------- */



.empty-state {
    text-align: center;
    padding: 48px 20px 64px;
    color: var(--muted);
    animation: emptyFadeIn 360ms ease-out;
}

@keyframes emptyFadeIn {
    from {
        opacity: 0;
        transform: translateY(-4px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.empty-state h2 {
    font-size: 20px;
    font-weight: 600;
    margin: 16px 0 8px;
    color: var(--ink);
}

/* TL-59: the two additions NoAccess.aspx needs on top of the shared empty state - the refused
   path, and the way back. Both are inside .empty-state, which already centres and dims. */
.no-access-path {
    margin-top: 4px;
}

    .no-access-path code {
        font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
        font-size: 13px;
        color: var(--ink);
        background: var(--surface-subtle);
        border: 1px solid var(--border);
        border-radius: 6px;
        padding: 2px 6px;
        word-break: break-all;
    }

/* =================================================================================================
   TL-165: THE ERROR REFERENCE ON Errors.aspx. A user reads this down the phone to support, so it is
   built to be read aloud and selected, not to be decorative.

   IT REUSES .no-access-path's code styling deliberately rather than inventing a second monospace
   treatment - the two pages are siblings and a reference should look the same in both.

   user-select: all IS THE POINT OF THE BLOCK. A 32-character hex id is miserable to select by
   dragging; one click takes the whole thing. It is on the code element only, so the surrounding
   labels stay normally selectable.

   NO max-width HERE, unlike .empty-state p (360px) - an id must not wrap mid-string, and
   word-break would hyphenate it into something a user reads out wrongly. It is 32 characters and
   fits on one line at every width the application supports. */
/* TL-169: THE ERROR PAGE READS LEFT-ALIGNED, NOT CENTRED.

   .empty-state sets text-align: center, which is right for NoAccess - a heading, one sentence
   and a button. This page is longer: an apology, what happens next, a reference to read out and
   two instructions about it. Centred ragged-both-sides text at that length is measurably harder
   to read, because every line starts at a different x and the eye has to re-find the start.

   Scoped to this page's own container rather than changing .empty-state, which NoAccess and the
   other empty states still want centred. */
.error-page .empty-state,
.error-page .empty-state p,
.error-page .empty-state h2 {
    text-align: left;
}

.error-page .empty-state p {
    margin-inline: 0;
}

.error-ref {
    margin: 20px 0 0;
    padding: 12px 16px;
    max-width: 360px;
    background: var(--surface-subtle);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    text-align: left;
}

    .error-ref .error-ref-label {
        margin: 0 0 6px 0;
        font-size: 12px;
        font-weight: 600;
        color: var(--muted);
    }

    .error-ref .error-ref-value {
        margin: 0 0 8px 0;
        max-width: none;
    }

        .error-ref .error-ref-value code {
            font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
            font-size: 14px;
            color: var(--ink);
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: 6px;
            padding: 6px 10px;
            display: inline-block;
            user-select: all;
        }

    .error-ref .error-ref-hint {
        margin: 0;
        font-size: 12px;
        color: var(--muted);
    }

/* The administrator-only detail card. Left deliberately plain - it is a wall of stack trace and
   nothing about it should compete with the message above it. */
.error-detail-note {
    font-size: 13px;
    color: var(--muted);
    margin: 0 0 12px 0;
}

/* TL-169: THE DETAIL MUST BE REACHABLE. A stack trace is taller than any viewport and the shell
   is a fixed-height app frame whose cards scroll internally, so the overflow had nowhere to go -
   the card clipped it and the page did not scroll, leaving the bottom of the trace unreachable
   for the one audience that needs it.

   THE PRE ITSELF IS THE SCROLL OWNER, not the page. Making the whole page scroll would work but
   would push the message and the reference off the top while an administrator reads the trace;
   keeping the scroll inside the block leaves the reference on screen, which is the thing they
   read out. max-height is in vh so it adapts to the window rather than guessing a pixel height.

   pre-wrap AND break-word together: a stack trace has very long unbroken frames, and without the
   break they would scroll sideways as well, which is the same unreachability in the other axis. */
.error-detail {
    max-height: 60vh;
    overflow: auto;
    background: var(--surface-subtle);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 12px;
}

    .error-detail .errors {
        display: block;
        font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
        font-size: 12px;
        line-height: 1.45;
        white-space: pre-wrap;
        overflow-wrap: break-word;
        word-break: break-word;
    }

/* The primary here is .btn.full (D02: .btn-primary is auto width); the column it fills is
   narrowed instead, so the button does not span the whole card. */
.no-access-actions {
    margin: 24px auto 0;
    max-width: 280px;
}

.empty-state p {
    font-size: 14px;
    max-width: 360px;
    margin: 0 auto;
    line-height: 1.5;
}

.empty-illustration svg {
    width: 96px;
    height: 96px;
    color: var(--brand-ink);
    opacity: .3;
    margin-bottom: 16px;
}

/* =====================================================================
   TL-223  Security/UserPreferences.aspx and Controls/ucPreferenceList
   Classes only, tokens only: themes and dark mode come from the variables.
   ===================================================================== */
.prefs-page .card + .card {
    margin-top: 16px;
}

/* Room for the profile picture later: the fields sit in a row that can take an avatar column. */
.prefs-profile-head {
    display: flex;
    gap: 16px;
    align-items: flex-start;
}

.prefs-profile-fields {
    flex: 1 1 auto;
    min-width: 0;
}

.prefs-links {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;   /* right-aligned in the row, like every other action row (developer 2026-10-13) */
    gap: 12px;
}

/* the buttons in the row are one size, whatever the words are */
.prefs-links .btn {
    flex: 0 0 11rem;
    width: 11rem;
    justify-content: center;
}

@media (max-width: 480px) {
    .prefs-links { justify-content: stretch; }
    .prefs-links .btn { flex: 1 1 100%; width: 100%; }
}

/* APPEARANCE (dark mode) - the three-option control in the account menu and on the Preferences hub row (JavaScript/theme-init.js).
   It is the standard radio-based segmented toggle (controls.css: .segmented-toggle, its labels and sliding thumb); this block only sets what differs:
   the labels carry an icon and a word, each option is 44px tall, and the selected one reads by fill, a check in the corner and a bold label, never
   by colour alone. The check sits in the corner so the three words fit at 264px (the menu on a 320 phone) without truncation. */
.segmented-toggle.segmented-toggle--appearance {   /* two classes: controls.css loads later and sets the same --seg-* hooks on .segmented-toggle */
    --seg-height: 50px;
    --seg-pad: 2px;
    --seg-thumb: var(--seg-thumb-active);
    box-sizing: border-box;
    width: 100%;
    transform: none;
}

    .segmented-toggle.segmented-toggle--appearance:hover { transform: none; }

/* no slide on load: theme-init.js moves the radios to the saved choice, and sets data-theme-ready on html once that has been painted */
html:not([data-theme-ready]) .segmented-toggle--appearance .segmented-toggle__thumb { transition: none; }

    .segmented-toggle--appearance .segmented-toggle__thumb { box-shadow: inset 0 0 0 1px var(--brand-ink); }

    .segmented-toggle--appearance .segmented-toggle__label {
        gap: 5px;
        padding: 0 4px;
        font-size: 13px;
        font-weight: 500;
        min-height: 44px;
    }

        .segmented-toggle--appearance .segmented-toggle__label .icon { flex: 0 0 auto; width: 18px; height: 18px; color: var(--brand-ink); }

    .segmented-toggle--appearance .segmented-toggle__input:checked + .segmented-toggle__label { font-weight: 700; }

    .segmented-toggle--appearance .segmented-toggle__input:checked + .segmented-toggle__label::after {
        content: "\2713";
        position: absolute;
        top: 3px;
        right: 6px;
        font-size: 10px;
        line-height: 1;
        color: var(--ink);
    }

@media (forced-colors: active) {
    .segmented-toggle--appearance .segmented-toggle__input:checked + .segmented-toggle__label { outline: 2px solid Highlight; outline-offset: -2px; }
}

/* In the account menu: a small "Appearance" label, then the control, as the first block. The menu is as wide as its widest line, so the block
   asks for 264px (the most that fits a 320 phone with the menu's own padding) and never more than the screen leaves. */
/* The three radios stay OUT of the grid on every page. reportShell.css (report pages only) has a bare input[type="radio"] rule (position: relative, 16px) that
   beats the one-class .segmented-toggle__input (position: absolute) in controls.css; the radios then became grid items, the labels scattered over two rows and
   the thumb sat over the wrong slot (appearance-align_handoff.md). Two classes win, and each label is pinned to its own column so the row can never reflow. */
.segmented-toggle--appearance .segmented-toggle__input {
    position: absolute;
    top: 0;
    left: 0;
    width: 1px;
    height: 1px;
    margin: 0;
    opacity: 0;
    pointer-events: none;
}

/* keyboard focus: the shared 3px ring recipe (theme.css, buttons and links) instead of the faint legacy --focus-ring tint, drawn inside the segment so the menu never clips it */
.segmented-toggle--appearance .segmented-toggle__input:focus-visible + .segmented-toggle__label {
    outline: 3px solid color-mix(in srgb, var(--brand-primary) 55%, var(--shade-toward));
    outline-offset: -3px;
}

.segmented-toggle--appearance .segmented-toggle__label { grid-row: 1; margin: 0; box-sizing: border-box; }
.segmented-toggle--appearance .segmented-toggle__label:nth-of-type(1) { grid-column: 1; }
.segmented-toggle--appearance .segmented-toggle__label:nth-of-type(2) { grid-column: 2; }
.segmented-toggle--appearance .segmented-toggle__label:nth-of-type(3) { grid-column: 3; }

/* in the account menu the options are icons only (developer 2026-10-17); the words stay as visually hidden text and the tooltip, so each icon is a touch bigger.
   The selected option is shown by the thumb behind its icon (tinted fill plus a 1px brand ring, and a Highlight outline in forced colours), so the corner check
   is not drawn here: with no word to share the slot it floated off-centre and said nothing the ring does not. The Preferences row (words) keeps its check. */
.user-flyout .segmented-toggle--appearance .segmented-toggle__label .icon { width: 22px; height: 22px; }
.user-flyout .segmented-toggle--appearance .segmented-toggle__input:checked + .segmented-toggle__label::after { content: none; }
.user-flyout .segmented-toggle--appearance .segmented-toggle__input:checked + .segmented-toggle__label .icon { color: var(--ink); }
.user-flyout .segmented-toggle--appearance .segmented-toggle__input:not(:checked) + .segmented-toggle__label:hover { background: var(--hover-chip); }
.theme-seg__title { display: block; margin: 2px 0 6px 2px; font-size: 12px; font-weight: 600; color: var(--muted); }
.user-flyout .theme-seg { width: min(264px, calc(100vw - 56px)); padding: 0 0 6px; }
/* PREFERENCES HUB (2026-10-16): a single column of rows, icon + title + one line + chevron, 44px or more, no divider lines (space and a hover well).
   The Appearance row carries the segmented control itself (.settings-row--control): below the text on a phone, to the right from 640 up. */
.settings-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }

.settings-row {
    display: flex;
    align-items: center;
    gap: 16px;
    width: 100%;
    min-height: 56px;
    padding: 8px 12px;
    border: 0;
    border-radius: 10px;
    background: transparent;
    color: var(--ink);
    font: inherit;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}

.settings-row:hover { background: var(--surface-2); }
.settings-row:focus-visible { outline: 3px solid color-mix(in srgb, var(--brand-primary) 55%, black); outline-offset: 2px; }

.settings-icon { flex: 0 0 auto; color: var(--brand-ink); }
.settings-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.settings-title { font-weight: 600; }
.settings-line { color: var(--muted); font-size: .9rem; }

/* the Appearance row: not a link or a button, so no hover well and no pointer; the control wraps under the text on a phone */
.settings-row--control { flex-wrap: wrap; cursor: default; }
.settings-row--control .settings-text { flex: 1 1 0; }   /* basis 0 so the icon and the words stay on one line and only the control wraps */
.settings-row--control:hover { background: transparent; }
.settings-row--control .segmented-toggle--appearance { flex: 1 1 100%; margin: 4px 0 4px; }
@media (min-width: 640px) {
    .settings-row--control { flex-wrap: nowrap; }
    .settings-row--control .segmented-toggle--appearance { flex: 0 0 300px; width: 300px; margin: 0; }
}

/* "go to this page" arrow (links only; the Appearance row has none). Phosphor arrow-up-right, not a chevron: chevrons are for expansion (UI_STANDARDS 9) */
.settings-row .settings-go { flex: 0 0 auto; width: 18px; height: 18px; color: var(--muted); }

@media (forced-colors: active) {
    .settings-row:hover { background: Highlight; color: HighlightText; }
}

.pref-section + .pref-section {
    margin-top: 20px;
}

.pref-section-title {
    margin: 0 0 8px;
    font-size: 13px;
    font-weight: 700;
    color: var(--muted);
}

.pref-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px 16px;
    padding: 12px 0;
    border-top: 1px solid var(--border);
}

    .pref-row:first-of-type {
        border-top: 0;
    }

    .pref-row.is-busy {
        opacity: .7;
    }

.pref-text {
    flex: 1 1 260px;
    min-width: 0;
}

.pref-label {
    font-weight: 600;
    color: var(--ink);
}

.pref-help,
.pref-note {
    margin: 4px 0 0;
    font-size: 13px;
    color: var(--muted);
    line-height: 1.4;
}

.pref-note {
    color: var(--ink);
}

.pref-control {
    display: flex;
    align-items: center;
    gap: 12px;
    flex: 0 1 auto;
    max-width: 100%;
}

    .pref-control .pref-input {
        min-width: 160px;
        max-width: 100%;
    }

.pref-status {
    min-height: 1.2em;
    font-size: 13px;
    color: var(--muted);
}

    .pref-status.ok {
        color: var(--success);
    }

    .pref-status.error {
        color: var(--danger);
    }

@media (max-width: 480px) {
    .pref-text {
        flex-basis: 100%;
    }

    .pref-control {
        flex-wrap: wrap;
        width: 100%;
    }

        .pref-control .pref-input {
            flex: 1 1 100%;
        }
}

/* TL-223 step C: changing your own email address */
.pref-email-line {
    display: flex;
    gap: 12px;
    align-items: center;
}

    .pref-email-line .input {
        flex: 1 1 auto;
        min-width: 0;
    }

    .pref-email-line .btn {
        flex: 0 0 auto;
    }

.pref-email-pending {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 12px;
    align-items: center;
    justify-content: space-between;
    margin-top: 8px;
    padding: 10px 12px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface-success);
    color: var(--ink);
    font-size: 14px;
}

.pref-email-form {
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--border);
}

/* display:flex above would otherwise beat the browser's [hidden] rule */
.pref-email-pending[hidden],
.pref-email-form[hidden],
.pref-email-line .btn[hidden] {
    display: none;
}

.pref-email-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
}

@media (max-width: 480px) {
    .pref-email-line {
        flex-wrap: wrap;
    }
}

/* TL-223: a link that is drawn as a button (Change password, Go to Preferences) must read as a BUTTON - no underline, in
   every state - and keeps .btn / .btn-secondary's own hover (the lift and the brand fill). The anchor's default
   underline was the only thing that made it look like a link. */
a.btn,
a.btn:hover,
a.btn:focus,
a.btn:active,
a.btn:visited {
    text-decoration: none;
}

a.btn-secondary:visited {
    color: var(--button-secondary);
}

a.btn-secondary:hover:not(:where(:disabled, [disabled], [aria-disabled="true"])) {
    color: var(--brand-on-primary);
}

/* =================================================================================================
   U2-013 (UX standards, 2026-10-12): THE HIT AREA IS 44x44, THE VISUAL SIZE IS FREE.

   Measured: the dialog close X is 31x20 and 35x25, icon buttons are 28px, small buttons were 20px,
   dialog buttons 39-40px. Touch is used by real people (landscape tablets), so the floor is
   44x44 (UI_STANDARDS.md 11). The pseudo-element grows the clickable area without moving the glyph
   or the layout, so nothing shifts. It is centred on the control and may overlap its padding or a
   neighbouring gap, never another control's centre.
   ================================================================================================= */
.modal-close {
    position: relative;
}

    .modal-close::after {
        content: "";
        position: absolute;
        left: 50%;
        top: 50%;
        width: max(100%, 44px);
        height: max(100%, 44px);
        transform: translate(-50%, -50%);
    }

/* On a touch screen the rest of the small controls follow, and normal buttons reach 44px. Fine
   pointers (a mouse) keep the compact desktop sizes. */
@media (pointer: coarse) {
    .btn:not(.btn-sm) {
        min-height: 44px;
    }

    .btn-sm,
    .icon-btn {
        position: relative;
    }

        .btn-sm::after,
        .icon-btn::after {
            content: "";
            position: absolute;
            left: 50%;
            top: 50%;
            width: max(100%, 44px);
            height: max(100%, 44px);
            transform: translate(-50%, -50%);
        }
}

/* D09 (UX standards): the shared page headers are the page's h1 now (ucReportHeader, ucInsightsHeader),
   and keep the size of the h2 they replaced; the report header's own rule is in reportShell.css. */
.report-header-row .card-header h1 {
    font-size: 1.5rem;
}

@media (max-width: 1100px) {
    .report-header-row .card-header h1 {
        font-size: 1.25rem;
    }
}

/* QuickBooks connections grid: a long company name wraps inside its own column and never runs over the id column */
.qbo-grid td { vertical-align: top; }
.qbo-grid td.qbo-name { white-space: normal; overflow-wrap: anywhere; min-width: 12rem; max-width: 28rem; }
.qbo-grid td.qbo-id { white-space: nowrap; }


/* =============================================================================================
   Notification authoring (standards S1 to S5, accepted 2026-10-14: docs/current/NOTIFICATION_AUTHORING_DECISIONS.md section 4).
   Reusable by any page: .status-chip, .section-index, .summary-collapse, .card-compact, .table-cards, .card-footer-stack.
   Tokens only. First used by Settings/Notifications/*.aspx (App_Themes/Core/noticeAuthor.css holds that page's own rules).
   ============================================================================================= */

/* S3: status chip. A tonal pill; the status is always spelled out in the text, the tone and the dot only support it. */
.status-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 10px;
    border-radius: 999px;
    border: 1px solid var(--border);
    background: var(--surface-subtle);
    color: var(--ink);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.5;
    white-space: nowrap;
}
.status-chip::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
.status-chip--live { background: var(--surface-success); border-color: var(--success); }
.status-chip--live::before { background: var(--success); }
.status-chip--scheduled { background: var(--surface-warning); border-color: var(--warning); }
.status-chip--scheduled::before { background: var(--warning); }
.status-chip--draft { background: var(--surface-subtle); }
.status-chip--ended, .status-chip--retired { background: var(--surface); }
.status-chip--ended::before, .status-chip--retired::before { background: var(--border); }

/* S3: sticky section index (UI_STANDARDS rule 6): one row that scrolls sideways on a short or narrow window */
.section-index {
    display: flex;
    gap: 20px;
    overflow-x: auto;
    position: sticky;
    top: 0;
    z-index: 2;
    padding: 2px 0;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}
.section-index .link { white-space: nowrap; min-height: 40px; display: inline-flex; align-items: center; }
.section-index .link[aria-current] { font-weight: 700; text-decoration: none; color: var(--brand-ink); }

/* S4: a summary that is a one-line digest when closed (the scheduler digest idea, outside a dialog). From 1024 it is a pane that
   stays open: put .summary-collapse--pane on it and open it from script at that width. */
.summary-collapse { border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface-subtle); }
.summary-collapse > summary { display: flex; flex-direction: column; gap: 2px; padding: 10px 12px; cursor: pointer; min-height: 44px; justify-content: center; }
.summary-collapse__title { font-weight: 600; }
.summary-collapse__digest { font-size: 13px; color: var(--muted); }
.summary-collapse__body { padding: 0 12px 14px; }
@media (min-width: 1024px) {
    .summary-collapse--pane > summary { pointer-events: none; }
    .summary-collapse--pane > summary::-webkit-details-marker { display: none; }
}

/* S1: form-card padding on phones and short windows (one shared rule) */
@media (max-width: 640px) { .card.card-compact { padding: 16px; } }
@media (max-height: 500px) { .card.card-compact { padding: 12px; } }

/* S2: a table at 768 and up, cards under it (was 640 until 2026-10-14: with the 56px left rail a 640 window leaves about 570px of content, too
   narrow for four columns, which scrolled sideways). The header row is kept for assistive technology and every cell is a block. */
@media (max-width: 767.98px) {
    .table-cards thead { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); }
    .table-cards, .table-cards tbody, .table-cards tr, .table-cards td { display: block; width: 100%; box-sizing: border-box; }
    .table-cards.table-modern tbody > tr > td { padding: 2px 0; border: 0; white-space: normal; }
    .table-cards.table-modern tbody > tr { padding: 12px; margin: 0 0 8px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
    .table-cards.table-modern tbody > tr:last-child { margin-bottom: 0; }
}

/* S5: a card footer that holds a status line above its actions (the buttons are direct children of the footer, secondary first,
   primary last in the source). Under 480 it stacks: status line, then the primary full width on top, the secondary under it
   (UI_STANDARDS 4.5, the safety net). Markup: .card-footer.card-footer-stack > (.card-footer-hint) + .btn-secondary + .btn-primary. */
.card-footer-stack { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 8px; }
.card-footer-stack .card-footer-hint { flex: 1 1 auto; margin-right: auto; }
.card-footer-stack .btn { width: auto; min-width: 96px; }
@media (max-width: 480px) {
    .card-footer-stack { flex-direction: column; align-items: stretch; flex-wrap: nowrap; }
    .card-footer-stack > * { order: -2; }
    .card-footer-stack > .btn-primary { order: -1; }
    .card-footer-stack > .btn-secondary { order: 0; }
    .card-footer-stack .btn { width: 100%; }
}
/* SMALL-FORM DENSITY (developer ruling 2026-10-14, UI_STANDARDS 11.1): a form card (.card-compact) is DESIGNED SMALL-FIRST and its normal
   buttons and icon buttons are 40px high up to 767 wide, not the 44px of the global touch rule. One class, one place: a new form gets it by
   being a .card-compact; nothing per page. Above 767 the normal sizes apply. */
@media (max-width: 767.98px) {
    .card.card-compact .btn:not(.btn-sm),
    .card.card-compact .icon-btn { min-height: 40px; }
    .card.card-compact .btn:not(.btn-sm):not(.icon-btn) { height: 40px; }
}

/* STORE PICKER COLLAPSE BAR (2026-10-15; JavaScript/UCScripts/storePickerCollapse.js). One bar above the tree: the one-line summary and a chevron.
   The control has no label by design; the bar does not add one. Tokens only. */
.store-picker-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    min-height: 36px;
    margin: 0 0 6px;
    padding: 6px 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-subtle);
    color: var(--ink);
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
}
.store-picker-bar:hover { background: var(--hover-chip); }
.store-picker-bar:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: 2px; }
.store-picker-bar.is-empty { color: var(--danger); }
.store-picker-chevron {
    flex: none;
    width: 8px;
    height: 8px;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg) translateY(-2px);
    transition: transform 140ms ease;
}
.store-picker.is-collapsed .store-picker-chevron { transform: rotate(-45deg); }
.store-picker.is-collapsed .StorePickerTree { display: none; }

/* COMPACT DATES (designer 2026-10-15; JavaScript/UCScripts/ucDateRange.js). The Range dropdown and a sliders button share one row; the resolved
   dates are one line of text under it; From and To fold behind the button. Same inputs, same values: only visibility changes. The folded state uses
   !important because report shells set the dates row's display themselves. */
.date-range.is-compact { display: flex; flex-direction: column; }
.date-range.is-compact .date-range-range { order: 1; }
.date-range.is-compact .date-range-dates { order: 2; margin-top: 8px; }
.date-range.is-compact:not(.is-custom) .date-range-dates { display: none !important; }
.date-range-select-row { display: flex; align-items: center; gap: 8px; }
.date-range-select-row > select { flex: 1 1 auto; min-width: 0; }
.date-range-custom { flex: none; width: 40px; min-width: 40px; height: 40px; padding: 0; }
.date-range-custom.is-active { background: var(--hover-chip); border-color: var(--brand-primary); }
.date-range-resolved { margin-top: 4px; font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* The row's sizes are fixed so no shell or rail rule can stretch the button or collapse the select (the right rail did both). */
.date-range .date-range-select-row { display: flex; align-items: center; gap: 8px; width: 100%; }
.date-range .date-range-select-row > select { flex: 1 1 0 !important; width: auto !important; min-width: 0 !important; }
.date-range .date-range-select-row > .date-range-custom { flex: 0 0 40px !important; width: 40px !important; max-width: 40px !important; min-width: 40px !important; height: 40px !important; padding: 0 !important; }
/* while the dates are being worked out, the line under the range is not shown (the control's own "working out the dates" message owns that moment) */
.date-range.date-range-resolving .date-range-resolved,
.date-range-resolving .date-range-resolved { visibility: hidden; }

/* Opening the custom dates scrolls them into view; this keeps them clear of a pinned footer (right-rail action bar, sticky action rows). */
.date-range .date-range-dates { scroll-margin-bottom: 96px; }

/* =========================================================================================
   ITEM PAGES: header card, breadcrumb and tabs (promoted 2026-10-16 from the "ITEM PAGE" block at the end of requests.css).
   Standard: docs/project/UI_STANDARDS.md section 2.5; small screens: docs/design/SMALL_SCREEN_PAGE_TYPES.md type T10.
   Page-neutral and tokens only. Markup, in order: header.card.item-head > nav.item-crumb > ol > li (parent .link, current li[aria-current="page"]);
   .item-head-row > .item-title (h1 or h2, .status-chip) + .item-actions; optional .tabs[role="tablist"] > button.tab[role="tab"] (the
   tab script lives with the page: JavaScript/cbs-case.js initTabs/selectTab); panels are .tab-panel. No new breakpoint (640 is in use).
   ========================================================================================= */
.item-head { margin: 0 0 16px; max-width: none; }
.item-crumb ol { display: flex; flex-wrap: wrap; align-items: center; gap: 0 4px; list-style: none; margin: 0 0 8px; padding: 0; font-size: .9rem; color: var(--muted); }
.item-crumb li { display: inline-flex; align-items: center; min-height: 40px; }
.item-crumb li + li::before { content: "/"; margin-right: 4px; color: var(--muted); }
.item-crumb li[aria-current="page"] { color: var(--ink); font-weight: 600; }
.item-crumb .link { opacity: 1; text-decoration: none; display: inline-flex; align-items: center; min-height: 40px; }   /* .link is opacity .75 elsewhere: the crumb is full strength for contrast; NOT underlined: the hover/focus bar is in the block NAV AND BREADCRUMB CURRENT STATE at the end of this file */
.item-crumb .link:focus-visible { outline: 3px solid color-mix(in srgb, var(--brand-primary) 55%, black); outline-offset: 2px; }

.item-head-row { display: flex; flex-wrap: wrap; gap: 12px 24px; align-items: flex-start; justify-content: space-between; }
.item-title { display: flex; flex-wrap: wrap; gap: 8px 12px; align-items: center; min-width: 0; }
.item-title h1, .item-title h2 { margin: 0; }
.item-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-left: auto; }   /* margin-left: auto keeps it on the trailing edge, also when it wraps under the title */
.item-actions:empty { display: none; }

/* tabs that swap panels: the header card and the tab bar stay, only the panel changes */
.item-head .tabs { margin-top: 16px; }
.tabs { display: flex; flex-wrap: nowrap; gap: 4px; box-shadow: inset 0 -1px 0 var(--border); }
.tabs[hidden] { display: none; }
.tab {
    appearance: none; background: none; border: 0; border-radius: 8px 8px 0 0; cursor: pointer;
    min-height: 40px; padding: 8px 16px; font: inherit; font-size: 14px; font-weight: 600; color: var(--muted);
    box-shadow: inset 0 -3px 0 transparent;
    transition: color .15s ease, background .15s ease, box-shadow .15s ease;
}
.tab:hover:not(:where(:disabled, [disabled], [aria-disabled="true"])) { color: var(--brand-ink); background: color-mix(in srgb, var(--brand-primary) 8%, var(--surface)); }
.tab[aria-selected="true"] { color: var(--brand-ink); box-shadow: inset 0 -3px 0 var(--brand-primary); }   /* colour and a bar: never colour alone */
.tab:focus-visible { outline: 3px solid color-mix(in srgb, var(--brand-primary) 55%, black); outline-offset: -3px; }
.tab-panel[hidden] { display: none; }
.tab-panel + .tab-panel { margin-top: 16px; }   /* without script the panels stack */
.tab-panel.is-entering { animation: tabIn .18s ease-out; }
@keyframes tabIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
    .tab-panel.is-entering { animation: none; }
    .tab { transition: none; }
}

@media (max-width: 640px) {
    /* the crumb is the parent only, with a chevron in front: "< Requests", a 44px hit area; the card is not sticky (it scrolls with the page) */
    .item-crumb li[aria-current="page"] { display: none; }
    .item-crumb li { min-height: 44px; }
    .item-crumb .link { min-height: 44px; padding-right: 8px; }
    .item-crumb .link::before { content: ""; width: 8px; height: 8px; margin-right: 8px; border-left: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(45deg); }
    .item-crumb ol { margin-bottom: 0; }
    .item-actions { width: 100%; justify-content: flex-start; }

    /* up to about four tabs share the width; a long label wraps to two lines */
    .tabs { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 0; }
    .tab { padding: 6px 4px; white-space: normal; text-align: center; line-height: 1.2; border-radius: 0; }
}

/* =========================================================================================
   NAV AND BREADCRUMB CURRENT STATE (2026-10-16)
   The item-page breadcrumb speaks the same language as the top bar and the left nav (the whole vocabulary is written up at the end of nav.css, same block name).
   Parent crumb: brand-coloured text, NO underline at rest. On hover and on keyboard focus the same 2px bar as the top navigation grows in from the centre,
   with the same duration, easing, thickness and (on a light surface) colour tokens (--nav-underline-*, defined at the top of nav.css). The current crumb is plain dark text.
   Selectors are (0,2,1) on purpose: requests.css still carries its own copy of the crumb rules (with an underline) and loads after this file; this wins without touching it.
   ========================================================================================= */
.item-crumb a.link,
.item-crumb a.link:visited { position: relative; color: var(--brand-ink); text-decoration: none; opacity: 1; font-weight: 500; }
.item-crumb a.link:hover { font-weight: 500; }   /* theme.css .link:hover goes to 600, which would shift the crumbs beside it */

.item-crumb a.link::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: calc(50% + .66em);
    height: var(--nav-underline-height);
    border-radius: var(--nav-underline-height);
    background: var(--nav-underline-on-light);
    transform: scaleX(0);
    transform-origin: center;
    transition: transform var(--nav-underline-duration) var(--nav-underline-ease);
    pointer-events: none;
}
.item-crumb a.link:hover::after,
.item-crumb a.link:focus-visible::after { transform: scaleX(1); }

.item-crumb li[aria-current="page"] { color: var(--ink); text-decoration: none; }

@media (max-width: 640px) {
    .item-crumb a.link::after { left: 16px; right: 8px; }   /* start after the 8px chevron and its 8px gap, stop before the 8px right padding */
}
@media (prefers-reduced-motion: reduce) {
    .item-crumb a.link::after { transition: none; }
}
@media (forced-colors: active) {
    .item-crumb a.link::after { background: LinkText; }
}

/* =============================================================================================
   OVERLAYS (2026-10-16, overlays lane): the scrim behind an open panel on a small screen.
   MT.Overlays (JavaScript/site.core.js) adds ONE div.mt-scrim (aria-hidden) to the body and writes the active overlay on the body as
   data-mt-overlay="rail|bell|account" (the left drawer has its own scrim in layout.css). Under 641 wide, or on a short landscape phone (the
   rail overlay's query), the scrim shows: the bell panel and the account menu live inside the app bar (z-index 1000), so their scrim sits just
   under it (990) and the bar stays in view; the right rail (1100) covers the bar, so its scrim sits just under the rail (1090). The colour is
   --scrim-panel, which has its own dark and light values. A tap on it, or Escape, dismisses the panel (script); html.mt-overlay-lock stops
   the page behind from scrolling. On a wide screen the scrim is never shown.
   ============================================================================================= */
.mt-scrim { display: none; }

@media (max-width: 640px), (max-width: 900px) and (max-height: 500px) {
    body[data-mt-overlay="bell"] .mt-scrim,
    body[data-mt-overlay="account"] .mt-scrim,
    body[data-mt-overlay="rail"] .mt-scrim {
        display: block;
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        background: var(--scrim-panel);
        z-index: 990;
        cursor: pointer;
        touch-action: none;
        -webkit-tap-highlight-color: transparent;
    }

    body[data-mt-overlay="rail"] .mt-scrim { z-index: 1090; }

    html.mt-overlay-lock,
    html.mt-overlay-lock body {
        overflow: hidden;
        overscroll-behavior: none;
    }
}