/* =============================================================================
   design-system.css — the interface rulebook, in code.
   Owner-approved 2026-08-07 across seven forks; the rules themselves live in
   .ai/design-system/RULEBOOK.md and this file is their only implementation.

   TWO INVARIANTS, both machine-checked by orderly/tests/test_design_system.py:

   1. EVERY selector here is scoped under `.ds`. Not one bare element selector,
      not one override of a legacy brick (.button/.table-list/.table-form/
      .status-chip/.page-menu/.sidebar/.messages). A screen opts in by putting
      `ds` on <body>; a screen that does not opt in cannot be touched by this
      file, no matter what we add to it later. That is what makes "the pilot is
      two screens" a fact rather than a promise — the other 108 templates are
      unreachable from here, and the two non-pilot pixel baselines prove it.

   2. Anything that sets `display` is paired with a [hidden] rule. This is not
      theory: it already broke in production once (see the comment in
      sitebase/templates/sitebase/execution.html) when an author's display:flex
      beat the browser's [hidden] and the red "saving is failing" strip hung
      permanently for the Execution team. The pairing lives at the bottom of
      this file and covers every brick at once.

   Colour tokens are NOT redefined here — they are the ones already in
   base.html's :root and stay the single source. What this file adds is the
   scales that were missing: spacing, type, control height.
   ============================================================================= */

.ds {
    /* Spacing scale — no other values exist. */
    --s1: 4px;
    --s2: 8px;
    --s3: 12px;
    --s4: 16px;
    --s6: 24px;
    --s8: 32px;
    --s12: 48px;

    /* Type scale — five sizes, replacing the eleven that grew by accident. */
    --fs-micro: 11px;   /* uppercase section labels, table headers */
    --fs-hint:  12px;   /* the one-sentence hint under a field */
    --fs-body:  13px;   /* working screens, tables, inputs */
    --fs-lead:  15px;   /* dialogs and the rare reading screen */
    --fs-title: 20px;   /* screen title */

    /* Control height — 24 / 32 / 40, nothing between. 32 is the default and is
       deliberately the same number for a table row, an input and a button, so
       rows, fields and buttons line up on one grid. (IBM Carbon fixes its data
       table rows at 24/32/40/64; we take the working middle.) */
    --h: 32px;
    --h-sm: 24px;
    --h-lg: 40px;

    --ds-radius: 4px;

    font-size: var(--fs-body);
    line-height: 1.5;
}

/* ============ BRICK: screen header ============ */
.ds .ds-pagehead {
    display: flex;
    align-items: center;
    gap: var(--s3);
    flex-wrap: wrap;
    padding-bottom: var(--s3);
    border-bottom: 2px solid var(--color-text);
}
.ds .ds-pagehead h1 {
    font-size: var(--fs-title);
    font-weight: 650;
    letter-spacing: -0.01em;
}
.ds .ds-pagehead .ds-sp { flex: 1; }

/* ============ BRICK: section heading ============ */
.ds .ds-sect {
    display: flex;
    gap: var(--s2);
    align-items: baseline;
    font-size: var(--fs-micro);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    margin: var(--s6) 0 var(--s2);
    padding-bottom: var(--s1);
    border-bottom: 1px solid var(--color-border);
}
.ds .ds-sect .ds-n { color: var(--color-text); }

/* ============ BRICK: button ============ */
.ds .ds-btn {
    height: var(--h);
    padding: 0 var(--s4);
    border: 1px solid var(--color-accent);
    background: var(--color-accent);
    color: #fff;
    border-radius: var(--ds-radius);
    font: inherit;
    font-size: var(--fs-body);
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    flex: none;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.ds .ds-btn:hover { background: var(--color-accent-hover); border-color: var(--color-accent-hover); }
.ds .ds-btn.ds-quiet { background: var(--color-surface); color: var(--color-text); border-color: var(--color-border-strong); }
.ds .ds-btn.ds-quiet:hover { background: var(--color-surface-alt); }
.ds .ds-btn.ds-ghost { background: var(--color-surface); color: var(--color-accent); border-color: var(--color-border-strong); }
.ds .ds-btn.ds-danger { background: var(--color-surface); color: var(--color-danger); border-color: var(--color-border-strong); }
/* Filled red is reserved for the ONE destructive action a confirm screen exists to
   ask about. Everywhere else a destructive control uses the outline form above, so
   that a page full of red never trains people to click through it. */
.ds .ds-btn.ds-danger-solid { background: var(--color-danger); border-color: var(--color-danger); color: #fff; }
/* Darkened on hover with a filter, not with a second red typed by hand: there is no
   «danger hover» token, and inventing one here is how a palette grows to 261 colours.
   base.html already darkens a row the same way. */
.ds .ds-btn.ds-danger-solid:hover { filter: brightness(0.92); }
.ds .ds-btn.ds-sm { height: var(--h-sm); padding: 0 var(--s2); font-size: var(--fs-hint); }

/* ============ BRICK: field (label + input + ONE hint) ============ */
.ds .ds-field { margin-bottom: var(--s3); }
.ds .ds-field > label {
    display: block;
    font-size: var(--fs-body);
    font-weight: 600;
    margin-bottom: var(--s1);
}
.ds .ds-in {
    height: var(--h);
    width: 100%;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--ds-radius);
    padding: 0 10px;
    font: inherit;
    font-size: var(--fs-body);
    background: var(--color-surface);
    color: var(--color-text);
}
.ds textarea.ds-in { height: auto; min-height: 96px; padding: var(--s2) 10px; }
.ds select.ds-in { padding: 0 6px; }
.ds .ds-in:focus { outline: 2px solid var(--color-accent); outline-offset: -1px; border-color: var(--color-accent); }
.ds .ds-in[readonly] { background: var(--color-surface-alt); color: var(--color-text-muted); }
/* The hint is ONE short sentence. Anything longer belongs on the help page. */
.ds .ds-hint { font-size: var(--fs-hint); color: var(--color-text-muted); margin-top: var(--s1); }
.ds .ds-src  { font-size: var(--fs-hint); color: var(--color-info); margin-top: var(--s1); }

.ds .ds-row2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s4); }
.ds .ds-row3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s4); }
.ds .ds-row4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s4); }

/* ============ BRICK: action bar ============ */
.ds .ds-actions {
    display: flex;
    gap: var(--s2);
    flex-wrap: wrap;
    align-items: center;
    margin: var(--s4) 0;
}

/* ============ BRICK: dense table — row is exactly 32px ============ */
.ds .ds-tblwrap {
    border: 1px solid var(--color-border);
    border-radius: var(--ds-radius);
    overflow: auto;
    background: var(--color-surface);
}
.ds table.ds-tbl {
    width: 100%;
    border-collapse: collapse;
    background: var(--color-surface);
    font-size: var(--fs-body);
    font-variant-numeric: tabular-nums;   /* digits line up under digits */
}
.ds table.ds-tbl th {
    height: var(--h);
    padding: 0 10px;
    text-align: left;
    font-size: var(--fs-micro);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    white-space: nowrap;
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border-strong);
    position: sticky;
    top: 0;
    z-index: 2;
}
.ds table.ds-tbl td {
    height: var(--h);
    padding: 0 10px;
    border-bottom: 1px solid var(--color-border);
}
.ds table.ds-tbl td.ds-num,
.ds table.ds-tbl th.ds-num { text-align: right; }
.ds table.ds-tbl tbody tr:hover { background: var(--color-accent-soft); }
.ds table.ds-tbl tbody tr.ds-total td { font-weight: 650; background: var(--color-surface-alt); }
.ds table.ds-tbl.ds-dense  th, .ds table.ds-tbl.ds-dense  td { height: var(--h-sm); font-size: var(--fs-hint); }
.ds table.ds-tbl.ds-roomy  th, .ds table.ds-tbl.ds-roomy  td { height: var(--h-lg); }

/* ============ BRICK: metric tile ============ */
.ds .ds-tiles {
    display: grid;
    /* A track may grow to 260px and no further. With «1fr» a single tile ate the
       whole row and read as a banner, not a tile — the owner's complaint on
       2026-08-09. Capping the track keeps one tile and five tiles the same shape. */
    grid-template-columns: repeat(auto-fit, minmax(150px, 260px));
    gap: var(--s3);
    margin: var(--s3) 0;
}
.ds .ds-tile {
    border: 1px solid var(--color-border);
    border-radius: var(--ds-radius);
    background: var(--color-surface);
    padding: var(--s3);
}
.ds .ds-tile .ds-k {
    font-size: var(--fs-micro);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    font-weight: 700;
}
.ds .ds-tile .ds-v {
    font-size: var(--fs-title);
    font-weight: 650;
    font-variant-numeric: tabular-nums;
    margin-top: var(--s1);
}
.ds .ds-tile .ds-s { font-size: var(--fs-hint); color: var(--color-text-muted); }

/* ============ BRICK: card and key/value list ============ */
.ds .ds-card {
    border: 1px solid var(--color-border);
    border-radius: var(--ds-radius);
    background: var(--color-surface);
    padding: var(--s4);
    margin-bottom: var(--s3);
}
.ds .ds-kv {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: var(--s2) var(--s4);
    font-size: var(--fs-body);
}
.ds .ds-kv dt { color: var(--color-text-muted); }
.ds .ds-kv dd { font-weight: 600; }

/* ============ BRICKS: the five mandatory screen states ============
   ideal / empty / loading / partial / error. A screen is not finished until
   all five are drawn — the state vocabulary, not decoration. */
.ds .ds-empty {
    border: 1px dashed var(--color-border-strong);
    border-radius: var(--ds-radius);
    background: var(--color-surface);
    padding: var(--s8);
    text-align: center;
}
.ds .ds-empty h3 { font-size: var(--fs-lead); margin-bottom: var(--s1); }
.ds .ds-empty p  { color: var(--color-text-muted); margin-bottom: var(--s3); }

.ds .ds-skel {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--ds-radius);
    padding: var(--s3);
}
.ds .ds-skel i {
    display: block;
    height: 12px;
    background: var(--color-surface-alt);
    border-radius: 3px;
    margin-bottom: 9px;
}
.ds .ds-skel i:nth-child(1) { width: 72%; }
.ds .ds-skel i:nth-child(2) { width: 54%; }
.ds .ds-skel i:nth-child(3) { width: 66%; }
.ds .ds-skel i:nth-child(4) { width: 45%; }
.ds .ds-skel i:last-child   { margin-bottom: 0; }

.ds .ds-partial {
    background: var(--color-warning-soft);
    border-left: 3px solid var(--color-warning);
    border-radius: var(--ds-radius);
    padding: var(--s2) var(--s3);
    font-size: var(--fs-hint);
    margin-bottom: var(--s2);
}

/* An error MUST answer "is my data safe?". If it is, say so in words — that is
   the project's red line expressed in the interface layer. */
.ds .ds-error {
    background: var(--color-surface);
    border: 1px solid var(--color-danger);
    border-left-width: 3px;
    border-radius: var(--ds-radius);
    padding: var(--s3);
}
.ds .ds-error b    { display: block; margin-bottom: var(--s1); }
.ds .ds-error span { color: var(--color-text-muted); }

/* ============ BRICK: no-access notice ============
   "Where the right is missing, hide the control" (owner, 2026-08-05). When a
   whole screen is out of reach the screen says so instead of rendering an
   empty shell. */
.ds .ds-noaccess {
    border: 1px dashed var(--color-border-strong);
    border-radius: var(--ds-radius);
    background: var(--color-surface);
    padding: var(--s8);
    text-align: center;
    color: var(--color-text-muted);
}
.ds .ds-noaccess b {
    display: block;
    color: var(--color-text);
    font-size: var(--fs-lead);
    margin-bottom: var(--s1);
}

/* ============ Spacing utilities ============
   The only sanctioned way to add space, so that `style="margin-bottom:14px"`
   never needs to be written again. */
/* base.html's reset strips list markers from every element, so a list that is
   genuinely a list has to ask for them back. */
.ds .ds-list { list-style: disc; padding-left: 20px; margin-bottom: var(--s3); }
.ds .ds-list li { margin-bottom: var(--s1); }
.ds .ds-list li:last-child { margin-bottom: 0; }

.ds .ds-mb2 { margin-bottom: var(--s2); }
.ds .ds-mb3 { margin-bottom: var(--s3); }
.ds .ds-mb4 { margin-bottom: var(--s4); }
.ds .ds-mb6 { margin-bottom: var(--s6); }
.ds .ds-narrow { max-width: 560px; }
.ds .ds-center { justify-content: center; }

/* ============ BRICK: message strip — TWO EXPLICIT LEVELS ============
   Until now the configurator decided «is this red?» by looking for «⚠️» inside
   the sentence. Delete the emoji (the rulebook says words, not pictures) and
   every shortage silently turns calm. So loudness is a named level carried by
   the markup, never a character in the text:

     ds-msg ds-alert   — the plan is at risk / something is refused. Red.
     ds-msg ds-notice  — a fact worth saying once. Calm, never red.

   Two, not five: a scale nobody can tell apart is a scale nobody reads. */
.ds .ds-msg {
    border: 1px solid var(--color-border);
    border-left-width: 3px;
    border-radius: var(--ds-radius);
    background: var(--color-surface);
    padding: var(--s2) var(--s3);
    margin-bottom: var(--s2);
    font-size: var(--fs-body);
}
.ds .ds-msg.ds-alert {
    border-color: var(--color-danger);
    background: var(--color-danger-soft);
}
.ds .ds-msg.ds-notice {
    border-color: var(--color-border-strong);
    background: var(--color-surface-alt);
}
.ds .ds-msg b { font-weight: 650; }

/* ============ BRICK: tag ============
   A short factual mark on a row — «ниже планки», «повтор», «язык». NOT a state:
   state is .status-chip and only .status-chip (rulebook §4). A tag says
   something extra about a row that already has a state. */
.ds .ds-tag {
    display: inline-block;
    padding: 0 var(--s2);
    border: 1px solid var(--color-border-strong);
    border-radius: 999px;
    background: var(--color-surface-alt);
    color: var(--color-text-muted);
    font-size: var(--fs-micro);
    font-weight: 650;
    white-space: nowrap;
}

/* ============ BRICK: mark on an ACCEPTED value ============
   Deliberately not shaped like an error: the value IS saved. This is the
   interface half of the project's red line — we never refuse input, so when we
   have something to say about it we say it next to the value that stayed. */
.ds .ds-mark {
    margin-top: var(--s1);
    font-size: var(--fs-hint);
    color: var(--color-warning);
}
/* A form error is the opposite case: the value did NOT pass. */
.ds .ds-err { margin-top: var(--s1); font-size: var(--fs-hint); color: var(--color-danger); }
.ds .ds-err ul { list-style: none; }

/* ============ BRICK: name of a number ============
   The label half of a figure, wherever it is not already a <th> or a <label>.
   It carries a class rather than a shape so the numbers-glossary guard can find
   every place a number is NAMED and check that name against the dictionary. */
.ds .ds-name { color: var(--color-text-muted); }

/* ============ BRICK: two-zone work split ============
   Working area on the left, the result of the current settings on the right.
   Collapses to one column when the window cannot hold both. */
.ds .ds-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 360px;
    gap: var(--s4);
    align-items: start;
}
@media (max-width: 1100px) {
    .ds .ds-split { grid-template-columns: minmax(0, 1fr); }
}

/* Value emphasis, by meaning and not by decoration. */
.ds .ds-bad  { color: var(--color-danger); }
.ds .ds-good { color: var(--color-success); }
.ds .ds-quietnote { color: var(--color-text-muted); font-size: var(--fs-hint); }
.ds .ds-break { word-break: break-all; }

/* A second level of detail. The rulebook allows two levels of disclosure and no
   more; this is the second one. */
.ds .ds-more { margin: var(--s4) 0; }
.ds .ds-more > summary {
    cursor: pointer;
    font-size: var(--fs-body);
    font-weight: 650;
    padding: var(--s2) 0;
}

/* =============================================================================
   BRICKS ADDED FOR THE CONFIGURATOR LAYOUT (redesign v2, увеличение 3).

   Every one of these exists because the owner-approved mockup
   (`sitebase/static/sitebase/preview/configurator-v2-*.html` and
   `configurator-done-v2-*.html`) shows a SHAPE the library did not have, and the
   rulebook's answer to «this screen needs a new shape» is a brick, never a
   `style=`. The mockup drew each of them with hand-typed colours; here they are
   rebuilt out of the palette tokens, so the two screens gain the layout without
   the CRM gaining a 262nd colour.
   ============================================================================= */

/* ============ BRICK: screen head ============
   Eyebrow (what kind of screen this is) + title + one sentence, with the live
   state on the right. The eyebrow answers «where am I» before the title has to. */
.ds .ds-screenhead {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--s4);
    flex-wrap: wrap;
    margin-bottom: var(--s4);
}
.ds .ds-eyebrow {
    font-size: var(--fs-micro);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--color-accent);
}
.ds .ds-screenhead h1 {
    font-size: var(--fs-title);
    font-weight: 650;
    letter-spacing: -0.01em;
    margin-top: var(--s1);
}
.ds .ds-sub { color: var(--color-text-muted); font-size: var(--fs-hint); margin-top: var(--s1); }
/* «пересчитывается сразу» / «обновляется по ходу работы» — a state of the screen,
   not of a row, so it is deliberately not a .status-chip. */
.ds .ds-live {
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    color: var(--color-success);
    font-size: var(--fs-hint);
    font-weight: 650;
    white-space: nowrap;
}
.ds .ds-live i {
    display: block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--color-success);
    flex: none;
}

/* ============ BRICK: card head (title + hint left, badge right) ============ */
.ds .ds-cardhead {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--s3);
    padding-bottom: var(--s2);
    margin-bottom: var(--s3);
    border-bottom: 1px solid var(--color-border);
}
.ds .ds-cardhead h2 { font-size: var(--fs-lead); font-weight: 650; }
.ds .ds-badge {
    display: inline-flex;
    align-items: center;
    flex: none;
    height: var(--h-sm);
    padding: 0 var(--s2);
    border-radius: 999px;
    background: var(--color-accent-soft);
    color: var(--color-accent-on-soft);
    font-size: var(--fs-micro);
    font-weight: 700;
    white-space: nowrap;
}

/* ============ BRICK: brief strip ============
   The client's brief and the screen, side by side, one cell per parameter. The
   comparison chip under each value is deliberately CALM on a divergence: the ТЗ
   is read by a parser that is not always right, and red here would cry wolf at
   the PM every single day (owner, 07.08.2026). */
.ds .ds-brief {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    border: 1px solid var(--color-border);
    border-radius: var(--ds-radius);
    background: var(--color-surface);
    margin-bottom: var(--s3);
}
.ds .ds-briefitem {
    min-width: 0;
    padding: var(--s3);
    border-right: 1px solid var(--color-border);
}
.ds .ds-briefitem:last-child { border-right: 0; }
.ds .ds-bk {
    font-size: var(--fs-micro);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    font-weight: 700;
}
.ds .ds-bv {
    display: flex;
    align-items: center;
    gap: var(--s2);
    min-height: var(--h);
    margin-top: var(--s1);
    font-size: var(--fs-lead);
    font-weight: 650;
}
.ds .ds-bv .ds-in { width: 88px; }
.ds .ds-cmp {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s2);
    margin-top: var(--s1);
    font-size: var(--fs-micro);
}
.ds .ds-cmp-now {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--color-text-muted);
}
.ds .ds-cmp-st {
    flex: none;
    padding: 0 var(--s2);
    border-radius: 999px;
    font-weight: 700;
    background: var(--color-surface-alt);
    color: var(--color-text-muted);
}
.ds .ds-cmp.ds-same .ds-cmp-st { background: var(--color-success-soft); color: var(--color-success); }

/* ============ BRICK: condition table ============
   Parameter · the control that sets it · how much of the base is left after it.
   The third column is the whole point of the shape: the PM reads the price of
   every condition in the same row where they change it. */
.ds table.ds-cond { table-layout: fixed; }
.ds table.ds-cond col:nth-child(1) { width: 22%; }
.ds table.ds-cond col:nth-child(3) { width: 21%; }
.ds table.ds-cond td { height: auto; padding: var(--s2) 10px; vertical-align: middle; }
.ds .ds-cname { display: block; font-weight: 650; }
.ds .ds-chelp {
    display: block;
    font-size: var(--fs-micro);
    font-weight: 400;
    color: var(--color-text-muted);
}
.ds .ds-cleft small {
    display: block;
    font-size: var(--fs-micro);
    font-weight: 400;
    color: var(--color-text-muted);
}
.ds .ds-cleft.ds-inert { font-weight: 400; color: var(--color-text-muted); }
/* One control line inside a cell: a select and its companion, or «от — до». */
.ds .ds-ctl {
    display: flex;
    align-items: center;
    gap: var(--s2);
    flex-wrap: wrap;
    min-width: 0;
}
.ds .ds-ctl > .ds-in { width: auto; flex: 1 1 130px; min-width: 88px; }
.ds .ds-ctl .ds-sep { flex: none; color: var(--color-text-muted); }
.ds table.ds-cond .ds-field { margin-bottom: 0; }
.ds table.ds-cond .ds-field > label { font-weight: 400; }

/* ============ BRICK: result panel ============
   The answer to the current settings, with ITS action inside it. The owner's
   mockup puts «Закрепить набор» and «Принять заказ» inside this panel on
   purpose: the button belongs next to the numbers it acts on, not in a bar at
   the bottom of the screen where the numbers are already off-screen. */
.ds .ds-panel {
    border: 1px solid var(--color-border);
    border-radius: var(--ds-radius);
    background: var(--color-surface);
    overflow: hidden;
    margin-bottom: var(--s3);
}
.ds .ds-panel.ds-sticky { position: sticky; top: var(--s3); }
.ds .ds-panelhead {
    padding: var(--s3) var(--s4);
    background: var(--color-sidebar-bg);
    color: #fff;
}
.ds .ds-panelhead .ds-kicker {
    font-size: var(--fs-micro);
    letter-spacing: 0.07em;
    text-transform: uppercase;
    font-weight: 700;
    color: var(--color-sidebar-text);
}
/* The colour is restated on the heading itself: base.html gives every h2 an
   explicit colour, so «inherit from the dark head» loses and the title came out
   dark-on-dark. */
.ds .ds-panelhead h2 { font-size: var(--fs-lead); font-weight: 650; margin-top: var(--s1); color: #fff; }
.ds .ds-panelhead p { font-size: var(--fs-hint); color: var(--color-sidebar-text); margin-top: var(--s1); }
.ds .ds-panelbody { padding: var(--s3) var(--s4) var(--s4); }
.ds .ds-panelbody .ds-tiles { margin-top: 0; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); }
.ds .ds-panelbody .ds-sect:first-child { margin-top: 0; }
.ds .ds-btn.ds-block { width: 100%; height: var(--h-lg); }
.ds .ds-ctanote {
    margin-top: var(--s2);
    font-size: var(--fs-micro);
    color: var(--color-text-muted);
    text-align: center;
}

/* ============ BRICK: outcome note ============
   One sentence directly under a group of figures, saying what they add up to. */
.ds .ds-note {
    background: var(--color-surface-alt);
    border-radius: var(--ds-radius);
    padding: var(--s2) var(--s3);
    margin-bottom: var(--s2);
    font-size: var(--fs-hint);
}
.ds .ds-note b { font-weight: 650; }

/* ============ BRICK: filter pills ============
   A named subset of the list below, with its own count on the pill. Not a
   .status-chip: a chip REPORTS a state, a pill is a control that selects one. */
.ds .ds-pills { display: flex; flex-wrap: wrap; gap: var(--s2); margin-bottom: var(--s3); }
.ds .ds-pill {
    display: inline-flex;
    align-items: center;
    gap: var(--s1);
    height: var(--h-sm);
    padding: 0 var(--s3);
    border: 1px solid var(--color-border-strong);
    border-radius: 999px;
    background: var(--color-surface);
    color: var(--color-text);
    font: inherit;
    font-size: var(--fs-hint);
    font-weight: 600;
    cursor: pointer;
}
.ds .ds-pill i { font-style: normal; font-weight: 400; color: var(--color-text-muted); }
.ds .ds-pill.ds-on { background: var(--color-accent); border-color: var(--color-accent); color: #fff; }
.ds .ds-pill.ds-on i { color: var(--color-accent-soft); }

/* ============ BRICK: choice line ============
   A radio or checkbox with the sentence that explains it, on one line. */
.ds label.ds-choice {
    display: flex;
    align-items: flex-start;
    gap: var(--s2);
    margin-bottom: var(--s1);
    font-size: var(--fs-body);
    font-weight: 400;
}
.ds label.ds-choice input { flex: none; margin-top: 3px; }

/* ============ BRICK: fact line ============
   A fact nobody ordered and nobody has to act on — stated once, calmly, and
   never dressed up as a divergence. */
.ds .ds-fact {
    margin-top: var(--s3);
    padding: var(--s2) var(--s3);
    border: 1px solid var(--color-border);
    border-radius: var(--ds-radius);
    background: var(--color-surface-alt);
    font-size: var(--fs-body);
}

/* ============ BRICK: risk card ============
   A destructive path described in full BEFORE its button is offered. The button
   itself stays an outline (.ds-danger): filled red is reserved for the confirm
   screen this card leads to. */
.ds .ds-card.ds-risk {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--s4);
    flex-wrap: wrap;
    border-color: var(--color-danger);
    background: var(--color-danger-soft);
}
.ds .ds-card.ds-risk h2 {
    font-size: var(--fs-lead);
    font-weight: 650;
    color: var(--color-danger);
    margin-bottom: var(--s1);
}

/* ============ INVARIANT 2: the [hidden] pairing ============
   Every brick above that sets `display` is overridden here when the element
   carries [hidden]. Without this a brick's display:flex silently beats the
   browser default and a hidden warning becomes a permanently visible one —
   which is exactly the production incident this rule exists to prevent.
   Keep this block LAST so it wins on equal specificity, and never remove it. */
.ds [hidden] { display: none !important; }
