/*
 * Component styles for the new (non-Bootstrap) design system.
 *
 * `.pl-btn`, `.pl-dropdown`, `.pl-dropdown-menu`, `.pl-dropdown-item` and
 * `.pl-pagination` carry the `pl-` prefix because those names collide with
 * real Bootstrap 5.3.3 selectors (checked, not guessed —
 * ui_ux/migration-plan.md §1/§2.2, and enforced by
 * scripts/check-no-bootstrap-classes.js). Nothing else here needs a prefix:
 * every other class below has zero matches in bootstrap.min.css, so there is
 * nothing to collide with and no `all: revert` workaround anywhere in this
 * file.
 */

/* ===== Buttons ========================================================= */

/* The resting button is a raised surface, not a rectangle drawn on the page:
   the hairline shadow is what separates "a control you press" from "a box with
   a word in it", and it is the difference the flat version kept losing. The
   horizontal padding is wider than the vertical on purpose — a label needs air
   either side of it before a button stops reading as a cramped placeholder. */
.pl-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-md);
    background: var(--color-surface);
    box-shadow: var(--shadow-sm);
    color: var(--color-text);
    font-family: inherit;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    /* Exactly 1, so a label's line box is 1em — the same height as the 1em
       `.icon` an icon-only button holds instead. Anything taller gives the two
       kinds different heights, and they sit side by side: every split button's
       arrow came out ~3px shorter than its label at 1.25. */
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
}

.pl-btn:hover {
    background: var(--color-bg-subtle);
    border-color: var(--color-neutral-text);
}

/* Pressed: the surface drops to the page — the fill darkens and the shadow
   goes. Every button needs this; without it a press is confirmed only by the
   page changing underneath, which is the tell that nothing is really being
   pushed. */
.pl-btn:active:not(:disabled) {
    background: var(--color-neutral-bg);
    box-shadow: none;
}

/* Neutral, whatever the variant: a faded primary read as a lighter blue
   button — available, only paler — rather than as one that cannot be pressed
   yet. Grey on grey says "not now" without borrowing the accent. */
.pl-btn:disabled,
.pl-btn:disabled:hover {
    border-color: var(--color-border);
    background: var(--color-neutral-bg);
    box-shadow: none;
    color: var(--color-text-subtle);
    cursor: not-allowed;
}

/* The border is a step darker than the fill rather than the same colour: a
   filled shape whose edge is its own colour has no edge, and reads as a swatch
   of accent with text on it. */
.pl-btn--primary {
    background: var(--color-primary);
    border-color: var(--color-primary-hover);
    color: var(--color-text-on-primary);
}

.pl-btn--primary:hover {
    background: var(--color-primary-hover);
    border-color: var(--color-primary-hover);
}

.pl-btn--primary:active:not(:disabled) {
    background: var(--color-primary-hover);
    border-color: var(--color-primary-hover);
}

/* Transparent rather than white, so it takes the colour of whatever it sits
   on. It keeps the base shadow: on this app's surfaces the two differ only on
   a tinted panel, and a secondary action beside a primary one should still be
   a raised control rather than an outline drawn on the background. */
.pl-btn--outline {
    background: transparent;
}

.pl-btn--sm {
    padding: var(--space-1) var(--space-3);
    font-size: var(--font-size-xs);
}

.pl-btn--danger {
    background: var(--color-danger);
    border-color: var(--color-danger);
    color: var(--color-text-on-primary);
}

/* The quiet destructive/secondary action inside a component that already has
   a primary one next to it (e.g. avatar Upload / Remove): no border, no fill,
   so the pair reads as one control with a secondary verb rather than two
   competing buttons. The hover fill gives it a target without promoting it. */
.pl-btn--text {
    border-color: transparent;
    background: transparent;
    /* No edge, so nothing to raise: a shadow under a borderless label reads as
       a smudge rather than as a surface. */
    box-shadow: none;
    color: var(--color-text-muted);
}

.pl-btn--text:hover {
    background: var(--color-neutral-bg);
    color: var(--color-text);
}

.pl-btn--danger:hover {
    background: var(--color-danger-hover);
    border-color: var(--color-danger-hover);
}

.pl-btn--disabled {
    border-color: var(--color-border);
    background: var(--color-neutral-bg);
    box-shadow: none;
    color: var(--color-text-subtle);
    cursor: auto;
}

.pl-btn--paginator-current {
    color: var(--color-primary);
    border-color: var(--color-primary);
    cursor: auto;
}

/* These already answer a hover with a fill, a colour shift, or an underline
   border — they must not also take the prose underline from `a:hover`. The
   anchor is named explicitly because a bare class selector scores below
   `a:hover` and loses, which is how `.pl-btn` and `.tab` links started
   underlining on hover. */
a.pl-btn:hover,
a.tab:hover,
a.pl-dropdown-item:hover,
a.row-link:hover {
    text-decoration: none;
}

/* ===== Status pills (§4.3 canonical vocabulary) ======================== */
/*
 * Semantic modifiers only — never `.status-pill--success`/`--warning`/
 * `--danger`. Text first, color second (UI-UX-Principles.md §7/§8).
 */

.status-pill {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 0 var(--space-2);
    height: 1.375rem;
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    white-space: nowrap;
    /* An inline-flex box takes its baseline from its first item, which is the
       icon in a pill that has one and the text in one that does not; side by
       side ("Pending" beside "Proposal pending") the two sat a few pixels
       apart. Centring on the line aligns them whatever they start with. */
    vertical-align: middle;
}

/* A proposed assessment waiting on the record, carried inside its state pill
   (`record_fields.pill(proposal=…)`): "Pending · proposal" in the pill's own
   colour. The pencil only shows on a resting state, below. */
.status-pill__proposal {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
}

.status-pill__proposal::before {
    content: "·";
}

.status-pill__proposal .icon {
    display: none;
}

/* Sized to the pill's own text rather than 1em of the surrounding line, so a
   pill reads the same wherever it is dropped. */
.status-pill .icon {
    width: 0.875rem;
    height: 0.875rem;
    flex: 0 0 auto;
}

/* Lifecycle */
.status-pill--proposal {
    background: var(--color-warning-bg);
    border-color: var(--color-warning-border);
    color: var(--color-warning);
}

.status-pill--published {
    background: var(--color-success-bg);
    border-color: var(--color-success-border);
    color: var(--color-success);
}

.status-pill--draft-only {
    background: var(--color-neutral-bg);
    border-color: var(--color-border);
    color: var(--color-neutral-text);
}

/* The primary accent, not green: the published revision is settled, but the
   pending draft is work in hand, the same accent the "Drafts in progress"
   count uses. Green said "nothing to see" about a row that has a draft open. */
.status-pill--published-with-draft {
    background: var(--color-primary-bg);
    border-color: var(--color-primary-border);
    color: var(--color-primary);
}

/* The arrow between the initial level and what the treatment leaves. Muted and
   small: it is punctuation between two pills, not a third thing to read. */
.risk-pair-arrow {
    color: var(--color-text-subtle);
    font-size: var(--font-size-sm);
}

/* An avoided risk: nothing left to carry. The one place green is the honest
   answer in this vocabulary — elsewhere a low risk is still a risk. */
.status-pill--risk-none {
    background: var(--color-success-bg);
    border-color: var(--color-success-border);
    color: var(--color-success);
}

/* How far a requirement has been implemented. Open is neutral, not amber: every requirement is
   Open the moment it is written, and colouring it would put a warning on a project's whole backlog
   on its first day. Only the two claims somebody actually made take colour, and Confirmed takes the
   success step because a check by a second pair of eyes leaves nothing outstanding. */
.status-pill--fulfillment-open {
    background: var(--color-neutral-bg);
    border-color: var(--color-border);
    color: var(--color-neutral-text);
}

/* The accent rather than amber: "somebody says it is built" is a claim in progress, not a warning,
   a resolution, or an applicability scope decision. Keeping lifecycle progress on the primary
   accent prevents it from being confused with the amber in-scope applicability signal. */
.status-pill--fulfillment-implemented {
    background: var(--color-primary-bg);
    border-color: var(--color-primary-border);
    color: var(--color-primary);
}

.status-pill--fulfillment-confirmed {
    background: var(--color-success-bg);
    border-color: var(--color-success-border);
    color: var(--color-success);
}

/* The per-plan tally beside the requirements a treatment cites. A row of pills with counts, not a
   table: it is read at a glance, and the table under it is where the detail lives. */
.requirement-fulfillment-tally {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin: 0 0 var(--space-2);
}

/* Input review outcomes */
.status-pill--input-initial {
    background: var(--color-neutral-bg);
    border-color: var(--color-border);
    color: var(--color-neutral-text);
}

.status-pill--input-still-valid {
    background: var(--color-success-bg);
    border-color: var(--color-success-border);
    color: var(--color-success);
}

/* An assessment superseded by a newer assessment or record revision. Shown, but never as current.
   Neutral, not amber: the row's State column already carries the amber "Reassessment required"
   pill when a newer revision caused the stale conclusion, so colouring the marker too says the same
   thing twice. */
.status-pill--assessment-previous {
    background: var(--color-neutral-bg);
    border-color: var(--color-border);
    color: var(--color-neutral-text);
}

/* What is outstanding on a record, one line per condition, under the state they add up to. The
 * default stays quiet for findings; a product risk groups the same reasons in `.record-attention`
 * because resolving them is the main reason to open that page. */
.record-condition {
    margin-top: var(--space-1);
    margin-bottom: 0;
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
}

/* The cause behind a risk's Review/Reassessment state. A neutral surface and slim warning rule
 * establish hierarchy without making static work guidance look like an alert. */
.record-attention {
    margin: var(--space-3) 0 var(--space-4);
    padding: var(--space-3) var(--space-4);
    border-left: 3px solid var(--color-warning-border);
    border-radius: var(--radius-sm);
    background: var(--color-bg-subtle);
}

.record-attention h5 {
    margin: 0 0 var(--space-2);
    color: var(--color-warning);
    font-size: var(--font-size-sm);
}

.record-attention .record-condition {
    position: relative;
    margin: var(--space-1) 0 0;
    padding-left: var(--space-3);
    color: var(--color-text);
    font-size: var(--font-size-sm);
}

.record-attention .record-condition::before {
    position: absolute;
    left: 0;
    color: var(--color-warning);
    content: "•";
}

/* An applicable finding with no risk linked yet. Information, not a call to action: the risk work
   may simply not have happened. The amber applicability modifier reports scope; this neutral
   modifier reports only missing linkage, so a normally progressing finding does not look overdue. */
.status-pill--finding-without-risk {
    background: var(--color-neutral-bg);
    border-color: var(--color-border);
    color: var(--color-neutral-text);
}

/* Applicability is a conclusion, not a lifecycle state. Keep its modifiers separate from record
   state even where the colors match: an applicable conclusion says scope, while record-assessed says
   lifecycle. Amber marks an in-scope decision that carries follow-up risk work; green marks an
   out-of-scope decision where no treatment is expected. */
.status-pill--finding-applicable {
    background: var(--color-warning-bg);
    border-color: var(--color-warning-border);
    color: var(--color-warning);
}

.status-pill--finding-not-applicable {
    background: var(--color-success-bg);
    border-color: var(--color-success-border);
    color: var(--color-success);
}

.status-pill--finding-duplicate {
    background: var(--color-neutral-bg);
    border-color: var(--color-border);
    color: var(--color-neutral-text);
}

/* Recurring review date has passed */
.status-pill--review-overdue {
    background: var(--color-danger-bg);
    border-color: var(--color-danger-border);
    color: var(--color-danger);
}

/* Requires review due to stale inputs, or because a recurring review date will soon pass. */
.status-pill--requires-review {
    background: var(--color-warning-bg);
    border-color: var(--color-warning-border);
    color: var(--color-warning);
}

/* Pending-input fulfillment — independent of both lifecycle and review
   schedule (§4.3): a `pending_document_input` link whose input has since
   been published, waiting on a new revision to formalize the
   `docrev_input` link. Distinct from `.input-change-count` below, which is
   about links that already exist. */
.status-pill--requires-revision {
    background: var(--color-warning-bg);
    border-color: var(--color-warning-border);
    color: var(--color-warning);
}

/* Superseded revision (Phase 7 — docrev/history.html, docrev/view_document.html):
   page-local, not one of §4.3's four document-list axes, but a real,
   data-backed per-revision state worth the same visual treatment. */
.status-pill--superseded {
    background: var(--color-neutral-bg);
    border-color: var(--color-border);
    color: var(--color-neutral-text);
}

/* Input-changed count — a count, not a named state, so not a .status-pill
   (§4.3). Hidden entirely at zero; templates omit the element rather than
   render it invisibly. */
.input-change-count {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
}

/* ===== Layout primitives ===============================================
 * Spacing belongs to a named class, not to a `style=` attribute on the one
 * element that happened to need it. These three absorbed 19 inline styles
 * that had drifted to four different values (0.5/1/2rem) for the same three
 * jobs; going through the primitive means the next page inherits the
 * decision instead of re-guessing it. */

/* A titled block — heading plus its table or list — separated from the next
   one. The vertical rhythm of a page built out of stacked sections. */
.page-section {
    margin-bottom: var(--space-8);
}

/* An action that belongs to the list above it (an "add…" dropdown under a
   table), set apart from the list without being a section of its own. */
.list-action {
    margin: var(--space-3) 0;
}

/* A row of actions closing off the content above it. */
.button-row--separated {
    margin-top: var(--space-4);
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--color-border);
}

/* ===== Panel (replaces Bootstrap's .card) =============================== */

.panel {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    background: var(--color-surface);
    box-shadow: var(--shadow-sm);
}

.panel > * {
    padding: var(--space-4);
}

.panel > * + * {
    border-top: 1px solid var(--color-border);
}

/* The one panel whose contents destroy something that cannot be rebuilt. The border is the whole
   treatment: a danger-tinted background behind a form would make the input and the button fight
   the surface they sit on, and the button already carries the colour. */
.panel--danger {
    border-color: var(--color-danger-border);
}

.panel--danger h2 {
    color: var(--color-danger);
}

/* Document instructions are authoring guidance rather than the document canvas itself. Keep a
   readable prose measure while allowing the safe Markdown vocabulary to retain its structure. */
.document-instructions-panel {
    margin-bottom: var(--space-4);
}

.document-instructions {
    max-width: var(--prose-max);
}

.document-instructions > :first-child {
    margin-top: 0;
}

.document-instructions > :last-child {
    margin-bottom: 0;
}

.document-instructions h1,
.document-instructions h2,
.document-instructions h3,
.document-instructions h4,
.document-instructions h5,
.document-instructions h6 {
    margin-block: var(--space-4) var(--space-2);
}

.document-instructions li + li {
    margin-top: var(--space-1);
}

.document-instructions-editor {
    margin-top: var(--space-4);
}

.document-instructions-editor > summary {
    width: max-content;
    list-style: none;
}

.document-instructions-editor > summary::-webkit-details-marker {
    display: none;
}

.document-instructions-editor > form {
    margin-top: var(--space-3);
    max-width: var(--form-max);
}

/* ===== Data table (replaces bare Bootstrap .table) ====================== */

/* Sized to its content, not to the container.
 *
 * `width: 100%` shares surplus width across every column in proportion to its
 * content, so on a wide display a three-column table spreads a date and a
 * button as far apart as the table is wide. Making one column absorb the
 * surplus instead is worse, not better: it stakes everything on that column
 * being the row's subject, and it is not — the assessment table leads with a
 * one-character `#`, which then claimed hundreds of pixels — and even where
 * the first column *is* the title, the remaining columns get stranded against
 * the right edge.
 *
 * Sizing to content keeps every column adjacent to its neighbours at any
 * viewport width, which is what §3's information density actually asks for.
 * `max-width: 100%` keeps a genuinely wide table inside its container, where
 * `.data-table-scroll` can take over. */
/* A white surface on the grey canvas. `separate` with no spacing rather than
   `collapse`, because a collapsed table ignores `border-radius`; the cells
   only ever draw a bottom rule, so nothing doubles up. The ring is a shadow
   rather than a border so it adds nothing to the table's width.

   Not `overflow: hidden` to keep the cells inside the rounded corners: a row's
   action menu opens inside the table and was cut off at its bottom edge. The
   corner cells are rounded instead, below. */
.data-table {
    width: auto;
    max-width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    border-radius: var(--radius-lg);
    background: var(--color-surface);
    box-shadow: 0 0 0 1px var(--color-border), var(--shadow-sm);
    font-size: var(--font-size-sm);
}

/* The table's own last row only. A table that groups rows in several
   `<tbody>`s — a finding and its excerpt — keeps the rule between groups;
   `tbody tr:last-child` stripped it from every group, leaving a line only under
   the reference cell that spans the pair. A cell spanning down into the last
   row ends there too. */
.data-table tbody:last-child tr:last-child > td,
.data-table tbody:last-child td[rowspan] {
    border-bottom: 0;
}

/* The cells that paint a background (the tinted header, a hovered or selected
   row) follow the table's corners themselves, since the table no longer clips
   them. */
.data-table thead tr:first-child > th:first-child {
    border-top-left-radius: var(--radius-lg);
}

.data-table thead tr:first-child > th:last-child {
    border-top-right-radius: var(--radius-lg);
}

.data-table tbody:last-child tr:last-child > td:first-child,
.data-table tbody:last-child td[rowspan]:first-child {
    border-bottom-left-radius: var(--radius-lg);
}

.data-table tbody:last-child tr:last-child > td:last-child {
    border-bottom-right-radius: var(--radius-lg);
}

.data-table:has(+ .pl-pagination) tbody:last-child tr:last-child > td,
.data-table-scroll:has(+ .pl-pagination) > .data-table tbody:last-child tr:last-child > td,
:is(.panel, .record-dialog) .data-table :is(th, td) {
    border-radius: 0;
}

/* Already on a white surface: a ring there would be a box inside a box. The
   tinted header row is enough to set the table off from the panel's text. */
:is(.panel, .record-dialog) .data-table {
    border-radius: 0;
    box-shadow: none;
}

/* The corner rules above are more specific than a plain panel selector, and a
   flat table has no corners to follow. */
:is(.panel, .record-dialog) .data-table thead tr:first-child > th,
:is(.panel, .record-dialog) .data-table tbody:last-child tr:last-child > td {
    border-radius: 0;
}

/* A table that is a panel's own section runs edge to edge, like the rules
   between the sections, rather than taking the padding every section gets:
   padded, its tinted header floated inside the panel as a second card. The
   outer cells take the panel's inset back, so the text still lines up with
   the heading above. */
.panel > .data-table {
    padding: 0;
}

.panel > .data-table :is(th, td):first-child {
    padding-left: var(--space-4);
}

.panel > .data-table :is(th, td):last-child {
    padding-right: var(--space-4);
}

/* A form that adds to the table above it (link a requirement), when both sit
   in one fact rather than as a panel's own sections: separate them, or the
   form's label reads as the table's last row. */
dd > .data-table + form {
    margin-top: var(--space-4);
}

/* The second line of a cell — the revision a link cites, a run's revision
   and date, why a link needs review — under the fact it qualifies. */
.data-table .data-table-secondary {
    display: inline-block;
    margin-top: 0.125rem;
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
}

/* For a table that should still span its container — one whose columns are
   headed toward filling the width anyway, where a content-width table would
   look arbitrarily short of the edge. */
.data-table--fill {
    width: 100%;
}

/* `overflow-x: auto` clips at the padding box, and the table's ring and drop
   shadow are drawn outside the table — so a wrapped table lost its border
   entirely. The padding is exactly the shadow's reach (1px ring, `--shadow-sm`
   2px further down), and the negative margin takes it back so the table still
   lines up with the content around it. */
.data-table-scroll {
    max-width: 100%;
    overflow-x: auto;
    overscroll-behavior-inline: contain;
    padding: 1px 1px 3px;
    margin: -1px -1px -3px;
}

.data-table th,
.data-table td {
    padding: var(--space-2) var(--space-3);
    text-align: left;
    border-bottom: 1px solid var(--color-border);
}

.data-table th {
    background: var(--color-bg-subtle);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
    text-transform: uppercase;
    letter-spacing: var(--letter-spacing-caps);
}

.data-table th .data-table-sort {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    color: inherit;
    font: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    white-space: nowrap;
}

.data-table-sort__indicator {
    width: 0;
    height: 0;
    border-right: 0.28rem solid transparent;
    border-bottom: 0.36rem solid currentColor;
    border-left: 0.28rem solid transparent;
    opacity: 0;
}

.data-table-sort:hover .data-table-sort__indicator,
.data-table-sort--active .data-table-sort__indicator {
    opacity: 1;
}

.data-table-sort--descending .data-table-sort__indicator {
    transform: rotate(180deg);
}

.data-table tbody tr:hover {
    background: var(--color-bg-subtle);
}

/* In a table the link *is* the row's subject, so it carries the row's text
   colour and weight rather than announcing itself in accent blue. The colour
   change on hover, paired with the row highlight, is the affordance. */
.data-table a {
    color: var(--color-text);
    font-weight: var(--font-weight-medium);
}

.data-table a:hover {
    color: var(--color-primary);
}

/* The excerpt link reuses `.row-link` for the whole-row affordance, so it
   inherits the reference column's `nowrap` below. The excerpt is prose clamped
   to two lines, and one unbreakable line would make this `colspan` cell as wide
   as the description and stretch the table past its container. */
.finding-results .finding-table__description-text {
    white-space: normal;
}

/* A reference is built from hyphen-separated parts (`ABCDE-RSK-001`), and every
   hyphen is a break opportunity. An auto-width table may then shrink the
   reference column down to `ABCDE-` and wrap the remainder, splitting the
   reference across two lines as soon as the project prefix grows. The reference
   link is the column's whole content, so making it unbreakable is also what
   fixes the column's min-content width. */
.data-table .row-link {
    white-space: nowrap;
}

/* A record or document reference — the code people cite. Monospaced so a
   column of them lines up character by character and `0`/`O`, `1`/`l` cannot
   be confused when one is copied into a report. Unbreakable for the same
   reason as `.row-link` above, which not every reference link carries. */
.data-table .data-table-reference {
    font-family: var(--font-mono);
    font-size: var(--font-size-sm);
    /* Normal, not the medium every other table link takes: a monospaced face
       is already darker and wider per character, and at medium the reference
       column outweighed the titles beside it. */
    font-weight: var(--font-weight-normal);
    white-space: nowrap;
}

/* Whole-row click affordance for rows whose primary link is enhanced by
   table-row-link.js (§5 Phase 5) — scoped to tables without the explicit
   opt-out used by picker tables, where the checkbox is the row action. */
.data-table:not([data-no-row-click]) tbody tr:has(.row-link) {
    cursor: pointer;
}

/* A finding's excerpt is a second row in the same group. Paint both rows
   while the pointer is over the group or the primary row is selected after
   returning to the list. */
.finding-results .finding-table__group:hover > tr,
.finding-results .finding-table__group:has(> .data-table-row--selected) > tr {
    background: var(--color-bg-subtle);
}

/* `rowspan` is absent when the excerpt is empty, so pairing must be keyed on the
   description row itself, not on its side effect. */
.finding-results .finding-table__group > tr:first-child:has(+ .finding-table__description-row) > td:not([rowspan]) {
    border-bottom: 0;
    padding-bottom: var(--space-1);
}

.finding-results .finding-table__description-row > td {
    padding-top: 0;
}


.finding-results .finding-table__description-link {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    width: 100%;
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-normal);
    line-height: var(--line-height-base);
    text-decoration: none;
}

.finding-results .finding-table__description-link:hover {
    color: var(--color-primary);
}

.finding-results .finding-table__description-label {
    flex: 0 0 auto;
    color: inherit;
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-caps);
    text-transform: uppercase;
}

.finding-results .finding-table__description-text {
    min-width: 0;
    overflow: hidden;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}

/* A `<table>` is the one panel child whose padding the panel cannot set: CSS
 * drops padding on a table box when `border-collapse: collapse`, which
 * `.data-table` uses — so `.panel > *`'s padding was computed and then
 * silently ignored, leaving the cells flush against the card edge while every
 * other child was inset.
 *
 * The edge cells carry it instead. That also leaves the row rules running the
 * full width of the card: the text lines up with the panel's other children
 * while the separators still read as belonging to the card rather than to the
 * table alone. */
/* `.empty-state--quiet` sets `padding: … 0`, which overrides the horizontal
   padding `.panel > *` gave it — the text then sat flush against the card edge
   while every sibling was inset. Restored here rather than in the modifier,
   which is also used outside a panel where zero is right. */
.panel > .empty-state--quiet {
    padding-inline: var(--space-4);
}

.panel > .data-table th:first-child,
.panel > .data-table td:first-child {
    padding-left: var(--space-4);
}

.panel > .data-table th:last-child,
.panel > .data-table td:last-child {
    padding-right: var(--space-4);
}

.panel > .data-table thead th {
    padding-top: var(--space-4);
}

.panel > .data-table tbody tr:last-child td {
    padding-bottom: var(--space-4);
    /* The card's own border closes the table; the row's would sit a pixel above
       it and read as a doubled rule. */
    border-bottom: 0;
}

.panel > .data-table--sm-padding tbody tr:last-child td {
    padding-bottom: var(--space-2);
}

/* ===== Context nav: route-level navigation (§4.2 — not ARIA tabs) ====== */

.context-nav {
    display: flex;
    gap: var(--space-1);
    overflow-x: auto;
    overflow-y: hidden;
    border-bottom: 1px solid var(--color-border);
    margin-bottom: var(--space-4);
}

/* Project tools open from the More menu. The generic context nav clips on the
   block axis for scrolling tab strips, which would cut this menu off. The
   desktop project strip fits its container and can safely expose the menu. */
.context-nav--project {
    overflow: visible;
}

.context-nav > a,
.context-nav-more__toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border: 0;
    border-bottom: 2px solid transparent;
    background: none;
    color: var(--color-text-muted);
    font: inherit;
    font-weight: var(--font-weight-medium);
    font-size: var(--font-size-sm);
    text-decoration: none;
    margin-bottom: -1px;
    white-space: nowrap;
    cursor: pointer;
}

/* Matches `.tab .icon` — the two nav strips stack directly on top of each
   other on a document page, so a different icon size between them would read
   as an inconsistency rather than as a hierarchy. */
.context-nav > a .icon,
.context-nav-more__toggle .icon {
    width: 1rem;
    height: 1rem;
}

.context-nav > a:hover,
.context-nav-more__toggle:hover {
    color: var(--color-text);
}

.context-nav > a[aria-current="page"],
.context-nav-more__toggle--active {
    color: var(--color-primary);
    border-bottom-color: var(--color-primary);
}

/* A register's size beside its tab name: a neutral counter, not a status, so
   it takes the accent only on the tab the reader is on. */
.context-nav__count {
    padding: 0.125rem var(--space-2);
    border-radius: 999px;
    background: var(--color-neutral-bg);
    color: var(--color-neutral-text);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
}

.context-nav__count:empty {
    display: none;
}

.context-nav > a[aria-current="page"] .context-nav__count {
    background: var(--color-primary-bg);
    color: var(--color-primary);
}

.context-nav-more {
    flex: 0 0 auto;
}

.context-nav-more__menu {
    margin-top: 0;
}

.context-nav-more .pl-dropdown-item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    white-space: nowrap;
}

.context-nav-more .pl-dropdown-item .icon {
    flex: 0 0 auto;
    width: 1rem;
    height: 1rem;
    color: var(--color-text-muted);
}

.context-nav-more .pl-dropdown-item[aria-current="page"] {
    background: var(--color-primary-bg);
    color: var(--color-primary);
}

.context-nav-more .pl-dropdown-item[aria-current="page"] .icon {
    color: inherit;
}

/* ===== In-page ARIA tabs (§4.2 — Phase 7 document workspace only) ====== */

.tabs {
    display: flex;
    max-width: 100%;
    gap: var(--space-1);
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-inline: contain;
    scrollbar-width: thin;
    border-bottom: 1px solid var(--color-border);
    margin-bottom: var(--space-4);
}

.profile-tabs {
    margin-bottom: var(--space-5);
}

.profile-tab-panel {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
}

.profile-tab-panel > .panel {
    width: 100%;
}

.profile-access-tokens-panel {
    max-width: var(--form-max);
}

.tab {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border: none;
    background: none;
    color: var(--color-text-muted);
    font-family: inherit;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    text-decoration: none;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    white-space: nowrap;
}

/* Slightly under the cap height of the label, so the icon reads as a marker
   rather than competing with the text. Inherits the tab's own colour, so it
   picks up the selected and hover states for free. */
.tab .icon {
    width: 1rem;
    height: 1rem;
}

.tab:hover {
    color: var(--color-text);
}

.tab[aria-selected="true"] {
    color: var(--color-primary);
    border-bottom-color: var(--color-primary);
}
/* ===== Stat tile ========================================================
 * One register's outstanding work: an icon chip, the register's name, and one
 * count per kind of work in it, each linking to the list filtered to exactly
 * the rows it counted where there is such a list. The same tile in the project
 * header and on the Overview. Zero counts are never rendered, so every tile
 * here has something in it. Colour comes from two custom properties set by a
 * `--primary`/`--warning` modifier: the tile's sets the glyph, each count's
 * its own number, so a Documents tile owing a review stays amber while its
 * drafts count stays blue. A tile wraps as a whole; only on a narrow screen do
 * its counts wrap inside it. */
.stat-grid {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: var(--space-4) 0;
}

.stat-tile {
    --tile-accent: var(--color-neutral-text);
    --tile-accent-bg: var(--color-neutral-bg);

    display: flex;
    flex: 0 1 auto;
    flex-wrap: wrap;
    min-width: 0;
    max-width: 100%;
    align-items: center;
    gap: var(--space-1) var(--space-2);
    min-height: 2.5rem;
    box-sizing: border-box;
    padding: var(--space-1) var(--space-3) var(--space-1) var(--space-1);
    border-radius: var(--radius-lg);
    background: var(--color-surface);
    box-shadow: 0 0 0 1px var(--color-border), var(--shadow-sm);
}

/* Work in hand rather than work owed — §8's primary accent. */
.stat-tile--primary,
.stat-tile-part--primary {
    --tile-accent: var(--color-primary);
    --tile-accent-bg: var(--color-primary-bg);
}

/* Something is owed: a review, a revision, an assessment. */
.stat-tile--warning,
.stat-tile-part--warning {
    --tile-accent: var(--color-warning);
    --tile-accent-bg: var(--color-warning-bg);
}

/* A tinted square behind the glyph — where the tile's colour lands. */
.stat-tile-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: var(--radius-md);
    background: var(--tile-accent-bg);
    color: var(--tile-accent);
}

.stat-tile .icon {
    width: 1rem;
    height: 1rem;
}

.stat-tile-register {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    white-space: nowrap;
}

.stat-tile-parts {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 var(--space-1);
    min-width: 0;
}

/* Padded so the hover wash has room around the number; the negative margin
   keeps that padding from widening the tile. */
.stat-tile-part {
    display: inline-flex;
    align-items: baseline;
    gap: 0.3em;
    padding: 0.125rem var(--space-1);
    margin: 0 calc(-1 * var(--space-1) / 2);
    border-radius: var(--radius-md);
    color: var(--color-text);
    font-size: var(--font-size-sm);
    text-decoration: none;
    white-space: nowrap;
}

a.stat-tile-part:hover {
    background: var(--color-neutral-bg);
    color: var(--color-text);
    text-decoration: none;
}

/* The count takes the accent too, so the colour reads from the number the
   reader is looking at and not only from the glyph beside it. */
.stat-tile-part strong {
    font-size: var(--font-size-md);
    font-weight: var(--font-weight-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--tile-accent);
}

.stat-tile-separator {
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

/* An assessment list beside the assessment being read, rather than above it.
 *
 * An even split. Sizing the list to its content instead left it hugging its
 * own columns with no slack while the detail ran wider than its longest line
 * needed — the list looked cramped and the card looked empty. Halves give the
 * list room to breathe its columns apart and hold the detail to a width its
 * facts grid and prose measure actually use. Stacks below the width where both
 * stop being readable — at which point the detail follows the list, which is
 * the order it was in before. */
.record-master-detail {
    display: grid;
    /* `auto-fit` rather than a media query: what matters is how much room this
       grid has, not how wide the window is, and the two differ by the sidebar
       and the page padding — a viewport breakpoint was splitting the pair at
       widths where the list had only ~488px and its columns wrapped.
       The floor is the widest of these tables at its natural width (the
       finding's seven columns need 38.6rem), rounded up, so a column never
       carries a table narrower than it can render. */
    grid-template-columns: repeat(auto-fit, minmax(40rem, 1fr));
    gap: var(--space-4);
    align-items: start;
}

/* Records pages share one vertical rhythm for action rows, cards, tables, and
   tab panels instead of each template inventing its own margins. */
.record-page,
.record-stack {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
}

.record-page > .page-actions,
.record-page > .pl-pagination,
.record-page > .tabs {
    margin-bottom: 0;
}

.record-page > .page-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
}

.record-page > .page-actions h1,
.record-page > .page-actions form {
    margin: 0;
}

.page-actions__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
}

.record-page > .pl-pagination {
    margin-top: 0;
}


/* ===== Overview in-place-edit row (Phase 6 — project/group Overview's
 * existing edit-in-place JS, carried over unchanged) ===================== */

.overview-edit-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-3);
}

.overview-edit-fields {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

/* ===== Status page (Phase 9 — _error/4xx|500.html) ======================
 * A centered single-panel layout for pages with no other content —
 * error pages today, reusable anywhere else a bare status message is
 * the whole page. */

.status-page {
    display: flex;
    justify-content: center;
    align-items: flex-start;
    min-height: calc(100vh - var(--topbar-height));
    padding-top: var(--space-8);
}

.status-page .panel {
    width: 100%;
    max-width: 26rem;
    text-align: center;
}

.status-page h1 {
    margin-bottom: var(--space-2);
}

.status-page pre {
    text-align: left;
    white-space: pre-wrap;
    word-break: break-word;
    font-size: var(--font-size-xs);
    background: var(--color-bg-subtle);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    margin: var(--space-3) 0;
}

/* ===== Dropdown / menu (§4.2 — built here, wired in Phase 2+) =========== */

.pl-dropdown {
    position: relative;
    display: inline-block;
}

/* Progressive enhancement (§4.2): before dropdown.js runs, this is a
   plain, always-visible list — nothing is hidden by default. Only once
   `.pl-dropdown--enhanced` is added (by the script, after it successfully
   initializes) does the menu become collapsible; JS then toggles the
   `hidden` attribute to open/close it. Never the reverse — a rule that
   hid the menu by default and relied on JS to reveal it would fail in
   exactly the case (no JS) this exists to cover. */
.pl-dropdown-menu {
    margin: var(--space-1) 0 0 0;
    padding: var(--space-1);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-surface);
    list-style: none;
}

.pl-dropdown--enhanced .pl-dropdown-menu {
    display: none;
    position: absolute;
    z-index: 20;
    min-width: 10rem;
    box-shadow: var(--shadow-md);
}

.pl-dropdown--enhanced .pl-dropdown-menu:not([hidden]) {
    display: block;
}

.pl-dropdown-item {
    display: block;
    width: 100%;
    padding: var(--space-2) var(--space-3);
    border: none;
    background: none;
    color: var(--color-text);
    font-family: inherit;
    font-size: var(--font-size-sm);
    text-align: left;
    text-decoration: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.pl-dropdown-item:hover {
    background: var(--color-neutral-bg);
}

.pl-dropdown-item--danger {
    color: var(--color-danger);
}

.pl-dropdown-item--danger:hover {
    background: var(--color-danger-bg);
}

/* A non-interactive row (e.g. a username display) — deliberately not a
   `.pl-dropdown-item`, so dropdown.js's keyboard/focus handling (which
   targets `.pl-dropdown-item`) correctly skips it. */
.pl-dropdown-label {
    padding: var(--space-2) var(--space-3);
    font-weight: var(--font-weight-semibold);
    font-size: var(--font-size-sm);
}

.pl-dropdown-display-name,
.pl-dropdown-account-name {
    display: block;
}

.pl-dropdown-account-name {
    margin-top: var(--space-1);
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-normal);
}

.pl-dropdown-divider {
    height: 1px;
    margin: var(--space-1) 0;
    background: var(--color-border);
}

/* A <form> wrapping a single .pl-dropdown-item (e.g. a sign-out button)
   shouldn't add its own box to the menu's layout. */
.pl-dropdown-menu form {
    margin: 0;
}

/* ===== Searchable dropdown (Phase 7 — link-an-attachment/input pickers,
 * dropdown.js's `[data-dropdown-search]` support) ======================== */

.pl-dropdown-menu--searchable {
    min-width: 16rem;
    max-height: 20rem;
    overflow-y: auto;
}

.pl-dropdown-search-wrap {
    position: sticky;
    top: 0;
    padding: var(--space-1);
    background: var(--color-surface);
}

.pl-dropdown-search-wrap .input {
    width: 100%;
}

.pl-dropdown-empty {
    padding: var(--space-2) var(--space-3);
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

/* ===== Icons ============================================================ */

.icon {
    width: 1em;
    height: 1em;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    vertical-align: -0.125em;
}

/* ===== Star toggle ==================================================== */
/*
 * Deliberately not a `.pl-btn`: this is a bare icon affordance sitting in a
 * table cell and beside a page title, where a bordered button would out-shout
 * the row it annotates — and there is no `.pl-btn--icon` to lean on.
 *
 * The primary signal is a *shape* change (outline vs filled), not colour, with
 * `aria-pressed` carrying it programmatically — so the control still reads
 * correctly in monochrome (UI-UX-Principles.md §7/§8). `--starred` names the
 * state rather than the colour, per the semantic-modifier rule above.
 */
.star-toggle-form {
    display: inline-flex;
}

.star-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-1);
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    color: var(--color-text-muted);
    background: none;
    /* `.icon` is sized in `em`. */
    font-size: var(--font-size-lg);
    line-height: 1;
    cursor: pointer;
}

.star-toggle:hover,
.star-toggle:focus-visible {
    color: var(--color-star-outline);
    background: var(--color-neutral-bg);
}

.star-toggle--starred .icon {
    fill: var(--color-star);
    stroke: var(--color-star-outline);
}

/* Shrink-to-fit control column in a `.data-table`. */
.star-toggle-cell {
    width: 1px;
    padding-right: 0;
}

/* ===== Scope header: "which group/project am I in" (§4.6) ============= */

.scope-header {
    margin-bottom: var(--space-4);
}

.scope-header-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin-bottom: var(--space-1);
}

/* The project's outstanding work, on its own line under the project name. The
   chips themselves are `.stat-tile`'s. */
.scope-header-status {
    flex: 1 0 100%;
    min-width: 0;
}

.scope-header-status .stat-grid {
    margin: 0;
}

/* Groups the star and the title against the row's `space-between`, which would
   otherwise spread them and the action button across the full width. */
.scope-header-title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
}

.project-lookup {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    max-width: 36rem;
}

.topbar .project-lookup {
    flex: 0 1 18rem;
    min-width: 10rem;
}

.project-lookup {
    position: relative;
}

/* Room on the left for the magnifier, which sits over the field's start. */
.project-lookup .input {
    flex: 1;
    min-width: 0;
    padding-left: calc(var(--space-2) + 1rem + var(--space-2));
}

.project-lookup-submit {
    position: absolute;
    left: var(--space-1);
    top: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-text-muted);
    cursor: pointer;
    transform: translateY(-50%);
}

.project-lookup-submit:hover {
    background: var(--color-neutral-bg);
    color: var(--color-text);
}

.project-lookup-submit .icon {
    width: 1rem;
    height: 1rem;
}

@media (max-width: 48rem) {
    .topbar .project-lookup {
        order: 1;
        flex: 1 0 100%;
        max-width: none;
    }
}

.scope-header h1 {
    margin-bottom: var(--space-1);
}

.scope-header-row h1 {
    margin-bottom: 0;
}

.scope-header-summary {
    color: var(--color-text-muted);
    margin: 0;
}

/* The project name rendered as context above a page with its own subject.
   Sized to sit clearly below the page's `<h1>` — it names where you are, and
   the heading below names what you are looking at. */
.scope-header--context {
    margin-bottom: var(--space-2);
}

/* Clearly below the page's `<h1>` at `2xl`, but large enough to read as the
   project you are working in rather than as a caption. Muted rather than
   small is what keeps it subordinate — dropping the size further made it look
   like metadata about the page instead of the thing containing it. */
.scope-header-context-name {
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-medium);
    color: var(--color-text-muted);
}

/* ===== Form primitives ================================================== */

.field {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    margin-bottom: var(--space-3);
}

.inline-field {
    display: inline-flex;
}

.field-row {
    flex-direction: row;
    margin-top: var(--space-2);
}

.field > label {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
}

.input,
.textarea,
.select {
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-md);
    background: var(--color-surface);
    color: var(--color-text);
    font-family: inherit;
    font-size: var(--font-size-base);
}

/* File uploads keep the native picker (it is the accessible one), but its
   button is styled to match `.pl-btn--outline` so an upload row reads like
   every other control pair instead of sprouting a browser-chrome button. */
.input[type="file"] {
    padding: 0;
    border: 0;
    background: transparent;
    font-size: var(--font-size-sm);
}

.input[type="file"]::file-selector-button {
    padding: var(--space-2) var(--space-4);
    margin-right: var(--space-3);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-md);
    background: var(--color-surface);
    box-shadow: var(--shadow-sm);
    color: var(--color-text);
    font-family: inherit;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    /* 1, like `.pl-btn`: it sits beside one (Choose File / Upload) and must
       come out the same height. */
    line-height: 1;
    cursor: pointer;
}

.input[type="file"]::file-selector-button:hover {
    background: var(--color-bg-subtle);
    border-color: var(--color-neutral-text);
}

.input[type="file"]::file-selector-button:active {
    background: var(--color-neutral-bg);
    box-shadow: none;
}

/* A native `<select>` is laid out by the platform, not by the box above: it
   ignores the shared padding for its own metrics and paints the OS chevron.
   Side by side in a filter bar with `.input`, that reads as two different
   control sets. `appearance: none` hands layout back to us; the chevron is an
   inline data URI so it is one file, no request, and it inherits nothing from
   the OS theme. `--control-height` pins both to the same box.
   Encoded with `%23` for `#` — a raw `#` starts a fragment inside `url()`.
 *
 * Matched by element, not by class alone: `.input` is worn by 53 `<input>`s,
 * 4 `<select>`s *and 17 `<textarea>`s*, so a bare `.input { height }` pins a
 * textarea to one line and clips whatever its `rows` asked for. A multi-line
 * control must size itself. */
input.input,
select.input,
.select {
    height: var(--control-height);
}

.select {
    appearance: none;
    padding-right: var(--space-8);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235b6470' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right var(--space-2) center;
    background-size: 1rem 1rem;
}

/* `.textarea` sets its own height from `rows`, so it is excluded above — but
   it still needs the shared idea of a comfortable line. */
/* Keeps the right padding that reserves room for the chevron — a `padding`
   shorthand here would collapse it and let the text run under the arrow. */
.select--sm {
    height: var(--control-height-sm);
    padding-block: var(--space-1);
    padding-left: var(--space-2);
    font-size: var(--font-size-sm);
    border-radius: var(--radius-sm);
    background-size: 0.875rem 0.875rem;
}

.textarea {
    resize: vertical;
    min-height: 6rem;
}

/* Most forms style their textareas with `.input`. A width the reader can drag past its column
   spills out of the card or field it belongs to, so only the height is theirs to change. */
textarea.input {
    resize: vertical;
}

/* Read-only look for the Overview pages' in-place-edit name/summary fields
   (Phase 6) — tracks the `readOnly`/`editable` JS toggle automatically via
   the pseudo-class instead of a separately-maintained class name. */
.input:read-only,
.textarea:read-only {
    border-color: transparent;
    background: transparent;
    padding-left: 0;
    padding-right: 0;
}

.input--heading {
    font-size: var(--font-size-2xl);
    font-weight: var(--font-weight-semibold);
}

/*
 * De-emphasised prose. `.muted` is the bare form used in record and snapshot pages; `.input--muted`
 * is the same treatment named for form context. Both are defined here because `.muted` was in
 * nineteen templates with no rule behind it, rendering at full emphasis wherever it appeared.
 */
.muted,
.input--muted {
    color: var(--color-text-muted);
}

.field-required-mark {
    color: var(--color-danger);
}

.field-hint {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
    line-height: var(--line-height-base);
}

.field--invalid .input,
.field--invalid .textarea,
.field--invalid .select {
    border-color: var(--color-danger);
}

.field-error {
    color: var(--color-danger);
    font-size: var(--font-size-xs);
}

/* ===== Simple form pages (Phase 4: login, group/new, new_project, etc.) */

/* Centred in the content column. A narrow form pinned to the left of a wide
   page reads as the corner of a wider layout that failed to load; centring
   makes the measure look chosen, and puts the fields under the reader's focus
   rather than off to one side. */
.form-page {
    /* `width: 100%` before the cap, because `margin-inline: auto` on a flex
       item (these pages sit inside `.record-page`, a flex column) makes it
       shrink to its content instead of filling — which is why the New finding
       form was rendering at 241px rather than at its measure. */
    width: 100%;
    max-width: var(--form-max);
    margin-inline: auto;
}

/* A form that carries two columns rather than one. Still centred, for the
   reason above — only the measure changes. */
.form-page--wide {
    max-width: var(--form-max-wide);
}

.form-error {
    color: var(--color-danger);
    background: var(--color-danger-bg);
    border: 1px solid var(--color-danger-border);
    border-radius: var(--radius-md);
    padding: var(--space-2) var(--space-3);
    margin-bottom: var(--space-3);
}

/* New documents have mutually exclusive origins, but each needs enough context
 * to make a template, a fresh Office file, and an upload comparable. Native
 * radios keep the choice keyboard-accessible while the label supplies the
 * button-sized hit target and selected state. */
.document-source-fieldset {
    margin: 0 0 var(--space-3);
    padding: 0;
    border: 0;
}

.document-source-fieldset > legend {
    margin-bottom: var(--space-2);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
}

.document-source-section + .document-source-section {
    margin-top: var(--space-4);
}

.document-source-section > h2 {
    margin: 0 0 var(--space-2);
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    text-transform: uppercase;
}

.document-source-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2);
}

.document-source-choice {
    display: grid;
    grid-template-areas: "icon copy control";
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--space-2);
    min-height: 5rem;
    padding: var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    background: var(--color-surface);
    box-shadow: var(--shadow-sm);
    cursor: pointer;
    transition: border-color var(--duration-fast), background-color var(--duration-fast);
}

.document-source-choice:hover {
    background: var(--color-bg-subtle);
    border-color: var(--color-border-strong);
}

.document-source-choice:has(input:checked) {
    border-color: var(--color-primary-border);
    background: var(--color-primary-bg);
}

.document-source-choice:has(input:focus-visible) {
    outline: 2px solid var(--color-focus-ring);
    outline-offset: 2px;
}

.document-source-choice > input {
    grid-area: control;
    margin: 0;
}

.document-source-icon {
    grid-area: icon;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: var(--radius-md);
    background: var(--color-primary-bg);
    color: var(--color-primary);
}

.document-source-choice:has(input:checked) .document-source-icon {
    background: var(--color-surface);
}

.document-source-icon .icon {
    width: 1.25rem;
    height: 1.25rem;
}

.document-source-copy {
    grid-area: copy;
    min-width: 0;
}

.document-source-copy strong,
.document-source-copy small {
    display: block;
}

.document-source-copy small {
    margin-top: var(--space-1);
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
}

.document-source-choice--unavailable {
    opacity: 0.6;
    box-shadow: none;
    cursor: not-allowed;
}

.document-source-choice--unavailable:hover {
    border-color: var(--color-border);
    background: var(--color-surface);
}

@media (max-width: 40rem) {
    .document-source-grid {
        grid-template-columns: 1fr;
    }
}
.document-template-create {
    margin-bottom: var(--space-5);
}

.document-template-heading,
.document-template-list-heading,
.document-template-card-heading {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-3);
}

.document-template-heading {
    margin-bottom: var(--space-3);
}

.document-template-heading h2,
.document-template-list-heading h2,
.document-template-card h3 {
    margin: 0;
}

.document-template-list-heading {
    align-items: center;
    margin-bottom: var(--space-3);
}

.document-template-count {
    min-width: 1.75rem;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-full);
    background: var(--color-primary-bg);
    color: var(--color-primary);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    text-align: center;
}

.document-template-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-3);
}

.document-template-card-heading {
    justify-content: flex-start;
}

.document-template-card-heading .input--muted {
    margin: var(--space-1) 0 0;
}

.document-template-card .facts {
    margin-top: var(--space-3);
}

.document-template-replace {
    margin-top: var(--space-3);
    padding-top: var(--space-3);
    border-top: 1px solid var(--color-border);
}

.document-template-replace summary {
    color: var(--color-primary);
    cursor: pointer;
    font-weight: var(--font-weight-semibold);
}

.document-template-replace[open] summary {
    margin-bottom: var(--space-3);
}

@media (max-width: 50rem) {
    .document-template-grid {
        grid-template-columns: 1fr;
    }
}


.form-warning {
    color: var(--color-warning);
    background: var(--color-warning-bg);
    border: 1px solid var(--color-warning-border);
    border-radius: var(--radius-md);
    padding: var(--space-2) var(--space-3);
    margin-bottom: var(--space-3);
}

/* ===== Form success (Phase 9 — profile.html's session-ended
 * confirmations) — .form-error's success counterpart. ============= */

.form-success {
    color: var(--color-success);
    background: var(--color-success-bg);
    border: 1px solid var(--color-success-border);
    border-radius: var(--radius-md);
    padding: var(--space-2) var(--space-3);
    margin-bottom: var(--space-3);
}

.profile-avatar-editor {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}

.profile-avatar-editor form {
  display: inline-block;
  margin-right: var(--space-2);
}

.profile-avatar {
  display: grid;
  width: 6rem;
  height: 6rem;
  flex: 0 0 auto;
  place-items: center;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  object-fit: cover;
}

.profile-avatar--fallback {
  color: var(--color-text-muted);
  background: var(--color-bg-subtle);
}

.profile-avatar--fallback .icon {
  width: 2.5rem;
  height: 2.5rem;
}

.button-row {
    display: flex;
    gap: var(--space-2);
}

.pl-split-button {
    display: inline-flex;
}

.pl-split-button > .pl-btn:first-child {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}

.pl-split-button-toggle {
    min-width: 2.25rem;
    margin-left: -1px;
    padding-inline: var(--space-2);
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
}

/* On an outline split button the two halves are told apart by their shared
   border. A filled one has no such line — both halves are the same block of
   colour — so the seam has to be drawn, in the darker shade the button
   already uses for hover. */
.pl-split-button .pl-btn--primary.pl-split-button-toggle {
    border-left-color: var(--color-primary-hover);
}

/* Anchored to its trigger's right edge. The default left anchor is right for
   a menu in the body of a page; a split button sitting at the far right of a
   header would open one off the side of the window. */
.pl-dropdown--enhanced .pl-dropdown-menu--end {
    right: 0;
}

/* ===== Bulk action forms ================================================= */

.bulk-action-form {
    margin-top: var(--space-4);
}

.bulk-action-row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--space-3);
}

/* The two bundle creation forms share a disclosure panel. Keep their controls
   aligned without carrying the generic form's extra top margin into the grid. */
.bundle-create-form {
    margin-top: 0;
}

.bundle-create-form .field {
    flex: 1 1 12rem;
    min-width: 0;
    margin-bottom: 0;
}

.bundle-create-form .input,
.bundle-create-form .select {
    width: 100%;
}

/* A page's title with its one row of page actions — the bundle and snapshot
   pages. Creating and comparing open dialogs from that row, so the content is
   the first thing under the title rather than a strip of inline forms. */
.page-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-3);
}

.page-header h1 {
    margin: 0;
}

.page-header__meta {
    margin: var(--space-1) 0 0;
}

.page-header .button-row {
    align-items: center;
}

/* The Documents panel's header is the panel's own first section, whose
   padding already separates it from the table below. */
.bundle-documents > .panel-header {
    flex-wrap: wrap;
    margin-bottom: 0;
}

.bundle-add-document {
    display: flex;
    flex: 0 1 26rem;
    gap: var(--space-2);
    min-width: 0;
}

.bundle-add-document .select {
    flex: 1 1 auto;
    min-width: 0;
}

.bundle-snapshot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
}

.bundle-snapshot__body {
    min-width: 0;
    margin: 0;
}

.bundle-snapshot__title {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-3);
    font-weight: var(--font-weight-medium);
}

.bundle-snapshot__meta {
    margin: var(--space-1) 0 0;
    font-size: var(--font-size-sm);
}

.bundle-snapshot__meta--warning {
    color: var(--color-warning);
}

.bundle-snapshot__types {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
    margin: var(--space-2) 0 0;
    padding: 0;
    list-style: none;
}

/* A panel holding a wide table: a narrow screen scrolls the table inside the
   card rather than letting it run out past the card's edge. */
.panel--scroll-x {
    overflow-x: auto;
}

/* A drift group's heading inside the panel, one per kind of change. */
.snapshot-drift__group {
    margin: 0;
    font-size: var(--font-size-md);
}

.snapshot-drift__since {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-normal);
}

.snapshot-captures__captured {
    white-space: nowrap;
}

/* Labels a radio group rather than a control, so `.field > label` misses it. */
.bundle-review-interval-label {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
}

.bundle-review-presets {
    align-self: flex-start;
}

.bundle-review-days .input {
    max-width: 10rem;
}

.bundle-tag-skipped p,
.bundle-tag-skipped ul {
    margin: 0;
}

.bundle-tag-skipped ul {
    padding-left: var(--space-5);
}

.admin-user-role-form {
    display: inline-flex;
    align-items: flex-end;
    gap: var(--space-2);
    white-space: nowrap;
}

/* Disable/Enable sits beside the role form in the same actions cell. */
.admin-user-access-form {
    display: inline-flex;
    margin-left: var(--space-2);
}

/* The inline "Edit username" and "Edit email" disclosures on a user row. */
.admin-user-edit summary {
    color: var(--color-primary);
    cursor: pointer;
    font-size: var(--font-size-sm);
}

.admin-user-edit-form {
    display: flex;
    gap: var(--space-2);
    margin-top: var(--space-2);
}

/* An account that can no longer sign in is what an admin scans this column for, so it keeps a
   filled pill; "not yet signed in" is work in hand (the accent), and Active rests. */
.status-pill--user-disabled {
    background: var(--color-neutral-bg);
    border-color: var(--color-neutral-border);
    color: var(--color-neutral-text);
}

.status-pill--user-not-signed-in {
    background: var(--color-primary-bg);
    border-color: var(--color-primary-border);
    color: var(--color-primary);
}

.status-pill--user-active {
    background: var(--color-success-bg);
    border-color: var(--color-success-border);
    color: var(--color-success);
}

/* User creation is a vertical workflow: keeping each control on its own row
   keeps the submit action visually separate from the fields. */
.admin-create-user-form .bulk-action-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
}

.admin-create-user-form .field {
    margin-bottom: 0;
}

.admin-create-user-form .input {
    width: 100%;
}

.admin-create-user-form .select {
    width: auto;
    align-self: flex-start;
}

.admin-create-user-form .pl-btn {
    justify-self: start;
}

.radio-group {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-normal);
}

.radio-group label {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
}

/* ===== Group index cards ===============================================
 * A card per group rather than a table: a group has no columns worth
 * comparing across rows, but it does have a size, the reader's standing in it,
 * and the pages that standing opens — which a one-line row had no room for. */

.group-cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
    gap: var(--space-4);
    margin: 0;
    padding: 0;
    list-style: none;
}

.group-card {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    background: var(--color-surface);
    box-shadow: var(--shadow-sm);
    transition: border-color var(--duration-fast) ease, box-shadow var(--duration-fast) ease;
}

.group-card:hover {
    border-color: var(--color-border-strong);
    box-shadow: var(--shadow-md);
}

.group-card-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-3);
}

.group-card-name {
    margin: 0;
    min-width: 0;
    font-size: var(--font-size-lg);
    line-height: var(--line-height-tight);
    overflow-wrap: anywhere;
}

.group-card-name a {
    color: var(--color-text);
}

/* No underline: the link covers the whole card, so the name would underline
   wherever the pointer is on it. The colour and the card's lift carry it. */
.group-card-name a:hover {
    color: var(--color-primary);
    text-decoration: none;
}

/* The whole card is the target for the group's page, not only its name: the
   name link's box is stretched over the card. The management links below sit
   above it, so they stay their own targets. */
.group-card-name a::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
}

.status-pill--group-role {
    flex: 0 0 auto;
    background: var(--color-neutral-bg);
    border-color: var(--color-neutral-border);
    color: var(--color-neutral-text);
}

/* Clamped so one long description cannot make its row of cards uneven. */
.group-card-summary {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
}

.group-card-summary--none {
    color: var(--color-text-subtle);
    font-style: italic;
}

.group-card-stats {
    display: flex;
    gap: var(--space-6);
    margin: auto 0 0;
}

.group-card-stats dt {
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    text-transform: uppercase;
    letter-spacing: var(--letter-spacing-caps);
}

.group-card-stats dd {
    margin: 0;
    font-size: var(--font-size-xl);
    font-weight: var(--font-weight-semibold);
    font-variant-numeric: tabular-nums;
    line-height: var(--line-height-tight);
}

.group-card-links {
    position: relative;
    display: flex;
    gap: var(--space-4);
    margin: 0 calc(-1 * var(--space-5)) calc(-1 * var(--space-4));
    padding: var(--space-3) var(--space-5);
    border-top: 1px solid var(--color-border-subtle);
    font-size: var(--font-size-sm);
}

.group-card-links a {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    color: var(--color-text-muted);
}

.group-card-links a:hover {
    color: var(--color-primary);
}

/* ===== Item list (Phase 7 — document workspace: attachments, approvals,
 * inputs, pending inputs) — a row of content plus a trailing action. ==== */

.item-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.item-list li {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-2) 0;
}

.item-list li + li {
    border-top: 1px solid var(--color-border);
}

.item-list-main {
    min-width: 0;
}

/* ===== Panel header row (Phase 7 — a panel's title plus a trailing
 * action/meta, e.g. the Content panel's export button) ================== */

.panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin-bottom: var(--space-3);
}

.panel-header h2 {
    margin: 0;
}

/* A panel's heading names a section of the page, under the page's own h1, so
   it steps down a size: at the h2 default every panel heading competed with
   the record title for first read. */
.panel h2 {
    font-size: var(--font-size-lg);
}

/* A trailing action keeps its label on one line; the heading beside it is the one to wrap. */
.panel-header__action {
    flex-shrink: 0;
    white-space: nowrap;
}

.panel-header-meta {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

/* ===== Empty state (Phase 4) ============================================ */

/* The prominent form: for a page or panel whose *entire* content is absent,
   where the box is the thing the reader is meant to land on. */
.empty-state {
    padding: var(--space-6);
    text-align: center;
    color: var(--color-text-muted);
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-lg);
}

/* The quiet form: for one section among several, where the absence is a fact
   about that section rather than the page's subject.
 *
 * A review dashboard exists to answer "what needs my attention?" (§11). With
 * the prominent box in every section, its two largest elements were the ones
 * reporting that nothing was wrong, and the rows that did need attention sat
 * between them in plain text — the emphasis exactly inverted. Same words,
 * proportionate weight.
 *
 * Not hidden outright, though the sidebar hides empty sections under §4.6:
 * there, an empty section carries no information, whereas "nothing overdue"
 * on a dashboard is a real answer and worth one line. */
.empty-state--quiet {
    padding: var(--space-2) 0;
    text-align: left;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    border: none;
    border-radius: 0;
}

/* ===== Documents view — master/detail (Phase 5) =========================
 * The first htmx-driven interaction in the app (§4.5/§5). `.documents-view`
 * itself isn't a separate element — `#document-results` (in documents.html)
 * is both the htmx swap target and the layout root.
 */

.documents-page-title {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
}

.documents-page-title .documents-count {
    margin-bottom: 0;
}

.documents-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
}

.finding-toolbar__controls {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
}

/* A view option, not a filter: it sits at the far end of the filter button's
   row and stays there. In the flow it followed the filter disclosure, so
   opening the filters — which makes the disclosure as wide as the row —
   threw it to the right of the controls, a line above where it had been. */
.finding-excerpts-toggle {
    position: absolute;
    top: 0;
    right: 0;
    display: flex;
    align-items: center;
    height: var(--control-height);
    margin: 0;
}

.finding-excerpts-toggle label {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

.finding-excerpts-toggle input {
    margin: 0;
    accent-color: var(--color-primary);
}

/* The filter-row control height, so whatever shares its row — the excerpt
   toggle — can line up with it by the same token. */
.document-filters > summary {
    height: var(--control-height);
    list-style: none;
    cursor: pointer;
}

.document-filters > summary::-webkit-details-marker {
    display: none;
}

/* Not `--color-neutral-bg`: that is exactly what `.pl-btn:hover` paints, so
   the open state would be indistinguishable from a hover and — being the more
   specific rule — would swallow the hover feedback on the control that is
   currently active. The primary tint reads as "on" and leaves hover free. */
.document-filters[open] > summary {
    background: var(--color-primary-bg);
    border-color: var(--color-primary);
    color: var(--color-primary);
}

.document-filters[open] > summary:hover {
    background: var(--color-primary-bg-hover);
}

.document-filter-controls {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-2);
}

.document-filter-controls .filter-bar {
    flex-wrap: wrap;
    align-items: center;
    margin-top: 0;
}

.document-filter-clear {
    margin: 0;
}

.filter-bar {
    display: flex;
    gap: var(--space-2);
}

/* ===== Bulk risk acceptance ==============================================
 * The sweep has more controls than a normal list filter. Keep its groups in a
 * narrow vertical flow instead of letting one flex row grow past the page,
 * while still putting related impact and context controls beside each other. */

.sweep-filter-panel {
    width: 100%;
    max-width: 64rem;
}

.sweep-panel-heading h2 {
    margin: 0 0 var(--space-1);
}

.sweep-panel-heading p {
    max-width: var(--prose-max);
    margin: 0;
}

.sweep-filter-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
}

.sweep-filter-group {
    min-width: 0;
    margin: 0;
    padding: var(--space-5) 0 0;
    border: 0;
    border-top: 1px solid var(--color-border);
}

.sweep-filter-group:first-child {
    padding-top: 0;
    border-top: 0;
}

.sweep-filter-group > legend {
    padding: 0;
    margin-bottom: var(--space-3);
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-semibold);
}

.sweep-filter-group > legend .muted {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-normal);
}

.sweep-filter-help {
    margin: calc(-1 * var(--space-2)) 0 var(--space-3);
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

.sweep-filter-fields {
    display: grid;
    gap: var(--space-3);
}

.sweep-filter-fields--rating {
    max-width: 18rem;
}

.sweep-filter-fields--impacts {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

.sweep-filter-fields--context {
    max-width: 42rem;
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.sweep-filter-field {
    min-width: 0;
    margin: 0;
}

.sweep-filter-field .select {
    width: 100%;
}

.sweep-filter-field--dropdown {
    align-items: flex-start;
}

.sweep-filter-dropdown {
    display: block;
    width: max-content;
    max-width: 100%;
}

.sweep-filter-dropdown-toggle {
    max-width: 100%;
    white-space: nowrap;
}

.sweep-filter-panel .sweep-filter-dropdown-menu {
    min-width: 10rem;
}

.sweep-filter-panel .sweep-filter-option {
    padding: var(--space-1) var(--space-2);
    font-size: var(--font-size-xs);
}

.sweep-filter-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border);
}

.sweep-filter-note {
    flex: 1 1 20rem;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

.sweep-decision-fieldset {
    margin: 0;
    padding: var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
}

.sweep-decision-fieldset > legend {
    padding: 0 var(--space-2);
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-semibold);
}

@media (max-width: 48rem) {
    .sweep-filter-fields--impacts,
    .sweep-filter-fields--context {
        grid-template-columns: 1fr;
    }

    .sweep-filter-dropdown,
    .sweep-filter-dropdown-toggle {
        width: 100%;
    }

    .sweep-filter-dropdown-toggle {
        justify-content: space-between;
    }
}

.filter-bar .input {
    width: 16rem;
}

.finding-filter-dropdown-toggle .icon,
.product-risk-filter-dropdown-toggle .icon,
.requirement-filter-dropdown-toggle .icon {
    width: 0.875rem;
    height: 0.875rem;
}

.finding-filter-dropdown-menu,
.product-risk-filter-dropdown-menu,
.requirement-filter-dropdown-menu {
    min-width: 12rem;
}

.finding-filter-dropdown-menu > li,
.product-risk-filter-dropdown-menu > li,
.requirement-filter-dropdown-menu > li {
    margin: 0;
}

.finding-filter-option,
.product-risk-filter-option,
.requirement-filter-option {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    width: 100%;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    color: var(--color-text);
    font-size: var(--font-size-sm);
    white-space: nowrap;
    cursor: pointer;
}

.finding-filter-option:hover,
.product-risk-filter-option:hover,
.requirement-filter-option:hover {
    background: var(--color-neutral-bg);
}

.finding-filter-option input,
.product-risk-filter-option input,
.requirement-filter-option input {
    margin: 0;
    accent-color: var(--color-primary);
}

.project-list-filter {
    flex-wrap: wrap;
    align-items: center;
    margin-bottom: var(--space-4);
}

/* The filter is the table card's header, as a register's toolbar is (see the
   toolbar join further down): the control and the rows it filters share one
   surface, and the table's own ring paints the rule between them. Over an
   empty state it stays a plain row. */
.project-list-filter:has(+ .data-table) {
    margin-bottom: 0;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    background: var(--color-surface);
    box-shadow: 0 0 0 1px var(--color-border), var(--shadow-sm);
}

.project-list-filter + .data-table,
.project-list-filter + .data-table thead tr:first-child > th {
    border-top-left-radius: 0;
    border-top-right-radius: 0;
}

/* `.row-link` is unbreakable for references; a project name is prose and
   must wrap, or one long name pushes the table past its container. */
.project-list-table .row-link {
    white-space: normal;
}

/* A project's description under its name — wraps, for the same reason. */
.project-list-summary {
    margin-top: 0.125rem;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

/* The project is the row's subject; its group is context and a filter
   shortcut, so it reads quieter than the name beside it. */
.project-list-table .project-list-group a {
    color: var(--color-text-muted);
    font-weight: var(--font-weight-normal);
}

.project-list-table .project-list-group a:hover {
    color: var(--color-primary);
}

.documents-count {
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    white-space: nowrap;
}

/* The findings count belongs directly beneath the heading. Keep the filter
   toggle close to that compact title block instead of adding the count's
   paragraph margin to the record-page rhythm. */
.finding-results .documents-count {
    margin: 0;
}

.finding-results > .finding-toolbar {
    margin-top: calc(-1 * var(--space-3));
}

.product-risk-results .documents-count {
    margin: 0;
}

.product-risk-results > .product-risk-toolbar {
    margin-top: calc(-1 * var(--space-3));
}

.requirement-results .documents-count {
    margin: 0;
}

.requirement-results > .requirement-toolbar {
    margin-top: calc(-1 * var(--space-3));
}

/* A register's toolbar right before its table is the table card's header,
   the counterpart of the pager footer below: the filter control and the rows
   it filters share one surface. Only when a table follows — over an empty
   state the toolbar stays a plain row, with no card to belong to. The
   table's own ring then paints the one rule between the two. */
.record-page > :is(.finding-toolbar, .product-risk-toolbar, .requirement-toolbar, .task-toolbar):has(+ .data-table),
.documents-toolbar:has(+ .documents-master-detail > .data-table:first-child) {
    margin-top: 0;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    background: var(--color-surface);
    box-shadow: 0 0 0 1px var(--color-border), var(--shadow-sm);
}

/* Close the gap each container puts between its children. */
.record-page > :is(.finding-toolbar, .product-risk-toolbar, .requirement-toolbar, .task-toolbar):has(+ .data-table) {
    margin-bottom: calc(-1 * var(--space-6));
}

.documents-toolbar:has(+ .documents-master-detail > .data-table:first-child) {
    margin-bottom: 0;
}

.record-page > :is(.finding-toolbar, .product-risk-toolbar, .requirement-toolbar, .task-toolbar) + .data-table,
.documents-toolbar + .documents-master-detail > .data-table:first-child {
    border-top-left-radius: 0;
    border-top-right-radius: 0;
}

.record-page > :is(.finding-toolbar, .product-risk-toolbar, .requirement-toolbar, .task-toolbar) + .data-table thead tr:first-child > th,
.documents-toolbar + .documents-master-detail > .data-table:first-child thead tr:first-child > th {
    border-radius: 0;
}

/* ===== Task register ====================================================
 * The toolbar wraps its controls, so the narrow layout puts them on as many
 * rows as they need rather than overflowing the page. */

.task-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
}

.task-filter-bar {
    flex-wrap: wrap;
    align-items: center;
    min-width: 0;
    max-width: 100%;
}

/* A select is as wide as its longest option, and a project option names its group too: at
   phone width that pushed Apply past the card's edge. Capped, the control truncates instead. */
.task-filter-bar .select {
    min-width: 0;
    max-width: 100%;
}

.task-filter-closed {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    font-size: var(--font-size-sm);
}

.task-results .documents-count {
    margin: 0;
}

.task-results > .task-toolbar {
    margin-top: calc(-1 * var(--space-3));
}

/* ===== Pagination ======================================================
 * One pager for every paged list — the documents table and the record
 * lists. Rows-per-page on the left, page links after it. Previously two
 * near-identical families (`.document-page-*` and `.record-page-*`) that
 * differed only in gap and top margin; the record list's tighter link gap
 * is the one kept, since the links are adjacent buttons. */

.pl-pagination {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
    margin-top: var(--space-4);
}

/* A pager right after its table is the table card's footer rather than a
   separate row below it: the two share one surface, so it reads as belonging
   to that table and not to whatever comes next on the page. The pager's own
   ring paints over the table's bottom edge, leaving one rule between them.
   A table in a horizontal scroller joins its pager the same way. */
.data-table:has(+ .pl-pagination),
.data-table-scroll:has(+ .pl-pagination) > .data-table {
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
}

.data-table + .pl-pagination,
.data-table-scroll + .pl-pagination,
.record-page > .data-table + .pl-pagination {
    margin-top: 0;
    padding: var(--space-2) var(--space-3);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    background: var(--color-surface);
    box-shadow: 0 0 0 1px var(--color-border), var(--shadow-sm);
}

/* `.record-page` and the documents grid space their children with a gap,
   which a margin of zero does not close. */
.record-page > .data-table:has(+ .pl-pagination) {
    margin-bottom: calc(-1 * var(--space-6));
}

.documents-master-detail > .data-table:has(+ .pl-pagination) {
    margin-bottom: calc(-1 * var(--space-4));
}

/* Table-sized text in the footer, not body-sized: it belongs to the table. */
.data-table + .pl-pagination,
.data-table-scroll + .pl-pagination {
    font-size: var(--font-size-sm);
}

.pl-pagination-size {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
}

.pl-pagination-links {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1);
}

.pl-pagination-ellipsis {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 2rem;
    padding: 0 var(--space-1);
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

/* Always stacked (table above, detail panel below) — not a two-column grid
   at any viewport width. An earlier version of this only stacked below
   1024px (originally 960px) and stayed side-by-side above it; changed to
   always-stacked instead of chasing further viewport-width edge cases
   (mobile browsers' "Request Desktop Site" mode substitutes its own wide
   virtual viewport — commonly ~980px — for the page's real one, which is
   what exposed the narrower breakpoint as insufficient in the first
   place). One layout, no breakpoint to get wrong. */
.documents-master-detail {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-4);
    align-items: start;
}

.data-table-row--selected {
    background: var(--color-primary-bg);
}

/* An edge mark on the selected row, so it still reads as the one the panel
   describes when the pointer is hovering a different row. */
.data-table-row--selected > td:first-child {
    box-shadow: inset 3px 0 0 var(--color-primary);
}

.data-table-actions {
    text-align: right;
    white-space: nowrap;
}

/* The one column that takes a stretched table's spare width. Without it the
   slack was shared out over every column, so a short pill sat in a wide empty
   cell. Given to the trailing actions column rather than to a text column:
   the content columns then sit packed from the left at their own widths, and
   the gap falls before the row menu at the right edge, instead of a wide title
   column pushing everything after it across the page. */
.data-table .data-table-grow {
    width: 100%;
}

/* A title column beside a growing one: given only its minimum, a table packs
   every column to its min-content width, which broke titles after each word.
   This floor keeps an ordinary title on one line; a longer one still wraps. */
.data-table .data-table-title {
    min-width: 18rem;
}

/* The heading row of one `<tbody>` group, such as the runs citing one
   document revision. A header cell, so it keeps the tinted background, but it
   names a thing rather than a column: sentence case at body size, so the pill
   and the link inside it read as they do anywhere else. */
.data-table th.data-table-group {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    text-transform: none;
    letter-spacing: normal;
}

.data-table th.data-table-group small {
    margin-left: var(--space-2);
    font-weight: normal;
}

/* A row's own menu trigger. Borderless, because an outlined button repeated
   on every row drew a column of boxes that read as content. On a device that
   can hover it also appears only for the row under the pointer, the row
   holding focus, or while its menu is open; a touch screen, which has no
   hover to reveal it, keeps it visible. Hidden by opacity rather than
   `visibility`, so it stays in the tab order and focusing it shows it. */
.data-table-actions .pl-btn--sm[data-dropdown-toggle] {
    border-color: transparent;
    background: transparent;
    color: var(--color-text-muted);
}

.data-table-actions .pl-btn--sm[data-dropdown-toggle]:hover,
.data-table-actions .pl-btn--sm[data-dropdown-toggle][aria-expanded="true"] {
    background: var(--color-neutral-bg);
    color: var(--color-text);
}

@media (hover: hover) {
    .data-table-actions .pl-btn--sm[data-dropdown-toggle] {
        opacity: 0;
    }

    .data-table tr:hover .data-table-actions .pl-btn--sm[data-dropdown-toggle],
    .data-table tr:focus-within .data-table-actions .pl-btn--sm[data-dropdown-toggle],
    .data-table-row--selected .data-table-actions .pl-btn--sm[data-dropdown-toggle],
    .data-table-actions .pl-btn--sm[data-dropdown-toggle][aria-expanded="true"] {
        opacity: 1;
    }
}

/* A count column in a `.data-table`. Right-aligned and tabular so a column of
   numbers can be compared by eye down its own edge, and shrink-to-fit so five
   of them beside a project name do not crowd out the name. */
.data-table-number {
    width: 1px;
    text-align: right;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* ===== Work count chip =================================================
 * A count of outstanding work sitting in a table cell, where `.stat-tile` is
 * the same count with room for a caption. It carries the tile's accent scale
 * — primary for work in hand, warning for work owed — because it is the same
 * measurement, and a reader who learns the colours on the header row must not
 * have to learn them again in the table.
 *
 * `--radius-md` and the tinted wash are the `.stat-tile-icon` chip's, not
 * `.status-pill`'s: a pill names one record's state, and these name a pile.
 *
 * Two classes in each colour selector on purpose. `.data-table a` sets every
 * table link to the row's text colour, and a single-class rule would lose to
 * it — which is how the chips first rendered as grey numbers in coloured
 * boxes. */
.work-count {
    display: inline-block;
    min-width: 2rem;
    padding: 0 var(--space-2);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    font-variant-numeric: tabular-nums;
    line-height: 1.5rem;
    text-align: center;
}

/* Work in hand. */
.work-count.work-count--primary {
    background: var(--color-primary-bg);
    border-color: var(--color-primary-border);
    color: var(--color-primary);
}

/* Something is owed. */
.work-count.work-count--warning {
    background: var(--color-warning-bg);
    border-color: var(--color-warning-border);
    color: var(--color-warning);
}

/* The row's total: filled rather than washed, because it is the number the
   table is sorted by and it has to hold its own beside five chips.
   Neutral rather than amber, though — it is a *sum*, and painting it in the
   owed accent made it read as a sixth kind of outstanding work while turning
   the column into a row of muddy olive blocks. Slate says "total" and leaves
   the amber to mean something. */
.work-count.work-count--total {
    background: var(--color-neutral-text);
    border-color: var(--color-neutral-text);
    color: var(--color-surface);
}

/* The wash deepens rather than the text changing colour, so a chip stays
   legible as itself while it is being pointed at. */
.work-count.work-count--primary:hover {
    background: var(--color-primary-bg-hover);
    border-color: var(--color-primary);
    color: var(--color-primary);
}

.work-count.work-count--warning:hover {
    background: var(--color-warning-border);
    border-color: var(--color-warning);
    color: var(--color-warning-hover);
}

/* Nothing owed. A dash rather than a `0`, so a quiet row is close to blank and
   the amount of colour in a row reads as how much is waiting in it before any
   number is read. The `0` is still in the markup for a screen reader, which
   has no ink to go on — see the template. */
.work-count-none {
    display: inline-block;
    min-width: 2rem;
    color: var(--color-border);
    text-align: center;
}

/* Available to a screen reader, absent from the page. The clip-path pair is
   the standard recipe: a zero-size element is skipped by some readers, so the
   text keeps its box and is cut out of the painted output instead. */
.pl-visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}


.document-detail {
    padding: var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    background: var(--color-surface);
    container-type: inline-size;
}

/* Beside the status card the panel has about half the page, and its five tabs
   ran past the edge — the last one cut mid-word behind a scroll nobody
   noticed. Measured on the panel, not the window, since the same window shows
   it at full width once the status card stacks. Without their icons, and with
   tighter padding, the labels fit; they still scroll if they ever do not. */
@container (max-width: 40rem) {
    .document-detail .tabs .icon {
        display: none;
    }

    .document-detail .tab {
        padding-inline: var(--space-2);
    }
}

/* Two side-by-side panels of equal width. `--collapsing` keeps the pair
   side-by-side only while there is room for it; the plain class stays two
   columns at every width. */
.grid-2 {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-4);
    align-items: start;
}

@media (max-width: 70rem) {
    .grid-2--collapsing {
        grid-template-columns: 1fr;
    }
}

.finding-summary-grid {
    grid-template-columns: minmax(0, 3fr) minmax(20rem, 2fr);
}

@media (max-width: 70rem) {
    .finding-summary-grid {
        grid-template-columns: 1fr;
    }
}

.finding-state-panel,
.finding-details-panel {
    min-width: 0;
}

/* The state panel's first row puts the six short finding facts beside the CVSS
   block. The prose row is explicitly placed below both columns so a description
   never gets trapped in the narrower facts column. */
.finding-state-panel__body {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    gap: var(--space-4);
    align-items: start;
}

.finding-state-panel__facts,
.finding-state-panel__description {
    min-width: 0;
}

.finding-state-panel__facts .facts--grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.finding-state-panel__description {
    grid-column: 1 / -1;
}

.finding-state-panel__body--without-cvss .finding-state-panel__facts {
    grid-column: 1 / -1;
}

@media (max-width: 48rem) {
    .finding-state-panel__body {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 36rem) {
    .finding-state-panel__facts .facts--grid {
        grid-template-columns: 1fr;
    }
}

/* The CVSS block is a section of the state panel, not a box pasted onto it: no
   background, just a hairline separating the two columns. */
.cvss-panel {
    min-width: 0;
    padding-left: var(--space-3);
    border-left: 1px solid var(--color-border-subtle);
    height: 100%;
}

@media (max-width: 48rem) {
    .cvss-panel {
        padding-top: var(--space-3);
        padding-left: 0;
        border-top: 1px solid var(--color-border-subtle);
        border-left: none;
    }
}

.cvss-panel__heading {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
}

.cvss-panel h5 {
    margin: 0;
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
}

.cvss-score {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    white-space: nowrap;
}

.cvss-score strong {
    font-size: var(--font-size-sm);
}

/* The canonical vector is reference material, not prose: monospaced, small, and
   on a solid surface so it stays legible instead of fading into the panel. One
   step below the smallest token so the long string fits the narrow column. */
.cvss-vector {
    display: block;
    margin-top: var(--space-2);
    padding: var(--space-1) var(--space-2);
    overflow-wrap: anywhere;
    background: var(--color-surface);
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-sm);
    color: var(--color-text-muted);
    font-family: var(--font-mono);
    font-size: var(--font-size-2xs);
}

/* One row per base metric, compact: the full metric name and its value read as a
   short column rather than a wall of cards. */
.cvss-metrics {
    width: 100%;
    margin: var(--space-2) 0 0;
    border-collapse: collapse;
    font-size: var(--font-size-xs);
}

.cvss-metrics th,
.cvss-metrics td {
    padding: var(--space-1) var(--space-2);
    border-top: 1px solid var(--color-border-subtle);
    text-align: left;
}

.cvss-metrics th {
    font-weight: var(--font-weight-normal);
    color: var(--color-text-muted);
}

.cvss-metrics td {
    color: var(--color-text);
}

.document-status {
    padding: var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    background: var(--color-surface);
}

.document-detail,
.document-status {
    min-width: 0;
}

.document-status .panel-header {
    margin-bottom: var(--space-2);
    justify-content: flex-start;
    gap: var(--space-2);
}

.document-status .panel-header h2,
.document-status-section h3 {
    margin: 0;
}

.document-status-section {
    padding: var(--space-3) 0;
}

.document-status-section + .document-status-section {
    border-top: 1px solid var(--color-border);
}

.document-status-section p {
    margin: var(--space-2) 0;
}

/* "What needs doing": each outstanding action as its pill plus the sentence
   explaining it. Unbulleted because the pill already marks each row. */
.document-action-items {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--space-3);
}

.document-action-items li {
    display: grid;
    gap: var(--space-1);
    justify-items: start;
}

.document-action-items p {
    margin: 0;
}

.stale-input-list .item-list-main {
    display: grid;
    gap: var(--space-1);
}

.input-review-disclosure {
    position: relative;
}

.input-review-disclosure summary {
    display: inline-flex;
    list-style: none;
}

.input-review-disclosure summary::-webkit-details-marker {
    display: none;
}

.input-review-form {
    width: min(24rem, 80vw);
    margin-top: var(--space-3);
    padding: var(--space-3);
    text-align: left;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-bg-subtle);
}

.document-input-review-toggle-cell {
    width: 2.5rem;
    padding-right: 0 !important;
}

.document-input-review-toggle {
    display: inline-grid;
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
    place-items: center;
    border: 0;
    border-radius: var(--radius-sm);
    color: var(--color-text-muted);
    background: transparent;
    cursor: pointer;
}

.document-input-review-toggle:hover,
.document-input-review-toggle:focus-visible {
    color: var(--color-text);
    background: var(--color-bg-subtle);
}

.document-input-review-toggle-icon {
    transition: transform 120ms ease;
}

.document-input-review-toggle[aria-expanded="true"] .document-input-review-toggle-icon {
    transform: rotate(90deg);
}

.document-input-review-detail td {
    background: var(--color-bg-subtle);
}

.secondary-link {
    display: inline-block;
    margin-top: var(--space-2);
    font-size: var(--font-size-sm);
}

/* ===== Project dependency graph ======================================== */

.dependency-scope-form {
    display: flex;
    align-items: flex-end;
    gap: var(--space-2);
    margin-bottom: var(--space-4);
    padding: var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    background: var(--color-bg-subtle);
}

.dependency-scope-form .field {
    min-width: min(22rem, 100%);
    margin: 0;
}

.dependency-graph-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-4);
    margin-bottom: var(--space-4);
}

.dependency-graph-header h1,
.dependency-graph-header p {
    margin: 0;
}

.dependency-graph-header p {
    margin-top: var(--space-1);
    color: var(--color-text-muted);
}

.dependency-graph-counts,
.dependency-graph-legend {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

.dependency-graph-counts span {
    white-space: nowrap;
}

.dependency-graph-legend {
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-border);
    border-bottom: 0;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    background: var(--color-bg-subtle);
}

.dependency-graph-legend span {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
}

.dependency-direction {
    margin-left: auto;
}

.dependency-edge {
    display: inline-block;
    width: 1.75rem;
    border-top: 2px solid var(--color-primary);
}

.dependency-edge--pending {
    border-top-style: dashed;
    border-top-color: var(--color-text-muted);
}

.dependency-edge--attention {
    border-top-color: var(--color-warning);
}

.dependency-graph-tools {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-border);
    border-top: 0;
    border-bottom: 0;
    background: var(--color-bg-subtle);
}

.dependency-graph-tools-label {
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
}

.dependency-graph-zoom-controls {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
}

.dependency-graph-zoom-button {
    min-width: 2rem;
    padding-right: var(--space-1);
    padding-left: var(--space-1);
    font-size: var(--font-size-lg);
}

.dependency-graph-zoom-level {
    min-width: 3.5rem;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    text-align: center;
}

.dependency-graph-canvas:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: -2px;
}


.dependency-graph-canvas {
    min-height: 24rem;
    padding: var(--space-4);
    overflow: auto;
    border: 1px solid var(--color-border);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    background-color: var(--color-surface);
    background-image: radial-gradient(var(--color-neutral-border) 0.75px, transparent 0.75px);
    background-size: var(--space-4) var(--space-4);
}

.dependency-graph-canvas svg {
    display: block;
    height: auto;
    min-height: 20rem;
    max-width: none;
    margin: auto;
}

.dependency-graph-canvas.is-zoomable svg {
    min-height: 0;
}

.dependency-graph-empty-note {
    padding: var(--space-3);
    color: var(--color-text-muted);
    text-align: center;
    border: 1px solid var(--color-border);
    border-top: 0;
}

.dependency-relationships {
    margin-top: var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    background: var(--color-surface);
}

.dependency-relationships summary {
    padding: var(--space-3) var(--space-4);
    font-weight: var(--font-weight-medium);
    cursor: pointer;
}

.dependency-relationships .data-table {
    border-top: 1px solid var(--color-border);
}

@media (max-width: 48rem) {
    .dependency-scope-form {
        align-items: stretch;
        flex-direction: column;
    }

    .dependency-graph-header {
        display: block;
    }

    .dependency-graph-counts {
        margin-top: var(--space-3);
    }

    .dependency-graph-tools {
        justify-content: space-between;
    }


    .dependency-direction {
        width: 100%;
        margin-left: 0;
    }
}

.document-detail-title {
    font-size: var(--font-size-lg);
    /* No bottom margin: this sits in a centre-aligned flex row beside the reference and the
       lifecycle pill, and flex centres the margin box — a bottom margin here lifts the title
       off the baseline of everything next to it. Spacing below the row is the header's. */
    margin: 0;
}

.document-detail-title-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
}
.record-selector-menu {
    width: min(28rem, calc(100vw - var(--space-8)));
    max-width: calc(100vw - var(--space-8));
    max-height: min(24rem, calc(100vh - 6rem));
    overflow-y: auto;
}

.record-selector-option {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-1);
    white-space: normal;
}

.record-selector-option__meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-1);
}

.record-selector-option__current {
    color: var(--color-primary);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
}

.record-selector-option__detail {
    max-width: 100%;
    overflow-wrap: anywhere;
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
}

.finding-reference-header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-3);
}
.finding-reference-header--stacked {
    flex-direction: column;
    align-items: flex-start;
}
.record-revision-controls {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.finding-reference-header h1 {
    min-width: 0;
    margin: 0;
    overflow-wrap: anywhere;
}

.finding-assessment-header {
    justify-content: flex-start;
    flex-wrap: wrap;
}

.finding-assessment-header h2 .status-pill {
    vertical-align: middle;
}

.record-follow-up .panel-header h3 {
    margin: 0;
}

/* A recorded risk judgement or its follow-up record shown as distinct acts. Each section owns
 * more vertical room than a lone panel child and restores the spacing its nested contents lose
 * by sitting one level below `.panel > *`. */
.record-detail__section {
    padding-block: var(--space-6);
}

.record-detail__section > * + * {
    margin-top: var(--space-4);
}

/* Assessment facts and follow-up are separate cards inside one htmx swap target. The gap must
 * therefore live on that wrapper rather than on `.record-page`, which sees only the target. */
#risk-assessment-summary {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
}

/* `#consideration-panel` is swapped whole when its evidence selector changes. Remove the panel's
 * padding from that transport wrapper and put it on the two semantic sections inside instead. */
.record-follow-up > .record-follow-up__body {
    padding: 0;
}

.record-follow-up__body > .record-detail__section {
    padding: var(--space-6) var(--space-4);
}

.record-follow-up__body > .record-detail__section + .record-detail__section {
    border-top: 1px solid var(--color-border);
}

/* Work gets a quiet picked-out surface; recorded evidence stays on the card's white surface.
 * The slim warning rule carries the distinction without turning the section into an alert. */
.record-follow-up__work {
    background: var(--color-bg-subtle);
    box-shadow: inset 3px 0 var(--color-warning-border);
}

.record-detail__eyebrow {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-caps);
    text-transform: uppercase;
}

.record-detail__eyebrow + .panel-header {
    margin-top: var(--space-1);
}

.record-follow-up .panel-header + * {
    margin-top: 0;
}

/* The ledger is audit history one click away, not a card of its own. Keep the marker row as
   quiet as the section headings it sits among. */
.record-detail__ledger > summary {
    cursor: pointer;
    width: max-content;
    font-weight: var(--font-weight-medium);
}

.finding-assessment-selector__empty {
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
}

/* Match the adjacent small selector, whose status pill makes its rendered height 2rem. Marking a
   risk not applicable ends its active workflow, so leave a stronger gap between that caution and
   the routine revision action. */
.new-risk-revision-button,
.new-risk-not-applicable-button,
.record-selector-action-button {
    min-height: 2rem;
    white-space: nowrap;
}

.new-risk-not-applicable-button {
    margin-inline-start: var(--space-2);
}

.record-dialog {
    width: min(42rem, calc(100vw - var(--space-6)));
    max-height: calc(100vh - var(--space-6));
    padding: 0;
    overflow: auto;
    color: var(--color-text);
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
}

.record-dialog--wide {
    width: min(64rem, calc(100vw - var(--space-6)));
}

/* Width for a dialog holding a two-column form. `--wide` is enough for two
   columns of fields; `--split` adds the room a findings table needs beside
   them. Below `.pl-form-split`'s breakpoint both are one column anyway. */
.record-dialog--split {
    width: min(86rem, calc(100vw - var(--space-6)));
}

/* A fixed header, a scrolling body and an action bar that does not move. The
   bar has to sit outside the scroll area — a dialog is already capped at the
   viewport height, so a footer inside the body is a submit button the reader
   has to go looking for.

   Its own modifier rather than part of a width: the finding revision dialog
   wants this shell at `--wide`, and a dialog wide enough for two columns is
   not the same question as a dialog too tall for the screen. */
/* `[open]` is load-bearing: author `display` beats the UA's
   `dialog:not([open]) { display: none }` whatever the specificity, so an
   unqualified `display: flex` here renders every closed dialog inline. */
.record-dialog--framed[open] {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.record-dialog--framed .record-dialog__form {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-height: 0;
}

.record-dialog--framed .record-dialog__body {
    min-height: 0;
    overflow: auto;
}

.record-dialog::backdrop {
    background: rgb(15 23 42 / 55%);
    backdrop-filter: blur(2px);
}

.record-dialog__header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-5) var(--space-5) var(--space-3);
    border-bottom: 1px solid var(--color-border);
}

.record-dialog__header h2 {
    margin: 0 0 var(--space-1);
}

.record-dialog__header .muted {
    margin: 0;
}

.record-dialog__eyebrow {
    margin: 0 0 var(--space-1);
    color: var(--color-primary);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.record-dialog__close {
    flex: 0 0 auto;
    min-width: 2rem;
    padding-inline: var(--space-2);
    font-size: var(--font-size-lg);
    line-height: 1;
}

.record-dialog__body {
    display: grid;
    /* `minmax(0, 1fr)`, not the implicit `auto` column: a grid track's automatic minimum is its
       content's min-content size, and a `<select>` never shrinks below its longest option — the
       create-task dialog's project control (group · project) forced the body to 432px inside a
       390px dialog, scrolling a field past the dialog's edge. With a zero minimum the column
       tracks the dialog and the control truncates like every other field. */
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5);
}

.record-dialog__actions {
    display: flex;
    justify-content: flex-end;
    align-items: flex-end;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-5) var(--space-5);
    border-top: 1px solid var(--color-border);
}

@media (max-width: 48rem) {
    .record-selector {
        flex-basis: 100%;
        width: 100%;
    }

    .record-selector > .pl-btn {
        width: 100%;
        justify-content: flex-start;
    }

    .record-selector-menu {
        width: 100%;
        max-width: none;
    }
}


/* Three rows: identity and the close control, the title, then the actions.
   With the title and the actions side by side, a long title squeezed the
   buttons into a stack and the header grew taller than the content under it. */
.document-detail-header {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin-bottom: var(--space-3);
}

.document-detail-meta {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
}

.document-detail-reference {
    padding: 0.125rem var(--space-2);
    border-radius: var(--radius-sm);
    background: var(--color-neutral-bg);
    color: var(--color-neutral-text);
    font-family: var(--font-mono);
    font-size: var(--font-size-sm);
    white-space: nowrap;
}

.document-detail-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    margin-left: auto;
    border-radius: var(--radius-md);
    color: var(--color-text-muted);
}

.document-detail-close:hover {
    background: var(--color-neutral-bg);
    color: var(--color-text);
    text-decoration: none;
}

.document-detail-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
}

/* The forms in the action row hold one button each; their default block
   margins would set that button lower than the split button beside it. */
.document-detail-actions form {
    margin: 0;
}

.document-detail-pills {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-3);
}

.document-status .document-detail-pills {
    margin-bottom: 0;
}

/* ===== Facts: label/value pairs ========================================
 * One `<dl>` vocabulary for every "here are this thing's attributes" block —
 * a document's detail panel, a record's revision, a profile, an input review.
 * Each `<div>` inside is one label/value pair. The label styling is the
 * constant; the modifiers only change how the pairs are arranged.
 *
 * Default: value under its label, pairs stacked vertically. */

.facts {
    margin: 0;
}

.facts > div + div {
    margin-top: var(--space-2);
}

/* Sentence case and muted, not uppercase and tracked: a record page carries a
   dozen of these, and in capitals every label shouted as loud as the heading
   above it. The value is what the reader came for; the label only names it. */
.facts dt {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-normal);
    color: var(--color-text-muted);
}

.facts dd {
    margin: 0;
}

/* Stacked only — `--inline` puts the label in a column beside the value,
   where a bottom margin on the label would push it off its own baseline. */
.facts:not(.facts--inline) dt {
    margin-bottom: var(--space-1);
}

/* A value that is several short tokens rather than a phrase — identifiers,
   advisory links. Wraps inline instead of stacking one per line: these are
   items of one fact, not a list of facts, and a column of 12-character
   identifiers spends a whole row on each while leaving the rest empty. */
.facts-list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Trailing space when the block is followed by more content in the same panel
   rather than ending it. */
.facts--spaced {
    margin-bottom: var(--space-4);
}

/* Label and value on one line, pairs hairline-separated — a revision or an
   assessment reads as a few compact rows rather than a wall. */
.facts--inline > div {
    display: flex;
    gap: var(--space-2);
    padding: var(--space-1) 0;
}

.facts--inline > div + div {
    margin-top: 0;
    border-top: 1px solid var(--color-border-subtle);
}

/* Fixed rather than content-sized so labels align into a column the eye can
   run down. 10rem, not the 12rem this started at: the widest label the app
   actually renders in this position is "Exploitation status" at 136px (8.5rem),
   so 12rem spent 56px of every row on nothing. Long prose labels are not a
   constraint here — those are `.facts-block`, which stacks instead. */
.facts--inline dt {
    flex: 0 0 10rem;
}

/* Prose the author wrote gets its own line instead of the fixed label column. */
.facts--inline .facts-block {
    display: block;
}

.facts--inline .facts-block dd {
    margin-top: var(--space-1);
    white-space: pre-wrap;
}

/* `pre-wrap` above is right for prose the author typed, and wrong for a value
   built from markup: the template's own indentation and line breaks then paint
   as literal leading spaces and blank lines, which is what turned a two-item
   identifier list into a half-screen of whitespace.
   Placed immediately after the rule it overrides and at the same specificity —
   at any earlier position it loses on source order and silently does nothing. */
.facts--inline .facts-block--rich dd {
    white-space: normal;
}

/* Short facts flow into as many columns as fit, prose spans the row.
 *
 * `--inline` gives every fact its own full-width row, which is right in a
 * narrow panel and wasteful in a wide one: half a dozen one-word values then
 * make a card taller than the screen while most of its width stays empty.
 * `auto-fit` means the same markup is one column in a half-width panel and
 * three or four across a full-width card, with no breakpoint to maintain. */
.facts--grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
    gap: 0 var(--space-6);
    align-items: start;
}

/* Spacing separates the cells, not rules: a border on each cell of a grid
   paints a row of disconnected line segments rather than one separator, which
   reads as a rendering fault. */
.facts--grid > div {
    padding: var(--space-2) 0;
}

/* Label above value here rather than beside it: in a column a fixed label
   gutter would leave the value squeezed into what little is left. */
.facts--grid dd {
    margin-top: var(--space-1);
}

/* Risk judgements and responses read as ordered label/value rows. Keep one
   fixed label track so every value starts at the same column. */
.facts--ordered {
    --facts-label-column: 10rem;
}

.facts--ordered > .facts__row {
    display: grid;
    grid-template-columns: var(--facts-label-column) minmax(0, 1fr);
    gap: var(--space-3);
    align-items: center;
    padding: var(--space-1) 0;
}

.facts--ordered > .facts__row + .facts__row {
    margin-top: 0;
}

.facts--ordered dt {
    margin-bottom: 0;
}

.facts--ordered dd {
    min-width: 0;
    margin-top: 0;
}

/* A prose block ends on a ragged short line, so the 8px that separates two
   one-word rows leaves the last line of the consequence sitting almost against
   the "Risk justification" label under it, and the label — smaller and fainter
   than the paragraph — groups with the text above it instead of the text it
   introduces. The space over a prose label has to be clearly larger than the
   space under it, and the label has to carry heading weight once it heads a
   paragraph rather than naming a single value. */
.facts--ordered > .facts-block {
    margin-top: var(--space-6);
}

.facts--ordered > .facts-block > dt {
    color: var(--color-text);
    margin-bottom: var(--space-1);
}

.facts--ordered > .facts-block dd {
    white-space: pre-wrap;
    max-width: var(--prose-max);
}

.facts--ordered > .facts-block dd.record-markdown {
    width: 100%;
    max-width: none;
    white-space: normal;
}

.facts__qualifier {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
}

/* Prose gets two columns rather than the whole row: one column is too narrow
   to read, the full width of a wide card is past the point where a line is
   easy to track, and two lets a card with several justifications pair them up
   instead of stacking every one. Clamps to the full row when the grid has
   fewer than two columns, so a narrow panel still works. */
.facts--grid > .facts-block {
    grid-column: span 2;
}
.facts--grid > .facts-block--markdown {
    grid-column: 1 / -1;
}

/* Plain prose may contain intentional line breaks. Template whitespace around the value is
   trimmed in the facts macro so pre-wrap only preserves whitespace the author entered. */
.facts--grid > .facts-block dd {
    white-space: pre-wrap;
    max-width: var(--prose-max);
}

.facts-block__detail {
    margin-top: var(--space-2);
}

/* Finding descriptions are rendered from sanitized Markdown. Keep its block structure, leave
 * large descriptions closed until a reader asks for them, and let both the excerpt and the full
 * detail use the entire panel rather than the generic prose measure. */
.facts--grid > .facts-block dd.record-markdown {
    width: 100%;
    max-width: none;
    white-space: normal;
}
.record-markdown__excerpt {
    margin: 0;
    color: var(--color-text-muted);
}
.record-markdown__excerpt + .record-markdown__disclosure {
    margin-top: var(--space-2);
}

.record-markdown__disclosure {
    width: 100%;
}

.record-markdown__disclosure > summary {
    cursor: pointer;
    width: max-content;
}

.record-markdown__content {
    margin-top: var(--space-3);
}

.record-markdown__content > :first-child {
    margin-top: 0;
}

.record-markdown__content > :last-child {
    margin-bottom: 0;
}

.record-markdown__content h1,
.record-markdown__content h2,
.record-markdown__content h3,
.record-markdown__content h4,
.record-markdown__content h5,
.record-markdown__content h6 {
    margin-block: var(--space-4) var(--space-2);
}

.record-markdown__content li + li {
    margin-top: var(--space-1);
}

.record-markdown__content img {
    max-width: 100%;
    height: auto;
}

.facts--grid:has(> div.facts-block:first-child:nth-last-child(2) + div.facts-block) > div.facts-block:first-child {
    border-right: 1px solid var(--color-border-subtle);
}

/* Three across, with the last pair spanning the full width — for a wide,
   shallow block inside a table cell. */
.facts--columns {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-3);
    margin: var(--space-1) 0;
}

.facts--columns > div + div {
    margin-top: 0;
}

.facts--columns > div:last-child {
    grid-column: 1 / -1;
}

.facts--columns dd {
    margin-top: var(--space-1);
}

@media (max-width: 640px) {
    .facts--columns {
        grid-template-columns: 1fr;
    }

    .facts--columns > div:last-child {
        grid-column: auto;
    }
    .facts--ordered {
        --facts-label-column: 8rem;
    }
}


/* ===== Record traceability ============================================= */
/*
 * The finding/product-risk links a record cites. Rendered as a list of inline
 * chips in a table cell, so they need their own separation: back to back with
 * no rule of their own two references read as one string.
 */
.traceability-link {
    display: inline-block;
    margin-right: var(--space-2);
    white-space: nowrap;
}

.traceability-link:last-child {
    margin-right: 0;
}

.traceability-fieldset {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    margin: 0;
}

.traceability-fieldset > legend {
    padding: 0 var(--space-2);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
}

/* What carrying a row will do to the end it was not asked about. The words
   carry the warning; the colour only echoes them. */
.carry-note {
    display: inline-block;
    margin-top: var(--space-1);
    color: var(--color-warning);
}

/* The search is the first thing in the picker and the rows it produces sit
   under it, so the control and the list it feeds read in that order. The row
   wrapper carries the combobox's own spacing, so it adds none of its own. */
.product-risk-finding-picker .field-row {
    margin-top: 0;
}

.product-risk-finding-picker > .data-table-scroll {
    margin-top: var(--space-3);
}

/* Nothing considered yet is not a table with no rows, it is no table. Hidden
   here rather than in the template because the tbody has to stay in the DOM
   for the picker script to append the first chosen row to; `:has()` puts the
   table back on screen the moment it does. */
.product-risk-finding-picker > .data-table-scroll:not(:has(tbody tr)) {
    display: none;
}

/* The requirements a treatment implements. Existing links stay visible while the bounded remote
 * combobox searches current requirement revisions for another one. */
.treatment-requirement-picker__list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.treatment-requirement-picker__option {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    padding: var(--space-2) 0;
    font-size: var(--font-size-sm);
    white-space: normal;
    cursor: pointer;
}

.treatment-requirement-picker__list > li + li {
    border-top: 1px solid var(--color-border-subtle);
}

.treatment-requirement-picker__option input {
    flex: 0 0 auto;
    margin-top: var(--space-1);
    accent-color: var(--color-primary);
}

.treatment-requirement-picker__reference {
    margin-right: var(--space-2);
    font-weight: var(--font-weight-medium);
    white-space: nowrap;
}

.treatment-requirement-picker__title {
    margin-right: var(--space-2);
}

/* A picked requirement on the new test case form: its text, and the button that drops it. Wraps at
 * phone width so a long title never pushes the button off screen. */
.test-case-requirement-row,
.task-link-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2) var(--space-3);
    padding: var(--space-2) 0;
    font-size: var(--font-size-sm);
}

.test-case-requirement-row__label,
.task-link-row__label {
    flex: 1 1 16rem;
    min-width: 0;
    overflow-wrap: anywhere;
}

.test-case-requirement-row__problem {
    margin-left: var(--space-2);
    color: var(--color-danger);
}

.treatment-requirement-picker__add {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--space-2);
    margin: 0;
}

/* The task link editor's kind control stands beside its search rather than above it: the two
   are one question ("which object?"), and stacked they read as two separate fields. */
.task-link-editor .treatment-requirement-picker__add > .select {
    width: auto;
}

.task-link-editor .record-combobox__search {
    flex: 1 1 16rem;
}

.record-combobox {
    margin-bottom: 0;
}

.record-combobox__search {
    position: relative;
    width: min(32rem, 100%);
}

.record-combobox__search > .input {
    width: 100%;
}

/* The dropdown hangs from the input it matches in width. A generous shadow and a hairline
 * border lift it off the form behind it; the panel padding keeps option hover blocks inset
 * rather than touching the edge. */
.record-combobox__results {
    position: absolute;
    z-index: 20;
    top: calc(100% + var(--space-1));
    width: 100%;
    max-height: 20rem;
    overflow-y: auto;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-surface);
    box-shadow: var(--shadow-md);
    padding: var(--space-1);
}

/* Inside a framed dialog the body scrolls, so an overlaid list is clipped at its edge. A dialog
 * whose one job is picking reserves room for the list instead. Not by putting the list in the flow:
 * it closes on blur, so the dialog would shrink under the pointer between mousedown and click and
 * the click would miss the button it was aimed at. */
.record-dialog--picker .record-dialog__body {
    min-height: 26rem;
}

.record-combobox__results:empty {
    display: none;
}

/* Names what the list is showing ("Recently updated" versus search hits). A caption, not an
 * option: it never takes focus or a click. */
.record-combobox__caption {
    padding: var(--space-2) var(--space-3) var(--space-1);
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    letter-spacing: var(--letter-spacing-caps);
    text-transform: uppercase;
}

.record-combobox__options {
    margin: 0;
    padding: 0;
    list-style: none;
}

.record-combobox__option {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    width: 100%;
    padding: var(--space-2) var(--space-3);
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-text);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.record-combobox__option:hover,
.record-combobox__option:focus,
.record-combobox__option[aria-selected="true"] {
    background: var(--color-primary-bg);
    outline: none;
}

.record-combobox__option:focus-visible {
    box-shadow: inset 0 0 0 2px var(--color-primary-border);
}

.record-combobox__option strong {
    color: var(--color-primary);
}

.record-combobox__option .muted {
    /* One line per option: long prose is capped server-side too; this keeps the shape
     * when the cap is exactly met and stops mid-word wrapping noise. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--font-size-sm);
}

.record-combobox__message {
    margin: 0;
    padding: var(--space-2) var(--space-3) var(--space-3);
}

/* A label that names a group of controls rather than one input, so it is not a <label>. */
.field-label {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
}

/* References inside a table cell — the requirements one treatment cites. */
.record-link-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.record-link-list > li + li {
    margin-top: var(--space-1);
}

/* One checkable option per line, so a long list stays scannable. */
.checkbox-row {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.checkbox-row + .checkbox-row {
    margin-top: var(--space-1);
}

/* The row a record's detail page is currently showing — the selected revision or assessment. */
.data-table tr.is-selected > td {
    background: var(--color-surface-subtle);
    font-weight: var(--font-weight-semibold);
}

/* A form kept out of the way until asked for. The summary is a full-width card header rather than
   a button floating inside the card, which keeps the trigger, body, and submit action visually
   related on both assessment forms. Keep closed cards clipped for their rounded surface, but let
   an open card's combobox escape its body: the absolute result list otherwise gets clipped after
   the first option near the bottom of the form. */
.record-form {
    overflow: hidden;
}

.record-form[open] {
    overflow: visible;
}

.record-form > summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border: 0;
    border-radius: 0;
    list-style: none;
    cursor: pointer;
    text-align: left;
}

.record-form > summary::after {
    content: "+";
    flex: 0 0 auto;
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-normal);
    line-height: 1;
}

.record-form[open] > summary {
    margin-bottom: 0;
}

.record-form[open] > summary::after {
    content: "−";
}

.record-form > summary.pl-btn--outline {
    background: var(--color-bg-subtle);
    color: var(--color-text);
}

.record-form > summary.pl-btn--outline:hover {
    background: var(--color-neutral-bg);
}

.record-form > summary::-webkit-details-marker {
    display: none;
}

/* Whatever the disclosure reveals, not specifically a `<form>`: a panel may
   offer more than one way to create the same thing, in which case the body is
   a grid of forms rather than one. */
.record-form > *:not(summary) {
    padding: var(--space-5) var(--space-4);
}

.record-form .button-row {
    margin-top: var(--space-4);
}

/* Assessment forms are a judgement, not a data dump. Keep the decision, its
 * supporting fields, and the final action visually distinct so the assessor
 * can answer one question at a time. */
.assessment-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
}

.assessment-form__intro {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-4);
}

.assessment-form__intro p {
    margin: 0;
}

.assessment-form__required-note {
    flex: 0 0 auto;
    /* `space-between` leaves a lone note on the left; where the intro is the note
       and nothing else, it still belongs opposite the fields it describes. */
    margin-inline-start: auto;
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
    white-space: nowrap;
}

.assessment-section {
    min-width: 0;
    margin: 0;
    padding: var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
}

.assessment-section > legend {
    padding: 0 var(--space-2);
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-semibold);
}

.assessment-section__hint {
    margin: 0 0 var(--space-4);
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

.assessment-section--decision {
    border-color: var(--color-primary-border);
    background: var(--color-bg-subtle);
}

.assessment-section--conditional {
    border-color: var(--color-primary-border);
    background: var(--color-primary-bg);
}

.assessment-section--reassessment {
    background: var(--color-bg-subtle);
}

.assessment-section--reassessment > summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    cursor: pointer;
    list-style: none;
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-semibold);
}

.assessment-section--reassessment > summary::after {
    content: "+";
    color: var(--color-text-muted);
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-normal);
    line-height: 1;
}

.assessment-section--reassessment[open] > summary {
    margin-bottom: var(--space-3);
}

.assessment-section--reassessment[open] > summary::after {
    content: "−";
}

.assessment-section--reassessment > summary::-webkit-details-marker {
    display: none;
}

.assessment-section .field:last-child {
    margin-bottom: 0;
}

.assessment-section .select,
.assessment-section .input,
.assessment-section .textarea {
    max-width: 100%;
}

.assessment-form__field-group {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

/* Two fields that belong to one question sit beside each other. `auto-fit`
   rather than a fixed count because the same grid renders inside a column of a
   split form and across the full width of a dialog; below the minimum it falls
   back to one column on its own. */
.assessment-field-grid {
    display: grid;
    gap: var(--space-3);
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

/* Likelihood, impact, and the risk they calculate to, in that reading order.
   A lower minimum than the grid above because these are two selects and a
   short read-only value, not paragraphs. */
.assessment-field-grid--risk-judgement {
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
}

/* Textareas start at the height of their own content rather than reserving
   five blank rows: the assessment form carries six of them, and the reserved
   space was what pushed the record button off a 1200px screen. `rows` stays on
   the element as the floor for browsers without `field-sizing`. */
.assessment-section textarea.input {
    field-sizing: content;
    min-height: 4.5rem;
    max-height: 16rem;
}


.assessment-field-grid .field {
    min-width: 0;
}

.assessment-field-grid .field:last-child {
    margin-bottom: var(--space-3);
}

.assessment-form__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border);
}

.assessment-form__actions-copy {
    flex: 1 1 16rem;
    min-width: 0;
}

.assessment-form__actions-copy .form-error {
    margin-top: 0;
}

.assessment-form__action-hint {
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

.assessment-form__actions > .button-row {
    flex: 0 1 auto;
}

.assessment-optional {
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-normal);
}

.assessment-treatment__intro {
    margin: 0;
}

.assessment-form__sub-actions {
    align-items: flex-start;
}

.assessment-form__sub-actions .form-error {
    flex: 1 1 100%;
    margin: 0 0 var(--space-2);
}

@media (max-width: 48rem) {
    .assessment-form__intro {
        flex-direction: column;
        gap: var(--space-2);
    }
}

/* ===== The split record form ============================================
 * A record form with more fields than a screen is tall reads as two columns:
 * what has to be filled in to submit on the left, what is optional or
 * supporting on the right. The create page and the assessment dialog use the
 * same shell so a reader who learns one has learned the other; only which
 * section goes in which column differs.
 *
 * Below the breakpoint it is one column in source order, which is why the
 * required column is written first. */
.pl-form-split > [data-col] {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    min-width: 0;
}

@media (min-width: 72rem) {
    .pl-form-split {
        display: grid;
        grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
        align-items: start;
        column-gap: var(--space-5);
        row-gap: var(--space-4);
    }

    .pl-form-split > [data-span="full"] {
        grid-column: 1 / -1;
    }
}

/* ===== The record form's action bar =====================================
 * One bar, one submit button, and the choices that used to be encoded in
 * which of four buttons you pressed. Spreading them over the sections they
 * belonged to meant scrolling to the action you wanted before you could take
 * it, and left a two-column layout with two action rows at different heights. */
.pl-actionbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-3) var(--space-4);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
}

/* Pinned to the bottom of the viewport on a page that scrolls, so the submit
   is reachable however far the form has grown. */
.pl-actionbar--page {
    position: sticky;
    bottom: 0;
    z-index: 5;
    box-shadow: 0 -2px 10px rgb(15 23 42 / 8%);
}

/* In a dialog the bar sits outside the scrolling body instead — see
   `.record-dialog__form` below. */
.pl-actionbar--dialog {
    padding-inline: var(--space-5);
    border: 0;
    border-top: 1px solid var(--color-border);
    border-radius: 0;
}

.pl-actionbar .form-error {
    flex: 1 1 100%;
    margin: 0 0 var(--space-2);
}

.pl-modes {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
}

.pl-modes > .pl-modes__label {
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
}

.pl-modes .muted,
.pl-actionbar > .muted {
    margin: 0;
    font-size: var(--font-size-sm);
}

/* Radios drawn as one segmented control. Radios rather than buttons because
   the choice has to survive being read back: which mode is selected is state
   the form submits, not an action. */
.pl-seg {
    display: inline-flex;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-md);
    overflow: hidden;
}

.pl-seg__option {
    padding: var(--space-2) var(--space-3);
    border-right: 1px solid var(--color-border-strong);
    background: var(--color-surface);
    font-size: var(--font-size-sm);
    cursor: pointer;
}

.pl-seg__option:last-child {
    border-right: 0;
}

.pl-seg__option:has(input:checked) {
    background: var(--color-primary);
    color: var(--color-text-on-primary);
    font-weight: var(--font-weight-medium);
}

/* Focus has to be visible on the label, because the control it belongs to is
   the visually hidden radio inside it. */
.pl-seg__option:has(input:focus-visible) {
    outline: 2px solid var(--color-focus-ring);
    outline-offset: -2px;
}

.pl-seg__option > input {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
}

/* The calculated risk beside the numbers it comes from: read-only, so it should not look like
   another field to fill in. Boxed, because it sits in the field grid as a third
   column and would otherwise read as an empty field. */
.risk-preview {
    align-self: end;
    margin: 0 0 var(--space-3) 0;
    padding: var(--space-2) var(--space-3);
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-md);
    color: var(--color-text-muted);
}

.risk-preview output {
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
}

/* Record state (§4.3's treatment applied to findings and product risks): a record is raised,
   judged, or waiting to be judged again — the same three states every record area shows. */
/* Amber, like "requires review": an unassessed record is outstanding work, not
   a neutral resting state, and the overview tile counts it as such. */
.status-pill--record-pending {
    background: var(--color-warning-bg);
    border-color: var(--color-warning-border);
    color: var(--color-warning);
}

.status-pill--record-assessed {
    background: var(--color-success-bg);
    border-color: var(--color-success-border);
    color: var(--color-success);
}

.status-pill--reassessment-required {
    background: var(--color-warning-bg);
    border-color: var(--color-warning-border);
    color: var(--color-warning);
}

/* Operational task states. Open is the normal state of the backlog — neutral, like a
   requirement's Open — and the two closed states rest; only overdue, which asks for action,
   keeps a filled pill. */
.status-pill--task-open,
.status-pill--task-cancelled {
    background: var(--color-neutral-bg);
    border-color: var(--color-border);
    color: var(--color-neutral-text);
}

.status-pill--task-done {
    background: var(--color-success-bg);
    border-color: var(--color-success-border);
    color: var(--color-success);
}

.status-pill--task-overdue {
    background: var(--color-danger-bg);
    border-color: var(--color-danger-border);
    color: var(--color-danger);
}

/* Risk levels and finding severities use the same accessible visual ramp, but remain separate
   classes because they are different domain judgements. */
/* A likelihood or an impact. Same four steps as the risk ramp, because both are
   1-5 ordinal scales and a reader comparing factors wants the same ladder — but
   named for the scale, since a likelihood is not a risk level. */
/* Treatment priority joins the same ramp rather than getting a fifth palette, but only High and
   Urgent take colour from it. Low and Normal share the neutral step deliberately: neither is a
   prompt to act, and colouring the ordinary case would put a coloured pill on every scheduled
   plan in the project. Colour that cries wolf stops being read. */
.status-pill--severity-none,
.status-pill--scale-low,
.status-pill--priority-low,
.status-pill--priority-normal,
.status-pill--risk-low,
.status-pill--severity-low {
    background: var(--color-neutral-bg);
    border-color: var(--color-border);
    color: var(--color-neutral-text);
}

.status-pill--scale-medium,
.status-pill--priority-high,
.status-pill--risk-medium,
.status-pill--severity-medium {
    background: var(--color-warning-bg);
    border-color: var(--color-warning-border);
    color: var(--color-warning);
}

.status-pill--scale-high,
.status-pill--priority-urgent,
.status-pill--risk-high,
.status-pill--severity-high {
    background: var(--color-danger-bg);
    border-color: var(--color-danger-border);
    color: var(--color-danger);
}

/* Filled rather than tinted: Critical is the same red as High, one step up in
   weight. A fifth hue would be a colour nobody could name against a level. */
.status-pill--scale-critical,
.status-pill--risk-critical,
.status-pill--severity-critical {
    background: var(--color-danger);
    border-color: var(--color-danger);
    color: var(--color-text-on-primary);
}

/* ===== Resting states in a table ========================================
 * In a register most rows are in a state that asks nothing of the reader:
 * published, assessed, superseded. Twenty filled pills down a column made the
 * one row that did need something — a pending review, a High severity — no
 * louder than the rest. Inside `.data-table` these resting states drop the
 * pill for a coloured marker beside plain text, so a filled pill now means
 * "look here". The label is unchanged, so the state is still read as text and
 * not from the colour (§7). Outside a table, where a pill names the one
 * record on the page, they keep the full pill.
 *
 * The bottom of each level scale rests too — a Low or no risk, a Low
 * severity, a Low or Normal priority asks for nothing — while Medium and up
 * keep their pills. The column still reads as a scale, since every level is
 * labelled; only the levels that matter are filled. */
.data-table :is(
    .status-pill--published,
    .status-pill--record-assessed,
    .status-pill--finding-not-applicable,
    .status-pill--fulfillment-confirmed,
    .status-pill--input-still-valid,
    .status-pill--risk-none,
    .status-pill--task-done,
    .status-pill--user-active
) {
    --pill-marker: var(--color-success);
}

.data-table :is(
    .status-pill--draft-only,
    .status-pill--superseded,
    .status-pill--assessment-previous,
    .status-pill--finding-duplicate,
    .status-pill--finding-without-risk,
    .status-pill--fulfillment-open,
    .status-pill--input-initial,
    .status-pill--risk-low,
    .status-pill--severity-low,
    .status-pill--severity-none,
    .status-pill--priority-low,
    .status-pill--priority-normal,
    .status-pill--task-open,
    .status-pill--task-cancelled
) {
    --pill-marker: var(--color-text-subtle);
}

.data-table .status-pill:is(
    .status-pill--published,
    .status-pill--record-assessed,
    .status-pill--finding-not-applicable,
    .status-pill--fulfillment-confirmed,
    .status-pill--input-still-valid,
    .status-pill--draft-only,
    .status-pill--superseded,
    .status-pill--assessment-previous,
    .status-pill--finding-duplicate,
    .status-pill--finding-without-risk,
    .status-pill--fulfillment-open,
    .status-pill--input-initial,
    .status-pill--risk-none,
    .status-pill--risk-low,
    .status-pill--severity-low,
    .status-pill--severity-none,
    .status-pill--priority-low,
    .status-pill--priority-normal,
    .status-pill--task-open,
    .status-pill--task-done,
    .status-pill--task-cancelled,
    .status-pill--user-active
) {
    gap: var(--space-2);
    padding: 0;
    border-color: transparent;
    background: transparent;
    color: var(--color-text);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-normal);

    /* A proposal on a resting record is the one thing on the row that still
       wants attention, but "Assessed · proposal" in plain text undersold it:
       here the marker is the pencil alone, in the accent, with the word kept
       for screen readers and the reason in its tooltip. */
    & .status-pill__proposal::before {
        content: none;
    }

    & .status-pill__proposal .icon {
        display: inline-block;
        color: var(--color-primary);
    }

    & .status-pill__proposal-text {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

/* The level a risk started at, beside the one that holds now: muted text, so a
   treated risk carries one risk pill rather than two (`risk_pair(table=true)`).
   It keeps its vocabulary modifier; only the drawing changes. After the
   resting rule above, which it must beat for a Low initial level. */
.data-table .status-pill.risk-pair-initial {
    padding: 0;
    border-color: transparent;
    background: transparent;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-normal);
}

.data-table .status-pill.risk-pair-initial > .icon {
    display: none;
}

/* A quiet pill whose glyph is the dash (Low, Not applicable, No risk
   assessment required) takes the dot instead. Drawn as plain text, a dash
   before the label read as an empty cell — "Low → — Low" looked like a value
   was missing — where in a filled pill it was only ever decoration. */
.data-table .status-pill > .icon:has(> use[href="#dash-lg"]) {
    display: none;
}

.data-table .status-pill:has(> .icon > use[href="#dash-lg"])::before {
    content: "";
    width: 0.5rem;
    height: 0.5rem;
    flex: 0 0 auto;
    border-radius: 50%;
    background: var(--pill-marker, currentColor);
}

/* The initial level is text, with no marker of its own. Specific enough to
   beat the dash rule above, whose `:has()` counts towards its weight. */
.data-table .status-pill.risk-pair-initial::before,
.data-table .status-pill.risk-pair-initial:has(> .icon > use[href])::before {
    content: none;
}

/* An empty cell's dash, and a zero count: there to say "nothing", so they
   recede rather than standing as a column of marks as dark as the data. */
.data-table-none,
/* A muted count that still links (the overview's zero Open tasks) would otherwise take the
   table link colour and read as work waiting. */
.data-table a.data-table-none {
    color: var(--color-text-subtle);
}

/* A pill with its own glyph keeps it as the marker; one without gets a dot.
   `--pill-marker` is only set on the resting states above, so these two rules
   cannot reach a filled pill: its icon keeps `currentColor`, and it has no dot. */
.data-table .status-pill > .icon {
    color: var(--pill-marker, currentColor);
}

.data-table .status-pill:not(:has(> .icon)):is(
    .status-pill--published,
    .status-pill--record-assessed,
    .status-pill--finding-not-applicable,
    .status-pill--fulfillment-confirmed,
    .status-pill--input-still-valid,
    .status-pill--draft-only,
    .status-pill--superseded,
    .status-pill--assessment-previous,
    .status-pill--finding-duplicate,
    .status-pill--finding-without-risk,
    .status-pill--fulfillment-open,
    .status-pill--input-initial,
    .status-pill--risk-none,
    .status-pill--risk-low,
    .status-pill--severity-low,
    .status-pill--severity-none,
    .status-pill--priority-low,
    .status-pill--priority-normal
)::before {
    content: "";
    width: 0.5rem;
    height: 0.5rem;
    flex: 0 0 auto;
    border-radius: 50%;
    background: var(--pill-marker);
}

.finding-details-list {
    padding-left: 0;
}

.finding-details-list li {
    list-style: none;
}

.subtle-border-right {
    border-right: 1px solid var(--color-border-subtle);
}

/* The document editor is a whole application rather than a widget: the document server renders
   its own toolbars, rulers and status bar inside this element, so anything less than the
   available height leaves it scrolling inside a page that also scrolls.
 *
 * The height is taken from the shell rather than computed from `100vh`. A viewport subtraction
 * has to guess at the topbar, the content padding and the page header above the editor, and it
 * is wrong on every window it was not measured in. The page opts in through the `body_class`
 * block in base.html, so no other page's layout changes.
 */
.page-editor .app-shell-content {
    display: flex;
    flex-direction: column;
}

.page-editor .app-shell-content > .content-width {
    display: flex;
    flex-direction: column;
    flex: 1;
    /* A flex item will not shrink below its content without this, and the editor would push the
       page taller instead of fitting inside it. */
    min-height: 0;
    /* The prose width cap is wrong for a spreadsheet: the same reason the editor takes the full
       height applies across. */
    max-width: none;
}

/* The workspace row is what the shell's height is divided into: the editor takes the width that
   is left, the instructions column takes a fixed slice of it. */
.editor-workspace {
    display: flex;
    flex: 1;
    gap: var(--space-3);
    /* Same reason as the content row above it: without this the row grows to its content instead
       of fitting inside the height it was given. */
    min-height: 0;
}

/* A notice standing where the viewer would be is as tall as its text, not as the row it sits in. */
.editor-workspace > .form-warning {
    align-self: flex-start;
}

.eurooffice-editor {
    flex: 1;
    /* A flex item defaults to not shrinking below its content, and the editor's content is a whole
       application: without this the instructions column would push it off the row. */
    min-width: 0;
    /* The floor is what applies where the shell has no definite height to divide - the narrow
       breakpoint, where the content row is auto-sized. */
    min-height: 40rem;
}

/* The footer's own height is what would push the document past the viewport on a page whose
   content fills it - and a window scrollbar beside the editor's own scrollbars is exactly what
   filling the height was meant to avoid. */
.page-editor .app-footer {
    display: none;
}

/* An action row that has more in it than a narrow column can hold on one line. The plain
   `.button-row` deliberately does not wrap - most are two or three buttons closing off a form,
   where wrapping would only ever be a symptom. */
.button-row--wrap {
    flex-wrap: wrap;
}

/* A rarely-used form folded away inside a panel. The summary reads as an action, so it is given
   the same alignment and gap as one rather than the browser's default marker-and-text run. */
.content-replace > summary {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    cursor: pointer;
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

.content-replace > form {
    margin-top: var(--space-3);
}

/* The editor page's header, on one row. Everything above the editor is height the document does
   not get, so the title, the state and the note that would otherwise be three stacked blocks
   share a line and wrap only when they have to. */
.editor-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-3);
    margin-bottom: var(--space-2);
}

.editor-bar h1 {
    margin: 0;
    font-size: var(--font-size-lg);
    /* Long document names shrink rather than pushing the state and the way back off the row. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.editor-bar-spacer {
    flex: 1 1 auto;
}

/* What sits at the far end of the bar when it holds more than a way back, kept together so that a
   bar too narrow for all of it moves the group to its own line instead of stranding the button
   from the text that explains the page. */
.editor-bar-end {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-3);
    margin-left: auto;
}

/* The guidance column, beside the editor rather than above it. Closed it is as wide as its own
   summary; open it is a fixed reading column that neither grows into the editor nor shrinks below
   a usable measure. A hairline rather than a panel: it is a section of this page, not a box
   pasted onto it - the same treatment `.cvss-panel` gets. */
.editor-instructions {
    display: flex;
    flex-direction: column;
    flex: 0 0 auto;
    min-height: 0;
    padding-left: var(--space-3);
    border-left: 1px solid var(--color-border-subtle);
}

.editor-instructions[open] {
    flex-basis: 20rem;
}

.editor-instructions > summary {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    cursor: pointer;
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
}

/* Guidance can be pages long, so the column scrolls rather than the page: the editor's height is
   what this page is for, and a window scrollbar beside the editor's own is what filling that
   height was meant to avoid. */
.editor-instructions > .document-instructions {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    margin-top: var(--space-2);
}

/* Below the breakpoint the row would leave the editor too narrow to be an editor, so the column
   goes back to being a block above it - capped in height, since there it is spending rows the
   document would otherwise get. */
@media (max-width: 70rem) {
    .editor-workspace {
        flex-direction: column;
    }

    .editor-instructions {
        order: -1;
        padding-left: 0;
        border-left: none;
        margin-bottom: var(--space-1);
    }

    .editor-instructions[open] {
        flex-basis: auto;
    }

    .editor-instructions > .document-instructions {
        flex: 0 1 auto;
        max-height: 12rem;
    }
}

.assessment-treatment {
    margin-top: var(--space-4);
}

/* The treatment spans both columns of a split form, so its own fields use that
   width instead of stacking down the middle of it. `display: contents` on the
   conditional wrappers lets the residual fields join this grid rather than
   forming a nested one-column block inside it; hiding a wrapper still hides
   its fields, which is all `residual_script` does with them. The response, landing and card
   wrappers, which exist for the treatment cards, dissolve here the same way. The in-place
   editor keeps the cards (`treatment-fields--cards`). */
.assessment-treatment .treatment-fields:not(.treatment-fields--cards) {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
    gap: 0 var(--space-4);
    align-items: start;
}

.assessment-treatment .treatment-fields:not(.treatment-fields--cards) :is(
    .treatment-fields__response,
    .treatment-fields__landing,
    .treatment-fields__card,
    .conditional-fields
) {
    display: contents;
}

.assessment-treatment .treatment-fields:not(.treatment-fields--cards) .conditional-fields[hidden] {
    display: none;
}

.assessment-treatment textarea.input {
    max-height: 12rem;
}

/* The derived-residual note explains the whole row above it, not one field. */
.assessment-treatment .treatment-fields:not(.treatment-fields--cards) [data-residual-derived] {
    grid-column: 1 / -1;
}

/* A section recorded in place rather than in a dialog. A tinted surface and an accent
   rule mark the one region of the page that is not yet a record; everything around it stays as
   read, which is the point of editing in place rather than in a modal. */
.record-detail__section--editing {
    padding-bottom: 0;
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    background: var(--color-bg-subtle);
    box-shadow: inset 3px 0 var(--color-primary);
}

.record-detail__section--editing .record-detail__eyebrow {
    color: var(--color-primary);
}

.treatment-editor {
    display: grid;
    gap: var(--space-5);
}

/* The response, then the residual risk it leads to: the residual is the outcome of the choice
   above it, and Accept and Avoid collapse it to a sentence. Side by side, the short residual
   card floated beside a tall response. */
.treatment-fields--cards {
    display: grid;
    gap: var(--space-5);
}

/* Full-width prose boxes run to lines too long to read or write in. The cap is the cards' width,
   so "Why the plan changed", outside them, ends where they do; inside, the card is the limit. */
.treatment-fields--cards textarea.input {
    max-width: calc(48rem + 2 * var(--space-4));
}

/* A revision's "Why the plan changed" follows both columns and explains the whole plan. */
.treatment-fields--cards > .field {
    grid-column: 1 / -1;
}

.treatment-fields__heading {
    margin: 0 0 var(--space-3);
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    letter-spacing: var(--letter-spacing-caps);
    text-transform: uppercase;
}

/* Capped like the prose boxes, so the card ends where its fields do rather than trailing empty
   surface across the page. */
.treatment-fields--cards .treatment-fields__card {
    max-width: calc(48rem + 2 * var(--space-4));
    padding: var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-surface);
}

/* The response always ends on a `.field` — Needs a requirement, or the external control when it
   shows — and so does the residual when it asks for one; that bottom margin would otherwise
   stack on the padding as dead space. */
.treatment-fields--cards .treatment-fields__response > .treatment-fields__card,
.treatment-fields--cards .treatment-fields__card:has(> [data-residual-fields]:not([hidden])) {
    padding-bottom: calc(var(--space-4) - var(--space-3));
}

.treatment-fields__initial {
    margin: 0 0 var(--space-4);
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

/* One row under the initial risk it is read against: likelihood, impact, and what they make. */
.treatment-fields--cards [data-residual-fields]:not([hidden]) {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 14rem)) minmax(0, 16rem);
    gap: 0 var(--space-3);
}

.treatment-fields--cards [data-residual-fields] .inline-field,
.treatment-fields--cards [data-residual-fields] .select {
    width: 100%;
}

.treatment-editor textarea.input {
    background: var(--color-surface);
}

.treatment-editor__requirements {
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border);
}

/* The section's closing edge rather than a floating bar: it bleeds to the card's edges and
   takes the card's bottom corners. The buttons keep to the right whether or not modes precede
   them. */
.record-detail__editor-actions {
    margin-inline: calc(-1 * var(--space-4));
    border: 0;
    border-top: 1px solid var(--color-border);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    background: transparent;
}

.record-detail__editor-actions > .button-row {
    margin-inline-start: auto;
}

/* With no modes to hold the left, the buttons start the bar and the validation message follows
   them, so it appearing does not move what the reader is about to press. */
.record-detail__editor-actions--start {
    justify-content: flex-start;
}

.record-detail__editor-actions--start > .button-row {
    margin-inline-start: 0;
}

.record-detail__editor-actions--stacked {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-3);
}

.record-detail__editor-actions--stacked > .button-row {
    margin-inline-start: 0;
}

/* The message spans the bar, as it does when it wraps below the buttons of a flat one. */
.record-detail__editor-actions--stacked > .form-error {
    align-self: stretch;
}

/* Fields left on the editing tint would read as disabled; the group boxes below give the
   sections their white cards. */
.assessment-editor .assessment-treatment,
.assessment-editor textarea.input {
    background: var(--color-surface);
}

/* The note belongs with the sentence it follows, not across the full width of the editor. */
.assessment-editor .assessment-form__intro {
    align-items: baseline;
    justify-content: flex-start;
}

.assessment-editor .assessment-form__intro .assessment-form__required-note {
    margin-inline-start: 0;
}

/* The in-place risk assessment reads as one column, like the treatment editor below it: the
   judgement, then the evidence it rests on, then its follow-ups. Side by side, the short evidence
   column floated beside a tall judgement. The create page and the proposal dialog keep the
   split, which suits a form that is the whole page or the whole dialog. */
.assessment-editor.assessment-form--risk .pl-form-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--space-5);
}

.assessment-editor.assessment-form--risk .pl-form-split > [data-col] {
    gap: var(--space-5);
}

/* Capped as the treatment cards are, so the two editors end at the same edge. */
.assessment-editor.assessment-form--risk .pl-form-split > [data-col],
.assessment-editor.assessment-form--risk .pl-form-split > .assessment-treatment {
    max-width: calc(48rem + 2 * var(--space-4));
}

/* The treatment editor's group box, in every in-place assessment editor: a caps heading above a
   white card. The fieldset keeps its legend, which is what names the group to assistive
   technology; lifted out of the border, it reads as the heading. The top margin is the room the
   heading takes. A finding's conclusion-specific sections lose their tint with it: which of them
   shows already says which conclusion was chosen. */
.assessment-editor fieldset.assessment-section {
    position: relative;
    margin-top: calc(1rem + var(--space-3));
    border-color: var(--color-border);
    background: var(--color-surface);
}

.assessment-editor fieldset.assessment-section > legend {
    position: absolute;
    top: calc(-1 * (1rem + var(--space-3)) - 1px);
    left: -1px;
    padding: 0;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    line-height: 1rem;
    letter-spacing: var(--letter-spacing-caps);
    text-transform: uppercase;
}

/* The disclosures read as the group boxes do. Closed, the heading is all there is — the caps
   heading, marked optional, that opens it. Open, Reassessment's fields take a card under it as a
   fieldset's do; the treatment brings its own cards, so it takes none. */
.assessment-editor :is(details.assessment-section--reassessment, details.assessment-treatment) {
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
}

.assessment-editor :is(details.assessment-section--reassessment, details.assessment-treatment) > summary {
    display: flex;
    align-items: baseline;
    justify-content: flex-start;
    gap: var(--space-2);
    width: auto;
    height: auto;
    min-height: 0;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    line-height: 1rem;
    letter-spacing: var(--letter-spacing-caps);
    text-transform: uppercase;
}

.assessment-editor :is(details.assessment-section--reassessment, details.assessment-treatment) > summary:hover {
    background: none;
    color: var(--color-text);
}

.assessment-editor :is(details.assessment-section--reassessment, details.assessment-treatment) > summary::after {
    font-size: var(--font-size-base);
}

.assessment-editor :is(details.assessment-section--reassessment, details.assessment-treatment) .assessment-optional {
    letter-spacing: normal;
    text-transform: none;
}

.assessment-editor details.assessment-section--reassessment[open] {
    position: relative;
    margin-top: calc(1rem + var(--space-3));
    padding: var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-surface);
}

.assessment-editor details.assessment-section--reassessment[open] > summary {
    position: absolute;
    top: calc(-1 * (1rem + var(--space-3)) - 1px);
    left: -1px;
}

.assessment-editor.assessment-form--risk details.assessment-treatment[open] > summary {
    margin-bottom: var(--space-3);
}

/* No panel dividers: the cards separate the parts. */
.assessment-editor.assessment-form--risk details.assessment-treatment > *:not(summary) {
    padding: 0;
    border-top: 0;
}

.assessment-editor.assessment-form--risk details.assessment-treatment > .assessment-treatment__intro {
    margin-bottom: var(--space-5);
}

/* As in the treatment editor: the requirements close the plan under a rule of their own. */
.assessment-editor.assessment-form--risk details.assessment-treatment > .treatment-requirement-picker {
    margin-top: var(--space-5);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border);
}

/* Capped as the assessment and treatment cards are, so every in-place editor ends at one edge. */
.consideration-editor > fieldset.assessment-section {
    max-width: calc(48rem + 2 * var(--space-4));
}

/* ===== Bundle template pages ============================================
 * These pages are an administrative library, not a raw form and table. The
 * layout gives the import task a clear landing zone, then lets the collection
 * read as a set of reusable assets. */
.bundle-template-page {
    display: grid;
    min-width: 0;
    gap: var(--space-8);
    padding-bottom: var(--space-8);
}

.bundle-template-page > * {
    min-width: 0;
}

.bundle-template-page-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-6);
    padding-bottom: var(--space-5);
    border-bottom: 1px solid var(--color-border);
}

.bundle-template-page-header h1,
.bundle-template-detail-heading h1 {
    margin-bottom: var(--space-1);
}

.bundle-template-kicker {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    margin: 0 0 var(--space-2);
    color: var(--color-primary);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-caps);
    text-transform: uppercase;
}

.bundle-template-kicker .icon {
    width: 0.875rem;
    height: 0.875rem;
}

.bundle-template-lead {
    max-width: 42rem;
    margin: 0;
    color: var(--color-text-muted);
    font-size: var(--font-size-lg);
}

.bundle-template-example-link {
    flex: 0 0 auto;
    margin-top: var(--space-4);
}

.bundle-template-example-link .icon,
.bundle-template-import-footer .icon {
    width: 1rem;
    height: 1rem;
}

.bundle-template-import-card {
    overflow: hidden;
}

.bundle-template-import-card > .bundle-template-card-heading {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-5) var(--space-6);
    background: var(--color-surface-subtle);
}

.bundle-template-card-heading > div {
    min-width: 0;
}

.bundle-template-card-heading h2,
.bundle-template-section-heading h2,
.bundle-template-danger-zone h2 {
    margin: 0;
}

.bundle-template-card-heading p {
    margin: var(--space-1) 0 0;
    color: var(--color-text-muted);
}

.bundle-template-card-icon,
.bundle-template-upload-icon,
.bundle-template-empty-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: var(--radius-md);
    background: var(--color-primary-bg);
    color: var(--color-primary);
}

.bundle-template-card-icon .icon {
    width: 1.25rem;
    height: 1.25rem;
}

.bundle-template-format,
.bundle-template-count {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    padding: var(--space-1) var(--space-2);
    border: 1px solid var(--color-primary-border);
    border-radius: 999px;
    background: var(--color-primary-bg);
    color: var(--color-primary);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    white-space: nowrap;
}

.bundle-template-format {
    margin-left: auto;
}

.bundle-template-import-card > form {
    padding: var(--space-5) var(--space-6) var(--space-6);
}

.bundle-template-upload {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-5);
    padding: var(--space-4);
    border: 1px dashed var(--color-primary-border);
    border-radius: var(--radius-md);
    background: var(--color-primary-bg);
}

.bundle-template-upload-copy {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
}

.bundle-template-upload-icon {
    width: 2rem;
    height: 2rem;
    background: var(--color-surface);
}

.bundle-template-upload-icon .icon {
    width: 1.125rem;
    height: 1.125rem;
}

.bundle-template-upload-copy div {
    display: grid;
    gap: var(--space-1);
}

.bundle-template-upload-copy span {
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

.bundle-template-upload .input[type="file"] {
    flex: 0 1 25rem;
    min-width: 0;
}

.bundle-template-import-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-5);
    margin-top: var(--space-4);
}

.bundle-template-import-footer .field-hint {
    max-width: 48rem;
}

.bundle-template-import-footer .pl-btn {
    flex: 0 0 auto;
    white-space: nowrap;
}

.bundle-template-library {
    display: grid;
    gap: var(--space-3);
}

.bundle-template-section-heading {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-4);
}

.bundle-template-section-heading .bundle-template-kicker {
    margin-bottom: var(--space-1);
}

.bundle-template-table-card > .data-table-scroll {
    padding: 0 var(--space-4) var(--space-4);
}

.bundle-template-table td:first-child {
    width: 100%;
}
.bundle-template-library,
.bundle-template-table-card,
.bundle-template-table-card > .data-table-scroll {
    min-width: 0;
}

.bundle-template-table th:last-child,
.bundle-template-table td:last-child {
    width: 9rem;
    text-align: right;
}

.bundle-template-name {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
}

.bundle-template-name-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 2rem;
    height: 2rem;
    border-radius: var(--radius-md);
    background: var(--color-neutral-bg);
    color: var(--color-neutral-text);
}

.bundle-template-name-icon .icon {
    width: 1.125rem;
    height: 1.125rem;
}

.bundle-template-name > span:last-child {
    display: grid;
    gap: var(--space-1);
    min-width: 0;
}

.bundle-template-name strong {
    overflow: hidden;
    text-overflow: ellipsis;
}

.bundle-template-name small {
    overflow: hidden;
    color: var(--color-text-muted);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-normal);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.bundle-template-document-count {
    display: inline-flex;
    min-width: 2rem;
    align-items: center;
    justify-content: center;
    padding: var(--space-1) var(--space-2);
    border-radius: 999px;
    background: var(--color-neutral-bg);
    color: var(--color-neutral-text);
    font-weight: var(--font-weight-semibold);
}

.bundle-template-empty {
    display: grid;
    justify-items: center;
    gap: var(--space-2);
    padding: var(--space-8) var(--space-6);
    border: 1px dashed var(--color-border-strong);
    border-radius: var(--radius-lg);
    background: var(--color-surface-subtle);
    text-align: center;
}

.bundle-template-empty h3 {
    margin: 0;
}

.bundle-template-empty p {
    max-width: 32rem;
    margin: 0;
    color: var(--color-text-muted);
}

.bundle-template-empty-icon {
    width: 3rem;
    height: 3rem;
    margin-bottom: var(--space-2);
}

.bundle-template-empty-icon .icon {
    width: 1.5rem;
    height: 1.5rem;
}

.bundle-template-detail-header {
    display: grid;
    gap: var(--space-5);
}

.bundle-template-back-link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    width: max-content;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
}

.bundle-template-back-link .icon {
    width: 1rem;
    height: 1rem;
    transform: rotate(90deg);
}

.bundle-template-detail-heading {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-5);
    padding-bottom: var(--space-5);
    border-bottom: 1px solid var(--color-border);
}

.bundle-template-documents-card > .bundle-template-section-heading {
    align-items: center;
    padding: var(--space-5) var(--space-6) var(--space-4);
}

.bundle-template-documents-card > .data-table-scroll {
    padding: 0 var(--space-4) var(--space-4);
}

.bundle-template-documents-table th:first-child,
.bundle-template-documents-table td:first-child {
    width: 28%;
}

.bundle-template-document-name {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.bundle-template-instructions {
    max-width: var(--prose-max);
    max-height: 12rem;
    margin: 0;
    padding: var(--space-3);
    overflow: auto;
    white-space: pre-wrap;
}

.bundle-template-empty--inside {
    margin: 0 var(--space-4) var(--space-4);
}

.bundle-template-danger-zone {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-5);
    padding: var(--space-5) var(--space-6);
    border: 1px solid var(--color-danger-border);
    border-radius: var(--radius-lg);
    background: var(--color-danger-bg);
}

.bundle-template-danger-zone .bundle-template-kicker {
    margin-bottom: var(--space-1);
    color: var(--color-danger);
}

.bundle-template-danger-zone p:last-child {
    margin: var(--space-1) 0 0;
    color: var(--color-text-muted);
}

.bundle-template-danger-zone form {
    flex: 0 0 auto;
    margin: 0;
}

@media (max-width: 48rem) {
    .bundle-template-page-header,
    .bundle-template-detail-heading,
    .bundle-template-card-heading,
    .bundle-template-import-footer,
    .bundle-template-danger-zone {
        align-items: stretch;
        flex-direction: column;
    }

    .bundle-template-page-header {
        gap: var(--space-4);
    }

    .bundle-template-example-link {
        align-self: flex-start;
        margin-top: 0;
    }

    .bundle-template-format {
        align-self: flex-start;
        margin-left: 0;
    }

    .bundle-template-import-card > .bundle-template-card-heading,
    .bundle-template-import-card > form {
        padding-inline: var(--space-4);
    }

    .bundle-template-upload {
        align-items: stretch;
        flex-direction: column;
        gap: var(--space-3);
    }

    .bundle-template-card-icon,
    .bundle-template-upload-icon {
        align-self: flex-start;
    }

    .bundle-template-upload .input[type="file"] {
        flex-basis: auto;
        max-width: none;
    }

    .bundle-template-import-footer .pl-btn {
        align-self: flex-start;
    }

    .bundle-template-section-heading {
        align-items: flex-start;
    }

    .bundle-template-table {
        min-width: 32rem;
    }
    .bundle-template-detail-heading .bundle-template-count {
        align-self: flex-start;
    }

    .bundle-template-detail-heading {
        gap: var(--space-3);
    }

    .bundle-template-danger-zone form {
        align-self: flex-start;
    }
}

/* The three blocks an import sheet is made of — the facts, the judgement, the
   response — in the same three colours the downloadable workbook bands its
   columns with, so the reference on this page and the sheet in Excel group the
   columns the same way.

   Deliberately none of the four risk-level colours: those appear as *values*
   inside the assessment and treatment blocks (the calculated Risk and Residual
   risk columns), and a block tinted the same would read as a level nobody
   assigned. */
.import-part {
    display: inline-block;
    padding: 0 var(--space-2);
    border-radius: var(--radius-sm);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    white-space: nowrap;
}

.import-part--risk {
    background: var(--color-neutral-bg);
    color: var(--color-neutral-text);
}

.import-part--assessment {
    background: var(--color-primary-bg);
    color: var(--color-primary-hover);
}

.import-part--treatment {
    background: var(--color-success-bg);
    color: var(--color-success);
}

.pat-list {
    margin-top: var(--space-6);
}

/* ===== Shell topbar controls (create task + task bell) =================== */

/* The shell's icon controls, sized together rather than as `--sm` buttons that
   happened to land beside each other: one height, one square box, so the
   cluster reads as one row in a bar whose only other contents are text. `--sm`
   is for dense table rows; at that size these sat well under the topbar's
   optical centre. */
/* Borderless, unlike every other `.pl-btn`. These are the only controls in an
   otherwise empty bar, and as outlined boxes they read as loose chips floating
   in it. A bar is chrome: its affordances should be quiet until reached for,
   and take their shape from the hover rather than carry one at rest. */
.topbar-trigger {
    position: relative;
    height: 2rem;
    width: 2rem;
    padding: 0;
    border-color: transparent;
    background: transparent;
    box-shadow: none;
    color: var(--color-text-muted);
}

.topbar-trigger:hover {
    background: var(--color-neutral-bg);
    border-color: transparent;
    color: var(--color-text);
}

/* Square: every one is icon-only, named by `aria-label` and shown by `title`,
   and label padding would make each a wide box around a small mark. */
.topbar-trigger .icon {
    /* `.icon` is sized in `em`, and the button's own `sm` text would draw a mark
       too light to carry a topbar on its own. */
    font-size: var(--font-size-md);
}

/* Over the bell's corner rather than in the link's flow: laid out inline, the
   arrival of the first assignment widened the link and shifted the topbar. The
   ring is the surface it sits on, so the badge reads as a disc on top of the
   control rather than as a notch cut out of its edge.

   Rendered only when the count is nonzero; the link it sits on is the stable
   out-of-band target, so the badge can come and go with the count. */
.topbar-badge {
    position: absolute;
    top: -0.25rem;
    right: -0.25rem;
    min-width: 1rem;
    height: 1rem;
    padding: 0 0.1875rem;
    border-radius: 999px;
    /* The accent, not `--color-danger`: an assignment is a colleague asking for
       a look, and the count of them is the most ordinary thing in the bar. Red
       is this app's word for a failure or a destructive action — spending it on
       an unread count both alarms the reader over nothing and devalues it where
       it does mean something. White on the accent clears 4.5:1 (~5.6:1). */
    background: var(--color-primary);
    box-shadow: 0 0 0 2px var(--color-surface);
    color: #ffffff;
    font-size: var(--font-size-2xs);
    font-weight: var(--font-weight-semibold);
    line-height: 1rem;
    text-align: center;
}

.topbar-trigger:hover .topbar-badge {
    box-shadow: 0 0 0 2px var(--color-neutral-bg);
}

.view-document-status {
    margin-top: 1rem;
}

/* ===== Toasts ===========================================================
 * A success flash lifted out of the page by flash-toast.js. Top right, under
 * the topbar, so it is in view however far the page was scrolled and never
 * covers the page's own heading or primary action. */
.toast-region {
    position: fixed;
    top: calc(var(--topbar-height) + var(--space-3));
    right: var(--space-4);
    z-index: 1000;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    width: min(24rem, calc(100vw - var(--space-8)));
}

.toast {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-3) var(--space-3) var(--space-4);
    border: 1px solid var(--color-success-border);
    border-left: 3px solid var(--color-success);
    border-radius: var(--radius-md);
    background: var(--color-surface);
    box-shadow: var(--shadow-md);
    animation: toast-in var(--duration-fast) ease-out;
}

.toast--leaving {
    opacity: 0;
    transform: translateY(-0.25rem);
    transition: opacity 200ms ease, transform 200ms ease;
}

/* The flash keeps its class (and so its role) but drops its banner styling:
   in the toast the border and the bar say "success". */
.toast .toast__message {
    flex: 1;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    color: var(--color-text);
    font-size: var(--font-size-sm);
}

.toast__close {
    flex: 0 0 auto;
    padding: 0 var(--space-1);
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--color-text-muted);
    font-size: var(--font-size-lg);
    line-height: 1;
    cursor: pointer;
}

.toast__close:hover {
    background: var(--color-neutral-bg);
    color: var(--color-text);
}

@keyframes toast-in {
    from {
        opacity: 0;
        transform: translateY(-0.25rem);
    }
}

@media (prefers-reduced-motion: reduce) {
    .toast,
    .toast--leaving {
        animation: none;
        transition: none;
    }
}

/* ===== Busy submit ======================================================
 * A submit button whose form is on its way (busy-submit.js): the label stays,
 * so the button keeps its width and still says what is happening, with a
 * spinner before it. The disabled styling above greys it. */
.pl-btn.is-busy::before {
    content: "";
    width: 0.875em;
    height: 0.875em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: busy-spin 700ms linear infinite;
}

@keyframes busy-spin {
    to {
        transform: rotate(360deg);
    }
}

@media (prefers-reduced-motion: reduce) {
    .pl-btn.is-busy::before {
        animation: none;
        border-right-color: currentColor;
        opacity: 0.6;
    }
}
