/* ============================================================
   GovOps - styles

   Imeritas Graphite, light ground. The tokens, shell, table, badge and button rules began as a copy of the
   Imeritas Agent host's wwwroot/css/site.css so the two read as one product, and the class names still
   match it. **The palette and type no longer do**, deliberately: the host is on the older blue-and-Inter
   system, and GovOps follows the Graphite mockups in docs/ui/ instead. The two read as one product again
   only once the host moves as well. That divergence is a decision, not a drift to correct.

   Two rules carried from the mockups that are easy to undo by accident:
   - Colour only ever reports state, and always sits beside a word. A status is a bordered pill in the
     status colour, never a filled block, so it survives being printed or read by someone who cannot
     separate the hues.
   - Mono is for data and labels, not for emphasis. .data is a value someone might copy; .label is the
     small-caps key above it.
   ============================================================ */

:root {
    /* Ink is the primary: Graphite has no accent colour, so an action is ink-filled. */
    --color-primary: #1B1D21;
    --color-primary-light: #65686E;
    --color-primary-dark: #000000;
    --color-primary-bg: #F4F3F0;

    --color-bg: #FBFAF8;
    --color-bg-secondary: #F4F3F0;
    --color-bg-tertiary: #F4F3F0;
    --color-border: #E2E0DB;
    --color-border-light: #E2E0DB;

    --color-text: #1B1D21;
    --color-text-secondary: #65686E;
    --color-text-muted: #65686E;
    --color-text-inverse: #F4F3F0;

    /* Status backgrounds are transparent, not a tint: see the colour rule above. */
    --color-success: #2B7A4B;
    --color-success-bg: transparent;
    --color-error: #C0372C;
    --color-error-bg: transparent;
    --color-warning: #8F6208;
    --color-warning-bg: transparent;
    --color-info: #1B1D21;
    --color-info-bg: transparent;

    --sidebar-width: 232px;
    --topbar-height: 52px;

    --font-family: 'Familjen Grotesk', 'Helvetica Neue', Arial, sans-serif;
    --font-mono: 'IBM Plex Mono', ui-monospace, 'Cascadia Code', Consolas, monospace;
    --font-size-xs: 0.75rem;
    --font-size-sm: 0.8125rem;
    --font-size-base: 0.875rem;
    --font-size-lg: 1rem;
    --font-size-xl: 1.125rem;

    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-5: 1.25rem;
    --space-6: 1.5rem;
    --space-8: 2rem;

    --radius-sm: 4px;
    --radius-md: 6px;
    --radius-lg: 8px;
    --radius-xl: 12px;
    --radius-full: 9999px;
}

*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

html, body {
    height: 100%;
    font-family: var(--font-family);
    color: var(--color-text);
    background: var(--color-bg-secondary);
    -webkit-font-smoothing: antialiased;
}

/* On body, NOT on html, and the difference was 12.5% of every size in the product.
   
   --font-size-base is 0.875rem, and rem is relative to the ROOT element. Setting it on html made the root
   14px rather than the browser's 16, so every other rem token was then computed against 14: 0.875rem
   resolved to 12.25px where the mockups say 14, 0.8125rem to 11.4 where they say 13, 0.75rem to 10.5 where
   they say 12. Nothing looked broken -- everything was proportionally a little small, on every screen,
   which is exactly the kind of wrong that survives a year of looking at it.
   
   Measured against docs/ui/shred-answer.html side by side rather than noticed: the sizes I had just
   "corrected" one at a time were the same 0.875 ratio each time, which is what gave it away. */
body { font-size: var(--font-size-base); }

/* ---------- Graphite primitives ----------
   Carried from docs/ui/. These four are the whole vocabulary the mockups use for keys, values, status and
   metadata; everything else is composed from them. */

/* The small-caps key above a value. Never a sentence -- it is a field name. */
.label {
    font-family: var(--font-mono);
    font-size: 10.5px;
    line-height: 1.4;
    font-weight: 500;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--color-text-secondary);
}

/* A value someone might copy: a solicitation number, a date, a count. */
.data {
    font-family: var(--font-mono);
    font-size: 12px;
    line-height: 1.5;
}

/* Status. Bordered in currentColor rather than filled, so the hue is never the only signal --
   pair it with .dot and a word. */
.pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 9px;
    border: 1px solid currentColor;
    border-radius: var(--radius-full);
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 500;
    line-height: 1.5;
    white-space: nowrap;
}

.dot {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
}

/* Metadata that is not a status: a notice type, a set-aside. Muted, never coloured. */
.tag {
    display: inline-flex;
    align-items: center;
    padding: 1px 6px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-text-secondary);
    white-space: nowrap;
}

a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible {
    outline: 2px solid var(--color-text);
    outline-offset: 2px;
}

a { color: var(--color-primary); text-decoration: none; }
a:hover { text-decoration: underline; }
code { font-family: var(--font-mono); font-size: 0.9em; background: var(--color-bg-tertiary); padding: 1px 4px; border-radius: var(--radius-sm); }

.muted { color: var(--color-text-secondary); }
.small { font-size: var(--font-size-xs); }
.nowrap { white-space: nowrap; }

/* ---------- Shell ---------- */

.app-shell {
    display: grid;
    grid-template-columns: var(--sidebar-width) 1fr;
    grid-template-rows: auto 1fr;
    grid-template-areas: "sidebar topbar" "sidebar content";
    height: 100vh;
    overflow: hidden;
}

/* Reading a shred is a mode, not a page.
   
   docs/ui/shred-overview.html, -requirements and -check are the only mockups with NO sidebar: the shred
   takes the window, and the breadcrumb is how you get back. That is also why they use 64px gutters where
   every other screen uses 40 -- there is no 232px rail taking the space. Built as a shell variant rather
   than a second layout, because everything else about the page is the same. */
.app-shell-focus {
    grid-template-columns: 1fr;
    grid-template-areas: "topbar" "content";
}

.app-shell-focus .topbar,
.app-shell-focus .main-content { padding-left: 64px; padding-right: 64px; }

/* The brand goes where the sidebar's was, since the sidebar is not there to hold it. */
.focus-brand { display: flex; align-items: center; gap: 10px; text-decoration: none; }
.focus-brand img { width: 40px; height: 22px; object-fit: contain; }

/* docs/ui/shred-answer.html: a slimmer header than the shred screens -- no tabs, no title block -- and two
   columns with 48px between them, which is wider than anywhere else because the two sides are unrelated.
   One is a decision; the other is what the decision is about. */
.answer-head { display: flex; justify-content: space-between; align-items: center; gap: 16px; width: 100%; }

.answer-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 380px;
    gap: 48px;
    align-items: start;
}

/* 820px and no panel: docs/ui/shred-answer.html puts the gate straight on the ground, because it IS the
   page rather than one card on it. */
.answer-main { display: flex; flex-direction: column; gap: var(--space-6); max-width: 820px; min-width: 0; }

/* 36px, as the sheet has it. This is the one question the product asks outright, and it is sized like a
   question rather than like a section heading. */
.answer-question { margin: 0; font-size: 36px; line-height: 1.1; font-weight: 600; letter-spacing: -0.024em; }
/* Sticky, so what you are answering about stays beside the answer on a long form. */
.answer-aside { display: flex; flex-direction: column; gap: 16px; position: sticky; top: var(--space-4); }
.answer-subject { margin: 0; font-size: 17px; font-weight: 600; line-height: 1.35; }
.answer-figures { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; padding-top: 10px; border-top: 2px solid var(--color-text); }

/* The two actions are not a pair: one leaves without deciding, the other decides. Pushed apart so the
   primary is not something a reader arrives at by drifting rightwards. */
.answer-actions { display: flex; justify-content: space-between; gap: 12px; padding-top: 14px; border-top: 1px solid var(--color-border); }
.answer-submit { min-width: 180px; }


@media (max-width: 1100px) {
    .answer-layout { grid-template-columns: minmax(0, 1fr); gap: 24px; }
}

.sidebar {
    grid-area: sidebar;
    background: var(--color-bg);
    border-right: 1px solid var(--color-border);
    display: flex;
    flex-direction: column;

    /* 24px between blocks, 20px/16px around them -- the sidebar spaces its own children rather than each
       of them carrying padding that has to agree with the others. */
    gap: 24px;
    padding: 20px 16px;
    box-sizing: border-box;
}

.sidebar-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 4px 8px;
    text-decoration: none;
}
.sidebar-brand:hover { text-decoration: none; }

/* The supplied mark, not a letter glyph. object-fit keeps it from stretching if the asset is replaced
   with the vector original at a different aspect ratio. */
.brand-mark {
    width: 44px;
    height: 24px;
    object-fit: contain;
}

.brand-name { font-size: 19px; font-weight: 600; letter-spacing: -0.02em; }

.nav-list { display: flex; flex-direction: column; gap: 2px; }

.nav-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-3);
    min-height: 40px;
    padding: 0 12px;
    border-radius: 6px;
    font-size: 14px;
    color: var(--color-text-secondary);
    font-weight: 500;
}

.nav-item:hover { background: var(--color-bg-tertiary); color: var(--color-text); text-decoration: none; }
.nav-item.active { background: var(--color-border); color: var(--color-text); font-weight: 600; }

/* A count beside a nav row: how many of that thing want you. Amber where it is a queue rather than news,
   which is the distinction the mockups draw between "1 to validate" and "2 updates". */
.nav-count { color: var(--color-text-secondary); }
.nav-count-due { color: var(--color-warning); }

/* Everything below the nav sits at the bottom. */
.sidebar-foot { margin-top: auto; display: flex; flex-direction: column; gap: 16px; }

.sidebar-meter {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 14px 12px;
    border: 1px solid var(--color-border);
    border-radius: 9px;
}
.sidebar-meter-value { font-size: 20px; font-weight: 600; }
.sidebar-meter-link { color: inherit; text-decoration: none; }
.sidebar-meter-link:hover { text-decoration: underline; }
.sidebar-meter-more { color: var(--color-text-secondary); }
.sidebar-meter-more:hover { color: var(--color-text); }

/* One segment per shred in the month: filled for what is left, empty for what is spent. Segments rather
   than a bar, because three of something is countable and a percentage is not. */
.meter-track { display: flex; gap: 4px; }
.meter-segment { flex-grow: 1; height: 6px; border-radius: 3px; background: var(--color-border-strong, #D8D6D1); }
.meter-segment-left { background: var(--color-text); }

.sidebar-user { display: flex; align-items: center; gap: 10px; padding: 0 8px; }
.sidebar-avatar {
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--color-text);
    color: var(--color-text-inverse);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    font-weight: 600;
}
/* Name over sign-out, in the two lines the mockup gave to a name over a tenant id. min-width: 0 so a
   long name wraps inside the column instead of pushing the avatar out of the sidebar. */
.sidebar-user-text { display: flex; flex-direction: column; min-width: 0; }
.sidebar-user-name { font-size: 13px; font-weight: 500; line-height: 1.3; }

/* The quiet second line, in the treatment the tenant id had: mono, small, secondary. A button next to a
   name should read as the name's caption, not compete with it. */
.sidebar-signout {
    padding: 0;
    border: 0;
    background: none;
    font-family: var(--font-mono);
    font-size: 12px;
    line-height: 1.5;
    color: var(--color-text-secondary);
    cursor: pointer;
    text-align: left;
}
.sidebar-signout:hover { color: var(--color-text); text-decoration: underline; }
.nav-item.disabled { color: var(--color-text-muted); cursor: default; }
.nav-item.disabled:hover { background: none; color: var(--color-text-muted); }
.nav-icon { width: 16px; height: 16px; flex-shrink: 0; }

.sidebar-footer {
    border-top: 1px solid var(--color-border);
    padding: var(--space-3) var(--space-4);
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.footer-value { font-size: var(--font-size-sm); font-weight: 600; }

.topbar {
    grid-area: topbar;
    background: var(--color-bg);
    border-bottom: 1px solid var(--color-border);
    display: flex;
    align-items: center;

    /* Measured off docs/ui/: 20px above and below, 40px either side. */
    padding: 20px 40px;
}

/* Title over subtitle, as every mockup header draws it. A subtitle beside the title read as part of it. */
.topbar-left { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.topbar-page-title { font-size: 24px; line-height: 1.2; font-weight: 600; letter-spacing: -0.02em; }
.topbar-page-subtitle { font-size: var(--font-size-xs); color: var(--color-text-muted); }

/* How things stand right now, hard right. When a page has a status and an action, they group together
   rather than each claiming its own half of the bar. */
.topbar-status { margin-left: auto; display: flex; align-items: center; gap: var(--space-2); }
/* Status and action apart, so the pill does not read as part of the button beside it. */
.topbar-status ~ .topbar-actions { margin-left: var(--space-6); }

.main-content {
    grid-area: content;
    overflow: auto;
    display: flex;
    flex-direction: column;
    padding: 28px 40px 40px;
    gap: 24px;
}

/* Spacing only. The padding lives on .main-content, measured off docs/ui/ as 28px/40px/40px, and this
   was adding --space-5 on top of it -- so every page had 48px above its first block and no gap at all
   between the blocks after it. 24px is what the mockups put between them. */
.page-content { display: flex; flex-direction: column; gap: 24px; flex: 1; min-width: 0; }

/* ---------- Framework components ---------- */

/* Mockup geometry: a 44px target, a 6px radius, weight 500. The height is the accessibility floor for a
   touch target and the reason it is stated rather than left to padding. */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: 0 var(--space-4);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    font-size: var(--font-size-base);
    font-weight: 500;
    font-family: var(--font-family);
    cursor: pointer;
    transition: background 0.15s, opacity 0.15s;
    text-decoration: none;
    box-sizing: border-box;
}

.btn:hover { text-decoration: none; }
.btn-primary { background: var(--color-primary); color: var(--color-text-inverse); border-color: var(--color-primary); }
.btn-primary:hover:not(:disabled) { background: var(--color-primary-dark); border-color: var(--color-primary-dark); }

/* Every button, not just the primary one. A disabled ghost was indistinguishable from a live one, which
   matters now that Follow is drawn disabled on the board and the notice page -- and :hover is excluded on
   both variants, because a browser still fires hover over a disabled control. */
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* The mockups' "secondary": surface fill on a line border, not a bare ghost. */
.btn-ghost { background: var(--color-bg); color: var(--color-text); border-color: var(--color-border); }
.btn-ghost:hover:not(:disabled) { background: var(--color-bg-tertiary); color: var(--color-text); }
.btn-sm { min-height: 32px; padding: 0 var(--space-3); font-size: var(--font-size-sm); }

.data-table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    overflow: hidden;
    font-size: var(--font-size-sm);
}

.data-table thead { background: var(--color-bg-tertiary); }

.data-table th {
    text-align: left;
    padding: var(--space-3) var(--space-4);
    font-weight: 600;
    font-size: var(--font-size-xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-text-secondary);
    border-bottom: 1px solid var(--color-border);
}

.data-table td {
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--color-border-light);
    vertical-align: top;
}

.data-table tbody tr:last-child td { border-bottom: none; }
.data-table tbody tr:hover { background: var(--color-bg-secondary); }

/* A badge is a .pill: bordered in the status colour, never filled with a tint, so the hue is not the only
   signal. The .badge-* variants below set only a colour and inherit the border from currentColor. */
.badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 9px;
    border: 1px solid currentColor;
    border-radius: var(--radius-full);
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 500;
    line-height: 1.5;
    white-space: nowrap;
}

.badge-warning { background: var(--color-warning-bg); color: var(--color-warning); }
.badge-success { background: var(--color-success-bg); color: var(--color-success); }
.badge-error { background: var(--color-error-bg); color: var(--color-error); }
.badge-info { background: var(--color-info-bg); color: var(--color-info); }

.empty-state { text-align: center; padding: var(--space-8); color: var(--color-text-secondary); }
.empty-state h3 { font-size: var(--font-size-lg); color: var(--color-text); font-weight: 600; }
.empty-state p { font-size: var(--font-size-sm); margin-top: var(--space-2); }

/* A filter that left nothing, which docs/ui/shreds.html draws as a dashed box rather than as the panel for
   a client who has never shredded. Different emptinesses, different shapes: one is a view, the other is a
   state of the account. */
.empty-state-filtered {
    padding: 48px;
    border: 1px dashed var(--color-border);
    border-radius: 9px;
    font-size: 14px;
}

/* ---------- GovOps ---------- */

.notice-panel {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-md);
    margin-bottom: var(--space-4);
}

.notice-error { background: var(--color-error-bg); color: var(--color-error); }

/* Three columns under one rule, as docs/ui/shreds.html measures it: border-top 2px, 14px of padding,
   24px between columns. It was three bordered boxes with a left accent, which is a different thing that
   happens to contain the same numbers. */
.allowance-strip {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 24px;
    border-top: 2px solid var(--color-text);
    padding-top: 14px;
    margin-bottom: var(--space-5);
}
.allowance-figure { display: flex; flex-direction: column; gap: 4px; }
.allowance-value { font-size: 30px; line-height: 1.1; font-weight: 600; letter-spacing: -0.02em; }
.allowance-detail { font-size: 13px; color: var(--color-text-secondary); }

@media (max-width: 52rem) {
    .allowance-strip { grid-template-columns: 1fr; gap: var(--space-3); }
}

.stat-strip {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--space-4);
    margin-bottom: var(--space-5);
}

.stat {
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    padding: var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.stat-value { font-size: 1.5rem; font-weight: 700; letter-spacing: -0.01em; }
.stat-label { font-size: var(--font-size-xs); color: var(--color-text-secondary); display: flex; flex-wrap: wrap; gap: var(--space-1); align-items: center; }

.toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-bottom: var(--space-5);
}

.toolbar label { font-weight: 600; font-size: var(--font-size-sm); }

.toolbar select {
    min-width: 280px;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-bg);
    font-family: var(--font-family);
    font-size: var(--font-size-sm);
    color: var(--color-text);
}

.toolbar select:focus { outline: none; border-color: var(--color-primary); box-shadow: 0 0 0 2px var(--color-primary-bg); }

.board-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: var(--space-5);
    align-items: start;
}

.result-heading { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-3); margin-bottom: var(--space-3); }
.result-heading h2 { font-size: var(--font-size-xl); font-weight: 600; }
.strip-label {
    font-size: var(--font-size-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-text-secondary);
    margin-bottom: var(--space-2);
}

/* The client's numbers, set apart from the board's so the two are never read as one. */
.stat-client { border-left: 3px solid var(--color-primary); }

/* The line under a figure, naming what it was spent on. A number with no subject is a number somebody has
   to go and look up. */
.stat-detail {
    font-family: var(--font-mono);
    font-size: 11px;
    line-height: 1.5;
    color: var(--color-text-secondary);
}

/* Filter chips over the shred list. `.chip` already carries the shape; this is the chosen one. */

/* Corrections as two columns: what was corrected, then what the reader said. A run-on line buries the
   item id, which is what anyone scanning for a particular requirement is looking for. */
.correction-list { display: grid; gap: var(--space-2); }
.correction {
    display: grid;
    grid-template-columns: minmax(8rem, 14rem) 1fr;
    gap: var(--space-3);
    align-items: baseline;
}
.correction-item { color: var(--color-text-secondary); word-break: break-word; }
.correction-note { line-height: 1.55; }
.correction-scope { margin: var(--space-3) 0 0; }

/* One column on a narrow screen: a 14rem label column beside wrapped prose is mostly empty space. */
@media (max-width: 40rem) {
    .correction { grid-template-columns: 1fr; gap: var(--space-1); }
}

/* A page's own action, opposite its title. */
.topbar-actions { margin-left: auto; display: flex; gap: var(--space-2); align-items: center; }

/* Near-misses, not matches: set apart from the client's real numbers. */
.stat-aside { border-left-color: var(--color-text-muted); }
.stat-action { font-size: var(--font-size-xs); font-weight: 600; margin-top: var(--space-1); }

.badge-neutral { background: var(--color-bg-tertiary); color: var(--color-text-secondary); }
.data-table tbody tr.row-no-naics { background: var(--color-bg-secondary); }
.data-table tbody tr.row-no-naics .notice-title { font-weight: 500; }

/* docs/ui/board.html lays a row out as 96px | minmax(0, 1fr) | 150px | 100px, so the notice takes every
   pixel the other three leave. It was capped at 420px, which stopped the content while the column kept
   going -- the empty band between a title and why it matched.
   
   And this is a <td>, so it is NOT a flex container. Giving a table cell display: flex takes it out of the
   table's layout: it stops being a column and drops below the row. It carried display: flex for a long time
   and looked fine, which is the dangerous kind of wrong -- the same declaration on .why-cell broke the
   board immediately. Block, with the children spaced by margins. */
.notice-cell > * { display: block; }
.notice-cell > * + * { margin-top: 2px; }
/* One class, two sizes, because the mockups draw the same link at two sizes: a board row's title is
   14px/1.4 (docs/ui/board.html) and a shred card's heading is 20px with -0.02em (docs/ui/shreds.html).
   Pinning the shared rule to 14px -- which is what stopped the notice page's 32px heading taking over
   both -- then shrank the Shreds heading to a row title. So the shared rule carries only what the two
   agree on, and each context states its own size.

   check-css-classes.py cannot see this one: both rules exist, both are scoped, and nothing is used
   without a rule. Only the mockups say the sizes differ. */
.notice-title { font-weight: 600; color: var(--color-text); }
.data-table .notice-title { font-size: 14px; line-height: 1.4; }
.shred-card .notice-title { font-size: 20px; line-height: 1.3; letter-spacing: -0.02em; }
.notice-title:hover { color: var(--color-primary); }
.notice-cell .muted { font-size: var(--font-size-xs); }

.runway { display: block; font-size: var(--font-size-xs); color: var(--color-text-secondary); margin: 2px 0; }
.runway-short { color: var(--color-warning); font-weight: 600; }

.chips { display: flex; flex-wrap: wrap; gap: var(--space-1); }

/* Why a notice matched: ground fill on a line border, mono, never coloured. A match is not a status. */
.chip {
    display: inline-flex;
    align-items: center;

    /* A flex container drops whitespace-only anonymous items, so the space between a chip's label and its
       count vanishes and "All 2" renders as "All2". The gap is what puts it back. */
    gap: var(--space-1);
    padding: 2px var(--space-2);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-bg-secondary);
    color: var(--color-text);
    font-family: var(--font-mono);
    font-size: 11px;
    white-space: nowrap;
}

/* A filter chip is a control rather than a label, and the mockup sizes it like one: 36px tall, 14px of
   side padding, fully rounded, 13px type. The inline chips above stay as they are -- they annotate a row
   and are not pressed. */
.shred-filters .chip {
    min-height: 36px;
    padding: 0 14px;
    border-radius: var(--radius-full);
    font-size: 13px;
    text-decoration: none;
}

/* A concern and an exclusion do carry state, so they borrow the pill treatment: colour on the border and
   the text, no fill. */
.chip-concern { background: transparent; border-color: currentColor; color: var(--color-warning); }

/* The chip in force. AFTER .chip deliberately: at equal specificity the later rule wins, and an earlier
   one is silently overridden by the very class it is meant to modify. */
/* A row, which it had never actually been. The chips are inline-flex so they sat in a line by
   themselves, and the moment a search form joined them it broke onto its own line -- a <form> is a block.
   The gap replaces the whitespace between inline elements that was doing the spacing before. */
.shred-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-4);
}
.chip-on { border-color: var(--color-text); background: var(--color-text); color: var(--color-bg); }
.chip-on .data { color: inherit; opacity: 0.75; }

.side-panel {
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    padding: var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.side-panel h3 { font-size: var(--font-size-base); font-weight: 600; }
.side-panel > p { font-size: var(--font-size-xs); }

.rule-list { list-style: none; display: flex; flex-direction: column; gap: var(--space-3); }
.rule-row { display: flex; justify-content: space-between; gap: var(--space-2); font-size: var(--font-size-sm); }
.rule-count { font-weight: 600; font-variant-numeric: tabular-nums; }
.rule-bar { height: 4px; background: var(--color-bg-tertiary); border-radius: var(--radius-full); margin-top: 4px; overflow: hidden; }
.rule-bar span { display: block; height: 100%; background: var(--color-primary-light); }

/* Filters */

/* Wraps, and "Sorted by" rides at the end with margin-left: auto -- so it sits right on a wide window
   and drops under the filters when there is no room. Fluid by design, as the mockup draws it. */
.filter-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

.filter { position: relative; }

.filter > summary {
    list-style: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;

    /* 36px tall, 12px of side padding, 6px radius, 13px -- docs/ui/board.html. Square because these open
       menus. The fully rounded pills are the shred filters, which are a segmented single choice; the shape
       difference is the control type, not decoration. */
    min-height: 36px;
    padding: 0 12px;
    border: 1px solid var(--color-border);
    border-radius: 6px;
    font-size: 13px;
    background: var(--color-bg);
    font-size: var(--font-size-sm);
    font-weight: 500;
    color: var(--color-text-secondary);
    user-select: none;
    white-space: nowrap;
}

.filter > summary::-webkit-details-marker { display: none; }
.filter > summary::after { content: " \25BE"; font-size: 0.7em; }
.filter > summary:hover { background: var(--color-bg-tertiary); color: var(--color-text); }
.filter.active > summary { border-color: var(--color-primary); background: var(--color-primary-bg); color: var(--color-primary-dark); }

.filter-menu {
    position: absolute;
    z-index: 20;
    top: calc(100% + 4px);
    left: 0;
    min-width: 260px;
    max-width: 380px;
    max-height: 340px;
    overflow-y: auto;
    padding: var(--space-2);
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
}

.filter-option {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 6px var(--space-2);
    border-radius: var(--radius-sm);
    font-size: var(--font-size-sm);
    cursor: pointer;
}

.filter-option:hover { background: var(--color-bg-secondary); }
.filter-option.empty { color: var(--color-text-muted); }
.filter-option.not-stated { border-top: 1px solid var(--color-border-light); margin-top: 4px; padding-top: 8px; font-style: italic; }
.filter-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.filter-count { font-variant-numeric: tabular-nums; color: var(--color-text-secondary); font-size: var(--font-size-xs); }
.filter-clear { font-size: var(--font-size-sm); }

.notice-meta { display: flex; gap: var(--space-2); font-size: var(--font-size-xs); color: var(--color-text-secondary); }

/* On a shred card the sheet marks this line .data -- mono, 12px. It is mostly a solicitation number and a
   timestamp, which is what the mono face is for. The board's version stays sans: there the same class
   carries an agency name. */
.shred-card .notice-meta { font-family: var(--font-mono); font-size: 12px; line-height: 1.5; }
.notice-meta > span + span::before { content: "\00B7"; margin-right: var(--space-2); color: var(--color-text-muted); }

.side-stack { display: flex; flex-direction: column; gap: var(--space-4); }

.criteria { display: flex; flex-direction: column; gap: var(--space-3); }
.criteria dt { font-size: var(--font-size-xs); font-weight: 600; color: var(--color-text-secondary); margin-bottom: 4px; }
.criteria dd { display: flex; flex-direction: column; gap: 4px; }
.criterion-note { font-size: var(--font-size-xs); color: var(--color-text-secondary); line-height: 1.4; }
.chip-exclude { background: transparent; border-color: currentColor; color: var(--color-error); }

/* Notice detail */

.back-link { display: inline-block; margin-bottom: var(--space-4); font-size: var(--font-size-sm); }

.section-heading { font-size: var(--font-size-base); font-weight: 600; margin: var(--space-6) 0 var(--space-3); }
.description { white-space: pre-wrap; line-height: 1.6; max-width: 80ch; }

.zone-note { margin-top: var(--space-1); padding: var(--space-2); border-radius: var(--radius-sm); background: var(--color-warning-bg); color: var(--color-warning); font-size: var(--font-size-xs); font-weight: 400; }

.detail-actions { display: flex; flex-direction: column; gap: var(--space-2); padding-top: var(--space-3); border-top: 1px solid var(--color-border-light); }

/* ---------- Narrow screens ---------- */

@media (max-width: 1100px) {
    .board-layout, .notice-layout { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 760px) {
    .app-shell { grid-template-columns: 1fr; grid-template-areas: "topbar" "content"; }
    .sidebar { display: none; }
    .stat-strip { grid-template-columns: minmax(0, 1fr); }
    .toolbar select { min-width: 0; flex: 1; }
    .board-table th:nth-child(2), .board-table td:nth-child(2),
    .board-table th:nth-child(3), .board-table td:nth-child(3) { display: none; }
}

/* Shreds */

.notice-ok { background: var(--color-success-bg); color: var(--color-success); }

.shred-form { display: contents; }

/* No max-width. docs/ui/shreds.html gives the cards the whole of main -- the 280px action column is what
   keeps a line of text from running the full width, and capping the list at 960px instead left the card
   narrower than drawn and the gutter beside it empty. The cap was invented; the column is the measure. */
.shred-list { display: flex; flex-direction: column; gap: var(--space-3); }

.shred-card {
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 9px;
    padding: 24px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

/* The mockup's card: what the shred is on the left, what you can do in a fixed 280px column on the right,
   32px between them. Measured off docs/ui/shreds.html rather than eyeballed -- the padding, the radius and
   the column width were all invented before, which is why three rounds of screenshots were needed to get
   close and still were not.

   Only when script has built the actions. Without it there is no right-hand column to reserve width for,
   and a 280px gutter holding nothing would be worse than the stack. */
.shred-card.has-card-actions {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 280px;
    grid-template-areas:
        "main actions"
        "body body";
    column-gap: 32px;
    row-gap: var(--space-3);
}

/* So the header's two children join the card's grid rather than forming their own row. */
.shred-card.has-card-actions .shred-head { display: contents; }
.shred-card.has-card-actions .shred-head-main { grid-area: main; }
.shred-card.has-card-actions .shred-actions { grid-area: actions; }
.shred-card.has-card-actions .shred-body { grid-area: body; }

/* "Which of these needs me" is the question a list of shreds answers, and the sheet answers it twice: with
   the status pill and with the card's own border. A shred waiting on a person is bordered in the text
   colour; a settled one keeps the muted border. */
.shred-card-due { border-color: var(--color-text); }

/* The card's notice facts and its four figures, from docs/ui/shreds.html: 32px between the figures, the
   value at 22px over a 12px caption. A row rather than a grid, because four is a row. */
.shred-facts { font-size: 14px; color: var(--color-text-secondary); }
.shred-figures { display: flex; gap: 32px; padding-top: 10px; flex-wrap: wrap; }
.shred-figures .figure { display: flex; flex-direction: column; gap: 0; }
.shred-figures .figure-value { font-size: 22px; line-height: 1.2; font-weight: 600; }
.shred-figures .figure-detail { font-size: 12px; color: var(--color-text-secondary); }

/* A 280px column beside wrapped prose is most of the width on a narrow screen. */
@media (max-width: 52rem) {
    .shred-card.has-card-actions {
        grid-template-columns: 1fr;
        grid-template-areas: "main" "actions" "body";
    }
}

.shred-card.shred-new { border-color: var(--color-primary); box-shadow: 0 0 0 2px var(--color-primary-bg); }
.shred-head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--space-3); }
.shred-head .notice-meta { margin-top: 4px; }

/* The card leads with its status, so the left column is one stack rather than title-and-badge. Beside
   .shred-card rather than next to the stat rules, which is where it was first written -- a rule placed far
   from the class it modifies is a rule waiting to be overridden by it. */
.shred-head-main { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }

/* What the shred is on the left, what you can do on the right, as docs/ui/shreds.html draws it. Empty in
   the markup and filled by script, so a reader with no script keeps the native disclosures instead of
   being shown buttons that do nothing. */
/* Centred against the left column, as the sheet draws it: the actions sit beside what they act on rather
   than at the top of a column that is mostly empty. */
.shred-actions {
    display: flex;
    flex-direction: column;
    gap: 8px;
    justify-content: center;
    flex-shrink: 0;
    align-items: stretch;
}
.shred-actions:empty { display: none; }

/* Everything a card can open, full width under both columns. */
.shred-body { display: flex; flex-direction: column; gap: var(--space-3); }
.shred-body:empty { display: none; }

/* The summary is the control until script makes a button of it. Hidden only once that has happened, so
   the panel is never left with no way to open it. */

/* .flag-ride and .flag-ride-list are defined with the rest of the answer screen, further down. They were
   here, from when the panel was a read-only list of what had been flagged; it is a set of checkboxes
   somebody can take items out of now, and two definitions of the same class is how one of them silently
   wins. .flag-ride-toggle went with the disclosure it opened. */

/* A prototype surface, linked where one has been built. Deliberately quieter than the gate. */
.review-link {
    font-weight: 600;
    font-size: var(--font-size-sm);
    color: var(--color-primary);
    margin-right: var(--space-2);
}
/* No pseudo-element glyph here: a CSS \XXXX escape has been mangled twice in this file already, and a
   new-tab link is signalled well enough by the label beside it. */

/* Collapsed by default, so the summary has to read as a control rather than as a caption. */
.shred-gate summary {
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-1) var(--space-2);
    margin-left: calc(var(--space-2) * -1);
    border-radius: var(--radius-sm);
    font-size: var(--font-size-sm);
    font-weight: 600;
    color: var(--color-primary);
    list-style: none;
}
.shred-gate summary::-webkit-details-marker { display: none; }
.shred-gate summary:hover { background: var(--color-bg-secondary); }
/* A document, not a panel.
   
   This was a 420px scrollbox at 13px on a grey inset, and all three were right while the shred was a
   disclosure crammed into a row of the Shreds list. On a page whose whole job is to show the shred they
   are all wrong: a window with its own scrollbar inside a scrolling page, holding 39,000 characters, at a
   size chosen to take up less room. Now that the list links out instead of inlining, the page is the only
   place a readout appears, so it reads at 15px with no ceiling and no second scrollbar. */
.readout {
    margin-top: var(--space-2);
    white-space: pre-wrap;
    font-size: 15px;
    line-height: 1.65;
}

/* 28px between blocks and no rule above them, as the sheet has it. The rule and the tight gap date from
   when this form was a disclosure inside a row of the Shreds list; on a page of its own the blocks are the
   structure and a line above the first one divides nothing from nothing. */
.gate-form { display: flex; flex-direction: column; gap: 28px; }
.gate-question { font-weight: 600; }
/* Stacked cards rather than a row of chips. Each choice carries its consequence underneath it, which is
   what somebody is really choosing between -- and three chips in a row read as filters, which is the one
   thing this control is not. A fieldset, because they are one question. */
.gate-options { display: flex; flex-direction: column; gap: var(--space-2); border: 0; margin: 0; padding: 0; }
.gate-options > legend { padding-bottom: var(--space-2); }

.gate-option {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    background: var(--color-bg);
    cursor: pointer;
}

/* Border AND an inset ring, as the sheet draws it: a 1px colour change alone is not enough to carry
   "this is the one you are about to record" on a screen with three near-identical cards. */
.gate-option:has(input:checked) {
    border-color: var(--color-primary);
    box-shadow: inset 0 0 0 1px var(--color-primary);
}

.gate-option input { margin-top: 2px; }
.gate-option-text { display: flex; flex-direction: column; gap: var(--space-1); }
.gate-option-name { font-size: var(--font-size-lg); font-weight: 600; }

/* What an option means, under the option. A title attribute carries the same words for a pointer, but a
   tooltip is not readable by touch or by keyboard -- and "can't validate yet" is the one answer whose
   meaning is not obvious from its name, which is exactly why it was renamed. So it is on the page.

   Full width, so a hint breaks the row of options after the one it belongs to rather than wrapping into
   the next gap. */
.gate-option-hint {
    font-size: var(--font-size-base);
    line-height: 1.5;
    color: var(--color-text-secondary);
}

/* Added by script when the chosen answer needs a reason. The server refuses either way, so this is a
   nudge rather than the control. */
.field-required { border-color: var(--color-primary); }
.gate-form textarea {
    width: 100%;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    font-family: var(--font-family);

    /* 14px with 1.55 leading, as the sheet has it. 13px here was the shared note size, and this box is
       not a note: on a correction the words in it are what gets recorded. */
    font-size: var(--font-size-base);
    line-height: 1.55;
    resize: vertical;
}
.gate-form .btn { align-self: flex-start; }


/* Sortable columns. The arrow is drawn, not typed: a CSS escape for an arrow character rendered as
   "backslash-21" plus the digits "95" on 2026-09-22, which is how the header came to read "NOTICE (95". */
.data-table th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.data-table th.sortable:hover { color: var(--text, #111); }
.data-table th.sorted-asc::after,
.data-table th.sorted-desc::after {
    content: "";
    display: inline-block;
    margin-left: 0.4rem;
    vertical-align: middle;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    opacity: 0.55;
}
.data-table th.sorted-asc::after { border-bottom: 5px solid currentColor; }
.data-table th.sorted-desc::after { border-top: 5px solid currentColor; }

/* The notice type in the table: text, not a badge. Most matches for an IT profile are market research, and
   a coloured tag on every row reads as a warning about the table rather than about a notice. */
.data-table .notice-type { white-space: nowrap; }
.data-table .notice-type.not-solicitation { color: var(--muted, #6b7280); }


/* Why it matched: bounded, so the chips wrap inside the column instead of stretching the row. Without a
   width the table gave this column whatever a full concern sentence asked for, and the notice title -- the
   thing a reader is actually scanning -- was squeezed into three words a line. */
/* The sheet's three fixed columns; the notice column is whatever is left.
   
   table-layout: fixed is what makes that true. Without it the browser sizes columns from their content, so
   a stated width is a suggestion and one long title moves every column -- and the widths fight whatever
   display the cells happen to have. Fixed means the first row's widths are the table's, full stop, which is
   what "the columns should not move between rows" needs. */
.board-table { table-layout: fixed; }

.board-table th:nth-child(1), .board-table td:nth-child(1) { width: 96px; }
.board-table th:nth-child(3), .board-table td:nth-child(3) { width: 150px; }

/* No display here. This is a <td>, and giving a table cell display: flex takes it out of the table's
   layout -- it stops being a column and drops below the row, which is exactly what it did. The chips are
   inline-flex and wrap on their own inside the column's width; this only keeps them off each other when
   they do. */
.why-cell { line-height: 2.1; }
.board-table th:nth-child(4), .board-table td:nth-child(4) { width: 100px; }


/* Anything carrying a tooltip says so the same way: a small "i" and a help cursor. Without it a shortened
   chip looks like all there is, and nobody hovers over text that appears complete. */
.chip[title], .hint[title] { cursor: help; }
.chip[title]::after, .hint[title]::after {
    content: "i";
    display: inline-block;
    margin-left: 0.3rem;
    width: 0.85em;
    height: 0.85em;
    line-height: 0.85em;
    font-size: 0.8em;
    font-style: italic;
    text-align: center;
    border: 1px solid currentColor;
    border-radius: 50%;
    opacity: 0.55;
    vertical-align: baseline;
}
.notice-type[title] { border-bottom: 1px dotted currentColor; cursor: help; }


/* The shred a notice linked to: marked, so arriving from a notice lands on the right card rather than at the
   top of a list of them. */
.shred-card.shred-linked { box-shadow: 0 0 0 2px var(--color-primary, #2563eb); scroll-margin-top: 1rem; }


/* A shred this client already has, marked on the board. Small, and a link: the mark is only useful if it
   takes you to the thing it marks. */
.badge-sm { font-size: var(--font-size-xs); padding: 1px 6px; align-self: flex-start; text-decoration: none; }
a.badge-sm:hover { text-decoration: underline; }


/* A rendered shred reads as a document. Until it is rendered it keeps the pre-wrap of the raw text, so a
   reader with no script still gets something legible rather than one long line. */
.readout-rendered { white-space: normal; }
.readout-rendered h2 { font-size: 19px; font-weight: 600; margin: var(--space-6) 0 var(--space-2); }
.readout-rendered h3 { font-size: 16px; font-weight: 600; margin: var(--space-4) 0 var(--space-1); }
.readout-rendered h4, .readout-rendered h5 { font-size: 14px; font-weight: 600; margin: var(--space-3) 0 var(--space-1); color: var(--color-text-secondary); }
.readout-rendered p { margin: 0 0 var(--space-2); }
.readout-rendered ul, .readout-rendered ol { margin: 0 0 var(--space-2); padding-left: var(--space-5); }
.readout-rendered li { margin-bottom: 2px; }
.readout-rendered li.nested { margin-left: var(--space-3); list-style-type: circle; }
.readout-rendered hr { border: 0; border-top: 1px solid var(--color-border); margin: var(--space-3) 0; }
.readout-rendered code { background: var(--color-bg); padding: 0 3px; border-radius: 3px; font-size: 0.9em; }
.readout-table { width: 100%; border-collapse: collapse; margin: 0 0 var(--space-3); font-size: var(--font-size-xs); }
.readout-table th, .readout-table td { border: 1px solid var(--color-border); padding: 4px 6px; text-align: left; vertical-align: top; }
.readout-table th { background: var(--color-bg); }

/* A provider error kept out of the way: read it when you want it. */
.note-detail summary { cursor: pointer; font-size: var(--font-size-xs); color: var(--color-text-secondary); }
.note-detail pre { max-height: 220px; overflow: auto; white-space: pre-wrap; word-break: break-word;
    font-size: var(--font-size-xs); background: var(--color-bg-secondary); padding: var(--space-2);
    border-radius: var(--radius-sm); }

/* Corrections already recorded against a shred, shown back on the card. */
.flag-record { margin-bottom: var(--space-2); font-size: var(--font-size-sm); }
.flag-record > summary { cursor: pointer; font-weight: 600; color: var(--color-text-secondary); }

/* ---------- Criteria editor ----------
   The board's zero state and the only way to change a board without hand-writing JSON. Field notes carry
   the rules a capture manager would otherwise get wrong from the values alone -- that six digits are
   required, that titles are matched and descriptions are not -- so they are part of the control rather than
   decoration, and are styled to be read. */

.criteria-form { display: flex; flex-direction: column; gap: var(--space-6); max-width: 1080px; }

.criteria-group {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding: var(--space-5);
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
}

.field-row { display: flex; flex-wrap: wrap; gap: var(--space-5); }

/* A label over its input. No flex-grow of its own: this was `flex: 1 1 280px`, which is right inside a
   .field-row -- a horizontal row where fields share the width -- and wrong everywhere else. In a COLUMN,
   which is what every form on a staff page is, grow applies to height: each field stretched to fill the
   panel and the invite form came out with a hand's width between its boxes. The sharing belongs to the
   row, so it is stated there. */
.field { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.field-row .field { flex: 1 1 280px; }

.field-input {
    font-family: var(--font-mono);
    font-size: 12px;
    line-height: 1.5;
    padding: var(--space-2) var(--space-3);
    color: var(--color-text);
    background: var(--color-bg-secondary);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    resize: vertical;
}
.field-input:read-only { color: var(--color-text-secondary); cursor: default; }
.field-input-narrow { max-width: 140px; }

.field-note { font-size: var(--font-size-sm); line-height: 1.5; color: var(--color-text-secondary); margin: 0; }

/* What a field says when it refuses a value. Red and the same size as the note it replaces, because it is
   read in the same place for the same reason -- and empty when there is nothing to say, so a validation
   span costs no space until it has words in it. */
.field-error { font-size: var(--font-size-sm); line-height: 1.5; color: var(--color-error); }
.field-error:empty { display: none; }
.field-note strong { color: var(--color-text); font-weight: 600; }

/* A switch reads as a sentence, so the label sits beside the box rather than above it. */
.field-check {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    font-size: var(--font-size-sm);
    line-height: 1.5;
    color: var(--color-text-secondary);
}
.field-check input { margin-top: 3px; flex-shrink: 0; }
.field-check strong { color: var(--color-text); font-weight: 600; }

/* The panel that says what a board looks for, with the link that now goes somewhere. */
.criteria-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-3); }

/* ---------- Sign in ----------
   Its own page rather than the app shell, because the shell assumes a signed-in user: a customer name, an
   allowance meter and a nav to pages that would all redirect back here. */

/* Measured off docs/ui/sign-in.html: a centred column with 64px of padding and 40px between the lockup,
   the card and the footer. Not a card vertically centred in the viewport, which is what this was -- the
   difference shows the moment the window is short. */
.sign-in-page {
    display: flex;
    flex-direction: column;
    align-items: center;
    min-height: 100vh;
    padding: 64px 16px;
    gap: 40px;
    background: var(--color-bg-secondary);
    box-sizing: border-box;
}

/* The lockup, at the size the mockup sets. This is the only screen with room for it; everywhere else gets
   the small mark in the sidebar. */
.sign-in-lockup { width: 180px; height: 142px; object-fit: contain; }

.sign-in-card {
    width: 100%;
    max-width: 420px;
    display: flex;
    flex-direction: column;
    gap: 18px;
    padding: 32px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 9px;
    box-sizing: border-box;
}

.sign-in-title { margin: 0; font-size: 26px; font-weight: 600; letter-spacing: -0.02em; }

.sign-in-form { display: flex; flex-direction: column; gap: 18px; }

/* A field here is a label over an input, 6px apart. `.field` is not used: it carries `flex: 1 1 280px` for
   the criteria editor's horizontal rows, and in a column that basis is read as a minimum HEIGHT -- which
   is what gave this page a 280px gap under every field until someone looked at it. */
.sign-in-field { display: flex; flex-direction: column; gap: 6px; }
.sign-in-field > label { font-size: 13px; font-weight: 500; }

/* Sans, not mono. `.field-input` is mono 12px because the criteria editor's values are codes; an email
   address is prose, and the mockup sets these at 14px in the body face. */
.sign-in-input {
    height: 44px;
    box-sizing: border-box;
    padding: 0 12px;
    border: 1px solid var(--color-border);
    border-radius: 6px;
    background: var(--color-bg);
    font-family: inherit;
    font-size: 14px;
    color: var(--color-text);
}

.sign-in-submit { width: 100%; }

.sign-in-footer { color: var(--color-text-secondary); }


/* ---------- Board ----------
   Geometry measured off docs/ui/board.html. The figures strip and the row grid are the two things that
   make the page read as the mockup rather than as a table with a sidebar. */

/* The pill carries its colour in `color` and borders in currentColor, so a variant sets one property. */
.pill-ok { color: var(--color-success); }
.pill-warn { color: var(--color-warning); }

/* The four figures. A rule above them and nothing boxing each one: the mockup deliberately does not draw
   these as cards, because cards invite reading them as four separate things rather than one strip. */
.board-figures {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 24px;
    border-top: 2px solid var(--color-text);
    padding-top: 14px;
}

.figure { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.figure-value { font-size: 30px; line-height: 1.1; font-weight: 600; font-variant-numeric: tabular-nums; }

/* What the number is of. A figure with no subject is a figure somebody has to go and look up. */
.figure-detail { font-size: 13px; line-height: 1.4; color: var(--color-text-secondary); }
.figure-detail a { font-weight: 500; }

@media (max-width: 68rem) {
    .board-figures { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 20px; }
}

.board-search {
    width: 260px;
    height: 36px;
    box-sizing: border-box;
    padding: 0 12px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-bg);
    font-family: inherit;
    font-size: 14px;
    color: var(--color-text);
}

/* A control that holds a state, drawn as held rather than as available: ink border either way, filled when
   on. A link rather than a button because it changes the view's URL like every other filter -- so its own
   label says which way it is, and the fill is never the only signal. */
.filter-toggle {
    display: inline-flex;
    align-items: center;
    min-height: 36px;
    padding: 0 12px;
    border: 1px solid var(--color-text);
    border-radius: var(--radius-md);
    background: var(--color-bg);
    font-size: 13px;
    text-decoration: none;
    cursor: pointer;
}

.filter-toggle:hover { background: var(--color-bg-tertiary); }
.filter-toggle.on { background: var(--color-text); color: var(--color-bg); }
.filter-toggle.on:hover { color: var(--color-bg); opacity: 0.9; }

/* How the list is ordered, stated rather than left to be inferred from the dates. */
/* Last in the bar and pushed to the end of it, which is why it reads as right-aligned on a wide window
   and wraps under the filters on a narrow one. Fluid, not placed. */
.filter-note { margin-left: auto; color: var(--color-text-secondary); }

/* Four columns: when it responds, what it is, why it is here, and what you have already done about it.
   Type, NAICS and set-aside are inside those cells rather than columns of their own -- six narrow columns
   put the title in a 30-character gutter, and the title is what anyone actually scans.

   The widths are stated here and nowhere else. A <colgroup> would have said them a second time, and under
   auto table layout it is these rules that actually decide. */
.board-table tbody td, .board-table thead th { padding: 14px 16px; }

.board-table th:last-child, .board-table tbody td:last-child { width: 7rem; }

/* A type that is not a solicitation is drawn back, because it is not a thing to bid yet. The rule that did
   this was written for .notice-type, which the row no longer uses. */
.tag.not-solicitation { color: var(--color-text-muted); }

/* For a label a screen reader needs and the layout does not. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.due-cell { display: flex; flex-direction: column; gap: 2px; align-items: flex-start; }
.due-date { font-size: 14px; font-weight: 600; }
.actions-cell { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }

/* The count and the way to see more, under the rows they describe. */
.data-table:has(tfoot) tbody tr:last-child td { border-bottom: 1px solid var(--color-border-light); }
.board-table tfoot td { border-bottom: none; }
.board-foot { display: flex; justify-content: space-between; align-items: center; gap: var(--space-3); }

/* ---------- Notice ---------- */

/* Measurements from docs/ui/notice.html. The header is the mockup's, drawn into the topbar slot: the
   topbar keeps the padding, the rule and the background so every screen's chrome lines up, and this
   relaxes the single-row centring the title-and-subtitle version needs. 24px below rather than 20px,
   because the mockup gives this header a taller bottom than its sides. */
.topbar-own { display: block; padding-bottom: 24px; }

.notice-head { display: flex; flex-direction: column; gap: 16px; }
.notice-head-top { display: flex; justify-content: space-between; align-items: center; gap: 16px; }

/* 40px and a 6px radius, docs/ui/notice.html. Bordered in the text colour because Follow is the page's own
   action rather than a secondary one -- it goes solid when it is on, which is why it is not a plain ghost. */
.btn-follow { min-height: 40px; padding: 0 14px; border-color: var(--color-text); font-size: 13px; }
.btn-follow.btn-sm { min-height: 28px; padding: 0 10px; font-size: 12px; }

/* The toggle is a form, so it sits in a row or a cell like the bare button it replaced. */
.follow-form { display: inline-flex; margin: 0; }
.request-actions .follow-form { flex-grow: 1; }
.request-actions .follow-form .btn { width: 100%; }

/* Followed: pressed, not a second call to action. */
.btn-following { background: var(--color-bg-tertiary); border-color: var(--color-border); }

.breadcrumb { display: flex; gap: 8px; align-items: center; font-size: 13px; }
.breadcrumb > span[aria-hidden] { color: var(--color-text-secondary); }

/* 40px between the title and the deadline, and the deadline does not shrink: a long title should wrap
   rather than squeeze the one fact on the page with a date in it. */
.notice-head-main { display: flex; justify-content: space-between; align-items: flex-start; gap: 40px; }
.notice-head-title { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.notice-tags { display: flex; gap: 6px; flex-wrap: wrap; }

/* The strong tag is the notice type, which the mockup draws in the text colour rather than the muted
   border every other tag gets -- it is the one tag that changes what a shred can find. */
.tag-strong { border-color: var(--color-text); color: var(--color-text); }

.notice-heading { margin: 0; font-size: 32px; line-height: 1.1; font-weight: 600; letter-spacing: -0.024em; }
.notice-agency { font-size: 15px; color: var(--color-text-secondary); }

.notice-head-deadline { display: flex; flex-direction: column; gap: 6px; align-items: flex-end; flex-shrink: 0; }
.notice-deadline { font-size: 22px; font-weight: 600; white-space: nowrap; }
.notice-deadline-none { color: var(--color-text-secondary); font-weight: 500; }
.notice-runway { color: var(--color-text-secondary); }

/* 340px rather than the board's 320: the aside holds the shred card, which carries two 44px buttons and
   has to fit "Follow it on the Shreds page" without wrapping mid-word. */
.notice-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: 24px;
    align-items: start;
}

.notice-column { display: flex; flex-direction: column; gap: 20px; }
.notice-aside { display: flex; flex-direction: column; gap: 16px; }

.notice-card {
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 9px;
    padding: 24px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

/* The aside's cards are narrower, so they get the mockup's smaller padding and heading. */
.notice-aside .notice-card { padding: 20px; }
.notice-card > h2 { margin: 0; font-size: 18px; font-weight: 600; }
.notice-aside .notice-card > h2 { font-size: 16px; }

/* Bordered in the text colour: the one card on the page that spends money. */
.notice-card-strong { border-color: var(--color-text); }

.notice-card-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.notice-card-head h2 { margin: 0; font-size: 18px; font-weight: 600; }
.notice-aside .notice-card-head h2 { font-size: 16px; }
.notice-card-foot { display: flex; gap: var(--space-2); padding-top: 14px; }

/* A panel that is not built yet. Marked by the label in its head, not by a colour: it is not a warning,
   and an amber panel per unbuilt section would make the page look broken rather than unfinished. */
.notice-card-pending { border-style: dashed; }

.notice-caution { flex-direction: row; gap: 14px; border-color: var(--color-warning); align-items: flex-start; }
.notice-caution .pill { flex-shrink: 0; }
.notice-caution-text { display: flex; flex-direction: column; gap: 4px; font-size: 14px; line-height: 1.55; }
.notice-caution-claim { font-weight: 600; }

/* Three columns, each fact over a rule. The rule is a border-top per fact rather than a divider between
   rows, which is what makes the columns line up when a fact in the middle column is two lines tall. */
.fact-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: 24px; }

.fact {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 12px 0;
    border-top: 1px solid var(--color-border);
    min-width: 0;
}

.fact-value { font-size: 14px; font-weight: 500; overflow-wrap: anywhere; }

/* The overview's fact row, as shred-overview.html draws it: label left, citation right, value under both. */
.fact-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; min-width: 0; }
.fact-head .fact-cite { font-size: 11.5px; text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 55%; }
.fact-head .fact-source { flex-shrink: 0; }
.fact .fact-value { font-size: 15px; line-height: 1.45; }
.fact-pill { align-self: flex-start; }

/* A value clipped to four lines, the rest one press away. Unclipped by the page's script when it fits. */
.fact-clip { display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; }
.fact-clip.fact-open { display: block; -webkit-line-clamp: unset; }
.fact-more { align-self: flex-start; padding: 0; border: 0; background: none; font: inherit; font-size: 12.5px; font-weight: 500; color: var(--color-text-secondary); text-decoration: underline; cursor: pointer; }
.fact-more:hover { color: var(--color-text); }

/* The package does not establish it: an answer, drawn quietly -- not an error, and not a dash. */
.fact-none { margin: 0; font-size: 14px; font-weight: 400; font-style: italic; color: var(--color-text-muted); }

/* The mock's twelve-column pairs: 01 (7) beside 02 (5), 03 (6) beside 04 (6). A lone member spans the row. */
.ov-pair { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 24px; align-items: start; }
.ov-pair > * { grid-column: span 12; }
.ov-pair > .ov-7 { grid-column: span 7; }
.ov-pair > .ov-5 { grid-column: span 5; }
.ov-pair > .ov-6 { grid-column: span 6; }
.ov-pair > :only-child { grid-column: 1 / -1; }
.ov-pair:empty { display: none; }
.ov-pair .fact-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 32px; }
.ov-pair .ov-6 .fact-grid { grid-template-columns: minmax(0, 1fr); }
.fact-grid-one { grid-template-columns: minmax(0, 1fr) !important; }
@media (max-width: 1100px) {
    .ov-pair > .ov-7, .ov-pair > .ov-5, .ov-pair > .ov-6 { grid-column: 1 / -1; }
}
@media (max-width: 640px) {
    .ov-pair .fact-grid { grid-template-columns: minmax(0, 1fr); }
}

/* Panel heading: the numbered label over the question it answers. */
.ov-head { display: flex; flex-direction: column; gap: 6px; }
.notice-card-head .ov-head h2 { font-size: 22px; line-height: 1.2; letter-spacing: -0.02em; }
.fact-value.data { font-size: 13px; font-weight: 400; }
.fact-line { display: block; font-weight: 500; }
.fact-phone { color: var(--color-text-secondary); font-weight: 400; }

/* A screening reason: the sentence, and the verdict it earned. Full sentences here, unlike the board's
   table, where the same reasons are clipped to a chip -- this card has the width for them. */
.reason {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 12px;
    padding-top: 8px;
    border-top: 1px solid var(--color-border);
    font-size: 13px;
    line-height: 1.45;
}

.reason-text { min-width: 0; }
.reason .pill { flex-shrink: 0; }

.pill-err { color: var(--color-error); }

/* The sentence that turns "it failed" into "and it will keep failing", so it is separated from the
   provider's own words rather than running on from them. */
.release-blocked { display: block; margin-top: 4px; font-weight: 600; }

/* The notice page's shred-request card, from docs/ui/shred-request-states.html. Named request-* and
   not shred-*, because .shred-card, .shred-head and .shred-actions are the Shreds page's and were here
   first -- two bare rules for one class, a thousand lines apart, is how the notice heading silently
   resized the board. check-css-classes.py caught this one before it shipped.: 20px padding, 14px between parts, a 17px
   heading, and the buttons pushed to the foot so that eight cards of different heights still line their
   actions up. Text above the button, always -- the card says what it costs before it costs anything. */
.request-card { gap: 14px; }
.request-card > h2 { font-size: 17px; font-weight: 600; margin: 0; }
.request-head { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.request-head h2 { font-size: 17px; font-weight: 600; margin: 0; }
/* The two text roles a card has: what it says, and the quieter line explaining it. Shared by the shred
   request card and the shred screen, which is why they are not named for either. */
.card-line { font-size: 14px; line-height: 1.55; }
.card-note { font-size: 13px; line-height: 1.55; color: var(--color-text-secondary); }

/* margin-top: auto so the actions sit at the foot of the card whatever is above them. The primary grows;
   the secondary takes the width of its own words, which is the mockup's arrangement for the two-button
   states -- 7 and 8, where the second action is the lesser one. */
.request-actions { display: flex; gap: 8px; margin-top: auto; }
.request-actions > form { display: flex; }
.request-action-main { flex-grow: 1; }

/* State 4's steps. A rule above each row rather than between them, so the first step is separated from the
   heading the same way the others are separated from each other. */
.request-stages { display: flex; flex-direction: column; }

.request-stage {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
    padding: 8px 0;
    border-top: 1px solid var(--color-border);
    font-size: 13px;
}

.request-stage-waiting { color: var(--color-text-secondary); }

/* A card reporting something that did not happen. The warning colour belongs on the border, not behind the
   text: the card is still readable, and the border is what distinguishes it from the one above it. */
.notice-card-warn { border-color: var(--color-warning); }

/* ---------- One shred, on its own screen ---------- */

.shred-head-right { display: flex; align-items: center; gap: 12px; }

/* docs/ui/shred-overview.html's tabs. Two of the three are drawn and dead, because the sheet's own shape
   says what is coming better than their absence would -- and a disabled tab promises nothing, where a live
   one leading back to this same page would be a lie about having arrived somewhere. */
.shred-tabs { display: flex; gap: 20px; border-bottom: 1px solid var(--color-border); }

.shred-tab {
    padding: 0 0 10px;
    font-size: 14px;
    font-weight: 500;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}

.shred-tab-on { border-bottom-color: var(--color-text); font-weight: 600; }
.shred-tab-off { color: var(--color-text-secondary); cursor: help; }

/* The readout is a document, so it gets a document's measure rather than the card's full width. Long lines
   are the reason a shred is hard to read, not the typeface. */
.shred-readout-card .readout { max-width: 80ch; }

/* The full shred, closed under the panels: a bar that says what opening it adds. */
.readout-details { gap: 0; }
.readout-summary { display: flex; justify-content: space-between; align-items: center; gap: 16px; cursor: pointer; list-style: none; }
.readout-summary::-webkit-details-marker { display: none; }
.readout-summary::after { content: "Open"; flex-shrink: 0; padding: 6px 14px; border: 1px solid var(--color-border); border-radius: var(--radius-md); font-size: 14px; font-weight: 500; }
.readout-details[open] .readout-summary::after { content: "Close"; }
.readout-summary-title { font-size: 22px; line-height: 1.2; font-weight: 600; letter-spacing: -0.02em; }
.readout-summary-adds { flex: 1; text-align: right; }
.readout-details[open] .readout { margin-top: var(--space-5); padding-top: var(--space-5); border-top: 1px solid var(--color-border); }
@media (max-width: 640px) {
    .readout-summary { flex-wrap: wrap; }
    .readout-summary-adds { text-align: left; flex-basis: 100%; order: 3; }
}

/* The citation under a fact. Quiet, because it is provenance rather than a finding -- but present on every
   row that has one, which is what separates this screen from the rendered Markdown. */
.fact-cite { color: var(--color-text-secondary); }

/* A value SAM stated rather than the solicitation. Deliberately the quietest mark on the page: it sits
   where .fact-cite would, at the same size and weight, so a reader scanning values is not interrupted and a
   reader asking "where did this come from" finds it in the place they already look. Not a pill -- a pill
   reads as a verdict, and this is not one; the field is as settled as any other, it just came from
   elsewhere. The SAM field it came from is on the title attribute, so the row carries one word rather than
   eleven rows each repeating "SAM.gov notice,". */
.fact-source {
    align-self: flex-start;
    font-size: 11px;
    line-height: 1.4;
    color: var(--color-text-secondary);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    padding: 0 4px;
    cursor: help;
    white-space: nowrap;
}

/* A gate's marks sit together and do not wrap away from each other. */
.gate-marks { display: flex; gap: 6px; align-items: center; flex-shrink: 0; }

.shred-citations { margin: 0; }

/* The Check tab's count, amber because it is a queue rather than news: these are waiting on a person. */
.shred-tab-count-due { color: var(--color-warning); }

/* Requirements. The id is the thing anyone scanning for a particular one looks at, so it does not wrap;
   the text takes whatever is left. */
.requirement-table td { vertical-align: top; }
.requirement-id { white-space: nowrap; color: var(--color-text-secondary); }
.requirement-text { display: block; margin-bottom: 4px; }

/* The source sentence, collapsed. Open by default would make a 147-row table unreadable; absent would make
   the row a claim with nothing behind it. */
.requirement-quote > summary {
    cursor: pointer;
    font-size: var(--font-size-xs);
    color: var(--color-text-secondary);
}

.requirement-quote blockquote,
.check-item blockquote {
    margin: var(--space-2) 0 0;
    padding-left: var(--space-3);
    border-left: 2px solid var(--color-border);
    font-size: var(--font-size-sm);
    line-height: 1.55;
}

.check-item {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-3) 0;
    border-top: 1px solid var(--color-border);
}

/* docs/ui/shred-requirements.html: a 250px filter rail, the table, and a 380px pane for what is selected.
   The pane sticks to the top of its column rather than stretching, because it is a card about one row and
   a card as tall as a 147-row table is not a card. */
.requirements-layout {
    display: grid;
    grid-template-columns: 250px minmax(0, 1fr) 380px;
    gap: 24px;
    align-items: start;
}

.requirement-filters { display: flex; flex-direction: column; gap: 24px; }
.requirement-search input { width: 100%; }
.filter-group { display: flex; flex-direction: column; gap: 2px; }

/* A filter is a line in a rail here, not a chip: there are as many of them as the shred found work areas,
   and a row of chips at that count wraps into a paragraph. */
.filter-line {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 8px;
    min-height: 32px;
    padding: 0 8px;
    border-radius: var(--radius-md);
    font-size: 13px;
    text-decoration: none;
}

.filter-line:hover { background: var(--color-bg-tertiary); }
.filter-line-on { background: var(--color-border); font-weight: 600; }
.filter-line .data { color: var(--color-text-secondary); }
.filter-line-on .data { color: inherit; }

.requirement-list { display: flex; flex-direction: column; gap: var(--space-3); }
.requirement-list > .muted { margin: 0; }
.data-table tbody tr.requirement-on { background: var(--color-bg-secondary); }
.requirement-on .requirement-text { font-weight: 500; }

.requirement-detail {
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 9px;
    padding: 24px;
    display: flex;
    flex-direction: column;
    gap: 18px;
    align-self: start;

    /* Follows the table rather than scrolling away from it, so a reader working down a long list keeps the
       pane in view -- and is bounded by what is left of the window, so it scrolls inside itself instead of
       growing past the fold. Without the bound, a long requirement pushed its own flag box below the
       viewport and reaching it meant scrolling the whole 356-row table.

       docs/ui/shred-requirements.html says the same thing in its own terms: its <main> is flex-grow: 1 with
       min-height: 0, which is how that sheet makes the columns scroll rather than the page.

       --topbar-height is measured, because this screen's masthead is the page's own and nothing static can
       know how tall a wrapped title makes it. See the script in _Layout. */
    position: sticky;
    top: var(--space-4);
    max-height: calc(100vh - var(--topbar-height) - var(--space-4) - var(--space-8));
    overflow-y: auto;
}

.requirement-detail-text { font-size: 18px; line-height: 1.4; font-weight: 500; }
.requirement-source { display: flex; flex-direction: column; gap: 8px; }

.requirement-source blockquote {
    margin: 0;
    padding: 14px 16px;
    background: var(--color-bg-secondary);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    font-size: 14px;
    line-height: 1.6;
}

.requirement-flag {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding-top: 14px;
    border-top: 1px solid var(--color-border);
}

.requirement-actions { display: flex; gap: 8px; align-items: center; }
.requirement-flagged { color: var(--color-warning); }

/* A figure that is a queue rather than a number: amber only when there is something in it. */
.figure-due { color: var(--color-warning); }

/* The loose quotations, listed rather than carded: there are more of them than of anything that needs
   doing, and the point of the list is that it is long and unalarming. */
.loose-list { margin: var(--space-2) 0 0; padding-left: var(--space-5); display: flex; flex-direction: column; gap: 4px; }
.loose-list li { font-size: var(--font-size-sm); }
.loose-list .muted { margin-left: var(--space-2); }

/* The passage the verifier found. Set as a quotation rather than as prose, because the reader is comparing
   it against another quotation and the two should look like the same kind of thing. */
.evidence { display: flex; flex-direction: column; gap: 4px; }
.loose-list blockquote { margin: 4px 0 0; }

@media (max-width: 1400px) {
    /* The pane drops under the table before the table starts crushing its own columns. */
    .requirements-layout { grid-template-columns: 250px minmax(0, 1fr); }
    .requirement-detail { grid-column: 1 / -1; position: static; }
}

@media (max-width: 1100px) {
    .requirements-layout { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 1100px) {
    .notice-head-main { flex-direction: column; gap: 16px; }
    .notice-head-deadline { align-items: flex-start; }
    .fact-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* The criteria editor, from govops-ui/profile-edit.html.

   Two columns, and the right one is the reason for the page: the mockup puts the match count and the
   excluded-by-rule bars beside the fields rather than under them, so a criterion is read while it is being
   written. 340px for the panel, as the sheet has it. */
.criteria-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: var(--space-6);
    align-items: start;
}

.criteria-main { display: flex; flex-direction: column; gap: var(--space-6); min-width: 0; }

/* A field's own label, 13px and medium -- heavier than .label, which is the 10.5px mono caption. The
   mockup uses both, and a rule heading is the larger of the two. */
.flabel { font-size: var(--font-size-sm); font-weight: 500; }

/* One rule: its name, its exclusion count, its control, and what it does. Separated by a line rather than
   by space, which is how the sheet groups them. */
.criteria-rule {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border);
}

.criteria-rule:first-of-type { padding-top: 0; border-top: 0; }

.criteria-rule-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-4); }

/* A section that describes something not built. Bordered in ink like the mockup's "start from SAM" panel,
   because it is the first thing on the page and a grey box reads as disabled rather than as planned. */
.criteria-group-pending { border: 1px solid var(--color-primary); border-radius: var(--radius-lg); padding: var(--space-5); }

.uei-row { display: flex; gap: var(--space-2); align-items: flex-end; flex-wrap: wrap; }
.uei-row .field { width: 260px; }

/* A value that can be switched off. 52px tall where it carries a second line, which is the mockup's NAICS
   button; one line otherwise. */
.toggle {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    min-height: 44px;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-bg);
    color: var(--color-text);
    font-family: inherit;
    font-size: var(--font-size-base);
    text-align: left;
    cursor: pointer;
}

.toggle-on { background: var(--color-primary); border-color: var(--color-primary); color: var(--color-text-inverse); }
.toggle-note { font-size: var(--font-size-xs); }

.toggle-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-2); }
.chip-rail { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }

/* Removing an excluded word and adding a suggested one are the same gesture in the mockup -- a chip you
   press -- so they share the chip shape and differ only in the border. */
.chip-remove { border: 1px solid var(--color-border); cursor: pointer; }
.chip-add { border: 1px dashed var(--color-text-secondary); background: transparent; cursor: pointer; }

/* One control, several choices, one of them on: the runway floor. Joined rather than spaced, so it reads
   as a single setting with a position. */
.segmented {
    display: flex;
    align-self: flex-start;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    overflow: hidden;
}

.segment {
    min-height: 40px;
    padding: 0 var(--space-4);
    border: 0;
    border-right: 1px solid var(--color-border);
    background: var(--color-bg);
    color: var(--color-text);
    font-family: inherit;
    font-size: var(--font-size-base);
    cursor: pointer;
}

.segment:last-child { border-right: 0; }
.segment-on { background: var(--color-primary); color: var(--color-text-inverse); }

.inline-add { display: flex; gap: var(--space-2); align-items: center; }
.inline-add .field-input { width: 240px; }

/* The panel. Bordered in ink and sticky, because it is what the page is for and the fields below it run
   past a screen. */
.criteria-preview {
    /* The scroll container is .main-content, which already begins BELOW the topbar, so the offset must not
       count the topbar again. With it in, the panel was pushed down by the topbar's height before any
       scrolling: sticky clamps an element DOWN when its resting place is above the offset, and this page's
       topbar carries a subtitle, which makes it about 100px. The panel sat 50px below the card beside it. */
    position: sticky;
    top: var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding: var(--space-5);
    background: var(--color-bg);
    border: 1px solid var(--color-primary);
    border-radius: var(--radius-lg);
}

.preview-count { display: flex; align-items: baseline; gap: var(--space-2); }

/* 44px, as the sheet has it. The number is the one thing on this page somebody looks at from across a
   desk while changing a rule. */
.preview-number { font-size: 2.75rem; line-height: 1; font-weight: 600; letter-spacing: -0.024em; }

.preview-bars {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding-top: var(--space-3);
    border-top: 1px solid var(--color-border);
}

.preview-bar { display: flex; flex-direction: column; gap: var(--space-1); }
.preview-bar-head { display: flex; justify-content: space-between; gap: var(--space-2); font-size: var(--font-size-sm); }
.preview-bar-track { height: 6px; background: var(--color-border); border-radius: 3px; }
.preview-bar-fill { height: 6px; background: var(--color-primary); border-radius: 3px; }

.preview-actions {
    display: flex;
    gap: var(--space-2);
    padding-top: var(--space-3);
    border-top: 1px solid var(--color-border);
}

@media (max-width: 1100px) {
    .criteria-layout { grid-template-columns: minmax(0, 1fr); }
    .criteria-preview { position: static; }
    .toggle-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* What SAM says about the company, under the UEI box. */
.entity-record {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border);
}

.entity-head { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.entity-facts { display: flex; gap: var(--space-4); flex-wrap: wrap; }
.entity-naics { display: flex; flex-direction: column; gap: var(--space-2); align-items: flex-start; }

/* A code SAM lists that the board already screens on. Marked rather than hidden, so the rail shows the
   whole registration and which part of it is in use. Not .chip-on, which the shred filters already use to
   mean a chosen filter and which fills the chip like a control; this one is a label, not a button. */
.chip-claimed { border-color: var(--color-primary); }

/* The app shell is a frame, not a document: .app-shell is 100vh and clips, and .main-content is the one
   thing that scrolls. The document was still offering a scrollbar of its own beside it -- two tracks at the
   right edge, the outer one scrolling nothing the reader wanted. Measured at 900x700: the body was exactly
   608 tall and the shell clipped at 608, while the root reported a 942px scroll area.

   On body rather than html, which is the same thing for the viewport (the root takes its overflow from
   html, and from body when html is visible) and leaves a page that opts out of the shell alone. Sign-in
   does exactly that -- Layout = null, its own document -- and it needs to scroll on a short window. */
.app-body { overflow: hidden; }

/* ---------- The staff area ----------
   One site, two modes. admin-customers.html draws the staff screens with the SAME 232px sidebar, the same
   brand and the same shell, inverted to the ink ground -- and a "Back to the app" link at the bottom. That
   is the design saying this is a mode of the product rather than a second product, and it is worth keeping:
   a separate admin site would need its own sign-in, its own session and its own deployment.

   Only the sidebar inverts. The content keeps the ordinary ground, because a staff member reading a
   customer's plan is reading the same kind of page as the customer does. */
.app-shell-admin .sidebar {
    background: var(--color-text);
    border-right-color: #31353B;
    color: #E9E7E2;
}

.app-shell-admin .sidebar a,
.app-shell-admin .sidebar .nav-item { color: #E9E7E2; }
.app-shell-admin .sidebar .brand-name { color: #E9E7E2; }
.app-shell-admin .sidebar .label,
.app-shell-admin .sidebar .nav-count,
.app-shell-admin .sidebar .nav-note,
.app-shell-admin .sidebar .sidebar-signout { color: #8C9099; }
.app-shell-admin .sidebar .nav-item:hover { background: #262A2F; }
.app-shell-admin .sidebar .nav-item.active { background: #31353B; color: #E9E7E2; font-weight: 600; }
.app-shell-admin .sidebar .sidebar-avatar { background: #E9E7E2; color: var(--color-text); }
.app-shell-admin .sidebar .sidebar-user-name { color: #E9E7E2; }
.app-shell-admin .sidebar .admin-aside,
.app-shell-admin .sidebar .sidebar-foot { border-top-color: #31353B; }

/* Which mode you are in, next to the brand rather than in the page title: the title changes per page and
   this does not. */
.brand-mode { color: #8C9099; }

.admin-aside {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-4) var(--space-3) 0;
    border-top: 1px solid var(--color-border);
}

.nav-note { font-size: var(--font-size-xs); line-height: 1.5; }

/* The way out of the mode, which is what stops the staff area reading as a separate site. */
.admin-back {
    display: flex;
    align-items: center;
    min-height: 40px;
    padding: 0 var(--space-3);
    border: 1px solid #31353B;
    border-radius: var(--radius-md);
    font-size: var(--font-size-sm);
    text-decoration: none;
}

.app-shell-admin .admin-back:hover { background: #262A2F; }

/* A staff screen is a wide form beside a narrow column of context, which is how admin-customer.html draws
   it. The same shape as the criteria editor, and for the same reason: the thing you act on stays in view. */
.admin-columns {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: var(--space-6);
    align-items: start;
}

.admin-main, .admin-side { display: flex; flex-direction: column; gap: var(--space-6); min-width: 0; }
.admin-form { display: flex; flex-direction: column; gap: var(--space-4); }
.form-actions { display: flex; gap: var(--space-2); align-items: center; }

/* A plan is picked, so the label IS the control: the radio is hidden and the box carries the state. */
.toggle-name { font-weight: 500; }
.toggle:has(input:checked) { background: var(--color-primary); border-color: var(--color-primary); color: var(--color-text-inverse); }
.toggle:focus-within { outline: 2px solid var(--color-primary); outline-offset: 2px; }

@media (max-width: 1100px) {
    .admin-columns { grid-template-columns: minmax(0, 1fr); }
}

/* A second line in a table cell is a LINE. .card-note was written for the shred cards, where its parent is
   a flex column and it stacks for free; dropped into a table cell it runs on from the value and reads as
   one string -- "Arbor Federal - IT servicesarbor". Block rather than flex on the cell, which is the
   mistake that took the board's "why it matched" out of its row. */
.data-table .card-note { display: block; }

/* The door into the staff area, in the customer chrome. Bordered like "Back to the app" on the other side,
   so the pair reads as one door with two handles rather than as two unrelated links. */
.sidebar-admin {
    display: flex;
    align-items: center;
    min-height: 40px;
    padding: 0 var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    font-size: var(--font-size-sm);
    text-decoration: none;
}

.sidebar-admin:hover { background: var(--color-bg-secondary); }

/* The search box sits on the END of the filter row, as admin-users.html draws it -- not under it. It was
   wrapping to its own line because the input takes the width it is given and the row is allowed to wrap;
   220px is what the sheet gives it, and at that width the row holds. */
.filter-search { margin-left: auto; }
.filter-search .board-search { width: 220px; }

/* An invite link on screen, which happens only when the mail could not be sent. Monospace and wrapping,
   because it will be copied by hand and a link broken across lines by a space is a link that does not
   work. Bordered so it reads as a thing to take rather than as prose. */
.invite-link {
    display: block;
    padding: var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-bg-secondary);
    font-family: var(--font-mono);
    font-size: var(--font-size-xs);
    line-height: 1.5;
    overflow-wrap: anywhere;
}

/* A heading inside a section that already has one: "Add someone", under the people it adds to. */
.section-subheading { font-size: var(--font-size-base); font-weight: 600; margin: 0; }

/* Actions in a staff form sit on a row, not stacked. .detail-actions stacks because the notice page's
   panel is narrow and a column is right there; in a wide form it stretched "Save plan" across the whole
   card. The aside keeps the stack, because a 340px panel is narrow in exactly the same way and the mockup
   draws its primary button full width. */
.admin-main .admin-form .detail-actions { flex-direction: row; align-items: center; flex-wrap: wrap; }

/* The two halves of a citation comparison. The heading row carries the file the excerpt is FROM, which on
   a wrong-document finding is not the file cited -- so it sits beside the label rather than in the prose,
   where it would read as part of the sentence. */
.evidence-head { display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.evidence-doc { color: var(--color-text-secondary); }

/* Why there is no excerpt. Quieter than a finding and louder than nothing: a reader who cannot tell
   "nothing to find" from "nobody looked" learns to skip both. */
.evidence-absent { border-left: 2px solid var(--color-border); padding-left: var(--space-3); }

/* What the check made of a requirement's quotation, under its source language. Separated by a rule rather
   than a card, because it is the second half of one answer and not a second subject. */
.requirement-check {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding-top: var(--space-3);
    border-top: 1px solid var(--color-border);
}

/* A flag box on a page that is read rather than filled in. Check stacks sixty-one findings; sixty-one open
   textareas turn a worklist into a form, so the box waits behind its summary. The requirement pane keeps
   its box open, because raising a flag is what that pane is for. */
.flag-details > summary {
    cursor: pointer;
    font-size: var(--font-size-sm);
    font-weight: 500;
    padding: var(--space-2) 0;
}

.flag-details[open] > summary { margin-bottom: var(--space-2); }

/* Check, with a pane. The mockup draws it as one column, and the product's own pattern is better here:
   settling a citation is the same act as settling a requirement, which has had a pane on the next tab
   along since it was built. 380px to match it, so the two tabs do not shift under the reader. */
.check-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 380px;
    gap: var(--space-6);
    align-items: start;
}

.check-main { display: flex; flex-direction: column; gap: var(--space-6); min-width: 0; }

@media (max-width: 1100px) {
    .check-layout { grid-template-columns: minmax(0, 1fr); }
}

/* What was flagged on the way here, each one something you can take back out before recording. */
.flag-ride { display: flex; flex-direction: column; gap: var(--space-2); }
.flag-ride-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-3); }

.flag-ride-panel {
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    padding: var(--space-1) var(--space-4) var(--space-3);
}

.flag-ride-list { margin: 0; padding: 0; list-style: none; }

.flag-ride-list li {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--color-border);
}

.flag-ride-list label { flex-grow: 1; font-size: var(--font-size-base); }
.flag-ride-list .data { color: var(--color-text-secondary); }

.gate-notes { display: flex; flex-direction: column; gap: var(--space-2); }

/* Marked when the words ARE the correction rather than a note beside it. */
.gate-notes textarea.is-required { border-color: var(--color-primary); }

/* The four things somebody checks before committing. Rows with a rule between, not figures: figures would
   make them a dashboard, and these are a checklist read once. */
.answer-facts { margin: var(--space-2) 0 0; }

.answer-fact {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) 0;
    border-top: 1px solid var(--color-border);
    font-size: var(--font-size-base);
}

.answer-fact dt { color: var(--color-text); }
.answer-fact dd { margin: 0; text-align: right; }

.answer-scope { max-width: 420px; }
.answer-buttons { display: flex; gap: var(--space-2); }

/* The sheet's sizes on the gate, which are its own and not the shared note scale.

   15px for the line under the question: it is the sentence that says who may answer and what the answer
   does, and at the 13px of a note it read as small print beside a 36px heading. */
.answer-lede { margin: 0; font-size: 15px; line-height: 1.6; color: var(--color-text-secondary); }

/* 13px, as the sheet has the link beside "From Check". */
.flag-ride-head a { font-size: var(--font-size-sm); }

/* 14px for the heading of a note in this aside. The shared card heading is 18px, which is right for a
   panel of content and wrong for a four-line aside beside a form. */
.answer-aside .notice-card-head h2 { font-size: var(--font-size-base); }

/* A one-button form inside a table cell or a list row. A form is block by default, which would push the
   button onto its own line under the thing it acts on. */
.inline-form { display: inline; }

/* The people on a customer, as admin-customer.html draws them: who on the left over their address, where
   they stand on the right, divided by a rule rather than spaced apart. */
.people { margin: 0; padding: 0; list-style: none; }

.person {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) 0;
    border-top: 1px solid var(--color-border);
}

.person-who { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.person-name { display: flex; align-items: center; gap: var(--space-2); font-size: var(--font-size-base); font-weight: 500; }
/* Cut, not broken. An address split mid-word reads as two strings -- "mike+arbor@imerit" over
   "as.com" -- and an address is the one thing on this row somebody might copy. The full text stays in
   the title. */
.person-email {
    color: var(--color-text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.person-state { display: flex; align-items: center; gap: var(--space-2); flex-shrink: 0; }

/* Standing, under the address. Wraps rather than squeezing the row, because a person can be both invited
   and missing an account and the two pills together are wider than this card. */
.person-flags { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: 4px; }

/* admin-users.html gives the register 1fr / 260px / 110px / 110px. Fixed, because the first column is a
   name over an address and will take everything it is given, pushing the three short columns into a
   ragged edge that makes the list hard to scan down. */
.users-table { table-layout: fixed; }
.users-table th:nth-child(2), .users-table td:nth-child(2) { width: 260px; }
.users-table th:nth-child(3), .users-table td:nth-child(3) { width: 120px; }

/* Wider than the sheet's 110px, because this column carries a button the sheet does not have: at 140px
   "Send the link again" broke across two lines inside its own button. */
.users-table th:nth-child(4), .users-table td:nth-child(4) { width: 190px; }

/* The customers somebody may act for, wrapping rather than stretching the row. */
.users-table .chips { flex-wrap: wrap; }

/* One subject per panel on a settings page, and a page of them is still readable at a sensible measure. */
.settings-panel { max-width: 640px; }

/* ── Shred overview: the sheet's figure strip, and sections 04 and 05 ────────────────────────────── */

/* Six tiles over a 2px rule, the same strip the Check tab and the board use -- which is what makes these
   screens read as one product. auto-fit rather than a fixed six, because Figures() leaves out anything
   worth nothing and a shred that produced four would otherwise draw two empty columns. */
.shred-figures {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: var(--space-4);
    border-top: 2px solid var(--color-text);
    padding-top: 14px;
    margin-bottom: var(--space-5);
}

.shred-figure { display: flex; flex-direction: column; gap: 4px; }
.shred-figure-value { font-size: 30px; line-height: 1.1; font-weight: 600; letter-spacing: -0.02em; overflow-wrap: anywhere; }
.shred-figure { min-width: 0; }
/* A qualifier is words, not a number: at phone width the mock's 30px puts three words on three lines. */
@media (max-width: 640px) {
    .shred-figure-value { font-size: 22px; }
}
.shred-figure-detail { font-size: 13px; color: var(--color-text-secondary); }

/* 04 -- one block per volume. */
.volume-list { display: flex; flex-direction: column; }

.volume {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 14px 0;
    border-top: 1px solid var(--color-border);
}

.volume-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-3); }
.volume-name { font-size: var(--font-size-base); font-weight: 600; }
.volume-limit { font-size: 14px; }

/* Format rules are short and read as a set -- font, margins, file type. Wrapped inline rather than
   stacked, because five one-word lines take more height than they are worth. */
.volume-rules {
    display: flex;
    flex-wrap: wrap;
    gap: 6px var(--space-3);
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: 13px;
    color: var(--color-text-secondary);
}

.volume-rules li { position: relative; }
.volume-rules li + li { padding-left: var(--space-3); }
.volume-rules li + li::before { content: "·"; position: absolute; left: 4px; }

/* 05 -- scope as a two-column set of areas, then the deliverables under them. Two columns because the
   fullest shred has 32 areas and one column of 32 is a page of its own. */
.work-areas {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 0 var(--space-5);
}

.work-area {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 12px 0;
    border-top: 1px solid var(--color-border);
}

.work-area-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-2); }
.work-area-name { font-size: 15px; font-weight: 600; }
.work-area-count { white-space: nowrap; }
.work-area-summary { margin: 0; font-size: 14px; line-height: 1.5; color: var(--color-text-secondary); }
.work-area-from { font-size: 12px; }

.deliverables { margin-top: var(--space-4); }

.deliverables-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-3);
    border-top: 2px solid var(--color-text);
    padding-top: 10px;
}

.deliverable-list { margin: 0; padding: 0; list-style: none; }

.deliverable {
    display: flex;
    gap: var(--space-3);
    padding: 10px 0;
    border-top: 1px solid var(--color-border);
    font-size: 14px;
    line-height: 1.5;
}

/* Fixed, so the ids line up down the column and the texts start at one edge. */
.deliverable-id { flex: 0 0 72px; color: var(--color-text-secondary); }

@media (max-width: 52rem) {
    .shred-figures { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-3); }
    .work-areas { grid-template-columns: 1fr; }
}

/* Overview section 03: the award procedure and the ways to be rated unacceptable (SHRED_OVERVIEW_GAPS 3.1, 3.2). */
.stated-list { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-4); }
.stated-items { margin: 0; padding-left: 1.4em; display: flex; flex-direction: column; gap: var(--space-2); }
.stated-items li { line-height: 1.5; }
.stated-items .pill, .stated-items .fact-cite { margin-left: var(--space-2); }

/* Overview section 05: an uncertainty under the work area it concerns (SHRED_OVERVIEW_GAPS 3.4). */
.work-area-unknown { margin: var(--space-1) 0 0; padding-left: var(--space-2); border-left: 2px solid var(--color-warning); color: var(--color-warning); font-size: var(--font-size-sm); line-height: 1.5; }
.work-area-unknown .data { margin-right: var(--space-1); }

/* ── Shred overview: the attention bar, section 06, and the quotation count beside the tabs ──────── */

/* The sheet's bar between the figures and section 01. Card-coloured rather than alarm-coloured: the pill
   inside it carries the severity, and a full-width red band above every shred with one loose quotation
   would read as a broken page rather than a queue. */
.attention {
    display: flex;
    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);
    margin-bottom: var(--space-5);
}

.attention-says { display: flex; align-items: center; gap: var(--space-3); font-size: var(--font-size-base); }

/* The nav's right-hand side. margin-left:auto rather than a wrapper, so the tabs keep their own spacing
   and this takes whatever is left -- and drops below them when there is not enough. */
.shred-tabs-aside {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-left: auto;
    padding-bottom: 12px;
}

/* 06 -- three groups side by side, each a count over a short list. */
.issue-groups {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--space-5);
}

.issue-group { display: flex; flex-direction: column; gap: 4px; }
.issue-count { font-size: 30px; line-height: 1.1; font-weight: 600; letter-spacing: -0.02em; }

.issue-list {
    margin: 6px 0 0 0;
    padding: 0;
    list-style: none;
    font-size: 14px;
    line-height: 1.5;
}

.issue-list li { padding: 6px 0; border-top: 1px solid var(--color-border); }

@media (max-width: 52rem) {
    .attention { flex-direction: column; align-items: flex-start; }
    .shred-tabs-aside { margin-left: 0; padding-bottom: 0; flex-wrap: wrap; }
    .issue-groups { grid-template-columns: 1fr; gap: var(--space-4); }
}

/* ── Shred overview, sections 02-05, laid out after docs/ui/shred-overview.html ── */

/* An open question under the gate or work area it bears on: amber text beside a dot, wrapping, never a pill
   that overflows -- the issues run to a sentence. */
.open-question { display: flex; gap: 6px; align-items: baseline; color: var(--color-warning); font-size: var(--font-size-sm); line-height: 1.45; }
.open-question .dot { flex-shrink: 0; width: 6px; height: 6px; border-radius: 50%; background: currentColor; transform: translateY(-1px); }

/* 02: one row per gate -- the marks and where it is stated, then the condition, then any open question. */
.gate-list { display: flex; flex-direction: column; }
.gate { display: flex; flex-direction: column; gap: 6px; padding: 14px 0; border-top: 1px solid var(--color-border); }
.gate-head { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.gate-cite { margin-left: auto; font-size: 12px; min-width: 0; max-width: 60%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gate-head { flex-wrap: nowrap; }
.gate-head .tag { flex-shrink: 0; }
.gate-text { font-size: 15px; line-height: 1.45; }

/* 03: the award as steps left to right, the last outlined because it is where the award happens. */
.award-steps { display: flex; gap: 8px; align-items: stretch; flex-wrap: wrap; margin-top: var(--space-4); }
.award-step { flex: 1 1 180px; min-width: 0; display: flex; flex-direction: column; gap: 6px; padding: 14px; border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-bg-secondary); }
.award-step-last { border-color: var(--color-text); background: transparent; }
.award-step-text { font-size: 14px; font-weight: 600; line-height: 1.35; }
.award-step-cite { font-size: 12px; line-height: 1.45; }
.award-step-arrow { display: flex; align-items: center; color: var(--color-text-muted); }

/* More than three steps, or steps that run to sentences: down the panel, numbered on a rail. */
.award-steps-down { flex-direction: column; flex-wrap: nowrap; gap: 0; }
.award-steps-down .award-step { flex: none; position: relative; padding: 12px 0 12px 22px; border: 0; border-left: 2px solid var(--color-border); border-radius: 0; background: transparent; }
.award-steps-down .award-step::before { content: ""; position: absolute; left: -6px; top: 16px; width: 10px; height: 10px; border-radius: 50%; background: var(--color-bg); border: 2px solid var(--color-text-muted); }
.award-steps-down .award-step-last { border-left-color: var(--color-border); }
.award-steps-down .award-step-last::before { background: var(--color-text); border-color: var(--color-text); }
.award-steps-down .award-step-text { font-weight: 500; }
.award-step-detail { font-size: 13px; line-height: 1.45; color: var(--color-text-secondary); }
.award-steps-down .award-step-text:has(+ .award-step-detail) { font-weight: 600; }

/* The sentence a headline was shortened from, opened by Full text. */
.fact-detail { font-size: 13px; line-height: 1.5; color: var(--color-text-secondary); }
.graded { display: flex; flex-direction: column; margin-top: var(--space-5); }
.graded > .label { padding-bottom: 8px; }
.graded-row { display: flex; justify-content: space-between; gap: 16px; padding: 10px 0; border-top: 1px solid var(--color-border); font-size: 14px; line-height: 1.45; }
.graded-row > .data { flex-shrink: 0; max-width: 40%; text-align: right; font-size: 12px; }
.graded-row .pill { margin-left: var(--space-2); }

/* 04: a card per volume, the page limit stated prominently, then how to get it in. */
.volume-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; }
.volume-card { display: flex; flex-direction: column; gap: 8px; padding: 16px; border: 1px solid var(--color-border); border-radius: var(--radius-md); }
.volume-card .volume-head { display: flex; flex-direction: column; gap: 2px; }
.volume-card .volume-head .data { font-size: 12px; }
.volume-card .volume-limit { font-size: 17px; font-weight: 600; line-height: 1.3; }
.volume-rule-list { margin: 0; padding-left: 16px; display: flex; flex-direction: column; gap: 6px; font-size: 13px; line-height: 1.45; color: var(--color-text-secondary); }
.submit-block { display: flex; flex-direction: column; gap: 4px; margin-top: var(--space-5); padding-top: var(--space-4); border-top: 2px solid var(--color-text); font-size: 14px; line-height: 1.45; }

/* 05: a card per work area -- name, what it asks for, what is unsettled, and the way into its requirements. */
.area-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px; }
.area-card { display: flex; flex-direction: column; gap: 10px; padding: 16px; border: 1px solid var(--color-border); border-radius: var(--radius-md); }
.area-from { font-size: 11px; }
.area-name { font-size: 17px; font-weight: 600; line-height: 1.3; }
.area-summary { margin: 0; font-size: 13px; line-height: 1.5; color: var(--color-text-secondary); }
.area-lines { margin: 0; padding-left: 16px; display: flex; flex-direction: column; gap: 6px; font-size: 13px; line-height: 1.45; }
.area-view { margin-top: auto; font-size: 13px; font-weight: 500; }

/* A gate's short name, over its condition (prompt .21 on). */
.gate-title { font-size: 15px; font-weight: 600; line-height: 1.35; }
.gate-title + .gate-text { font-size: 13px; color: var(--color-text-secondary); }

/* Following (following.html): the lists beside how you hear about them. */
.following-layout { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 24px; align-items: start; }
.following-main { display: flex; flex-direction: column; gap: 24px; min-width: 0; }
.following-section { display: flex; flex-direction: column; gap: 14px; }
.following-heading { margin: 0; font-size: 18px; font-weight: 600; display: flex; gap: 8px; align-items: baseline; }
.follow-list { background: var(--color-bg); border: 1px solid var(--color-border); border-radius: 9px; overflow: hidden; }
.follow-row { display: grid; grid-template-columns: minmax(0, 1fr) 170px 130px 60px; gap: 16px; padding: 14px 20px; border-bottom: 1px solid var(--color-border); align-items: center; }
.follow-row:last-child { border-bottom: 0; }
.follow-row-head { padding-top: 10px; padding-bottom: 10px; background: var(--color-bg-tertiary); }
.follow-notice { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.follow-closes { display: flex; flex-direction: column; gap: 2px; font-size: 13px; }
.following-aside { background: var(--color-bg); border: 1px solid var(--color-border); border-radius: 9px; padding: 22px; display: flex; flex-direction: column; gap: 18px; align-items: flex-start; }
.following-aside-heading { margin: 0; font-size: 16px; font-weight: 600; }
.following-channel { display: flex; flex-direction: column; gap: 4px; padding-top: 12px; border-top: 1px solid var(--color-border); align-self: stretch; }
.following-channel-name { font-size: 14px; font-weight: 600; }

/* The mockup's switch, on. Pressing it posts the unfollow, so it is a submit button that looks like one. */
.follow-switch { width: 44px; height: 26px; border-radius: 999px; border: 0; padding: 3px; display: flex; justify-content: flex-end; background: var(--color-text); cursor: pointer; }
.follow-switch-knob { width: 20px; height: 20px; border-radius: 50%; background: var(--color-bg); }

/* Updates: a followed notice, what changed on it, and when. */
.update-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) 110px; gap: 16px; padding: 14px 20px; border-bottom: 1px solid var(--color-border); align-items: start; }
.update-row:last-child { border-bottom: 0; }
.update-lines { margin: 0; padding-left: 16px; display: flex; flex-direction: column; gap: 4px; font-size: 13px; line-height: 1.45; }
.follow-switch:focus-visible { outline: 2px solid var(--color-text); outline-offset: 2px; }

/* A follow keyed by notice id will not carry over to the solicitation SAM posts as a new notice. */
.follow-caveat { margin: 0; font-size: 12px; line-height: 1.45; color: var(--color-warning); }

/* A board row listed only because it is followed, with the rules that would otherwise hide it. */
.followed-note { display: block; margin-top: 6px; font-size: 12px; line-height: 1.45; color: var(--color-text-secondary); }

@media (max-width: 900px) {
    .following-layout { grid-template-columns: minmax(0, 1fr); }
    .follow-row { grid-template-columns: minmax(0, 1fr) auto; }
    .follow-row-head { display: none; }
}

/* Reports (admin-reports.html): a section heading with its as-of time, and a figure that is an alarm. */
.report-section { display: flex; flex-direction: column; gap: var(--space-4); margin-bottom: var(--space-6); }
.report-heading { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; }
.report-heading .section-heading { margin: 0; }
.figure-alarm .figure-value, .figure-alarm .figure-detail { color: var(--color-error); }
.figure-warn .figure-value { color: var(--color-warning); }
.report-small-table { width: auto; }

/* Usage (admin-usage.html). */
.usage-month { display: flex; align-items: center; gap: var(--space-2); justify-content: flex-end; margin-bottom: var(--space-4); }

/* The month picker, matched to the .input control in the admin-statement mockup. The markup carried
   class="input" from that mockup, but no .input rule has ever existed here, so the select fell through
   to the browser default and matched nothing.
   Written in tokens rather than the mockup's hex, because the tokens already ARE those values --
   --color-bg is #FBFAF8, --color-border #E2E0DB, --radius-md 6px, --font-size-base 14px, --space-3
   12px -- so this tracks the design system instead of pinning a copy of it.
   40px, which is the height the mockup sets on this control specifically, overriding the 44px its base
   rule carries. */
.usage-month select {
    width: auto;
    height: 40px;
    box-sizing: border-box;
    padding: 0 var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-bg);
    font-family: inherit;
    font-size: var(--font-size-base);
    color: var(--color-text);
}

.usage-month select:focus { outline: none; border-color: var(--color-primary); box-shadow: 0 0 0 2px var(--color-primary-bg); }
.page-content > .board-figures { grid-template-columns: repeat(5, minmax(0, 1fr)); margin-bottom: var(--space-6); }
.figure-of { font-size: 16px; font-weight: 500; color: var(--color-text-secondary); }
@media (max-width: 68rem) { .page-content > .board-figures { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Shreds per day: one bar a day, its height the share of the busiest day. Weekends grey, days to come faint. */
.usage-bars { display: flex; align-items: flex-end; gap: 3px; height: 120px; padding-bottom: 2px; border-bottom: 1px solid var(--color-border); }
.usage-bar { flex: 1; min-width: 0; height: max(var(--h), 2px); background: var(--color-text); border-radius: 2px 2px 0 0; }
.usage-bar-weekend { background: var(--color-text-muted); opacity: 0.55; }
.usage-bar-future { background: var(--color-border); opacity: 1; }
.usage-bars-axis { display: flex; justify-content: space-between; font-size: 11.5px; }

.usage-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 32px; align-items: start; }
@media (max-width: 68rem) { .usage-pair { grid-template-columns: minmax(0, 1fr); } }

/* Reports' cost histogram: five columns, the count over each bar, adopted ones in grey because they cost nothing. */
.cost-bars { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; align-items: end; height: 150px; border-bottom: 1px solid var(--color-border); }
.cost-bar { display: flex; flex-direction: column; align-items: stretch; justify-content: flex-end; gap: 4px; height: 100%; text-align: center; font-size: 12px; }
.cost-bar-fill { display: block; height: max(var(--h), 2px); max-height: 100px; background: var(--color-text); border-radius: 2px 2px 0 0; }
.cost-bar-free { background: var(--color-text-muted); opacity: 0.55; }
.cost-bar-label { font-size: 11.5px; color: var(--color-text-secondary); margin-bottom: -22px; }
.cost-bars + .usage-list { margin-top: 28px; }

/* Staff billing per customer: read first, forms open on demand. */
.billing-figures { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.billing-disclosure { display: flex; flex-direction: column; gap: var(--space-3); }
.billing-disclosure > summary { list-style: none; align-self: flex-start; cursor: pointer; }
.billing-disclosure > summary::-webkit-details-marker { display: none; }
.billing-disclosure[open] > summary { margin-bottom: var(--space-2); }
.form-actions { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }

/* Customer standing: a line under the title, amber or red when held, with its three buttons. */
.standing-bar { display: flex; justify-content: space-between; align-items: center; gap: var(--space-4); flex-wrap: wrap; padding: 12px 16px; margin-bottom: var(--space-4); border: 1px solid var(--color-border); border-radius: 9px; background: var(--color-bg); }
.standing-bar-held { border-color: var(--color-warning); }
.standing-says { display: flex; align-items: baseline; gap: var(--space-3); flex-wrap: wrap; min-width: 0; flex: 1 1 360px; }
.standing-form { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.standing-form .field-input { width: 220px; min-height: 32px; }

/* A group of choices laid out like any other field: no browser fieldset box. */
fieldset.field { border: 0; padding: 0; margin: 0; min-width: 0; }
fieldset.field > legend { padding: 0; margin-bottom: 8px; }

/* A customer's history: newest first, the date at the right. */
.history-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.history-list li { display: flex; justify-content: space-between; gap: 12px; padding: 8px 0; border-top: 1px solid var(--color-border); font-size: 13px; line-height: 1.45; }
.history-list li time { flex-shrink: 0; font-size: 12px; }

/* A short list of labelled counts, one per ruled line. */
.usage-list { margin: 0; display: flex; flex-direction: column; }
.usage-list > div { display: flex; justify-content: space-between; gap: 16px; padding: 10px 0; border-top: 1px solid var(--color-border); font-size: 14px; }
.usage-list dt { margin: 0; }
.usage-list dd { margin: 0; text-align: right; }

/* A shred the documents have moved past: amber, inside the card it concerns. */
.update-alert { display: flex; flex-direction: column; gap: 4px; padding: 12px 14px; margin-bottom: var(--space-3); border: 1px solid var(--color-warning); border-radius: 6px; font-size: 13px; line-height: 1.5; }
.update-alert strong { color: var(--color-warning); font-size: 14px; }
.update-alert-action { margin-top: 6px; }

/* A segmented control whose choices are links, on staff Shreds. */
a.segment { display: inline-flex; align-items: center; text-decoration: none; }

/* The account menu: the name in the sidebar foot opens Usage, Billing, Settings and Sign out, upward. */
.user-menu { position: relative; }
.user-menu > summary { list-style: none; cursor: pointer; border-radius: var(--radius-md); padding: 6px 8px; }
.user-menu > summary::-webkit-details-marker { display: none; }
.user-menu > summary:hover, .user-menu[open] > summary { background: var(--color-bg-secondary); }
.user-menu > summary:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.sidebar-user-hint { font-family: var(--font-mono); font-size: 12px; line-height: 1.5; color: var(--color-text-secondary); }
.user-menu-caret { margin-left: auto; width: 8px; height: 8px; border-right: 1.5px solid currentColor; border-top: 1.5px solid currentColor; transform: rotate(-45deg); opacity: 0.6; }
.user-menu[open] .user-menu-caret { transform: rotate(135deg); }
.user-menu-panel {
    position: absolute;
    left: 0;
    right: 0;
    bottom: calc(100% + 6px);
    display: flex;
    flex-direction: column;
    padding: 6px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
    z-index: 20;
}
.user-menu-item {
    display: flex;
    align-items: center;
    min-height: 36px;
    padding: 0 10px;
    border: 0;
    border-radius: var(--radius-sm, 4px);
    background: none;
    color: var(--color-text);
    font-family: inherit;
    font-size: var(--font-size-sm);
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    width: 100%;
}
.user-menu-item:hover, .user-menu-item:focus-visible { background: var(--color-bg-secondary); }
.user-menu-signout { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--color-border); }

/* The Terms of Service: the text in a readable measure, then the box and the button. */
.terms-page { max-width: 860px; }
.terms-text { max-height: 60vh; overflow-y: auto; }
.terms-accept { display: flex; flex-direction: column; gap: var(--space-3); margin-top: var(--space-4); }
.terms-agree { display: flex; gap: var(--space-3); align-items: flex-start; font-size: var(--font-size-sm); line-height: 1.5; }
.terms-agree input { margin-top: 3px; width: 18px; height: 18px; flex-shrink: 0; }
.terms-decline { margin-top: var(--space-2); }
