/*
 * 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;

    /* 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;
    --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;

    --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;
}

.jru-visually-hidden {
    position: absolute;
    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);
}

/*
 * 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;
}

.jru-content {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
    padding: 18px 24px 24px;
}

/* -------------------------------------------------------------------- nav */
.jru-nav {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 10px 0 16px;
}

.jru-nav__group {
    padding: 14px 16px 5px;
    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: 12px 0 4px;
    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: 1px 8px;
    padding: 0 10px;
    height: 32px;
    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;
}

.jru-shell--rail .jru-nav__label {
    display: 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: 26px;
    margin: 9px 8px 1px;
    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 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: 32px;
    margin: 12px 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;
    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;
}

/*
 * 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;
}

.jru-grid__num {
    text-align: right;
    font-family: var(--jru-mono);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* 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 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;
}

/*
 * 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;
}

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 the long-value case this rule was written for - a load file's
 * 243-character column list - is unchanged: it is capped at its container and scrolls in place.
 */
.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);
}

/*
 * 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;
}

/* ------------------------------------------------------------------ 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;
}

/* 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;
}

/* ------------------------------------------------------------ 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;
}

/* ---------------------------------------------------------- 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);
}

.jru-totals__actions {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-left: auto;
}

/* ------------------------------------------------------------ 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 `.jru-content` 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 whose fields sit on its foot, so a field with a hint and one without line up at the control. */
.jru-form__row--end {
    align-items: end;
}

/*
 * 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));
}

.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 {
    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;
}

.jru-refusal__code {
    font-family: var(--jru-mono);
    font-size: 11px;
    opacity: 0.85;
}

/*
 * 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;
}

/*
 * 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.
 */
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 8px;
}

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;
}

.jru-grid__add .mud-button-root {
    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;
}

/* ------------------------------------------------------------ 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.
 */
.jru-filepicker {
    display: flex;
    align-items: center;
    gap: 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;
}

/* 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;
}

/* ------------------------------------------------------------ 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 .jru-content 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-content: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-content: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;
}

/*
 * 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-content: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);
}

/* 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 `.jru-content` 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 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. */
.jru-money-input input {
    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.
 */
.jru-field .mud-switch {
    margin-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);
}

.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;
}
