﻿/*
 * JRU RAF design tokens - the implementation of STYLES.md.
 *
 * This file is the single source for every colour, metric and radius in the application.
 * A value that is not here gets added here first: no hard-coded hex in a component, ever,
 * including inline SVG.
 *
 * MudBlazor is driven from these tokens by re-pointing its own custom properties at ours
 * (see the MudBlazor bridge below), so a colour is declared once and both our components
 * and Mud's read the same value. A palette declared a second time in C# is the
 * dual-presence defect STYLES.md names.
 *
 * Every token a component reads is declared in BOTH the light and the dark block, so no
 * component ever branches on theme.
 */

/* ------------------------------------------------------------------ fonts */
/* Self-hosted. No CDN. Licence sits beside the files in wwwroot/fonts. */
@font-face {
    font-family: "IBM Plex Sans";
    src: url("../fonts/ibm-plex-sans-400.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "IBM Plex Sans";
    src: url("../fonts/ibm-plex-sans-500.woff2") format("woff2");
    font-weight: 500;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "IBM Plex Sans";
    src: url("../fonts/ibm-plex-sans-600.woff2") format("woff2");
    font-weight: 600;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "IBM Plex Mono";
    src: url("../fonts/ibm-plex-mono-400.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "IBM Plex Mono";
    src: url("../fonts/ibm-plex-mono-600.woff2") format("woff2");
    font-weight: 600;
    font-style: normal;
    font-display: swap;
}

/* --------------------------------------------------------- tokens: light */
:root {
    /* Brand: the company's lime and charcoal, which is the letterhead.
       TWO ROLES, AND THEY ARE DIFFERENT COLOURS ON LIGHT. --jru-primary is a FILL: a button, a count, a
       selection bar, always the lime #B9CC04 with the charcoal --jru-on-primary label on it (9.70:1, and
       6.58:1 on the #98A803 it darkens to). --jru-primary-bright is INK: a link, a selected tab's text, a
       focus ring, anything that has to be read or seen against the page. The lime is 1.79:1 on white and
       cannot be ink on light, so light inks in the darkest shade of the same hue, #4E5702, at 7.21:1 on
       the page. A rule that colours text or an outline reads the bright token; a rule that fills reads
       primary. Cost of mixing them: lime text nobody can read. */
    --jru-primary:            #B9CC04;
    --jru-primary-hover:      #98A803;
    --jru-primary-surface:    #F6F9E0;
    --jru-primary-ring:       rgba(185, 204, 4, 0.35);
    --jru-primary-bright:     #4E5702;
    /* The bright token again as a bare triplet, for Mud's rgba() washes. A custom property cannot be
       split into channels, so this literal is unavoidable: change one, change the other. */
    --jru-primary-bright-rgb: 78, 87, 2;
    --jru-on-primary:         #1A1A1A;

    /* The logo's own colour, in both themes and on every ground. The mark is a cut-out, so it is never
       recoloured to suit what it stands on: the ground shows through its roads instead. */
    --jru-mark:               #B9CC04;

    /* The highlighter the Letters screen marks a changed word with, on the page it previews and in the
       subject line: the brand ramp's brightest lime, a pen on paper, always under charcoal text. The
       letter renderers and the mail frame cannot read a custom property, so PreviewInk says it again:
       change one, change the other. */
    --jru-highlighter:        #E1FA05;

    /* A sheet of paper on the desk the Letters screen previews on. Paper is white in both themes. */
    --jru-paper:              #FFFFFF;
    --jru-paper-shadow:       0 1px 2px rgba(15, 23, 42, 0.10), 0 6px 18px rgba(15, 23, 42, 0.08);

    /* Shell.
       Charcoal with no tint at all: the grey of the logo's lettering, so the lime is the only colour in
       the sidebar. A tinted grey beside the lime reads as khaki. The lime is 8.43:1 on it. */
    --jru-shell:              #262626;
    --jru-shell-deep:         #1E1E1E;
    --jru-shell-line:         rgba(255, 255, 255, 0.08);
    --jru-shell-text:         #D4D4D4;
    --jru-shell-text-strong:  #FAFAFA;
    --jru-shell-text-muted:   #929292;
    /* A group heading is read, so it carries 4.5:1 on the shell (4.86:1). */
    --jru-shell-group:        #929292;
    --jru-shell-hover:        rgba(255, 255, 255, 0.06);
    /* A white wash, never a lime one: lime over charcoal mixes to khaki. The lime is the marker beside
       the item instead, in .jru-nav__item.active. */
    --jru-shell-active:       rgba(255, 255, 255, 0.08);

    /* Surfaces */
    /* Said a second time in ThemeService.PageColour, which feeds meta name="theme-color". A meta tag
       cannot read a custom property, so the pair is unavoidable: change one, change the other. */
    --jru-page:               #F4F6F8;
    --jru-surface:            #FFFFFF;
    --jru-surface-alt:        #F8FAFC;
    --jru-border:             #E2E8F0;
    /* The boundary of a control, so it carries 3:1 against the surface it is drawn on (WCAG 1.4.11).
       At the pale slate it used to be it measured 1.48:1 and an operator could not see where a text
       box ended. --jru-border and --jru-border-soft draw separators, which is decoration, and neither
       is held to that. */
    --jru-border-strong:      #7F8C9E;
    --jru-border-soft:        #F1F5F9;
    --jru-hover:              #F8FAFC;

    /* Text */
    --jru-text:               #0F172A;
    --jru-text-secondary:     #475569;
    /* De-emphasised, and still text somebody has to read: 4.5:1 against page, surface and surface-alt
       (WCAG 1.4.3). It is NOT the disabled colour - see --jru-text-disabled below. */
    --jru-text-muted:         #5F6D82;
    /* A control that cannot be used. WCAG exempts it from contrast, and it is a separate token
       because the two wants are opposite: muted text has to be read, a disabled control has to
       recede. One token serving both is how muted text ended up at 2.56:1. */
    --jru-text-disabled:      #94A3B8;

    /* Status */
    --jru-success:            #15803D;
    --jru-attention:          #D97706;
    --jru-danger:             #B91C1C;
    --jru-info:               #2563EB;

    /* Status badge trios */
    --jru-badge-pass-bg:      #F0FDF4;
    --jru-badge-pass-fg:      #15803D;
    --jru-badge-pass-bd:      #BBF7D0;
    --jru-badge-partial-bg:   #F0FDF4;
    --jru-badge-partial-fg:   #166534;
    --jru-badge-partial-bd:   #BBF7D0;
    --jru-badge-pended-bg:    #FFF7ED;
    --jru-badge-pended-fg:    #92400E;
    --jru-badge-pended-bd:    #FED7AA;
    --jru-badge-warning-bg:   #FFFBEB;
    --jru-badge-warning-fg:   #92400E;
    --jru-badge-warning-bd:   #FDE68A;
    --jru-badge-fail-bg:      #FEF2F2;
    --jru-badge-fail-fg:      #B91C1C;
    --jru-badge-fail-bd:      #FECACA;
    --jru-badge-info-bg:      #EFF6FF;
    --jru-badge-info-fg:      #2563EB;
    --jru-badge-info-bd:      #BFDBFE;
    --jru-badge-draft-bg:     #F8FAFC;
    --jru-badge-draft-fg:     #475569;
    --jru-badge-draft-bd:     #E2E8F0;

    /* Row and reason tints */
    --jru-row-attention:      #FFFDFB;
    --jru-reason-amber-bg:    #FFFBF5;
    --jru-reason-amber-fg:    #92400E;
    --jru-reason-red-bg:      #FEF7F7;
    --jru-reason-red-fg:      #B91C1C;

    /* Type */
    --jru-sans:               "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
    --jru-mono:               "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;

    /* Shape */
    --jru-radius-card:        11px;
    --jru-radius-control:     7px;
    --jru-radius-input:       6px;

    /* Elevation */
    --jru-elev-1:             0 1px 2px rgba(15, 23, 42, 0.04);
    --jru-elev-2:             0 2px 8px rgba(15, 23, 42, 0.08);
    --jru-elev-sticky:        0 -2px 6px rgba(15, 23, 42, 0.05);

    /* Layout */
    --jru-control-h:          34px;

    /* The heading row's height, and the offset the filter row under it sticks at. One number, or
       the two disagree by a pixel or two and the filter row shaves the heading's own border. */
    --jru-grid-head-h:        30px;

    /* The band row above the headings on a very wide grid, and the two frozen identity columns
       beside it. The second column's `left` is the first one's width, so all three are declared
       here and nowhere else: a width chosen at a call site leaves a seam that moves. */
    --jru-grid-band-h:        25px;
    --jru-grid-stick-1:       176px;
    --jru-grid-stick-2:       108px;

    --jru-drawer-width:       236px;
    --jru-rail-width:         56px;
    --jru-appbar-height:      52px;
    --jru-list-panel:         360px;
    --jru-queue-panel:        262px;
    --jru-inspector:          320px;
    --jru-lookup-width:       720px;

    /* Motion */
    --jru-motion:             170ms;
    --jru-ease:               cubic-bezier(0.2, 0, 0.2, 1);

    /* How patient the product is, in one place: how long a wait runs before it says out loud that
       it has become an unusual one. An animation delay, never a timer. */
    --jru-loader-delay:       900ms;

    color-scheme: light;
}

/* ---------------------------------------------------------- tokens: dark */
[data-theme="dark"] {
    --jru-page:               #161616;
    --jru-surface:            #1F1F1F;
    --jru-surface-alt:        #161616;
    --jru-border:             #2E2E2E;
    /* 3:1 against surface and page, as in light: 3.33:1 and 3.65:1. It once measured 1.27:1 here, and
       a text box on dark had no visible edge at all. */
    --jru-border-strong:      #707070;
    --jru-border-soft:        #2E2E2E;
    --jru-hover:              #282828;

    --jru-text:               #EDEDED;
    --jru-text-secondary:     #A8A8A8;
    /* 4.5:1 against page, surface and hover: 5.97:1, 5.43:1 and 4.86:1. */
    --jru-text-muted:         #949494;
    --jru-text-disabled:      #666666;

    /* On dark the lime is both fill and ink: 9.18:1 on the surface as a link. The fill keeps its
       charcoal label. The selection tint is lighter than on light, because lime over charcoal turns
       khaki past about a tenth. */
    --jru-primary:            #B9CC04;
    --jru-primary-hover:      #98A803;
    --jru-primary-bright:     #B9CC04;
    --jru-primary-bright-rgb: 185, 204, 4;
    --jru-primary-surface:    rgba(185, 204, 4, 0.08);
    --jru-primary-ring:       rgba(185, 204, 4, 0.30);
    --jru-on-primary:         #1A1A1A;

    /* The paper stays white; only its shadow deepens to read on the dark desk. */
    --jru-paper-shadow:       0 1px 2px rgba(0, 0, 0, 0.55), 0 6px 18px rgba(0, 0, 0, 0.45);

    --jru-shell:              #111111;
    --jru-shell-deep:         #0B0B0B;

    --jru-success:            #4ADE80;
    --jru-attention:          #FBBF24;
    --jru-danger:             #FCA5A5;
    --jru-info:               #7DD3FC;

    /* A 16 percent wash of the hue, a 45 percent border, the bright hue as text. */
    --jru-badge-pass-bg:      rgba(74, 222, 128, 0.16);
    --jru-badge-pass-fg:      #4ADE80;
    --jru-badge-pass-bd:      rgba(74, 222, 128, 0.45);
    --jru-badge-partial-bg:   rgba(52, 211, 153, 0.16);
    --jru-badge-partial-fg:   #34D399;
    --jru-badge-partial-bd:   rgba(52, 211, 153, 0.45);
    --jru-badge-pended-bg:    rgba(251, 146, 60, 0.16);
    --jru-badge-pended-fg:    #FB923C;
    --jru-badge-pended-bd:    rgba(251, 146, 60, 0.45);
    --jru-badge-warning-bg:   rgba(251, 191, 36, 0.16);
    --jru-badge-warning-fg:   #FBBF24;
    --jru-badge-warning-bd:   rgba(251, 191, 36, 0.45);
    --jru-badge-fail-bg:      rgba(252, 165, 165, 0.16);
    --jru-badge-fail-fg:      #FCA5A5;
    --jru-badge-fail-bd:      rgba(252, 165, 165, 0.45);
    --jru-badge-info-bg:      rgba(125, 211, 252, 0.16);
    --jru-badge-info-fg:      #7DD3FC;
    --jru-badge-info-bd:      rgba(125, 211, 252, 0.45);
    --jru-badge-draft-bg:     rgba(148, 163, 184, 0.16);
    --jru-badge-draft-fg:     #94A3B8;
    --jru-badge-draft-bd:     rgba(148, 163, 184, 0.45);

    --jru-row-attention:      rgba(251, 191, 36, 0.06);
    --jru-reason-amber-bg:    rgba(251, 191, 36, 0.10);
    --jru-reason-amber-fg:    #FBBF24;
    --jru-reason-red-bg:      rgba(252, 165, 165, 0.10);
    --jru-reason-red-fg:      #FCA5A5;

    --jru-elev-1:             0 1px 2px rgba(0, 0, 0, 0.30);
    --jru-elev-2:             0 2px 8px rgba(0, 0, 0, 0.42);
    --jru-elev-sticky:        0 -2px 6px rgba(0, 0, 0, 0.34);

    color-scheme: dark;
}

/* ------------------------------------------------------- MudBlazor bridge */
/*
 * Mud emits its own custom properties from MudTheme. Pointing them at our tokens is what
 * keeps a colour declared once. JruRafTheme.cs therefore carries no palette: see the note
 * in that file.
 *
 * The selector is html:root, not :root, deliberately. MudThemeProvider injects its own
 * :root block into the head at render time, which lands after this stylesheet and would
 * win on source order. html:root scores one element higher, so our tokens win wherever
 * the block ends up, and no rule here needs !important.
 */
html:root,
html:root[data-theme="dark"] {
    /*
     * Mud's primary is INK, because Mud reads it for far more text than fill: a text or outlined
     * button, a checkbox, a switch, a tab. The components Mud fills with it re-point it at the lime in
     * the rule under this block, so a filled button still wears the logo's colour. The hover is a tint
     * behind a text button, which is what Mud means by it, and darken is the filled hover.
     */
    --mud-palette-primary:            var(--jru-primary-bright);
    --mud-palette-primary-rgb:        var(--jru-primary-bright-rgb);
    --mud-palette-primary-hover:      var(--jru-primary-surface);
    --mud-palette-primary-darken:     var(--jru-primary-hover);
    --mud-palette-primary-text:       var(--jru-on-primary);
    --mud-palette-secondary:          var(--jru-info);
    --mud-palette-success:            var(--jru-success);
    --mud-palette-warning:            var(--jru-attention);
    --mud-palette-error:              var(--jru-danger);
    --mud-palette-info:               var(--jru-info);
    --mud-palette-background:         var(--jru-page);
    --mud-palette-background-gray:    var(--jru-surface-alt);
    --mud-palette-surface:            var(--jru-surface);
    --mud-palette-text-primary:       var(--jru-text);
    --mud-palette-text-secondary:     var(--jru-text-secondary);
    --mud-palette-text-disabled:      var(--jru-text-disabled);
    --mud-palette-divider:            var(--jru-border);
    --mud-palette-lines-default:      var(--jru-border);
    --mud-palette-lines-inputs:       var(--jru-border-strong);
    --mud-palette-action-default:     var(--jru-text-secondary);

    /*
     * The disabled pair, and they are not decoration. Unbridged, Mud falls back to its own dark
     * defaults: a disabled outlined button rendered as a near-black bar with its label invisible
     * inside it, which reads as an error state rather than as a control that is not ready yet.
     * A disabled control should recede, not shout.
     */
    --mud-palette-action-disabled:            var(--jru-text-disabled);
    --mud-palette-action-disabled-background: var(--jru-surface-alt);
    --mud-default-borderradius:       var(--jru-radius-control);
}

/*
 * What Mud FILLS with primary takes the lime and its charcoal label. Re-pointing the variable on the
 * element rather than restyling the background leaves every one of Mud's own states - hover, focus,
 * active, ripple - reading the value it expects, and the icon inside inherits the label colour.
 */
.mud-button-filled-primary,
.mud-chip-filled.mud-chip-color-primary,
.mud-fab-primary,
.mud-fab-filled-primary,
.mud-avatar-filled-primary,
.mud-alert-filled-primary,
.mud-theme-primary {
    --mud-palette-primary: var(--jru-primary);
}

/*
 * The type families are NOT bridged here. JruRafTheme.cs declares them, because Mud builds
 * its typography classes from the C# object and a second declaration in this file would be
 * the dual presence STYLES.md forbids. Colour and metric live here; type family lives there.
 */

/* ------------------------------------------------------------------- base */
html,
body {
    height: 100%;
}

body {
    margin: 0;
    background: var(--jru-page);
    color: var(--jru-text);
    font-family: var(--jru-sans);
    font-size: 13.5px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}

h1 {
    font-size: 19px;
    font-weight: 600;
    letter-spacing: -0.01em;
    margin: 0;
}

h2 {
    font-size: 15px;
    font-weight: 600;
    margin: 0;
}

a {
    color: var(--jru-primary-bright);
    text-decoration: none;
}

a:hover {
    text-decoration: underline;
}

/* Focus is always visible, on every interactive element including table rows. */
:focus-visible {
    outline: 2px solid var(--jru-primary-bright);
    outline-offset: 1px;
    border-radius: 3px;
}

/*
 * Touch, said once for everything that takes a press.
 *
 * `manipulation` drops the 300ms wait a touch browser spends deciding whether a tap was the first
 * half of a double-tap zoom. The product is a console driven at speed and none of its controls
 * zoom, so that wait buys nothing and costs a third of a second on every press.
 *
 * The tap highlight is set rather than left alone: the default is a grey-blue wash the platform
 * picks, which lands on a lime button as a colour nothing in the token set contains. Focus and
 * hover already say a control was hit.
 */
button,
a,
label,
summary,
input,
select,
textarea,
[role="button"],
[role="tab"],
[role="option"] {
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
}

/*
 * SKIP TO CONTENT, and it has to be the first focusable thing in the document.
 *
 * The shell puts a sidebar of nav links and an app bar ahead of every screen, so a keyboard reaches
 * the thing it came for through about forty stops. This is one.
 *
 * It is off-screen rather than hidden: `display:none` and `visibility:hidden` both take an element
 * out of the tab order, which is the one thing this must not lose. The target carries
 * `tabindex="-1"` so the caret genuinely lands there - a fragment link scrolls without it, and a
 * keyboard user carries on tabbing from the nav they just skipped.
 */
.jru-skiplink {
    position: absolute;
    top: 6px;
    left: 6px;
    z-index: 2000;
    padding: 9px 14px;
    border-radius: var(--jru-radius-control);
    background: var(--jru-primary);
    color: var(--jru-on-primary);
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
    transform: translateY(calc(-100% - 10px));
    transition: transform var(--jru-motion) var(--jru-ease);
}

.jru-skiplink:focus-visible {
    transform: translateY(0);
}

.jru-content:focus,
.jru-content:focus-visible {
    outline: none;
}

@media (prefers-reduced-motion: reduce) {
    .jru-skiplink {
        transition: none;
    }
}

/*
 * The page heading is the one exception, and it is not an accessibility regression.
 *
 * The router moves focus to the h1 after every navigation (FocusOnNavigate in Routes.razor) so
 * that a screen reader announces the page it has arrived on. That is worth keeping. What is not
 * worth keeping is what it looked like: the rule above painted a 2px box the full width of the
 * main pane around the heading of every screen, on every navigation, for a target nobody can
 * click. The announcement still happens; the box does not.
 */
h1:focus,
h1:focus-visible {
    outline: none;
}

.jru-mono {
    font-family: var(--jru-mono);
    font-variant-numeric: tabular-nums;
}

/*
 * A code box whose value is kept in capitals shows capitals from the first key, not from the moment
 * the box is left. The value itself is upper-cased on change by CodeTextField; this is only the
 * letters under the caret while typing.
 */
.jru-upper input {
    text-transform: uppercase;
}

/*
 * Pinned to the corner of its containing block, never left where it would have sat in the flow.
 * Every scroll box in the shell is unpositioned, so an absolute box's containing block is outside
 * the one it appears in and the scroll box does not clip it. Left in place, the balance mark's
 * label on a claim far down a batch's list sat 218px below the window and made the whole page
 * scroll, dragging the menu up with it and leaving blank space under the screen.
 *
 * The rail's words go the same way rather than to display:none, which took each icon's name out of
 * the accessibility tree along with the text and left a column of unnamed links.
 */
.jru-visually-hidden,
.jru-shell--rail .jru-nav__label {
    position: absolute;
    top: 0;
    left: 0;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ------------------------------------------------------------------ shell */
/*
 * The main pane owns the viewport height and clips its overflow, so a screen can pin its
 * own header and a sticky totals strip. Anything that floats above the shell is
 * position:fixed markup of our own - never a MudDrawer, which would be positioned inside
 * this clip and silently never appear.
 */
.jru-shell {
    display: grid;
    grid-template-columns: var(--jru-drawer-width) minmax(0, 1fr);
    height: 100vh;
    overflow: hidden;
}

.jru-shell--rail {
    grid-template-columns: var(--jru-rail-width) minmax(0, 1fr);
}

.jru-sidebar {
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: var(--jru-shell);
    border-right: 1px solid var(--jru-shell-line);
}

.jru-brand {
    display: flex;
    align-items: center;
    gap: 9px;
    height: var(--jru-appbar-height);
    padding: 0 14px;
    flex: 0 0 auto;
    border-bottom: 1px solid var(--jru-shell-line);
    color: var(--jru-shell-text-strong);
    font-size: 14px;
    font-weight: 600;
    letter-spacing: -0.01em;
    white-space: nowrap;
    overflow: hidden;
}

/*
 * The mark stands on the charcoal directly, with no tile behind it: its roads are cut out, and a tile
 * would turn them into a second colour the logo does not have.
 */
.jru-brand__mark {
    flex: 0 0 auto;
    color: var(--jru-mark);
}

.jru-main {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
}

.jru-appbar {
    display: flex;
    align-items: center;
    gap: 10px;
    height: var(--jru-appbar-height);
    flex: 0 0 auto;
    padding: 0 18px;
    background: var(--jru-surface);
    border-bottom: 1px solid var(--jru-border);
}

.jru-appbar__spacer {
    flex: 1 1 auto;
}

.jru-iconbutton {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    flex: 0 0 auto;
    border: 1px solid transparent;
    border-radius: var(--jru-radius-input);
    background: transparent;
    color: var(--jru-text-secondary);
    cursor: pointer;
    font-size: 18px;
    line-height: 1;
    transition: background var(--jru-motion) var(--jru-ease);
}

.jru-iconbutton:hover {
    background: var(--jru-hover);
    color: var(--jru-text);
}

/*
 * A CONTROL THAT IS UNAVAILABLE HAS TO LOOK UNAVAILABLE, or it invites the press and swallows it.
 * Left to the base rules a disabled one keeps the live colour, the pointer cursor AND the hover
 * tint, so it lights up under the operator's finger and then does nothing: the two controls on a
 * closed payment run's lines were reported as broken rather than as unavailable, and there was
 * nothing on the screen to correct that reading. The tooltip carries the reason and this carries
 * the affordance; the pair is what "a control that refuses is better than one that vanishes"
 * needs to be true.
 */
.jru-iconbutton:disabled {
    color: var(--jru-text-disabled);
    cursor: not-allowed;
}

.jru-iconbutton:disabled:hover {
    background: transparent;
    color: var(--jru-text-disabled);
}

/*
 * An anchor wearing the row control's clothes - DownloadLink. Its own defaults are the whole
 * difference it used to show beside a delete button: a link colour that made the download read as
 * the important one of the two, and an underline on hover where a row control tints a square.
 */
a.jru-iconbutton {
    color: var(--jru-text-secondary);
    text-decoration: none;
}

a.jru-iconbutton:hover {
    color: var(--jru-text);
    text-decoration: none;
}

.jru-iconbutton:focus-visible {
    outline: 2px solid var(--jru-primary-bright);
    outline-offset: -2px;
}

.jru-searchcue {
    display: flex;
    align-items: center;
    gap: 8px;
    height: 30px;
    padding: 0 10px;
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-input);
    background: var(--jru-surface-alt);
    color: var(--jru-text-muted);
    font-size: 12px;
    cursor: pointer;
    min-width: 210px;
}

.jru-kbd {
    font-family: var(--jru-mono);
    font-size: 10.5px;
    padding: 1px 5px;
    border: 1px solid var(--jru-border);
    border-radius: 4px;
    background: var(--jru-surface);
    color: var(--jru-text-secondary);
}

/* The shortcut printed at the right edge of the search cue. */
.jru-searchcue__keys {
    margin-left: auto;
}

/* A menu item that has to be a form's submit button - Sign out posts - drawn across the menu like the
   items beside it. A button's content box centres by default. */
.jru-menu__formbutton {
    width: 100%;
    text-align: left;
}

/*
 * THE MAIN PANE HOLDS EVERY SCREEN THE OPERATOR HAS OPEN, stacked in one grid cell with one in front.
 * Each screen is its own scroll box and carries the padding the pane used to, so a screen brought back
 * is scrolled where it was left.
 *
 * Behind is visibility:hidden, never display:none: a display:none box forgets its scroll position, and
 * the whole point of keeping a screen is that nothing about it moves. The hidden screen still takes no
 * room, because it shares the cell with the one in front. WorkspaceView adds inert beside it.
 */
.jru-content {
    flex: 1 1 auto;
    min-height: 0;
    display: grid;
    grid-template: minmax(0, 1fr) / minmax(0, 1fr);
    overflow: hidden;
}

.jru-screen {
    grid-area: 1 / 1;
    min-width: 0;
    min-height: 0;
    overflow: auto;
    padding: 18px 24px 24px;
}

.jru-screen--behind {
    visibility: hidden;
}

/* ------------------------------------------------------------ open screens */
/*
 * The tabs in the app bar, one per open screen. Shaped like the search cue beside them - the same
 * height, radius and type - so the bar reads as one row of controls rather than a second strip.
 *
 * The screen in front is marked the way the menu marks where the operator is and the way a page's own
 * tabs mark the one showing: the lime bar. It is a fill, so it reads --jru-primary; the label stays in
 * the text colour, with a weight change, so the mark reads in greyscale too.
 *
 * The close control shows on the tab in front and on the one under the pointer or the caret, the way
 * a browser's own tabs do. Everywhere else it would be six crosses in a row competing with the names.
 * While a screen has been refused for want of room, every tab shows it, because closing one is the
 * thing the operator has just been told to do.
 *
 * TABS SHRINK THE WAY A BROWSER'S DO, their names cut short, before the strip scrolls. A strip that
 * only scrolled put the tab in front out of sight at half a laptop's width, and WorkspaceTabs brings
 * it back into view when it cannot all fit.
 */
/* A thin scrollbar rather than none: in a window too narrow for every tab, it is the only sign there
   are more. */
.jru-screens {
    flex: 0 1 auto;
    min-width: 0;
    overflow-x: auto;
    scrollbar-width: thin;
}

.jru-screens__list {
    display: flex;
    align-items: center;
    gap: 4px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.jru-screens__tab {
    display: flex;
    align-items: center;
    flex: 0 1 auto;
    min-width: 84px;
    height: 30px;
    border: 1px solid transparent;
    border-radius: var(--jru-radius-input);
    color: var(--jru-text-secondary);
    transition: background var(--jru-motion) var(--jru-ease);
}

.jru-screens__tab:hover {
    background: var(--jru-hover);
    color: var(--jru-text);
}

.jru-screens__tab.is-front {
    background: var(--jru-surface-alt);
    border-color: var(--jru-border);
    box-shadow: inset 0 -2px 0 var(--jru-primary);
    color: var(--jru-text);
    font-weight: 500;
}

.jru-screens__link {
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    min-width: 0;
    gap: 7px;
    height: 100%;
    padding: 0 8px 0 10px;
    color: inherit;
    font-size: 12.5px;
    text-decoration: none;
    border-radius: var(--jru-radius-input);
}

.jru-screens__link:hover {
    color: inherit;
    text-decoration: none;
}

.jru-screens__link:focus-visible,
.jru-screens__close:focus-visible {
    outline: 2px solid var(--jru-primary-bright);
    outline-offset: -2px;
}

.jru-screens__icon {
    flex: 0 0 auto;
    font-size: 16px;
}

.jru-screens__label {
    min-width: 0;
    max-width: 160px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.jru-screens__close {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    width: 22px;
    height: 22px;
    margin-right: 4px;
    padding: 0;
    border: none;
    border-radius: 4px;
    background: transparent;
    color: var(--jru-text-muted);
    cursor: pointer;
    font-size: 14px;
    visibility: hidden;
}

.jru-screens__tab.is-front .jru-screens__close,
.jru-screens__tab:hover .jru-screens__close,
.jru-screens__tab:focus-within .jru-screens__close,
.jru-screens--full .jru-screens__close {
    visibility: visible;
}

.jru-screens__close:hover {
    background: var(--jru-border);
    color: var(--jru-text);
}

/* Why the last screen asked for did not open. Empty, it takes no room; drawn, the tabs give way. */
.jru-screens__notice {
    flex: 0 0 auto;
    max-width: 280px;
    color: var(--jru-reason-amber-fg);
    font-size: 12px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.jru-screens__notice:empty {
    display: none;
}

/* -------------------------------------------------------------------- nav */
/*
 * THE HEIGHTS HERE ARE SET SO AN ADMINISTRATOR'S MENU FITS A 1080p WINDOW, with both folding groups
 * closed: 903px, where 32px items with a gap between them and 14px above each heading made it 1080px
 * and put Reports below the foot of the window. No spacing fits a laptop, which has about 680px for
 * the menu; the rail and Ctrl K are how a small screen reaches the rest. An item is still 28px tall,
 * past the smallest target anybody's pointer has to hit.
 */
.jru-nav {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 6px 0 8px;
}

.jru-nav__group {
    padding: 8px 16px 3px;
    color: var(--jru-shell-group);
    font-size: 9.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.12em;
}

.jru-shell--rail .jru-nav__group {
    padding: 9px 0 3px;
    font-size: 0;
}

.jru-shell--rail .jru-nav__group::after {
    content: "";
    display: block;
    width: 18px;
    height: 1px;
    margin: 0 auto;
    background: var(--jru-shell-line);
}

.jru-nav__item {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 8px;
    padding: 0 10px;
    height: 28px;
    border-radius: var(--jru-radius-input);
    color: var(--jru-shell-text);
    font-size: 12.5px;
    white-space: nowrap;
    overflow: hidden;
    cursor: pointer;
    transition: background var(--jru-motion) var(--jru-ease);
}

.jru-nav__item:hover {
    background: var(--jru-shell-hover);
    color: var(--jru-shell-text-strong);
    text-decoration: none;
}

/* The lime marker is the logo's colour saying where you are; the wash under it is white, because lime
   over charcoal mixes to khaki. */
.jru-nav__item.active {
    background: var(--jru-shell-active);
    box-shadow: inset 3px 0 0 var(--jru-mark);
    color: var(--jru-shell-text-strong);
    font-weight: 500;
}

.jru-nav__icon {
    flex: 0 0 auto;
    width: 18px;
    font-size: 17px;
    line-height: 1;
    display: grid;
    place-items: center;
}

.jru-shell--rail .jru-nav__item {
    justify-content: center;
    padding: 0;
}

/*
 * The line between the pipeline and the rest of Claims. Inset from both sides and shorter in the rail
 * than the line that stands for a group heading there, so it reads as a pause inside a group and not
 * as the start of another.
 */
.jru-nav__divider {
    height: 1px;
    margin: 6px 18px;
    background: var(--jru-shell-line);
}

.jru-shell--rail .jru-nav__divider {
    margin: 6px 23px;
}

/*
 * The rail's label, drawn by jru-nav.js beside the item under the pointer or the caret. The shell's
 * own charcoal in both themes, so it reads as the menu reaching out and not as a surface of the
 * screen it lies over. It never takes the pointer: it is a label, not a control. Below every panel and
 * dialog, because nothing in the menu can be under the pointer while one is open.
 */
.jru-navtip {
    position: fixed;
    z-index: 1100;
    transform: translateY(-50%);
    padding: 4px 9px;
    border: 1px solid var(--jru-shell-line);
    border-radius: var(--jru-radius-input);
    background: var(--jru-shell-deep);
    color: var(--jru-shell-text-strong);
    font-size: 12.5px;
    line-height: 1.4;
    white-space: nowrap;
    pointer-events: none;
}

/*
 * A group that folds: Reference and Administration. Its heading is a button wearing the group
 * heading's type, so it still reads as a heading, with the count and an arrow for what it holds. The
 * text starts on the same 16px line as every other heading, and the arrow is the only thing that moves.
 */
.jru-nav__fold {
    display: flex;
    align-items: center;
    gap: 6px;
    width: calc(100% - 16px);
    height: 24px;
    margin: 4px 8px 0;
    padding: 0 6px 0 8px;
    border: 0;
    border-radius: var(--jru-radius-input);
    background: transparent;
    color: var(--jru-shell-group);
    font-family: inherit;
    font-size: 9.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    text-align: left;
    cursor: pointer;
    transition: background var(--jru-motion) var(--jru-ease),
                color var(--jru-motion) var(--jru-ease);
}

.jru-nav__fold:hover {
    background: var(--jru-shell-hover);
    color: var(--jru-shell-text);
}

/* Inside the button, or the nav's own scroll box cuts the ring off at both sides. */
.jru-nav__fold:focus-visible {
    outline-offset: -2px;
}

.jru-nav__foldlabel {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.jru-nav__count {
    font-variant-numeric: tabular-nums;
    letter-spacing: 0;
}

/*
 * The operator's own feedback items with something on them they have not seen. The logo's lime with
 * the charcoal label, as the board's Replied mark wears. In the rail it sits on the icon's corner,
 * because the words beside it are gone there.
 */
.jru-nav__news {
    flex: 0 0 auto;
    min-width: 16px;
    height: 16px;
    margin-left: auto;
    padding: 0 5px;
    border-radius: 999px;
    background: var(--jru-primary);
    color: var(--jru-on-primary);
    font-size: 10.5px;
    font-weight: 600;
    line-height: 16px;
    letter-spacing: 0;
    text-align: center;
    text-transform: none;
    font-variant-numeric: tabular-nums;
}

.jru-shell--rail .jru-nav__item,
.jru-shell--rail .jru-nav__fold {
    position: relative;
}

/* The item's top-right corner, so it covers a corner of the icon rather than the icon. */
.jru-shell--rail .jru-nav__news {
    position: absolute;
    top: 0;
    right: 0;
    min-width: 12px;
    height: 12px;
    padding: 0 2px;
    font-size: 8.5px;
    line-height: 12px;
}

/* The rail's stand-in for the words. The full menu has the words and does not need it. */
.jru-nav__foldicon {
    display: none;
}

.jru-nav__chevron {
    display: grid;
    place-items: center;
    width: 16px;
    line-height: 1;
    transition: transform var(--jru-motion) var(--jru-ease);
}

.jru-nav__chevron .mud-icon-root {
    font-size: 16px;
}

.jru-nav__fold[aria-expanded="true"] .jru-nav__chevron {
    transform: rotate(90deg);
}

.jru-shell--rail .jru-nav__fold {
    justify-content: center;
    gap: 0;
    height: 28px;
    margin: 9px 8px 1px;
    padding: 0;
    color: var(--jru-shell-text);
}

.jru-shell--rail .jru-nav__foldlabel,
.jru-shell--rail .jru-nav__count {
    display: none;
}

.jru-shell--rail .jru-nav__foldicon {
    display: grid;
    place-items: center;
    width: 18px;
}

.jru-shell--rail .jru-nav__chevron {
    width: 10px;
}

.jru-shell--rail .jru-nav__chevron .mud-icon-root {
    font-size: 13px;
}

@media (prefers-reduced-motion: reduce) {
    .jru-nav__fold,
    .jru-nav__chevron {
        transition: none;
    }
}

/* ------------------------------------------------------------ page header */
.jru-pageheader {
    display: flex;
    align-items: flex-start;
    gap: 16px;
    margin-bottom: 16px;
}

.jru-pageheader__text {
    min-width: 0;
    flex: 1 1 auto;
}

.jru-pageheader__meta {
    margin-top: 2px;
    color: var(--jru-text-muted);
    font-size: 11.5px;
}

.jru-pageheader__actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
}

/* ------------------------------------------------------------------- card */
.jru-card {
    background: var(--jru-surface);
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-card);
    box-shadow: var(--jru-elev-1);
}

.jru-card__header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 15px;
    border-bottom: 1px solid var(--jru-border-soft);
    font-size: 13.5px;
    font-weight: 600;
}

/* The title may wrap. As a flex item its minimum width was its content, so a title carrying a long
   file name - the load report's "Practices from ..." - pushed the badge off the card's edge. */
.jru-card__header > :first-child {
    min-width: 0;
    overflow-wrap: anywhere;
}

/* A select in a form that has no business spanning the form's width. */
.jru-select--capped {
    max-width: 320px;
}

/*
 * ONE ROW OF THE LOAD LIST: which step it is, and what it loads.
 *
 * The entities are offered in the order they depend on one another - a plan cannot load before its
 * scheme - and a list carrying a real order that says nothing about it reads as a list in no order at
 * all, which is how it was reported. The number is the order, stated.
 *
 * It is a fixed column rather than a prefix on the label, so every label starts on one x and the
 * two-digit steps do not shunt them. Tabular figures for the same reason.
 */
.jru-loadstep {
    display: flex;
    align-items: baseline;
    gap: 10px;
    min-width: 0;
}

.jru-loadstep__number {
    flex: 0 0 auto;
    width: 1.4em;
    text-align: right;
    font-family: var(--jru-mono);
    font-variant-numeric: tabular-nums;
    font-size: 11.5px;
    color: var(--jru-text-muted);
}

/*
 * A card header reads left to right as identity then actions, and the actions keep the right edge.
 * Ten headers pushed them there with an inline `flex:1 1 auto` div, which is the same layout
 * decision `.jru-toolbar__controls` already settled: name the trailing group and give it the auto
 * margin, rather than name an empty element between the two. The gap is the header's own, so a
 * group of one looks no different from a group of three.
 */
.jru-card__header-end {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-left: auto;
}

.jru-card__body {
    padding: 15px;
}

/*
 * A RESULT CARD: what a run produced - its figures, the lines under them, and a section of detail.
 * The output run and the load report wrote the same three paddings inline, which is spacing decided
 * in a .razor file where the token set cannot answer for it.
 *
 * THE LINE IS A FLEX ROW, and that is the overflow fix as well as the gap. "Filed in" beside a server
 * path was a literal space and an inline identifier, and `.jru-code` can only bound itself once it is
 * a flex or grid item, so a long path ran past the card. As a flex child it is capped at the line and
 * scrolls in place. The last line takes the section's bottom padding, so the card never ends on 8px.
 */
.jru-result__block {
    padding: 12px;
}

.jru-result__line {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px;
    min-width: 0;
    padding: 0 12px 8px;
}

.jru-result__line:last-child,
.jru-result__section {
    padding-bottom: 12px;
}

.jru-result__section {
    padding: 0 12px 12px;
}

/*
 * A DIALOG'S TITLE ROW - the icon that says what kind of question this is, beside the question. All
 * three dialogs wrote the same inline flex here; only their content differs, one computing its icon,
 * one fixed and one interpolating a run number, so the layout is what is shared and a component
 * would be wrapping three different things to pass on one rule.
 */
.jru-dialog__title {
    display: flex;
    align-items: center;
    gap: 8px;
}

/*
 * A CONFIRMATION'S THREE PARTS: the record it is about, the consequence, and what goes with it.
 * All four measurements were inline styles on the markup - two margins, a weight and a size - which
 * is spacing and type decided in a .razor file where the token set cannot answer for them. The list
 * indent is the browser's own put back deliberately: a cascade of things about to be destroyed reads
 * as a list, and the default 40px pushes it away from the sentence that introduces it.
 */
.jru-confirm__record {
    margin-bottom: 6px;
}

.jru-confirm__name {
    font-weight: 600;
}

.jru-confirm__cascade {
    margin-top: 10px;
}

.jru-confirm__cascade ul {
    margin: 6px 0 0;
    padding-left: 18px;
}

.jru-confirm__cascade li {
    font-size: 12.5px;
}

/* The sentence under a dialog's question that explains what the choice does. */
.jru-confirm__detail {
    font-size: 12.5px;
    margin-bottom: 12px;
}

/* The filter card above a master-detail grid. The gap under it was an inline margin on the one
   screen that has one, which is a spacing decision written in markup and owned nowhere. */
.jru-card--filters {
    margin-bottom: 16px;
}

/*
 * THE GAP BETWEEN TWO STACKED CARDS, named from each side because either card can be the one that
 * knows. `--above` is a card with another card under it; `--below` is a card that follows one. A
 * card that is both - a repeated card inside a loop - takes both, and the margins collapse the way
 * the inline pair they replaced did.
 *
 * WHY TWO NAMES FOR ONE MEASUREMENT. Which card carries the gap is decided by which of the two is
 * conditional: a card rendered only sometimes cannot be the one that spaces the pair, or the gap
 * disappears with it. Naming both sides lets the screen put it on the card that is always there.
 *
 * WHY ONE VALUE. Twenty-six of these were written inline in five spellings across two measurements,
 * 12px and 16px, and no screen could say why it had the one it had. Twelve is what
 * `.jru-banner--spaced` already uses for the same job one element up.
 */
.jru-card--above {
    margin-bottom: 12px;
}

.jru-card--below {
    margin-top: 12px;
}

.jru-card__toolbar {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 15px;
    border-bottom: 1px solid var(--jru-border-soft);
    flex-wrap: wrap;
}

/*
 * The row UNDER a grid - what is shown of what there is, and the control that fetches more. It is
 * not the toolbar: that narrows the rows and belongs above them, and this reports on them. The
 * members screen drew it with `.jru-card__toolbar` plus two inline border overrides to undo the
 * bottom rule and add a top one, which is the same class meaning two things and a screen correcting
 * it in markup.
 */
.jru-card__footer {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 15px;
    border-top: 1px solid var(--jru-border-soft);
    flex-wrap: wrap;
}

.jru-card__footer-end {
    margin-left: auto;
}

/* ------------------------------------------------------------------- grid */
/*
 * Density is functional. Rows 34-36px, cells 12.5px. Wide content scrolls inside its own
 * container so the shell never scrolls horizontally.
 */
/*
 * The grid virtualises, and a virtualiser needs a viewport with a height it can measure. Without
 * the max-height this box takes its height from its content and its content takes its height from
 * the box, so nothing is ever in view and the grid renders its headings over empty space. It is a
 * max and not a height, so a five-row list is still five rows tall.
 */
.jru-grid__scroll {
    overflow-x: auto;
    overflow-y: auto;
    min-width: 0;
    min-height: 0;
    max-height: calc(100vh - var(--jru-appbar-height) - 210px);
}

table.jru-grid {
    width: 100%;
    min-width: 0;
    border-collapse: separate;
    border-spacing: 0;
    font-size: 12.5px;
    line-height: 1.35;
}

table.jru-grid thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--jru-surface-alt);
    border-bottom: 1px solid var(--jru-border);
    padding: 7px 10px;
    text-align: left;
    white-space: nowrap;
    color: var(--jru-text-secondary);
    font-size: 10.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

table.jru-grid tbody td {
    height: var(--jru-control-h);
    padding: 0 10px;
    border-bottom: 1px solid var(--jru-border-soft);
    vertical-align: middle;
}

table.jru-grid tbody tr:hover {
    background: var(--jru-hover);
}

table.jru-grid tbody tr.is-selected {
    background: var(--jru-primary-surface);
}

table.jru-grid tbody tr.is-attention {
    background: var(--jru-row-attention);
}

table.jru-grid tbody tr.is-retired td {
    color: var(--jru-text-muted);
}

table.jru-grid tbody tr.is-clickable {
    cursor: pointer;
}

/*
 * THE BAND ROW: the groups a very wide grid's columns fall into, drawn.
 *
 * Only the Overview report earns it. Thirty-five columns reach from capture through the escalation
 * letters into the court applications and the payments, and OverviewColumns already declares them in
 * groups - so rendering them as thirty-five siblings throws away structure the code holds and the
 * operator has to scan one heading at a time to find out where in the sheet they are.
 *
 * It sticks above the heading row rather than with it, so the two stack while the body scrolls under
 * them. The offset is --jru-grid-head-h's counterpart and is declared once here: the heading row
 * below reads it, or the two disagree by a pixel and the band row shaves the heading's own border.
 */
table.jru-grid thead tr.jru-grid__bands th {
    top: 0;
    height: var(--jru-grid-band-h);
    padding: 0 10px;
    font-size: 10px;
    letter-spacing: 0.1em;
    color: var(--jru-text-muted);
    border-bottom: 1px solid var(--jru-border-soft);
}

table.jru-grid thead:has(tr.jru-grid__bands) tr:not(.jru-grid__bands) th {
    top: var(--jru-grid-band-h);
}

/*
 * The rule at a band's first column, carried down through the body. It is the thing that makes a
 * band legible once the reader has scrolled past its heading: the name goes, the boundary stays.
 */
.jru-grid__band-start {
    border-left: 1px solid var(--jru-border);
}

/*
 * A COLUMN THAT HOLDS ITS PLACE while the rest of the row scrolls under it.
 *
 * Scrolled to Payment To JRU Amount on a thirty-five column grid, the operator can no longer see
 * whose money they are reading, which is the whole failure of a wide report. The identity columns
 * stay put instead.
 *
 * The widths are fixed and declared here because the second column's `left` is the first one's
 * width: a width set at a call site and an offset set here is the dual-presence defect, and it
 * shows up as a one-pixel seam that moves as the content changes. A sticky cell also needs an
 * opaque ground of its own - the row's background paints behind the row, not behind a cell lifted
 * out of it - so every state the row can be in redeclares it.
 */
table.jru-grid th.jru-grid__stick-1,
table.jru-grid td.jru-grid__stick-1,
table.jru-grid th.jru-grid__stick-2,
table.jru-grid td.jru-grid__stick-2 {
    position: sticky;
}

table.jru-grid th.jru-grid__stick-1,
table.jru-grid td.jru-grid__stick-1 {
    left: 0;
    width: var(--jru-grid-stick-1);
    min-width: var(--jru-grid-stick-1);
    max-width: var(--jru-grid-stick-1);
}

table.jru-grid th.jru-grid__stick-2,
table.jru-grid td.jru-grid__stick-2 {
    left: var(--jru-grid-stick-1);
    width: var(--jru-grid-stick-2);
    min-width: var(--jru-grid-stick-2);
    max-width: var(--jru-grid-stick-2);
}

/* A band cell spanning exactly the frozen pair sticks with them and covers both widths. */
table.jru-grid thead tr.jru-grid__bands th.jru-grid__stick-1 {
    width: calc(var(--jru-grid-stick-1) + var(--jru-grid-stick-2));
}

/* Above the body's sticky cells, which are above the ordinary ones. */
table.jru-grid thead th.jru-grid__stick-1,
table.jru-grid thead th.jru-grid__stick-2 {
    z-index: 3;
}

table.jru-grid tbody td.jru-grid__stick-1,
table.jru-grid tbody td.jru-grid__stick-2 {
    z-index: 1;
    background: var(--jru-surface);
}

table.jru-grid tbody tr:hover td.jru-grid__stick-1,
table.jru-grid tbody tr:hover td.jru-grid__stick-2 {
    background: var(--jru-hover);
}

table.jru-grid tbody tr.is-attention td.jru-grid__stick-1,
table.jru-grid tbody tr.is-attention td.jru-grid__stick-2 {
    background: var(--jru-row-attention);
}

table.jru-grid tbody tr.is-selected td.jru-grid__stick-1,
table.jru-grid tbody tr.is-selected td.jru-grid__stick-2 {
    background: var(--jru-primary-surface);
}

/*
 * The seam at the frozen block's right edge. A border on the cell scrolls with the table's own
 * border-collapse:separate spacing and leaves a gap; a painted pseudo-element does not.
 */
table.jru-grid .jru-grid__stick-2::after,
table.jru-grid thead tr.jru-grid__bands th.jru-grid__stick-1::after {
    content: "";
    position: absolute;
    top: 0;
    right: -1px;
    bottom: 0;
    width: 1px;
    background: var(--jru-border);
}

/* A frozen name is capped at its column rather than widening it. The whole value is in the row. */
.jru-grid__clip {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
 * ONE LINE PER ROW, on a held list long enough to scroll far. The virtualiser places every row at
 * the grid's fixed row height, so a row wrapping to two or three lines puts each row after it in the
 * wrong place: on the Overview report's nine thousand rows a fast scroll left the grid blank until it
 * was scrolled again. The cell stays on its line and the column takes the width.
 */
table.jru-grid td.jru-grid__oneline {
    white-space: nowrap;
}

/*
 * THE COLUMN THAT TAKES WHAT THE OTHERS LEAVE, and clips inside it rather than widening the table.
 * In an auto-laid table a long line in its last column pushes the grid wider than its card; a zero
 * max-width lets the column have the remaining width and no more, and the cell's .jru-grid__clip
 * ends the line with an ellipsis there. NOT width: 100% as well: that takes the width from the
 * columns whose headings state one, and a date column wraps onto three lines.
 */
table.jru-grid td.jru-grid__fill {
    max-width: 0;
}

/*
 * A record named in a cell: its type, then its own key, quieter. The key is the part that differs row
 * to row, so it is the part set apart; it keeps the text face, because a key is as often a name or a
 * phrase ("I Ramatsoele", "Demand letter") as a number, and a name in a code face reads as a code.
 */
.jru-auditrecord {
    display: flex;
    align-items: baseline;
    gap: 8px;
    min-width: 0;
    white-space: nowrap;
}

.jru-auditrecord > :first-child {
    flex: none;
}

.jru-auditrecord__key {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--jru-text-secondary);
    font-variant-numeric: tabular-nums;
}

/*
 * A read-only summary in a panel: each caption beside its value, a line each. A column of captions
 * stacked over values spent two lines on every fact and read as a form with nothing to fill in.
 */
.jru-auditfacts {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    align-items: baseline;
    column-gap: 20px;
    row-gap: 10px;
    margin: 0;
}

.jru-auditfacts dt {
    color: var(--jru-text-secondary);
    font-size: 10.5px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.07em;
}

.jru-auditfacts dd {
    margin: 0;
    min-width: 0;
    overflow-wrap: anywhere;
}

/* A clipped value with a short flag beside it, on the one line, the flag never clipped. */
.jru-grid__flagged {
    display: flex;
    align-items: baseline;
    gap: 6px;
    min-width: 0;
    white-space: nowrap;
}

.jru-grid__flagged > .jru-grid__clip {
    flex: 1 1 auto;
    min-width: 0;
}

.jru-grid__flagged > :not(.jru-grid__clip) {
    flex: none;
}

.jru-grid__num {
    text-align: right;
    font-family: var(--jru-mono);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/*
 * A NUMBER COLUMN'S HEADING SITS OVER ITS NUMBERS.
 *
 * `table.jru-grid thead th` states `text-align: left` and outranks `.jru-grid__num` on specificity,
 * so every amount, count and batch number in the product was right-aligned under a heading drawn at
 * the column's left edge - a heading a column's width away from what it names. The heading keeps the
 * heading's own face rather than the figures' mono.
 *
 * The sort caret goes on the heading's left here, so the word itself ends where the figures do.
 */
table.jru-grid thead th.jru-grid__num {
    text-align: right;
    font-family: inherit;
}

table.jru-grid thead th.jru-grid__num .jru-grid__sort {
    flex-direction: row-reverse;
}

/* A reason written under a badge in a cell - a refused e-mail's server message, a double payment's
   sentence. It wraps, because the reason is the part the operator came to read. */
.jru-cellnote {
    margin-top: 3px;
    color: var(--jru-text-secondary);
    font-size: 11.5px;
    white-space: normal;
    overflow-wrap: anywhere;
}

/* A note about something still to decide - a tariff code whose rows carry two rule categories. The
   badge's amber ink and not --jru-attention, which is a fill colour and reads at about 3:1 as text
   this small. */
.jru-cellnote--attention {
    color: var(--jru-badge-warning-fg);
}

/* A note under a right-aligned amount lines up with the amount, and names the rule that set it. */
.jru-grid__num .jru-cellnote {
    font-family: var(--jru-sans);
    font-variant-numeric: normal;
}

/* A sentence under a card's heading that says how to read the list below it. Card padding on the
   sides so it starts where the first column does; one step up from a field hint, because it is read
   rather than glanced at. */
.jru-card__lede {
    margin: 0;
    padding: 10px 15px 2px;
    max-width: 80ch;
    color: var(--jru-text-secondary);
    font-size: 12.5px;
}

/* A long free-text value cut to one line, with the whole of it in the cell's title. */
.jru-grid__truncate {
    max-width: 320px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
 * A REFERENCE IN A GRID COLUMN THAT MUST NOT SET THE COLUMN'S WIDTH. Put on a block .jru-code inside
 * the cell, where .jru-code's own rule takes over: never wrapped, never cut, and a value longer than
 * this scrolls in place. A table lays its columns out by their longest cell, so on Claims enquiries
 * the one twenty-character supplier reference somewhere in the rendered window took the column to
 * 162px, the practice name down to four lines on every row, and the Run column off the card at 1366.
 * Fourteen characters show whole, which is most of them; the title carries the rest.
 *
 * Compound, because .jru-code is declared further down with its own max-width and would win a tie.
 */
.jru-code.jru-grid__ref {
    max-width: 100px;
}

/*
 * The actions cell stays a table cell. Setting display:flex on a td takes it out of the table's
 * own layout, so it stops sizing with its column and stops aligning with the row beside it -
 * the buttons drift up against the cell's top edge and the column width is whatever the content
 * happens to be. Right-align the cell and let the buttons sit inline instead.
 */
td.jru-grid__actions,
th.jru-grid__actions {
    text-align: right;
    white-space: nowrap;
}

/*
 * Every control in an actions cell is 26px and the cell carries 6px above and below it, so the
 * row grows to 38px and the control has clear space on all four sides. A control left at its own
 * height touched the row's top, bottom and right border at once - a 32px icon button inside a
 * 34px row has half a pixel of air, and Mud's own small button has none.
 *
 * The cell holds four shapes - a labelled button, an icon button, a bare link around an icon and
 * our own .jru-iconbutton - so all four are sized here rather than by whichever page happened to
 * notice. One height keeps a row with two of them from stepping.
 */
table.jru-grid tbody td.jru-grid__actions {
    padding: 6px 12px;
}

table.jru-grid thead th.jru-grid__actions {
    padding-right: 12px;
}

/*
 * THE ROW CONTROLS ARE PINNED TO THE RIGHT OF THE SCROLL BOX, AND THAT IS WHAT MAKES THEM
 * REACHABLE.
 *
 * Wide content scrolls inside its own container, which is the rule the shell is built on - but a
 * column of controls that scrolls with it is a control the operator never learns about. Measured on
 * the capture screen at 1536 wide: the claim lines grid wants 1039px in an 844px pane, so Delete,
 * Reject, Copy and the add row's own Add button all sat 115px past the right edge, behind a
 * scrollbar nobody had reason to look for. Two testers asked in writing how to delete a line and how
 * to delete a document. Both controls had been there all along.
 *
 * Sticky costs nothing where the grid fits: the cell is already at the right edge and stays there.
 *
 * THE CELL HAS TO BE OPAQUE OR THE ROW SCROLLS THROUGH IT. A td draws no background of its own, so
 * each of the four row states is restated here. Two of them are translucent in the dark theme, and
 * a translucent colour over nothing is still nothing: those are layered over the surface as a
 * one-stop gradient rather than given a second hard-coded colour that would have to be kept in step
 * with the row's.
 */
table.jru-grid thead th.jru-grid__actions,
table.jru-grid tbody td.jru-grid__actions {
    position: sticky;
    right: 0;
    border-left: 1px solid var(--jru-border-soft);
}

table.jru-grid tbody td.jru-grid__actions {
    z-index: 1;
    background: var(--jru-surface);
}

/* Above the body cells sliding under it, and above its own column's heading corner. */
table.jru-grid thead th.jru-grid__actions {
    z-index: 2;
}

/*
 * AND A BOX THAT TAKES THE CARET COMES OUT FROM UNDER THEM. The browser scrolls a focused box into
 * view, but it counts the pinned column as part of the view, so the claim line's Claimed box took the
 * caret while sitting behind the add row's own Add and Save at 1536 wide (feedback 176 and 177). The
 * margin is the widest actions column in the product with a little to spare; on a narrower one the
 * box comes a few pixels further out than it strictly needs to, which costs nothing.
 *
 * ON THE BOXES, NEVER AS PADDING ON THE SCROLL BOX. Padding puts the pinned column itself inside the
 * zone the browser keeps clear, so pressing a row's Edit or Delete focused a button the browser then
 * tried to scroll out of that zone, and the grid jumped sideways under the pointer on every click.
 */
.jru-grid__scroll:has(th.jru-grid__actions) td:not(.jru-grid__actions) :is(input, textarea) {
    scroll-margin-inline-end: 160px;
}

table.jru-grid tbody tr:hover td.jru-grid__actions {
    background: var(--jru-hover);
}

table.jru-grid tbody tr.is-selected td.jru-grid__actions {
    background: linear-gradient(var(--jru-primary-surface), var(--jru-primary-surface)), var(--jru-surface);
}

table.jru-grid tbody tr.is-attention td.jru-grid__actions {
    background: linear-gradient(var(--jru-row-attention), var(--jru-row-attention)), var(--jru-surface);
}

.jru-grid__actions > * + * {
    margin-left: 8px;
}

.jru-grid__actions .mud-button-root {
    min-height: 26px;
    height: 26px;
    padding: 0 10px;
    font-size: 12px;
    text-transform: none;
    letter-spacing: 0;
}

.jru-grid__actions .mud-button-root.mud-icon-button {
    width: 26px;
    padding: 0;
}

.jru-grid__actions .mud-button-root .mud-button-icon-start {
    margin-right: 5px;
    margin-left: 0;
}

.jru-grid__actions .jru-iconbutton,
.jru-grid__actions .mud-link {
    display: inline-grid;
    place-items: center;
    width: 26px;
    height: 26px;
    vertical-align: middle;
}

.jru-grid__actions .mud-link {
    border-radius: var(--jru-radius-input);
}

.jru-grid__actions .mud-link:hover {
    background: var(--jru-hover);
}

.jru-grid__actions .mud-icon-root {
    font-size: 17px;
}

/*
 * The four states. An empty grid rendered as a blank rectangle is a screen the operator
 * thinks is broken.
 */
.jru-gridstate {
    display: grid;
    place-items: center;
    gap: 8px;
    padding: 44px 20px;
    text-align: center;
    color: var(--jru-text-secondary);
}

.jru-gridstate__icon {
    font-size: 26px;
    color: var(--jru-text-muted);
}

.jru-gridstate__title {
    font-size: 13.5px;
    font-weight: 600;
    color: var(--jru-text);
}

.jru-gridstate__detail {
    font-size: 12.5px;
    max-width: 46ch;
}

.jru-gridstate--error .jru-gridstate__icon {
    color: var(--jru-danger);
}

.jru-gridstate--denied .jru-gridstate__icon {
    color: var(--jru-attention);
}

/* ------------------------------------------------------------------ badge */
.jru-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 9px;
    border-radius: 999px;
    border: 1px solid transparent;
    font-size: 11px;
    font-weight: 500;
    white-space: nowrap;
}

.jru-badge--pass {
    background: var(--jru-badge-pass-bg);
    color: var(--jru-badge-pass-fg);
    border-color: var(--jru-badge-pass-bd);
}

.jru-badge--partial {
    background: var(--jru-badge-partial-bg);
    color: var(--jru-badge-partial-fg);
    border-color: var(--jru-badge-partial-bd);
}

.jru-badge--pended {
    background: var(--jru-badge-pended-bg);
    color: var(--jru-badge-pended-fg);
    border-color: var(--jru-badge-pended-bd);
}

.jru-badge--warning {
    background: var(--jru-badge-warning-bg);
    color: var(--jru-badge-warning-fg);
    border-color: var(--jru-badge-warning-bd);
}

.jru-badge--fail {
    background: var(--jru-badge-fail-bg);
    color: var(--jru-badge-fail-fg);
    border-color: var(--jru-badge-fail-bd);
}

.jru-badge--info {
    background: var(--jru-badge-info-bg);
    color: var(--jru-badge-info-fg);
    border-color: var(--jru-badge-info-bd);
}

.jru-badge--draft {
    background: var(--jru-badge-draft-bg);
    color: var(--jru-badge-draft-fg);
    border-color: var(--jru-badge-draft-bd);
}

/* ------------------------------------------------------------------ money */
.jru-money {
    display: inline-block;
    font-family: var(--jru-mono);
    font-variant-numeric: tabular-nums;
    text-align: right;
    white-space: nowrap;
}

.jru-money--nil {
    color: var(--jru-text-muted);
}

.jru-money--benefit {
    color: var(--jru-success);
}

.jru-money--derived {
    color: var(--jru-text-secondary);
}

.jru-money--reversing {
    color: var(--jru-danger);
}

/* ------------------------------------------------------------------- code */
/* Secondary text inside a cell: a dash that means "not applicable", a name beside a date. It reads
   quieter than the value it sits with and never carries meaning on its own. */
.jru-muted {
    color: var(--jru-text-muted);
}

/* The counterpart to .jru-muted: something the operator still has to do. It is louder than muted and
   quieter than a refusal, and it always carries a word - "None yet", never a colour on its own. */
.jru-unfinished {
    color: var(--jru-attention);
}

/*
 * An identifier is never broken across lines, so this stays nowrap. What it must not do is paint
 * outside the thing holding it: given a long value - the column list a load file must carry is
 * hundreds of characters - the line ran straight through the card's edge and across the page.
 *
 * Inline, in a grid cell or a sentence, overflow and max-width do nothing and this is the same rule
 * it always was. Blockified, as a field's child or any other flex or grid item, the box is now
 * bounded by its container and scrolls its own content instead of escaping it.
 *
 * flex-shrink: 0 is what makes that safe, and it is not an optimisation. A flex item carrying an
 * overflow other than visible loses its automatic minimum size, so in a row beside prose it is the
 * one thing free to shrink to nothing: a two-character reference beside a 110-character message
 * came out 11.6px wide holding 16px of text, which renders "#3" as "#" and a sliver. An identifier
 * clipped to its first character is worse than one that is merely long, because nothing on the
 * screen says it was cut. The prose beside it wraps, which is what prose is for.
 *
 * max-width: 100% still bounds it, so a long value is capped at its container and scrolls in place
 * rather than pushing its card open. A LIST of identifiers is not that case and does not belong here:
 * see .jru-columns below, which wraps.
 */
.jru-code {
    font-family: var(--jru-mono);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    max-width: 100%;
    overflow-x: auto;
    scrollbar-width: thin;
    flex-shrink: 0;
}

.jru-code__described {
    color: var(--jru-text-secondary);
    font-family: var(--jru-sans);
}

/*
 * A LIST OF FIELD NAMES, WHICH WRAPS. .jru-code does not, on purpose - an identifier cut in half
 * reads as a different identifier - but a list of them is prose made of identifiers, and the whole
 * point of showing it is that the reader can see all of it.
 *
 * The load page is the one reader. Its longest is a member file's twenty-eight columns: 311
 * characters, 2,509px on one line inside a 1,124px card, so twenty of the twenty-eight sat off the
 * right-hand edge behind a thin scrollbar nobody looks for. It wraps here, and the separator is a
 * comma and a space so the block can be selected and pasted into a spreadsheet's heading row.
 */
.jru-columns {
    font-family: var(--jru-mono);
    font-size: 12px;
    line-height: 1.7;
    overflow-wrap: anywhere;
}

/* The ones a file may leave out. Quieter than the ones it may not, and the caption above says which
   is which - the tint is the second signal, never the only one. */
.jru-columns--optional {
    color: var(--jru-text-muted);
}

/*
 * One detail line under a Claims report row: the item and its tariff code, the description, and any
 * rejection code with its words. Laid out rather than run together, because Razor drops the
 * whitespace between an expression and the element after it and "ConsultationSHP" reads as one word
 * that is neither.
 */
/* One claim's block of detail lines, with the gap before the next claim. It was an inline margin. */
.jru-reportclaim {
    margin-bottom: 6px;
}

.jru-reportline {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px;
}

/*
 * A short list's row with a line under its word: an office and its e-mail address. The list is as
 * wide as the box, so a long address is cut to one line and whole in its title.
 */
.jru-shortlist__option {
    display: flex;
    flex-direction: column;
    min-width: 0;
    line-height: 1.3;
}

.jru-shortlist__detail {
    font-size: 11.5px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ------------------------------------------------------------------ chips */
.jru-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 7px;
}

.jru-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 26px;
    padding: 0 11px;
    border-radius: 999px;
    border: 1px solid var(--jru-border);
    background: var(--jru-surface);
    color: var(--jru-text-secondary);
    font-size: 11.5px;
    cursor: pointer;
    transition: background var(--jru-motion) var(--jru-ease),
                border-color var(--jru-motion) var(--jru-ease);
}

.jru-chip:hover {
    background: var(--jru-hover);
}

.jru-chip.is-on {
    background: var(--jru-primary-surface);
    border-color: var(--jru-primary-bright);
    color: var(--jru-primary-bright);
    font-weight: 500;
}

/*
 * A LABEL THAT IS NOT A CONTROL. Same shape as `.jru-chip` and deliberately none of its
 * affordances: no pointer, no hover, no transition, and it is never put on a `button`.
 *
 * The roles screen is why this exists. It drew each capability as `.jru-chip` on a `span`, so the
 * capability names looked exactly like the chips that filter the audit trail and the load page, and
 * did nothing when pressed. A thing that looks pressable and is not is a worse defect than a plain
 * word, because the reader has to try it to find out.
 */
.jru-tag {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 24px;
    padding: 0 10px;
    border-radius: 999px;
    border: 1px solid var(--jru-border);
    background: var(--jru-surface-alt);
    color: var(--jru-text-secondary);
    font-size: 11.5px;
    white-space: nowrap;
}

.jru-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

/*
 * A PERMISSIONS MATRIX: what can be opened down the side, the roles across the top, a tick where the
 * role has it. It is not a grid - there is nothing in it to sort, search or select - so it carries
 * none of the grid's affordances: no pointer on a row, no selection bar, no sortable heading. Read
 * across for everything one role reaches, down for every role that reaches one area.
 */
.jru-matrix-page {
    max-width: 1180px;
}

.jru-matrix__scroll {
    overflow-x: auto;
}

table.jru-matrix {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: 12.5px;
    line-height: 1.4;
}

.jru-matrix__rolecol {
    width: 140px;
}

table.jru-matrix thead th {
    background: var(--jru-surface-alt);
    border-bottom: 1px solid var(--jru-border);
    padding: 10px 12px;
    vertical-align: bottom;
    text-align: left;
    font-weight: 600;
}

table.jru-matrix thead th.jru-matrix__corner {
    color: var(--jru-text-secondary);
    font-size: 10.5px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

table.jru-matrix thead th.jru-matrix__role {
    text-align: center;
}

.jru-matrix__role-name {
    display: block;
    color: var(--jru-text);
    font-size: 13px;
}

/* Holds its height while the count is read, so the table does not move when it arrives. */
.jru-matrix__role-count {
    display: block;
    min-height: 17px;
    margin-top: 2px;
    color: var(--jru-text-muted);
    font-size: 11.5px;
    font-weight: 400;
}

.jru-matrix__role-count a {
    color: var(--jru-primary-bright);
    text-decoration: none;
}

.jru-matrix__role-count a:hover {
    text-decoration: underline;
}

table.jru-matrix tbody tr.jru-matrix__section > th {
    padding: 14px 12px 6px;
    border-bottom: 1px solid var(--jru-border-soft);
    color: var(--jru-text-secondary);
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-align: left;
    text-transform: uppercase;
}

table.jru-matrix tbody th.jru-matrix__area,
table.jru-matrix tbody td.jru-matrix__cell {
    padding: 10px 12px;
    border-bottom: 1px solid var(--jru-border-soft);
    vertical-align: middle;
}

/* The card's footer draws the rule under the last row; the row drawing one too would double it. */
table.jru-matrix tbody tr:last-child > * {
    border-bottom: none;
}

table.jru-matrix tbody th.jru-matrix__area {
    text-align: left;
    font-weight: 400;
}

.jru-matrix__area-name {
    display: block;
    color: var(--jru-text);
    font-size: 13px;
    font-weight: 600;
}

.jru-matrix__area-detail {
    display: block;
    margin-top: 2px;
    color: var(--jru-text-muted);
    font-size: 12px;
}

table.jru-matrix tbody td.jru-matrix__cell {
    text-align: center;
}

/* A tick and its lock sit side by side; MudCheckBox is a block of its own and pushed the lock under it. */
.jru-matrix__tick {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 2px;
}

.jru-matrix__tick .mud-checkbox {
    margin: 0;
}

/* Following the row under the pointer is what makes a wide matrix readable across. */
table.jru-matrix tbody tr:not(.jru-matrix__section):hover > * {
    background: var(--jru-hover);
}

.jru-matrix__yes {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 999px;
    background: var(--jru-primary-surface);
    color: var(--jru-primary-bright);
}

.jru-matrix__yes .mud-icon-root {
    font-size: 16px;
}

.jru-matrix__no {
    color: var(--jru-text-muted);
}

.jru-chip__count {
    font-family: var(--jru-mono);
    font-size: 10.5px;
    color: var(--jru-text-muted);
}

/* -------------------------------------------------------------- code list */
/*
 * A PARAMETER'S CODE LIST, WHICH IS A VALUE AND NOT A SET OF OPTIONS.
 *
 * Deliberately not `.jru-chips`: a chip is one of several choices and is drawn whether or not it is
 * taken, and every one of these is taken by definition. What it needs instead is a removal on each
 * entry, which is why the item is a shape of its own rather than a variant.
 */
.jru-chiplist {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 0 0 8px;
    padding: 0;
    list-style: none;
}

.jru-chiplist__item {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 0 2px 0 9px;
    border-radius: 999px;
    border: 1px solid var(--jru-border);
    background: var(--jru-surface);
}

.jru-chiplist__add {
    display: flex;
    align-items: flex-start;
    gap: 8px;
}

/* ------------------------------------------------------------ settings list */

/*
 * A SETTINGS PAGE'S CARD: one line per setting, its name and what it does on the left, its value on
 * the right, and the whole line the button that opens it (SettingRow). Parameters and System are
 * drawn this way; a line is a button so the keyboard reaches every setting with Tab and opens it with
 * Enter.
 *
 * ONE CARD: the search in its toolbar, then the tab strip, then the open section's lines flush to the
 * card's edges, so a line's hover reaches the border as it does in every list.
 */
.jru-settings-card > .jru-tabpanel {
    padding: 0;
}

/* What the search found, and the control that clears it, above the matches. */
.jru-settings__found {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 6px 15px;
    border-bottom: 1px solid var(--jru-border-soft);
    color: var(--jru-text-secondary);
    font-size: 12.5px;
}

/* A section's name over the matches the search found in it. */
.jru-settings__group {
    margin: 0;
    padding: 9px 15px 6px;
    background: var(--jru-surface-alt);
    border-bottom: 1px solid var(--jru-border-soft);
    color: var(--jru-text-secondary);
    font-size: 12px;
    font-weight: 600;
}

.jru-settings__group:not(.jru-settings__found + .jru-settings__group) {
    border-top: 1px solid var(--jru-border-soft);
}

/* A module's own settings under its ticks: its name and its check, then its lines. */
.jru-settings__subhead {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
    padding: 10px 15px;
    background: var(--jru-surface-alt);
    border-top: 1px solid var(--jru-border-soft);
    border-bottom: 1px solid var(--jru-border-soft);
}

.jru-settings__subhead-title {
    margin: 0;
    font-size: 13px;
    font-weight: 600;
}

.jru-settings__banner {
    padding: 10px 15px 0;
}

/*
 * WHAT THE SEARCH MATCHED, MARKED IN THE LINE. A lime fill with the charcoal label, the one pairing the
 * brand allows on both themes: lime washed over charcoal turns khaki.
 */
mark.jru-match {
    background: var(--jru-primary);
    color: var(--jru-on-primary);
    border-radius: 2px;
    padding: 0 1px;
}
.jru-settings {
    list-style: none;
    margin: 0;
    padding: 0;
}

.jru-settings > li + li {
    border-top: 1px solid var(--jru-border-soft);
}

.jru-settings__item {
    display: flex;
    align-items: center;
    gap: 16px;
    width: 100%;
    padding: 10px 15px;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.jru-settings__item:hover {
    background: var(--jru-hover);
}

.jru-settings__item:focus-visible {
    outline-offset: -2px;
}

.jru-settings__item--missing {
    cursor: default;
}

.jru-settings__item--missing:hover {
    background: none;
}

.jru-settings__item--retired .jru-settings__name {
    color: var(--jru-text-muted);
}

.jru-settings__text {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.jru-settings__name {
    font-weight: 600;
}

.jru-settings__hint {
    color: var(--jru-text-muted);
    font-size: 0.8125rem;
}

.jru-settings__value {
    display: flex;
    flex: 0 1 40%;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 6px;
    min-width: 0;
    overflow-wrap: anywhere;
    text-align: right;
}

.jru-settings__go {
    flex: none;
    color: var(--jru-text-muted);
}

/* A list in a card, under its settings: its caption and Add, then its items. */
.jru-settings__list {
    display: grid;
    gap: 4px;
    padding: 10px 15px 15px;
    border-top: 1px solid var(--jru-border-soft);
}

.jru-settings__list-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.jru-settings--nested {
    margin-top: 6px;
    border: 1px solid var(--jru-border-soft);
    border-radius: var(--jru-radius-control);
}

.jru-settings--nested .jru-settings__item {
    padding: 8px 12px;
}

@media (max-width: 600px) {
    .jru-settings__item {
        flex-wrap: wrap;
    }

    .jru-settings__value {
        flex-basis: 100%;
        justify-content: flex-start;
        text-align: left;
    }

    .jru-settings__go {
        display: none;
    }
}

/* A setting's box with its unit beside it: R before an amount, days or % after a number. */
.jru-setting-input {
    display: flex;
    align-items: center;
    gap: 8px;
}

.jru-setting-input > .mud-input-control {
    flex: 1 1 auto;
}

.jru-setting-input > .mud-input-control.jru-setting-input__figure {
    flex: 0 1 12rem;
}

.jru-setting-input__unit {
    flex: none;
    color: var(--jru-text-muted);
    white-space: nowrap;
}

/* ------------------------------------------------------------ metric card */
/*
 * The cards are their own surfaces, so the row is a grid and never sits inside a card:
 * a bordered box inside a bordered box reads as a nested panel nobody meant.
 */
.jru-metrics {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 12px;
    min-width: 0;
}

/* A preview row of figures above the grid it describes. Three screens wrote this margin inline. */
.jru-metrics--above {
    margin-bottom: 16px;
}

.jru-metric {
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: 13px 15px;
    background: var(--jru-surface);
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-card);
    box-shadow: var(--jru-elev-1);
}

.jru-metric__label {
    color: var(--jru-text-secondary);
    font-size: 10.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.07em;
}

.jru-metric__value {
    font-family: var(--jru-mono);
    font-variant-numeric: tabular-nums;
    font-size: 23px;
    font-weight: 600;
    letter-spacing: -0.02em;
}

.jru-metric__meta {
    color: var(--jru-text-muted);
    font-size: 11px;
}

/*
 * A metric that is also the way to the rows behind it. The whole card is the target rather than a
 * control inside it: the figure is what the reader is aiming at, and a 34px link beside a 23px
 * number is a smaller thing to hit than the thing that made them want to.
 */
.jru-metric--link {
    color: inherit;
    text-decoration: none;
    transition: border-color 120ms ease, box-shadow 120ms ease, transform 120ms ease;
}

.jru-metric--link:hover,
.jru-metric--link:focus-visible {
    border-color: var(--jru-primary-bright);
    box-shadow: var(--jru-elev-2);
}

/* Motion says it is a control without moving anything the reader is measuring against. */
.jru-metric--link:hover {
    transform: translateY(-1px);
}

@media (prefers-reduced-motion: reduce) {
    .jru-metric--link {
        transition: none;
    }

    .jru-metric--link:hover {
        transform: none;
    }
}

/* ---------------------------------------------------------------- overview */
.jru-overview__letters {
    margin-top: 16px;
}

.jru-overview__letter {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 0;
    border-bottom: 1px solid var(--jru-border-soft);
}

.jru-overview__letter:last-child {
    border-bottom: none;
}

/* Wide enough for the longest of the three names, so the badges line up in a column rather than
   stepping in and out with the word beside them. */
.jru-overview__letter-name {
    flex: 0 0 96px;
    font-weight: 600;
}

.jru-overview__more {
    color: var(--jru-primary-bright);
    font-size: 12px;
    font-weight: 600;
    text-decoration: none;
}

.jru-overview__more:hover {
    text-decoration: underline;
}

/*
 * A section of figures under the letters, headed outside any card: the cards are their own surfaces,
 * so the heading is a rule and a title over the row rather than a box around it.
 */
.jru-overview__section {
    margin-top: 24px;
}

.jru-overview__section-head {
    display: flex;
    align-items: baseline;
    gap: 12px;
    padding-bottom: 8px;
    margin-bottom: 12px;
    border-bottom: 1px solid var(--jru-border);
}

.jru-overview__section-titles {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 10px;
    min-width: 0;
}

.jru-overview__section-title {
    margin: 0;
    font-size: 13.5px;
    font-weight: 600;
}

.jru-overview__section-meta {
    color: var(--jru-text-muted);
    font-size: 12px;
}

.jru-overview__section-head .jru-overview__more {
    margin-left: auto;
    white-space: nowrap;
}

/*
 * The attorneys' eight cards: four to a row, so they read as two rows of four - before court, then in
 * court and after - rather than as however many the width happens to fit.
 */
.jru-metrics--court {
    grid-template-columns: repeat(4, minmax(0, 1fr));
}

@media (max-width: 1100px) {
    .jru-metrics--court {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 560px) {
    .jru-metrics--court {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ----------------------------------------------------------- court overview */
/*
 * The attorneys' overview (feedback 200): their cards, then how far every matter still owed has
 * got, then the diary beside the money. Every figure is a link to the rows behind it.
 */
.jru-court__stages {
    margin-top: 16px;
}

/* One step per stage, in the order a matter moves through them. Wraps rather than scrolling at 1366. */
/* Twelve stages fit one row in the content column of a 1440px screen; narrower, they wrap evenly. */
.jru-court__strip {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
    gap: 6px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.jru-court__step {
    display: flex;
    flex-direction: column;
    gap: 2px;
    height: 100%;
    padding: 8px 10px;
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-card);
    color: inherit;
    text-decoration: none;
    transition: border-color 120ms ease;
}

.jru-court__step:hover,
.jru-court__step:focus-visible {
    border-color: var(--jru-primary-bright);
}

/* A stage nobody is at stays in its place in the order, quieter, so the strip keeps its shape. */
.jru-court__step--empty {
    color: var(--jru-text-muted);
}

.jru-court__step-count {
    font-family: var(--jru-mono);
    font-variant-numeric: tabular-nums;
    font-size: 17px;
    font-weight: 600;
}

.jru-court__step-label {
    color: var(--jru-text-secondary);
    font-size: 11px;
    line-height: 1.25;
}

.jru-court__columns {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: 16px;
    margin-top: 16px;
    min-width: 0;
}

.jru-court__window,
.jru-court__more {
    font-size: 11.5px;
}

.jru-court__window {
    margin-bottom: 6px;
}

.jru-court__more {
    margin-top: 6px;
}

.jru-court__nothing {
    padding: 8px 0;
    color: var(--jru-text-muted);
}

.jru-court__diary {
    margin: 0;
    padding: 0;
    list-style: none;
}

.jru-court__diary-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 0;
    border-bottom: 1px solid var(--jru-border-soft);
    color: inherit;
    text-decoration: none;
}

.jru-court__diary li:last-child .jru-court__diary-row {
    border-bottom: none;
}

.jru-court__diary-row:hover .jru-court__diary-who {
    text-decoration: underline;
}

/* Wide enough for "Wed 14/10/2026", so the badges line up in a column. */
.jru-court__diary-date {
    flex: 0 0 112px;
    font-family: var(--jru-mono);
    font-size: 12px;
}

.jru-court__diary-who {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.jru-court__money {
    display: flex;
    flex-direction: column;
}

.jru-court__money-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 0 4px;
    color: inherit;
    text-decoration: none;
}

.jru-court__money-row:hover .jru-court__money-label {
    text-decoration: underline;
}

.jru-court__money-label {
    font-weight: 600;
}

.jru-court__split {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 16px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--jru-border-soft);
    font-size: 12px;
}

.jru-court__split:last-child {
    border-bottom: none;
}

/* ---------------------------------------------------------- totals strip */
/*
 * The figures a screen is judged on, pinned to the bottom of the pane while the operator works the
 * list above them. The main pane owns the viewport height and clips its overflow, which is what
 * makes position: sticky here land against the pane rather than against the document.
 *
 * The payment run is the reason it exists: the operator ticks lines at the top of a long grid and
 * the difference they are driving to zero must never scroll out of sight.
 */
.jru-totals {
    position: sticky;
    bottom: 0;
    z-index: 2;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 22px;
    padding: 10px 15px;
    background: var(--jru-surface-alt);
    border-top: 1px solid var(--jru-border);
    box-shadow: var(--jru-elev-sticky);
}

.jru-totals__item {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.jru-totals__label {
    color: var(--jru-text-secondary);
    font-size: 10.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.07em;
}

.jru-totals__value {
    font-size: 14px;
    font-weight: 600;
}

/* Colour plus a word, never colour alone: the label beside it says which figure this is, and the
   badge under it says balanced or short. Verify in greyscale. */
.jru-totals__value--balanced {
    color: var(--jru-success);
}

.jru-totals__value--short {
    color: var(--jru-danger);
}

/*
 * A claim that does not balance (R-19): the amount it is out by and which way, in the badge's amber
 * ink, with the warning icon so it reads in greyscale. It stands in for the balance figure in the
 * totals strip and sits in the meta line of the claim list, so it inherits its size from either.
 */
.jru-balance {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--jru-badge-warning-fg);
    font-weight: 600;
    white-space: nowrap;
}

.jru-balance .jru-balance__icon {
    font-size: 1.15em;
    width: 1.15em;
    height: 1.15em;
}

.jru-totals__actions {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-left: auto;
}

/*
 * THE FIGURE THE REPORT EXISTS TO PRODUCE, at the metric size the token set already carries.
 *
 * A strip of seven figures at one weight says nothing about which of them is the answer. Exactly one
 * item per strip takes this, and it is the figure the screen is for: the balance outstanding on the
 * Overview report, what is owed on the Claims report. Everything beside it is what it was worked out
 * from and stays at the strip's own size.
 */
.jru-totals__item--lead .jru-totals__value {
    font-size: 23px;
    line-height: 1.15;
    letter-spacing: -0.02em;
}

/* The rule between the lead figure and its workings. Stretches to the strip's own height. */
.jru-totals__rule {
    align-self: stretch;
    width: 1px;
    background: var(--jru-border);
}

/* --------------------------------------------------------- report toolbar */
/*
 * ONE ROW, IN READING ORDER: which report, over what dates, narrowed by what, then Run.
 *
 * It replaced a full-height panel of boxes. The panel held the top third of the screen for a whole
 * session and pushed the first row of the answer below the fold at 1366x768, and its shape fought
 * the job: a lookup drew a cramped entry plus a permanently empty box reading "Press F4 to look up",
 * three times, each under a hint reading "Leave blank for every X". Eleven lines of instructional
 * text on the Claims panel alone, and six boxes for three filters.
 *
 * THE CONTROLS ARE TYPED, AND ONE CONTROL IS ONE PARAMETER. A date is a calendar with named periods,
 * a lookup is the lookup dialog F4 already opens, and a parameter that is not set says so in its own
 * words rather than leaving an empty box and a sentence explaining what empty means.
 *
 * WHAT DESCRIBES THE DOCUMENT IS NOT ON THIS ROW. Sort order, the column set and Print description
 * do not change which rows come back, so they sit with Print and Export on the result line. They
 * were under a legend reading "How it looks", between the filters and the Run button.
 */
.jru-reporttoolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 11px 15px;
    border-bottom: 1px solid var(--jru-border-soft);
}

.jru-reporttoolbar__rule {
    align-self: stretch;
    width: 1px;
    margin: 0 3px;
    background: var(--jru-border);
}

/* Run sits over the scrim an open control lays across the page (feedback 186). Under it, the click
   that was meant to run the report only closed the date panel, and nothing said the run never
   happened. Run closes the panel itself. */
.jru-reporttoolbar__end {
    position: relative;
    z-index: 1200;
    display: flex;
    align-items: center;
    gap: 8px;
    margin-left: auto;
}

/*
 * THE SEGMENTED CONTROL: the one choice that restructures every other control on the row.
 *
 * It is not `.jru-chip`. A chip group is a set of independent choices; this is one choice among a
 * closed set, and drawing it as chips put the report type in the same weight as the date basis
 * beside it. Round-and-lime is reserved for a view, so the selected segment fills without the pill
 * shape: it is a mode, not a view of a list.
 */
.jru-reportseg {
    display: inline-flex;
    padding: 2px;
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-control);
    background: var(--jru-surface-alt);
}

.jru-reportseg__item {
    appearance: none;
    border: 0;
    border-radius: 5px;
    height: 28px;
    padding: 0 11px;
    background: transparent;
    color: var(--jru-text-secondary);
    font: inherit;
    font-size: 12px;
    white-space: nowrap;
    cursor: pointer;
    transition: background var(--jru-motion) var(--jru-ease);
}

.jru-reportseg__item:hover {
    background: var(--jru-hover);
}

.jru-reportseg__item[aria-pressed="true"] {
    background: var(--jru-primary);
    color: var(--jru-on-primary);
    font-weight: 600;
}

/*
 * ONE PARAMETER, AS A CONTROL THAT OPENS SOMETHING.
 *
 * The key is quiet and the value is in ink, which is the split the token and the grid already use:
 * two runs of one report differ in their values, not their parameter names.
 */
.jru-reportctl {
    position: relative;
}

.jru-reportctl__btn {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    height: var(--jru-control-h);
    padding: 0 11px;
    max-width: 270px;
    border: 1px solid var(--jru-border-strong);
    border-radius: var(--jru-radius-input);
    background: var(--jru-surface);
    color: var(--jru-text);
    font: inherit;
    font-size: 12.5px;
    cursor: pointer;
    transition: background var(--jru-motion) var(--jru-ease);
}

.jru-reportctl__btn:hover {
    background: var(--jru-hover);
}

.jru-reportctl__btn[aria-expanded="true"] {
    border-color: var(--jru-primary-bright);
    box-shadow: 0 0 0 3px var(--jru-primary-ring);
}

.jru-reportctl__key {
    color: var(--jru-text-muted);
    white-space: nowrap;
}

.jru-reportctl__value {
    font-family: var(--jru-mono);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
 * A PARAMETER LEFT OPEN SAYS SO IN WORDS - "Every scheme" - and reads quieter than one that was set.
 * It is written out rather than left blank: an empty control needs a sentence under it explaining
 * what empty means, and that sentence is what this replaced.
 */
.jru-reportctl__value--any {
    font-family: var(--jru-sans);
    font-weight: 400;
    color: var(--jru-text-muted);
}

/* A required parameter with nothing in it. Amber and a word, and Run is held until it is set: the
   refusal belongs before the click, not after it. */
.jru-reportctl__btn.is-needed {
    border-color: var(--jru-badge-warning-bd);
    background: var(--jru-badge-warning-bg);
}

.jru-reportctl__btn.is-needed .jru-reportctl__value {
    font-family: var(--jru-sans);
    font-weight: 600;
    color: var(--jru-badge-warning-fg);
}

/*
 * Clearing is its own target beside opening, because changing a parameter and taking it off are
 * different intentions - the same reason the filter token has a body and a cross.
 *
 * THE CROSS IS THE BUTTON'S SIBLING AND IS PLACED OVER ITS RIGHT END, where it sat when it was drawn
 * inside it. Inside, assistive technology flattened it into the button's name and it could not be
 * reached on its own. The button keeps the room it took: its own padding, the cross and the gap.
 */
.jru-reportctl__btn.has-clear {
    padding-right: 35px;
}

.jru-reportctl__clear {
    position: absolute;
    top: 50%;
    right: 12px;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 17px;
    height: 17px;
    padding: 0;
    flex: none;
    border: 0;
    border-radius: 50%;
    background: var(--jru-border);
    color: var(--jru-text-secondary);
    font: inherit;
    font-size: 11px;
    line-height: 1;
    cursor: pointer;
}

.jru-reportctl__clear:hover {
    background: var(--jru-danger);
    color: var(--jru-surface);
}

.jru-reportctl__caret {
    flex: none;
    color: var(--jru-text-muted);
}

/* The scrim that closes an open control. Transparent: a dimmed page says a modal opened, and these
   are menus. Same decision as the filter bar's. */
.jru-reportctl__scrim {
    position: fixed;
    inset: 0;
    z-index: 1199;
}

/*
 * THE DATE CONTROL'S PANEL: named periods first, the two boxes beside them, and the basis above
 * them because "which dates" and "between when" are one question the screen used to ask in two
 * places - a chip row up here and two boxes further down.
 *
 * Typing two dates to ask for last month is the slowest thing on any claims screen.
 */
.jru-reportdates {
    display: grid;
    grid-template-columns: 172px minmax(0, 1fr);
    min-width: 440px;
}

.jru-reportdates__periods {
    display: grid;
    align-content: start;
    gap: 1px;
    padding: 8px;
    border-right: 1px solid var(--jru-border-soft);
}

.jru-reportdates__period {
    appearance: none;
    text-align: left;
    padding: 7px 10px;
    border: 0;
    border-radius: var(--jru-radius-input);
    background: transparent;
    color: var(--jru-text);
    font: inherit;
    font-size: 12.5px;
    cursor: pointer;
}

.jru-reportdates__period:hover {
    background: var(--jru-hover);
}

.jru-reportdates__period[aria-pressed="true"] {
    background: var(--jru-primary-surface);
    color: var(--jru-primary-bright);
    font-weight: 600;
}

.jru-reportdates__custom {
    display: grid;
    align-content: start;
    gap: 11px;
    padding: 12px 13px;
}

/* The popover's own section heading. Smaller than a card's, because a menu is glanced at. */
.jru-reportpop__label {
    margin-bottom: 6px;
    color: var(--jru-text-secondary);
    font-size: 10.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.07em;
}

/* A menu of parameters that are not on the row: the long tail, per report type. */
.jru-reportmenu {
    width: 240px;
    padding: 6px;
}

.jru-reportmenu__item {
    display: flex;
    width: 100%;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 7px 9px;
    border: 0;
    border-radius: var(--jru-radius-input);
    background: transparent;
    color: var(--jru-text);
    font: inherit;
    font-size: 12.5px;
    text-align: left;
    cursor: pointer;
}

.jru-reportmenu__item:hover {
    background: var(--jru-hover);
}

.jru-reportmenu__item:disabled {
    color: var(--jru-text-disabled);
    cursor: default;
}

.jru-reportmenu__on {
    font-size: 11px;
    color: var(--jru-text-muted);
}

.jru-reportmenu__note {
    padding: 6px 10px;
    color: var(--jru-text-muted);
    font-size: 11.5px;
}

/* What the document looks like, kept with Print and Export rather than with the filters. */
.jru-reportlayout {
    display: grid;
    gap: 12px;
    width: 272px;
    padding: 12px 13px;
}

/*
 * WHAT A RUN LEFT OUT, in one line.
 *
 * R-175, R-263 and R-177 all require the run to say so: a total that silently omits lines is what a
 * reconciliation trips over. What they do not require is six sentences of reasoning restated to the
 * same operator every day, which is what this replaced. The counts stay and the wording moves behind
 * the disclosure, for the day somebody queries a figure.
 *
 * Rows the operator can already see are not counted here. A mixed percentage says so in its own cell
 * and a claim with no plan is flagged on its own row.
 */
.jru-runnote {
    padding: 0 15px 12px;
    margin-top: -4px;
    color: var(--jru-text-muted);
    font-size: 11.5px;
}

/*
 * Each clause of the line opens with its count, so the gap between clauses is declared here.
 *
 * It cannot be a space in the markup: Razor drops the whitespace between one `@if` block's last
 * element and the next one's first, so two clauses rendered by two conditions ran together as
 * "...has a document on file.78 document types are not shown here." A margin is not subject to
 * that, and the first clause takes none so the line still starts flush.
 */
.jru-runnote__count {
    margin-left: 6px;
    font-family: var(--jru-mono);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: var(--jru-text-secondary);
}

.jru-runnote__count:first-child {
    margin-left: 0;
}

/* The disclosure sits inside the sentence, so it drops Mud's button metrics down to the note's own
   size. It stays a button rather than a link: it toggles something on this page, it does not go
   anywhere, and a link that does not navigate is the affordance lying. */
.jru-runnote .jru-runnote__why.mud-button-root {
    min-width: 0;
    height: auto;
    margin-left: 6px;
    padding: 0 2px;
    vertical-align: baseline;
    color: var(--jru-primary-bright);
    font-size: 11.5px;
    font-weight: 500;
    text-transform: none;
    text-decoration: underline;
    text-underline-offset: 2px;
}

.jru-runnote__full {
    margin-top: 8px;
    padding: 10px 13px;
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-control);
    background: var(--jru-surface-alt);
}

.jru-runnote__full > div {
    color: var(--jru-text-secondary);
    font-size: 12.5px;
    max-width: 96ch;
}

.jru-runnote__full > div + div {
    margin-top: 4px;
}

/* WHAT A LETTER PASS SAID, two lines at most, with the whole of it on hover. A pass that refused a
   letter per member once wrote the same sentence fifty times, and the row stood thirteen lines tall;
   the clamp is what keeps a long note from taking the grid over, whatever wrote it. */
.jru-letterrun-said {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    overflow-wrap: anywhere;
}

/*
 * WHAT A REPORT WILL PRODUCE, before it has been asked for.
 *
 * A screen that opens as a form over white space says nothing about what a row is going to be. This
 * says it once, in the words the report's own sections use, and names the column groups with their
 * counts so the operator knows the shape before they ask for it.
 */
.jru-reportblank {
    display: grid;
    gap: 14px;
    justify-items: start;
    padding: 26px 15px 30px;
}

.jru-reportblank__lede {
    margin: 0;
    max-width: 62ch;
    color: var(--jru-text-secondary);
    font-size: 13.5px;
}

.jru-reportblank__bands {
    display: flex;
    flex-wrap: wrap;
    gap: 7px;
}

.jru-reportblank__band {
    display: inline-flex;
    align-items: baseline;
    gap: 7px;
    padding: 5px 11px;
    border: 1px dashed var(--jru-border-strong);
    border-radius: var(--jru-radius-control);
    color: var(--jru-text-secondary);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.07em;
}

.jru-reportblank__count {
    font-family: var(--jru-mono);
    font-weight: 400;
    letter-spacing: 0;
    color: var(--jru-text-muted);
}

/* The result line: what the run produced, and the control that exports it. The export sits on the
   thing it exports rather than in the page header, where it appeared and disappeared with the rows
   and moved Run as it arrived. */
.jru-runresult {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px 14px;
    padding: 12px 15px;
}

.jru-runresult__title {
    font-size: 15px;
    font-weight: 600;
}

.jru-runresult__count {
    font-family: var(--jru-mono);
    font-variant-numeric: tabular-nums;
    font-size: 12px;
    color: var(--jru-text-secondary);
}

.jru-runresult__end {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-left: auto;
}

/* The popover surface itself, for a control on the report toolbar. Paper="false" on the MudPopover
   is what leaves the surface to this rather than to MudBlazor's own card, as the filter bar does. */
.jru-reportpop {
    margin-top: 6px;
    background: var(--jru-surface);
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-control);
    box-shadow: var(--jru-elev-2);
    overflow: hidden;
}

/* ------------------------------------------------------------ empty state */
.jru-empty {
    display: grid;
    place-items: center;
    gap: 9px;
    padding: 52px 24px;
    text-align: center;
}

.jru-empty__icon {
    font-size: 30px;
    color: var(--jru-text-muted);
}

.jru-empty__title {
    font-size: 15px;
    font-weight: 600;
}

.jru-empty__detail {
    color: var(--jru-text-secondary);
    font-size: 12.5px;
    max-width: 52ch;
}

/* ----------------------------------------------------------------- loader */
/*
 * WHAT A WAIT SAYS, AND WHEN IT SAYS IT.
 *
 * The mark moves from the first frame, because "something is happening" is worth saying at once.
 * The line under it stays transparent until --jru-loader-delay has passed, because "this has taken
 * longer than it normally does" is not true yet - and a turning spinner cannot say it, since it
 * turns at the same speed for eight seconds as for eight hundred milliseconds.
 *
 * THE LATE STAGE IS AN ANIMATION DELAY, NEVER A TIMER. The note is in the document from the moment
 * the wait starts and is simply transparent until its delay has run, so nothing holds a timer,
 * nothing disposes one, and a read that finishes early removes an element nobody ever saw.
 *
 * THE GEOMETRY IS JruMark's. This file says which of the four blades dims first and how long the
 * turn takes. It says nothing else about the shape of the logo, because a second declaration of that
 * shape is a second logo.
 */
.jru-loader {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    padding-block: 18px;
    text-align: center;
    color: var(--jru-mark);
}

.jru-loader__note {
    margin: 0;
    font-size: 12.5px;
    color: var(--jru-text-muted);
    opacity: 0;
    animation: jru-loader-arrive 220ms ease-out var(--jru-loader-delay) forwards;
}

/*
 * The blades dim one after another, clockwise from the top right, so the pinwheel the logo already is
 * reads as turning while nothing on the screen moves. The wave goes once round and the disc then rests
 * whole for a beat, so the loop reads as traffic through a junction rather than as a wheel spinning.
 *
 * Opacity on a blade and never a transform: each blade is placed by its own rotate attribute, and a
 * CSS transform would replace it and stack all four in the top right.
 */
.jru-loader__mark .jru-mark__blade {
    animation: jru-loader-turn 1800ms var(--jru-ease) infinite;
}

.jru-loader__mark .jru-mark__blade--se {
    animation-delay: 180ms;
}

.jru-loader__mark .jru-mark__blade--sw {
    animation-delay: 360ms;
}

.jru-loader__mark .jru-mark__blade--nw {
    animation-delay: 540ms;
}

@keyframes jru-loader-arrive {
    to {
        opacity: 1;
    }
}

@keyframes jru-loader-turn {
    0%,
    46%,
    100% {
        opacity: 1;
    }

    18% {
        opacity: 0.22;
    }
}

@keyframes jru-loader-breathe {
    0%,
    100% {
        opacity: 1;
    }

    50% {
        opacity: 0.4;
    }
}

/* ------------------------------------------------------- slide-over panel */
/*
 * position:fixed markup of our own. A MudDrawer here is clipped by .jru-main and never
 * appears.
 */
.jru-scrim {
    position: fixed;
    inset: 0;
    z-index: 1200;
    background: rgba(15, 23, 42, 0.36);
    animation: jru-fade var(--jru-motion) var(--jru-ease);
}

.jru-slideover {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 1201;
    /*
     * 720px, not 560. A panel that carries only fields fits in 560, but the ones that carry a
     * CHILD GRID do not: the attorney firm's plan list is six columns, and at 560 every one of
     * them wrapped over three lines and the table still scrolled sideways inside a panel that
     * was itself only half the screen. Wide content is meant to scroll in its own container as
     * a last resort, not as the normal way a panel is read.
     */
    width: min(720px, 96vw);
    display: flex;
    flex-direction: column;
    background: var(--jru-surface);
    border-left: 1px solid var(--jru-border);
    box-shadow: var(--jru-elev-2);
    animation: jru-slide-in var(--jru-motion) var(--jru-ease);
}

.jru-slideover__header {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 0 0 auto;
    padding: 13px 16px;
    border-bottom: 1px solid var(--jru-border);
}

.jru-slideover__title {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 15px;
    font-weight: 600;
}

/*
 * `overscroll-behavior: contain` stops the scroll chaining out of the panel. Without it, reaching the
 * end of a long form carries on scrolling the screen behind the scrim, so the page the operator
 * is not looking at moves while the panel they are reading stays put. The body is the only scroller
 * in here, which is why it carries it and the panel does not.
 */
.jru-slideover__body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 16px;
}

/*
 * The right padding reserves the feedback launcher's corner. The launcher is a permanent fixture
 * that now floats above this panel, and the panel is anchored to the same edge - so without the
 * reserve the button an operator came here to press has a 34px square sitting on it. A fixture that
 * is always there is a layout fact, not an overlay to be worked around.
 */
.jru-slideover__footer {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    flex: 0 0 auto;
    padding: 12px 16px;
    padding-right: 62px;
    border-top: 1px solid var(--jru-border);
    box-shadow: var(--jru-elev-sticky);
}

/*
 * The footer is already end-aligned, so an action that belongs at the far left is the exception and
 * is the only thing that needs saying. Four footers said it with an inline `flex:1 1 auto` div
 * before the Cancel button, which also meant the nine footers with nothing on the left carried a
 * growing element that did nothing at all.
 */
.jru-slideover__footer-lead {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-right: auto;
}

/* A count or a note in the footer's lead, at the grid's size rather than a button's. */
.jru-slideover__footer-note {
    font-size: 12.5px;
}

/*
 * The unsaved-changes guard's three answers, inside the banner that asks the question. It was an
 * inline flex row on the markup, which is the one gap in this panel the footer does not own and so
 * the one nobody could find again. The guard offers to save rather than merely warning, so the three
 * sit together as a choice rather than as a warning with a button after it.
 */
.jru-slideover__guard {
    display: flex;
    gap: 8px;
    margin-top: 8px;
}

/* A button under a banner's sentence - the overview's Try again. */
.jru-banner__actions {
    margin-top: 8px;
}

@keyframes jru-slide-in {
    from {
        transform: translateX(14px);
        opacity: 0;
    }

    to {
        transform: translateX(0);
        opacity: 1;
    }
}

@keyframes jru-fade {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

/* ------------------------------------------------------------------ forms */
.jru-form {
    display: grid;
    gap: 12px;
}

/*
 * A block inside a form that is set apart from the fields above it - a table of what changed, a
 * sentence standing in for one - and the one action at the foot of a read-only panel. Both add to the
 * form's own gap on purpose, and both were inline margins on the audit trail's panel.
 */
.jru-form__section {
    margin-top: 12px;
}

.jru-form__actions {
    margin-top: 16px;
}

.jru-form__row {
    display: grid;
    gap: 12px;
    grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
}

/*
 * A row carrying a coded field, which is two controls wide and not one.
 *
 * `.jru-coded__entry` is a fixed 150px and what the chosen row is described by takes the rest, so
 * an auto-fit row gives every field the same share and the description gets whatever 150px leaves.
 * Measured on the claim header inside the detail half of a split: four fields across about 800px is
 * 191px each, so the practice name had 15px of a dashed box and rendered as an ellipsis - the field
 * that exists to tell the capturer which practice they are on told them nothing.
 *
 * The minimum is the entry box plus enough of a name to recognise it by, so the row lays out two or
 * three up as the pane allows rather than one per field at every width.
 */
.jru-form__row--coded {
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}

/*
 * A short box before the long one it belongs to: a practice's initials before its name.
 *
 * An even split gives five characters of initials half the row and leaves a seventy-character name
 * the other half. The short box takes what its caption needs and the long one takes the rest.
 */
.jru-form__row--short-first {
    grid-template-columns: 7rem minmax(0, 1fr);
}

/*
 * The same with a title in front: a practice's Dr or Ms, then its initials, then its name. The title
 * box is a type-ahead with a button, so it takes a little more than the initials do.
 */
.jru-form__row--short-first.jru-form__row--titled {
    grid-template-columns: 10rem 7rem minmax(0, 1fr);
}

.jru-field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.jru-field__label {
    color: var(--jru-text-secondary);
    font-size: 10.5px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.07em;
}

.jru-field__hint {
    color: var(--jru-text-muted);
    font-size: 11px;
}

.jru-field__error {
    color: var(--jru-danger);
    font-size: 11px;
}

/*
 * A HINT THAT WARNS. Same element and same size as a hint, in the attention tint, because what it
 * says is still about the value rather than against it.
 *
 * WHY NOT .jru-field__error. That element is what :has() keys the red outline and the red caption
 * off, so a warning wearing it would mark the field as refused - and the operator would go looking
 * for something to fix on a field that saves. The one screen that had a warning under a field wrote
 * the tint inline, which is a colour decision living in markup.
 */
.jru-field__hint--attention {
    /* The badge's amber ink, not --jru-attention: that one is a fill colour and reads at about 3:1
       as small text on white. */
    color: var(--jru-badge-warning-fg);
    font-weight: 500;
}

/*
 * A FIELD THAT WARNS MARKS ITSELF, amber, from the presence of its own warning - the refusal's
 * mechanism in the colour that means "look at this" rather than "this will not save". The tint
 * inside the box is what separates it from an ordinary outline for somebody who cannot tell amber
 * from grey, and the line under it still says what is wrong.
 *
 * Declared BEFORE the refusal's rules on purpose: a field that is both warned and refused shows red,
 * because the refusal is the one that stops the save. The refusal sets no fill of its own, so the
 * tint is withheld from a refused field here rather than left under a red outline.
 */
.jru-field:has(> .jru-field__hint--attention):not(:has(> .jru-field__error)) .mud-input.mud-input-outlined {
    background: var(--jru-badge-warning-bg);
}

.jru-field:has(> .jru-field__hint--attention) .mud-input.mud-input-outlined .mud-input-outlined-border,
.jru-field:has(> .jru-field__hint--attention)
    .mud-input.mud-input-outlined:not(.mud-disabled):not(:focus-within):hover
    .mud-input-outlined-border {
    border-color: var(--jru-attention);
}

/*
 * A FIELD THAT WAS REFUSED MARKS ITSELF, from the presence of its own message.
 *
 * The message under the control and the red outline on it are one decision, not two: :has() keys
 * the styling off the error element, so a field cannot end up outlined with nothing to read or
 * carrying a message that does not show. Nothing to set on the way in and nothing to clear on the
 * way out - the next save renders the error or it does not.
 *
 * The caption goes red with it, because an operator scanning a long form reads the labels first.
 * The hint stays muted: it is advice, and advice that turns red every time a field is refused
 * teaches nobody anything.
 */
.jru-field:has(> .jru-field__error) > .jru-field__label {
    color: var(--jru-danger);
}

.jru-field:has(> .jru-field__error) .mud-input.mud-input-outlined .mud-input-outlined-border {
    border-color: var(--jru-danger);
    border-width: 1px;
}

.jru-field:has(> .jru-field__error) .mud-input.mud-input-outlined:focus-within .mud-input-outlined-border {
    border-width: 2px;
}

/*
 * The hover state has to be beaten on its own terms. Mud paints a hovered outline from
 * :not(.mud-disabled):not(:focus-within):hover, which outranks a plain descendant rule, so a refused
 * field went back to looking ordinary the moment the pointer crossed it - which on a date picker is
 * exactly when the operator is reading the message under it.
 */
.jru-field:has(> .jru-field__error)
    .mud-input.mud-input-outlined:not(.mud-disabled):not(:focus-within):hover
    .mud-input-outlined-border {
    border-color: var(--jru-danger);
}

.jru-field:has(> .jru-field__error) > .jru-readonly {
    border-color: var(--jru-danger);
}

/* A refusal says what to do next, and it names its rule for the operator's support call. */
.jru-refusal {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 9px 12px;
    border-radius: var(--jru-radius-input);
    background: var(--jru-reason-red-bg);
    color: var(--jru-reason-red-fg);
    border: 1px solid var(--jru-badge-fail-bd);
    font-size: 12.5px;
}

.jru-refusal--warning {
    background: var(--jru-reason-amber-bg);
    color: var(--jru-reason-amber-fg);
    border-color: var(--jru-badge-warning-bd);
}

/*
 * WHERE A BANNER SITS. Not named for a tone, because a refusal, a warning and an outcome all sit in
 * the same three places and none of them should carry its own copy of the measurement. The banner
 * components are the only things that render these; no screen writes one.
 *
 * WHY NOT ON .jru-refusal ITSELF. Twenty-one of the banners this replaced carried no margin at all,
 * because their container already spaced them, and putting one on the base class would space every
 * one of those. Three states is what the markup actually holds, so three is what BannerSpacing says.
 */
.jru-banner--spaced {
    margin-bottom: 12px;
}

/* Inside a tab body, which owns its own padding: the banner insets to match the rows under it. */
.jru-banner--inset {
    margin: 0 12px 10px;
}

/*
 * An outcome says what already happened and asks for nothing. Same shape as a refusal, because the
 * eye reads a banner, and a different tint: amber everywhere else here means something needs the
 * operator's attention, and a file that was written needs none. It takes the info trio the badges
 * already use rather than a private tint, so light and dark are one decision.
 *
 * SPACING IS NOT ITS BUSINESS. .jru-banner--spaced and --inset carry that for every banner tone
 * alike, so an outcome placed inside a tab body spaces like a refusal placed there.
 */
.jru-outcome {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 9px 12px;
    border-radius: var(--jru-radius-input);
    background: var(--jru-badge-info-bg);
    color: var(--jru-badge-info-fg);
    border: 1px solid var(--jru-badge-info-bd);
    font-size: 12.5px;
}

/*
 * A link inside a banner wears the banner's own ink and an underline. The product's link colour on a
 * tinted banner reads as a darker word rather than as somewhere to go, and the next step it names is
 * the reason the banner carries a link at all.
 */
.jru-outcome a,
.jru-refusal a {
    color: inherit;
    text-decoration: underline;
}

/*
 * The inline add row. It is part of the grid and reads as the next row rather than as a form:
 * a tinted band, the same column widths, and controls sized to sit inside a 34px row.
 */
/*
 * 4px either side, not 8: the boxes carry their own inner padding, so a box's text still starts
 * near its heading's, and the claim line's seven typed boxes give back 56px between them. That is
 * what puts Apportioned beside Claimed on screen at 1536 wide (feedback 176).
 */
table.jru-grid tbody tr.jru-grid__add > td {
    background: var(--jru-surface-alt);
    border-top: 1px solid var(--jru-border);
    border-bottom: none;
    height: 44px;
    padding: 5px 4px;
}

table.jru-grid tbody tr.jru-grid__add:hover > td {
    background: var(--jru-surface-alt);
}

.jru-grid__add .mud-input-control {
    margin: 0;
}

.jru-grid__add .mud-input.mud-input-outlined .mud-input-slot {
    height: 30px;
    font-size: 12.5px;
}

/*
 * A DATE PICKER IN A GRID CELL HAS TO LEAVE ROOM FOR THE DATE. Mud's adornment is a 48px icon button
 * carrying 12px of padding around a 24px icon, which is right beside a full-height field and wrong
 * inside a 30px add row: in the claim line's Service In cell it took so much of the width that the
 * input shrank to 66px and rendered "20" and a calendar where 2026-09-07 should be. The button keeps
 * the icon's own 24px and gains a 3px ring, which is still a 30px target on a 30px row.
 */
.jru-grid__add .mud-input-adornment {
    margin-left: 4px;
}

.jru-grid__add .mud-input-adornment .mud-icon-button {
    padding: 3px;
    margin-right: 0;
}

/*
 * AND THE DATE ITSELF HAS A FLOOR. A grid wider than its pane gives every cell its narrowest, and a
 * picker's narrowest is its calendar button: at 1536 wide the claim line's Service In rendered "10"
 * and a calendar, which read as a field that could only be picked from. Measured, not chosen: the
 * typed 10-09-2026 needs 80px of input, and the button and its margin take the other 48.
 */
.jru-grid__add .mud-picker .mud-input-control {
    min-width: 128px;
}

/*
 * A CODED FIELD THAT DOES NOT REPEAT WHAT IT CHOSE IS SIZED FOR THE CODE. The claim line's tariff
 * box leaves the description to the Description box beside it; at the entry's 150px the Tariff
 * column still took the width of three columns for a five-character code.
 */
.jru-grid__add .jru-coded--bare .jru-coded__entry {
    width: 96px;
}

/*
 * WHAT IS TYPED HAS TO BE READ BACK, SO THE TYPED BOXES HAVE FLOORS TOO. A grid wider than its pane
 * gives every cell its narrowest, and an input's narrowest is a few characters: at 1536 wide the
 * claim line's Claimed box showed "2070.0" of 2070.00, its Units "1.0" and its Description nine
 * letters (feedback 176 and 178). Measured, not chosen, at the add row's 12.5px: the amount holds
 * 9999999.99 whole, the units 99.99, and the description about twenty capitals.
 *
 * THE THREE ADD UP TO A BUDGET, NOT A WISH. With the claims list folded the lines grid has 1220px
 * at 1536 wide, and these floors are what leave Claimed and Apportioned clear of Add with about
 * 20px to spare. A wider floor here pushes Apportioned's figure under the buttons again.
 */
.jru-grid__add .jru-entry--amount {
    min-width: 116px;
}

.jru-grid__add .jru-entry--units {
    min-width: 64px;
}

.jru-grid__add .jru-entry--text {
    min-width: 200px;
}

/*
 * THE ADD ROW'S OWN BUTTONS, AND NOTHING THAT MERELY SHARES THEIR CLASS - the same carve-out the
 * dense form makes further down, for the same reason. Mud puts .mud-button-root on the ripple base
 * inside a switch, and that base is the box the thumb is centred in: it is 24px square against a
 * 10px track, so the thumb and the track share a centreline. Stretched to 30px it is 3px taller
 * either side of centre, the thumb drops by 3 and the track does not, and the switch reads as a
 * knob hanging off the bottom right of its own track. Reported against the plan add row, which is
 * the only switch in the system inside an add row.
 */
.jru-grid__add .mud-button-root:not(.mud-icon-button) {
    min-height: 30px;
    font-size: 12.5px;
    text-transform: none;
    letter-spacing: 0;
}

/* What the empty state used to say, said under the add row instead of in place of it. */
.jru-grid__addhint {
    padding: 10px 15px 14px;
    color: var(--jru-text-muted);
    font-size: 11.5px;
}

/*
 * The foot of a windowed list too long for a browser to scroll whole. It is a row of the table so it
 * arrives where the reader does, at the end, rather than as a banner saying it before it is true.
 */
table.jru-grid tbody tr.jru-grid__capped > td {
    padding: 12px 15px;
    color: var(--jru-text-muted);
    font-size: 12px;
    text-align: center;
    border-bottom: none;
}

/*
 * A grid inside a slide-over. The panel is narrower than a page, so the cells stop wrapping
 * and the table scrolls as one instead of every column collapsing to its longest word.
 */
.jru-slideover table.jru-grid {
    white-space: nowrap;
}

.jru-slideover table.jru-grid tbody td {
    max-width: 22ch;
    overflow: hidden;
    text-overflow: ellipsis;
}

/*
 * A table whose cells are the point: the audit trail's before and after. Cut at 22ch with an
 * ellipsis and no title, a changed address or reference could not be read on the one screen whose
 * job is showing what a field said before, so these wrap instead.
 */
.jru-slideover table.jru-grid.jru-grid--values {
    white-space: normal;
}

.jru-slideover table.jru-grid.jru-grid--values tbody td {
    max-width: none;
    overflow: visible;
    overflow-wrap: anywhere;
}

/*
 * A before or an after: as often a word ("Rejected", an office's name) as a number, so the text face
 * with its digits lined up. A status in a code face reads as a code.
 */
.jru-auditvalue {
    font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------ child list */
/*
 * The add row under a panel's own grid. It is a form inside a form, so it is boxed and
 * indented rather than left to run into the fields above it.
 */
.jru-childform {
    display: grid;
    gap: 12px;
    padding: 12px;
    margin-top: 8px;
    background: var(--jru-surface-alt);
    border: 1px solid var(--jru-border-soft);
    border-radius: var(--jru-radius-control);
}

/* The add button under a child form, pinned to the foot of its row. It wore .jru-field for the
   column, which is JruField's wrapper to write, so the column is stated here instead. */
.jru-childform__action {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    min-width: 0;
}

/* Cards stacked down a detail page, with the gap the card grid uses. */
.jru-stack {
    display: grid;
    gap: 16px;
    min-width: 0;
}

/* A count beside a card heading. It states a fact; it is not a status. */
.jru-card__count {
    color: var(--jru-text-muted);
    font-size: 11.5px;
    font-weight: 400;
}

/* Two controls side by side inside one grid cell, or a control and its note. */
.jru-inlinepair {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

/* --------------------------------------------------------- the file picker */
/*
 * JruFilePicker. The browser's own file control takes none of this - not the font, not the height,
 * not the disabled palette, not the theme - so the input is moved out of sight and the <label for>
 * is what is drawn and clicked. Moved rather than display: none, because an input that is not
 * rendered cannot be tabbed to; the ring the input would have drawn is drawn on the label instead.
 */
/*
 * Choose from SharePoint beside Browse for a file is two buttons, and the claim tab gives the picker a
 * quarter of its row, so the second one wraps under the first rather than running off the card.
 */
.jru-filepicker {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 10px;
    min-width: 0;
}

.jru-filepicker__input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    overflow: hidden;
    white-space: nowrap;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
}

.jru-filepicker__button {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: var(--jru-control-h);
    padding: 0 12px;
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-control);
    background: var(--jru-surface);
    color: var(--jru-text);
    font-size: 12.5px;
    font-weight: 500;
    white-space: nowrap;
    cursor: pointer;
}

.jru-filepicker__button:hover {
    background: var(--jru-surface-alt);
    border-color: var(--jru-primary-bright);
}

/* The input is off-screen, so the ring it earns has to be drawn on what the operator can see. */
.jru-filepicker__input:focus-visible + .jru-filepicker__button {
    outline: 2px solid var(--jru-primary-bright);
    outline-offset: 1px;
}

/*
 * A disabled picker recedes and stays where it is. Both document tabs disable it until a type is
 * chosen, and a control that vanished would leave nothing on screen to explain why no file can be
 * filed. pointer-events stays on so the cursor still reads as refused rather than as absent.
 */
.jru-filepicker.is-disabled .jru-filepicker__button {
    background: var(--jru-surface-alt);
    border-color: var(--jru-border);
    color: var(--jru-text-muted);
    cursor: not-allowed;
}

.jru-filepicker__hint {
    color: var(--jru-text-muted);
    font-size: 11.5px;
    min-width: 0;
}

/*
 * Choose from SharePoint is a real button beside the label that is one. A button does not inherit the
 * page's font the way a label does, so it is told to, and it draws its own focus ring because there is
 * no hidden input behind it to borrow one from.
 */
button.jru-filepicker__button {
    font-family: inherit;
    line-height: inherit;
}

button.jru-filepicker__button:focus-visible {
    outline: 2px solid var(--jru-primary-bright);
    outline-offset: 1px;
}

/* ------------------------------------------------------------------ choose from SharePoint */
/*
 * The lookup's surface with a trail of folders over the search line. The trail is the only way back
 * up, so every folder on it but the last is a button, drawn as a link.
 */
.jru-sharepoint__trail {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2px;
    padding: 6px 15px 0;
    font-size: 12.5px;
    color: var(--jru-text-secondary);
    min-width: 0;
}

.jru-sharepoint__crumb {
    padding: 2px 4px;
    border: 0;
    border-radius: var(--jru-radius-control);
    background: none;
    color: var(--jru-primary-bright);
    font: inherit;
    cursor: pointer;
    max-width: 260px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

button.jru-sharepoint__crumb:hover {
    text-decoration: underline;
}

button.jru-sharepoint__crumb:focus-visible {
    outline: 2px solid var(--jru-primary-bright);
    outline-offset: 1px;
}

.jru-sharepoint__crumb.is-current {
    color: var(--jru-text);
    font-weight: 600;
    cursor: default;
}

.jru-sharepoint__sep {
    color: var(--jru-text-muted);
}

.jru-sharepoint__name {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

.jru-sharepoint__folder {
    color: var(--jru-primary-bright);
}

.jru-sharepoint__file {
    color: var(--jru-text-muted);
}

.jru-sharepoint__retry {
    padding: 0 15px 12px;
}

/* A fact stated above a grid: which firm's plans these are, which batch this is. */
.jru-contextline {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 15px;
    border-bottom: 1px solid var(--jru-border-soft);
    color: var(--jru-text-secondary);
    font-size: 12.5px;
}

/* ------------------------------------------------------------------ split */
/*
 * A list beside the record being worked on, where the record panel manages its own
 * height rather than scrolling inside a fixed one. The capture batch's claims use it:
 * the claim panel holds tabs, and a panel that clips its own overflow fights them.
 *
 * Same two rules as master-detail below, and for the same reason: minmax(0, 1fr) on the
 * second track and min-width: 0 down the chain, or a wide claim table pushes the record
 * panel past the page edge.
 */
.jru-split {
    display: grid;
    grid-template-columns: var(--jru-list-panel) minmax(0, 1fr);
    gap: 16px;
    align-items: start;
    min-width: 0;
}

.jru-split__list,
.jru-split__detail {
    min-width: 0;
}

/*
 * THE LIST FOLDS AWAY WHILE A CLAIM'S LINES ARE TYPED. A claim line needs about 1,450px once its
 * amount and description can be read, and beside the list it got 844 at 1536 wide (feedback 176).
 * Hidden, never removed: the list keeps its search, order and selection for when it is shown again.
 */
.jru-split--folded {
    grid-template-columns: minmax(0, 1fr);
}

.jru-split--folded > .jru-split__list {
    display: none;
}

/* ------------------------------------------------------------ record list */
/*
 * A master pane where the record is an area, not a row of cells. See RecordList.razor for why a
 * table cannot fit the narrow half of a split: the constraint is the pane, and the pane is the
 * design.
 */
.jru-recordlist__wrap {
    min-width: 0;
    overflow-y: auto;
    max-height: calc(100vh - var(--jru-appbar-height) - 210px);
}

.jru-recordlist {
    list-style: none;
    margin: 0;
    padding: 0;
    min-width: 0;
}

/*
 * One hairline between records, no shadow and no rounding. A stack of raised cards reads as a dozen
 * separate things to decide about; this is one list to pick from.
 */
.jru-recordlist__item {
    display: grid;
    /*
     * A BUTTON'S CONTENT BOX CENTRES ITSELF, and that is a user-agent style rather than anything
     * this sheet asked for: left alone, the grid column sized to its content and sat in the middle
     * of the record, so the amount could not reach the right edge and the title had no width to
     * truncate against. The column is told to take the whole record.
     */
    grid-template-columns: minmax(0, 1fr);
    justify-items: stretch;
    gap: 3px;
    width: 100%;
    padding: 9px 12px 9px 13px;
    border-left: 3px solid transparent;
    border-bottom: 1px solid var(--jru-border-soft);
    background: transparent;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: background var(--jru-motion) var(--jru-ease);
}

.jru-recordlist__item:hover {
    background: var(--jru-hover);
}

/*
 * The accent bar AND the tint. The bar is what survives greyscale and a cheap monitor, which is the
 * same reason the grid's selected row carries one: an operator arrowing down a column of
 * near-identical practice names is reading what is highlighted, not what they last clicked.
 */
.jru-recordlist__item.is-selected {
    border-left-color: var(--jru-primary);
    background: var(--jru-primary-surface);
}

.jru-recordlist__item.is-attention {
    background: var(--jru-row-attention);
}

.jru-recordlist__item.is-attention.is-selected {
    background: var(--jru-primary-surface);
}

/* The list is the tab stop, so the ring belongs to it and marks where the caret is. */
.jru-recordlist:focus-visible {
    outline: 2px solid var(--jru-primary-bright);
    outline-offset: -2px;
}

/*
 * The identifying line: what the record IS, and the badge that says where it stands. The title takes
 * the room the other two do not need and truncates rather than wrapping, because a practice name
 * broken over three lines was half of what made the old pane unreadable.
 */
.jru-recordlist__line {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

/*
 * NOTHING ON THESE LINES SHRINKS EXCEPT THE TITLE. A flex item's default is to shrink, and a status
 * badge given the chance shrank to a circle holding the first letter of the word - a badge reading
 * "S" where it should read "Sent". The title is the one thing with room to lose, and it loses it as
 * an ellipsis.
 */
.jru-recordlist__line > * {
    flex: 0 0 auto;
    min-width: 0;
}

.jru-recordlist__key {
    font-family: var(--jru-mono);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    font-size: 12.5px;
    flex: 0 0 auto;
}

.jru-recordlist__line > .jru-recordlist__title {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 12.5px;
    font-weight: 500;
}

/*
 * The qualifying line. It WRAPS deliberately: this pane is the narrow half of a split and the whole
 * point of the shape is that a record grows downwards instead of pushing the panel sideways.
 */
.jru-recordlist__line--meta {
    flex-wrap: wrap;
    gap: 4px 10px;
    color: var(--jru-text-muted);
    font-size: 11.5px;
}

.jru-recordlist__line > .jru-recordlist__amount {
    margin-left: auto;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* The order of a list with no headings to press. One select, one direction toggle. */
.jru-sortcontrol {
    display: flex;
    align-items: center;
    gap: 4px;
}

.jru-sortcontrol__select {
    width: 104px;
}

/*
 * IN A MASTER PANE THE TOOLBAR IS ONE LINE. The search asks for 320px and the pane is 358px, so with
 * anything beside it the row wrapped and the sort control floated on a line of its own looking like
 * a control somebody had dropped. Here the search takes what is left instead of asking for a width.
 */
.jru-split__list .jru-toolbar__search,
.jru-masterdetail .jru-toolbar__search {
    flex: 1 1 auto;
}

.jru-split__list .jru-card__toolbar,
.jru-masterdetail .jru-card__toolbar {
    flex-wrap: nowrap;
    gap: 8px;
    padding: 10px 12px;
}

@media (max-width: 1100px) {
    .jru-split {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* -------------------------------------------------------------- dual list */
/*
 * Available on the left, selected on the right, and the move actions between them. The
 * shape of Create Audit Batch and Prepare Batches for Send, and the one screen the
 * client asked to be walked through before it is signed off - which is why the third
 * panel, the batches that were left out and why, is part of the screen and not a
 * tooltip.
 *
 * The two panels are equal and neither is the "real" one: an operator moves rows both
 * ways and a narrower side would read as the lesser half.
 */
.jru-duallist {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    gap: 12px;
    align-items: stretch;
    min-width: 0;
}

.jru-duallist__panel {
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.jru-duallist__panel .jru-grid__scroll {
    overflow-y: auto;
    min-height: 0;
    max-height: calc(100vh - var(--jru-appbar-height) - 300px);
}

.jru-duallist__actions {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 8px;
}

@media (max-width: 1100px) {
    .jru-duallist {
        grid-template-columns: minmax(0, 1fr);
    }

    .jru-duallist__actions {
        flex-direction: row;
        justify-content: center;
    }
}

/* ---------------------------------------------------------- master-detail */
/*
 * The search-and-record layout. The list panel is fixed width and scrolls on its own; the
 * record panel takes the rest and scrolls on its own. The shell never scrolls
 * horizontally, and the layout holds at 1366 by 768.
 *
 * minmax(0, 1fr) on the second track, and min-width: 0 down the chain: a grid item's
 * default minimum size is its min-content, so a wide table would otherwise push the
 * record panel past the page edge.
 */
/*
 * A MASTER-DETAIL SCREEN FILLS THE PANE AND THE PAGE ITSELF DOES NOT SCROLL. The two panels each
 * scroll their own body, which is the whole point of the pattern: the list stays put while the
 * record is read, and the record stays put while the list is walked.
 *
 * The panels used to take max-height: calc(100vh - appbar - 132px), and 132 was a guess at what sits
 * above them. What actually sits above them is the page header plus a filter card whose height is
 * the screen's own business and changes the moment its Filters panel is opened - so the panels ran
 * past the bottom of the pane and the screen scrolled too. Three scrollbars in one screen, one of
 * which moved the headings out of sight, and the operator has to work out which of them owns the
 * wheel. The height is now taken from what is left rather than from a number, so it is right at any
 * viewport and stays right when anything above it changes.
 *
 * :has() rather than a class on the page, because .jru-masterdetail IS the declaration that this is
 * a fill screen. A second flag would be a second place to forget.
 */
.jru-screen:has(.jru-masterdetail) {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* Everything above the panels keeps its own height. Without this they are flex items free to
   shrink, and a squeezed screen compresses its heading rather than its list. */
.jru-screen:has(.jru-masterdetail) > *:not(.jru-masterdetail) {
    flex: 0 0 auto;
}

.jru-masterdetail {
    display: grid;
    grid-template-columns: var(--jru-list-panel) minmax(0, 1fr);
    gap: 16px;
    min-height: 0;
    min-width: 0;
    flex: 1 1 auto;
}

.jru-masterdetail__list,
.jru-masterdetail__record {
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.jru-masterdetail__list .jru-grid__scroll,
.jru-masterdetail__record .jru-tabpanel {
    overflow-y: auto;
    min-height: 0;
}

/*
 * A TAB THAT IS A LIST HAS ONE SCROLLER, AND IT IS THE GRID'S.
 *
 * The tab panel scrolls its own body, and the grid inside it carried its own cap from the viewport
 * (100vh - appbar - 210px), which is taller than what the record panel leaves the tab. So a member's
 * claims scrolled inside the grid while the panel scrolled the grid, and the operator got two bars
 * side by side against one list. Here the grid takes what is left of the panel instead: whatever sits
 * above it keeps its own height, the grid shrinks to the rest, and the panel has nothing left to
 * scroll. The grid stays the scroller rather than the panel, because its headings stick to its own
 * box and its virtualiser measures that box.
 *
 * Not when the panels stack: then the page scrolls and the record panel is as tall as its content,
 * so there is no height to fill and the grid keeps its own cap.
 */
@media not all and (max-width: 1100px) {
    .jru-masterdetail__record .jru-tabpanel:has(> .jru-grid__scroll) {
        display: flex;
        flex-direction: column;
    }

    .jru-masterdetail__record .jru-tabpanel:has(> .jru-grid__scroll) > * {
        flex: 0 0 auto;
    }

    .jru-masterdetail__record .jru-tabpanel > .jru-grid__scroll {
        flex: 0 1 auto;
        max-height: none;
    }
}

/*
 * Narrow enough and the two panels stack, at which point they cannot both own a viewport height:
 * the page scrolls again, and each panel is as tall as its content. The fill layout is undone
 * here as well as the grid track, or the stacked panels are squeezed into one screenful.
 */
@media (max-width: 1100px) {
    .jru-screen:has(.jru-masterdetail) {
        display: block;
        overflow: auto;
    }

    .jru-masterdetail {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ------------------------------------------------------------------- tabs */
/*
 * Our own tab strip rather than MudTabs, for the same reason the slide-over is our own
 * markup: these have to sit inside a panel that owns its height and scrolls its body,
 * and a component that manages its own overflow fights that.
 *
 * Colour is never the only signal. The selected tab carries a weight change and an
 * underline as well as a colour, so it reads in greyscale.
 */
.jru-tabs {
    display: flex;
    gap: 2px;
    padding: 0 15px;
    border-bottom: 1px solid var(--jru-border-soft);
    overflow-x: auto;
    flex: 0 0 auto;
}

.jru-tabs__tab {
    appearance: none;
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    padding: 10px 12px;
    font: inherit;
    font-size: 12.5px;
    color: var(--jru-text-secondary);
    cursor: pointer;
    white-space: nowrap;
}

/*
 * A TAB THAT CANNOT BE REACHED YET, AND IT HAS TO LOOK UNREACHABLE.
 *
 * There was no rule for this at all. The strip states its own colour and its own `cursor: pointer`
 * as author declarations, which outrank the user agent's disabled styling, and the hover rule below
 * was not scoped away from a disabled tab - so an unreachable tab rendered in the colour of a
 * reachable one, under a pointer, lighting up on hover, and the only way to find out was to click it
 * and get nothing. The sighted operator was worse served than the screen reader user, which is the
 * reverse of what the strip's ARIA was doing.
 *
 * The selector reads the ARIA state because the tab is `aria-disabled` rather than `disabled`: the
 * strip's arrow keys have to be able to land on it and say what it is.
 */
.jru-tabs__tab[aria-disabled="true"] {
    color: var(--jru-text-disabled);
    cursor: default;
}

/* Scoped away from an unreachable tab: a hover tint on a tab that does nothing is an invitation. */
.jru-tabs__tab:not([aria-disabled="true"]):hover {
    color: var(--jru-text);
    background: var(--jru-hover);
}

.jru-tabs__tab.is-selected {
    color: var(--jru-primary-bright);
    border-bottom-color: var(--jru-primary);
    font-weight: 600;
}

.jru-tabs__tab:focus-visible {
    outline: 2px solid var(--jru-primary-bright);
    outline-offset: -2px;
}

/* A count beside a tab label, so the operator can see there is something on it. */
.jru-tabs__count {
    display: inline-block;
    margin-left: 6px;
    padding: 0 5px;
    border-radius: 999px;
    background: var(--jru-border);
    color: var(--jru-text-secondary);
    font-size: 10.5px;
    font-weight: 600;
}

.jru-tabpanel {
    padding: 15px;
    flex: 1 1 auto;
}

/* A size gauge above the document grid it measures. */
.jru-gauge-row {
    margin-bottom: 12px;
}

/* A warning shown when a record is opened. It states a fact, it does not refuse. */
.jru-warnings {
    display: grid;
    gap: 6px;
    margin-bottom: 12px;
}

/* A read-only value in a form: the derived count, the resolved firm. Never an input, so
   nobody types into something the system computes. */
.jru-readonly {
    display: flex;
    align-items: center;
    min-height: var(--jru-control-h);
    padding: 0 10px;
    border: 1px dashed var(--jru-border-strong);
    border-radius: var(--jru-radius-input);
    color: var(--jru-text-secondary);
    font-size: 13px;
}

/* A group heading inside a form. Section 11 lays the member tab out in four of them. */
.jru-fieldset {
    display: grid;
    gap: 12px;
    padding-top: 4px;
}

/* A row of checkboxes that wraps, for a choice of several like the court download's document types. */
.jru-checkrow {
    display: flex;
    flex-wrap: wrap;
    gap: 0 20px;
}

.jru-fieldset__legend {
    color: var(--jru-text-secondary);
    font-size: 10.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    padding-bottom: 4px;
    border-bottom: 1px solid var(--jru-border-soft);
}

/*
 * A SENT CLAIM'S OUTPUT, ON THE CLAIM: the member's files as the send batch's output folder holds
 * them, and the two ways of producing them again. One bordered panel, a file per row and the
 * choices under it, each beside what it does. Drawn as a hint, two buttons of different weights and
 * a line of icons and timestamps running into each other, nothing in it lined up with anything else,
 * and which choice e-mails the fund was said only by the confirmation behind it.
 *
 * THE PANEL IS ITS OWN CONTAINER, because the claim's record panel is as wide as the list beside
 * it leaves it, which the viewport does not say. Narrow, the time drops under the file's name and
 * each choice's sentence under its button.
 */
.jru-outputpanel {
    container-type: inline-size;
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-control);
    background: var(--jru-surface);
    overflow: hidden;
}

.jru-outputpanel__head {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 40px;
    padding: 4px 6px 4px 12px;
    border-bottom: 1px solid var(--jru-border);
}

.jru-outputpanel__title {
    color: var(--jru-text);
    font-size: 12.5px;
    font-weight: 600;
    white-space: nowrap;
}

.jru-outputpanel__note {
    flex: 1 1 auto;
    min-width: 0;
    color: var(--jru-text-muted);
    font-size: 11.5px;
}

/* Icon, name, when it was written, then the download on the right edge the refresh above it keeps,
   so the two controls stand in one column. The rows share the list's tracks, so each time starts
   just past the longest name rather than across the width of the form from it. */
.jru-outputpanel__files {
    display: grid;
    grid-template-columns: auto max-content max-content minmax(0, 1fr) auto;
    column-gap: 10px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.jru-outputfile {
    display: grid;
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
    align-items: center;
    min-height: 40px;
    padding: 4px 6px 4px 12px;
}

.jru-outputfile .jru-iconbutton {
    grid-column: -2;
}

.jru-outputfile__empty {
    grid-column: 2 / -2;
}

.jru-outputfile + .jru-outputfile {
    border-top: 1px solid var(--jru-border-soft);
}

.jru-outputfile__icon {
    display: inline-flex;
    color: var(--jru-text-muted);
}

.jru-outputfile__name {
    color: var(--jru-text);
    font-size: 13px;
    font-weight: 500;
}

.jru-outputfile__when {
    padding-left: 14px;
    color: var(--jru-text-muted);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* The choices sit on the alternate ground, so the decision reads as its own band of the panel. */
.jru-outputpanel__again {
    display: grid;
    gap: 10px;
    padding: 12px;
    background: var(--jru-surface-alt);
}

.jru-outputpanel__files + .jru-outputpanel__again {
    border-top: 1px solid var(--jru-border);
}

/* One grid for both choices, so the buttons take the wider label's width between them and the two
   sentences start on one line down. */
.jru-outputpanel__choices {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    align-items: center;
    gap: 8px 14px;
}

.jru-outputpanel__choices .mud-button-root {
    width: 100%;
    justify-content: flex-start;
}

.jru-outputpanel__does {
    color: var(--jru-text-secondary);
    font-size: 12px;
}

@container (max-width: 560px) {
    .jru-outputpanel__files {
        display: block;
    }

    .jru-outputfile {
        grid-template-columns: auto minmax(0, 1fr) auto;
        column-gap: 10px;
    }

    .jru-outputfile__when {
        grid-column: 2;
        grid-row: 2;
        padding-left: 0;
    }

    .jru-outputfile .jru-iconbutton {
        grid-column: 3;
        grid-row: 1 / span 2;
    }

    .jru-outputfile__empty {
        grid-column: 2 / -1;
    }

    .jru-outputpanel__choices {
        grid-template-columns: minmax(0, 1fr);
        gap: 4px;
    }

    .jru-outputpanel__does + .mud-button-root {
        margin-top: 8px;
    }
}

/* One note, newest first. The author and the time are the audit columns. */
/* The list of notes under the add form, with the gap it had as an inline margin. */
.jru-notes {
    margin-top: 12px;
}

.jru-note {
    padding: 10px 12px;
    border: 1px solid var(--jru-border-soft);
    border-radius: var(--jru-radius-control);
    background: var(--jru-surface-alt);
}

.jru-note__meta {
    color: var(--jru-text-muted);
    font-size: 11px;
    margin-bottom: 4px;
}

.jru-note__text {
    font-size: 12.5px;
    white-space: pre-wrap;
    /* A pasted address or path has no break opportunity, and pre-wrap alone lets it push the tab
       panel into a sideways scroll. */
    overflow-wrap: anywhere;
}

/* ------------------------------------------------------------ coded field */
.jru-coded {
    display: flex;
    align-items: stretch;
    gap: 6px;
    min-width: 0;
}

.jru-coded__entry {
    width: 150px;
    flex: 0 0 auto;
}

.jru-coded__described {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    padding: 0 10px;
    height: var(--jru-control-h);
    border: 1px dashed var(--jru-border);
    border-radius: var(--jru-radius-input);
    background: var(--jru-surface-alt);
    color: var(--jru-text-secondary);
    font-size: 12.5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---------------------------------------------------------- lookup dialog */
/*
 * THE LOOKUP IS A SEARCH SURFACE, AND IT IS DRESSED AS ONE.
 *
 * It was a card: a 13.5px bold heading, a boxed outlined field in a toolbar, a table, a footer
 * strip. Everything on it had the same weight, so nothing on it said what to do, and the thing an
 * operator is meant to do first - type - looked like one field of a form somebody had not finished.
 *
 * Ctrl+K's palette is the same gesture: a surface appears, you type, you arrow, you press Enter.
 * So this wears the same clothes. The title drops to a muted 11px caption, the search line becomes
 * the largest type on the panel with the magnifier as its only ornament, and the rule under it is
 * the one border in the upper half. What is left is a sentence: this is a Member, type.
 */
.jru-lookup__overlay {
    position: fixed;
    inset: 0;
    z-index: 1300;
    display: flex;
    justify-content: center;
    align-items: flex-start;

    /* Nearer the top than the middle. A panel that grows downwards as rows arrive must not walk up
       the screen while somebody is reading it, which centring makes it do on every keystroke. */
    padding-top: 10vh;
}

/*
 * A LOOKUP OPENED FROM INSIDE A MUDDIALOG HAS TO ESCAPE THE DIALOG'S BOX.
 * Mud's open animation runs with `both` fill, so it leaves `transform: scale(1)` on .mud-dialog for as
 * long as the dialog is open, and any transform makes the element the containing block for a fixed
 * descendant. The overlay above was therefore `inset: 0` of the dialog rather than of the viewport,
 * and the dialog's own `overflow: auto` cut the lookup down to its search line and one row - measured
 * on the reject dialog, 600 x 248 at the dialog's own position. A filled animation outranks a plain
 * declaration, so the animation goes as well as the transform. Both have finished by the time anybody
 * can press F4, and scale(1) moves nothing, so the dialog does not change by a pixel.
 */
.mud-dialog:has(.jru-lookup__overlay) {
    animation: none;
    transform: none;
}

.jru-lookup {
    width: min(var(--jru-lookup-width), 96vw);
    overflow: hidden;
}

.jru-lookup__head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 16px 0;
}

/* The magnifier leads the line, so the caption sits over it rather than beside a second icon. */
.jru-lookup__search .mud-input-adornment-start {
    margin-right: 2px;
}

.jru-lookup__title {
    color: var(--jru-text-muted);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.09em;
}

/*
 * The search line, and it is JruSearchBox wearing this dialog's clothes rather than a second search
 * control. Mud's outline, its floating notch and its dense height are all stripped here and only
 * here, so the box reads as a line of type on a rule the way the command palette's does. The
 * behaviour - the one debounce, the clear button, the value handling that does not drop keystrokes -
 * is the component's and is untouched.
 */
.jru-lookup__search {
    padding: 2px 6px 6px;
    border-bottom: 1px solid var(--jru-border);
}

/*
 * .jru-lookup leads every one of these on purpose. The density block sets the same properties on
 * the same four classes further down the file, so at equal weight it wins on order alone and the
 * line silently stayed 13px - which is the whole point of the design here, and it looked like
 * nothing had changed.
 */
.jru-lookup .jru-lookup__search .mud-input.mud-input-outlined .mud-input-outlined-border {
    border: 0;
}

.jru-lookup .jru-lookup__search .mud-input.mud-input-outlined .mud-input-slot {
    height: auto;
    font-size: 17px;
    padding: 6px 0;
}

.jru-lookup .jru-lookup__search .mud-input.mud-input-outlined .mud-input-slot::placeholder {
    font-size: 15px;
    color: var(--jru-text-muted);
    opacity: 1;
}

.jru-lookup__search .mud-input-adornment .mud-icon-root {
    color: var(--jru-text-muted);
}

/* The panel takes the focus ring the bare line gave up, so the surface as a whole says where the
   caret is. Inside the box there is nothing left to draw one around. */
.jru-lookup:has(.jru-lookup__search input:focus) {
    border-color: var(--jru-primary-bright);
}

/*
 * ONE SCROLLER, AND IT IS THE GRID'S. This box used to carry the max-height and overflow: auto
 * itself, with .jru-grid__scroll inside it carrying its own - and the grid's cap is taken from the
 * viewport (100vh - appbar - 210px), which at any ordinary window is taller than the 440px this
 * panel allows. So the inner box scrolled its rows, the outer box scrolled the inner box, and the
 * operator got two bars side by side against the same list with no way to tell which owned the
 * wheel. The panel now only clips; the grid keeps the measurable height its virtualiser needs, and
 * the number that decides how tall the list is is stated once, here.
 */
.jru-lookup__body {
    overflow: hidden;
    padding: 4px 0;
}

/*
 * The rows are the scroller here, not the body above them - the body is `overflow: hidden` and the
 * grid's own box carries the height. So the scroll containment goes here, and scoped to the lookup:
 * `.jru-grid__scroll` is every grid in the product, and stopping the page from scrolling when a grid
 * reaches its end is a decision about every screen rather than about this dialog.
 *
 * Without it, reaching the last of 200 rows carries on scrolling the screen behind the scrim.
 */
.jru-lookup__body > .jru-grid__scroll {
    max-height: min(52vh, 440px);
    overscroll-behavior: contain;
}

/*
 * The row the keyboard is standing on, and it has to read at a glance: Enter commits it, and an
 * operator arrowing down a column of near-identical surnames is choosing by what is highlighted
 * rather than by what they last clicked. A tint alone was almost invisible - the accent bar is what
 * makes it survive a greyscale screenshot and a cheap monitor both.
 */
.jru-lookup .jru-grid tbody tr.is-selected {
    background: var(--jru-primary-surface);
    box-shadow: inset 3px 0 0 var(--jru-primary);
}

.jru-lookup .jru-grid tbody tr.is-selected td:first-child {
    font-weight: 600;
}

/*
 * Headings recede: they label the columns, they are not content. THE PANEL'S OWN COLOUR RATHER THAN
 * TRANSPARENT, because this row is sticky: transparent let every row scrolling under it show through
 * its glyphs, so the headings sat in a smear of half a practice name. Receding is a matter of the
 * muted text and the soft rule under it, not of being see-through.
 */
.jru-lookup .jru-grid thead th,
.jru-lookup .jru-grid thead tr.jru-grid__filters td,
.jru-lookup .jru-grid thead tr.jru-grid__filters th {
    background: var(--jru-surface);
    border-bottom: 1px solid var(--jru-border-soft);
    color: var(--jru-text-muted);
}

.jru-lookup__foot {
    display: flex;
    align-items: center;
    padding: 10px 16px;
    border-top: 1px solid var(--jru-border-soft);
}

/*
 * The keys are printed because a shortcut nobody is told about is a shortcut nobody uses, and it is
 * the .jru-kbd the app bar prints Ctrl K in so the product teaches its keys one way. Hidden on a
 * narrow panel, where the count and the buttons are what has to fit.
 */
.jru-lookup__keys {
    display: flex;
    align-items: center;
    gap: 5px;
    color: var(--jru-text-muted);
    font-size: 11px;
    margin-right: 14px;
}

.jru-lookup__keys .jru-kbd + span {
    margin-right: 6px;
}

@media (max-width: 720px) {
    .jru-lookup__keys {
        display: none;
    }
}

.jru-lookup__footer {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
}

.jru-lookup__count {
    flex: 1 1 auto;
    color: var(--jru-text-muted);
    font-size: 11.5px;
}

/* ------------------------------------------------------------ size gauge */
.jru-gauge {
    display: flex;
    align-items: center;
    gap: 10px;
}

.jru-gauge__track {
    flex: 1 1 auto;
    min-width: 90px;
    height: 7px;
    border-radius: 999px;
    background: var(--jru-border);
    overflow: hidden;
}

.jru-gauge__fill {
    height: 100%;
    border-radius: 999px;
    transition: width var(--jru-motion) var(--jru-ease);
}

.jru-gauge__fill--ok {
    background: var(--jru-success);
}

.jru-gauge__fill--near {
    background: var(--jru-attention);
}

.jru-gauge__fill--over {
    background: var(--jru-danger);
}

.jru-gauge__text {
    flex: 0 0 auto;
    font-family: var(--jru-mono);
    font-size: 11.5px;
    color: var(--jru-text-secondary);
}

/* ---------------------------------------------------------- global search */
.jru-palette {
    position: fixed;
    inset: 0;
    z-index: 1300;
    display: flex;
    justify-content: center;
    align-items: flex-start;
    padding-top: 12vh;
    background: rgba(15, 23, 42, 0.36);
}

.jru-palette__panel {
    width: min(620px, 94vw);
    background: var(--jru-surface);
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-card);
    box-shadow: var(--jru-elev-2);
    overflow: hidden;
}

.jru-palette__input {
    width: 100%;
    box-sizing: border-box;
    border: 0;
    border-bottom: 1px solid var(--jru-border);
    background: transparent;
    color: var(--jru-text);
    font-family: var(--jru-sans);
    font-size: 14px;
    padding: 13px 15px;
    outline: none;
}

/* The ring is replaced, not removed: a primary rule under the box, the width of the panel, which
   reads as "this is where typing goes" without a rounded outline fighting the panel's own corners. */
.jru-palette__input:focus-visible {
    box-shadow: inset 0 -2px 0 var(--jru-primary-bright);
}

.jru-palette__results {
    max-height: 46vh;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 6px;
}

.jru-palette__item {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 8px 10px;
    border-radius: var(--jru-radius-input);
    cursor: pointer;
    font-size: 13px;
}

.jru-palette__item.is-active,
.jru-palette__item:hover {
    background: var(--jru-primary-surface);
}

/* "No screen matches that" sits where a result would, and is not one: it takes no pointer and no
   hover tint, or it lights up like a choice there is nothing behind. */
.jru-palette__item.jru-palette__empty {
    cursor: default;
}

.jru-palette__item.jru-palette__empty:hover {
    background: transparent;
}

.jru-palette__group {
    color: var(--jru-text-muted);
    font-size: 11px;
    margin-left: auto;
}

/* ------------------------------------------------------- phase placeholder */
.jru-phase {
    display: grid;
    place-items: center;
    gap: 10px;
    padding: 60px 24px;
    text-align: center;
}

/* Larger than a cell icon because it is the only thing above the heading on a screen that has
   nothing else on it. It was a 30px inline Style on the component. */
.jru-phase__icon {
    font-size: 30px;
}

.jru-phase__badge {
    font-family: var(--jru-mono);
    font-size: 11.5px;
    padding: 2px 10px;
    border-radius: 999px;
    background: var(--jru-badge-info-bg);
    color: var(--jru-badge-info-fg);
    border: 1px solid var(--jru-badge-info-bd);
}

/* ---------------------------------------------------------- account screens */
/*
 * Sign-in is the one screen with its own type scale: a 22px statement, a 20px title and 38-40px
 * controls. Everywhere else in the application the largest type is 23px on a metric and the
 * controls are 32-34px.
 */
/*
 * `dvh` after `vh`, and the safe-area insets on top of the padding.
 *
 * On a phone `100vh` is the viewport with the browser's toolbars retracted, which is taller than
 * what is on screen when the page loads: the panel sits low and the sign-in button is under the
 * toolbar until something scrolls. `dvh` is the height actually showing. The `vh` line stays as the
 * fallback for anything that does not know the unit.
 */
.jru-account {
    display: grid;
    place-items: center;
    min-height: 100vh;
    min-height: 100dvh;
    padding: calc(24px + env(safe-area-inset-top)) calc(24px + env(safe-area-inset-right))
             calc(24px + env(safe-area-inset-bottom)) calc(24px + env(safe-area-inset-left));
    background: var(--jru-page);
}

.jru-account__panel {
    width: min(420px, 100%);
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.jru-account__brand {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 15px;
    font-weight: 600;
    color: var(--jru-text);
}

/*
 * Sign-in is a page surface rather than the shell, and the mark is the logo's lime on it all the same:
 * the page shows through the roads, which is what the original does on paper.
 */
.jru-account__brand .jru-mark {
    flex: 0 0 auto;
    color: var(--jru-mark);
}

.jru-account__statement {
    margin: 0;
    font-size: 22px;
    font-weight: 600;
    letter-spacing: -0.01em;
    line-height: 1.25;
    color: var(--jru-text);
}

.jru-account__card h1,
.jru-account__card h2 {
    font-size: 20px;
    font-weight: 600;
    margin: 0 0 4px;
}

/*
 * The Identity pages are plain semantic markup with no classes of their own, so they are styled
 * from the tokens by element inside this card. They used to lean on Bootstrap, which is gone.
 */
.jru-account__card form > div {
    display: flex;
    flex-direction: column;
    gap: 5px;
    margin-bottom: 14px;
}

.jru-account__card label {
    font-size: 12.5px;
    font-weight: 500;
    color: var(--jru-text-secondary);
    text-transform: none;
    letter-spacing: 0;
}

.jru-account__card input:not([type="checkbox"]) {
    width: 100%;
    box-sizing: border-box;
    height: 38px;
    padding: 0 11px;
    border: 1px solid var(--jru-border-strong);
    border-radius: var(--jru-radius-input);
    background: var(--jru-surface);
    color: var(--jru-text);
    font-family: var(--jru-sans);
    font-size: 13.5px;
}

/* `:focus-visible`, so the ring answers the keyboard and not the pointer. The outline is dropped
   only because the two lines under it put a heavier one back. */
.jru-account__card input:not([type="checkbox"]):focus-visible {
    outline: none;
    border-color: var(--jru-primary-bright);
    box-shadow: 0 0 0 3px var(--jru-primary-ring);
}

/* The Stay signed in row: checkbox and its words on one line.
   `display: flex` is the point of the rule. A label is inline, and a flex item is blockified to
   `block` rather than to `flex`, so without this line the direction, the alignment and the gap below
   were all inert and the box sat on the text's baseline with a collapsed space beside it. */
.jru-account__card label:has(input[type="checkbox"]) {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 8px;
    color: var(--jru-text);
}

.jru-account__card input[type="checkbox"] {
    width: 15px;
    height: 15px;
    accent-color: var(--jru-primary-bright);
    margin: 0;
}

.jru-account__card button[type="submit"] {
    width: 100%;
    height: 40px;
    border: 0;
    border-radius: var(--jru-radius-control);
    background: var(--jru-primary);
    color: var(--jru-on-primary);
    font-family: var(--jru-sans);
    font-size: 13.5px;
    font-weight: 600;
    cursor: pointer;
    transition: background var(--jru-motion) var(--jru-ease);
}

.jru-account__card button[type="submit"]:hover {
    background: var(--jru-primary-hover);
}

/*
 * The browser check above Sign in (ALTCHA). It draws itself from its own custom properties, so they
 * are set here from the tokens: it follows the theme like the boxes around it and spans the card.
 */
.jru-account__botcheck {
    --altcha-color-base: var(--jru-surface);
    --altcha-color-base-content: var(--jru-text);
    --altcha-border-color: var(--jru-border-strong);
    --altcha-border-radius: var(--jru-radius-input);
    --altcha-color-primary: var(--jru-primary-bright);
    --altcha-color-primary-content: var(--jru-on-primary);
    --altcha-color-success: var(--jru-success);
    --altcha-max-width: 100%;
}

/* The card's own checkbox rule above sizes every checkbox in it to 15px, the widget's included, and
   at that size its tick has no room and it reads as a filled square. Its own size, back. */
.jru-account__botcheck .altcha-checkbox input {
    width: var(--altcha-checkbox-size);
    height: var(--altcha-checkbox-size);
}

/*
 * The development sign-in strip. It reads as a tool and not as part of the product: outlined
 * buttons on a dashed rule, muted, below the real form. It is rendered only in Development,
 * because the list that drives it is empty everywhere else.
 */
.jru-account__dev {
    margin-top: 16px;
    padding-top: 14px;
    border-top: 1px dashed var(--jru-border-strong);
}

.jru-account__dev-title {
    font-size: 10.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--jru-text-muted);
    margin-bottom: 8px;
}

/*
 * Carries the card class so it outranks the field-wrapper rule above.
 *
 * This is a direct child of a form, so `.jru-account__card form > div` matches it too - and that
 * rule is one class and two elements against this one's single class, which wins. The four buttons
 * were being laid out by the rule that stacks a label over its input: one column, 5px apart, rather
 * than the two this asks for. Nothing looked broken, which is why it survived.
 */
.jru-account__card .jru-account__dev-buttons {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
}

/* Deliberately not the primary submit above: a shortcut must not look like the way in. */
.jru-account__card .jru-account__dev-buttons button[type="submit"] {
    width: 100%;
    height: 32px;
    border: 1px solid var(--jru-border-strong);
    background: var(--jru-surface);
    color: var(--jru-text-secondary);
    font-size: 12.5px;
    font-weight: 500;
}

.jru-account__card .jru-account__dev-buttons button[type="submit"]:hover {
    background: var(--jru-hover);
    border-color: var(--jru-primary-bright);
    color: var(--jru-text);
}

.jru-account__dev-note {
    margin-top: 8px;
    font-size: 11px;
    color: var(--jru-text-muted);
}

/* A refusal says what happened. It is never a colour alone: the message names the field and what is
   wrong with it, and the red is the second signal rather than the only one.
   The record-level refusal above the form is RefusalBanner and is styled with every other banner. */
.jru-account__card .validation-message {
    color: var(--jru-danger);
    font-size: 11.5px;
}

/* --------------------------------------------------------- unhandled error */
/*
 * Blazor flips this to display:block when a circuit fails. It must default to none, or the bar
 * sits across every screen from first paint - which the scaffold's app.css used to do and this
 * file now owns.
 */
/*
 * 1700, above the feedback launcher and above Mud's toast and tooltip. At 1400 it was under a
 * dialog's own 1402, so the bar announcing that the circuit is gone was painted behind whatever was
 * open when it went - and a dialog on a dead circuit is exactly when somebody needs to be told to
 * reload.
 */
#blazor-error-ui {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1700;
    padding: 11px 16px;
    background: var(--jru-badge-fail-bg);
    color: var(--jru-badge-fail-fg);
    border-top: 1px solid var(--jru-badge-fail-bd);
    box-shadow: var(--jru-elev-sticky);
    font-size: 12.5px;
}

/* A typed amount, right-aligned in the mono face with tabular figures, the way MoneyDisplay prints
   the figure it is being checked against. Left in sans on the left, 1234.50 typed beside R 1 234.50
   drawn read as two different numbers. MudBlazor sets the face on `.mud-input > input.mud-input-root`,
   which outranks a class and an element, so the face is set at that rule's weight plus this class. */
.jru-money-input input,
.jru-money-input .mud-input > input.mud-input-root {
    text-align: right;
    font-family: var(--jru-mono);
    font-variant-numeric: tabular-nums;
}

#blazor-error-ui .dismiss {
    float: right;
    cursor: pointer;
    padding: 0 6px;
    border: 0;
    background: transparent;
    color: inherit;
    font: inherit;
}

/* ---------------------------------------------------------------- density */
/*
 * MudBlazor's defaults are taller than this console wants. Scoped classes only - never
 * !important on a global element selector.
 */
.jru-dense .mud-input-control {
    margin-top: 0;
}

.jru-dense .mud-input.mud-input-outlined .mud-input-slot {
    height: var(--jru-control-h);
    padding-top: 0;
    padding-bottom: 0;
    font-size: 13px;
}

/*
 * A select's slot is a div, not an input, and only an input centres its own single line inside a
 * height it was given. Left as a block the line sits hard against the top border: the value reads
 * as shaved along its ascenders and as hanging off the top of a box with a gap underneath it, which
 * is one defect that arrives looking like two. The line box takes the whole control height instead,
 * which centres it and keeps the block layout the ellipsis on a long value needs - a flex row here
 * would centre it and lose the truncation.
 *
 * The height and this line-height are the same number and must stay the same number, so both read
 * the one token.
 */
.jru-dense .mud-input.mud-input-outlined div.mud-input-slot {
    line-height: var(--jru-control-h);
}

/*
 * A textarea sizes itself from its own rows attribute, so the fixed height above must not reach
 * one: with it, a field asking for six lines renders as a single 34px box that clips its own
 * placeholder in the middle, and reads as a plain input somebody mislabelled. It reaches the
 * parameter value field as well as the feedback dialog.
 */
.jru-dense .mud-input.mud-input-outlined textarea.mud-input-slot {
    height: auto;
    padding-top: 7px;
    padding-bottom: 7px;

    /*
     * THE MASK IS PART OF THE PADDING AND HAS TO MOVE WITH IT. Mud hides the top of a textarea
     * behind a gradient so text scrolled up does not show through the outline's label notch, and
     * the transparent band is exactly its own padding-top - 10.5px on the dense outlined variant.
     * Shortening the padding to 7px without shortening the mask leaves 3.5px of the FIRST line
     * erased: the dot comes off every i and the tallest ascenders are shaved, which reads as a
     * font defect rather than as a clipped line. Reported against the feedback dialog's own
     * message box, which is the largest textarea in the system.
     */
    mask-image: linear-gradient(to bottom, transparent 7px, black 7px);
}

/*
 * A SWITCH IS AS WIDE AS THE SWITCH, AND THE CULPRIT IS CROSS-AXIS STRETCH RATHER THAN THE LABEL.
 * Mud's own .mud-switch and .mud-checkbox are inline-flex, so in ordinary flow - a grid cell, a
 * paragraph - they already size to themselves. Inside .jru-field they do not: that is a flex column,
 * its cross axis is the horizontal one, and align-items defaults to stretch, so the control is
 * blown out to the full width of the field.
 *
 * Measured on the capture batch panel at 1920: the visible switch is 44px and its label was 687, so
 * 640px of blank space beside the caption was a live hit area, and a click anywhere in it put the
 * batch On Hold. That is a bad field for it: a held batch silently drops out of Create Audit Batch
 * (R-28), and the hold is taken off on the batch's own screen, not here, so a stray click is a
 * batch nobody audits until somebody wonders where it went.
 *
 * fit-content rather than a fixed width, because the same control carries a caption of its own on
 * other screens and that caption has to stay clickable; max-width so a long one still wraps. It is
 * left unscoped deliberately - it is a no-op wherever the control was already shrink-to-fit, and it
 * is the same defect in any future stretch container. A tick box in a grid cell is untouched by it,
 * which is checked rather than assumed: injected into a real cell it measures 24px either way.
 */
.mud-input-control-boolean-input,
.mud-input-control-boolean-input > .mud-input-control-input-container,
label.mud-switch,
label.mud-checkbox {
    width: fit-content;
    max-width: 100%;
}

/*
 * A LABELLED BUTTON, AND NOTHING THAT MERELY SHARES ITS CLASS. Mud puts .mud-button-root on the
 * ripple base inside a switch and a checkbox as well as on a real button, so an unqualified
 * min-height here stretched a 24px switch base to 32px: the ripple is a circle on a box that is
 * now taller than it is wide, so pressing On Hold painted a dark blob hanging below the track.
 * Reported against the capture batch panel and it was every switch in the system.
 */
.jru-dense .mud-button-root:not(.mud-icon-button) {
    min-height: 32px;
    font-size: 12.5px;
    text-transform: none;
    letter-spacing: 0;
}

/*
 * A SWITCH LINES UP WITH THE CONTROLS ABOVE IT. Mud pads the switch's span by 7px to give the thumb
 * its travel, so the visible track started 7px inside the field's left edge while every input,
 * picker and read-only value in the same column started at the edge. The control is pulled back by
 * exactly that padding rather than having it removed, because the padding is what the thumb moves
 * within.
 *
 * Moved by position, never by a negative margin. The control is fit-content, and a margin of -7px
 * makes its container 7px narrower than the label needs, so a switch carrying its own caption wraps
 * its last word onto a second line.
 */
.jru-field .mud-switch {
    position: relative;
    left: -7px;
}

/*
 * A disabled button recedes. Mud paints its own background on one, and with the palette
 * unbridged that background was near-black with the label lost inside it.
 */
.mud-button-root.mud-button-disabled,
.mud-button-root:disabled {
    background-color: transparent;
    border-color: var(--jru-border);
    color: var(--jru-text-muted);
    box-shadow: none;
}

.mud-button-root.mud-button-disabled:hover,
.mud-button-root:disabled:hover {
    background-color: transparent;
}

/* No entrance animation on page load: operators re-enter these screens hundreds of times
   a day. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 1ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 1ms !important;
    }
}

/*
 * The one carve-out from the rule above, and the trade is deliberate: a loader that stops moving
 * altogether reads as a page that has stalled, which is the exact thing it exists to disprove. It is
 * apparent travel round the mark rather than a change of brightness that makes people ill, so the
 * disc breathes as one instead of turning blade by blade.
 *
 * !important is required to beat the blanket rule, and the higher specificity is what wins the tie.
 */
@media (prefers-reduced-motion: reduce) {
    .jru-loader__mark {
        animation: jru-loader-breathe 2400ms ease-in-out infinite !important;
    }

    .jru-loader__mark .jru-mark__blade {
        animation: none !important;
    }
}

/* Paper has no waits, and a screen printed mid-read would carry one across the page. */
@media print {
    .jru-loader {
        display: none;
    }
}

/* ==========================================================================
 * FEEDBACK - the overlay, the capture surface, and the console that reads them
 *
 * The launcher is mounted by MainLayout, so this is the shell's stylesheet and not one screen's.
 * The capture surface is built by screen-capture.js and styled from here, because there is nowhere
 * else a colour is allowed to be; it sits above everything the application draws, since the whole
 * point is to select over the top of it.
 *
 * No hard-coded hex outside the token set, one accent, depth by border. Same rules as everything
 * above. The three rgba values here are the scrim, the band fill and its outer wash, laid over
 * whatever the operator is photographing rather than over a themed surface.
 * ========================================================================== */

/* The launcher is a bare 34px square in the bottom-right corner, and that is a decision rather than
   a default: a wider fixture there covers a screen's primary action and takes the click aimed at it.
   Below the error UI, above everything else. */
/*
 * ABOVE EVERY MODAL, DELIBERATELY. At 1150 it sat under the slide-over scrim (1200) and under
 * MudBlazor's dialog stack (container 1400, overlay 1401, dialog 1402), so the one moment an
 * operator most wants to report something - a panel or a dialog is in front of them doing the wrong
 * thing - was the one moment the button could not be clicked. Nothing said so: it was drawn, at 65%
 * opacity, with an overlay swallowing the click.
 *
 * The order this sits in, top down: the capture overlay (3000) is above everything because it
 * photographs it; the unhandled-error bar (1700) is above this because a dead circuit cannot send
 * feedback anyway; a toast (Mud's 1500) is above it so a message is never hidden behind it; then
 * this, then every dialog, panel and scrim below.
 */
.jru-fb-launcher {
    position: fixed;
    right: 12px;
    bottom: 12px;
    z-index: 1450;
}

.jru-fb-launcher__button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    padding: 0;
    background: var(--jru-surface);
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-control);
    box-shadow: var(--jru-elev-2);
    color: var(--jru-text-secondary);
    cursor: pointer;

    /* It fades back until it is wanted. It is a permanent fixture on every screen, and an
       always-full-contrast square in the corner reads as an unread badge. */
    opacity: 0.65;
    transition: opacity 120ms ease, border-color 120ms ease, color 120ms ease;
}

.jru-fb-launcher__button:hover,
.jru-fb-launcher__button:focus-visible {
    opacity: 1;
    border-color: var(--jru-primary-bright);
    color: var(--jru-primary-bright);
}

/* The word is still in the DOM for a screen reader; only the sighted layout drops it. Removing it
   outright would leave an icon-only control with no accessible name. */
.jru-fb-launcher__label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/*
 * Toasts land bottom right, and the launcher is already there. Mud stacks them from the corner
 * upwards, so without this the first toast of a run sits exactly on top of the button - which is
 * how a fixture that is always present ends up covered by the one thing that appears when something
 * has just happened. The offset clears the button's 34px and its 12px inset.
 *
 * Stated here rather than as a snackbar option because it is a measurement of the launcher, and the
 * two numbers have to move together.
 */
.mud-snackbar-location-bottom-right {
    padding-bottom: 46px;
}

/* Chrome that floats over the application must never reach paper, and a print rule is the right
   instrument: a list of printable screens maintained by hand would not include the next one added. */
@media print {
    .jru-fb-launcher {
        display: none;
    }
}

/* Hidden for the duration of a capture: the dialog asking for the picture sits on top of the thing
   being photographed. Visibility rather than display, so nothing reflows and the screen is captured
   exactly as the operator had it. */
.jru-fb-hidden {
    visibility: hidden !important;
}

.jru-fb-capture {
    position: fixed;
    inset: 0;
    z-index: 3000;
    cursor: crosshair;
    background: rgba(38, 38, 38, 0.28);
    touch-action: none;
    user-select: none;
}

.jru-fb-capture__hint {
    position: absolute;
    top: 28px;
    left: 50%;
    transform: translateX(-50%);
    padding: 8px 14px;
    background: var(--jru-surface);
    border: 1px solid var(--jru-primary-bright);
    border-radius: var(--jru-radius-control);
    color: var(--jru-text);
    font: 400 13px/1.4 var(--jru-sans);
    white-space: nowrap;
}

/* The rubber band. A thin stroke, and the area inside it is left clear so the thing being selected
   stays visible while it is selected. */
.jru-fb-capture__band {
    position: absolute;
    border: 1px solid var(--jru-primary-bright);
    background: rgba(185, 204, 4, 0.10);
    box-shadow: 0 0 0 9999px rgba(38, 38, 38, 0.10);
    pointer-events: none;
}

.jru-fb-capture__size {
    position: absolute;
    padding: 2px 6px;
    background: var(--jru-primary);
    border-radius: 3px;
    color: var(--jru-on-primary);
    font: 500 11px/1.4 var(--jru-mono);
    pointer-events: none;
}

/* the dialog -------------------------------------------------------------- */

.jru-fb-context {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

/* A screen URL is long and unbreakable and must not widen the dialog. */
.jru-fb-context .jru-code {
    overflow-wrap: anywhere;
}

.jru-fb-prior-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 8px 10px;
    background: var(--jru-surface-alt);
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-input);
}

.jru-fb-prior {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 0;
    border-top: 1px solid var(--jru-border-soft);
    min-width: 0;
}

.jru-fb-prior:first-of-type {
    border-top: 0;
}

/* The capture button and its advice sit on one row. Without the flex basis the button shrinks to
   its min-content and "Capture an area" wraps onto two lines. */
.jru-fb-actions {
    display: flex;
    align-items: flex-start;
    gap: 10px;
}

.jru-fb-actions > .mud-button-root {
    flex: 0 0 auto;
    white-space: nowrap;
}

.jru-fb-thumbs {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 10px;
}

.jru-fb-thumb {
    position: relative;
    width: 132px;
    padding: 4px;
    background: var(--jru-surface-alt);
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-control);
}

.jru-fb-thumb img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: 3px;
}

.jru-fb-thumb__remove {
    position: absolute;
    top: -8px;
    right: -8px;
    width: 22px;
    height: 22px;
    background: var(--jru-surface);
    border: 1px solid var(--jru-border-strong);
    border-radius: 50%;
    color: var(--jru-text-secondary);
    font: 500 12px/1 var(--jru-sans);
    cursor: pointer;
}

.jru-fb-thumb__remove:hover {
    border-color: var(--jru-danger);
    color: var(--jru-danger);
}

.jru-fb-thumb__meta {
    display: block;
    padding-top: 4px;
    color: var(--jru-text-muted);
    font-size: 10px;
    letter-spacing: 0.04em;
}

/* the console ------------------------------------------------------------- */

.jru-fb-pager {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 12px;
    padding: 8px 12px;
    border-top: 1px solid var(--jru-border);
}

.jru-fb-detail__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
}

/* What the operator wrote, kept as they wrote it: a report is often a numbered list, and flattening
   the line breaks makes one unreadable. */
.jru-fb-message {
    margin: 0;
    white-space: pre-wrap;
    /* A pasted address has no break opportunity, and the facts beside it already break anywhere. */
    overflow-wrap: anywhere;
    font-size: 13px;
    color: var(--jru-text);
}

/* A note on the item keeps the line breaks it was typed with, and breaks a long address. */
.jru-fb-note {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* A screen URL and a browser string are both long and unbreakable, and a dl inside a dialog has
   nowhere to put the overflow. */
.jru-fb-facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 190px), 1fr));
    gap: 10px 16px;
    margin: 0;
}

.jru-fb-facts dt {
    color: var(--jru-text-secondary);
    font-size: 10.5px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.07em;
}

.jru-fb-facts dd {
    margin: 0;
    font-size: 12.5px;
    overflow-wrap: anywhere;
}

.jru-fb-shots {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 240px), 1fr));
    gap: 12px;
    margin-top: 6px;
}

.jru-fb-shot {
    margin: 0;
    min-width: 0;
}

.jru-fb-shot img {
    display: block;
    width: 100%;
    height: auto;
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-control);
}

.jru-fb-shot figcaption {
    padding-top: 4px;
    font-size: 11px;
}

.jru-fb-timeline {
    margin: 6px 0 0;
    padding: 0;
    list-style: none;
}

.jru-fb-timeline__item {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 8px 0 8px 12px;
    border-left: 2px solid var(--jru-border-strong);
    font-size: 12.5px;
}

/* A transition nobody typed, drawn quietly: it is context, not conversation. */
.jru-fb-timeline__item--system {
    border-left-color: var(--jru-border);
    color: var(--jru-text-secondary);
}

/* OUR SIDE OF THE CONVERSATION. The answer and the retest line carry the primary colour, so the
   thread reads as two voices without a label on every line. */
.jru-fb-timeline__item--ours {
    border-left: 3px solid var(--jru-primary);
    background: var(--jru-surface-alt);
    padding-right: 12px;
}

/* Plain prose at reading size. It may run to several paragraphs, so the dialog scrolls rather than
   the text, and it takes no monospace: this is the one thing on the dialog written to be read rather
   than scanned. */
.jru-fb-answer__text {
    margin: 4px 0 0;
    font-size: 13px;
    line-height: 1.55;
    white-space: pre-wrap;
}

/* Our own record, folded. The summary carries the field label's weight so it reads as a heading
   rather than as a control. */
.jru-fb-history > summary {
    cursor: pointer;
}

.jru-fb-history[open] > summary {
    margin-bottom: 4px;
}

.jru-fb-prior__answer {
    padding: 6px 0 2px 12px;
    border-left: 3px solid var(--jru-primary);
}

.jru-fb-prior__reply {
    padding: 6px 0 2px;
}

.jru-fb-prior__replyactions {
    display: flex;
    justify-content: flex-end;
    gap: 6px;
    padding-bottom: 4px;
}

/* The board's rows are two lines, the reporter's name under the date, so the cells get air above and
   below. With none, a two-line report touched the hairline on both sides. */
.jru-fb-board table.jru-grid tbody td {
    padding-block: 5px;
}

/* What was said, clamped at the two lines the row already is, with the attachments on the right. */
.jru-fb-said {
    display: flex;
    align-items: flex-start;
    gap: 12px;
}

.jru-fb-said__words {
    flex: 1 1 auto;
    min-width: 0;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    overflow-wrap: anywhere;
}

.jru-fb-said__extras {
    flex: none;
    display: flex;
    gap: 8px;
    white-space: nowrap;
}

.jru-fb-screen {
    max-width: 260px;
}

/* SOMEBODY IS WAITING. It sits in front of the words on the board row rather than at the end of it,
   because the message is clamped and anything after it can fall off. It is inline, on the words'
   first line: as a block of its own it wrapped above them and read as the row before's. */
.jru-fb-replied {
    display: inline-block;
    margin-right: 6px;
    line-height: 1.35;
    padding: 0 6px;
    border-radius: 999px;
    background: var(--jru-primary);
    color: var(--jru-on-primary);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Something changed on the reader's own item. Outlined, so it is not read as Replied, which is ours
   to act on; this one is theirs to read. */
.jru-fb-replied--changed {
    background: transparent;
    box-shadow: inset 0 0 0 1px var(--jru-mark);
    color: var(--jru-text);
}

.jru-fb-clientlog__box {
    padding: 8px 10px;
    background: var(--jru-surface-alt);
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-input);
}

/* The browser's own record. IT SCROLLS IN ITS OWN BOX rather than widening the dialog: what sits
   here is stack traces and request URLs, both long single lines with no break opportunity, and a pre
   that grows to its content pushes the whole dialog off the screen. Wrapping instead would be worse,
   because a wrapped trace loses the column alignment that makes a frame list readable at a glance. */
.jru-fb-clientlog {
    max-height: 320px;
    margin: 8px 0 0;
    padding: 8px;
    overflow: auto;
    background: var(--jru-page);
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-input);
    color: var(--jru-text-secondary);
    font-family: var(--jru-mono);
    font-size: 11px;
    line-height: 1.5;
    white-space: pre;
    tab-size: 2;
}

/* One action and its hint, laid out like a field but not one: there is no control to caption, so
   it does not wear .jru-field, which is JruField's to write. The button is a column flex item and
   would stretch to the width, so it sizes to its own label. */
.jru-fb-open {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.jru-fb-open > .mud-button-root {
    align-self: flex-start;
}

/* ------------------------------------------------- grid: sorting and windows */
/*
 * A sortable heading is a button inside the th, not a clickable th. The th keeps aria-sort, which
 * is where a screen reader looks for it; the button is what takes focus and the focus ring, which
 * a th cannot be given without outlining the whole sticky cell.
 */
.jru-grid__sort {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    cursor: pointer;
}

.jru-grid__sort:hover {
    color: var(--jru-text);
}

.jru-grid__sort:focus-visible {
    outline: 2px solid var(--jru-primary-bright);
    outline-offset: 2px;
    border-radius: var(--jru-radius-control);
}

table.jru-grid thead th.is-sorted {
    color: var(--jru-text);
}

/*
 * The caret holds its width whether or not it is drawn, so turning a sort on does not shift the
 * heading beside it by a character.
 */
.jru-grid__sort-mark {
    display: inline-block;
    min-width: 8px;
    font-size: 9px;
    color: var(--jru-primary-bright);
}

/*
 * A window still in flight. The scrollbar has already promised a row here, so the placeholder is a
 * row of the same height rather than a sentence: prose in its place reads as content that arrived
 * and happens to say Loading.
 */
.jru-grid__pending td {
    height: var(--jru-control-h);
    padding: 0 10px;
}

.jru-grid__pending-bar {
    display: block;
    width: 40%;
    height: 8px;
    border-radius: var(--jru-radius-control);
    background: var(--jru-border-soft);
    animation: jru-grid-pending 1.1s var(--jru-ease) infinite;
}

@keyframes jru-grid-pending {
    0%, 100% { opacity: 0.45; }
    50%      { opacity: 0.9; }
}

@media (prefers-reduced-motion: reduce) {
    .jru-grid__pending-bar {
        animation: none;
    }
}

/* ------------------------------------------------------------ grid: toolbar */
/*
 * The search takes the left and stops growing; everything else is pushed to the far end. Ten
 * screens spaced this with an inline `flex:1 1 auto` div, which is one layout decision copied ten
 * times and owned nowhere.
 */
.jru-toolbar__search {
    flex: 0 1 320px;
    min-width: 0;
}

.jru-toolbar__controls {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-left: auto;
    flex-wrap: wrap;
}

.jru-search {
    width: 100%;
}

/*
 * TWO CLEAR CROSSES IN ONE BOX, AND THE BROWSER'S IS THE ONE THAT GOES.
 *
 * `type="search"` is what buys the searchbox role and the search keyboard on a phone, and it brings
 * a clear cross of Chromium's own with it. The box already has one - Mud's `Clearable` - so without
 * this there are two, a hand's width apart, doing the same thing. That is precisely what the lookup
 * dialog's own note refuses about stacking two crosses in one column, and there it was two DIFFERENT
 * meanings; here it is the same meaning twice, which is no better.
 *
 * Mud's is the one kept: it is the one every other search in the product has, and it is the one the
 * component's own debounce and value handling are wired to.
 */
.jru-search input::-webkit-search-cancel-button {
    display: none;
}

/*
 * A search box sitting under a column heading rather than over a list. It is the same control with
 * the same debounce: what it loses is the magnifier and the room the magnifier took, because the
 * heading directly above it already says what the box narrows.
 */
.jru-search--column .mud-input.mud-input-outlined .mud-input-slot {
    height: 26px;
    font-size: 12px;
}

.jru-search--column .mud-input-adornment .mud-icon-button {
    padding: 2px;
}

.jru-search--column .mud-icon-root {
    font-size: 15px;
}

/*
 * The filter row is part of the heading, not the first row of data. It stays stuck to the top with
 * the headings above it, and it is the surface colour rather than the row colour so the boxes read
 * as chrome an operator types into and never as a record.
 */
table.jru-grid thead tr.jru-grid__filters th,
table.jru-grid thead tr.jru-grid__filters td {
    position: sticky;
    top: var(--jru-grid-head-h);
    z-index: 1;
    background: var(--jru-surface-alt);
    border-bottom: 1px solid var(--jru-border);
    padding: 4px 8px 6px;
    vertical-align: middle;
}

/*
 * The Filters trigger. It is the toolbar's own control rather than one a screen supplies, so it is
 * styled here beside the row it belongs to and not as a variant of `.jru-chip`: a chip is one option
 * among several and this opens a region. Lit while the region is open, which is what tells somebody
 * the panel below belongs to the button they just pressed.
 */
.jru-toolbar__filters {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 28px;
    padding: 0 11px;
    border-radius: 6px;
    border: 1px solid var(--jru-border);
    background: var(--jru-surface);
    color: var(--jru-text-secondary);
    font: inherit;
    font-size: 11.5px;
    cursor: pointer;
    transition: background var(--jru-motion) var(--jru-ease),
                border-color var(--jru-motion) var(--jru-ease);
}

.jru-toolbar__filters:hover {
    background: var(--jru-hover);
}

.jru-toolbar__filters.is-on {
    background: var(--jru-primary-surface);
    border-color: var(--jru-primary-bright);
    color: var(--jru-primary-bright);
}

/*
 * How many filters are in force, on the closed button. The applied row says which ones; this says
 * that there are some, which is the half a reader needs while the panel is shut.
 *
 * `--jru-primary` on `--jru-on-primary`, and never the bright token with a label of its own choosing.
 * The pair is the charcoal label on the lime, 9.70:1 at 10px, and white on the lime would be 1.79:1.
 * It is the pair every other filled surface here uses - the sign-in button and the capture size tag -
 * and the bright token stays what it is for: text, an outline, and a border.
 */
.jru-toolbar__filters-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: 999px;
    background: var(--jru-primary);
    color: var(--jru-on-primary);
    font-family: var(--jru-mono);
    font-size: 10px;
}

/*
 * The filters panel: a region of the card that makes it taller, never a floating surface. The main
 * pane clips its overflow, so a popover here would need fixed positioning and a place in the layer
 * order to earn nothing - the rows below simply move down.
 *
 * It is `--alt` coloured so the open panel reads as chrome rather than as the first band of data,
 * and it carries the row gap the toolbar has because its content is labelled fields rather than
 * controls on one line.
 */
.jru-card__toolbar--filters {
    display: block;
    background: var(--jru-surface-alt);
    padding: 14px 15px;
}

.jru-filters {
    display: flex;
    flex-wrap: wrap;
    gap: 18px;
    align-items: flex-start;
}

/*
 * One narrowing inside the panel: its name above it, so a control that is not self-describing says
 * what it narrows. On the row above, a chip group needs no label because its options are the label.
 */
.jru-filters__field {
    display: flex;
    flex-direction: column;
    gap: 7px;
    min-width: 0;
}

.jru-filters__label {
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--jru-text-muted);
}

/* A select inside the Filters panel, wide enough for the longest record type it offers. */
.jru-filters__select {
    min-width: 230px;
}

/*
 * The applied row is a second row of the toolbar region, not a second toolbar. It carries no
 * bottom border of its own: the region it closes already has one.
 */
.jru-card__toolbar--applied {
    padding-top: 0;
}

.jru-applied {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    flex-wrap: wrap;
}

.jru-applied__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 7px;
}

/*
 * A removable chip reads as on, because it only exists while its filter is in force. That is the
 * difference from `.jru-chip`, which is a choice among several and is drawn whether or not it is
 * the one selected.
 */
.jru-applied__chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 24px;
    padding: 0 9px;
    border-radius: 999px;
    border: 1px solid var(--jru-primary-bright);
    background: var(--jru-primary-surface);
    color: var(--jru-primary-bright);
    font-size: 11.5px;
    cursor: pointer;
    transition: background var(--jru-motion) var(--jru-ease);
}

.jru-applied__chip:hover {
    background: var(--jru-hover);
}

.jru-applied__chip-x {
    font-size: 13px;
    line-height: 1;
}

.jru-applied__end {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-left: auto;
}

.jru-applied__clear {
    padding: 0;
    border: 0;
    background: none;
    color: var(--jru-primary-bright);
    font: inherit;
    font-size: 11.5px;
    cursor: pointer;
    text-decoration: underline;
}

.jru-applied__note {
    color: var(--jru-text-muted);
    font-size: 11.5px;
}

/* A line under a filter bar saying how far its search reached, where it reached less than the box
   promises: the archive's search leaves the values out across a wide window. */
.jru-searchreach {
    font-size: 12px;
    padding: 6px 16px 0;
}

/* ---------------------------------------------------------- grid: filter bar */
/*
 * THE ROW THAT NARROWS A LIST, AND IT IS A REGION OF THE CARD IT NARROWS.
 *
 * Search left, the trigger beside it, the tokens in force, and what the narrowing found at the far
 * right. It replaces `.jru-card__toolbar` on a converted screen rather than sitting beside it: a
 * card with both would be asking the same question in two places.
 *
 * THE SHAPE GRAMMAR IS THE WHOLE POINT. Square-cornered and unfilled is a statement about the data,
 * which is every token here. Round and lime-filled is a choice that is on, and exactly one thing on
 * a card may wear it - the active view, in the heading band. Nothing on this row is round.
 */
.jru-filterbar {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 10px 15px;
    border-bottom: 1px solid var(--jru-border-soft);
    flex-wrap: wrap;
}

.jru-filterbar__search {
    flex: 0 1 320px;
    min-width: 0;
}

/*
 * MudBlazor's dense input carries 4px above and below, so the search box measured 42px against a
 * 32px button and every other thing on the row sat 5px high of the box's centre line. The row is
 * top-aligned on purpose - a wrapped token block must not drag the search box down the card - so
 * the alignment has to come from the controls being the same height, not from centring them.
 */
.jru-filterbar__search .mud-input-control {
    margin-top: 0;
    margin-bottom: 0;
}

/*
 * The popover anchors to this. MudPopover positions itself against its nearest positioned ancestor,
 * so without this the menu lands against the page and not against the button that opened it.
 */
.jru-filterbar__anchor {
    position: relative;
    flex: none;
}

/*
 * Dashed until it is doing something, which is how it reads as "add one" rather than as a control
 * with a state. It takes the token's square corner because what it produces is a token.
 */
.jru-filterbar__add {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    /* 34px: the height of the search box beside it, so the row has one centre line. */
    height: 34px;
    padding: 0 12px;
    border: 1px dashed var(--jru-border-strong);
    border-radius: 4px;
    background: transparent;
    color: var(--jru-text-muted);
    font: inherit;
    font-size: 12.5px;
    cursor: pointer;
}

.jru-filterbar__add:hover {
    background: var(--jru-hover);
    color: var(--jru-text);
    border-style: solid;
}

.jru-filterbar__add.is-on {
    border-style: solid;
    border-color: var(--jru-primary-bright);
    background: var(--jru-primary-surface);
    color: var(--jru-primary-bright);
}

/*
 * What makes a click anywhere else close the menu. Transparent on purpose: a dimmed page says a
 * modal has opened, and this is a menu. It sits just under MudBlazor's popover layer so the menu
 * itself stays clickable.
 */
.jru-filterbar__scrim {
    position: fixed;
    inset: 0;
    z-index: 1199;
}

.jru-filterbar__tokens {
    display: flex;
    align-items: center;
    gap: 7px;
    flex-wrap: wrap;
    flex: 1 1 260px;
    min-width: 0;
    min-height: 34px;
}

.jru-filterbar__end {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-left: auto;
    height: 34px;
    flex: none;
}

/*
 * The narrowing's own feedback. The denominator is what the list holds, never the filtered total -
 * that reads "n of n" and says nothing.
 */
.jru-filterbar__result {
    color: var(--jru-text-muted);
    font-size: 12.5px;
    white-space: nowrap;
}

.jru-filterbar__result b {
    font-family: var(--jru-mono);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: var(--jru-text);
}

.jru-filterbar__clear {
    padding: 4px 2px;
    border: 0;
    background: none;
    color: var(--jru-primary-bright);
    font: inherit;
    font-size: 12.5px;
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
}

.jru-filterbar__clear:hover {
    color: var(--jru-text);
}

/*
 * ONE NARROWING, AS A SENTENCE. Property and operator in quiet sans, value in mono ink: two views of
 * one list differ in their values, not their properties, so the value is what the eye lands on.
 *
 * TWO TARGETS. The body opens this property's editor and the cross removes it, because changing a
 * filter and dropping it are different intentions.
 */
.jru-filtertoken {
    display: inline-flex;
    align-items: stretch;
    border: 1px solid var(--jru-border-strong);
    border-radius: 4px;
    background: var(--jru-surface);
    overflow: hidden;
    max-width: 100%;
}

.jru-filtertoken__body {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 30px;
    padding: 0 9px;
    border: 0;
    background: transparent;
    color: var(--jru-text-muted);
    font: inherit;
    font-size: 12.5px;
    cursor: pointer;
    min-width: 0;
}

.jru-filtertoken__body:hover {
    background: var(--jru-hover);
}

.jru-filtertoken__prop,
.jru-filtertoken__op {
    color: var(--jru-text-muted);
    white-space: nowrap;
}

.jru-filtertoken__val {
    font-family: var(--jru-mono);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    font-weight: 500;
    color: var(--jru-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Several values collapse to the first plus a count. A comma list pushes the row to three lines. */
.jru-filtertoken__more {
    font-family: var(--jru-mono);
    font-size: 11px;
    color: var(--jru-text-muted);
    flex: none;
}

.jru-filtertoken__x {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    border: 0;
    border-left: 1px solid var(--jru-border);
    background: transparent;
    color: var(--jru-text-muted);
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
    flex: none;
}

.jru-filtertoken__x:hover {
    background: var(--jru-badge-fail-bg);
    color: var(--jru-danger);
}

/* ------------------------------------------------- grid: the filter popover */
/*
 * Rendered through MudPopoverProvider, which sits at the layout root. The main pane clips its
 * overflow, so a floating surface written inside it is cut off with nothing in the console to say
 * why. `Paper="false"` on the popover is what leaves the surface to these rules rather than to
 * MudBlazor's own card.
 */
.jru-filterpop {
    /*
     * The menu sat with its top edge exactly on the button's bottom edge, so the button's lit border
     * read as the menu's own top rule and the two became one shape.
     */
    margin-top: 6px;
    width: 320px;
    max-width: calc(100vw - 32px);
    background: var(--jru-surface);
    border: 1px solid var(--jru-border-strong);
    border-radius: 6px;
    box-shadow: var(--jru-elev-2);
    overflow: hidden;
}

.jru-filterpop__head {
    display: flex;
    align-items: center;
    gap: 8px;
    /*
     * 13px, not 11px: the list's own 5px plus an option's 8px puts every label and group heading
     * 13px from the edge, and a head 2px to the left of them is the drift that reads as untidy.
     */
    padding: 9px 13px;
    border-bottom: 1px solid var(--jru-border);
    background: var(--jru-surface-alt);
    font-size: 12px;
    color: var(--jru-text-muted);
}

.jru-filterpop__title {
    color: var(--jru-text);
    font-weight: 500;
}

.jru-filterpop__back {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--jru-primary-bright);
    font: inherit;
    font-size: 12px;
    cursor: pointer;
}

.jru-filterpop__list {
    /* Tall enough that a six-property schema does not scroll. A checklist whose options come from
       the data can still run past it, which is what the cap is for. */
    max-height: 380px;
    overflow-y: auto;
    padding: 5px;
}

/* The box that narrows a long checklist. On the list's own 13px edge, so it lines up with the
   options under it. */
.jru-filterpop__narrow {
    padding: 9px 13px 3px;
}

/* A long list narrowed to nothing says so, rather than leaving an empty panel above Apply; a very
   long one says how many options it has not drawn. */
.jru-filterpop__empty,
.jru-filterpop__more {
    padding: 9px 8px;
    font-size: 12.5px;
    color: var(--jru-text-muted);
}

/* The question the properties under it answer, which is how a menu gets scanned rather than read. */
.jru-filterpop__group {
    padding: 8px 8px 4px;
    font-size: 11px;
    color: var(--jru-text-muted);
    letter-spacing: 0.02em;
}

/*
 * A heading that follows an option has to separate from it. Without this the space above a group
 * matched the space between two options, and the groups stopped grouping anything.
 */
.jru-filteropt + .jru-filterpop__group {
    margin-top: 7px;
}

.jru-filteropt {
    display: flex;
    align-items: center;
    /* Stated, not left to the default. An option whose count happens to be absent has nothing
       pushing it to the far edge, and without this it centred itself and sat a centimetre to the
       right of the options above it. */
    justify-content: flex-start;
    gap: 9px;
    width: 100%;
    padding: 7px 8px;
    border: 0;
    border-radius: 5px;
    background: transparent;
    color: var(--jru-text);
    font: inherit;
    font-size: 13px;
    text-align: left;
    cursor: pointer;
}

.jru-filteropt:hover,
.jru-filteropt:focus-visible {
    background: var(--jru-hover);
}

.jru-filteropt:disabled {
    opacity: 0.45;
    cursor: default;
}

.jru-filteropt__label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.jru-filteropt__note {
    margin-left: auto;
    font-size: 11.5px;
    color: var(--jru-text-muted);
    flex: none;
}

/* A count is what choosing this option gives you. An option with no count shows none. */
.jru-filteropt__count {
    margin-left: auto;
    font-family: var(--jru-mono);
    font-size: 11.5px;
    font-variant-numeric: tabular-nums;
    color: var(--jru-text-muted);
    flex: none;
}

/*
 * A ticked box is FILLED, not tinted. A near-white wash reads as an outline, which is the defect
 * that started the whole shape grammar.
 */
.jru-filteropt__box {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 15px;
    height: 15px;
    border: 1px solid var(--jru-border-strong);
    border-radius: 3px;
    font-size: 11px;
    line-height: 1;
    color: var(--jru-on-primary);
    flex: none;
}

.jru-filteropt[aria-checked="true"] .jru-filteropt__box {
    background: var(--jru-primary);
    border-color: var(--jru-primary-hover);
}

.jru-filterpop__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    /* The same 13px the head and every row use. */
    padding: 9px 13px;
    border-top: 1px solid var(--jru-border);
    background: var(--jru-surface-alt);
}

.jru-filterpop__hint {
    font-size: 11.5px;
    color: var(--jru-text-muted);
}

.jru-filterpop__fields {
    display: flex;
    flex-direction: column;
    gap: 11px;
    padding: 11px;
}

.jru-filterpop__periods {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.jru-filterpop__pair {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 9px;
}

.jru-filterpop__field {
    display: flex;
    flex-direction: column;
    gap: 5px;
    min-width: 0;
}

.jru-filterpop__fieldlabel {
    font-size: 11.5px;
    color: var(--jru-text-muted);
}

/*
 * A named period is a shortcut that fills the two boxes, so it is a momentary choice rather than a
 * filter: it may be round and filled while it is the one selected without competing with the active
 * view, because it lives inside a popover and not on the card.
 */
.jru-filterperiod {
    height: 26px;
    padding: 0 10px;
    border: 1px solid var(--jru-border);
    border-radius: 999px;
    background: var(--jru-surface);
    color: var(--jru-text-muted);
    font: inherit;
    font-size: 12px;
    cursor: pointer;
}

.jru-filterperiod:hover {
    background: var(--jru-hover);
    color: var(--jru-text);
}

.jru-filterperiod[aria-pressed="true"] {
    background: var(--jru-primary);
    border-color: var(--jru-primary-hover);
    color: var(--jru-on-primary);
    font-weight: 600;
}

/* The date field wraps its picker only to catch focusout; the wrapper must not be a box. */
.jru-datefield {
    display: contents;
}

/*
 * A DATE BOX SHOWS THE WHOLE DATE, WHATEVER SITS BESIDE IT. Mud's input is width 100% with a
 * min-width of 0, so every adornment is taken out of the date: the plan and attorney add rows carry a
 * clear cross as well as the calendar, and at 1536 wide their From and To read "24-02-2" and
 * "31-12-2". The floor is on the text itself, in ch so it follows the font, and the box grows around
 * it rather than the date shrinking inside it. Ten characters of date and half of one to spare;
 * content-box, as Mud's input is, so the padding is on top.
 */
.jru-datefield input.mud-input-root {
    min-width: 10.5ch;
}

/*
 * TEXT THAT IS NOT A DATE: A RED BOX AND THE REASON UNDER IT. Drawn by the date field itself rather
 * than through Mud's Error parameter, which the picker resets on its own validation. Mud's own
 * "Not a valid date time" is hidden while the reason shows, so the box says one thing.
 */
.jru-datefield--problem .mud-input-outlined-border {
    border-color: var(--jru-danger) !important;
}

/* Important because Mud's helper carries d-flex, whose display is itself important. */
.jru-datefield--problem .mud-input-helper-text {
    display: none !important;
}

.jru-datefield__problem {
    margin-top: 3px;
    color: var(--jru-danger);
    font-size: 0.75rem;
    line-height: 1.3;
}

/*
 * MAIL STAGING: THE SWITCH, THE QUEUE AND THE MAIL AS IT WILL ARRIVE.
 *
 * THE SWITCH IS A STATEMENT, NOT A SETTINGS ROW. It is the one fact that decides whether anything on
 * the screen matters, so it reads as a sentence in the heading's weight with the mark beside it: a
 * lime disc and a lime rule down the card's edge while mail is held, the untinted grey of anything
 * at rest while it is not. The toggle alone reads the same on and off to anybody who does not know
 * which side is which, and a colour alone does not survive greyscale - the sentence carries both.
 *
 * THE LIME IS A FILL AND A MARKER, NEVER A WASH. The disc is lime with the charcoal glyph, as every
 * lime fill is, and the rule is the marker the active menu item wears. Nothing is tinted with it.
 */
.jru-mailgate {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 18px 14px 16px;
    border-left: 4px solid var(--jru-border);
}

.jru-mailgate.is-on {
    border-left-color: var(--jru-primary);
}

.jru-mailgate__mark {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    border: 1px solid var(--jru-border);
    background: var(--jru-surface-alt);
    color: var(--jru-text-muted);
    transition: background-color var(--jru-motion) var(--jru-ease), color var(--jru-motion) var(--jru-ease);
}

.jru-mailgate.is-on .jru-mailgate__mark {
    border-color: var(--jru-primary);
    background: var(--jru-primary);
    color: var(--jru-on-primary);
}

.jru-mailgate__text {
    flex: 1 1 auto;
    min-width: 0;
}

.jru-mailgate__title {
    margin: 0;
    font-size: 15px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--jru-text);
}

.jru-mailgate__detail {
    margin: 2px 0 0;
    max-width: 78ch;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--jru-text-secondary);
}

.jru-mailgate__control {
    flex: 0 0 auto;
}

@media (max-width: 760px) {
    .jru-mailgate {
        flex-wrap: wrap;
    }

    .jru-mailgate__control {
        flex-basis: 100%;
        padding-left: 52px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .jru-mailgate__mark {
        transition: none;
    }
}

/* The subject is the second line of a record: read, not scanned, so it steps back from the purpose. */
.jru-recordlist__line > .jru-mailstage__subject {
    font-weight: 400;
    color: var(--jru-text-secondary);
}

.jru-mailstage__to {
    overflow-wrap: anywhere;
}

/*
 * THE OPEN MAIL IS AN ENVELOPE OVER A LETTER. The body scrolls on its own under the heading, so
 * Release and Discard never leave the screen while the mail is being read. The envelope is a mail
 * client's header - the labels a quiet column, the values in the reading colour - and the preview
 * takes whatever height is left, so a tall pane shows the whole mail without a second scroller.
 */
.jru-mailstage__body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 15px;
}

.jru-envelope {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    gap: 6px 20px;
    margin: 0;
    font-size: 12.5px;
    line-height: 1.45;
}

.jru-envelope dt {
    color: var(--jru-text-muted);
}

.jru-envelope dd {
    margin: 0;
    min-width: 0;
    overflow-wrap: anywhere;
    color: var(--jru-text);
}

.jru-envelope__subject {
    font-weight: 600;
}

.jru-envelope__files {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.jru-envelope__file {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    color: var(--jru-text-muted);
}

.jru-envelope__filename {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--jru-text);
}

.jru-envelope__filesize {
    flex: 0 0 auto;
    font-size: 11.5px;
    font-variant-numeric: tabular-nums;
}

/*
 * THE MAIL ITSELF, in a frame that runs nothing. A mail is always light - there is no theme in
 * somebody else's inbox - so the frame draws its own ground and the page's dark theme stops at its
 * edge, which is what the recipient will see.
 */
.jru-mailpreview {
    flex: 1 0 auto;
    display: flex;
    flex-direction: column;
    min-height: 520px;
    margin: 0;
    overflow: hidden;
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-control);
    background: var(--jru-surface-alt);
}

.jru-mailpreview__caption {
    flex: 0 0 auto;
    padding: 7px 12px;
    border-bottom: 1px solid var(--jru-border);
    font-size: 11.5px;
    color: var(--jru-text-muted);
}

.jru-mailpreview__frame {
    flex: 1 1 auto;
    width: 100%;
    min-height: 480px;
    border: 0;
}

.jru-mailstage__log summary {
    cursor: pointer;
    font-size: 12.5px;
    color: var(--jru-text-secondary);
}

.jru-mailstage__log pre {
    max-height: 280px;
    margin: 8px 0 0;
    padding: 10px 12px;
    overflow: auto;
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-control);
    background: var(--jru-surface-alt);
    font-family: var(--jru-mono);
    font-size: 11.5px;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* Stacked, the page scrolls again and the record is as tall as its content, so the mail keeps a height. */
@media (max-width: 1100px) {
    .jru-mailstage__body {
        overflow-y: visible;
    }

    .jru-mailpreview {
        min-height: 70vh;
    }
}

/*
 * MAIL RECIPIENTS: where each trust's remittance advice and each firm's instruction letters go.
 *
 * The addresses are edited in the header of the mail they go out on - a To line and a Cc line, the
 * caption in the left column as a mail client sets it - so the record reads as the mail rather than
 * as a form about one. An address is a chip: it can be taken off or moved to the other line where
 * it sits, and the box at the end of the line takes the next one.
 */
.jru-mailrecipients__body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 15px;
}

.jru-mailrecipients__go {
    margin-left: 12px;
}

.jru-addressenvelope {
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-card);
    background: var(--jru-surface);
    box-shadow: var(--jru-elev-2);
    overflow: hidden;
}

.jru-addressenvelope__title {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 14px;
    border-bottom: 1px solid var(--jru-border);
    background: var(--jru-surface-alt);
    font-size: 12.5px;
    color: var(--jru-text-secondary);
}

.jru-addressline {
    display: grid;
    grid-template-columns: 52px minmax(0, 1fr);
    align-items: start;
    border-bottom: 1px solid var(--jru-border);
}

.jru-addressline:last-child {
    border-bottom: 0;
}

.jru-addressline__label {
    padding: 13px 0 0 14px;
    font-size: 12.5px;
    color: var(--jru-text-muted);
}

.jru-addressline__field {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    min-height: 46px;
    min-width: 0;
    padding: 8px 12px 8px 0;
}

.jru-addressline__field:focus-within {
    box-shadow: inset 0 -2px 0 var(--jru-primary);
}

.jru-addressline.is-refused .jru-addressline__field {
    box-shadow: inset 0 -2px 0 var(--jru-danger);
}

.jru-addressline__chips {
    display: contents;
    margin: 0;
    padding: 0;
    list-style: none;
}

.jru-addresschip {
    display: inline-flex;
    align-items: center;
    max-width: 100%;
    min-width: 0;
    height: 28px;
    padding: 0 2px 0 10px;
    border: 1px solid var(--jru-border);
    border-radius: 999px;
    background: var(--jru-surface-alt);
    font-size: 13px;
    color: var(--jru-text);
}

.jru-addresschip__text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.jru-addresschip__button {
    flex: 0 0 auto;
    display: inline-grid;
    place-items: center;
    width: 24px;
    height: 24px;
    margin-left: 2px;
    padding: 0;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: var(--jru-text-muted);
    cursor: pointer;
}

.jru-addresschip__button:hover {
    background: var(--jru-hover);
    color: var(--jru-text);
}

.jru-addresschip__button:disabled {
    cursor: default;
    opacity: 0.5;
}

.jru-addressline__empty {
    padding-left: 2px;
    font-size: 13px;
    color: var(--jru-reason-amber-fg);
}

/* Where a trust's addresses are read and it has none, as on the payment run's remittance tab. */
.jru-mailrecipients__missing {
    color: var(--jru-reason-amber-fg);
}

.jru-addressline__input {
    flex: 1 1 200px;
    min-width: 140px;
    height: 28px;
    padding: 0 4px;
    border: 0;
    outline: 0;
    background: transparent;
    font: inherit;
    font-size: 13px;
    color: var(--jru-text);
}

.jru-addressline__input::placeholder {
    color: var(--jru-text-muted);
}

.jru-addressline__error {
    grid-column: 2;
    margin: -2px 12px 10px 0;
    font-size: 12.5px;
    color: var(--jru-danger);
}

.jru-mailrecipients__hint,
.jru-mailrecipients__changed {
    margin: 0;
    font-size: 12.5px;
    color: var(--jru-text-muted);
}

.jru-mailrecipients__uses {
    display: grid;
    gap: 12px;
    max-width: 72ch;
    margin: 4px 0 0;
    padding: 0;
    list-style: none;
}

.jru-mailrecipients__uses li {
    display: grid;
    grid-template-columns: 20px minmax(0, 1fr);
    gap: 10px;
    color: var(--jru-text-muted);
}

.jru-mailrecipients__uses li.is-held {
    color: var(--jru-reason-amber-fg);
}

.jru-mailrecipients__use {
    font-weight: 600;
    color: var(--jru-text);
}

.jru-mailrecipients__uses p {
    margin: 2px 0 0;
    font-size: 13px;
    color: var(--jru-text-secondary);
}

.jru-mailrecipients__uses li.is-held p {
    color: var(--jru-reason-amber-fg);
}

@media (max-width: 1100px) {
    .jru-mailrecipients__body {
        overflow-y: visible;
    }
}

/*
 * A CLAIM'S SEND BATCH FOLDERS: the Output Folder tab on the claim. A bar naming the batch with the
 * one action that covers everything, then a section per folder whose heading carries the folder's
 * path on the server, then the standard grid. The path is set in the code face because it is
 * something a person types into Explorer, and one click selects all of it.
 */
.jru-folder {
    display: grid;
    gap: 16px;
    min-width: 0;
}

.jru-folder__bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px 16px;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--jru-border-soft);
}

.jru-folder__where {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px 10px;
    min-width: 0;
}

.jru-folder__baricon {
    color: var(--jru-text-muted);
}

.jru-folder__batch {
    color: var(--jru-text);
    font-size: 14px;
    font-weight: 600;
}

.jru-folder__summary {
    color: var(--jru-text-muted);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

.jru-folder__baractions {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

.jru-folder__note {
    padding: 8px 12px;
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-control);
    background: var(--jru-surface-alt);
    color: var(--jru-text-secondary);
    font-size: 12.5px;
}

.jru-folder__section {
    display: grid;
    gap: 8px;
    min-width: 0;
}

.jru-folder__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 2px 10px;
    min-width: 0;
}

.jru-folder__title {
    color: var(--jru-text);
    font-size: 13px;
    font-weight: 600;
}

.jru-folder__count {
    color: var(--jru-text-muted);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

.jru-folder__path {
    margin-left: auto;
    min-width: 0;
    color: var(--jru-text-muted);
    font-family: var(--jru-mono);
    font-size: 11.5px;
    overflow-wrap: anywhere;
    user-select: all;
}

/* An empty folder is a slot the files would sit in, so it keeps the slot's outline. */
.jru-folder__none {
    padding: 12px 14px;
    border: 1px dashed var(--jru-border);
    border-radius: var(--jru-radius-control);
    color: var(--jru-text-muted);
    font-size: 12.5px;
}

/* The name cell. Pack names run to fifty characters with no space in them, so the name breaks after
   an underscore rather than push the columns beside it off the panel. */
.jru-folder__file {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    vertical-align: middle;
}

.jru-folder__fileicon {
    flex: 0 0 auto;
    color: var(--jru-text-muted);
}

.jru-folder__filename {
    min-width: 0;
    color: var(--jru-text);
    font-weight: 500;
    overflow-wrap: break-word;
}

/* The claim's own documents among the member's. Ink on the primary surface, the selection's pair,
   so it reads as "the one you are on" in both themes without washing lime over grey. */
.jru-folder__tag {
    flex: 0 0 auto;
    padding: 1px 7px;
    border: 1px solid rgba(var(--jru-primary-bright-rgb), 0.35);
    border-radius: 999px;
    background: var(--jru-primary-surface);
    color: var(--jru-primary-bright);
    font-size: 11px;
    font-weight: 600;
    white-space: nowrap;
}

/*
 * THE PREVIEW: one file over the screen at a size a page can be read at. The dialog takes most of
 * the window's height and the frame takes whatever the bar above it leaves, so a letter is read
 * without a second scrollbar inside the dialog's own.
 */
.mud-dialog.jru-filepreview {
    display: flex;
    flex-direction: column;
    height: min(92vh, 1200px);
}

.jru-filepreview__title {
    min-width: 0;
    padding-right: 32px;
}

.jru-filepreview__name {
    min-width: 0;
    font-size: 14px;
    font-weight: 600;
    overflow-wrap: break-word;
}

.jru-filepreview__content {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
}

.jru-filepreview__body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 10px;
    min-height: 0;
    padding-bottom: 16px;
}

.jru-filepreview__bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 16px;
}

.jru-filepreview__facts {
    display: inline-flex;
    flex: 1 1 auto;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 12px;
    min-width: 0;
}

.jru-filepreview__kind {
    color: var(--jru-text);
    font-size: 12.5px;
    font-weight: 600;
}

.jru-filepreview__meta {
    color: var(--jru-text-muted);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

.jru-filepreview__steps,
.jru-filepreview__actions {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

.jru-filepreview__position {
    min-width: 4.5em;
    color: var(--jru-text-secondary);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    text-align: center;
}

.jru-filepreview__stage {
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    justify-content: center;
    min-height: 0;
    overflow: hidden;
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-control);
    background: var(--jru-surface-alt);
}

.jru-filepreview__frame {
    width: 100%;
    height: 100%;
    border: 0;
    background: var(--jru-surface);
}

.jru-filepreview__picture {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

/* ---------------------------------------------------------------- letters */
/*
 * THE LETTERS SCREEN: THE DOCUMENTS, THEIR WORDS, AND THE PAGE.
 *
 * One card filling the pane as master-detail does: a tab per document across the top, and under it two
 * panes that each scroll on their own, the rows of the part open (Details, Wording or Labels) in reading
 * order and the page itself on a desk. The one loud thing is the highlighter: what was changed is
 * marked in lime on the page, the box it was typed in carries the lime rule, and the count of changes
 * not saved beside a tab is lime too, so the eye moves between them without reading any.
 *
 * Under 1100px the panes stack and the page scrolls, because two stacked panes cannot each own a
 * viewport height.
 */
.jru-screen:has(.jru-letters) {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.jru-letters-screen {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

.jru-letters-screen > *:not(.jru-letters) {
    flex: 0 0 auto;
}

.jru-letters {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    min-width: 0;
    overflow: hidden;
}

/* The documents' strip reads as the card's heading: a little taller and larger than a record's tabs. */
.jru-letters > .jru-tabs > .jru-tabs__tab {
    padding: 12px 14px;
    font-size: 13.5px;
}

.jru-letters > .jru-tabpanel {
    display: flex;
    flex-direction: column;
    min-height: 0;
    padding: 0;
}

/* What is not saved yet is counted in the highlighter's lime, the same colour as the changed box. */
.jru-letters .jru-tabs__count {
    background: var(--jru-primary);
    color: var(--jru-on-primary);
}

.jru-letters__panes {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    flex: 1 1 auto;
    min-height: 0;
}

.jru-letters__editor,
.jru-letters__preview {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
}

.jru-letters__preview {
    border-left: 1px solid var(--jru-border);
}

/* The words. Who receives the document and when, then its parts, then the rows of the part open. */
.jru-letters__summary {
    flex: 0 0 auto;
    margin: 0;
    padding: 12px 20px 2px;
    color: var(--jru-text-muted);
    font-size: 13px;
}

.jru-letters__editor > .jru-tabs {
    padding: 0 12px;
}

.jru-letters__editor > .jru-tabpanel {
    min-height: 0;
    overflow-y: auto;
    padding: 2px 20px 20px;
}

/* A setting changed on Parameters: its value as text under the caption, no box to type into, and the
   link that opens it there beside it. */
.jru-letters__fixed {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 2px 14px;
}

.jru-letters__fixedvalue {
    color: var(--jru-text);
    font-size: 14px;
    font-weight: 600;
}

.jru-letters__link {
    color: var(--jru-primary-bright);
    font-size: 12px;
    font-weight: 500;
    text-decoration: none;
}

.jru-letters__link:hover {
    text-decoration: underline;
}

.jru-letters__attachments {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}

.jru-letters__section {
    padding-top: 16px;
}

.jru-letters__sectiontitle {
    margin: 0 0 6px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--jru-border-soft);
    color: var(--jru-text-secondary);
    font-size: 13px;
    font-weight: 600;
}

/* A box somebody changed carries the lime rule at its edge: the same colour the page highlights the
   words in. The rule sits in the gutter, so nothing in the box moves when it appears. */
.jru-letters__field {
    margin: 0 0 0 -14px;
    padding: 8px 0 10px 11px;
    border-left: 3px solid transparent;
}

.jru-letters__field.is-changed {
    border-left-color: var(--jru-primary);
}

.jru-letters__tokens {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-top: 6px;
}

.jru-letters__tokenlead {
    color: var(--jru-text-muted);
    font-size: 12px;
}

.jru-letters__token {
    padding: 2px 9px;
    border: 1px solid var(--jru-border);
    border-radius: 999px;
    background: var(--jru-surface);
    color: var(--jru-text-secondary);
    font: inherit;
    font-size: 12px;
    line-height: 1.5;
    cursor: pointer;
}

.jru-letters__token:hover {
    border-color: var(--jru-border-strong);
    color: var(--jru-text);
}

.jru-letters__token:focus-visible,
.jru-letters__restore:focus-visible,
.jru-letters__link:focus-visible {
    outline: 2px solid var(--jru-primary-bright);
    outline-offset: 2px;
}

.jru-letters__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 14px;
    margin-top: 6px;
    color: var(--jru-text-muted);
    font-size: 12px;
}

.jru-letters__meta:empty {
    display: none;
}

/* The put-back buttons, one item of the row above: when they do not fit beside who changed it, both
   move to the next line side by side, rather than the second dropping under the first. */
.jru-letters__restores {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 4px 14px;
}

.jru-letters__restore {
    padding: 0;
    border: 0;
    background: none;
    color: var(--jru-primary-bright);
    font: inherit;
    font-weight: 500;
    cursor: pointer;
}

.jru-letters__restore:hover {
    text-decoration: underline;
}

.jru-letters__savebar {
    flex: 0 0 auto;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 12px;
    padding: 10px 16px 10px 20px;
    border-top: 1px solid var(--jru-border);
    background: var(--jru-surface);
}

.jru-letters__savecount {
    font-weight: 600;
}

.jru-letters__savehint {
    flex: 1 1 auto;
    color: var(--jru-text-muted);
    font-size: 12px;
}

.jru-letters__saveactions {
    display: flex;
    gap: 8px;
}

/* The page, on a desk. */
.jru-letters__previewhead {
    flex: 0 0 auto;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 12px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--jru-border);
}

.jru-letters__previewtitle {
    margin: 0 auto 0 0;
    font-size: 14px;
    font-weight: 600;
}

.jru-letters__desk {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 18px;
    padding: 20px;
    background: var(--jru-page);
}

.jru-letters__page {
    display: block;
    width: 100%;
    max-width: 640px;
    height: auto;
    background: var(--jru-paper);
    box-shadow: var(--jru-paper-shadow);
}

.jru-letters__mail {
    display: flex;
    flex-direction: column;
    flex: 1 0 auto;
    width: 100%;
    max-width: 680px;
    margin: 0;
    overflow: hidden;
    border: 1px solid var(--jru-border);
    border-radius: var(--jru-radius-control);
    background: var(--jru-surface);
}

.jru-letters__subject {
    display: flex;
    gap: 8px;
    padding: 8px 12px;
    border-bottom: 1px solid var(--jru-border);
    font-size: 13px;
    line-height: 1.5;
    overflow-wrap: anywhere;
}

.jru-letters__subjectlead {
    flex: 0 0 auto;
    color: var(--jru-text-muted);
}

.jru-letters__mailframe {
    flex: 1 1 auto;
    width: 100%;
    min-height: 600px;
    border: 0;
}

.jru-letters__legend {
    flex: 0 0 auto;
    display: flex;
    flex-wrap: wrap;
    gap: 4px 10px;
    margin: 0;
    padding: 8px 16px;
    border-top: 1px solid var(--jru-border);
    color: var(--jru-text-muted);
    font-size: 12px;
}

.jru-letters__mark {
    padding: 0 2px;
    border-radius: 2px;
    color: var(--jru-on-primary);
}

.jru-letters__mark--changed {
    background: var(--jru-highlighter);
}

.jru-letters__mark--focused {
    background: var(--jru-border);
    color: inherit;
}

@media (max-width: 1100px) {
    .jru-screen:has(.jru-letters) {
        display: block;
        overflow: auto;
    }

    .jru-letters {
        overflow: visible;
    }

    .jru-letters__panes {
        grid-template-columns: minmax(0, 1fr);
    }

    .jru-letters__preview {
        border-top: 1px solid var(--jru-border);
        border-left: 0;
    }

    .jru-letters__editor > .jru-tabpanel,
    .jru-letters__desk {
        overflow: visible;
    }
}

/* The not-imported panel's counts and the rows an import left behind: a list read down, no bullets. */
.jru-notimported__list {
    display: grid;
    gap: 4px;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Where the rows are read from, under the card that lists them. */
.jru-notimported__folder {
    margin: 8px 0 0;
    font-size: 12.5px;
}
