/* NCWRP - Razor conversion.
   No CSS framework: the old application carries three of them and they fight. Custom
   properties below are the whole design system, and both themes are defined so the app
   follows the operating system rather than pinning one look. */

:root {
    color-scheme: light dark;

    --bg: #f6f7f9;
    --surface: #ffffff;
    --surface-2: #f0f2f5;
    --border: #dfe3e8;
    --text: #16191d;
    --text-muted: #626b76;
    --accent: #0f7c5a;
    --accent-soft: #e3f3ec;
    --accent-text: #0a5a41;
    --shadow: 0 1px 2px rgb(16 24 40 / 6%), 0 1px 3px rgb(16 24 40 / 10%);
    --radius: 10px;

    /* THE STANDARD GAP BETWEEN STACKED PAGE BLOCKS. Every block-level band on a page - the head,
       the month pager, the filter card, the colour key, a table - is separated by this and by
       nothing else. Use it rather than a fresh margin on one page: a one-off is how two screens
       end up a few pixels apart for no reason anybody can explain later. */
    --stack: 18px;

    --status-active: #0f7c5a;
    --status-active-bg: #e3f3ec;
    --status-lapsed: #a45b00;
    --status-lapsed-bg: #fdf0dd;
    --status-dormant: #5b5f66;
    --status-dormant-bg: #eceef1;
    --status-finished: #8a3a3a;
    --status-finished-bg: #fbe9e9;

    --row-low-value: #fdead0;
    --row-no-value: #fbdada;
    --row-po-per-collection: #fbe1ea;
    --row-po-open: #eaf6ef;
    --row-po-completed: #ececf0;
    --row-po-month-end: #dcedfa;

    /* Collections list. The legacy screen paints these white / green / orange with yellow over the
       top, which is the worst possible set: green, orange and yellow are the three that collapse
       into each other under red-green colour blindness, which is 1 in 12 men. The hues below are
       from the Okabe-Ito colourblind-safe palette and, more to the point, THE COLOUR IS NEVER THE
       ONLY CUE - each row also carries a two-letter code (.job-flag) and a solid left edge, so the
       key can be read without seeing colour at all. Three separate cues: text, edge, tint.
       Lightness is deliberately different too, so they still separate in greyscale. */
    --job-minimum-load: #0072b2;      /* blue */
    --job-wasted-journey: #a34e8f;    /* reddish purple */
    --job-needs-detail: #b45309;      /* amber, and the darkest of the three */

    /* A FOURTH BLOCKING STATE. Okabe-Ito vermillion, which sits close to the amber above in hue -
       and that is tolerable ONLY because the CODE is the primary cue: ND and PO are two letters
       apart before any colour is involved. Do not add a fifth without a code to go with it. */
    --job-no-order: #d55e00;          /* vermillion */
    --row-minimum-load: #dceefc;
    --row-wasted-journey: #f9ddf0;
    --row-needs-detail: #fdefc8;

    --special-invoicing: #7a3ea1;
}

@media (prefers-color-scheme: dark) {
    :root {
        --bg: #12151a;
        --surface: #1a1e25;
        --surface-2: #21262e;
        --border: #2e343d;
        --text: #e6e9ee;
        --text-muted: #9aa3b0;
        --accent: #3ecf9a;
        --accent-soft: #16302a;
        --accent-text: #7fe3c0;
        --shadow: none;

        --status-active: #3ecf9a;
        --status-active-bg: #14302a;
        --status-lapsed: #e2a24c;
        --status-lapsed-bg: #33270f;
        --status-dormant: #a2abb8;
        --status-dormant-bg: #262b33;
        --status-finished: #e78585;
        --status-finished-bg: #351d1d;

        --row-low-value: #4a3418;
        --row-no-value: #4a2222;
        --row-po-per-collection: #45253a;
        --row-po-open: #17301f;
        --row-po-completed: #2a2a30;
        --row-po-month-end: #1c3550;
        --job-minimum-load: #56b4e9;
        --job-wasted-journey: #d98cc4;
        --job-needs-detail: #e69f00;
        --job-no-order: #ee7733;
        --row-minimum-load: #16344d;
        --row-wasted-journey: #3d1c33;
        --row-needs-detail: #46330d;
        --special-invoicing: #c79bea;
    }
}

* { box-sizing: border-box; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    -webkit-font-smoothing: antialiased;
}

a { color: inherit; }

/* ---- a link that opens a new tab ---------------------------------------
   SAYS SO BEFORE YOU CLICK IT, not after. A new tab that arrives unannounced reads as the back
   button being broken; the arrow is what turns it into a choice.

   THE ARROW IS A SHAPE, NOT A TINT, which is the same rule as .job-flag and the --job-* tokens:
   a state that matters has to survive greyscale. The element also carries a title attribute, so
   the affordance reaches a screen reader and a hover, not just a sighted click.

   No new colour token - --text-muted is already defined in both themes. */
.link-new-tab::after {
    content: "97";           /* an arrow, not a colour */
    display: inline-block;
    margin-left: 0.25em;
    font-size: 0.85em;
    line-height: 1;
    color: var(--text-muted);
}

/* Paper has no tabs. */
@media print {
    .link-new-tab::after { content: ""; }
}

/* ---- shell ---------------------------------------------------------- */

.shell { display: flex; min-height: 100vh; }

/* ---- showing and hiding the menu ---------------------------------------
   A CHECKBOX AND TWO SELECTORS. No script: the toggle works with JavaScript switched off, from the
   keyboard, and before anything has loaded. wwwroot/js/nav-toggle.js only remembers the choice in a
   cookie, which _Layout reads back so the first paint is already right.

   CHECKED MEANS SHOWN. The control then announces itself the right way round - "Menu, checkbox,
   checked" is a menu that is showing - and the default with no cookie is the menu visible, which is
   what somebody who has never touched the toggle should get.

   NOT SCOPED TO A BREAKPOINT, on purpose. Below 760px the sidebar is a band above the content
   rather than a column beside it, and hiding a band that is eating the top of a phone screen is
   worth more there than here, not less.

   NO TRANSITION ON THE WIDTH, also on purpose. The Google map on the home page is never re-fitted
   after it is built (see head-office-map.js), so animating the space beside it would hand it sixty
   intermediate sizes to render into instead of one. It snaps. */
.nav-toggle-state {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
}

#nav-shown:not(:checked) ~ .sidebar { display: none; }

/* ---- the filter card's show/hide, one control for every list screen ------------------------

   THE SAME ARRANGEMENT AS THE MENU ABOVE, and for the same reason: a checkbox and these rules do
   the work, so it opens and closes with JavaScript off; nav-toggle.js only remembers the choice in
   a cookie, which _Layout reads so the first paint is already right.

   TWO SELECTORS BECAUSE THERE ARE TWO SHAPES. Most list screens render `<form class="filters">` as
   a top-level element, so it is a direct sibling of the checkbox; the map page nests its card
   inside `#map-page`, so it is a descendant of one. Neither is worth changing to suit a selector.

   `display: none` and not `visibility` or a height animation: a hidden filter card must not keep
   its space, which is the entire point - the Sites list at 1080px showed TWO rows with the card
   open, measured, because 250px of filters and a three-line actions column left room for nothing
   else. */
/* A FILTER IS A GET; AN ADD OR AN EDIT IS A POST. That distinction is what makes this safe, and
   the first version of this rule did not make it: `.filters` is the general grid-of-fields card
   style, and TWENTY-ONE of its uses are POST forms - the inline row editors on Jobs, Collections,
   Groups, Drivers, Vehicles, Destinations, the Add rows on StaffList and the site-type lists, the
   Report Builder's save box. Hiding `.filters` outright collapsed the edit form somebody was
   halfway through typing into. Matching on method="get" hits the thirty-two real filter cards and
   nothing else, with no markup change on any of them. */
#filters-shown:not(:checked) ~ form.filters[method="get"],
#filters-shown:not(:checked) ~ * form.filters[method="get"] { display: none; }

.filters-toggle-state {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
}

.filters-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 8px;
    border-radius: 8px;
    color: var(--text-muted);
    cursor: pointer;
    font-size: 13px;
}

.filters-toggle:hover { background: var(--surface-2); color: var(--text); }
.filters-toggle-icon { width: 17px; height: 17px; display: block; }

/* A CONTROL THAT DOES NOTHING IS WORSE THAN NO CONTROL, so it is not rendered on a page with no
   filter card. :has() is what makes this possible without every page declaring whether it has one.
   Where :has() is unsupported the toggle simply shows and does nothing visible - a cosmetic
   fallback, not a broken page. */
.content:not(:has(form.filters[method="get"])) .filters-toggle { display: none; }

/* THE STATE IS A WORD, NOT A COLOUR. The icon changes shape - a full funnel when the card is
   showing, an outline when it is not - because the client is colourblind and so is one man in
   twelve. */
#filters-shown:checked ~ .topbar .filters-toggle { color: var(--text); }
#filters-shown:checked ~ .topbar .filters-toggle-icon { fill: currentColor; fill-opacity: 0.18; }

/* WHETHER ANYTHING IS ACTUALLY FILTERED, and it has to survive the card being collapsed: a hidden
   filter card must not be able to hide that the list is showing a subset. Text, not a dot alone. */
.filters-active {
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--accent-text);
    font-size: 11.5px;
    font-weight: 700;
    white-space: nowrap;
}

/* The spacer between the left-hand toggles and the sign-in details. See .nav-toggle. */
.topbar-user { margin-left: auto; }


.nav-toggle {
    /* Against the sidebar it controls, not marooned at the far right beside the sign-out link.
       THE SPACER MOVED TO .topbar-user, and it had to: this used to carry `margin-right: auto`,
       which pushed everything after it to the right - including the filter toggle, which belongs
       in this left-hand group. Putting the auto margin on the first element of the RIGHT group
       works whether or not the filter toggle is on the page, which it is not on most of them. */
    display: inline-flex;
    align-items: center;
    padding: 4px;
    margin-left: -4px;
    border-radius: 8px;
    color: var(--text-muted);
    cursor: pointer;
}

.nav-toggle:hover { background: var(--surface-2); color: var(--text); }

.nav-toggle-icon { width: 20px; height: 20px; display: block; }

/* SHAPE, NOT COLOUR: three bars to open, a double chevron to close. A control whose only difference
   between states is a tint is unreadable to one man in twelve. */
.nav-toggle-show { display: none; }
#nav-shown:not(:checked) ~ .content .nav-toggle-show { display: block; }
#nav-shown:not(:checked) ~ .content .nav-toggle-hide { display: none; }

/* The ring has to be drawn on the LABEL, because focus lands on the clipped checkbox - which is a
   sibling of .content, not of the label inside it. Without this the toggle was the only interactive
   control on the page with no visible focus. */
#nav-shown:focus-visible ~ .content .nav-toggle {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

.sidebar {
    /* Wide enough for an icon, a two-word label on one line, and a chevron. */
    width: 232px;
    flex: 0 0 232px;
    background: var(--surface);
    border-right: 1px solid var(--border);
    padding: 20px 14px;
    display: flex;
    flex-direction: column;
    gap: 22px;
    position: sticky;
    top: 0;
    height: 100vh;
    overflow-y: auto;
}

.brand {
    display: flex;
    align-items: center;
    gap: 10px;
    text-decoration: none;
    font-weight: 650;
    letter-spacing: -0.01em;
}

/* THE REAL LOGO, NOT A LETTER IN A GREEN BOX. Width-led, because the artwork is a lockup whose
   three lines of type stop being readable long before the mark does; the height follows from the
   width and height attributes on the img, so nothing jumps while the file loads.

   Both themes come from two files rather than two colour tokens - see Pages/Shared/_BrandLogo.cshtml
   for why. There is no colour here to define twice. */
.brand-logo {
    width: 100%;
    height: auto;
    display: block;
}

/* THE DARK COLOURWAY, FROM THE SAME FILE. brightness(0) zeroes the colour channels and leaves the
   alpha channel alone, so the artwork becomes a clean silhouette with its anti-aliased edges
   intact; invert(1) then makes that silhouette white.

   A CSS filter rather than a second file behind a <picture> source, because a media query
   re-evaluates live and <picture> does not: measured, switching the system theme with the page open
   left the browser showing the source it chose at load - a white logo on a white card - until the
   page was reloaded. */
@media (prefers-color-scheme: dark) {
    .brand-logo { filter: brightness(0) invert(1); }
}

.brand { max-width: 180px; }

/* The sign-in card has more room and nothing competing for it, so the logo can carry the screen. */
.login-brand .brand-logo { max-width: 240px; }

/* ---- sidebar navigation ---------------------------------------------
   Collapsible groups, one icon per row, and the group holding the current page opens itself. Head
   Office has around thirty destinations; flat and always-visible they did not fit on a laptop
   screen, so the sidebar scrolled and the bottom third of the application sat below the fold.

   The groups are <details>/<summary>, so the browser owns the open/closed state, the keyboard and
   the screen-reader semantics. There is no JavaScript in the menu.
   -------------------------------------------------------------------- */

.nav { display: grid; gap: 2px; align-content: start; }

.nav-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }

/* One shared row shape for links and group headings, so a group heading and a destination sit on
   exactly the same rhythm and only their weight tells them apart. */
.nav a,
.nav-group-head {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 7px 9px;
    border-radius: 8px;
    text-decoration: none;
    color: var(--text-muted);
    font-weight: 500;
    font-size: 13.5px;
    cursor: pointer;
}

.nav a:hover,
.nav-group-head:hover { background: var(--surface-2); color: var(--text); }

.nav a.active { background: var(--accent-soft); color: var(--accent-text); font-weight: 600; }

.nav-icon {
    width: 16px; height: 16px;
    flex: 0 0 16px;
    opacity: 0.75;
}

.nav a.active .nav-icon { opacity: 1; }

/* Long labels wrap rather than overflow or get clipped - "Sources of Volunteers" and "Site Yards
   and Collections" both do at this width. */
.nav a > span,
.nav-group-head > span { min-width: 0; line-height: 1.3; }

/* ---- collapsible groups ---- */

.nav-group { margin: 0; }

.nav-group-head {
    font-weight: 600;
    color: var(--text);
    list-style: none;                 /* Firefox draws its own marker without this. */
}

.nav-group-head::-webkit-details-marker { display: none; }
.nav-group-head::marker { content: ""; }

/* Pushed to the far edge, and it is the only thing in the row that moves. */
.nav-chevron {
    width: 14px; height: 14px;
    flex: 0 0 14px;
    margin-left: auto;
    opacity: 0.55;
    transition: transform 120ms ease;
}

.nav-group[open] > .nav-group-head > .nav-chevron { transform: rotate(90deg); }

/* The children of an open group, indented against a rule so the nesting reads without a box. */
.nav-sublist {
    margin: 2px 0 6px;
    padding-left: 9px;
    border-left: 1px solid var(--border);
    margin-left: 17px;                /* Lines the rule up under the centre of the group's icon. */
}

.nav-sublist a { font-weight: 450; }

/* A GROUP INSIDE A GROUP - Clients and Sites > Sites. Its head sits at the same weight as the links
   beside it rather than at the top-level group's weight, because at this depth it is a peer of
   "Client List", not a heading over the whole section. The nested rule indents from its parent's,
   so the two levels read as two levels; without the smaller offset the second rule landed on top of
   the first and the nesting was invisible. */
.nav-subgroup > .nav-group-head { font-weight: 450; }

.nav-sublist .nav-sublist {
    margin-left: 11px;
    padding-left: 8px;
}

@media (prefers-reduced-motion: reduce) {
    .nav-chevron { transition: none; }
}

.sidebar-note {
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--text-muted);
    border-top: 1px solid var(--border);
    padding-top: 14px;
}

.content { flex: 1 1 auto; padding: 26px 30px 60px; min-width: 0; }

/* ---- pages that fill the window ---------------------------------------
   OPT-IN, VIA ViewData["ContentFill"]. A long list is easier to work through when the list itself
   scrolls and everything that tells you WHERE you are - the month, the filters, the key, the column
   headings, the totals - stays put. Without this the whole page scrolls and the headings go first,
   which is exactly backwards.

   The mechanism is one flex column: everything keeps its natural height, the table takes what is
   left, and `min-height: 0` is what actually lets it shrink (a flex item will not go below its
   content height without it). No JavaScript and no magic offset, so a banner appearing or the alert
   above the pager showing up does not need the number retuning.

   Desktop only. Below the breakpoint the sidebar stops being a column and becomes a band above the
   content, and pinning the content to the viewport height there would leave a list a few rows tall.

   THE OPT-OUT IS NOT OPTIONAL FOR A PAGE OF SEVERAL TABLES, and the symptom is unmistakable once
   seen: every direct child gets a share of ONE window, so ten .table-scroll boxes each collapse to
   a header row and a scrollbar. Auto Correspondence announced "44 templates across 10 types" and
   showed about four of them. If a page renders a .table-scroll inside a loop, it wants
   ViewData["ContentFill"] = false. */
@media (min-width: 761px) {
    .content-fill {
        display: flex;
        flex-direction: column;
        height: 100vh;
        padding-bottom: 26px;
        overflow: hidden;
    }

    .content-fill > * { flex: 0 0 auto; }

    /* GROW 0, SHRINK 1, and that pair is the whole behaviour: a short list keeps its natural
       height rather than being stretched into a mostly-empty card, and a long one shrinks to the
       space left and scrolls inside itself. `min-height: 0` is what lets it shrink at all - a flex
       item will not go below its content height without it. */
    .content-fill > .table-scroll {
        flex: 0 1 auto;
        min-height: 0;
        overflow: auto;
    }

    /* A LIST WRAPPED IN A FORM STILL HAS TO FILL AND SCROLL. The client list's grid is editable
       in every row, so the whole table sits inside one <form> - which puts an element between
       .content-fill and .table-scroll and stops the rule above matching. Left alone the list
       renders at its full natural height inside an overflow:hidden column, and the bottom rows
       become UNREACHABLE rather than merely below the fold - the same failure the comment above
       describes, and the one .map-split > .table-scroll already had to undo when its table was
       nested.

       So the form takes the grow/shrink the table-scroll used to have, and hands it back to the
       table-scroll inside it. min-height: 0 on BOTH: a flex item will not shrink below its
       content height without it, and one missing link in the chain is enough to break it. */
    .content-fill > .list-form {
        display: flex;
        flex-direction: column;
        flex: 0 1 auto;
        min-height: 0;
    }

    .content-fill > .list-form > .table-scroll {
        flex: 0 1 auto;
        min-height: 0;
        overflow: auto;
    }
}

/* ---- page furniture -------------------------------------------------- */

.page-head { margin-bottom: var(--stack); }

/* ---- actions in the page head ------------------------------------------
   ONLY WHEN THERE ARE ANY. .page-head is used by nine screens that have a single child and expect
   block layout, so the flex row is applied with :has() rather than unconditionally - adding actions
   to one screen must not quietly reflow the eight that have none.
   Wraps rather than overflows: the site screen's row is four controls and a disclosure, which does
   not fit a narrow window beside a heading. */
.page-head:has(.head-actions) {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--stack);
    flex-wrap: wrap;
}

.head-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
}

/* The disclosures, revealed in place. `summary` carries the button's own look so the row reads as
   one set of controls rather than a link loose among buttons. */
.head-actions details { position: relative; }

.head-actions summary {
    list-style: none;
    cursor: pointer;
}

.head-actions summary::-webkit-details-marker { display: none; }

/* THE PANEL IS TAKEN OUT OF FLOW. Left in it, opening the override code or the collection request
   form makes the whole page head taller and shoves the form below it down the screen - which is
   what a disclosure in a toolbar must not do.
   RIGHT-ALIGNED, because the row sits at the right-hand end of the page head and a left-aligned
   card would hang off the window. This is the page head, not a scrolling list, so there is no
   clipping ancestor and no need for the fixed-position anchoring `.sig-pop` and `.row-menu-items`
   require - see the note on those in CLAUDE.md before copying this pattern into a table. */
.head-actions .pop-card {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: 20;
    width: max-content;
    max-width: min(30rem, 90vw);
    padding: 12px 14px;
    text-align: left;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}

.head-actions .pop-card > :first-child { margin-top: 0; }
.head-actions .pop-card > :last-child { margin-bottom: 0; }
.head-actions .pop-card .field { margin-bottom: 10px; }
.head-actions .pop-card textarea { min-width: 22rem; max-width: 100%; }

/* Paper never has an action to press. */
@media print {
    .head-actions { display: none; }
}
h1 { margin: 0; font-size: 24px; letter-spacing: -0.02em; }
.muted { color: var(--text-muted); margin: 4px 0 0; font-size: 14px; }
.empty { color: var(--text-muted); padding: 40px; text-align: center; }

/* ---- an empty list that explains itself --------------------------------
   Sits under .empty and names the filters that produced nothing. Kept narrow on purpose: it is
   prose to be read, not a grid. */
.empty-detail {
    max-width: 62ch;
    margin: 0 auto var(--stack);
    text-align: left;
}

.empty-detail ul { margin: 4px 0 12px; padding-left: 1.25em; }
.empty-detail li { margin-bottom: 4px; }

.dashboard-alert {
    background: var(--status-lapsed-bg);
    color: var(--status-lapsed);
    border-radius: var(--radius);
    padding: 10px 14px;
    margin: 0 0 10px;
    font-size: 14px;
}

.dashboard-alert a { text-decoration: underline; font-weight: 600; }

/* The stacked fallback height, used below the breakpoint and anywhere the map is not inside a
   .map-col that can give it the window's height. */
.map-canvas { width: 100%; height: 360px; margin-bottom: 14px; border-radius: var(--radius); }

/* ---- the home page's map and nearest-enterprise list --------------------
   WHY THIS EXISTS. .map-canvas is 360px tall whatever the window, so on a wide screen the map drew
   itself as a letterbox and pushed the list of enterprises off the bottom of a .content-fill page.
   A .content-fill page does not scroll, so those rows were unreachable rather than merely below the
   fold - the same failure, and the same cause, as the chart at .chart-svg. Measured at 2000x1000:
   the map was 1708x360, an aspect of 4.7:1, and the list was clipped.

   A FLEX ROW RATHER THAN A GRID, and that is not the filter-card rule being ignored. A grid of
   named tracks is right for a card of eleven filters that has to line up; this is two panes, one
   flexible and one fixed-ish, where the second pane is often ABSENT - no postcode has been typed,
   so nothing is near anything, and on a development box that is the normal case because
   IpGeolocator returns nothing for a loopback address. Flex gives the map the whole width in that
   case with no second rule; a two-track grid would leave a hole.

   STACKED IS THE DEFAULT AND THE ROW IS THE OPT-IN, so a browser that does not understand the
   container query below gets the safe layout rather than two panes crushed into 290px each. */
.map-split { display: flex; flex-direction: column; gap: var(--stack); }

.map-col {
    min-width: 0;
    display: flex;
    flex-direction: column;
    /* The gap, not a margin on the map, so an empty #directions-panel - which is display:none -
       leaves no gap behind it. */
    gap: 14px;
}

.map-col > .map-canvas, .map-col > #directions-panel { margin-bottom: 0; }

/* A CONTAINER QUERY, NOT A MEDIA QUERY, AND THE MENU TOGGLE IS THE REASON. Whether there is room
   for two panes is a question about the CONTENT's width, and hiding the sidebar now adds 232px to
   that without the window changing at all. Keyed to the window this laid out two columns of 290px
   at 900px wide - measured - and refused to lay out two comfortable ones at 900px with the menu
   hidden, which is the same amount of room the desktop case gets at 1132px.

   The container is #map-page and not .content: inline-size containment makes an element the
   containing block for fixed-position descendants, and .content is the ancestor of every anchored
   popover in the application (.sig-pop, .row-menu-items). Putting it there would clip them. */
#map-page { container-type: inline-size; }

@media (min-width: 761px) {
    /* .content-fill > * is flex: 0 0 auto, which would leave #map-page at its natural height and
       any percentage height inside it resolving against nothing - a map in a 0px box. The id
       out-specifies that rule and hands the leftover height down instead. min-height: 0 is what
       actually lets a flex item shrink below its content.

       overflow: auto rather than letting .content-fill clip: when the panes are STACKED because
       the column is narrow, or the window is very short, the overflow has somewhere to go. Clipping
       is what put the list out of reach in the first place. */
    #map-page {
        flex: 1 1 auto;
        min-height: 0;
        display: flex;
        flex-direction: column;
        overflow: auto;
    }

    /* STACKED, THE PANES KEEP THEIR NATURAL HEIGHTS AND #map-page SCROLLS. flex: 0 0 auto because
       the default `1` shrink squeezed the list to 2 PIXELS - measured at 900x760 - while the map
       above it held its 360px. A pane crushed to nothing is not a smaller pane, it is a missing
       one. */
    #map-page > .map-split { flex: 0 0 auto; }
}

/* Two panes once the column is wide enough to hold both. 940px is not a round number picked by
   eye: the list's min-content width is 451px, measured, most of it the "Distance (miles, as the
   crow flies)" heading - so a narrower pane than that scrolls sideways on every row. 940 leaves
   480px of list beside 442px of map, which is the narrowest either is worth having. */
@container (min-width: 940px) {
    /* The id has to be repeated: the stacked rule above is `#map-page > .map-split` and would win
       on specificity however late this block comes. */
    #map-page > .map-split { flex: 1 1 auto; min-height: 0; flex-direction: row; }

    .map-split > .map-col { flex: 1 1 auto; min-height: 0; }

    /* height: auto UNSETS the 360px above so the flex line can size it. The floor stops the map
       becoming a strip of Google's logo on a short window; below that #map-page scrolls, which is
       better than clipping it. */
    .map-split > .map-col > .map-canvas { flex: 1 1 auto; min-height: 200px; height: auto; }

    /* Turn-by-turn directions can run to thirty steps. Scrolls inside its own half of the column
       rather than pushing the map out of the page. */
    .map-split > .map-col > #directions-panel { flex: 0 1 auto; overflow: auto; }

    /* Wide enough for the longest enterprise name at the top end, narrow enough on a laptop to
       leave the map a usable shape. It has to restore the scroll and the min-height that
       `.content-fill > .table-scroll` gave it before it was nested - without them the table has no
       height cap, and `.grid th`'s sticky headings silently stop sticking. */
    .map-split > .table-scroll {
        /* 480 = the table's measured min-content width of 451px, plus room for the vertical
           scrollbar this pane always has. At 460 it was six pixels short and the list gained a
           HORIZONTAL scrollbar as well - measured, not estimated; clientWidth excludes the
           vertical bar and min-content does not know about it. */
        flex: 0 0 clamp(480px, 38%, 720px);
        min-height: 0;
        overflow: auto;
    }
}

/* ONE FIELD, AND THE SHARED GRID WAS SPENDING 221px OF CARD ON IT. Measured at 2000x1000: eight
   equal tracks with seven of them empty, .field-buttons taking a full row of its own below, and the
   two notes a third row. That is the right layout for the invoice list's eleven filters and the
   wrong one for a single postcode box - and on THIS page the height comes straight off the map
   underneath, which is what made the map a letterbox in the first place.

   Four tracks and no forced row for the buttons puts the lot on one line: 221px becomes 94px, and
   the map is 127px taller for it. Scoped to this form rather than changed in .filters, because the
   eleven-filter case still needs its own row for the buttons.

   A HIGHER THRESHOLD THAN THE SPLIT'S, and measured the same way. The three buttons need about
   400px on one line and the two notes about 200px each; at 868px the notes were squeezed to 40px
   and wrapped to eight lines, making the card 177px tall - taller than the two rows it replaced.
   Below 1100 the standard grid is simply better. */
@container (min-width: 1100px) {
    #map-page > .filters {
        grid-template-columns: minmax(180px, 240px) auto minmax(0, 1fr) minmax(0, 1fr);
        align-items: end;
    }

    #map-page > .filters > .field-buttons { grid-column: auto; }
    #map-page > .filters > .muted { align-self: center; margin: 0; }
}

/* A GRID, NOT A WRAPPING FLEX ROW. With four filters either behaves; the invoice list has eleven,
   and wrapping flex laid them out in ragged columns of different widths with the buttons marooned
   in the middle of the card. A grid of equal tracks puts every label on a shared baseline, keeps
   the columns aligned down the card, and reflows to fewer tracks on a narrow window without any
   breakpoint of its own.

   Fields size themselves from the track, so a filter never dictates the width of the card. */
.filters {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    align-items: end;
    gap: 12px 14px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 14px;
    margin-bottom: var(--stack);
    box-shadow: var(--shadow);
}

/* The buttons get the full width of the last row and sit at its right-hand end - the place the eye
   goes after reading the filters, and out of the way of the fields. */
.filters > .field-buttons {
    grid-column: 1 / -1;
    justify-content: flex-end;
    border-top: 1px solid var(--border);
    margin-top: 2px;
    padding-top: 12px;
}

/* A YES/NO CHOICE DOES NOT NEED 240 PIXELS. The tracks stay equal - that rule is what stopped the
   eleven-filter cards laying out in ragged columns - but the CONTROL inside a short field is
   capped, so "(All) / Yes / No" is not stretched across a track sized for a date and a client name.
   The label and the baseline stay on the grid; only the box gets smaller.

   Opt-in with class="field field-short", because only the page knows which of its choices are
   short: a two-option select of client names is not one. */
.filters .field-short .input { max-width: 132px; }

/* A form with one or two filters should not have its controls flung to the far side of a wide card,
   so a single-row card keeps the buttons beside the fields. */
.filters-inline { grid-template-columns: repeat(auto-fit, minmax(190px, max-content)); }
.filters-inline > .field-buttons {
    grid-column: auto;
    justify-content: flex-start;
    border-top: none;
    margin-top: 0;
    padding-top: 0;
}

/* ---- a filter card with too many controls to read flat -------------------
   THIRTEEN CONTROLS IN ONE ROW OF EQUAL TRACKS is past what the flat card handles. The Report
   Builder hit it, and a one-line hint under a single field was enough to stretch its whole row and
   leave the card ragged.

   GROUPS, NOT A LONGER ROW. Each group is a <fieldset> with a <legend>, which is the real HTML for
   "these belong together": a screen reader announces the group name before each control inside it,
   and somebody hunting for the chart option looks under "Chart" rather than scanning thirteen
   labels for it.

   The GROUPS are the grid items, so .filters' own track sizing still applies and
   `.filters > .field-buttons { grid-column: 1 / -1 }` still puts the buttons on their own last row.
   Tracks are narrower here than in the flat card because a group spans several of them. */
/* FLEX HERE, GRID IN THE FLAT CARD, AND THAT IS NOT THE RULE BEING IGNORED. The reason .filters is
   a grid is that a wrapping flex row laid BARE FIELDS out in ragged columns of different widths
   with the buttons marooned in the middle. These are not bare fields - they are self-contained
   boxes with their own internal grid, so there are no columns to keep aligned between them, and the
   buttons still get their own full-width row below.

   What flex buys is the thing a track grid cannot do here: groups GROW to fill the row they land
   on. Measured with spans over a seven-track grid at 1600px, the second row held a 2-span and a
   3-span and left a 370px hole at the end of it - the same raggedness this whole change is about. */
.filters-grouped {
    display: flex;
    flex-wrap: wrap;
    gap: 12px 14px;
    /* Boxes on one row share a height, so their bottoms line up. Ragged bottoms are what a card
       full of boxes of different heights looks like, and it reads as an accident. */
    align-items: stretch;
}

.filters-grouped > .field-buttons { flex: 1 1 100%; }

.filter-group {
    /* Grow to fill the row, with a basis that reflects how much the group holds. The basis is what
       decides where it wraps; the grow is what stops a hole at the end of a row. */
    flex: 1 1 240px;
    min-width: 0;
    margin: 0;
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 4px 12px 12px;
    background: var(--bg);
}

/* THE BASIS IS SIZED TO HOLD THE GROUP'S FIELDS SIDE BY SIDE, which is the whole job it has. Set
   too small, four groups crowded onto one row, "Over what period" stacked its three fields
   vertically and - because boxes on a row share a height - dragged every other box to 210px.
   Measured at 1600px. A group narrower than its own contents is not a smaller group, it is a taller
   one. */
.filter-group-1 { flex: 1 1 250px; }
.filter-group-2 { flex: 1.2 1 340px; }
.filter-group-3 { flex: 1.6 1 480px; }

.filter-group > legend {
    padding: 0 6px;
    font-size: 12px;
    font-weight: 650;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--text-muted);
}

/* THE FIELDSET IS NOT ITSELF THE GRID. A <legend> inside a grid or flex fieldset is taken out of
   normal flow by the browser and engines disagree about the result; an inner wrapper lays the
   fields out and sidesteps the whole class of bug. */
.filter-group-fields {
    display: grid;
    /* 132px, not the flat card's 190: three of these have to fit inside a 480px group, and the
       labels here are one or two short words because the GROUP now carries the context that used
       to be crammed into every label. */
    grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
    gap: 10px 12px;
    /* Inside a group the controls DO share a baseline, exactly as they do in the flat card - it is
       between groups that a shared baseline stops meaning anything. */
    align-items: start;
}

/* A NOTE UNDER A CONTROL, SIZED SO IT DOES NOT SET THE ROW'S HEIGHT. .muted is 14px and carries a
   top margin, which is right for a paragraph on a page and too heavy under a select - it was what
   made one field taller than its neighbours and the card ragged. */
.field-hint {
    margin: 5px 0 0;
    font-size: 12px;
    line-height: 1.35;
    color: var(--text-muted);
}

.filters .input, .filters select.input { min-width: 0; width: 100%; }
.filters-wide .input { min-width: 0; }
.filters-wide select.input { min-width: 0; }

/* ---- legend ------------------------------------------------------------ */

.legend {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    align-items: center;
    justify-content: center;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 10px 14px;
    margin-bottom: var(--stack);
    font-size: 12.5px;
    color: var(--text-muted);
}

.legend-item { display: inline-flex; align-items: center; gap: 6px; }

.swatch {
    display: inline-block;
    width: 13px;
    height: 13px;
    border-radius: 3px;
    border: 1px solid var(--border);
}

.swatch.row-low-value { background: var(--row-low-value); }
.swatch.row-no-value { background: var(--row-no-value); }
.swatch.row-po-per-collection { background: var(--row-po-per-collection); }
.swatch.row-po-month-end { background: var(--row-po-month-end); }

/* THE COLLECTIONS KEY IS READABLE WITHOUT COLOUR. Each state has a two-letter code that appears
   both here and on the row itself, so the key can be used by someone who cannot tell the tints
   apart - which is the normal case, not an edge case. The colour and the left edge are the second
   and third cues, not the only ones. See the --job-* tokens for why the legacy green/orange/yellow
   set had to go. */
.job-flag {
    display: inline-block;
    min-width: 24px;
    padding: 1px 5px;
    margin-right: 6px;
    border-radius: 4px;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-align: center;
    color: #ffffff;
    vertical-align: 1px;
}

.job-flag.flag-normal { background: var(--text-muted); }
.job-flag.flag-minimum-load { background: var(--job-minimum-load); }
.job-flag.flag-wasted-journey { background: var(--job-wasted-journey); }
.job-flag.flag-needs-detail { background: var(--job-needs-detail); }
.job-flag.flag-no-order { background: var(--job-no-order); }

/* In the key the code IS the swatch, so there is nothing to read off a colour. */
.legend .job-flag { margin-right: 0; }

/* A row carries its tint and a solid left edge in the full-strength hue. Needs-detail wins the tint
   because it is the state that stops an invoice, but the row still shows BOTH codes when a job is,
   say, a minimum load whose yards are unsplit - the original loses that, having only one colour to
   give. */
.grid tr.job-minimum-load > td { background: var(--row-minimum-load); }
.grid tr.job-wasted-journey > td { background: var(--row-wasted-journey); }
.grid tr.job-needs-detail > td { background: var(--row-needs-detail); }

.grid tr.job-minimum-load > td:first-child { box-shadow: inset 4px 0 0 var(--job-minimum-load); }
.grid tr.job-wasted-journey > td:first-child { box-shadow: inset 4px 0 0 var(--job-wasted-journey); }
.grid tr.job-needs-detail > td:first-child { box-shadow: inset 4px 0 0 var(--job-needs-detail); }

/* The hover highlight must not wipe the state colour off the row - it is the state that matters,
   not which row the pointer happens to be over. */
.grid tbody tr.job-minimum-load:hover > td { background: var(--row-minimum-load); }
.grid tbody tr.job-wasted-journey:hover > td { background: var(--row-wasted-journey); }
.grid tbody tr.job-needs-detail:hover > td { background: var(--row-needs-detail); }

/* ---- collections: the WTN cell, signatures, and the inline edit row ------ */

/* The note reference, then whatever the note still needs: on a manual note, whether its scan is
   in; otherwise the pen that shows the signatures. Both sit beside the reference rather than in
   columns of their own, because on most rows there is nothing to show. */
.wtn-cell { display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; }

/* A manual note with no scan is exactly the row the yellow is warning about, so it says so in
   words as well - the colour alone does not tell you which of the two reasons applies. */
.wtn-missing { font-size: 11.5px; font-style: italic; color: var(--status-lapsed); }

.wtn-link { font-size: 11.5px; font-weight: 600; }

/* The signature hover. Focusable as well as hoverable: the legacy screen's version was mouse-only,
   and a keyboard or touch user could never see a signature at all. */
.sig-hover {
    position: relative;
    display: inline-flex;
    align-items: center;
    color: var(--text-muted);
    cursor: pointer;
}

.sig-hover:hover, .sig-hover:focus-visible { color: var(--accent); outline: none; }
.sig-hover svg { width: 15px; height: 15px; }

.sig-pop {
    position: absolute;
    left: 0;
    top: calc(100% + 6px);
    z-index: 30;
    display: none;
    gap: 6px;
    padding: 8px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: 0 4px 14px rgb(16 24 40 / 18%);
}

.sig-hover:hover .sig-pop, .sig-hover:focus-visible .sig-pop, .sig-hover:focus-within .sig-pop {
    display: flex;
}

/* THE POPOVER ON THE LAST ROW USED TO BE CUT OFF, and giving the table its own scrollbar would have
   made that worse: an absolutely-positioned child is clipped by any ancestor that scrolls.

   Anchor positioning fixes both halves at once. `position: fixed` takes the card out of the scroll
   container's clipping entirely, `position-anchor` keeps it pinned to its own pen anyway, and
   `flip-block` swings it above the pen when there is no room below.

   EVERY ANCHOR NEEDS ITS OWN NAME, ONE PER ROW. A shared name does NOT resolve to the pen the card
   happens to sit inside: the anchor lookup takes the LAST element in tree order carrying that name,
   so on a hundred-row list every card anchored to the last row's pen and opened somewhere off the
   bottom of the page. That was measured, not guessed - and it is invisible if you only ever test the
   last row, which is exactly what it looks like when it is working. The row supplies the name as a
   custom property; custom properties inherit, so the card reads the same one without repeating it.

   Behind @supports because it is Chrome and Edge only today. Everywhere else keeps the absolute
   card above, which is positioned correctly and may still be clipped on the last row - the same
   behaviour as before, not a regression. */
@supports (anchor-name: --a) {
    .sig-hover { anchor-name: var(--anchor); }

    .sig-pop {
        position: fixed;
        position-anchor: var(--anchor);
        position-area: block-end span-inline-end;
        position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
        left: auto;
        top: auto;
        margin-top: 6px;
    }
}

/* Signatures are drawn dark on transparent, so on a dark theme they would be ink on ink. The white
   card behind them is deliberate in both themes - it is a scan of a signature, not a UI element. */
.sig-pop img {
    display: block;
    width: auto;
    max-width: 190px;
    max-height: 90px;
    background: #ffffff;
    border-radius: 4px;
}

/* The edit form opens as a row under the collection it edits, rather than as a dialogue or a
   separate page: the numbers being corrected are the ones directly above it, and the four splits
   only make sense read against the total on that line. */
.grid tr.edit-row > td {
    background: var(--surface-2);
    padding: 14px 12px;
    border-top: 2px solid var(--accent);
}

.grid tr.edit-row .filters { margin-bottom: 0; }
.grid tr.edit-row form + form { margin-top: 12px; border-top: 1px solid var(--border); padding-top: 12px; }
.grid tr.edit-row .input { min-width: 0; }
.grid tr.edit-row input[type="number"] { width: 92px; }
.grid tr.edit-row input[type="date"] { width: 150px; }
.grid tr.edit-row input[type="text"] { width: 130px; }

/* ---- editing a row IN PLACE -------------------------------------------
   .edit-row above replaces the row with one wide cell. This one keeps the row a row: the cells
   stay in their columns and the editable ones become inputs, so the value you are typing is still
   under the heading that names it. Used by the client list's rates and flags.

   The row is marked by a LEFT BAR as well as a tint - a state that matters must not be carried by
   colour alone, and an edited row is the most important state on the screen while it lasts. */
.grid tr.row-editing > td {
    background: var(--surface-2);
    vertical-align: middle;
}

.grid tr.row-editing > td:first-child {
    box-shadow: inset 3px 0 0 0 var(--accent);
    font-weight: 600;
}

/* An input sized to sit inside a grid cell rather than to fill a filter card. */
.input-cell {
    width: 100%;
    min-width: 0;
    max-width: 110px;
    padding: 4px 6px;
    font-size: 0.92em;
}

.grid tr.row-editing select.input-cell { max-width: 130px; }

/* ---- a list whose every row is editable --------------------------------
   The client list renders an input in every rate, sales-person, group and flag cell, which is
   what the WebForms screen does. Sized to the DATA rather than to a filter card: a rate is four
   characters and initials are two, and giving them a filter-width box is what made the grid
   unreadable when the columns were text.

   No colour of their own - an input in a grid cell should read as a cell you can type in, not as
   a form bolted onto a table. */
/* MIN-WIDTH BEATS MAX-WIDTH, and that is why the first attempt at this did nothing.
   `.input` carries `min-width: 260px` so a filter box never collapses. `.input-cell` set
   `min-width: 0` to undo it - but both are single-class selectors, so the cascade falls through
   to SOURCE ORDER, `.input` is declared later, and its 260px won. Every cell control rendered at
   260px against a max-width of 68px that the browser is required to ignore: measured on the live
   list, the table came out 3,203px wide inside a 1,611px window, so 1,592px of it - eight of the
   seventeen columns - was off-screen.

   Scoped under .grid td so these win on specificity rather than on being lower down the file,
   which is the thing that broke. Sized to the DATA: a rate is three digits and a decimal, initials
   are two letters, an abbreviation is about five. */
.grid td .input-cell {
    width: 100%;
    min-width: 0;
    padding: 4px 6px;
    font-size: 0.92em;
}

/* Room for the number spinner, which Chrome renders inside the box. */
.grid td .input-rate     { max-width: 82px; text-align: right; }
.grid td .input-initials { max-width: 62px; text-transform: uppercase; }
.grid td .input-abbrv    { max-width: 84px; }
.grid td .input-yesno    { max-width: 74px; }

/* ---- a cell that has just saved itself --------------------------------
   State on a control that saved without being asked to. It has to be visible without being
   loud: this fires on every field a user touches, so an animation would make the screen twitch.

   NOT COLOUR ALONE. A saved cell also gets a heavier border and a failed one keeps its outline
   until it is corrected, so the two are distinguishable in greyscale and by anybody who cannot
   separate the green from the red - which is the whole reason .job-flag carries codes.
   A failed save also puts the reason in the title attribute, which is the only text there is
   room for in a grid cell. */
.cell-saving { opacity: 0.6; }

.cell-saved {
    border-color: var(--status-active);
    box-shadow: inset 0 0 0 1px var(--status-active);
    transition: box-shadow 0.4s ease-out, border-color 0.4s ease-out;
}

.cell-failed {
    border-color: var(--status-lapsed);
    box-shadow: inset 0 0 0 2px var(--status-lapsed);
}

/* ---- a two-state toggle made of links ---------------------------------
   Active Sites / All Sites on the client screen. Two anchors rather than a control, so it works
   before any script runs and each state is a real URL.

   THE CHOSEN ONE IS NOT ONLY A COLOUR. It takes the solid button treatment AND carries
   aria-pressed, so which is active survives greyscale and reaches a screen reader. */
.toggle-group { display: inline-flex; gap: 0; }

.toggle-group > .btn:first-child { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.toggle-group > .btn:last-child  { border-top-left-radius: 0; border-bottom-left-radius: 0; margin-left: -1px; }

.btn-toggle-on {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
    font-weight: 600;
}

/* The head above the sites table: the toggle on the left, what is showing on the right. */
.site-list-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}

.site-list-head .muted { margin: 0; }

/* ---- an account contact, edited in its row -----------------------------
   The rows are ordinary cells - the per-row <form> lives outside the table and the inputs reach
   it with form=, because a <form> cannot contain <tr>. So the values stay under the headings
   that name them, and each contact still saves on its own.

   An e-mail is the widest thing here by a long way and a telephone the narrowest; sized to the
   data rather than every box taking the same share. */
.grid td .input-email { max-width: 260px; }

/* ---- the blank row that adds a record ---------------------------------
   The new record is the FIRST ROW OF THE TABLE, not a card above it, so it inherits the table's
   columns exactly - First Name sits under First Name. It was a .filters grid, which has its own
   widths and lined up with nothing.

   MARKED THREE WAYS, because a state that matters must not be carried by colour alone: a tint, a
   left bar, and the word "New" in the action cell. In greyscale the bar and the word both
   survive.

   --accent-soft is already defined in both themes. */
.grid tr.row-new > td {
    background: var(--accent-soft);
    border-bottom: 2px solid var(--border);
}

.grid tr.row-new > td:first-child { box-shadow: inset 3px 0 0 0 var(--accent); }

/* Sits beside the Add button rather than in a column of its own - there is no spare column, and
   the row is self-evidently the blank one anyway. */
.pill-new {
    background: var(--accent-soft);
    color: var(--accent-text);
    border: 1px solid var(--accent);
}

@media print {
    /* Paper cannot be typed into. */
    .grid tr.row-new { display: none; }
}

/* Mid-swap. Dimmed rather than blanked, so the block does not collapse and jump the page. */
[aria-busy="true"] { opacity: 0.55; }

/* The Save that commits the whole grid. Sticky at the top so it stays reachable down 511 rows;
   the copy at the foot is a plain one. */
.list-actions {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: var(--stack);
}

@media print {
    .list-actions { display: none; }
}

/* ---- a menu on a table row --------------------------------------------
   <details> rather than script: the browser owns open/closed, Escape, and the focus order, and it
   works before any JavaScript has run. The panel is absolute so it does not push the row apart, and
   anchored where supported so it is not clipped by the list's own scrollbar - the same problem, and
   the same fix, as .sig-pop. */

.row-menu { position: relative; display: inline-block; }

.row-menu > summary {
    cursor: pointer;
    color: var(--accent-text);
    font-weight: 600;
    list-style: none;
}

.row-menu > summary::-webkit-details-marker { display: none; }
.row-menu > summary::after { content: " ▾"; opacity: 0.6; }
.row-menu[open] > summary::after { content: " ▴"; }
.row-menu > summary:hover { text-decoration: underline; }

.row-menu-items {
    position: absolute;
    left: 0;
    top: calc(100% + 4px);
    z-index: 30;
    display: flex;
    flex-direction: column;
    min-width: 190px;
    padding: 5px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: 0 4px 14px rgb(16 24 40 / 18%);
}

.row-menu-items a {
    padding: 7px 9px;
    border-radius: 6px;
    font-size: 13px;
    font-weight: 500;
    text-decoration: none;
    color: var(--text);
    white-space: nowrap;
}

.row-menu-items a:hover { background: var(--surface-2); color: var(--accent-text); }

/* One name per row, for the reason spelled out above .sig-pop. */
@supports (anchor-name: --a) {
    .row-menu { anchor-name: var(--anchor); }

    .row-menu-items {
        position: fixed;
        position-anchor: var(--anchor);
        position-area: block-end span-inline-end;
        position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
        left: auto;
        top: auto;
        margin-top: 4px;
    }
}

/* The row that asks for the next page - see wwwroot/js/scroll-load.js. */
.grid tr.scroll-sentinel td {
    padding: 14px 12px;
    text-align: center;
    border-bottom: none;
    background: var(--surface);
}

/* The queued-chaser panel on the invoice list. Wider than a plain row menu because it lists the
   reminders as well as the actions, and its buttons wrap onto their own lines. */
.reminder-panel { min-width: 260px; gap: 4px; }
.reminder-panel .reminder-line { margin: 0 0 6px; padding: 0 4px; font-size: 12.5px; line-height: 1.4; }
.reminder-panel form { margin: 0; }
.reminder-panel .btn { width: 100%; justify-content: flex-start; text-align: left; }

/* Credit notes on the invoice list. The CR code carries the meaning; the colour is the second
   cue - see the --job-* tokens and .job-flag for why. */
.credit-flag {
    display: inline-block;
    padding: 1px 5px;
    margin-right: 5px;
    border-radius: 4px;
    background: var(--job-wasted-journey);
    color: #ffffff;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.04em;
}

.credit-panel { min-width: 250px; }
.credit-panel .pill { font-size: 10.5px; padding: 1px 6px; }

/* ---- saved searches ----------------------------------------------------- */

.saved-searches {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-bottom: 18px;
}

.saved-search { display: inline-flex; gap: 4px; }
.btn-xls { padding: 8px 10px; font-size: 12.5px; }

.field { display: grid; gap: 5px; }
.field label { font-size: 12.5px; font-weight: 600; color: var(--text-muted); }

/* AFTER .field ON PURPOSE, AND IT MATTERS. Fourteen pages carry class="field field-buttons" on
   their button row. While .field came later in this file its `display: grid` won the cascade, one
   column, and every Search / XLS / Reset row rendered as a vertical stack - which is exactly what
   the invoice list's filter card looked like. Declaring it here fixes all fourteen at once; do not
   move it back above .field. */
.field-buttons {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: end;
    gap: 8px;
    padding-bottom: 1px;
}

/* INSIDE A TABLE CELL IT MUST NOT WRAP. A row-actions cell is narrow, so wrapping put Delete on a
   second line and doubled the height of every row in the list. The cell is nowrap instead; if the
   buttons do not fit, the table scrolls sideways, which it already does. */
.grid .field-buttons { flex-wrap: nowrap; }

/* [hidden] IS ONLY display:none IN THE UA STYLESHEET, so any author rule that sets display - and
   .btn does - beats it and the element stays on screen. scroll-load.js hides the pager's links this
   way, and without this they simply did not go. */
[hidden] { display: none !important; }

/* CLIPPED, NOT display:none, AND THE DIFFERENCE IS THE WHOLE POINT. An element hidden with
   display:none or visibility:hidden leaves the accessibility tree AND the tab order; this one stays
   in both, it just occupies no space. That is what a text label on an icon control needs, and what
   a checkbox needs if the Tab key is still to reach it.

   The 1px box with clip-path rather than width:0 is deliberate: a zero-sized element is treated as
   invisible by some screen readers, and `text-indent: -9999px` breaks in right-to-left. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* Invisible until it has focus, then a normal-looking button in the top-left corner. */
.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 100;
    padding: 10px 14px;
    background: var(--accent);
    color: #fff;
    border-radius: 0 0 var(--radius) 0;
    text-decoration: none;
    font-weight: 600;
}

/* :focus, NOT :focus-visible. The link is off-screen until it has focus, so there is no
   mouse-click case to suppress - and a browser or assistive tool that moves focus without deciding
   it is "visible" would otherwise leave the user tabbed onto something they cannot see. */
.skip-link:focus { left: 0; outline: 2px solid var(--text); outline-offset: -4px; }

.input {
    min-width: 260px;
    padding: 8px 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--bg);
    color: var(--text);
    font: inherit;
}

.input:focus-visible, .btn:focus-visible, .grid th a:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

.check { display: flex; align-items: center; gap: 7px; padding-bottom: 8px; font-size: 14px; }
.check input { width: 15px; height: 15px; accent-color: var(--accent); }

.input option.option-group { font-weight: 700; background: var(--accent-soft); }

.btn {
    padding: 8px 14px;
    border: 1px solid transparent;
    border-radius: 8px;
    background: var(--accent);
    color: #fff;
    font: inherit;
    font-weight: 550;
    cursor: pointer;
    text-decoration: none;
    display: inline-block;
}

.btn-quiet {
    background: var(--surface);
    color: var(--text);
    border-color: var(--border);
}

.btn-quiet:hover { background: var(--surface-2); }

/* ---- grid ------------------------------------------------------------ */

.table-scroll {
    overflow-x: auto;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}

.grid { width: 100%; border-collapse: collapse; font-size: 14px; }

.grid th {
    /* Only bites once an ancestor scrolls vertically - see .content-fill. z-index keeps the rows
       from sliding over the headings rather than under them. */
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--surface-2);
    text-align: left;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text-muted);
    white-space: nowrap;
    border-bottom: 1px solid var(--border);
}

.grid th a {
    display: block;
    padding: 10px 12px;
    text-decoration: none;
    color: inherit;
}

.grid th a:hover { color: var(--text); }

/* The sorted column. The tint is the third cue, not the first: the arrow says which way, and
   aria-sort says it to a screen reader - see _SortHeader.cshtml. */
.grid th.sorted { color: var(--accent-text); background: var(--accent-soft); }
.grid th.num a { text-align: right; }
.arrow { opacity: 0.85; font-size: 10px; }

.grid td { padding: 9px 12px; border-bottom: 1px solid var(--border); vertical-align: top; }
.grid tbody tr:last-child td { border-bottom: none; }
.grid tbody tr:hover { background: var(--surface-2); }

.nowrap { white-space: nowrap; }
.site-name { display: block; font-weight: 550; }
.site-special { color: var(--special-invoicing); font-weight: 700; }
.address { display: block; color: var(--text-muted); font-size: 12.5px; margin-top: 1px; }

/* Row highlights, matching the legend above the search filters. */
.grid tbody tr.row-low-value { background: var(--row-low-value); }
.grid tbody tr.row-no-value { background: var(--row-no-value); }
.grid tbody tr.row-po-per-collection { background: var(--row-po-per-collection); }

/* A purchase order's state on the site screen. Two of these reuse the site list's own tokens, so
   "low order value" is one colour across the application rather than two that nearly agree.
   Every row also carries the word in its Status column - the tint is the second cue. */
.grid tbody tr.row-po-open      { background: var(--row-po-open); }
.grid tbody tr.row-po-completed { background: var(--row-po-completed); }
.grid tbody tr.row-low-value    { background: var(--row-low-value); }
.grid tbody tr.row-no-value     { background: var(--row-no-value); }

.swatch.row-po-open      { background: var(--row-po-open); }
.swatch.row-po-completed { background: var(--row-po-completed); }
.grid tbody tr.row-po-month-end { background: var(--row-po-month-end); }
.grid tbody tr[class^="row-"]:hover, .grid tbody tr[class*=" row-"]:hover { filter: brightness(0.95); }

.pill {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
}

.pill-active { background: var(--status-active-bg); color: var(--status-active); }
.pill-lapsed { background: var(--status-lapsed-bg); color: var(--status-lapsed); }
.pill-dormant { background: var(--status-dormant-bg); color: var(--status-dormant); }
.pill-finished { background: var(--status-finished-bg); color: var(--status-finished); }
.pill-cert { background: var(--accent-soft); color: var(--accent-text); }

/* ---- pager ----------------------------------------------------------- */

/* The gap below is the same --stack every other band uses. Without it the month buttons sit flush
   against the filter card below and read as one control group, which they are not. */
.pager { display: flex; align-items: center; gap: 14px; margin: 14px 0 var(--stack); flex-wrap: wrap; }
.pager .muted { margin: 0; }

/* Anything at the far end of a pager - today the collections-confirmed toggle, which acts on the
   month the pager is showing and so belongs beside it rather than on a line of its own. */
.pager-end { margin-left: auto; }

@media (max-width: 760px) {
    /* Once the pager wraps there is no "far end" to push to, and the auto margin would leave the
       button stranded on its own row anyway. */
    .pager-end { margin-left: 0; }
}

@media (max-width: 760px) {
    .shell { flex-direction: column; }

    /* The sidebar stops being a column beside the content and becomes a band above it. It also
       stops being full-height and sticky, or it would take the whole first screen on a phone.
       The nav itself needs no change: collapsed groups are already compact, which is the one
       place this pattern pays for itself twice. */
    .sidebar {
        width: auto;
        flex: none;
        height: auto;
        position: static;
        gap: 14px;
        padding: 14px 16px;
    }

    .sidebar-note { display: none; }
    .content { padding: 18px 16px 40px; }
    .input { min-width: 0; width: 100%; }
}

/* ---- login ------------------------------------------------------------- */

.login-body { background: var(--bg); }

.login-shell {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: 20px;
}

.login-card {
    width: 100%;
    max-width: 340px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 28px 26px;
}

.login-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    font-weight: 650;
    letter-spacing: -0.01em;
    font-size: 18px;
    margin-bottom: 22px;
}

.login-form { display: grid; gap: 14px; }
.login-form .field label { display: block; margin-bottom: 5px; }
.login-form .input { width: 100%; min-width: 0; }
.login-submit { width: 100%; text-align: center; margin-top: 4px; }

.login-error {
    margin: 0;
    padding: 9px 11px;
    border-radius: 8px;
    background: var(--status-finished-bg);
    color: var(--status-finished);
    font-size: 13.5px;
}

/* The collections a refused invoice batch named (D-150). Sits directly under the .form-error that
   introduces it, so it reads as one message rather than a list floating on its own. */
.blocked-jobs {
    margin: -8px 0 14px;
    padding: 10px 14px 10px 32px;
    border-radius: 8px;
    background: var(--status-finished-bg);
    color: var(--status-finished);
    font-size: 13px;
}

.blocked-jobs li { margin-bottom: 3px; }
.blocked-jobs li:last-child { margin-bottom: 0; }

/* ---- top bar (current user) --------------------------------------------- */

.topbar {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 12px;
    margin-bottom: 14px;
    font-size: 13px;
    color: var(--text-muted);
}

.topbar form { margin: 0; }

.topbar-logout {
    background: none;
    border: none;
    padding: 0;
    color: inherit;
    font: inherit;
    text-decoration: underline;
    cursor: pointer;
}

/* ---- client groups' contact lists --------------------------------------- */

.contact-list {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.contact-list li {
    font-size: 13px;
}

/* ---- success measures (year-total row) --------------------------------- */
.grid tbody tr.row-total td { font-weight: 700; background: var(--surface-2); }

/* Totals row for the Internal Reports billables grid. */
.grid tfoot td { font-weight: 700; background: var(--surface-2); border-top: 2px solid var(--border); }

/* Remittance/Index: the ID-range filter needs boxes much narrower than .input's 260px default. */
.input-narrow { min-width: 70px; max-width: 90px; }

/* ---- BACS payments report (expandable invoice breakdown) --------------- */
details > summary { cursor: pointer; color: var(--accent-text); font-weight: 600; }
/* THE BREAKDOWN IS A TABLE INSIDE A TABLE CELL. It began as a bulleted column of bare invoice ids,
   which made one enterprise's row about 470px tall and pushed the other four off the screen; wrapping
   them fixed the height but not the substance - "the invoice numbers only are useless", so the
   breakdown now carries the client, the site and both totals and needs real columns.

   It stays COMPACT so it reads as a detail of the row above rather than a second grid: smaller type,
   tighter cells, and no shadow or radius of its own. */
.bacs-lines {
    margin: 8px 0 0;
    font-size: 12.5px;
    box-shadow: none;
}

.bacs-lines th,
.bacs-lines td { padding: 4px 10px; }

.bacs-lines th { font-size: 11.5px; }

/* ---- WTN receipt (print view) ------------------------------------------ */

.wtn-doc {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 30px;
    max-width: 900px;
    margin: 0 auto 24px;
}

.wtn-doc-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 24px;
    flex-wrap: wrap;
    margin-bottom: 16px;
}

.wtn-doc-title { font-size: 22px; font-weight: 700; }
.wtn-doc-logo { max-width: 200px; max-height: 150px; }

.wtn-detail { width: 100%; border-collapse: collapse; margin-bottom: 4px; }

.wtn-detail td {
    padding: 6px 10px;
    border: 1px solid var(--border);
    vertical-align: top;
}

.wtn-detail td:first-child:not([colspan]) {
    width: 220px;
    text-align: right;
    font-weight: 600;
    color: var(--text-muted);
    background: var(--surface-2);
    white-space: nowrap;
}

.wtn-divider td { border: none; padding-top: 14px; }
.wtn-declaration { text-align: left; }

.wtn-sig-cell { text-align: center; }
/* A CEILING, NOT A HEIGHT. The tablets send the whole pad (1339 x 566 for WTN-274-552); the Flutter
   app sends the signature cropped to its ink - 126 x 105, and 276 x 6 for a straight stroke - and a
   fixed 150px height blew those up into blurred black bars on the note e-mailed to the client
   (D-355, measured 2026-11-06). Large signatures still shrink to the box; small ones stay sharp. */
.wtn-sig-cell img { max-width: 100%; max-height: 150px; width: auto; height: auto; }
.wtn-sig-missing { color: var(--text-muted); font-style: italic; }

.wtn-doc-footer {
    display: flex;
    justify-content: space-between;
    gap: 24px;
    flex-wrap: wrap;
    margin-top: 4px;
}

.wtn-doc-footer .cwr-logo { width: 75px; float: left; margin-right: 10px; }
.wtn-doc-footer .cwr-blurb { color: var(--accent-text); font-size: 12px; margin: 0; }

/* ONE WTN, ONE SHEET. At the screen's sizes a WTN with an enterprise logo and two signatures runs a
   quarter of a page onto a second sheet, its collector signature and footer stranded there - measured
   from a print render of WTN-243-480's content, 2026-10-18. The legacy page only ever fitted because
   ABCpdf squeezed it to about 74%. Paper loses the card's padding (the page margin does that job),
   some cell padding and two inches of signature box, and keeps the type at full size.
   THE HEADER AND FOOTER STAY ON ONE ROW EACH, as the legacy note had them. Left to wrap, the long
   title pushes the enterprise logo onto a row of its own and the long enterprise address pushes the
   Community Wood Recycling block onto another, and those two rows were what still tipped the footer
   over. */
@media print {
    .sidebar, .topbar, .no-print { display: none !important; }
    .content { padding: 0; }
    .wtn-doc { box-shadow: none; border: none; max-width: none; padding: 0; }
    .wtn-detail td { padding: 3px 8px; }
    .wtn-divider td { padding-top: 6px; }
    .wtn-sig-cell img { max-height: 90px; }

    /* The member's logo at 150px tall, plus the destination-of-waste row added for the e-mailed
       copy (D-355), put WTN-235-2110 on two sheets (measured 2026-11-06: 1,050px of a 1,047px
       page even at 90px). Narrower, it also lets the title stay on one line. */
    .wtn-doc-logo { max-height: 80px; max-width: 160px; }

    .wtn-doc-header,
    .wtn-doc-footer { flex-wrap: nowrap; }

    .wtn-doc-title { flex: 1 1 auto; min-width: 0; }
    .wtn-doc-logo { flex: none; }
    .wtn-doc-footer > * { flex: 1 1 0; }
}

/* PAPER DOES NOT SCROLL. .content-fill pins the page to the window and puts the list in a scroll
   box, which on paper would print one screenful and silently drop the rest. Undone for every print
   context, in one place, rather than remembered per page. */
@media print {
    .content-fill {
        display: block;
        height: auto;
        overflow: visible;
    }

    /* A DESCENDANT, NOT A CHILD. The home page's list is nested one level down inside .map-split,
       and a `>` here printed one screenful of it and dropped the rest - exactly the failure the
       comment above is about. */
    .content-fill .table-scroll { overflow: visible; }

    /* The client list's form is a flex column on screen so the grid can fill the window. On paper
       it must not be one, or the table is capped to a page and the rest is dropped - the same
       reason .content-fill itself is reset above. The min-width query that sets it is not a
       screen-only query, so it applies here unless undone. */
    .content-fill > .list-form {
        display: block;
        min-height: 0;
    }
    .grid th { position: static; }

    /* Same undoing for the home page's two panes: stacked on paper, and the map gets a definite
       height because a flexed one collapses to nothing once the flex row is gone. */
    #map-page { display: block; overflow: visible; }
    .map-split { display: block; }
    .map-col { display: block; }

    /* SELECTOR MATCHED TO THE CONTAINER QUERY'S, not shortened. The rule this has to beat is
       `.map-split > .map-col > .map-canvas` at three classes; written as `.map-col > .map-canvas`
       it lost on specificity and the map printed at its 200px floor instead. Later in the file is
       only a tie-breaker when the specificity is equal. */
    .map-split > .map-col > .map-canvas { height: 90mm; min-height: 0; margin-bottom: 8mm; }
}
/* .bacs-lines li has gone with the <ul> - the breakdown is a table now. */

/* ---- monthly report detail ----------------------------------------------- */

.report-meta {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 4px 16px;
    margin: 4px 0 20px;
    font-size: 14px;
}

.report-meta dt { color: var(--text-muted); font-weight: 600; }
.report-meta dd { margin: 0; }

/* ---- the heading that starts a block -----------------------------------
   A RULE, NOT JUST A GAP. Twenty-two pixels of margin is not a boundary: on the site screen,
   which is six stacked sections of form fields, every section ran into the next one and the
   headings read as labels rather than as divisions. A hairline does the separating so the
   spacing does not have to grow to do it.

   --stack is the standard gap between stacked page blocks, so the space above comes from that
   token rather than a fresh literal - see the CSS conventions in CLAUDE.md.

   :first-child drops the rule and the space, because a block at the top of a card has nothing
   above it to be separated from. */
.section-heading {
    font-size: 16px;
    margin: var(--stack) 0 12px;
    padding-top: calc(var(--stack) - 8px);
    border-top: 1px solid var(--border);
}

.section-heading:first-child {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
}

/* Paper prints the rule too - it is the only thing dividing the sections there either. */

/* ---- invoice print view (Invoicing/Detail) ------------------------------ */

.invoice-doc {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 30px;
    max-width: 900px;
    margin: 0 auto 24px;
}

.invoice-doc-header {
    display: flex;
    justify-content: space-between;
    gap: 24px;
    flex-wrap: wrap;
    margin-bottom: 24px;
}

.invoice-doc-address { line-height: 1.7; }

/* 250px is the legacy logo's width on its 800px page, which comes out the same size on paper. */
.invoice-doc-logo {
    display: block;
    width: 250px;
    max-width: 100%;
    height: auto;
    margin-bottom: 20px;
}

/* Black line art vanishes on the dark surface, so the screen inverts it. Paper is white whatever
   the theme, so print takes the original back - this block has to stay after the dark one. */
@media (prefers-color-scheme: dark) {
    .invoice-doc-logo { filter: invert(1); }
}

@media print {
    .invoice-doc-logo { filter: none; }
}

.invoice-doc-meta { text-align: right; min-width: 260px; }
.invoice-doc-meta p { margin: 4px 0; color: var(--accent-text); }

.invoice-doc-title {
    font-size: 26px;
    text-align: center;
    margin-bottom: 8px;
}

.invoice-overdue {
    color: var(--status-finished);
    font-weight: 700;
    font-size: 15px;
    margin-top: 6px;
}

.invoice-lines th, .invoice-lines td { text-align: right; }
.invoice-lines th:first-child, .invoice-lines td:first-child { text-align: left; }

.invoice-totals { text-align: right; margin-top: 16px; }
.invoice-totals p { margin: 4px 0; }

.invoice-footnotes {
    margin-top: 18px;
    font-size: 11.5px;
    color: var(--text-muted);
}

.invoice-doc-footer {
    margin-top: 30px;
    padding-top: 16px;
    border-top: 1px solid var(--border);
    font-size: 12px;
    color: var(--text-muted);
    display: flex;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
}

.invoice-legal { max-width: 420px; margin-top: 6px; }

@media print {
    .sidebar, .topbar, .no-print { display: none !important; }
    .content { padding: 0; }
    .invoice-doc { box-shadow: none; border: none; max-width: none; }
}

/* ---- a printed document's footer sits at the foot of the page ---------- */

/* NOT WHEREVER THE CONTENT HAPPENS TO END. Greg Smale, 2026-10-13: "footers on some pdfs don't seem
   to be at the bottom of the page". The legacy ABCpdf invoice never pinned it either - it followed
   an 850px block, 74% down the page (D-326) - and these templates put it straight after the totals:
   53% down on a one-line invoice, measured from a real render.

   .doc-page on the document, .doc-foot on its footer as a DIRECT child. One rule for the invoice,
   the self-billed invoice and the WTN, so the three cannot drift apart.

   100vh IS THE PRINTABLE PAGE AREA in Chromium's print layout - measured, not assumed: a
   min-height: 100vh column printed to A4 with 10mm margins put its last line at the foot of page 1
   and made no second page. So this follows whatever margins are in force, PdfDocumentOptions' for a
   PDF or the browser's own for Print, and no page size is written into CSS, which
   PdfDocumentOptions keeps out deliberately.

   PLACED AFTER BOTH DOCUMENTS' SCREEN RULES, and it has to be: they set `margin: 0 auto 24px` with
   the same specificity, a media query adds none, and the later rule wins. That 24px under a
   page-tall box is a second sheet with nothing on it.

   NOTHING ELSE MAY PRINT ABOVE THE DOCUMENT, for the same reason - a page-tall box below anything
   at all is pushed onto a second sheet, footer and all. Both pages mark everything else no-print;
   the impersonation banner was the one thing left, and "Viewing as..." means nothing on paper.

   A DOCUMENT LONGER THAN A PAGE IS NOT HELPED: CSS cannot know the page count, so its footer still
   follows the content. Invoice 61201 (15 lines) ends page 1 and its footer starts page 2, before and
   after. Paper only - the screen view does not change. */
@media print {
    .impersonation-banner { display: none !important; }

    .doc-page {
        min-height: 100vh;
        display: flex;
        flex-direction: column;
        margin: 0;
    }

    .doc-page > .doc-foot {
        margin-top: auto;
        break-inside: avoid;
    }
}

/* ---- remittance advice (print view) ------------------------------------ */

.remit-doc {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 24px 26px;
    margin-bottom: 24px;
}

.remit-doc-head {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 24px;
    margin-bottom: 18px;
}

.remit-doc-address { line-height: 1.5; }

.remit-doc-title { text-align: right; max-width: 320px; }

.remit-doc-heading {
    font-size: 18px;
    font-weight: 700;
    letter-spacing: 0.02em;
    margin-bottom: 8px;
}

.remit-doc-totals { text-align: right; margin-top: 10px; }
.remit-doc-totals p { margin: 4px 0; }

.remit-doc-footer {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 24px;
    margin-top: 22px;
    padding-top: 14px;
    border-top: 1px solid var(--border);
    font-size: 12px;
}

/* ---- Sites/Index: per-row Notes (details) and Delete actions ----------- */
/* THE SITE ACTIONS NOW LIVE IN A ROW MENU, so this class no longer stacks a cell three lines tall.
   It styles the popover's contents instead - see .row-menu-items, and the comment on the cell in
   Pages/Sites/Index.cshtml for what it replaced. Wider than a plain row menu because it carries the
   two notes boxes as well as the two actions, the same reason .reminder-panel is. */
.site-actions-menu { min-width: 290px; gap: 6px; }
.site-actions-menu form { margin: 0; }
.site-actions-menu .btn { width: 100%; justify-content: center; }

.notes-form { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; }
.notes-form textarea { min-height: 54px; }

/* ---- Generic inline validation/error banner, non-login forms ----------- */
/* Same styling as .login-error under a reusable name - Drivers, StaffList and Sites/Edit all
   reference this. */
.form-error {
    margin: 0 0 14px;
    padding: 9px 11px;
    border-radius: 8px;
    background: var(--status-finished-bg);
    color: var(--status-finished);
    font-size: 13.5px;
}

/* ---- Sites/Edit: one field spanning the full row inside .filters ------- */
.field-wide { flex: 1 1 100%; }
.field-wide .input { width: 100%; }

/* ---- Company/Edit, Enterprise/Edit: two-column entity forms ------------ */
.form-columns {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 24px;
    margin-bottom: 20px;
}

.field-column { display: flex; flex-direction: column; gap: 14px; }

@media (max-width: 760px) {
    .form-columns { grid-template-columns: 1fr; }
}

/* A form that has to sit beside links in a table cell without becoming its own block - used for
   actions that write state and therefore cannot be plain anchors. */
.inline-form { display: inline; }

/* ---- the member's success-measures return (EnterpriseOnly/SuccessMeasures) ----------------
   A numbered questionnaire rather than a grid of fields: the questions are long sentences and
   members refer to them by number, so the numbering is content and <ol> carries it.            */

.sm-form { max-width: 900px; }
.sm-current { font-weight: 600; padding: 0 4px; }

.sm-questions { margin: 0 0 4px; padding-left: 28px; }
.sm-questions > li { margin-bottom: 18px; line-height: 1.5; }
.sm-questions label { display: block; margin-bottom: 6px; }

.sm-subquestions {
    list-style: none;
    margin: 10px 0 0;
    padding: 0 0 0 4px;
}

.sm-subquestions > li {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 6px;
}

.sm-subquestions label { display: inline; margin: 0; flex: 0 0 220px; }

.sm-number { width: 120px; font-variant-numeric: tabular-nums; }
.sm-unit { margin-left: 6px; color: var(--text-muted); }

/* Asterisk on required questions. Colour alone would not say it, so the mark itself is the
   signal and the page head explains what it means. */
.sm-required { color: var(--status-lapsed); font-weight: 600; }

.sm-help {
    display: block;
    margin-top: 6px;
    color: var(--text-muted);
    font-size: 13px;
    line-height: 1.5;
}

.sm-saved {
    margin: 0 0 16px;
    padding: 10px 14px;
    background: var(--status-active-bg);
    border: 1px solid var(--status-active);
    border-left-width: 3px;
    border-radius: var(--radius);
    font-size: 14px;
}

/* ---- impersonation banner (_Layout, every page) ------------------------------------------
   Amber rather than red: viewing as a member is a normal, deliberate act, not an error. It only
   has to be impossible to miss, because the failure it prevents is reading one member's figures
   as the whole organisation's - which produces no error at all, just a wrong conclusion.        */

.impersonation-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    margin: 0 0 18px;
    padding: 12px 16px;
    background: var(--status-lapsed-bg);
    border: 1px solid var(--status-lapsed);
    border-left-width: 3px;
    border-radius: var(--radius);
    font-size: 14px;
    line-height: 1.55;
}

.impersonation-banner p { margin: 0; flex: 1 1 340px; }
.impersonation-banner strong { color: var(--status-lapsed); }
.impersonation-banner form { flex: 0 0 auto; }
.impersonation-banner .btn-quiet { white-space: nowrap; }

/* ---- Ask a question (Pages/Ask) ----------------------------------------------------------
   Built entirely from the tokens at the top of this file, so the dark block at line 37 covers
   this section without a single extra rule - and so PdfStylesheet's dark-block strip keeps
   working. No literal colours below, deliberately.                                            */

.ask-form { max-width: 780px; margin: 0 0 20px; }
.ask-form .field { margin-bottom: 10px; }

.ask-form textarea.input {
    width: 100%;
    min-height: 74px;
    resize: vertical;
    font: inherit;
    line-height: 1.5;
}

.ask-thinking { color: var(--text-muted); font-size: 14px; }
.ask-hint { margin: 10px 0 0; color: var(--text-muted); font-size: 13px; }

/* The answer card. The accent edge is the one piece of colour on the page, so the figures are
   what the eye lands on rather than the prose underneath them. */
.ask-answer {
    max-width: 920px;
    margin: 0 0 20px;
    padding: 20px 22px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}

.ask-asked { margin: 0 0 6px; color: var(--text-muted); font-size: 14px; }
.ask-understood { margin: 0 0 14px; font-size: 14px; }
.ask-aside { color: var(--text-muted); }
.ask-empty-note { margin: 0 0 14px; color: var(--text-muted); font-size: 14px; }

.ask-figures {
    display: flex;
    flex-wrap: wrap;
    gap: 12px 32px;
    margin: 0 0 16px;
    padding: 14px 0;
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
}

.ask-figure dt {
    margin: 0;
    color: var(--text-muted);
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
}

/* tabular-nums so a column of figures lines up on the decimal point. */
.ask-figure dd {
    margin: 3px 0 0;
    font-size: 18px;
    font-variant-numeric: tabular-nums;
}

.ask-figure-lead dd { font-size: 27px; font-weight: 600; color: var(--accent-text); }

.ask-label {
    margin: 0 0 5px;
    color: var(--text-muted);
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: .04em;
}

.ask-prose { margin: 0 0 14px; font-size: 15px; line-height: 1.55; }
.ask-provenance { margin: 0; color: var(--text-muted); font-size: 12px; line-height: 1.5; }

/* Every non-answer outcome: not configured, needs clarification, declined, unavailable. One
   treatment on purpose - none of them is a crash, and none should read as one. */
.ask-declined {
    max-width: 920px;
    margin: 0 0 20px;
    padding: 16px 20px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-left: 3px solid var(--status-lapsed);
    border-radius: var(--radius);
}

.ask-declined h2 { margin: 0 0 8px; font-size: 16px; }
.ask-declined p { margin: 0 0 10px; font-size: 14px; line-height: 1.55; }
.ask-declined p:last-child { margin-bottom: 0; }
.ask-declined ul { margin: 0 0 10px; padding-left: 20px; font-size: 14px; }
.ask-declined li { margin-bottom: 3px; }

.ask-working, .ask-hints {
    max-width: 920px;
    margin: 0 0 20px;
    padding: 16px 20px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.ask-hints h2 { margin: 0 0 8px; font-size: 15px; }

/* The working is a <details>. Closed, it should read as one quiet line rather than an empty
   card, so the padding stays tight and the summary carries the whole height. */
.ask-working { padding: 12px 20px; }

.ask-working summary {
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    font-size: 14px;
    font-weight: 600;
    color: var(--accent-text);
    /* Both needed: the standard property and the WebKit pseudo-element, or the default triangle
       sits alongside the one drawn below. */
    list-style: none;
}

.ask-working summary::-webkit-details-marker { display: none; }
.ask-working summary::before { content: "\25B8"; font-size: 11px; }
.ask-working[open] summary::before { content: "\25BE"; }
.ask-working summary:hover { text-decoration: underline; }
.ask-working[open] summary { margin-bottom: 12px; }
.ask-working[open] { padding-bottom: 16px; }
.ask-working .report-meta { margin-bottom: 0; }
.ask-hints ul { margin: 6px 0 14px; padding-left: 20px; font-size: 14px; }
.ask-hints li { margin-bottom: 4px; }
.ask-hints p:last-child { margin-bottom: 0; font-size: 13px; color: var(--text-muted); line-height: 1.55; }

@media (max-width: 760px) {
    .ask-figures { gap: 12px 20px; }
    .ask-figure-lead dd { font-size: 23px; }
}

@media print {
    .no-print, .sidebar, .topbar, .page-head { display: none !important; }
    .content { padding: 0; }
    .remit-doc { box-shadow: none; border: none; border-radius: 0; page-break-after: always; }
    .remit-doc:last-child { page-break-after: auto; }
    .table-scroll { overflow: visible; border: none; box-shadow: none; }
}

/* ---------------------------------------------------------------------------------------------
   THE REPORTING API PAGE: documentation blocks, and the key management table.

   Built from the tokens rather than fresh colours, so both themes are covered by definitions that
   already exist - the rule is that a colour with only one definition is a bug, and the safest way
   to keep it is not to introduce one. Everything below resolves through --surface, --border,
   --text-muted and the accent pair, all of which are already defined in :root AND in the dark
   block.

   .card is the same visual language as .filters - same surface, border, radius, shadow and the
   same --stack beneath it - because a page of stacked blocks should have one rhythm, not two.
   --------------------------------------------------------------------------------------------- */
.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 16px 18px;
    margin-bottom: var(--stack);
    box-shadow: var(--shadow);
}

.card > h2:first-child { margin-top: 0; }
.card h3 { margin-bottom: 4px; }

/* A confirmation, not an alert. Uses the accent pair so it reads as "this worked". */
/* ---- the site summary sheet (Pages/Sites/Print) ------------------------
   Viewed in a tab and then printed, so it has to work on both. NO NEW COLOUR TOKENS: it is built
   entirely from --border, --text-muted and --surface-2, which are already defined in both themes,
   so there is nothing here that can be right in one theme and wrong in the other.

   THREE COLUMNS WHERE THERE IS ROOM, one where there is not. The original is three fixed-width
   floats - 273px, 311px, and whatever is left - which is why it only ever fitted one paper size. */
/* A SHEET, NOT A FULL-WIDTH PAGE. Without a cap the three columns stretch to whatever the window
   is - measured at 816px each on a wide monitor - and the reading line gets far too long. Print
   drops the cap, because the paper already is one. */
.print-sheet { max-width: 62rem; margin: 0 auto; }

.print-bar {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-bottom: var(--stack);
}

.print-columns {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--stack);
    margin-bottom: var(--stack);
}

.print-columns h2,
.print-grid + h2 {
    font-size: 15px;
    margin: 0 0 8px;
    padding-bottom: 4px;
    border-bottom: 1px solid var(--border);
}

/* Label above value, not beside it. A two-column dl wraps badly at 240px and the addresses are the
   widest thing on the sheet. */
.print-fields { margin: 0; }
.print-fields dt {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    margin-top: 8px;
}
.print-fields dd { margin: 1px 0 0; font-size: 13px; }
.print-fields dt:first-child { margin-top: 0; }

/* The notes block, which is prose. Kept to a readable measure rather than the full sheet width -
   a 62rem line of running text is unreadable, and these fields run to hundreds of lines. */
.print-notes { margin-bottom: var(--stack); }
.print-notes dd { max-width: 90ch; white-space: pre-wrap; }

.print-grid { font-size: 12px; }
.print-footer { margin-top: var(--stack); font-size: 11px; color: var(--text-muted); }

@media print {
    .print-bar { display: none; }
    .print-sheet { max-width: none; }

    /* Keep a column whole rather than splitting a field off its label across a page break. */
    .print-columns section { break-inside: avoid; }
    .print-footer { break-before: avoid; }
}

.notice {
    background: var(--accent-soft);
    color: var(--accent-text);
    border: 1px solid var(--accent);
    border-radius: var(--radius);
    padding: 10px 14px;
    margin-bottom: var(--stack);
}

/* THE FAILURE VARIANT. Both themes, and the tint is never the only cue - every notice using this
   opens with a word ("Not sent:") because the client is colourblind and so is one man in twelve.
   Vermillion from Okabe-Ito, which stays distinguishable from the green above in greyscale
   because it is darker as well as a different hue. */
:root {
    --warn: #a03414;
    --warn-soft: #fdece6;
    --warn-text: #7d2810;
}

@media (prefers-color-scheme: dark) {
    :root {
        --warn: #e06e46;
        --warn-soft: #3a1c12;
        --warn-text: #f3b79c;
    }
}

.notice-warn {
    background: var(--warn-soft);
    color: var(--warn-text);
    border-color: var(--warn);
}

/* CODE THAT MUST NOT WRAP AND MUST NOT WIDEN THE PAGE. A Power BI snippet is wider than a narrow
   window, so it scrolls inside its own box - the body never scrolls sideways. */
.code-block,
.key-value {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 12px 14px;
    overflow-x: auto;
    font-family: ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace;
    font-size: 13px;
    line-height: 1.5;
}

/* The one-time key. Bigger and selectable in one gesture, because it is going to be copied. */
.key-value {
    font-size: 15px;
    user-select: all;
    word-break: break-all;
    white-space: pre-wrap;
}

.key-issued { border-color: var(--accent); }

.endpoint { border-top: 1px solid var(--border); padding-top: 14px; margin-top: 14px; }
.endpoint:first-of-type { border-top: 0; padding-top: 0; margin-top: 0; }
.endpoint h3 { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* Columns / measures / group-by side by side where there is room, stacked where there is not. */
.endpoint-lists {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 14px 22px;
}

.endpoint-lists h4 { margin: 0 0 6px; font-size: 13px; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); }
.endpoint-lists dl { margin: 0; }
.endpoint-lists dt { margin-top: 8px; }
.endpoint-lists dt:first-child { margin-top: 0; }
.endpoint-lists dd { margin: 2px 0 0; color: var(--text-muted); font-size: 13px; }

/* Paper does not scroll, and nobody prints a credential on purpose. */
@media print {
    .card { break-inside: avoid; box-shadow: none; }
    .code-block, .key-value { overflow-x: visible; white-space: pre-wrap; }
    .key-issued { display: none !important; }
}

/* ---------------------------------------------------------------------------------------------
   THE LINE CHART. Server-rendered SVG - see Pages/LineChartView.cs for why, rather than Chart.js.

   THE PALETTE IS OKABE-ITO, which is designed to stay distinguishable under all three common forms
   of colourblindness. It is not the only cue: every series also has its own dash pattern and its
   own marker shape, and either identifies the line on its own. Print this in greyscale and it is
   still readable, which is the actual test.

   Pure yellow (#F0E442) is deliberately absent - it is part of Okabe-Ito but disappears as a thin
   line on a white background. Its slot is taken by a second blue, which the dash and marker still
   separate from the first.

   Both themes, as the rule requires. The dark values are lightened rather than reused: the same
   hues at the same lightness against #1a1e25 lose the darker end of the set entirely.
   --------------------------------------------------------------------------------------------- */
:root {
    --chart-1: #0072b2;   /* blue */
    --chart-2: #d55e00;   /* vermillion */
    --chart-3: #009e73;   /* bluish green */
    --chart-4: #cc79a7;   /* reddish purple */
    --chart-5: #e69f00;   /* orange */
    --chart-6: #56b4e9;   /* sky blue */
    --chart-7: #4c4c4c;   /* near-black, distinct in greyscale */
    --chart-8: #8c5a00;   /* dark amber */
    --chart-grid: #e6e9ee;
    --chart-axis: #9aa3ad;
    --chart-surface: #ffffff;
}

/* Dark, exactly the way the rest of this stylesheet does it - a plain media query on :root, with
   no data-theme guard. This application follows the operating system and has no theme toggle, so a
   guard here would be the only rule in the file that responds to one, and would imply a feature
   that does not exist. If a toggle is ever added it goes in one place, at the top, for every token
   at once. */
@media (prefers-color-scheme: dark) {
    :root {
        --chart-1: #6ab7ff;
        --chart-2: #ff8c4b;
        --chart-3: #3ecf9a;
        --chart-4: #f0a3c8;
        --chart-5: #ffc04d;
        --chart-6: #9ad8ff;
        --chart-7: #c3ccd6;
        --chart-8: #d9a24a;
        --chart-grid: #2e343d;
        --chart-axis: #6d7681;
        --chart-surface: #1a1e25;
    }
}

.chart {
    margin: 0 0 var(--stack);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 14px 16px;
    box-shadow: var(--shadow);
}

.chart figcaption {
    font-weight: 600;
    margin-bottom: 8px;
}

.chart-note {
    display: block;
    font-weight: 400;
    font-size: 13px;
    color: var(--text-muted);
    margin-top: 2px;
}

/* Scales to the container and stays sharp at any size, which a <canvas> does not.

   BUT IT MUST NOT EAT THE PAGE. An SVG with a viewBox and width:100% takes its height from the
   aspect ratio, so on a 2,234px-wide window this drew itself 884px tall - which pushed the legend
   and the table below the fold on a .content-fill page, and a content-fill page does not scroll.
   The legend and the data underneath were simply unreachable. Measured, not theorised.

   max-height plus preserveAspectRatio="meet" scales the drawing down to fit instead: it stays
   centred and keeps its proportions, and everything below it stays on screen. */
.chart-svg {
    width: 100%;
    height: auto;
    max-height: min(400px, 40vh);
    display: block;
}

/* On a narrow screen the cap would squash a 3.2:1 box to a couple of centimetres. Let it take its
   natural height there and let the page scroll instead - which it does, because the fill layout
   only applies where there is height to fill. */
@media (max-width: 700px) {
    .chart-svg { max-height: none; }
}

.chart-grid { stroke: var(--chart-grid); stroke-width: 1; }
.chart-axis { stroke: var(--chart-axis); stroke-width: 1.5; }

.chart-axis-label { fill: var(--text-muted); font-size: 12px; }
.chart-axis-y { text-anchor: end; }
.chart-axis-x { text-anchor: middle; }

.chart-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart-marker { stroke-width: 0; }

/* An open marker is the same shape drawn hollow - a fourth distinguishing cue, and it stops
   overlapping points from merging into one blob. */
.chart-marker-open { fill: var(--chart-surface); stroke-width: 2; }

/* ---- categorical bar chart ---------------------------------------------
   See BarChartView for why bars exist alongside the line chart. The rules here are only about
   size and ink; everything that carries meaning is length, position and printed text.

   THE HEIGHT CAP IS LIFTED. .chart-svg caps at min(400px, 40vh) so a line chart cannot push a
   table off a content-fill page. A bar chart's height IS its data - twenty-five bars squeezed into
   400px are sixteen pixels apart and unreadable - and it is only drawn on the Report Builder,
   which sets ContentFill = false and scrolls. If a bar chart is ever put on a filling page, this
   is the rule that has to be revisited rather than worked around there. */
.chart-svg-bars { max-height: none; }

/* ONE COLOUR WHEN THERE IS ONE SERIES. Length is the comparison and the name beside it is the
   identity, so colour carries nothing and is allowed to be decoration. --chart-1 is the first
   Okabe-Ito entry, defined for both themes. A STACK is different: its segments have to be told
   apart, so each takes its own colour AND a hatch pattern - either identifies it alone, which is
   what keeps the chart readable in greyscale and for the one man in twelve who is colourblind. */
.chart-bar { fill: var(--chart-1); }

.chart-seg-0, .chart-seg-0.chart-seg-fill { fill: var(--chart-1); }
.chart-seg-1, .chart-seg-1.chart-seg-fill { fill: var(--chart-2); }
.chart-seg-2, .chart-seg-2.chart-seg-fill { fill: var(--chart-3); }
.chart-seg-3, .chart-seg-3.chart-seg-fill { fill: var(--chart-4); }
.chart-seg-4, .chart-seg-4.chart-seg-fill { fill: var(--chart-5); }
.chart-seg-5, .chart-seg-5.chart-seg-fill { fill: var(--chart-6); }
.chart-seg-6, .chart-seg-6.chart-seg-fill { fill: var(--chart-7); }
.chart-seg-7, .chart-seg-7.chart-seg-fill { fill: var(--chart-8); }

/* The hatch itself, drawn over the segment's own colour in the pattern tile. */
.chart-seg-hatch { stroke: var(--chart-surface); opacity: 0.85; }

/* A COLUMN CHART GROWS SIDEWAYS, so it scrolls in its own box rather than shrinking to fit and
   taking the labels with it. The bars form grows downwards and needs no such thing. */
.chart-scroll { overflow-x: auto; }
.chart-svg-columns { max-height: none; min-width: 100%; }

/* A row with no figure at all. A zero-length bar would be indistinguishable from a real zero, so
   the row gets a tick on the axis and the words instead - see PlottedBar.Missing. */
.chart-bar-none { stroke: var(--chart-axis); stroke-width: 2; }

.chart-bar-name { fill: var(--text); font-size: 13px; }
.chart-bar-value { fill: var(--text-muted); font-size: 12px; font-variant-numeric: tabular-nums; }
.chart-marker-stroke { fill: none; stroke-width: 2; }

/* One rule per series: the colour lands on the line, the marker and the legend key together. */
.chart-series-0 .chart-line, .chart-series-0 .chart-marker-open, .chart-series-0 .chart-marker-stroke { stroke: var(--chart-1); }
.chart-series-0 .chart-marker { fill: var(--chart-1); }
.chart-series-1 .chart-line, .chart-series-1 .chart-marker-open, .chart-series-1 .chart-marker-stroke { stroke: var(--chart-2); }
.chart-series-1 .chart-marker { fill: var(--chart-2); }
.chart-series-2 .chart-line, .chart-series-2 .chart-marker-open, .chart-series-2 .chart-marker-stroke { stroke: var(--chart-3); }
.chart-series-2 .chart-marker { fill: var(--chart-3); }
.chart-series-3 .chart-line, .chart-series-3 .chart-marker-open, .chart-series-3 .chart-marker-stroke { stroke: var(--chart-4); }
.chart-series-3 .chart-marker { fill: var(--chart-4); }
.chart-series-4 .chart-line, .chart-series-4 .chart-marker-open, .chart-series-4 .chart-marker-stroke { stroke: var(--chart-5); }
.chart-series-4 .chart-marker { fill: var(--chart-5); }
.chart-series-5 .chart-line, .chart-series-5 .chart-marker-open, .chart-series-5 .chart-marker-stroke { stroke: var(--chart-6); }
.chart-series-5 .chart-marker { fill: var(--chart-6); }
.chart-series-6 .chart-line, .chart-series-6 .chart-marker-open, .chart-series-6 .chart-marker-stroke { stroke: var(--chart-7); }
.chart-series-6 .chart-marker { fill: var(--chart-7); }
.chart-series-7 .chart-line, .chart-series-7 .chart-marker-open, .chart-series-7 .chart-marker-stroke { stroke: var(--chart-8); }
.chart-series-7 .chart-marker { fill: var(--chart-8); }

/* The open markers keep the fill they were given above; re-stated after it so it wins. */
.chart-marker-open { fill: var(--chart-surface); }

.chart-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 18px;
    list-style: none;
    margin: 10px 0 0;
    padding: 0;
    font-size: 13px;
}

.chart-legend li { display: flex; align-items: center; gap: 7px; }
.chart-key { width: 34px; height: 12px; flex: 0 0 auto; }

/* Paper does not scroll, and a chart is the one thing on these pages worth the ink. */
@media print {
    .chart { break-inside: avoid; box-shadow: none; }
    .chart-svg { max-height: 9cm; }
}

/* ---------------------------------------------------------------------------------------------
   THE REPORT BUILDER'S FIGURE CARDS. Tokens only, so both themes come from definitions that
   already exist - the same reasoning as the .card block above.
   --------------------------------------------------------------------------------------------- */
.figure-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 12px;
}

.figure {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 12px 14px;
}

/* The measure the reader chose. A LEFT BAR, not just a tint - the accent has to survive greyscale
   and colourblindness, so the shape carries it too. */
.figure-lead {
    border-left: 4px solid var(--accent);
    background: var(--accent-soft);
}

.figure-label { font-size: 13px; color: var(--text-muted); }
.figure-value { font-size: 22px; font-weight: 600; margin: 2px 0 6px; }
.figure-lead .figure-value { color: var(--accent-text); }
.figure-definition { font-size: 12px; color: var(--text-muted); line-height: 1.45; }

.notes { margin: 0; padding-left: 20px; }
.notes li { margin-bottom: 8px; line-height: 1.5; }

@media print {
    .figure-grid { break-inside: avoid; }
}

/* ---------------------------------------------------------------------------------------------
   THE AUTO-CORRESPONDENCE EDITOR.

   Tokens only, so both themes come out of definitions that already exist - the same reasoning as
   the .card block. The one new pair is --warn, because a confirmation panel is not an accent and
   not a status pill; it is defined for both themes below and NEVER carries the meaning on its own,
   which is why the panel also has a heading that says what it is.
   --------------------------------------------------------------------------------------------- */

:root {
    --warn: #a45b00;
    --warn-bg: #fdf0dd;
    --warn-text: #7a4400;
}

@media (prefers-color-scheme: dark) {
    :root {
        --warn: #e2a24c;
        --warn-bg: #33270f;
        --warn-text: #f0c384;
    }
}

/* A CONFIRMATION, AND IT HAS TO LOOK DIFFERENT FROM .notice, which means "this worked". The left
   bar carries it as well as the tint, because a tint alone is not a cue - one man in twelve cannot
   separate this hue from the accent green. */
.confirm-card {
    background: var(--warn-bg);
    border-color: var(--warn);
    border-left: 4px solid var(--warn);
    color: var(--warn-text);
}

.confirm-card h3 { color: var(--warn-text); margin-top: 0; }
.confirm-card .muted { color: var(--warn-text); opacity: 0.85; }
.confirm-list { margin: 0 0 10px; padding-left: 20px; }
.confirm-list li { margin-bottom: 6px; line-height: 1.5; }

/* The three recipient boxes. A grid of equal tracks rather than a wrapping flex row - the same
   correction .filters needed: with three boxes of very different lengths, wrapping leaves the
   short one marooned beside a column of eleven checkboxes. */
.route-columns {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 14px;
    margin-bottom: var(--stack);
}

.route-box {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-2);
    padding: 10px 14px 4px;
    min-width: 0;
}

.route-box legend {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text-muted);
    padding: 0 6px;
}

.route-list { list-style: none; margin: 0; padding: 0; }
.route-list > li { margin-bottom: 6px; }
.route-list .check { padding-bottom: 0; }
.route-detail { margin: 0 0 0 22px; }

/* A number and its unit read as one control, so they sit on one line. */
.field-unit { display: flex; align-items: baseline; gap: 8px; }
.field-unit .input { width: auto; }

/* ---- the merge-field palette ---- */

.token-palette {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-2);
    padding: 10px 14px;
    margin-bottom: var(--stack);
}

.token-palette summary { font-weight: 600; cursor: pointer; }
.token-list { list-style: none; display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 0; padding: 0; }

.token {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
    padding: 3px 8px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface);
    color: var(--text);
    cursor: pointer;
}

.token:hover { border-color: var(--accent); color: var(--accent-text); background: var(--accent-soft); }

/* What the sanitiser removed, and which merge fields cannot be traced. Not an error - a report. */
.notes-panel {
    border: 1px solid var(--border);
    border-left: 4px solid var(--warn);
    border-radius: var(--radius);
    background: var(--surface-2);
    padding: 10px 14px;
    margin-bottom: var(--stack);
}

.notes-panel h3 { margin: 0 0 4px; font-size: 14px; }
.notes-panel ul { margin: 8px 0 0; padding-left: 20px; font-size: 13px; }
.notes-panel .token-list { padding-left: 0; }

/* ---- the body editor ----

   THE TEXTAREA IS THE FIELD AND THE SURFACE IS AN ENHANCEMENT, so the two share one bordered box
   and one height: whichever is showing, the page does not jump. See wwwroot/js/html-editor.js. */

.html-editor {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    overflow: hidden;
}

.html-editor-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    padding: 6px 8px;
    background: var(--surface-2);
    border-bottom: 1px solid var(--border);
}

.html-editor-btn {
    min-width: 30px;
    padding: 4px 8px;
    border: 1px solid transparent;
    border-radius: 6px;
    background: transparent;
    color: var(--text);
    font: inherit;
    font-size: 13px;
    cursor: pointer;
}

.html-editor-btn:hover { background: var(--surface); border-color: var(--border); }
.html-editor-btn[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-text); }
.html-editor-sep { width: 1px; height: 18px; background: var(--border); margin: 0 4px; }
.html-editor-toggle { margin-left: auto; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

.html-editor-linkbar {
    display: flex;
    gap: 6px;
    width: 100%;
    padding-top: 6px;
}

.html-editor-linkbar .input { flex: 1 1 auto; min-width: 0; }

.html-editor-surface {
    min-height: 320px;
    max-height: 60vh;
    overflow: auto;
    padding: 14px 16px;
    background: var(--surface);
    color: var(--text);
    line-height: 1.5;
}

.html-editor-surface:focus { outline: 2px solid var(--accent); outline-offset: -2px; }
.html-editor-surface img { max-width: 100%; height: auto; }
.html-editor-surface table { border-collapse: collapse; width: 100%; }

/* THE CELL EDGES ARE SHOWN HERE AND NOWHERE ELSE. An e-mail table used for layout has no borders,
   which makes it invisible to edit - you cannot put the caret in a cell you cannot see. Outline,
   not border, so it takes up no space and changes no measurement. */
.html-editor-surface td, .html-editor-surface th { outline: 1px dashed var(--border); padding: 6px; }

.html-editor-source,
.body-source {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
    line-height: 1.5;
    tab-size: 2;
}

.html-editor .html-editor-source {
    width: 100%;
    border: 0;
    border-radius: 0;
    min-height: 320px;
    resize: vertical;
}

.html-editor.showing-source .html-editor-source { display: block; }

/* ---- the preview ---- */

.email-preview {
    width: 100%;
    height: 70vh;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: #f4f5f7;
}

/* A delete sits apart from the save, with air above it, so the two are never one button row. */
.danger-form {
    margin-top: var(--stack);
    padding-top: 14px;
    border-top: 1px solid var(--border);
}

@media print {
    .html-editor-bar, .token-palette, .field-buttons, .danger-form { display: none; }
    .html-editor-surface { max-height: none; overflow: visible; }
    .email-preview { height: auto; }
}

/* ---------------------------------------------------------------------------------------------
   THE AUTO-CORRESPONDENCE QUEUE.

   FIVE STATES, AND COLOUR CANNOT CARRY FIVE. Each one has a two-or-three letter code as well as a
   tint, and the key on the page spells out what each means - "expired" is not a word whose
   definition anybody can infer, and the definition is the entire point of the screen. Same pattern
   as .job-flag and the --job-* tokens.

   The hues are Okabe-Ito and differ in LIGHTNESS as well as hue, so they still separate in
   greyscale and under red-green colour blindness.
   --------------------------------------------------------------------------------------------- */

:root {
    --queue-expired: #b45309;      /* amber, darkest - the one still awaiting a decision */
    --queue-due: #0072b2;           /* blue */
    --queue-scheduled: #5b5f66;     /* grey - nothing to do */
    --queue-sent: #0f7c5a;          /* green */
    --queue-cancelled: #8a3a3a;     /* muted red */
    --queue-missing: #a34e8f;       /* reddish purple - a different KIND of problem */

    --queue-expired-bg: #fdefc8;
    --queue-due-bg: #dceefc;
    --queue-scheduled-bg: #eceef1;
    --queue-sent-bg: #e3f3ec;
    --queue-cancelled-bg: #fbe9e9;
}

@media (prefers-color-scheme: dark) {
    :root {
        --queue-expired: #e2a24c;
        --queue-due: #6db8e8;
        --queue-scheduled: #a2abb8;
        --queue-sent: #3ecf9a;
        --queue-cancelled: #e78585;
        --queue-missing: #d79ac6;

        --queue-expired-bg: #33270f;
        --queue-due-bg: #12293a;
        --queue-scheduled-bg: #262b33;
        --queue-sent-bg: #14302a;
        --queue-cancelled-bg: #351d1d;
    }
}

.queue-summary {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 10px;
    margin-bottom: var(--stack);
}

.queue-tile {
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-areas: "flag count" "flag name";
    align-items: center;
    column-gap: 10px;
    padding: 12px 14px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    text-decoration: none;
    color: var(--text);
    box-shadow: var(--shadow);
}

.queue-tile:hover { border-color: var(--accent); }

/* THE CHOSEN ONE GETS A THICKER EDGE, not just a tint - see the note above. */
.queue-tile-on { border-width: 2px; border-color: var(--accent); }

.queue-tile .queue-flag { grid-area: flag; }
.queue-count { grid-area: count; font-size: 20px; font-weight: 650; }
.queue-name { grid-area: name; font-size: 12.5px; color: var(--text-muted); }

.queue-flag {
    display: inline-block;
    min-width: 34px;
    padding: 2px 6px;
    border-radius: 5px;
    color: #ffffff;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-align: center;
}

.queue-expired { background: var(--queue-expired); }
.queue-due { background: var(--queue-due); }
.queue-scheduled { background: var(--queue-scheduled); }
.queue-sent { background: var(--queue-sent); }
.queue-cancelled { background: var(--queue-cancelled); }
.queue-missing { background: var(--queue-missing); }

/* The row tint is the SECOND cue and only the expired rows carry one - tinting all 129,020 rows
   by state would make the list unreadable and tell nobody anything they cannot see in the flag. */
.queue-row-expired td { background: var(--queue-expired-bg); }

.queue-key summary { font-weight: 600; cursor: pointer; }
.queue-key dl { margin: 10px 0 0; }
.queue-key dt { margin-top: 10px; font-weight: 600; }
.queue-key dd { margin: 2px 0 0 46px; color: var(--text-muted); font-size: 13px; line-height: 1.5; }

@media print {
    .queue-summary { break-inside: avoid; }
    .queue-tile { box-shadow: none; }
}

/* The queue's headline: the two findings on ONE line, with the numbers linking to their own rows.
   See the note in Queue.cshtml on what this replaced and why. */
.queue-headline {
    margin: 0 0 var(--stack);
    padding: 8px 12px;
    border-left: 4px solid var(--queue-expired);
    background: var(--queue-expired-bg);
    border-radius: var(--radius);
    font-size: 13.5px;
    line-height: 1.5;
    color: var(--text);
}

.queue-headline a { color: inherit; }

/* ---------------------------------------------------------------------------------------------
   A FORM GRID, WHICH IS NOT A FILTER CARD.

   The Site and Client edit screens were laid out with `.filters`, because it was the nearest thing
   that already existed. It is the wrong tool: `.filters` sets `align-items: end`, which is right
   for a row of filters that are all one control tall and wrong for a form where one field carries a
   three-line hint, the next is a textarea and the one after is a checkbox. Measured on the Site edit
   screen, that produced controls sitting at four different heights across one row with their labels
   scattered between them.

   SUBGRID IS WHAT ACTUALLY FIXES IT. Every field is label / control / hint, and spanning the
   parent's rows makes those three line up ACROSS the whole row - so a label that wraps to two lines
   moves its own control down and nobody else's. Without it, a wrapped label ("Additional Notes for
   Confirmation Email") shifts one control out of line with its neighbours, which is most of what
   made the screen look broken.

   The @supports fallback is a floor under the label instead: cruder, costs a little height on every
   single-line label, and keeps the controls aligned where subgrid is unavailable.
   --------------------------------------------------------------------------------------------- */
/* ---- a form, at a readable measure -------------------------------------
   A FORM IS NOT A LIST, AND IT MUST NOT FILL THE WINDOW. auto-fit with a 210px minimum gives
   NINE COLUMNS on a 2000px monitor, which is what made the site screen "extremely clunky and
   difficult to follow": Invoice Address and its Postcode ended up two feet apart with unrelated
   fields between them, and a section with two controls left a hand's width of nothing in the
   middle. The eye has to cross the whole screen to read one record.

   Capped at --form-cols tracks. A TOKEN rather than a literal, because the right number is a
   judgement that will get revisited, and because a section that genuinely needs to be wider can
   raise it on itself rather than by growing a second grid.

   The tables below these forms are separate elements and still take the whole window - a long
   list wants the width, a form does not. */
:root { --form-cols: 4; --form-col: 250px; }

.form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 14px 16px;
    align-items: start;
    max-width: calc(var(--form-cols) * var(--form-col));
}

.form-grid > .field {
    min-width: 0;
    display: grid;
    grid-template-rows: auto auto 1fr;
    row-gap: 5px;
}

@supports (grid-template-rows: subgrid) {
    .form-grid > .field {
        grid-template-rows: subgrid;
        grid-row: span 3;
    }
}

@supports not (grid-template-rows: subgrid) {
    .form-grid > .field > label { min-height: 2.5em; display: flex; align-items: flex-end; }
}

/* A field that needs the room - an address, a note - takes two tracks rather than being squeezed
   to the same width as a postcode. It still sits on the same rows as everything beside it. */
.form-grid > .field-wide { grid-column: span 2; }
.form-grid > .field-full { grid-column: 1 / -1; }

/* A checkbox has no label above it, so it would sit on the LABEL row and float above its
   neighbours' controls - which is exactly what "Use Parent Invoice Address" and "Finished" were
   doing. Pushed down onto the control row instead. */
.form-grid > .check {
    grid-row: span 3;
    align-self: start;
    padding: 0;
    margin-top: 1.55em;
}

@supports not (grid-template-rows: subgrid) {
    .form-grid > .check { margin-top: 2.5em; }
}

/* The hint sits on its own row, so it never pushes the control out of line. */
.form-grid .field-hint { margin: 0; align-self: start; }

/* A read-only value shown where a control would be - "Date added", "Remaining". Same height as an
   input so the row keeps its rhythm. */
.form-value {
    display: flex;
    align-items: center;
    min-height: 36px;
    font-size: 14px;
    font-weight: 550;
}

/* A section of a form. The heading is part of the card rather than floating above it, because a
   bare <h2> between two cards reads as belonging to neither. */
.form-section { margin-bottom: var(--stack); }
.form-section > h2 { margin: 0 0 12px; font-size: 15px; }

/* Two note boxes side by side rather than in the auto-fitting run - see the comment in
   Pages/Sites/Edit.cshtml on why a textarea must not share a row with a date picker. */
.form-grid-halves { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }

/* A GROUP OF SWITCHES THAT BELONG TOGETHER, on one line. A fieldset with a legend, because that is
   the real HTML for it and it is what a screen reader announces - five loose checkboxes scattered
   through a grid say nothing about being five facets of one question. */
.check-group {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-2);
    padding: 8px 14px 10px;
    margin: 0 0 var(--stack);
}

.check-group legend {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text-muted);
    padding: 0 6px;
}

.check-row { display: flex; flex-wrap: wrap; gap: 6px 22px; }
.check-row .check { padding-bottom: 0; }

/* AN "ADD" FOLDED INTO A GRID ROW - the client group list's summary-email cell. Open, it is two
   small forms; closed, it is one word, which is what lets a hundred-row list stay a list. The
   summary reads as a quiet action rather than a heading: it IS one. */
.row-add > summary {
    cursor: pointer;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--accent);
    padding: 2px 0;
    list-style: none;
}

.row-add > summary::-webkit-details-marker { display: none; }
.row-add > summary::marker { content: ""; }

/* A caret drawn here rather than left to the browser's marker, which cannot be styled the same way
   across browsers - the same reason .nav-chevron exists. */
.row-add > summary::before {
    content: "+";
    display: inline-block;
    width: 1em;
    font-weight: 700;
}

.row-add[open] > summary::before { content: "−"; }   /* a real minus sign, not a hyphen */

.row-add > summary:hover { text-decoration: underline; }

.row-add-form {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-top: 6px;
}

.row-add-form .input { min-width: 0; max-width: 260px; }

.row-add p { margin: 6px 0 0; }

/* SOMEBODY ELSE HAS ADDED ROWS TO THIS LIST. News, not an alarm - it sits in the flow above the
   content rather than floating over it, because a notice that covers a row the user is reaching for
   is worse than no notice.

   IT IS NOT CARRYING ITS MEANING IN THE COLOUR. The text says how many and of what; the tint is the
   second cue. --accent-soft rather than a warning colour on purpose: nothing has gone wrong. */
.live-banner {
    margin: 0 0 var(--stack);
    padding: 8px 12px;
    border: 1px solid var(--accent);
    border-left-width: 4px;
    border-radius: var(--radius);
    background: var(--accent-soft);
    font-size: 13.5px;
}

.live-banner a { font-weight: 650; }

@media print { .live-banner { display: none !important; } }

/* ---- an entity record as label-left rows --------------------------------
   THE CLIENT CHOSE THIS SHAPE, 2026-09-01, from three drawn against this stylesheet - "C looks so
   much better". His complaint about the form it replaces was "the spacing is huge, the field sizes
   too big on this decent sized monitor... the number fields in particular really shouldn't take up
   my space".

   A ROW IS ~32px, NOT ~60. The label sits beside its control rather than above it, so a field costs
   one line instead of two, and the whole member record fits a laptop screen instead of scrolling
   twice. That is the actual answer to "the spacing is huge" - .form-grid made the fields narrower
   but the page no shorter.

   THE LABEL COLUMN IS FIXED, so every control starts at the same x and the eye runs down one edge
   rather than a ragged one. It is a hard cap on label length: a label that does not fit wraps and
   the row grows, which is the honest failure and is why the column is 172px rather than 120.

   THE CONTROL COLUMN IS minmax(0, 1fr) - without the 0 minimum a long value in an input refuses to
   shrink and pushes the row wider than its card.

   NOT FOR A LIST. This is for one record's fields. A list is .grid. */
.rec-set { display: grid; gap: 14px; max-width: 720px; }

.rec {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    overflow: hidden;
}

.rec-head {
    font-size: 12.5px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
    background: var(--surface-2);
    padding: 6px 16px;
    border-bottom: 1px solid var(--border);
    margin: 0;
}

.rec-row {
    display: grid;
    grid-template-columns: 172px minmax(0, 1fr);
    gap: 0 14px;
    align-items: center;
    padding: 5px 16px;
}

/* A hairline BETWEEN rows only - a border on every row would double up against the card edge. */
.rec-row + .rec-row { border-top: 1px solid var(--surface-2); }

/* Rows whose control is taller than one line - an address block, a textarea - sit their label at
   the top rather than floating it in the vertical middle of the stack. */
.rec-row-tall { align-items: start; }
.rec-row-tall > .rec-label { padding-top: 6px; }

.rec-label { font-size: 13px; font-weight: 600; color: var(--text-muted); }

/* The controls are smaller than a filter box and must not inherit .input's 260px floor - the whole
   point is that a postcode occupies a postcode's worth of room. */
.rec .input {
    min-width: 0;
    width: 100%;
    padding: 5px 9px;
    border-radius: 6px;
    background: var(--surface);
    font-size: 14px;
}

/* Several short controls sharing one row - the two commission rates, a run of checkboxes. */
.rec-inline { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 18px; padding: 2px 0; }
.rec-inline .check { padding-bottom: 0; }

/* A value with its unit spelled out beside it, so the box itself can be small. */
.rec-unit { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-muted); }
.rec-unit .input { width: 74px; }

/* Widths by what the value IS. A postcode is eight characters; a telephone number is eleven. */
.rec .input-code { width: 120px; }
.rec .input-tel { width: 150px; }

/* A stack of lines that are one field - the four address lines under one label. */
.rec-lines { display: grid; gap: 4px; }

/* ---- the action bar, in the same place on every record ------------------
   PINNED TO THE BOTTOM OF THE WINDOW. The form it replaces put Save below eighteen stacked fields,
   and on the one screen that had not opted out of fill-and-scroll it was clipped off entirely with
   no scrollbar to reach it - "I can't see the save, there's no way to get to the button". Sticky
   means it is in the same place whether the record is four rows or forty.

   IT NEEDS THE PAGE TO SCROLL, so a screen using it sets ViewData["ContentFill"] = false. */
.form-bar {
    position: sticky;
    bottom: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: var(--stack);
    padding: 10px 0;
    background: var(--bg);
    border-top: 1px solid var(--border);
}

/* PAPER DOES NOT SCROLL, so nothing sticks to it - the same undo every fill-and-scroll rule needs. */
@media print {
    .form-bar { position: static; border-top: 0; }
    .rec { box-shadow: none; break-inside: avoid; }
}

/* ---- the impact report, as a document ----------------------------------
   ONE STYLESHEET FOR THREE OUTPUTS: the screen, the browser's own print, and Chromium's PDF. The
   report partials carry no styles of their own, so what a client is sent and what Head Office looked
   at cannot drift.

   IT READS AS A DOCUMENT, NOT A SCREEN, because two of the three outputs are paper. Measured
   type, generous leading, and a white ground even in dark mode - see below. */
.report {
    max-width: 720px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 28px 32px 32px;
}

.report-head { margin-bottom: 22px; }
.report-title { margin: 0; font-size: 21px; letter-spacing: -0.01em; }
.report-owner { margin: 2px 0 0; font-size: 14px; color: var(--text-muted); }
.report-period { margin: 6px 0 0; font-size: 13px; color: var(--text-muted); }

/* FOUR FIGURES, NOT TWELVE. A report that leads with everything leads with nothing; the rest are
   in the tables below and are not hidden. auto-fit so it becomes two-up on a narrow window and on
   paper rather than squeezing four columns to nothing. */
.report-figures {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 14px 18px;
    padding: 18px 0;
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
}

.report-figure-value {
    font-size: 27px;
    font-weight: 650;
    letter-spacing: -0.02em;
    line-height: 1.15;
    color: var(--accent-text);
}

.report-figure-label { font-size: 12.5px; color: var(--text-muted); margin-top: 2px; }

.report-section {
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin: 24px 0 8px;
}

.report-table { width: 100%; border-collapse: collapse; font-size: 14.5px; }
.report-table th { text-align: left; font-weight: 500; padding: 6px 0; }
.report-table td { padding: 6px 0; }
.report-table tr + tr th, .report-table tr + tr td { border-top: 1px solid var(--surface-2); }
.report-pc { width: 5.5em; color: var(--text-muted); }

.report-total th, .report-total td {
    font-weight: 650;
    border-top: 1px solid var(--border) !important;
}

/* PAPER IS WHITE AND HAS NO SHADOWS. Without this the PDF renders the dark palette onto the page -
   Chromium honours prefers-color-scheme, so a machine set to dark would print white-on-black and
   drink a cartridge doing it. */
@media print {
    .report {
        max-width: none;
        border: 0;
        border-radius: 0;
        box-shadow: none;
        padding: 0;
        background: #ffffff;
        color: #16191d;
    }

    .report-figure-value { color: #0a5a41; }
    .report-section, .report-period, .report-owner, .report-pc, .report-figure-label { color: #4a525c; }
    .report-head, .report-section, .report-table { break-inside: avoid; }
}

/* A role column on the site's contact grid: narrow, centred, and carrying a tick rather than a
   word. Six of them side by side, so every pixel of heading width counts. */
.role-col { text-align: center; width: 58px; }
.role-on { color: var(--accent); font-weight: 700; }

/* A purchase order with nothing left on it. The WORD is in the cell beside it - this is the second
   cue, not the only one. */
.value-spent { color: var(--status-finished); font-weight: 650; }

/* A SELECT NEVER NEEDS TO BE WIDER THAN ITS LONGEST OPTION. In a sparse row - the E-Mail Management
   block has two - an auto-fit track is half the page, and a dropdown offering "" or "Stopped"
   stretched across 700px reads as a mistake. Text inputs are NOT capped: an address line genuinely
   wants the room. */
.form-grid select.input { max-width: 340px; }

/* AN INPUT MUST FIT ITS TRACK. `.input` carries `min-width: 260px` so a filter box never collapses,
   but a .form-grid track is 210-250px, so every control in one of these forms was 10-50px wider
   than the column holding it - the grid could not shrink them and the fields ran into each other.
   `.filters` already resets it the same way (see the rule near the top of this file); the form grid
   had never been given the equivalent. */
.form-grid .input { min-width: 0; width: 100%; }

/* A NUMBER IS NOT AN ADDRESS. Same reasoning as the select above, and the client's words on
   2026-09-01: "The number fields in particular really shouldn't take up my space." A commission
   rate of 13 in a 250px box reads as a mistake and pushes everything after it down the page.
   min-width is reset alongside, because .input's 260px would otherwise win outright and the cap
   would do nothing - the trap this stylesheet has fallen into before. */
.form-grid .input[type="number"],
.form-grid .input[type="date"] { min-width: 0; max-width: 150px; }

/* Short by nature - a postcode, a VAT number, a phone number. Applied on the field rather than
   guessed from the type, because "text" says nothing about how long the text is. */
.input-short { min-width: 0; max-width: 150px; }
