/* ───────────────────────────────────────────────────────────────────────────
   RFQ — base reset, element defaults and the shared component vocabulary.

   Tokens live in css/tokens.css and are the only source of color, radius and
   type. NOTHING IN THIS FILE MAY HARDCODE A COLOR (invariant 1).

   THE RESET GOES ON `html, body`, not on a `.ds-root` wrapper. FlowMES needs
   that wrapper only because its design system has to coexist with Bootstrap
   across 209 legacy pages. This is greenfield: there is no Bootstrap, no
   Syncfusion and no Radzen, and none should be added.
   ─────────────────────────────────────────────────────────────────────────── */

@import url("css/tokens.css");

/* ── Reset ───────────────────────────────────────────────────────────────── */

*,
*::before,
*::after {
    box-sizing: border-box;
}

html,
body {
    margin: 0;
    padding: 0;
    height: 100%;
    background: var(--bg);
    color: var(--ink-body);
    font-family: var(--font-sans);
    font-size: var(--fs-base);
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

h1,
h2,
h3,
h4,
p,
figure,
blockquote,
dl,
dd {
    margin: 0;
}

ul[class],
ol[class] {
    margin: 0;
    padding: 0;
    list-style: none;
}

img,
svg {
    max-width: 100%;
    display: block;
}

button,
input,
select,
textarea {
    font: inherit;
    color: inherit;
}

/* ── Type roles ──────────────────────────────────────────────────────────── */

.page-title {
    font-family: var(--font-display);
    font-size: var(--fs-xl);
    font-weight: var(--fw-medium);
    line-height: 1.2;
    color: var(--ink);
    letter-spacing: -0.01em;
}

.page-subtitle {
    font-size: var(--fs-md);
    color: var(--ink-3);
}

.section-heading {
    font-size: var(--fs-lg);
    font-weight: var(--fw-medium);
    color: var(--ink);
}

.card-title {
    font-size: var(--fs-lead);
    font-weight: var(--fw-semibold);
    color: var(--ink);
}

.field-label {
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--ink-3);
}

.note {
    font-size: var(--fs-note);
    color: var(--ink-3);
}

/* MONO + TABULAR FIGURES ON EVERY IDENTIFIER, MONEY VALUE AND QUANTITY
   (invariant 14). Tabular figures give every digit the same advance width, so
   a column forms a straight edge and an outlier is visible without reading. */
.mono {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

.mono--id {
    font-weight: var(--fw-semibold);
    color: var(--ink);
}

/* A value the system does not have. Never blank — blank reads as "loading". */
.absent {
    color: var(--ink-4);
    font-style: normal;
}

/* For text that must exist without being seen — most often a <th> whose column
   holds controls rather than data. Leaving that header empty is not the simpler
   option, it is the one where a screen reader announces a nameless column.

   NOT `display: none` and NOT `visibility: hidden`: both remove the element from
   the accessibility tree too, which defeats the entire purpose. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ── Focus: ONE ring, on every focusable control (invariant 12) ──────────── */
/* The border does NOT go accent on focus — it steps to a stronger grey and the
   ring carries the focus. Never `outline: none` without a replacement. */

/* Scoped to things a person can actually operate. A bare `:focus-visible` also
   catches the page <h1>, which Blazor's FocusOnNavigate focuses on every route
   change — the heading then wears a focus ring on arrival at every page, which
   reads as an input box and is wrong. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[role="button"]:focus-visible,
[tabindex="0"]:focus-visible {
    outline: 2px solid transparent;
    box-shadow: 0 0 0 3px var(--ring);
    border-radius: var(--radius);
}

/* The routing focus target. It IS focused, deliberately, so screen readers land
   on the new page's title — it just must not be painted as a control. Not an
   unreplaced `outline: none`: this element has no focus affordance to remove,
   because it is not interactive. */
h1[tabindex="-1"]:focus,
h1[tabindex="-1"]:focus-visible {
    outline: none;
    box-shadow: none;
}

/* ── Buttons ─────────────────────────────────────────────────────────────── */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-s);
    height: var(--control-h);
    padding: 0 var(--space-m);
    border-radius: var(--radius);
    border: 1px solid transparent;
    font-size: var(--fs-base);
    font-weight: var(--fw-medium);
    cursor: pointer;
    text-decoration: none;
    white-space: nowrap;
    /* Transitions are 100ms on background and border-color only. Never animate
       layout properties (invariant 7). */
    transition: background var(--dur-base) var(--ease-in-out),
                border-color var(--dur-base) var(--ease-in-out),
                color var(--dur-base) var(--ease-in-out);
}

.btn--sm { height: var(--control-h-sm); padding: 0 var(--space-s); font-size: var(--fs-note); }
.btn--lg { height: var(--control-h-lg); padding: 0 var(--space-l); font-size: var(--fs-md); }

/* ONE PRIMARY ACTION PER VIEW. This is one of law 1's two sanctioned uses of
   full-strength accent. */
.btn--primary {
    background: var(--accent);
    color: var(--on-accent);
}

.btn--primary:hover { background: var(--accent-2); }
.btn--primary:active { background: var(--accent-3); }

/* Secondary borders NEVER move on hover — only the wash changes (invariant 11). */
.btn--secondary {
    background: var(--surface);
    color: var(--ink-body);
    border-color: var(--line-strong);
}

.btn--secondary:hover { background: var(--surface-hover); }
.btn--secondary:active { background: var(--surface-active); }

.btn--ghost {
    background: transparent;
    color: var(--ink-quiet);
}

.btn--ghost:hover { background: var(--surface-hover); }
.btn--ghost:active { background: var(--surface-active); }

/* NO SOLID RED BUTTON EXISTS (invariant 10). In-flow deletes are danger-ghost:
   red text, GREY hover wash. Red never grows on approach — a delete button
   that reddens as the cursor nears it gets louder the closer you get to the
   mistake. Red lives in the words. */
.btn--danger-ghost {
    background: transparent;
    color: var(--danger-deep);
}

.btn--danger-ghost:hover { background: var(--surface-hover); color: var(--danger-2); }
.btn--danger-ghost:active { background: var(--surface-active); }
.btn--danger-ghost:focus-visible { box-shadow: 0 0 0 3px var(--ring-danger); }

/* The confirm dialog's destructive button. The gravest action is the darkest
   object, which is why --ink-max exists. */
.btn--destructive {
    background: var(--ink);
    color: var(--on-ink);
}

.btn--destructive:hover { background: var(--ink-max); }

/* Use the native `disabled` attribute, never a hand-rolled class —
   `pointer-events: none` is what suppresses the hover on a dead control
   (invariant 6). */
.btn:disabled,
.btn[aria-disabled="true"] {
    opacity: 0.5;
    cursor: not-allowed;
    pointer-events: none;
}

/* ── Inputs ──────────────────────────────────────────────────────────────── */

.input,
.textarea {
    width: 100%;
    background: var(--surface);
    color: var(--ink-body);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    padding: 0 var(--space-s);
    font-size: var(--fs-base);
    font-family: var(--font-sans);
    transition: border-color var(--dur-base) var(--ease-in-out),
                background var(--dur-base) var(--ease-in-out);
}

.input { height: var(--control-h); }

.textarea {
    padding: var(--space-s);
    min-height: 88px;
    line-height: 1.5;
    resize: vertical;
}

/* INPUT BORDERS DO MOVE ON HOVER — unlike secondary buttons, whose borders
   never do. The difference is deliberate: an input is a place to put something
   and the border is its affordance; a button's border is just its edge. */
.input:hover,
.textarea:hover { border-color: var(--ink-4); }

/* On focus the border steps to a stronger grey and the RING carries the focus.
   The border does not go accent. */
.input:focus-visible,
.textarea:focus-visible {
    border-color: var(--ink-3);
    box-shadow: 0 0 0 3px var(--ring);
    outline: 2px solid transparent;
}

.input::placeholder,
.textarea::placeholder { color: var(--ink-4); }

.input:disabled,
.textarea:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    background: var(--surface-2);
}

/* ── Badges — ALWAYS carry a text label, never color alone (invariant 13) ── */

.badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    height: var(--control-h-chip);
    padding: 0 var(--space-s);
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    white-space: nowrap;
}

/* THE HAIRLINE IS NOT OPTIONAL — a borderless soft badge goes mushy against a
   --surface-2 row. */
.badge--success { background: var(--success-soft); color: var(--success-deep); border-color: var(--success-line); }
.badge--info    { background: var(--info-soft);    color: var(--info-deep);    border-color: var(--info-line); }
.badge--violet  { background: var(--violet-soft);  color: var(--violet-deep);  border-color: var(--violet-line); }
.badge--danger  { background: var(--danger-soft);  color: var(--danger-deep);  border-color: var(--danger-line); }

/* Warning text is --warning-deep, never --warning: the base is 2.80:1 on white
   and is not legible as text. */
.badge--warning { background: var(--warning-soft); color: var(--warning-deep); border-color: var(--warning-line); }

/* Passive — a state with no condition attached. Wears ink, not a semantic. */
.badge--neutral {
    background: var(--surface-2);
    color: var(--ink-quiet);
    border-color: var(--line);
}

/* ── Cards — border only. The lightest step on the elevation ladder. ─────── */

.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-1);
}

.card__body { padding: var(--space-l); }

.card__header {
    padding: var(--space-m) var(--space-l);
    border-bottom: 1px solid var(--line);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-m);
}

/* ── Stat tiles ──────────────────────────────────────────────────────────── */

.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: var(--space-m);
}

.stat {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: var(--space-l);
    box-shadow: var(--shadow-1);
    /* 3px wayfinding rail. WAYFINDING ONLY, never status. */
    border-left: 3px solid var(--rail-grey);
}

.stat--action   { border-left-color: var(--rail-orange); }
.stat--planning { border-left-color: var(--rail-blue); }
.stat--supply   { border-left-color: var(--rail-teal); }
.stat--analysis { border-left-color: var(--rail-purple); }

.stat__label {
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--ink-3);
    margin-bottom: var(--space-s);
}

.stat__value {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xl);
    font-weight: var(--fw-semibold);
    color: var(--ink);
    line-height: 1.1;
}

.stat__meta {
    font-size: var(--fs-note);
    color: var(--ink-3);
    margin-top: var(--space-xs);
}

/* ── Tables ──────────────────────────────────────────────────────────────── */

.table-wrap {
    /* Wide content scrolls inside its own container; the page body must never
       scroll horizontally. */
    overflow-x: auto;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--shadow-1);
}

/* A table already inside a bordered card supplies its own frame, so the wrap
   drops its border, radius and shadow rather than drawing a second one. */
.table-wrap--flush {
    border: none;
    border-radius: 0;
    box-shadow: none;
}

.table {
    width: 100%;
    /* Never narrower than this, or columns crush into unreadable slivers before
       the wrap starts scrolling. Below it the .table-wrap takes over and scrolls
       horizontally — the page itself still never does. */
    min-width: 720px;
    border-collapse: collapse;
    font-size: var(--fs-base);
}

/* COLUMN SIZING. A table at width:100% with no hints divides space evenly,
   which is wrong in both directions: a 4-digit RFQ number gets the same room as
   a 60-character subject, and every column stretches absurdly on a wide screen.
   These two classes fix it and are all the vocabulary needed.

   .col-grow absorbs the slack — put it on the one column that benefits from
   extra room. Width 100% on a table cell is a maximiser, not a literal width;
   the browser gives it whatever is left after the tight columns are satisfied.

   .col-tight shrinks to its content. Width 1% is the counterpart trick: it
   means "as small as the content allows". */
.table .col-grow {
    width: 100%;
    min-width: 16ch;
    /* The one column allowed to wrap. */
    white-space: normal;
}

.table .col-tight {
    width: 1%;
    white-space: nowrap;
}

/* Rows stay ONE LINE TALL. Without this the body cells wrap even though their
   header is tight — "15 Sep, 05:09" split across two lines and every row grew
   to match the worst cell in it, which destroys the straight-edge scan that
   tabular figures exist to give. Content that genuinely cannot fit widens the
   table and the wrap scrolls; the page still never does. */
.table th,
.table td {
    white-space: nowrap;
}

.table th.col-grow,
.table td.col-grow {
    white-space: normal;
}

.table thead th {
    text-align: left;
    padding: var(--space-s) var(--space-m);
    background: var(--surface-2);
    border-bottom: 1px solid var(--line);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--ink-3);
    white-space: nowrap;
}

/* A SORTED header wears ink, not accent — it is a fact about the column, not
   an action (invariant 9). */
.table thead th[aria-sort] { color: var(--ink); }

.table tbody td {
    padding: var(--space-s) var(--space-m);
    /* Row dividers use --line-2, which is lighter than --line, or a dense table
       reads as striped. */
    border-bottom: 1px solid var(--line-2);
    color: var(--ink-body);
    vertical-align: middle;
}

.table tbody tr:last-child td { border-bottom: none; }

.table tbody tr {
    transition: background var(--dur-base) var(--ease-in-out);
}

.table tbody tr:hover { background: var(--surface-hover); }

/* One of only two sanctioned accent-as-state exceptions: the SELECTED TABLE ROW
   washes --accent-soft. Not extensible. */
.table tbody tr[aria-selected="true"] { background: var(--accent-soft); }

.table .num {
    text-align: right;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* ── Urgency uses DOTS, not badges, so status and urgency never look alike
      (invariant 13). ─────────────────────────────────────────────────────── */

.dot {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    flex: none;
}

.dot--overdue { background: var(--danger); }
.dot--due-soon { background: var(--warning); }
.dot--ok { background: var(--success); }

/* ── Empty state ─────────────────────────────────────────────────────────── */

.empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-s);
    padding: var(--space-xxl) var(--space-l);
    text-align: center;
}

.empty__headline {
    font-size: var(--fs-body);
    font-weight: var(--fw-medium);
    color: var(--ink-2);
}

.empty__body {
    font-size: var(--fs-note);
    color: var(--ink-3);
    max-width: 48ch;
}

/* ── App shell ───────────────────────────────────────────────────────────── */

.shell {
    display: grid;
    grid-template-columns: var(--sidebar-w) 1fr;
    min-height: 100vh;
}

/* No sidebar. Sign in, change password, signed out, access denied — every page a
   person sees before, or instead of, being signed in. The navigation is absent on
   purpose: it counts open RFQs and unreviewed mail, which are real numbers about
   real work and none of an unauthenticated visitor's business. */
.shell--bare { grid-template-columns: 1fr; }

/* ── Signed-out pages ────────────────────────────────────────────────────── */

.signin {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-l);
    padding: var(--space-xl);
}

.signin__brand {
    display: flex;
    align-items: center;
    gap: var(--space-s);
}

/* The one full-strength accent on the page (law 1), spent on the brand mark —
   which leaves the Sign in button the second, and nothing else competing. */
/* Width is by content, not fixed: the mark holds a word now, not one letter.
   Height stays on the chip step so it still sits as a chip beside the name. */
.signin__mark {
    padding-inline: var(--space-s);
    height: var(--control-h-chip);
    border-radius: var(--radius-sm);
    background: var(--accent);
    color: var(--on-accent);
    display: grid;
    place-items: center;
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    flex: none;
}

.signin__name {
    font-size: var(--fs-lead);
    font-weight: var(--fw-semibold);
    color: var(--ink);
}

/* Capped, not fluid. Grids fill the screen because a wide table is more useful
   wide; a login form is not. Two fields stretched across a 1920px monitor look
   like a mistake, and the eye has further to travel between label and input. */
.signin__card {
    width: 100%;
    max-width: 380px;
}

.signin__title {
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    color: var(--ink);
    margin-bottom: var(--space-m);
}

.signin__submit {
    width: 100%;
    justify-content: center;
    margin-top: var(--space-s);
}

.signin__help {
    margin-top: var(--space-m);
    font-size: var(--fs-note);
    color: var(--ink-3);
    line-height: 1.5;
}

/* ── Form fields ─────────────────────────────────────────────────────────── */

.field {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
}

.field__label {
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--ink-body);
}

.field__optional {
    font-weight: var(--fw-regular);
    color: var(--ink-4);
    text-transform: none;
}

.field__hint {
    font-size: var(--fs-note);
    color: var(--ink-3);
}

/* Danger is the right colour here and almost nowhere else: this is the one place
   the message means "you must fix this before anything happens". */
.field__error {
    font-size: var(--fs-note);
    color: var(--danger);
}

/* ── A field whose control is a tick box ─────────────────────────────────── */

/* No .field__label above the control: the label sits BESIDE a checkbox, and a
   heading over a single tick box reads as a section with one item in it. */
.field--check { gap: var(--space-xs); }

/* THE WHOLE ROW IS THE <label>, so the text is part of the hit target rather
   than decoration next to it. A 16px box is a small thing to hit accurately
   with gloves on, and this turns it into a full-width one. */
.check {
    display: flex;
    align-items: center;
    gap: var(--space-s);
    cursor: pointer;
    user-select: none;
}

.check__label {
    font-size: var(--fs-sm);
    color: var(--ink-body);
}

/* A password on screen, shown once, to be read aloud or copied down. Monospace
   and spaced out because the whole point is that it is transcribed correctly. */
.temp-password {
    font-family: var(--font-mono);
    font-size: var(--fs-base);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    padding: 2px var(--space-xs);
    border-radius: var(--radius-sm);
    background: var(--surface);
    border: 1px solid var(--line-strong);
    user-select: all;
}

.sidebar {
    background: var(--surface);
    border-right: 1px solid var(--line);
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    height: 100vh;
}

.sidebar__brand {
    display: flex;
    align-items: center;
    gap: var(--space-s);
    height: var(--topbar-h);
    padding: 0 var(--space-l);
    border-bottom: 1px solid var(--line);
    flex: none;
}

.sidebar__mark {
    width: 22px;
    height: 22px;
    border-radius: var(--radius-sm);
    background: var(--accent);
    color: var(--on-accent);
    display: grid;
    place-items: center;
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    flex: none;
}

.sidebar__name {
    font-size: var(--fs-lead);
    font-weight: var(--fw-semibold);
    color: var(--ink);
    letter-spacing: -0.01em;
}

.sidebar__nav {
    padding: var(--space-m) var(--space-s);
    display: flex;
    flex-direction: column;
    gap: 2px;
    overflow-y: auto;
    flex: 1;
}

.sidebar__group {
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--ink-4);
    padding: var(--space-m) var(--space-s) var(--space-xs);
}

.navitem {
    display: flex;
    align-items: center;
    gap: var(--space-s);
    height: var(--control-h);
    padding: 0 var(--space-s);
    border-radius: var(--radius);
    color: var(--ink-quiet);
    text-decoration: none;
    font-size: var(--fs-base);
    font-weight: var(--fw-medium);
    transition: background var(--dur-fast) var(--ease-in-out),
                color var(--dur-fast) var(--ease-in-out);
    /* Reserve the rail so the label does not shift by 3px when active. */
    border-left: 3px solid transparent;
}

.navitem:hover {
    background: var(--surface-hover);
    color: var(--ink-body);
}

/* THE ACTIVE ITEM WEARS INK, NOT ACCENT. It is a fact about where you are, not
   an action (law 2, invariant 9). The accent on this view is spent on the
   primary button. */
.navitem.active {
    background: var(--surface-active);
    color: var(--ink);
    border-left-color: var(--ink);
}

.navitem__count {
    margin-left: auto;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xs);
    color: var(--ink-3);
}

.sidebar__footer {
    padding: var(--space-m);
    border-top: 1px solid var(--line);
    flex: none;
}

.integration {
    display: flex;
    align-items: center;
    gap: var(--space-s);
    font-size: var(--fs-note);
    color: var(--ink-3);
    padding: var(--space-xs) 0;
}

/* ── Signed-in account ───────────────────────────────────────────────────── */

/* Sits under the integration dots, separated by a rule rather than by spacing
   alone: "who am I" and "what is connected" are different questions, and a gap
   large enough to say so would push the block off a short viewport. */
.account {
    margin-top: var(--space-s);
    padding-top: var(--space-s);
    border-top: 1px solid var(--line);
}

.account__name {
    font-size: var(--fs-note);
    font-weight: 600;
    color: var(--ink-1);
    /* One line. A long display name must not reflow the sidebar. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.account__meta {
    font-size: var(--fs-note);
    color: var(--ink-3);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* A LINK, NOT A BUTTON, visually. Signing out is a real action but never the
   action anyone came to this page for, and the accent budget (law 1) is already
   spent on the brand mark and the page's own primary button. */
.account__signout {
    display: inline-block;
    margin-top: var(--space-xs);
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-size: var(--fs-note);
    color: var(--ink-3);
    text-decoration: underline;
    cursor: pointer;
}

.account__signout:hover {
    color: var(--ink-1);
}

.main {
    min-width: 0;
    display: flex;
    flex-direction: column;
}

/* FLUID, NOT CAPPED. Grids fill whatever width the screen gives them. This was
   max-width: 1280px, which parked a 1920px monitor's last 600px as dead space
   next to a table that was scrolling its own columns. */
.page {
    padding: var(--space-xl);
    width: 100%;
    max-width: 100%;
    min-width: 0;
}

/* ── Settings: a second nav column inside the ordinary shell ─────────────── */

/* A GRID, NOT A FLEX ROW, so `minmax(0, 1fr)` can do its one job: let the body
   column shrink below the intrinsic width of a wide table inside it. Without it
   a settings table with long values pushes the whole page horizontally and the
   sub-nav slides off screen — the failure .table-wrap exists to prevent. */
.settings {
    display: grid;
    grid-template-columns: 200px minmax(0, 1fr);
    align-items: start;
}

.settings__nav {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--space-xl) 0 var(--space-xl) var(--space-l);
    position: sticky;
    top: 0;
}

/* Narrower and quieter than .navitem. This is navigation WITHIN a page; making
   it look like the main sidebar would set up a competition between the two and
   leave no way to tell at a glance which level you were moving through. */
.settings__link {
    display: flex;
    align-items: center;
    height: var(--control-h-chip);
    padding: 0 var(--space-s);
    border-radius: var(--radius-sm);
    color: var(--ink-quiet);
    text-decoration: none;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    transition: background var(--dur-fast) var(--ease-in-out),
                color var(--dur-fast) var(--ease-in-out);
}

.settings__link:hover { background: var(--surface-hover); color: var(--ink-body); }

/* Ink, not accent — where you are is a fact, not an action. Same rule the main
   sidebar follows, minus the rail, which would read as a second hierarchy. */
.settings__link.active {
    background: var(--surface-active);
    color: var(--ink);
}

.settings__group {
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--ink-4);
    padding: var(--space-m) var(--space-s) var(--space-xs);
}

.settings__body { min-width: 0; }

/* A read-only label/value pair. NOT a table: these are unrelated facts that
   happen to be listed together, and a table promises columns that mean
   something down their length.

   The value is pushed right so a column of them lines up down the card — which
   is what makes five policy numbers scannable rather than five sentences. */
.factrow {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-l);
    font-size: var(--fs-sm);
}

.factrow__label { color: var(--ink-3); }

.factrow__value { color: var(--ink); text-align: right; }

/* One column below the breakpoint the main shell already uses: the sub-nav
   becomes a horizontal strip above the content rather than a column beside it. */
@media (max-width: 900px) {
    .settings { grid-template-columns: 1fr; }

    .settings__nav {
        flex-direction: row;
        flex-wrap: wrap;
        position: static;
        padding: var(--space-l) var(--space-xl) 0;
    }

    /* The group headings are a column affordance. In a wrapped row they read as
       stray words between links. */
    .settings__group { display: none; }
}

.page__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-l);
    margin-bottom: var(--space-xl);
    flex-wrap: wrap;
}

.page__actions {
    display: flex;
    align-items: center;
    gap: var(--space-s);
}

.stack { display: flex; flex-direction: column; gap: var(--space-l); }
.row { display: flex; align-items: center; gap: var(--space-s); }
.row--wrap { flex-wrap: wrap; }
.spacer { flex: 1; }

/* ── Filter chips — selection wears ink ──────────────────────────────────── */

.chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    height: var(--control-h-chip);
    padding: 0 var(--space-s);
    border-radius: var(--radius);
    border: 1px solid var(--line);
    background: var(--surface);
    color: var(--ink-quiet);
    font-size: var(--fs-note);
    font-weight: var(--fw-medium);
    cursor: pointer;
    transition: background var(--dur-base) var(--ease-in-out),
                border-color var(--dur-base) var(--ease-in-out),
                color var(--dur-base) var(--ease-in-out);
}

.chip:hover { background: var(--surface-hover); }

/* Active filter chip wears INK. This is the mistake law 2 catches most often:
   an active chip is a fact about the list, not an action. */
.chip.active {
    background: var(--ink);
    border-color: var(--ink);
    color: var(--on-ink);
}

.chip__count {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xs);
    opacity: 0.75;
}

/* ── Tabs — the active tab wears INK, never accent ───────────────────────── */

.tabs {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    border-bottom: 1px solid var(--line);
    overflow-x: auto;
    /* REQUIRED, not tidying. `overflow-x: auto` makes overflow-y `auto` too, and
       the -1px margin below pushes content 1px past the box — enough for Chrome
       to draw a vertical scrollbar and steal 15px off the right of the strip. */
    overflow-y: hidden;
}

.tab {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    height: var(--control-h-lg);
    padding: 0 var(--space-m);
    border: none;
    background: transparent;
    color: var(--ink-3);
    font-size: var(--fs-base);
    font-weight: var(--fw-medium);
    cursor: pointer;
    white-space: nowrap;
    /* Reserve the underline so the label does not shift 2px when selected. */
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    transition: color var(--dur-base) var(--ease-in-out),
                background var(--dur-base) var(--ease-in-out);
}

.tab:hover { background: var(--surface-hover); color: var(--ink-body); }

.tab.active {
    color: var(--ink);
    border-bottom-color: var(--ink);
}

.tab__count {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xs);
    color: var(--ink-4);
}

.tab.active .tab__count { color: var(--ink-3); }

/* ── Detail header ───────────────────────────────────────────────────────── */

.page-title__sep {
    color: var(--ink-4);
    margin: 0 var(--space-xs);
}

.factgrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: var(--space-l);
}

.fact { min-width: 0; }

.fact > div:last-child {
    margin-top: var(--space-xs);
    color: var(--ink-body);
}

/* ── Message thread ──────────────────────────────────────────────────────── */

.message {
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: var(--space-m);
    background: var(--surface);
    /* 3px rail distinguishes our replies from theirs at a glance. Wayfinding,
       not status — inbound and outbound are directions, not conditions. */
    border-left: 3px solid var(--rail-blue);
}

.message--out { border-left-color: var(--rail-teal); }

.message__head {
    display: flex;
    align-items: center;
    gap: var(--space-s);
    flex-wrap: wrap;
    margin-bottom: var(--space-s);
}

.message__subject {
    font-weight: var(--fw-medium);
    color: var(--ink);
    margin-bottom: var(--space-xs);
}

.message__body {
    color: var(--ink-body);
    font-size: var(--fs-note);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* ── Notes ───────────────────────────────────────────────────────────────── */

.note-item {
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: var(--space-m);
    background: var(--surface);
    /* Grey rail. A note is a human aside, not a status — there is deliberately
       no semantic hue here. */
    border-left: 3px solid var(--rail-grey);
}

.note-item__head {
    display: flex;
    align-items: baseline;
    gap: var(--space-s);
    flex-wrap: wrap;
    margin-bottom: var(--space-xs);
}

.note-item__author {
    font-weight: var(--fw-medium);
    color: var(--ink);
    font-size: var(--fs-note);
}

.note-item__body {
    color: var(--ink-body);
    /* Line breaks the author typed are content, not markup. pre-wrap keeps them
       without ever letting the text be parsed as HTML. */
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.note-compose {
    display: flex;
    flex-direction: column;
    gap: var(--space-s);
    align-items: flex-start;
}

.note-compose__actions {
    display: flex;
    align-items: center;
    gap: var(--space-m);
}

/* ── Compose ─────────────────────────────────────────────────────────────── */

.compose {
    display: flex;
    flex-direction: column;
    gap: var(--space-m);
    padding: var(--space-m);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface-2);
}

.compose__field {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
}

/* An input sized to its content rather than the row — for the tone select,
   which would otherwise stretch across the whole composer. */
.input--auto { width: auto; min-width: 160px; }

/* A model suggestion, marked as such. --violet is "special, never status":
   this is neither a condition nor an action, it is a different KIND of
   content, which is exactly the job violet has in the system. */
.suggestion {
    border-left: 3px solid var(--violet);
    background: var(--surface);
}

/* ── Activity feed ───────────────────────────────────────────────────────── */

.feed {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-m);
}

.feed__item {
    display: grid;
    grid-template-columns: 140px 1fr;
    gap: var(--space-m);
    align-items: start;
}

.feed__time { white-space: nowrap; }

@media (max-width: 560px) {
    .feed__item { grid-template-columns: 1fr; gap: var(--space-xs); }
}

/* ── Clickable rows ──────────────────────────────────────────────────────── */

/* The row navigates on click as a mouse convenience. It is NOT the only way
   in — the identifier is a real anchor, so keyboard and screen-reader users
   have a focusable, announced target. A row-click with no link inside is
   unreachable without a mouse. */
.table tbody tr.is-linked { cursor: pointer; }

.link-id {
    color: var(--ink);
    text-decoration: none;
    border-bottom: 1px solid transparent;
    transition: border-color var(--dur-base) var(--ease-in-out);
}

.link-id:hover { border-bottom-color: var(--ink); }

/* ── Banners ─────────────────────────────────────────────────────────────── */

.banner {
    display: flex;
    align-items: flex-start;
    gap: var(--space-s);
    padding: var(--space-m);
    border-radius: var(--radius-lg);
    border: 1px solid transparent;
    font-size: var(--fs-note);
}

.banner--warning { background: var(--warning-soft); color: var(--warning-deep); border-color: var(--warning-line); }
.banner--danger  { background: var(--danger-soft);  color: var(--danger-deep);  border-color: var(--danger-line); }
.banner--info    { background: var(--info-soft);    color: var(--info-deep);    border-color: var(--info-line); }

/* SPARINGLY. Green here means "the thing you came to do is done" — the password is
   changed, go and sign in — and it earns its colour only because the page has
   nothing else on it. A success banner over a form somebody is still filling in
   reads as approval of the form, not of a finished action. */
.banner--success { background: var(--success-soft); color: var(--success-deep); border-color: var(--success-line); }

/* ── Links ───────────────────────────────────────────────────────────────── */

.link {
    color: var(--ink);
    text-decoration: none;
    border-bottom: 1px solid var(--line-strong);
    transition: border-color var(--dur-base) var(--ease-in-out);
}

.link:hover { border-bottom-color: var(--ink); }

/* ── Blazor framework surfaces ───────────────────────────────────────────── */

.valid.modified:not([type=checkbox]) { outline: 1px solid var(--success); }
.invalid { outline: 1px solid var(--danger); }
.validation-message { color: var(--danger-deep); }

/* The error boundary is the system voice: one charcoal register, raised
   surface in both themes. */
.blazor-error-boundary {
    background: var(--voice);
    color: var(--voice-text);
    padding: var(--space-m) var(--space-l);
    border-radius: var(--radius-lg);
    font-size: var(--fs-note);
}

.blazor-error-boundary::after { content: "An error has occurred."; }

/* ── Responsive ──────────────────────────────────────────────────────────── */

@media (max-width: 860px) {
    .shell { grid-template-columns: 1fr; }

    .sidebar {
        position: static;
        height: auto;
        border-right: none;
        border-bottom: 1px solid var(--line);
    }

    .sidebar__nav {
        flex-direction: row;
        flex-wrap: wrap;
        overflow-x: auto;
    }

    /* The integration dots go; the ACCOUNT STAYS. Hiding the whole footer was
       fine when it held nothing but status, but it now holds the only way to
       sign out — and a session you cannot end on a phone is a session that gets
       left open on a phone. */
    .sidebar__group,
    .integration { display: none; }

    .sidebar__footer {
        padding: var(--space-s) var(--space-m);
        border-top: 0;
    }

    .account {
        margin-top: 0;
        padding-top: 0;
        border-top: 0;
        display: flex;
        align-items: baseline;
        gap: var(--space-s);
    }

    .account__meta { display: none; }

    .account__signout { margin-top: 0; }

    .page { padding: var(--space-l); }
}

/* ── Filter verdict row ──────────────────────────────────────────────────── */

.verdict {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    padding: var(--space-s) 0;
    /* Violet: a model opinion is a different KIND of content, not a status. */
    border-left: 3px solid var(--violet);
    padding-left: var(--space-m);
    white-space: normal;
}

/* ── Select ──────────────────────────────────────────────────────────────── */

/* Its own rule rather than being folded into `.input`. A select is not a place
   to type — it has no placeholder, no caret, and a native arrow that needs room
   on the right — so sharing a selector would mean unpicking those differences
   from inside a shared block. Everything a person can operate still gets the
   same one focus ring (invariant 12). */
.select {
    height: var(--control-h);
    max-width: 100%;
    background: var(--surface);
    color: var(--ink-body);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    padding: 0 var(--space-s);
    font-size: var(--fs-base);
    font-family: var(--font-sans);
    transition: border-color var(--dur-base) var(--ease-in-out),
                background var(--dur-base) var(--ease-in-out);
}

.select:hover { border-color: var(--ink-4); }

.select:focus-visible {
    border-color: var(--ink-3);
    box-shadow: 0 0 0 3px var(--ring);
    outline: 2px solid transparent;
}

.select:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    background: var(--surface-2);
}

/* ── Console — a rail of things, and the one you picked ──────────────────── */

/* THE APPLICATION'S ONLY TWO-PANE LAYOUT, and it is written as a named pattern
   rather than as one page's private markup, because the second page that wants
   it is where an unnamed layout gets copied and diverges.

   It is for a SMALL, NAMED set you switch between and edit in place — roles
   today. It is not a list page: a list is scanned and filtered, and that belongs
   in `.table`. If the rail ever needs a search box, the thing in it stopped
   being a rail. */
.console {
    display: grid;
    grid-template-columns: var(--console-rail-w) 1fr;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-1);
    overflow: hidden;
}

.console__rail {
    border-right: 1px solid var(--line);
    background: var(--surface-2);
    padding: var(--space-s);
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.console__railhead {
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--ink-3);
    padding: var(--space-s) var(--space-s) var(--space-xs);
}

.railitem {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-s);
    width: 100%;
    padding: var(--space-s) var(--space-m);
    border: none;
    border-radius: var(--radius);
    background: transparent;
    color: var(--ink-body);
    font-family: var(--font-sans);
    font-size: var(--fs-base);
    text-align: left;
    cursor: pointer;
    transition: background var(--dur-base) var(--ease-in-out);
}

.railitem:hover { background: var(--surface-hover); }

/* WHERE YOU ARE IS A FACT, NOT AN ACTION, so the selected item wears ink
   (law 2, invariant 9) — the same call the sidebar's active nav item makes. */
.railitem.is-on {
    background: var(--ink);
    color: var(--on-ink);
    font-weight: var(--fw-medium);
}

.railitem__name {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}

/* THE COUNTS ARE WHY THE RAIL WORKS. A two-pane layout hides everything you are
   not looking at, so without these the rail is a list of names and you have to
   visit a role to learn the first thing about it. */
.railitem__sub {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xs);
    color: var(--ink-4);
}

.railitem.is-on .railitem__sub { color: var(--ink-5); }

.console__body {
    padding: var(--space-l);
    display: flex;
    flex-direction: column;
    gap: var(--space-xl);
    min-width: 0;
}

.console__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-m);
    flex-wrap: wrap;
}

.console__titlewrap {
    display: flex;
    align-items: center;
    gap: var(--space-s);
    flex-wrap: wrap;
}

.console__actions { display: flex; gap: var(--space-s); }

/* The rail TURNS rather than collapsing into a dropdown. A dropdown would hide
   the counts above, which are the only reason you can choose without visiting. */
@media (max-width: 820px) {
    .console { grid-template-columns: 1fr; }

    .console__rail {
        flex-direction: row;
        overflow-x: auto;
        border-right: none;
        border-bottom: 1px solid var(--line);
        gap: var(--space-xs);
    }

    .console__railhead { display: none; }

    .railitem { width: auto; white-space: nowrap; }
}

/* ── Checklist — the set of things one thing may do ──────────────────────── */

.checklist {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
}

.checkrow {
    display: flex;
    align-items: flex-start;
    gap: var(--space-m);
    padding: var(--space-m);
    border-bottom: 1px solid var(--line-2);
    cursor: pointer;
    transition: background var(--dur-base) var(--ease-in-out);
}

.checkrow:last-child { border-bottom: none; }
.checkrow:hover { background: var(--surface-hover); }

/* A row whose box is dead must not offer a hover, same as the control itself
   (invariant 6). The label is not the disabled element, so it needs saying. */
.checkrow:has(.tick:disabled) { cursor: not-allowed; }
.checkrow:has(.tick:disabled):hover { background: transparent; }

.checkrow__text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.checkrow__name {
    font-weight: var(--fw-medium);
    color: var(--ink);
}

.checkrow__why {
    font-size: var(--fs-note);
    color: var(--ink-3);
}

/* WHO ELSE GRANTS THIS — the one thing a full grid gives away for free and a
   two-pane layout cannot. Without it, "am I about to remove the last route to
   this?" is unanswerable from the screen you are standing on. */
.checkrow__also {
    font-size: var(--fs-xs);
    color: var(--ink-4);
    margin-top: 2px;
}

.checkrow__also strong {
    color: var(--ink-3);
    font-weight: var(--fw-medium);
}

/* Granted by nobody. --warning, not --danger: nothing has failed, a human has
   to decide. */
.checkrow__also--none {
    color: var(--warning-deep);
    font-weight: var(--fw-medium);
}

.checkrow__key {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--ink-4);
    margin-top: 2px;
}

/* A TICKED BOX IS A FACT ABOUT THE ROW, NOT AN ACTION, so it wears ink
   (law 2, invariant 9). This is the rule broken most often, and here the browser
   breaks it for you: the default `accent-color` is its own blue. */
.tick {
    width: 16px;
    height: 16px;
    margin: 2px 0 0;
    flex: none;
    accent-color: var(--ink);
    cursor: pointer;
}

.tick:focus-visible {
    box-shadow: 0 0 0 3px var(--ring);
    border-radius: var(--radius-sm);
    outline: 2px solid transparent;
}

.tick:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* ── Dialogs ─────────────────────────────────────────────────────────────── */

/* Built on the native <dialog>, so the browser owns the modal semantics that are
   easy to get wrong by hand: the top layer, inert background content, focus
   contained, and Escape. The component still has to move focus deliberately and
   return it to the trigger — see the accessibility contract in
   docs/STYLE_GUIDE.md, none of which CSS can do.

   ::backdrop IS THE SCRIM, which is why there is no .scrim class here. A
   hand-rolled overlay div sits in the normal stacking context and loses to
   whatever z-index somebody adds later. */
.dialog {
    width: min(520px, calc(100vw - var(--space-xl) * 2));
    max-height: calc(100vh - var(--space-xxl) * 2);
    padding: 0;
    border: none;
    border-radius: var(--radius-xl);
    background: var(--surface);
    color: var(--ink-body);
    /* The heaviest step on the ladder. The strongest interruption dominates. */
    box-shadow: var(--shadow-3);
}

.dialog::backdrop { background: var(--scrim); }

.dialog__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-m);
    padding: var(--space-l) var(--space-l) var(--space-m);
}

.dialog__title {
    font-size: var(--fs-body);
    font-weight: var(--fw-medium);
    color: var(--ink);
}

.dialog__sub {
    font-size: var(--fs-note);
    color: var(--ink-3);
    margin-top: var(--space-xs);
}

/* THE SCRIM IS NEVER THE ONLY WAY OUT. Clicking outside is a mouse convenience
   and is invisible to everyone else, so every dialog carries this. */
.dialog__close {
    flex: none;
    width: var(--control-h-sm);
    height: var(--control-h-sm);
    border: none;
    border-radius: var(--radius);
    background: transparent;
    color: var(--ink-4);
    font-size: var(--fs-md);
    cursor: pointer;
    transition: background var(--dur-base) var(--ease-in-out);
}

.dialog__close:hover { background: var(--surface-hover); color: var(--ink-2); }

.dialog__body {
    padding: 0 var(--space-l) var(--space-l);
    display: flex;
    flex-direction: column;
    gap: var(--space-m);
    overflow-y: auto;
}

.dialog__foot {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-s);
    padding: var(--space-m) var(--space-l);
    border-top: 1px solid var(--line);
}

/* Itemized consequences. A destructive confirm names what happens rather than
   asking whether you are sure. */
.consequences {
    margin: 0;
    padding-left: 18px;
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    font-size: var(--fs-base);
}

/* RED LIVES IN THE WORDS (invariant 10), because the button beside it is
   charcoal and never red. */
.irreversible {
    color: var(--danger-deep);
    font-weight: var(--fw-medium);
}

/* ── Banner with an action in it ─────────────────────────────────────────── */

/* A warning that carries its own fix. Used where the thing being reported is
   both unmissable and one control away from resolved — a permission no role
   grants — so that noticing it and fixing it are not two screens apart. */
.banner__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-s);
    min-width: 0;
}

.banner__act {
    display: flex;
    align-items: center;
    gap: var(--space-s);
    flex-wrap: wrap;
}

/* ═══════════════════════════════════════════════════════════════════════════
   PERMISSIONS PAGE

   Ported from FlowMES's /admin/permissions (ticket 1093) and retuned to this
   application's tokens and laws. Page-scoped under `perm__` where the thing is
   local; anything reusable earned a house class instead.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Input group — an input with a leading icon ──────────────────────────── */

.input-group { position: relative; display: flex; align-items: center; width: 100%; }

.input-group__icon {
    position: absolute;
    left: var(--space-s);
    display: flex;
    color: var(--ink-4);
    pointer-events: none;
}

.input-group .input { padding-left: 30px; }

/* ── Combobox — a searchable picker ──────────────────────────────────────── */

/* REPLACES <select> ON EVERY SUBJECT PICKER. A native select offers exactly one
   search: first-letter type-ahead against the start of the label. That is fine
   for four roles and useless for three hundred users, and the picker is the
   control this page is navigated by. */
.combobox { position: relative; }

.combobox__list {
    position: absolute;
    z-index: 40;
    top: calc(100% + var(--space-xs));
    left: 0;
    right: 0;
    max-height: 280px;
    overflow-y: auto;
    margin: 0;
    padding: var(--space-xs);
    list-style: none;
    background: var(--surface);
    border: 1px solid var(--line);
    /* 8px means "this floats" — the radius ladder is semantic. */
    border-radius: var(--radius-float);
    box-shadow: var(--shadow-menu);
}

.combobox__option {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-s);
    width: 100%;
    padding: var(--space-s);
    border: none;
    border-radius: var(--radius);
    background: transparent;
    color: var(--ink-body);
    font-family: var(--font-sans);
    font-size: var(--fs-note);
    text-align: left;
    cursor: pointer;
}

.combobox__option:hover { background: var(--surface-hover); }

/* The KEYBOARD CURSOR, not the held value. The held value is shown in the field
   itself; this is where Enter would land. One of the two sanctioned tint-accent
   uses — a cursor is neither a selection nor an action. */
.combobox__option.is-active { background: var(--accent-tint); color: var(--accent-ink); }

.combobox__match { font-weight: var(--fw-semibold); color: var(--ink); }
.combobox__option.is-active .combobox__match { color: var(--accent-ink-2); }

.combobox__meta {
    flex: none;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--ink-4);
}

.combobox__empty {
    padding: var(--space-s);
    font-size: var(--fs-note);
    color: var(--ink-3);
}

/* Outside-click dismissal as a transparent layer UNDER the list, not a document
   listener: a JS handler outlives the component if disposal is ever missed, and
   this cannot. */
.combo-scrim {
    position: fixed;
    inset: 0;
    z-index: 30;
}

/* ── Toggle switch ───────────────────────────────────────────────────────── */

/* A BUTTON, NOT A CHECKBOX, because it must stay hoverable when it is locked.
   `disabled` kills pointer events, and the tooltip naming the grantor permission
   required is the entire value of a locked row — grey it out silently and the
   user learns nothing except that they are stuck. */
.toggle {
    position: relative;
    flex: none;
    width: 34px;
    height: 20px;
    padding: 0;
    border: 1px solid var(--line-strong);
    border-radius: 999px;
    background: var(--surface-2);
    cursor: pointer;
    transition: background var(--dur-base) var(--ease-in-out),
                border-color var(--dur-base) var(--ease-in-out);
}

.toggle::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 14px;
    height: 14px;
    border-radius: 999px;
    background: var(--ink-4);
    transition: transform var(--dur-base) var(--ease-out),
                background var(--dur-base) var(--ease-in-out);
}

.toggle:hover { background: var(--surface-hover); }

/* ON WEARS INK. Whether a role holds a permission is a fact about the role, not
   an action (law 2, invariant 9). */
.toggle.is-on {
    background: var(--ink);
    border-color: var(--ink);
}

.toggle.is-on::after {
    background: var(--on-ink);
    transform: translateX(14px);
}

.toggle:focus-visible {
    outline: 2px solid transparent;
    box-shadow: 0 0 0 3px var(--ring);
}

/* Locked, not disabled — see above. Reads as unavailable without going dead. */
.toggle.is-locked {
    opacity: 0.55;
    cursor: not-allowed;
    background: repeating-linear-gradient(
        45deg,
        var(--surface-2),
        var(--surface-2) 3px,
        var(--surface-hover) 3px,
        var(--surface-hover) 6px);
}

.toggle.is-locked.is-on { background: var(--ink-4); border-color: var(--ink-4); }

/* ── Checkbox with a partial state ───────────────────────────────────────── */

.checkbox {
    width: 16px;
    height: 16px;
    margin: 0;
    flex: none;
    accent-color: var(--ink);
    cursor: pointer;
}

.checkbox:focus-visible {
    outline: 2px solid transparent;
    box-shadow: 0 0 0 3px var(--ring);
    border-radius: var(--radius-sm);
}

.checkbox:disabled { opacity: 0.5; cursor: not-allowed; }

/* The DOM `indeterminate` property cannot be set from Razor — it is not an
   attribute — so the partial state is carried in markup and painted here. */
.checkbox.is-indeterminate {
    appearance: none;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    background: var(--surface);
    position: relative;
}

.checkbox.is-indeterminate::after {
    content: "";
    position: absolute;
    left: 3px;
    right: 3px;
    top: 50%;
    height: 2px;
    transform: translateY(-50%);
    border-radius: 1px;
    background: var(--ink);
}

/* ── Category swatches ───────────────────────────────────────────────────── */

.swatch {
    width: 8px;
    height: 8px;
    border-radius: var(--radius-sm);
    flex: none;
}

/* Semantic by MEANING, not by hue-picking: Security is the one that blocks work
   if it is wrong, Commercial is special-not-status (violet, per the guide). */
.swatch--security { background: var(--danger); }
.swatch--commercial { background: var(--violet); }
.swatch--none { background: var(--ink-5); }

/* ── Page furniture ──────────────────────────────────────────────────────── */

.perm__title-row { display: flex; align-items: baseline; gap: var(--space-m); flex-wrap: wrap; }

.perm__meta { font-size: var(--fs-note); color: var(--ink-3); }

.perm__spacer { flex: 1 1 auto; }

.subject-bar {
    display: flex;
    align-items: flex-end;
    gap: var(--space-m);
    flex-wrap: wrap;
    padding: var(--space-m) var(--space-l);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-1);
}

.subject-bar .field { flex: 0 1 320px; min-width: 200px; }

.perm__cat-summary { display: flex; align-items: center; gap: var(--space-s); flex-wrap: wrap; }

/* Permissions on the left, members on the right. Collapses at the same
   breakpoint the console does, so the page has one responsive story. */
.split-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: var(--space-l);
    align-items: start;
}

@media (max-width: 1100px) {
    .split-row { grid-template-columns: 1fr; }
}

.panel {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-1);
}

.panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-m);
    padding: var(--space-m) var(--space-l);
    border-bottom: 1px solid var(--line);
}

.panel-head h3 {
    margin: 0;
    font-size: var(--fs-lead);
    font-weight: var(--fw-semibold);
    color: var(--ink);
}

.perm__head-actions { display: flex; align-items: center; gap: var(--space-s); }

.toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-m);
    flex-wrap: wrap;
    padding: var(--space-m) var(--space-l);
    border-bottom: 1px solid var(--line);
}

.toolbar__spacer { flex: 1 1 auto; }

.perm__search { flex: 0 1 240px; min-width: 160px; }

.perm__chips { display: flex; align-items: center; gap: var(--space-xs); flex-wrap: wrap; }

.perm__switch {
    display: inline-flex;
    align-items: center;
    gap: var(--space-s);
    font-size: var(--fs-note);
    color: var(--ink-body);
    cursor: pointer;
}

/* ── Permission rows ─────────────────────────────────────────────────────── */

.perm__grouphead td { background: var(--surface-2); padding: 0 !important; }

.perm__group-head-row {
    display: flex;
    align-items: center;
    gap: var(--space-s);
    padding: var(--space-s) var(--space-m);
}

.perm__group-toggle {
    display: flex;
    align-items: center;
    gap: var(--space-s);
    flex: 1 1 auto;
    padding: 0;
    border: none;
    background: transparent;
    color: var(--ink);
    font-family: var(--font-sans);
    font-size: var(--fs-note);
    font-weight: var(--fw-semibold);
    text-align: left;
    cursor: pointer;
}

.perm__chev {
    width: 14px;
    height: 14px;
    flex: none;
    color: var(--ink-4);
    transition: transform var(--dur-base) var(--ease-out);
}

.perm__chev.is-open { transform: rotate(90deg); }

.perm__group-count {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xs);
    color: var(--ink-4);
    font-weight: var(--fw-regular);
}

.perm__name { font-weight: var(--fw-medium); color: var(--ink); }

/* Clipped, so the full name goes on the title attribute. */
.perm__code {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--ink-4);
    max-width: 38ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.perm__cell--grow { width: 100%; }

.perm__lock {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    font-size: var(--fs-xs);
    color: var(--ink-3);
    white-space: nowrap;
}

.perm__lock-icon { width: 13px; height: 13px; flex: none; }

.perm__lock--open { color: var(--ink-4); }

.pager {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: var(--space-s) var(--space-l);
    border-top: 1px solid var(--line);
}

.pager__info {
    font-size: var(--fs-note);
    color: var(--ink-3);
    font-variant-numeric: tabular-nums;
}

/* ── Members ─────────────────────────────────────────────────────────────── */

.perm__member {
    display: flex;
    align-items: center;
    gap: var(--space-s);
    padding: var(--space-s) var(--space-l);
    border-bottom: 1px solid var(--line-2);
}

.perm__member:last-child { border-bottom: none; }

.perm__who { flex: 1 1 auto; min-width: 0; }

.perm__who-name {
    font-size: var(--fs-base);
    color: var(--ink-body);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.perm__who-sub { font-size: var(--fs-xs); color: var(--ink-4); }

.perm__add-row {
    display: flex;
    align-items: center;
    gap: var(--space-s);
    padding: var(--space-m) var(--space-l);
    border-bottom: 1px solid var(--line-2);
    flex-wrap: wrap;
}

.perm__add-row .combobox { flex: 1 1 160px; }

.avatar {
    display: grid;
    place-items: center;
    flex: none;
    width: var(--control-h-sm);
    height: var(--control-h-sm);
    border-radius: 999px;
    background: var(--surface-2);
    border: 1px solid var(--line);
    color: var(--ink-quiet);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
}

.iconbtn {
    display: grid;
    place-items: center;
    flex: none;
    width: var(--control-h-sm);
    height: var(--control-h-sm);
    padding: 0;
    border: none;
    border-radius: var(--radius);
    background: transparent;
    color: var(--ink-4);
    cursor: pointer;
    transition: background var(--dur-base) var(--ease-in-out),
                color var(--dur-base) var(--ease-in-out);
}

.iconbtn:hover { background: var(--surface-hover); color: var(--ink-2); }

.iconbtn:focus-visible {
    outline: 2px solid transparent;
    box-shadow: 0 0 0 3px var(--ring);
}

.iconbtn:disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; }

/* ── By permission / By user: the source list ────────────────────────────── */

.perm__index-grid {
    display: grid;
    grid-template-columns: 320px minmax(0, 1fr);
    gap: var(--space-l);
    align-items: start;
}

@media (max-width: 1100px) {
    .perm__index-grid { grid-template-columns: 1fr; }
}

.perm__catalog-list { max-height: 560px; overflow-y: auto; }

.perm__catalog-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-s);
    width: 100%;
    padding: var(--space-s) var(--space-l);
    border: none;
    border-bottom: 1px solid var(--line-2);
    background: transparent;
    color: var(--ink-body);
    font-family: var(--font-sans);
    font-size: var(--fs-base);
    text-align: left;
    cursor: pointer;
    transition: background var(--dur-base) var(--ease-in-out);
}

.perm__catalog-item:hover { background: var(--surface-hover); }

/* The label and its code stack. Both are spans so the row can stay a <button>;
   without this they run together on one line and the code looks like part of
   the name. */
.perm__catalog-text {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}

/* Where you are is a fact. Ink, not accent. */
.perm__catalog-item.is-on { background: var(--ink); color: var(--on-ink); }

/* BOTH CHILDREN MUST GIVE UP THEIR OWN COLOR HERE. `.perm__name` is
   `var(--ink)`, which on an ink fill is ink on ink — the label simply
   disappears and the row renders as a bare code. Inheriting is what makes the
   selected row legible; it is not tidying. */
.perm__catalog-item.is-on .perm__name { color: inherit; }
.perm__catalog-item.is-on .perm__code { color: var(--ink-5); }

/* A source is WHERE a permission came from — a role, or a grant on the person.
   Rendered as a set, never collapsed: revoking one route when another still
   carries it changes nothing, and a single "source" string hides that. */
.perm__source {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    height: var(--control-h-chip);
    padding: 0 var(--space-s);
    border-radius: var(--radius);
    border: 1px solid var(--line-strong);
    background: var(--surface);
    font-size: var(--fs-note);
    color: var(--ink-body);
}

.perm__sources { display: flex; gap: var(--space-xs); flex-wrap: wrap; }

.perm__form-stack { display: flex; flex-direction: column; gap: var(--space-l); }

.perm__foot-danger { margin-right: auto; }

.perm__textarea { min-height: 72px; padding: var(--space-s); resize: vertical; }

/* ── Title row — a heading with badges beside it ─────────────────────────── */

/* A heading and the facts about it share a baseline. Separate from
   `.console__titlewrap`, which is the console's own; this one is general and any
   card header may use it. */
.titlerow {
    display: flex;
    align-items: center;
    gap: var(--space-s);
    flex-wrap: wrap;
}

/* ── Code block — a command to copy ──────────────────────────────────────── */

/* FOR COMMANDS THE READER IS MEANT TO RUN, not for output and not for code
   samples. Mono with tabular figures like every other identifier in the system
   (invariant 14), and it scrolls sideways inside its own box rather than letting
   a long URL widen the page. */
.codeblock {
    margin: var(--space-s) 0 0;
    padding: var(--space-m);
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow-x: auto;
}

.codeblock code {
    font-family: var(--font-mono);
    font-size: var(--fs-note);
    color: var(--ink-body);
    white-space: pre;
}

/* ── A checkrow standing on its own ──────────────────────────────────────── */

/* `.checkrow` is a member of a `.checklist` and borrows that container's frame.
   One on its own has nothing to sit in, so it brings its own — rather than an
   inline style, which invariant 2 forbids for anything token-backed. */
.checkrow--boxed {
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

.checkrow--boxed:last-child { border-bottom: 1px solid var(--line); }
