:root {
    /* Ledger palette tokens */
    --bz-bg: #ebe7df;
    --bz-surface: #f8f6f1;
    --bz-surface-alt: #f1ede1;
    /* One step deeper than surface-alt — reserved for structural table chrome (column headers,
       group dividers) so it reads as clearly distinct from surface-alt's own use as the subtle
       body-row stripe, rather than the two competing for the same tone. */
    --bz-surface-deep: #e3dac4;
    --bz-border: #d9d3c6;
    --bz-border-soft: #e6e1d3;
    --bz-text: #26241f;
    --bz-text-muted: #6b6558;
    --bz-accent: #4b5d3a;
    --bz-accent-hover: #3c4a2e;
    --bz-accent-soft: #e1e8d9;
    --bz-accent-contrast: #f8f6f1;
    --bz-heading: #2f3e52;

    /* Warm brown — the palette's contrast colour, for the one thing on a page that has to be found
       before anything is read. Today that is the state a group of requests is in.

       **Where it is used narrowed, and that is why a warm colour works now.** It was briefly on the
       state of every request in the list as well as on the group headings. Emphasising the same
       word twice made the column compete with the grouping that organises it — and it was in that
       arrangement that an orange failed, because a badge beside a request reads as a status, and a
       warm status badge reads as a warning. As a heading above a group it reads as a heading.

       The attempts and why they failed, since the next person will reach for one of them:

       - **Slate ink** was too well behaved. It sat down beside --bz-heading and read as more body
         text, which is the problem this token exists to solve.
       - **Burnt amber at #9a4508** read as an error — too close to --bz-danger's brick once
         darkened enough to be legible.
       - **A lighter green** is the same hue family as --bz-accent. It would not have read as a
         second colour, it would have read as the olive gone wrong.
       - **Deep teal** worked and was legible, but it is a cool note in a warm palette and reads as
         imported rather than belonging.

       This brown keeps the warmth without the alarm: it is browner and less saturated than the
       amber, so it sits with the cream ground rather than shouting off it, and it is further from
       the brick than the amber was. 5.3:1 on --bz-surface-deep, which is the band it has to work
       on — comfortably past the 4.5:1 that small bold text needs, and the constraint that decides
       how bright this can go.

       --emphasis is the ink; --emphasis-soft and --emphasis-border are the same colour as a tint,
       kept for the request view's paused banner. */
    --bz-emphasis: #7d4a15;
    --bz-emphasis-soft: #f6ecdf;
    --bz-emphasis-border: #dcc3a0;
    --bz-emphasis-contrast: #fbf7f2;


    /* --- what kind of fact this is, for request summaries ----------------------------------
 
       A request is shown in the area list, in three dashboard panels and in a third party's own
       list, and it should be recognisable in all of them. Colour carries the *kind* of each fact
       so the eye can find the same thing in the same colour wherever it is looking.

       **By kind, never by position.** Colouring "axis 1" and "axis 2" differently was the first
       idea and is a trap: axis 1 is Plant on a corrective action, Category on a help desk and
       something else again on the next area, and a supplier's list spans four customers with four
       different sets. A per-position colour teaches an association that is false the moment
       somebody opens another area — and looks deliberate while doing it.

       **Three hues, and no fourth.** The history above records four attempts at adding one
       contrast colour to this palette and why each failed; a summary needing five would have hit
       all four failures at once. So these are the three that already exist, each given the job it
       already half had, and the fourth distinction is carried by form rather than hue — an inverse
       chip, not a new colour.

       Colour is never the only carrier. Every chip is labelled, so this survives a monochrome
       print and a reader who cannot tell the three apart. */

    /* Where a request is filed: its axes. Slate, because an axis is structure rather than content
       — it is the same ink as a heading, which is what an axis effectively is. */
    --bz-kind-axis: #34465c;
    --bz-kind-axis-soft: #e7ebf1;

    /* What a request says: the fields the area marked worth seeing. Olive, the product's own
       colour, for the part that is the tenant's own words. */
    --bz-kind-field: #41522f;
    --bz-kind-field-soft: #e6ece0;

    /* Where it has got to: the state. The emphasis brown, which is already what a state is drawn
       in above a group on the area list — this makes that agree with everywhere else rather than
       introducing anything. */
    --bz-kind-state: var(--bz-emphasis);
    --bz-kind-state-soft: var(--bz-emphasis-soft);

    /* The organisation outside this one. Slate again, filled rather than tinted: it is the same
       kind of fact as an axis — structural, about where the request sits — and the one that most
       needs to be found at a glance. Distinguished by weight instead of by a hue this palette has
       already rejected four candidates for. */
    --bz-kind-party-ink: #f4f2ec;
    --bz-kind-party-fill: #34465c;
    --bz-danger: #8c3a2e;
    --bz-danger-hover: #712d24;
    --bz-danger-soft: #f2e2dc;
    --bz-danger-border: #dcb9ab;
    --bz-focus-ring: rgba(75, 93, 58, 0.28);

    /* What an empty field looks like. Named rather than written as #fff at each of the four
       places that need it, because "the background of something you type into" is a decision and
       the next person changing the palette has to be able to find all of it at once. */
    --bz-field: #ffffff;

    /* The band behind a column header, everywhere.
       Most tables had none. .table thead th set the typography and no background, so a header
       showed whatever was behind it — the page for a bare table, the panel for one inside
       .admin-panel — and the three tables that did set one had each set a different tone. They
       were not choosing a header colour: all three are sticky and simply had to be opaque or the
       rows would scroll through them. Three answers to a question nobody asked.
       A step darker than surface-deep, which the comment at the top reserved for table chrome and
       only one of the four honoured.
       **The ink had to move with it, and that is the part worth keeping.** --bz-text-muted on
       surface-deep was already 4.16:1, under the 4.5:1 that 11px uppercase needs, so darkening the
       band alone would have made an existing shortfall worse. This pairing is 5.37:1 — clear of
       the threshold while still plainly quieter than body ink, which is the whole job of a column
       header. */
    --bz-table-header: #ded4bb;
    --bz-table-header-ink: #57513f;

    /* Bootstrap theme overrides */
    --bs-body-font-family: "Segoe UI", -apple-system, "Helvetica Neue", Helvetica, Arial, sans-serif;
    --bs-body-bg: var(--bz-bg);
    --bs-body-color: var(--bz-text);
    --bs-emphasis-color: var(--bz-text);
    --bs-emphasis-color-rgb: 38, 36, 31;
    --bs-secondary-color: var(--bz-text-muted);
    --bs-secondary: #7a7462;
    --bs-secondary-rgb: 122, 116, 98;
    --bs-secondary-bg: var(--bz-surface-alt);
    --bs-tertiary-bg: var(--bz-surface);
    --bs-border-color: var(--bz-border);
    --bs-link-color: var(--bz-accent);
    --bs-link-color-rgb: 75, 93, 58;
    --bs-link-hover-color: var(--bz-accent-hover);
    --bs-primary: var(--bz-accent);
    --bs-primary-rgb: 75, 93, 58;
    --bs-success: #4b5d3a;
    --bs-success-rgb: 75, 93, 58;
    --bs-info: #8a6a22;
    --bs-info-rgb: 138, 106, 34;
    --bs-danger: var(--bz-danger);
    --bs-danger-rgb: 140, 58, 46;
    --bs-danger-text-emphasis: var(--bz-danger-hover);
    --bs-danger-bg-subtle: var(--bz-danger-soft);
    --bs-danger-border-subtle: var(--bz-danger-border);

    /* Rows are one colour, divided by a hairline, and lit under the pointer.
       Zebra striping went because it could not be applied honestly: a request is two physical
       rows, groups interrupt the count, and so every list that mattered had its own hand-rolled
       alternation that the rest did not. A rule between rows needs no counting. The hover tint
       does the job striping was really there for — keeping your eye on one row across a wide
       table — and only for the row you are actually on.
       The hairline itself is Bootstrap's own rule under every .table cell, in --bz-border by way of
       --bs-border-color; it was always there, and the stripes were competing with it. */
    --bz-row-hover: #ece5d2;

    /* Density & type scale */
    --bs-body-font-size: 0.9375rem;
    --bs-body-line-height: 1.45;
    --bs-border-radius: 0.25rem;
    --bs-border-radius-sm: 0.1875rem;
    --bs-border-radius-lg: 0.375rem;
}

html, body {
    font-family: var(--bs-body-font-family);
    background-color: var(--bz-bg);
    color: var(--bz-text);
}

h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
    letter-spacing: -0.005em;
}

h5, .h5 {
    font-size: 1.0625rem;
    font-weight: 600;
}

label, .form-label {
    font-size: 0.875rem;
    font-weight: 500;
}

.table {
    font-size: 0.875rem;
    font-variant-numeric: tabular-nums;
}

.table thead th {
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--bz-table-header-ink);
    background-color: var(--bz-table-header);
    border-bottom-width: 1px;
}

/* The row under the pointer. Through Bootstrap's own state variable rather than a background, so
   it layers the same way .table-active and friends do. Group headings are left alone: they are a
   band with a job of their own, and lighting them would make them read as a row.

   A request drawn as two lines lights as one: hovering either half lights both, which is what the
   sibling and :has() selectors are for. Without them the half you are not on stays dark and the
   pair reads as two things. */
.table > tbody > tr:not(.group-header-row):hover > *,
.table > tbody > tr.request-primary-row:hover + tr.request-detail-row > *,
.table > tbody > tr.request-primary-row:has(+ tr.request-detail-row:hover) > * {
    --bs-table-bg-state: var(--bz-row-hover);
}

/* A row that leads somewhere says so. See app.js — a click anywhere on it follows the link marked
   data-row-link, which is the reference in every list of requests. */
.table > tbody > tr:has(a[data-row-link]),
.table > tbody > tr.request-primary-row:has(a[data-row-link]) + tr.request-detail-row {
    cursor: pointer;
}

a, .btn-link {
    color: var(--bz-accent);
}

a:hover, .btn-link:hover {
    color: var(--bz-accent-hover);
}

.btn-primary {
    --bs-btn-color: var(--bz-accent-contrast);
    --bs-btn-bg: var(--bz-accent);
    --bs-btn-border-color: var(--bz-accent);
    --bs-btn-hover-color: var(--bz-accent-contrast);
    --bs-btn-hover-bg: var(--bz-accent-hover);
    --bs-btn-hover-border-color: var(--bz-accent-hover);
    --bs-btn-focus-shadow-rgb: 75, 93, 58;
    --bs-btn-active-color: var(--bz-accent-contrast);
    --bs-btn-active-bg: var(--bz-accent-hover);
    --bs-btn-active-border-color: var(--bz-accent-hover);
    --bs-btn-disabled-color: var(--bz-accent-contrast);
    --bs-btn-disabled-bg: var(--bz-accent);
    --bs-btn-disabled-border-color: var(--bz-accent);
}

.btn-outline-primary {
    --bs-btn-color: var(--bz-accent);
    --bs-btn-border-color: var(--bz-accent);
    --bs-btn-hover-color: var(--bz-accent-contrast);
    --bs-btn-hover-bg: var(--bz-accent);
    --bs-btn-hover-border-color: var(--bz-accent);
    --bs-btn-focus-shadow-rgb: 75, 93, 58;
    --bs-btn-active-color: var(--bz-accent-contrast);
    --bs-btn-active-bg: var(--bz-accent);
    --bs-btn-active-border-color: var(--bz-accent);
    --bs-btn-disabled-color: var(--bz-accent);
    --bs-btn-disabled-border-color: var(--bz-accent);
}

.btn-outline-secondary {
    --bs-btn-color: var(--bz-text-muted);
    --bs-btn-border-color: var(--bz-border);
    --bs-btn-hover-color: var(--bz-text);
    --bs-btn-hover-bg: var(--bz-surface-alt);
    --bs-btn-hover-border-color: var(--bz-border);
    --bs-btn-focus-shadow-rgb: 107, 101, 88;
    --bs-btn-active-color: var(--bz-text);
    --bs-btn-active-bg: var(--bz-surface-alt);
    --bs-btn-active-border-color: var(--bz-border);
    --bs-btn-disabled-color: var(--bz-text-muted);
    --bs-btn-disabled-border-color: var(--bz-border);
}

.btn-danger {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--bz-danger);
    --bs-btn-border-color: var(--bz-danger);
    --bs-btn-hover-bg: var(--bz-danger-hover);
    --bs-btn-hover-border-color: var(--bz-danger-hover);
    --bs-btn-focus-shadow-rgb: 140, 58, 46;
    --bs-btn-active-bg: var(--bz-danger-hover);
    --bs-btn-active-border-color: var(--bz-danger-hover);
    --bs-btn-disabled-bg: var(--bz-danger);
    --bs-btn-disabled-border-color: var(--bz-danger);
}

.btn-outline-danger {
    --bs-btn-color: var(--bz-danger);
    --bs-btn-border-color: var(--bz-danger);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--bz-danger);
    --bs-btn-hover-border-color: var(--bz-danger);
    --bs-btn-focus-shadow-rgb: 140, 58, 46;
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--bz-danger);
    --bs-btn-active-border-color: var(--bz-danger);
    --bs-btn-disabled-color: var(--bz-danger);
    --bs-btn-disabled-border-color: var(--bz-danger);
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem var(--bz-surface), 0 0 0 0.25rem var(--bz-focus-ring);
}

.form-control:focus, .form-select:focus {
    border-color: var(--bz-accent);
    box-shadow: 0 0 0 0.25rem var(--bz-focus-ring);
}

.form-check-input:checked {
    background-color: var(--bz-accent);
    border-color: var(--bz-accent);
}

.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid var(--bz-accent);
}

.invalid {
    outline: 1px solid var(--bz-danger);
}

.validation-message {
    color: var(--bz-danger);
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: var(--bz-text-muted);
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* Shared chrome for admin pages (Components/Pages/App/Admin/*) — kept global rather than
   per-page scoped CSS since more than one admin page now uses this same panel/field look. */
.admin-panel {
    background-color: var(--bz-surface);
    border: 1px solid var(--bz-border);
    border-radius: var(--bs-border-radius-lg, 0.375rem);
    padding: 1.25rem;
    margin-bottom: 1.25rem;
}

.admin-panel > h2 {
    margin-top: 0;
    color: var(--bz-heading);
}

/* Same look as .admin-panel, under its own name for non-admin content pages (RequestView's field
   groups/Roles/History, Dashboard's summary lists, …) — kept global for the same reason: more
   than one page uses it now, and it needs to look identical wherever it appears. */
.section-panel {
    background-color: var(--bz-surface);
    border: 1px solid var(--bz-border);
    border-radius: var(--bs-border-radius-lg, 0.375rem);
    padding: 1.25rem;
    margin-bottom: 1.25rem;
}

.section-panel > h2 {
    margin-top: 0;
    color: var(--bz-heading);
}

.section-panel > summary {
    margin-top: 0;
    margin-bottom: 0.75rem;
    color: var(--bz-heading);
    font-size: 1.0625rem;
    font-weight: 600;
    cursor: pointer;
}

    .section-panel > summary:hover {
        color: var(--bz-accent-hover);
    }

    /* No content is showing while collapsed, so the panel shouldn't reserve space below its
       own title — matches the padding a plain (non-collapsible) section-panel has around a
       single line of content. */
    details.section-panel:not([open]) > summary {
        margin-bottom: 0;
    }

.section-panel > :last-child {
    margin-bottom: 0;
}

.admin-field {
    max-width: none;
    margin-bottom: 0.9rem;
}

.admin-top-row {
    display: flex;
    flex-wrap: wrap;
    gap: 1.5rem;
    align-items: flex-start;
    margin-bottom: 0.9rem;
}

.admin-role-field {
    flex: 0 0 16rem;
}

/* Shared native <dialog> chrome — kept global once a second page (Role Assignments) needed
   the same look already established on RequestView's dialogs. */
.app-dialog {
    width: 100%;
    max-width: 34rem;
    background-color: var(--bz-surface);
    color: var(--bz-text);
    border: 1px solid var(--bz-border);
    border-radius: var(--bs-border-radius-lg, 0.375rem);
    padding: 1.25rem;
}

/* A matrix needs the width a form does not: every state is a column, and squeezing them into
   34rem turns a grid meant to be read across into one that scrolls. Still capped, so it does
   not become a full-width sheet on a large screen. */
.app-dialog-wide {
    max-width: 62rem;
}

.app-dialog::backdrop {
    background-color: rgba(38, 36, 31, 0.4);
}

/* Shared column-header filter/sort chrome (FilterSortHeader.razor) — kept global since every
   filterable table in the app (AreaView, My Activity, Person Picker) uses the same control. */
.request-toolbar-item {
    position: relative;
    white-space: nowrap;
    padding-right: 1.5rem;
}

.active-filter-badge {
    color: var(--bz-accent);
    margin-left: 0.25rem;
    vertical-align: middle;
}

.filter-icon {
    appearance: none;
    border: none;
    background: none;
    padding: 0;
    position: absolute;
    right: 0.5rem;
    top: 50%;
    transform: translateY(-50%);
    line-height: 0;
    color: var(--bz-text-muted);
    cursor: pointer;
}

    .filter-icon:hover {
        color: var(--bz-text);
    }

    .filter-icon.active {
        color: var(--bz-accent);
    }

.filter-overlay {
    position: fixed;
    inset: 0;
    z-index: 9;
    background: transparent;
}

.filter-popover {
    position: absolute;
    top: 100%;
    right: 0.5rem;
    z-index: 10;
    margin-top: 0.25rem;
    padding: 0.5rem;
    min-width: 10rem;
    background: var(--bz-surface);
    border: 1px solid var(--bz-border);
    border-radius: var(--bs-border-radius, 0.25rem);
    box-shadow: 0 0.25rem 0.5rem rgba(38, 36, 31, 0.15);
    font-weight: normal;
    text-transform: none;
    cursor: default;
    white-space: normal;
}

.filter-popover-start {
    right: auto;
    left: 0.5rem;
}

.filter-popover-sort {
    display: flex;
    gap: 0.375rem;
    margin-bottom: 0.5rem;
}

    .filter-popover-sort .btn {
        flex: 1 1 auto;
    }

.filter-popover input,
.filter-popover select {
    width: 100%;
}

/* The Blazor error banner, styled here rather than in a component's scoped stylesheet.
   Both layouts render one, and a scoped rule only ever matched MainLayout's — so on every public
   page the banner defaulted to visible and sat there saying "an unhandled error has occurred"
   with nothing wrong. On the home page and the trust page in particular, that is the last thing
   somebody should read. */
#blazor-error-ui {
    color-scheme: light only;
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 0.75rem;
    top: 0.5rem;
}

/* The billing headline. One number, large, because it is the whole point of the page and the
   thing a customer checks against an invoice. */
.billing-headline {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    margin-bottom: 0.75rem;
    flex-wrap: wrap;
}

.billing-count {
    font-size: 2.75rem;
    font-weight: 600;
    line-height: 1;
    color: var(--bz-heading);
    font-variant-numeric: tabular-nums;
}

.billing-count-label {
    color: var(--bz-text-muted);
}

/* A printed test request has to say so.
   
   Screen has the draft bar and the accent, and both are lost the moment somebody prints a page
   and carries it into a meeting — which is exactly where a fabricated incident does damage,
   because paper has no context and nobody checks the URL it came from.
   
   Applied to <main class="main-draft">, which the layout already sets whenever the workspace is
   a draft, so this needs no new state. See docs/bizuncto-billing.md §7. */
@media print {
    main.main-draft {
        position: relative;
    }

    main.main-draft::before {
        content: "TEST";
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%) rotate(-35deg);
        font-size: 8rem;
        font-weight: 700;
        letter-spacing: 0.15em;

        /* Grey rather than a colour: a mono printer renders a light colour as almost nothing, and
           a watermark that does not survive the printer it is sent to is not a watermark. */
        color: rgba(0, 0, 0, 0.12);

        /* Behind the content, and not clickable — this must never sit between somebody and the
           page on screen if a print stylesheet is ever previewed. */
        z-index: 0;
        pointer-events: none;
        white-space: nowrap;
    }

    /* Repeated at the top of every printed page, because the diagonal mark is fixed and a long
       request runs to several sheets — only the first would carry it otherwise. */
    main.main-draft::after {
        content: "Test data from a draft area — not a real record.";
        display: block;
        margin-bottom: 1rem;
        font-weight: 600;
    }
}

/* A validation message beside the label it is about, rather than only in a summary at the top.
   The asterisk says a field is required; it does not say this one is the one you missed. */
.field-error {
    color: var(--bs-danger, #dc3545);
    font-weight: 400;
    font-size: 0.875em;
    margin-left: 0.5rem;
}

/* A multi-list, boxed so it reads as one control rather than as loose checkboxes that happen to be
   near each other. Same border, radius and background as an input — the background matters most,
   because it is a shade deeper than the panel behind it and that contrast is what makes every other
   control on the form look like somewhere you answer rather than somewhere you read.

   Inside it, options run across the width when there is width and stack when there is not. Four
   options one per line is four lines of mostly empty space, and it pushes the rest of the form
   down for nothing. auto-fit with a minimum lets the browser choose the column count, so there is
   no breakpoint to maintain and it behaves on a phone without being told to.

   The height cap is what a native multi-select does: twenty options should scroll inside their own
   control, not push the Save button off the screen. */
.check-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: 0.125rem 1.5rem;

    padding: 0.5rem 0.75rem;
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius, 0.375rem);
    background-color: var(--bz-field);

    max-height: 12rem;
    overflow-y: auto;
}

/* An unticked box is white, everywhere one appears.
   Bootstrap gives a checkbox the same background as a text input, which on this palette is the same
   tone as whatever it sits on — so an unticked one was a thin border and nothing else, and "none
   selected" looked much like "not a control". White is the one thing here that reads as empty.

   Not scoped to the multi-list, though that is where it was noticed. A Boolean field is the same
   control answering a different question, and one of them looking like a control while the other
   does not is the drift CLAUDE.md's one-interaction-model rule exists to stop.

   :not(:checked) on purpose. A ticked box takes the accent fill and its white tick from Bootstrap,
   and setting a background unconditionally would quietly erase both. */
.form-check-input:not(:checked) {
    background-color: var(--bz-field);
}
/* Paused, beside the state it is parked in.

   Muted on purpose. The state name next to it is plain text and the grouping above carries the
   emphasis, so a loud marker here would be the third thing competing to be read in one column.
   All this has to do is be noticed once — it is rare, and the request view says the rest. */
.state-pill-paused {
    display: inline-block;
    margin-left: 0.4rem;
    padding: 0.05rem 0.4rem;
    border: 1px solid var(--bz-border);
    border-radius: 0.75rem;
    background-color: transparent;
    color: var(--bz-text-muted);
    font-size: 0.75rem;
    font-weight: 500;
    line-height: 1.4;
    white-space: nowrap;
}

/* The reference that opens a request, in every list that shows one.

   Block with the cell's own padding pulled back out, so the clickable area is the whole cell
   rather than the glyphs inside it. "Incident 3" is a target you have to aim at, and a single
   digit is worse — the reference is the way into a request, so it should be the easiest thing on
   the row to hit, not the hardest.

   A minimum width as well as padding, because padding alone only grows the target around the
   glyphs and "5" has almost none to grow around — the column itself has to stop being two
   characters wide. The negative margins cancel the padding, so the text sits where it always did
   and only the reachable area changes. */
.request-link {
    display: inline-block;
    min-width: 2.75rem;
    padding: 0.4rem 0.5rem;
    margin: -0.4rem -0.5rem;
    text-decoration: none;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.request-link:hover {
    text-decoration: underline;
}

/* --- list idiom, shared by every list of requests --------------------------
   These began scoped to the area view. The third-party list now groups and carries the same
   per-request fact row, so they are global — one heading means one thing wherever it appears,
   which is the UI consistency constraint applied to CSS rather than to controls. */

/* The band keeps surface-deep, the tone reserved for structural table chrome, which puts these in
   the same family as the column headers above them. What carries the emphasis is the ink: the
   contrast colour rather than heading slate, which was close enough to body text to be read rather
   than found. A band this strong in colour as well as tone was tried and made the groups shout
   over the rows they exist to organise. */
.request-group-header-row td {
    padding-top: 0.6rem;
    padding-bottom: 0.6rem;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--bz-emphasis);
    background-color: var(--bz-surface-deep);
    border-top: 1px solid var(--bz-border);
    border-bottom: 1px solid var(--bz-border);
}

.request-group-header-row:first-child td {
    border-top: none;
}

/* The heading of a group is a button that folds it — see GroupHeaderRow. The button borrows
   everything from the band it sits in, so each band keeps its own look and only gains a chevron
   and a pointer. */
.group-toggle {
    display: block;
    width: 100%;
    padding: 0;
    margin: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    text-align: left;
    cursor: pointer;
}

.group-toggle:focus-visible {
    outline: 2px solid var(--bz-accent);
    outline-offset: 2px;
}

/* Points down when open, right when folded. */
.group-chevron {
    display: inline-block;
    width: 0.45rem;
    height: 0.45rem;
    margin-right: 0.6rem;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: translateY(-0.15em) rotate(45deg);
    transition: transform 0.12s ease;
}

.is-collapsed .group-chevron {
    transform: rotate(-45deg);
}

/* Rows under a group heading are indented, so they read as belonging to it. A clear step in from
   the heading's text rather than level with it: level with the name was tried first, and read as
   aligned rather than as inside.

   Everything after the first heading in a table is inside some group, which is why a sibling
   selector is enough and no row has to be told. A list grouped twice (the dashboard's third-party
   panel) steps in again past the inner heading, whose own text already starts further in. */
.table > tbody > .group-header-row ~ tr:not(.group-header-row) > :first-child {
    padding-left: 2.5rem;
}

.table > tbody > .request-subgroup-header-row ~ tr:not(.group-header-row) > :first-child {
    padding-left: 3.8rem;
}

@media (prefers-reduced-motion: reduce) {
    .group-chevron {
        transition: none;
    }
}

/* The admin matrices' group band. Here rather than in each page's scoped stylesheet: the row is
   drawn by GroupHeaderRow, and a page's scoped rules do not reach markup another component renders.
   Quieter than the request lists' band, because these groups are only a way through a long grid. */
.admin-group-header-row td {
    padding-top: 0.6rem;
    padding-bottom: 0.6rem;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--bz-text-muted);
    background-color: var(--bz-surface-alt);
    border-top: 1px solid var(--bz-border);
    border-bottom: 1px solid var(--bz-border);
}

.admin-group-header-row:first-child td {
    border-top: none;
}

/* Held back from the name beside it: lighter weight, and a little air so the two are not read as
   one string. */
.request-group-count {
    font-weight: 400;
    text-transform: none;
    letter-spacing: normal;
    margin-left: 0.4rem;
}

/* The second line of a request: where it has got to, indented under the reference it belongs to.

   Quieter and smaller than the line above, which is the swap that matters. These used to be the
   loud half — the columns carried state and dates while the fields saying what a request is
   *about* sat underneath in muted text. Somebody scanning for one particular piece of work
   recognises it by its subject rather than by its date, so the subject moved up and this moved
   down. Kept under the old name too, because the third-party lists still use it. */
.request-detail-row > td,
.request-header-fields-row > td {
    padding-top: 0.15rem;
    padding-bottom: 0.6rem;
    color: var(--bz-text-muted);
    font-size: 0.8125rem;
}

/* The indent is an empty reference cell rather than a padding rule, so the detail lines up under
   the request it belongs to at whatever width that column settles at. */
.request-detail-indent {
    padding: 0;
}

/* A grid rather than a flexible row — column widths are fixed by field count alone, so the Nth
   field lines up at the same position on every request regardless of how long any one row's values
   happen to be. */
.request-header-fields {
    display: grid;
    column-gap: 1.5rem;
    row-gap: 0.15rem;
}

.request-header-field {
    display: inline-flex;
    align-items: baseline;
    gap: 0.35rem;
}

.request-header-field-label {
    font-weight: 600;
    color: var(--bz-text);
    white-space: nowrap;
}

.request-header-field-label::after {
    content: ":";
}

/* The first line keeps its normal border; the detail line directly under it reads as part of the
   same block rather than as a new row, so the rule between them is dropped. */
.request-primary-row > td,
.request-row-with-header-fields > td {
    border-bottom: none;
}

/* The lighter of two nested group bands, for a list grouped twice — the dashboard's third-party
   panel groups by organisation and then by the kind of work it sends.

   Deliberately quieter than .request-group-header-row rather than a second colour: the two say
   "this is inside that", and an equal band would make them read as siblings. No band at all was
   tried and the areas ran together. */
.request-subgroup-header-row td {
    padding-top: 0.45rem;
    padding-bottom: 0.35rem;
    padding-left: 1.5rem;
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--bz-text-muted);
    background-color: var(--bz-surface-alt);
    border-bottom: 1px solid var(--bz-border-soft);
}

/* The containing block for a column header's icon and popover, both of which are absolutely
   positioned. Block so it fills the header cell, relative so they measure from it.

   Without this they measured from whatever happened to be positioned further up — which was the
   sticky `th` on the area view and the page itself everywhere else, putting the icon outside the
   table where nobody could find it. */
.filter-header {
    display: block;
    position: relative;
}

/* The tick column on the activity digest: as narrow as the control in it, so the request beside it
   starts where the eye expects rather than after an indent. */
.activity-tick {
    width: 2rem;
}

/* The area a request belongs to, under its reference. Muted and small because it answers "which
   one of these is it" rather than being the thing itself — the digest spans every area somebody
   works in, so the reference alone is not enough to place it. */
.activity-area {
    font-size: 0.75rem;
    color: var(--bz-text-muted);
}

.activity-event {
    font-size: 0.8125rem;
    line-height: 1.5;
}

/* The time each thing happened, held back from what happened. Somebody scans this column for the
   verb and the name; the timestamp is what they read second. */
.activity-when {
    margin-left: 0.4rem;
    color: var(--bz-text-muted);
    font-variant-numeric: tabular-nums;
}

/* Who invited somebody, on the page they land on before they have an organisation.

   Uses the emphasis colour rather than an alert one. This is the most welcome thing on the page —
   a customer is waiting for them — and styling it as a warning would read as a problem to solve
   rather than the reason they are here. See PendingTenant.razor. */
.invitation-note {
    background: var(--bz-emphasis-soft);
    border: 1px solid var(--bz-emphasis-border);
    border-left: 3px solid var(--bz-emphasis);
    border-radius: 4px;
    padding: 0.9rem 1.1rem;
    margin-bottom: 1.5rem;
    max-width: 44rem;
}

.invitation-note strong {
    color: var(--bz-emphasis);
}

/* The two create-an-organisation forms on the same page. Never had any rules of its own — the
   class was in the markup and nowhere else — so the box and the button ran the full width of the
   window on a desktop. */
.onboarding-form {
    max-width: 26rem;
    margin-top: 1rem;
}

/* Every field somebody types into is white, everywhere one appears.

   Bootstrap gives a text input and a select the same background as the body, and on this palette
   that is the page itself — so a field read as a disabled one, or as a line of text that happened
   to have a border. That is the same complaint the checkbox rule above answers, arriving at the
   other controls: white is the one tone here that reads as empty and waiting.

   Disabled and read-only fields are deliberately excluded and keep the tinted background. If
   everything were white, a field that genuinely cannot be typed into would lose the only signal it
   has — which would be this problem again, pointing the other way. */
.form-control,
.form-select,
.form-control:focus,
.form-select:focus {
    background-color: var(--bz-field);
}

.form-control:disabled,
.form-select:disabled,
.form-control[readonly] {
    background-color: var(--bz-surface-alt);
}

/* Who handles what, as a grid — people down, a customer's areas across.

   The area names are the wide part and the ticks are narrow, so the first column takes what it
   needs and the rest share evenly. Without that the name column collapses to the width of the
   longest word and the ticks spread across the page, which makes a row hard to follow back to the
   person it belongs to. */
/* Narrow columns are named rather than counted.
   This was written by position — everything after the first, shrink — back when the first column
   was the only text in the grid. Splitting the person into surname, forename, job title and
   address left four text columns fighting over one "auto", so the first took the width of the
   table and the other three were squeezed to their longest word. A tick column is narrow because
   it holds a tick, which is a fact about the column rather than about where it happens to sit. */
.contact-matrix .tick-cell,
.contact-matrix .admin-cell,
.contact-matrix .action-cell {
    width: 1%;
    white-space: nowrap;
}

/* Centred on the cell rather than sitting against its left edge, which is where a checkbox in a
   table cell otherwise lands and makes the column read as ragged. */
.contact-matrix .form-check-input {
    margin: 0;
    vertical-align: middle;
}

/* User Admin was tinted and ruled off, to stop it reading as another area work could arrive in.
   That was written while it sat among the area columns; moved to the person's side of the table,
   beside Status, the position says it and the tint was one distinction too many on a row that
   already carries four. The class stays for the width rule above. */

/* Somebody whose access has been withdrawn, in the people grid.

   Muted rather than struck through or tinted red. They are not a problem and not a mistake — a
   withdrawn person is usually somebody who has left, and the row still has to be read, because
   Allow access is on it. Quieter than the rest is the whole of what this needs to say; the Status
   column says the rest in words. */
.contact-matrix .person-withdrawn td {
    color: var(--bz-text-muted);
}

/* The two ends of the access toggle.

   One width for both, taken from the longer label, so the column does not change shape as somebody
   works down a list withdrawing and restoring. Two buttons that swap places and sizes read as two
   different controls rather than as one thing in two states.

   Allow access takes the accent green against Withdraw's red. Both name what pressing them does —
   red for taking something away, green for giving it back — rather than describing the state the
   row is already in, which would put the colours the other way round and mean the opposite. */
.contact-matrix .access-toggle {
    min-width: 10rem;
}

.contact-matrix .btn-allow-access {
    color: var(--bz-accent);
    border-color: var(--bz-accent);
    background-color: transparent;
}

.contact-matrix .btn-allow-access:hover,
.contact-matrix .btn-allow-access:focus {
    color: var(--bz-emphasis-contrast);
    background-color: var(--bz-accent);
    border-color: var(--bz-accent);
}

/* A table whose column widths are held (see app.js) can no longer widen a column to fit a long
   word, so the word wraps inside its cell instead of running over the next one. */
table[data-columns-locked] > tbody > tr > td {
    overflow-wrap: anywhere;
}

/* A panel heading that folds its table away. The chevron and button rules are GroupHeaderRow's,
   reused rather than reinvented; these two only undo the full-width block a table row wanted, so
   the heading can sit beside its own controls. */
.panel-toggle {
    display: inline-flex;
    align-items: baseline;
    width: auto;
}

/* How many rows the table below is showing. Quiet rather than a badge: it is a fact about the
   panel, not a notification, and the panels that do carry unread counts should stay the ones that
   catch the eye. */
.panel-count {
    margin-left: 0.5rem;
    font-size: 0.8em;
    font-weight: 400;
    color: var(--bz-text-muted);
}

/* "Acknowledged", which is one word and must stay one word. The header above it is deliberately
   empty — the column says only whether a row is already cleared — and an empty header gives the
   browser nothing to size against, so it squeezed the column to a character and stacked the word
   down the page. */
.notification-status {
    width: 1%;
    white-space: nowrap;
}

/* The tick box in a digest, and the area you can actually hit.
   A 1rem box in the middle of a cell asks for a precision nobody should need to spend on "I have
   read this" — and the panels it appears in are read by ticking several in a row. The label fills
   the cell, so the target is the column's width and the row's height rather than the glyph; the
   delegated row-click handler in app.js already steps aside for a label, so a miss toggles the box
   instead of opening the request. */
.activity-tick {
    width: 2.75rem;
}

.tick-target {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 2rem;
    margin: 0;
    padding: 0.25rem;
    cursor: pointer;
}

.tick-target .form-check-input {
    width: 1.1rem;
    height: 1.1rem;
    margin: 0;
    cursor: pointer;
}

/* An activity line is now a link to that entry in the request's history, so it needs to look like
   one — but quietly. A digest of a dozen events in link blue reads as a warning rather than a list,
   so the colour stays the text's and the underline waits for the pointer. */
a.activity-event {
    display: block;
    color: inherit;
    text-decoration: none;
}

a.activity-event:hover,
a.activity-event:focus-visible {
    text-decoration: underline;
}

/* The entry somebody arrived to see. A wash rather than a border, so the row does not change
   height and the rows around it do not move as it is applied. It stays until the page is left:
   a highlight that fades is one you can miss by blinking, and this one answers "which of these
   did I click". */
.history-highlight > td {
    background-color: var(--bz-highlight, rgba(255, 214, 102, 0.28));
}

/* "Next" and "Last", beside the day buttons on the dashboard's two windowed panels.
   Quiet and small: it is a preposition rather than a control, and it earns its place only because
   the two panels carry identical buttons pointing in opposite directions. */
.window-direction {
    font-size: 0.8125rem;
    color: var(--bz-text-muted);
    white-space: nowrap;
}

/* ---- anonymous intake ------------------------------------------------------------------------
   Shared by /submit/{key}, its confirmation and /submit/status. In app.css rather than three
   scoped files because the three pages are one journey and should not drift apart. */

.intake {
    max-width: 44rem;
}

.intake h1 {
    font-size: 2rem;
}

.intake .lead {
    font-size: 1.1rem;
    color: var(--bz-text-muted);
    max-width: 60ch;
}

/* Said plainly and early rather than in small print at the bottom. Somebody deciding how much to
   write should read this before they write it, not after. */
.intake-notice {
    border: 1px solid var(--bz-border);
    border-left: 4px solid var(--bz-emphasis);
    background: var(--bz-surface-alt);
    border-radius: 0.375rem;
    padding: 1rem 1.25rem;
    margin: 1.5rem 0 2rem;
}

.intake-notice p {
    color: var(--bz-text);
    max-width: 60ch;
}

.intake-group {
    border: 1px solid var(--bz-border-soft);
    border-radius: 0.375rem;
    padding: 1.25rem;
    margin-bottom: 1.5rem;
}

.intake-group legend {
    font-size: 1rem;
    font-weight: 600;
    color: var(--bz-heading);
    padding: 0 0.5rem;
    width: auto;
}

.intake-field {
    margin-bottom: 1rem;
}

.intake-field > label {
    display: block;
    font-weight: 500;
    margin-bottom: 0.25rem;
}

.intake-sublabel {
    display: block;
    font-size: 0.9rem;
    color: var(--bz-text-muted);
    margin: 0.5rem 0 0.25rem;
}

.intake-required {
    color: var(--bz-danger);
    margin-left: 0.15rem;
}

/* The one thing on the page somebody has to copy down, so it is the one thing that looks like it
   has to be copied down. */
.intake-reference {
    border: 2px solid var(--bz-accent);
    border-radius: 0.5rem;
    padding: 1.5rem;
    margin: 1.5rem 0;
    text-align: center;
    background: var(--bz-surface);
}

.intake-reference-label {
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--bz-text-muted);
    margin-bottom: 0.5rem;
}

.intake-reference-code {
    font-family: ui-monospace, "Cascadia Mono", "Consolas", monospace;
    font-size: 1.6rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    color: var(--bz-heading);
    margin: 0;
    /* Selectable in one go, because the first thing anybody does is try to copy it. */
    user-select: all;
}

.intake-result {
    margin-top: 2rem;
    border-top: 1px solid var(--bz-border-soft);
    padding-top: 1.5rem;
}

.intake-status {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.35rem 1.5rem;
    margin: 1rem 0;
}

.intake-status dt {
    font-weight: 600;
    color: var(--bz-text-muted);
}

.intake-status dd {
    margin: 0;
}

.intake-update {
    border: 1px solid var(--bz-border-soft);
    border-radius: 0.375rem;
    padding: 1rem 1.25rem;
    background: var(--bz-surface-alt);
}

.intake-update h3 {
    font-size: 1rem;
    font-weight: 600;
}

.intake-update p {
    max-width: 60ch;
    white-space: pre-wrap;
}

.intake-update-when,
.intake-none {
    color: var(--bz-text-muted);
    font-size: 0.9rem;
}

/* The kind colours as ink rather than as a chip.
 
   For the lists that are tables with a column per fact — the area list — where a chip would put a
   second grid of coloured boxes on top of the grid the table already is. That page's own comments
   record the same argument being had about the state column and settled the same way: the layout
   is tuned, and emphasis that competes with the structure organising it is emphasis in the wrong
   place.

   Same hues as .fact-axis and .fact-field, so a reader carries one association between the
   dashboard and here. The column heading is the label, which is what keeps colour from being the
   only thing telling them apart. */
.fact-axis-ink {
    color: var(--bz-kind-axis);
}

.fact-field-ink {
    color: var(--bz-kind-field);
}

/* The axis cells are the exception to the muted detail row, and saying so is load-bearing.

   On the area list an axis value is a <td> inside .request-detail-row, which is painted
   var(--bz-text-muted) by the rule further up. That selector scores a class and an element; the
   bare .fact-axis-ink above scores a class, and loses. So the axis column rendered grey while the
   field column — a <span> one row up, with nothing competing for it — rendered green, and half a
   colour scheme reads worse than none: the half that works teaches the reader that the colour
   means something.

   Written as an exception at the same level as the rule it overrides rather than by qualifying the
   token rule with td, which would only tie on specificity and win on source order — correct today
   and broken by whoever next moves a block.

   Found by opening the page, after a merge, with the suite green. Nothing in it could have caught
   this: bUnit renders markup with no stylesheet, so a test can prove the class is on the element
   and never that it won. */
.request-detail-row > td.fact-axis-ink {
    color: var(--bz-kind-axis);
}
