/*
 * Design system: components. Classes are namespaced `.ds-`; values come from tokens.css through var(--ds-*).
 */

/* ═══ Button ═══════════════════════════════════════════════════════════════
   Style: (default) | .ds-btn-white | .ds-btn-accent | .ds-btn-ghost | .ds-btn-danger
   Size:  .ds-btn-xs | .ds-btn-sm | (default) | .ds-btn-lg
   Type is implied by content: icon only, text only, or both. */

.ds-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--ds-space-2);
    box-sizing: border-box;
    min-height: var(--ds-control-height);
    padding: var(--ds-control-pad) var(--ds-btn-pad-x);

    /* No border: White's stroke is an inset box-shadow, so every button keeps its 42px. */
    border: 0;
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
    background: var(--ds-bnw-900);
    color: var(--ds-color-text);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-button);
    font-weight: var(--ds-weight-medium);
    line-height: var(--ds-leading-control);

    /* Always one line. In a fixed width, wrap the text in `.ds-btn-label` to ellipsise it. */
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
    transition: background var(--ds-transition-fast), opacity var(--ds-transition-fast);
}

/* `:only-child` ignores text nodes, so `[aria-label]` is what marks the icon-only button. */
.ds-btn[aria-label]:has(> .ds-icon:only-child) {
    width: var(--ds-control-height);
    padding: var(--ds-control-pad);
}

/* A bare text node is invisible to selectors, so the trailing case needs `.ds-btn-label` before the icon. */
.ds-btn:not([aria-label]):has(> .ds-icon:first-child) { padding-left: var(--ds-btn-pad-icon); }
.ds-btn:not([aria-label]):has(> .ds-icon:last-child:not(:first-child)) { padding-right: var(--ds-btn-pad-chevron); }

.ds-btn-xs:not([aria-label]):has(> .ds-icon:first-child) { padding-left: var(--ds-btn-pad-icon-xs); }
.ds-btn-xs:not([aria-label]):has(> .ds-icon:last-child:not(:first-child)) { padding-right: var(--ds-btn-pad-chevron-xs); }

.ds-btn-sm:not([aria-label]):has(> .ds-icon:first-child) { padding-left: var(--ds-btn-pad-icon-sm); }
.ds-btn-sm:not([aria-label]):has(> .ds-icon:last-child:not(:first-child)) { padding-right: var(--ds-btn-pad-chevron-sm); }

.ds-btn-lg:not([aria-label]):has(> .ds-icon:first-child) { padding-left: var(--ds-btn-pad-icon-lg); }
.ds-btn-lg:not([aria-label]):has(> .ds-icon:last-child:not(:first-child)) { padding-right: var(--ds-btn-pad-chevron-lg); }

/* `text-overflow` needs a text box, so the label carries the truncation. */
.ds-btn:has(> .ds-btn-label) {
    min-width: 0;
}

.ds-btn-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.ds-btn:hover { background: #e5e4e3; }

.ds-btn:focus-visible {
    background: #dad9d8;
    outline: var(--ds-focus-ring-width) solid var(--ds-accent-focus);
    outline-offset: var(--ds-focus-ring-offset);
}
.ds-btn:active { background: #c8c7c6; }

.ds-btn:disabled,
.ds-btn[aria-disabled="true"] {
    opacity: 0.4;
    background: var(--ds-bnw-900);
    cursor: not-allowed;
}

.ds-btn-white {
    background: var(--ds-color-surface);
    box-shadow: inset 0 0 0 1px var(--ds-color-border-strong);
}
.ds-btn-white:hover { background: #f5f5f5; }
.ds-btn-white:focus-visible { background: #ebebeb; }
.ds-btn-white:active { background: #e0e0e0; }

.ds-btn-white:disabled,
.ds-btn-white[aria-disabled="true"] { background: var(--ds-color-surface); }

.ds-btn-accent {
    background: var(--ds-accent-default);
    color: var(--ds-color-text-on-accent);
}
.ds-btn-accent:hover { background: var(--ds-accent-hover); }
.ds-btn-accent:focus-visible { background: var(--ds-accent-focus); }
.ds-btn-accent:active { background: var(--ds-accent-press); }

.ds-btn-accent:disabled,
.ds-btn-accent[aria-disabled="true"] { background: var(--ds-accent-default); }

/* Destructive actions. The red is a placeholder token. */
.ds-btn-danger {
    background: var(--ds-color-danger-default);
    color: var(--ds-color-text-on-accent);
}
.ds-btn-danger:hover { background: var(--ds-color-danger-hover); }
.ds-btn-danger:focus-visible { background: var(--ds-color-danger-focus); }
.ds-btn-danger:active { background: var(--ds-color-danger-press); }

.ds-btn-danger:disabled,
.ds-btn-danger[aria-disabled="true"] { background: var(--ds-color-danger-default); }

/* On the sunken panel the default fill is the surface colour. */
.ds-btn--onsunken { background: var(--ds-bnw-850); }
.ds-btn--onsunken:hover { background: #dad9d8; }
.ds-btn--onsunken:focus-visible { background: #cfcecd; }
.ds-btn--onsunken:active { background: #c8c7c6; }

.ds-btn--onsunken:disabled,
.ds-btn--onsunken[aria-disabled="true"] { background: var(--ds-bnw-850); }

.ds-btn-ghost { background: transparent; }
.ds-btn-ghost:hover { background: var(--ds-bnw-900); }
.ds-btn-ghost:focus-visible { background: #e5e4e3; }
.ds-btn-ghost:active { background: #dad9d8; }

.ds-btn-ghost:disabled,
.ds-btn-ghost[aria-disabled="true"] { background: transparent; }

/* The stroke a filled style carries in its own colours; White draws it as part of its plate. */
.ds-btn-outline { box-shadow: inset 0 0 0 1px var(--ds-color-border-strong); }

/* A settled positive state that pressing again undoes. */
.ds-btn-success {
    background: var(--ds-color-success-bg);
    color: var(--ds-color-success-text);
}

.ds-btn-success:hover { background: var(--ds-color-success-bg-hover); }
.ds-btn-success:focus-visible { background: var(--ds-color-success-bg-hover); }
.ds-btn-success:active { background: var(--ds-color-success-bg-press); }

.ds-btn-success:disabled,
.ds-btn-success[aria-disabled="true"] { background: var(--ds-color-success-bg); }

/* Declared after both, to win at equal specificity. */
.ds-btn-ghost.ds-btn-danger {
    background: transparent;
    color: var(--ds-color-danger-text);
}

/* Separate rules: a list holding an unknown selector (`:focus-visible`) is dropped whole. */
.ds-btn-ghost.ds-btn-danger:hover { background: var(--ds-color-danger-bg); }
.ds-btn-ghost.ds-btn-danger:focus-visible { background: var(--ds-color-danger-bg); }
.ds-btn-ghost.ds-btn-danger:active { background: #f0d0d0; }

.ds-btn-ghost.ds-btn-danger:disabled,
.ds-btn-ghost.ds-btn-danger[aria-disabled="true"] { background: transparent; }

.ds-btn-xs {
    min-height: var(--ds-control-height-xs);

    /* Half a pixel low on purpose (see --ds-btn-nudge-xs). */
    padding:
        calc(var(--ds-control-pad-xs) + var(--ds-btn-nudge-xs))
        var(--ds-btn-pad-x-xs)
        calc(var(--ds-control-pad-xs) - var(--ds-btn-nudge-xs));

    gap: var(--ds-space-1);
    font-size: var(--ds-text-meta);
}

.ds-btn-xs[aria-label]:has(> .ds-icon:only-child) {
    width: var(--ds-control-height-xs);
    padding:
        calc(var(--ds-control-pad-xs) + var(--ds-btn-nudge-xs))
        var(--ds-control-pad-xs)
        calc(var(--ds-control-pad-xs) - var(--ds-btn-nudge-xs));
}

.ds-btn-xs > .ds-icon {
    width: 16px;
    height: 16px;
}

.ds-btn-sm {
    min-height: var(--ds-control-height-sm);
    padding: var(--ds-control-pad-sm) var(--ds-btn-pad-x-sm);
}

.ds-btn-sm[aria-label]:has(> .ds-icon:only-child) {
    width: var(--ds-control-height-sm);
    padding: var(--ds-control-pad-sm);
}

.ds-btn-lg {
    min-height: var(--ds-control-height-lg);
    padding: var(--ds-control-pad-lg) var(--ds-btn-pad-x-lg);
    font-size: var(--ds-text-button-lg);
}

.ds-btn-lg[aria-label]:has(> .ds-icon:only-child) {
    width: var(--ds-control-height-lg);
    padding: var(--ds-control-pad-lg);
}

/* ═══ Icon ════════════════════════════════════════════════════════════════
   Every icon in the set is 24x24. */

.ds-icon {
    display: inline-block;
    flex: none;
    width: var(--ds-icon-size);
    height: var(--ds-icon-size);
    vertical-align: middle;
}

/* `accent` marks what the reader is asked to act on. */
.ds-icon-ok { color: var(--ds-color-success-text); }
.ds-icon-warn { color: var(--ds-color-warning-text); }
.ds-icon-bad { color: var(--ds-color-danger-text); }
.ds-icon-accent { color: var(--ds-accent-default); }
.ds-icon-muted { color: var(--ds-color-text-muted); }

/* ═══ Label ═══════════════════════════════════════════════════════════════
   A static pill. Tone: .ds-label-info | .ds-label-success | .ds-label-warning */

.ds-label {
    display: inline-flex;
    align-items: center;
    box-sizing: border-box;
    height: var(--ds-label-height);

    /* Optical centring: all-caps text reads 1px high, so the padding shifts it down 1px. */
    padding: 2px var(--ds-space-4) 0;

    /* A true arc: a squircled pill is a lozenge. */
    border-radius: 20px;
    corner-shape: round;

    max-width: 100%;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    background: var(--ds-color-info-bg);
    color: var(--ds-color-info-text);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-meta);
    font-weight: var(--ds-weight-medium);
    line-height: var(--ds-leading-control);
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

/* A value shown as plain text. A tone class brings the box back. */
.ds-label--value {
    height: auto;
    padding: 0;
    gap: var(--ds-space-2);
    background: transparent;
    color: var(--ds-color-text);
    font-size: var(--ds-text-body);
    letter-spacing: normal;
    text-transform: none;
}

.ds-label--value > .ds-icon {
    flex: none;
    width: 18px;
    height: 18px;
}

.ds-label-info {
    background: var(--ds-color-info-bg);
    color: var(--ds-color-info-text);
}

.ds-label-success {
    background: var(--ds-color-success-bg);
    color: var(--ds-color-success-text);
}

.ds-label-warning {
    background: var(--ds-color-warning-bg);
    color: var(--ds-color-warning-text);
}

/* Between success and warning, for a pill read as a scale. */
.ds-label-caution {
    background: var(--ds-color-caution-bg);
    color: var(--ds-color-caution-text);
}

/* ⚠ Placeholder danger colour (see tokens). */
.ds-label-danger {
    background: var(--ds-color-danger-bg);
    color: var(--ds-color-danger-text);
}

.ds-label-sm {
    gap: var(--ds-space-1);
    height: var(--ds-label-height-sm);
    padding: 1px var(--ds-space-3) 0;
    font-size: 10px;
}

.ds-label--action {
    border: 0;
    cursor: pointer;
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-meta);
}

.ds-label--action:hover {
    filter: brightness(0.95);
}

.ds-label-sm > .ds-icon {
    width: 16px;
    height: 16px;
}

/* A status as its glyph alone, a circle the height of the small label; `aria-label` carries the word. */
.ds-label--icon {
    justify-content: center;
    width: var(--ds-label-height-sm);
    height: var(--ds-label-height-sm);
    padding: 0;
}

.ds-label--icon > .ds-icon {
    width: 16px;
    height: 16px;
}

/* ── The row a run of labels sits in ──────────────────────────────────────
   Two or more labels always go in one of these: they are `inline-flex` and get no space from any other parent. */
.ds-tags,
.ds-card-tags {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--ds-space-2);
    min-width: 0;
}

/* `tagTone()` hashes a tag's type to one of these, so a tag keeps its colour everywhere. */
.ds-label-t1 {
    background: var(--ds-color-info-bg);
    color: var(--ds-color-info-text);
}

.ds-label-t2 {
    background: var(--ds-color-success-bg);
    color: var(--ds-color-success-text);
}

.ds-label-t3 {
    background: var(--ds-color-warning-bg);
    color: var(--ds-color-warning-text);
}

.ds-label-t4 {
    background: var(--ds-hero-800);
    color: var(--ds-hero-200);
}

.ds-label-t5 {
    background: var(--ds-bnw-850);
    color: var(--ds-bnw-300);
}

.ds-label-t6 {
    background: var(--ds-color-danger-bg);
    color: var(--ds-color-danger-text);
}

/* ⚠ WebKit lands the `.ds-label` nudge 1px lower; `hanging-punctuation` is the Safari sniff.
   Must follow `.ds-label-sm`, whose `padding` shorthand would reset it, and exclude `--value`. */
@supports (hanging-punctuation: first) {
    .ds-label:not(.ds-label--value) {
        padding-top: 1px; /* the base 2, less 1 */
    }

    /* A small label starts at 1px, so it lands at 0. */
    .ds-label-sm:not(.ds-label--value) {
        padding-top: 0;
    }
}

/* ── Overflow menu ───────────────────────────────────────────────────────
   Opens a list of actions. `.ds-dropdown` picks a value instead. */

.ds-menu {
    position: relative;
    display: inline-flex;
}

.ds-menu-trigger {
    gap: var(--ds-space-1);
    border: 0;
    cursor: pointer;
    font-family: var(--ds-font-body);
}

/* ⚠ `:where()` keeps this at zero specificity, so the `--button`, `--dots` and `--lg` restatements below win on order.
   */
.ds-menu-trigger:where(:not(.ds-menu-trigger--glyph)) > .ds-icon {
    flex: none;
    width: var(--ds-space-4);
    height: var(--ds-space-4);
}

/* Undoes the pill's tighter gap. ⚠ Never put `.ds-btn` and `.ds-label` on one element. */
.ds-menu-trigger--button {
    gap: var(--ds-space-2);
}

/* Full icon size: `--ds-btn-pad-chevron` is tuned to it. */
.ds-menu-trigger--button > .ds-icon {
    width: var(--ds-icon-size);
    height: var(--ds-icon-size);
}

.ds-menu-trigger:empty {
    padding: 0 var(--ds-space-2);
}

/* Restates the icon size, which the pill rule above would shrink to 12px. */
.ds-menu-trigger--dots > .ds-icon {
    width: var(--ds-icon-size);
    height: var(--ds-icon-size);
}

/* Only the gap: the glyph takes its size from the button class. */
.ds-menu-trigger--glyph {
    gap: var(--ds-space-2);
}

.ds-menu-trigger--lg {
    justify-content: center;
    width: var(--ds-control-height);
    height: var(--ds-control-height);
    padding: 0;
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
}

.ds-menu-trigger--lg > .ds-icon {
    width: var(--ds-icon-size);
    height: var(--ds-icon-size);
}

/* Doubled selector: `.ds-dropdown-item` sets `color` later at equal specificity. */
.ds-dropdown-item.ds-menu-item-danger {
    color: var(--ds-color-danger-text);
}

.ds-menu-separator {
    height: 1px;
    margin: var(--ds-space-2) 0;
    background: var(--ds-color-border);
}

/* ═══ Badge ═══════════════════════════════════════════════════════════════
   An icon carrier. Default 24 (r6), .ds-badge-lg 33 (r12). */

.ds-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    width: 24px;
    height: 24px;

    /* A token, so the arc fallback reaches it. */
    border-radius: var(--ds-radius-badge);
    background: var(--ds-color-badge);
    color: var(--ds-color-text-on-accent);
}

.ds-badge--muted {
    background: var(--ds-bnw-500);
}

.ds-badge-lg {
    width: 33px;
    height: 33px;
    padding: 4px 5px;

    /* Its own token: a badge keeps the plain squircle compensation. */
    border-radius: var(--ds-radius-badge-lg);
}

/* ═══ Checkbox ════════════════════════════════════════════════════════════
   Markup: <label class="ds-checkbox"><input type="checkbox"><span></span>Text</label> */

/* ⚠ The box sits on the label's first line. The offset centres it there; `flex-start` alone rides high. */
.ds-checkbox {
    display: inline-flex;
    align-items: flex-start;
    gap: var(--ds-space-4);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-body);
    line-height: var(--ds-leading-normal);
    color: var(--ds-color-text);
    cursor: pointer;
}

/* The label form only: `.ds-checkbox-mark` is centred by the control it sits in. */
.ds-checkbox > span {
    margin-top: calc((var(--ds-text-body) * var(--ds-leading-normal) - var(--ds-checkbox-size)) / 2);
}

.ds-checkbox input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

/* `.ds-checkbox-mark` is a checkbox drawn as one element, for inside a button, where an <input> is invalid. */
.ds-checkbox > span,
.ds-checkbox-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    flex: none;
    width: var(--ds-checkbox-size);
    height: var(--ds-checkbox-size);
    border: 2px solid var(--ds-bnw-700);
    border-radius: var(--ds-radius-xs);
    transition: background var(--ds-transition-fast), border-color var(--ds-transition-fast);
}

.ds-checkbox > span::after,
.ds-checkbox-mark::after {
    content: "";
    width: 11px;
    height: 9px;
    background: currentcolor;
    color: var(--ds-color-surface);
    opacity: 0;
    clip-path: polygon(13% 45%, 3% 57%, 39% 90%, 97% 20%, 86% 10%, 38% 68%);
}

.ds-checkbox:hover > span { border-color: var(--ds-bnw-650); }
.ds-checkbox:active > span { border-color: var(--ds-bnw-550); }

.ds-checkbox input:focus-visible + span {
    border-color: var(--ds-bnw-600);
    outline: 2px solid var(--ds-accent-focus);
    outline-offset: 2px;
}

.ds-checkbox input:checked + span,
.ds-checkbox-mark[data-checked="true"] {
    background: var(--ds-color-toggle-on);
    border-color: var(--ds-color-toggle-on);
}

.ds-checkbox input:checked + span::after,
.ds-checkbox-mark[data-checked="true"]::after { opacity: 1; }

.ds-checkbox:hover input:checked + span {
    background: var(--ds-hero-400);
    border-color: var(--ds-hero-400);
}

.ds-checkbox input:checked:focus-visible + span {
    background: var(--ds-hero-350);
    border-color: var(--ds-hero-350);
}

.ds-checkbox:active input:checked + span {
    background: var(--ds-hero-300);
    border-color: var(--ds-hero-300);
}

.ds-checkbox input:disabled + span {
    background: none;
    border-color: var(--ds-color-border);
}

.ds-checkbox:has(input:disabled) {
    color: var(--ds-color-text-muted);
    cursor: not-allowed;
}

/* A fixed choice still shows which way it is set, greyed. */
.ds-checkbox input:checked:disabled + span,
.ds-checkbox-mark[data-checked="true"][data-disabled="true"] {
    background: var(--ds-bnw-700);
    border-color: var(--ds-bnw-700);
}

/* ═══ Table ═══════════════════════════════════════════════════════════════
   Markup: <table class="ds-table"><thead><tr><th>Name</th><th class="ds-table-check">On</th></tr></thead>
           <tbody><tr><td><span class="ds-table-title">…</span><span class="ds-table-subtitle">…</span></td>
           <td class="ds-table-check"><label class="ds-checkbox"><input type="checkbox" aria-label="…"><span></span></label></td></tr></tbody></table>
   A `ds-table-row--disabled` row reads muted; disable its controls too. A `ds-table-group` row's one
   `<th colspan>` names the rows under it; a `ds-table-row--link` row opens its record on click and Enter.
   A table that is a card's whole content sits in `.ds-card.ds-card--table`, so its rows run edge to edge.
   A `ds-table--compact` table holds dense lines of text, such as a log.
   A `ds-table-row--note` row's one `<td colspan>` adds a muted line to the row above, with no rule between them. */

.ds-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--ds-text-body);
    line-height: var(--ds-leading-normal);
    color: var(--ds-color-text);
}

.ds-table th {
    padding: 0 var(--ds-space-3) var(--ds-space-2);
    border-bottom: 1px solid var(--ds-color-border);
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
    font-weight: normal;
    text-align: left;
    white-space: nowrap;
}

.ds-table td {
    padding: var(--ds-space-3);
    border-bottom: 1px solid var(--ds-color-border-subtle);
    vertical-align: middle;
}

.ds-table tbody tr:last-child td {
    border-bottom: none;
}

.ds-table th:first-child,
.ds-table td:first-child {
    padding-left: 0;
}

.ds-table th:last-child,
.ds-table td:last-child {
    padding-right: 0;
}

/* A narrow, centred column holding one control; even padding, even at the table's edge. */
.ds-table th.ds-table-check,
.ds-table td.ds-table-check {
    width: 1%;
    padding-right: var(--ds-space-3);
    padding-left: var(--ds-space-3);
    text-align: center;
}

.ds-table-check > .ds-checkbox {
    vertical-align: middle;
}

.ds-table-check > .ds-checkbox > span {
    margin-top: 0;
}

.ds-table-title,
.ds-table-subtitle {
    display: block;
    min-width: 0;
    overflow-wrap: anywhere;
}

/* A small label after a row's title, such as Linked or Automatic; its text shares the title's baseline. */
.ds-table-title > .ds-label {
    margin-left: var(--ds-space-2);
    vertical-align: baseline;
}

.ds-table-subtitle {
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
}

.ds-table-row--disabled .ds-table-title {
    color: var(--ds-color-text-muted);
}

/* One line under the row above, cut short with an ellipsis; `max-width: 0` lets the cell shrink below its text. */
.ds-table tr:has(+ .ds-table-row--note) td {
    padding-bottom: var(--ds-space-1);
    border-bottom: none;
}

.ds-table .ds-table-row--note td {
    max-width: 0;
    overflow: hidden;
    padding-top: 0;
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Heads the rows up to the next group, on a band across the table. */
.ds-table .ds-table-group th {
    padding-top: var(--ds-space-2);
    padding-bottom: var(--ds-space-2);
    border-bottom: none;
    background: var(--ds-color-surface-sunken);
    color: var(--ds-color-text);
    font-size: var(--ds-text-body);
    font-weight: var(--ds-weight-medium);
}

.ds-table-row--link {
    cursor: pointer;
}

.ds-table-row--link:hover td {
    background: var(--ds-bnw-950);
}

/* Every column but the last holds its line; the last folds. */
.ds-table--compact {
    font-size: var(--ds-text-meta);
}

.ds-table--compact td {
    padding: var(--ds-space-1) var(--ds-space-3);
    vertical-align: top;
    white-space: nowrap;
}

.ds-table--compact td:last-child {
    white-space: normal;
    overflow-wrap: anywhere;
}

/* Controls in a compact table are bare: the rows already line them up. */
.ds-table--compact :is(.ds-input, .ds-dropdown-field) {
    min-height: var(--ds-control-height-xs);
    padding: var(--ds-space-1) var(--ds-space-2);
    border: 0;
    box-shadow: none;
    background: transparent;
    font-size: var(--ds-text-meta);
}

.ds-table--compact .ds-dropdown-field:hover,
.ds-table--compact .ds-input:hover {
    background: var(--ds-bnw-950);
}

/* The card clips, so the first and last rows' fill follows its corners. */
.ds-card.ds-card--table {
    padding: var(--ds-space-5) 0 0;
    overflow: clip;
}

/* No header row to make room for: the first and last rows take an even margin, so their fill still meets the edges. */
.ds-card.ds-card--table:not(:has(thead)) {
    padding: 0;
}

.ds-card--table:not(:has(thead)) .ds-table tbody tr:first-child > * {
    padding-top: calc(var(--ds-space-3) + var(--ds-space-2));
}

.ds-card--table:not(:has(thead)) .ds-table tbody tr:last-child > * {
    padding-bottom: calc(var(--ds-space-3) + var(--ds-space-2));
}

.ds-card--table .ds-table :is(th, td):first-child {
    padding-left: var(--ds-space-6);
}

.ds-card--table .ds-table :is(th, td):last-child {
    padding-right: var(--ds-space-6);
}

/* Inset: the table has no frame, and a ring past its edge would be clipped by a scrolling parent. */
.ds-table-row--link:focus-visible {
    outline: var(--ds-focus-ring-width) solid var(--ds-accent-focus);
    outline-offset: calc(-1 * var(--ds-focus-ring-width));
}

/* ═══ Radio ═══════════════════════════════════════════════════════════════
   Markup: <label class="ds-checkbox"><input type="radio" name="…"><span></span>Text</label>
   The same control, keyed on the input's type. */

/* ⚠ `corner-shape: round`: base.css would squircle a 50% radius into a rounded square. */
.ds-checkbox > input[type="radio"] + span {
    border-radius: 50%;
    corner-shape: round;
}

.ds-checkbox > input[type="radio"] + span::after {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    corner-shape: round;
    clip-path: none;
}

/* ═══ Switch ══════════════════════════════════════════════════════════════
   Markup: <label class="ds-switch"><input type="checkbox"><span></span></label>
   Rules target `input + span`, the track, because a caption may also sit inside the label. */

.ds-switch {
    display: inline-flex;
    gap: var(--ds-space-3);
    align-items: center;
    cursor: pointer;
}

.ds-switch input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.ds-switch > input + span {
    display: block;
    position: relative;
    box-sizing: border-box;
    width: 57px;
    height: 30px;
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
    background: var(--ds-bnw-700);
    transition: background var(--ds-transition-fast);
}

.ds-switch > input + span::after {
    content: "";
    position: absolute;
    top: var(--ds-space-1);
    left: var(--ds-space-1);
    width: 24px;
    height: 24px;
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
    background: var(--ds-color-surface);
    transition: transform var(--ds-transition-fast);
}

.ds-switch:hover > input + span { background: var(--ds-bnw-650); }

.ds-switch input:focus-visible + span {
    background: var(--ds-bnw-600);
    outline: 2px solid var(--ds-accent-focus);
    outline-offset: 2px;
}
.ds-switch:active > input + span { background: var(--ds-bnw-550); }

.ds-switch input:checked + span { background: var(--ds-color-toggle-on); }
.ds-switch input:checked + span::after { transform: translateX(27px); }
.ds-switch:hover input:checked + span { background: var(--ds-hero-400); }
.ds-switch input:checked:focus-visible + span { background: var(--ds-hero-350); }
.ds-switch:active input:checked + span { background: var(--ds-hero-300); }

.ds-switch input:disabled + span { background: var(--ds-color-border); }
.ds-switch:has(input:disabled) { cursor: not-allowed; }

/* ═══ MenuCell ════════════════════════════════════════════════════════════
   A selectable list row. .ds-menucell-active for the selected state. */

.ds-menucell {
    display: flex;
    align-items: center;
    gap: var(--ds-space-4);
    box-sizing: border-box;
    width: 100%;
    min-height: 48px;
    padding: var(--ds-space-1) var(--ds-space-5);
    border: 0;
    border-radius: var(--ds-radius-md);
    background: transparent;
    color: var(--ds-bnw-250);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-body);
    text-align: left;
    cursor: pointer;
    transition: background var(--ds-transition-fast);
}

.ds-menucell:hover { background: var(--ds-bnw-950); }

.ds-menucell-active,
.ds-menucell[aria-current="true"] { background: var(--ds-bnw-900); }

.ds-menucell-body {
    flex: 1;
    min-width: 0;
    overflow: hidden;
}

/* Both lines truncate: the row has a fixed min-height. Tight leading keeps the two lines one entry. */
.ds-menucell-title,
.ds-menucell-description {
    display: block;
    overflow: hidden;
    line-height: var(--ds-leading-tight);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ds-menucell-description {
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
}

.ds-menucell-meta {
    flex: none;
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
}

.ds-menucell-title:has(> .ds-menucell-status) {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
}

.ds-menucell-status {
    flex: none;
    width: 18px;
    height: 18px;
}

.ds-menucell-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Wrap the cell and its action in `.ds-menucell-row`: a button cannot nest in a button. */
.ds-menucell-row {
    position: relative;
    display: flex;
    align-items: center;
}

.ds-menucell-row > .ds-menucell {
    flex: 1;
    min-width: 0;
}

/* Two rules, so a browser without `:has()` still reserves the action's width. */
.ds-menucell-row--action > .ds-menucell {
    padding-right: calc(var(--ds-control-height-sm) + var(--ds-space-2));
}

.ds-menucell-row:has(> .ds-menucell-action) > .ds-menucell {
    padding-right: calc(var(--ds-control-height-sm) + var(--ds-space-2));
}

.ds-menucell-action {
    position: absolute;
    z-index: 1;
    right: var(--ds-space-3);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--ds-control-height-sm);
    height: var(--ds-control-height-sm);
    padding: 0;
    border: 0;
    border-radius: var(--ds-radius-sm);
    background: transparent;
    color: var(--ds-color-text-secondary);
    cursor: pointer;

    /* `opacity`, for the reason given at `.ds-dropdown-item-action`. */
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--ds-transition-fast);
}

.ds-menucell-row:hover > .ds-menucell-action,
.ds-menucell-row:focus-within > .ds-menucell-action {
    opacity: 1;
    pointer-events: auto;
}

@media (hover: none) {
    /* No pointer to reveal it with. */
    .ds-menucell-action {
        opacity: 1;
        pointer-events: auto;
    }
}

/* Pointing at the action keeps the row lit. */
.ds-menucell-row:hover > .ds-menucell:not(.ds-menucell-active, [aria-current="true"]) {
    background: var(--ds-bnw-950);
}

.ds-menucell-action:hover { background: var(--ds-bnw-850); }

/* ═══ Content menu ════════════════════════════════════════════════════════
   A card holding the sections of one thing. Each line's right end reports on its section. */

.ds-contentmenu {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-1);
    box-sizing: border-box;
    padding: var(--ds-space-3);
    border-radius: var(--ds-radius-md);      /* drawn 18 */
    background: var(--ds-color-surface);
    box-shadow: var(--ds-shadow-card);
}

/* For inside a container that is already a card. */
.ds-contentmenu--bare {
    padding: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
}

.ds-contentmenu-item {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
    box-sizing: border-box;
    width: 100%;

    /* Fixed, so a row without an icon lines up with the rest. */
    min-height: var(--ds-control-height-sm);
    padding: var(--ds-space-2) var(--ds-space-3);
    border: 0;
    border-radius: var(--ds-radius-sm);      /* drawn 12 */
    background: transparent;
    color: var(--ds-color-text);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-meta);
    font-weight: var(--ds-weight-medium);
    text-align: left;
    cursor: pointer;
    transition: background var(--ds-transition-fast);
}

/* Hover is half the tint of selection. */
.ds-contentmenu-item:hover { background: var(--ds-bnw-950); }

.ds-contentmenu-item--active,
.ds-contentmenu-item[aria-current="true"] { background: var(--ds-bnw-900); }

.ds-contentmenu-item--disabled {
    color: var(--ds-color-text-muted);
    cursor: default;
}

.ds-contentmenu-item--disabled:hover { background: transparent; }

.ds-contentmenu-icon {
    flex: none;
    width: var(--ds-icon-size);
    height: var(--ds-icon-size);
}

/* ⚠ `flex: 0 1 auto`, so the count pill sits against the name. The meta's `margin-left: auto` holds the right edge. */
.ds-contentmenu-label {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── A row with a second line (`sub`) ─────────────────────────────────────── */
.ds-contentmenu-name,
.ds-contentmenu-sub {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: var(--ds-text-body);
}

/* ⚠ The line box equals the glyph's height, which aligns the two in a top-aligned row. */
.ds-contentmenu-name { line-height: var(--ds-icon-size); }

.ds-contentmenu-sub {
    font-weight: var(--ds-weight-regular);
    line-height: var(--ds-leading-tight);
}

/* Per row: the rows beside it are one line each. */
.ds-contentmenu-item--stacked { align-items: flex-start; }

.ds-contentmenu-item--stacked .ds-contentmenu-label { white-space: normal; }

.ds-contentmenu-item--stacked .ds-contentmenu-meta {
    min-height: 0;
    align-self: flex-start;
}

/* Its looks come from the `.ds-label` classes it also wears. */
.ds-contentmenu-count {
    flex: none;
}

.ds-contentmenu-counts {
    display: inline-flex;
    align-items: center;
    gap: var(--ds-space-2);
}

.ds-contentmenu--multiline .ds-contentmenu-label {
    display: -webkit-box;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    white-space: normal;
}

.ds-contentmenu--multiline .ds-contentmenu-item {
    align-items: flex-start;
}

/* The first line's height, so it sits on that line. */
.ds-contentmenu--multiline .ds-contentmenu-meta {
    min-height: 0;
    align-self: flex-start;
}

/* `min-height` is the small control's, so a button in the meta keeps the row's height. */
.ds-contentmenu-meta {
    display: flex;
    align-items: center;
    flex: none;
    justify-content: flex-end;
    min-height: var(--ds-control-height-xs);
    margin-left: auto;
    padding-left: var(--ds-space-2);
    color: var(--ds-color-text-muted);
    font-weight: var(--ds-weight-regular);
    white-space: nowrap;
}

/* ═══ Card ════════════════════════════════════════════════════════════════ */

.ds-card {
    box-sizing: border-box;
    padding: var(--ds-space-6);
    border-radius: var(--ds-radius-card);
    background: var(--ds-color-surface);
    box-shadow: var(--ds-shadow-card);
}

/* Whatever ends a card, a row built as a head included, adds nothing under the card's padding. */
.ds-card > :last-child {
    margin-bottom: 0;
}

.ds-card-body {
    margin: var(--ds-space-4) 0 0;
    color: var(--ds-color-text);
    white-space: pre-wrap;
}

/* Separate boxes, so the meta stays on the first row however the labels wrap. */
.ds-card-head {
    display: flex;
    flex-wrap: nowrap;
    align-items: flex-start;
    gap: var(--ds-space-3);
    margin-bottom: var(--ds-space-4);
}

/* A card's action row: its last row, held to the bottom of a card stretched to its neighbours' height. */
.ds-card:has(> .ds-card-foot) {
    display: flex;
    flex-direction: column;
}

.ds-card-foot {
    display: flex;
    align-items: center;
    gap: var(--ds-space-3);
    margin-top: auto;
    padding-top: var(--ds-space-5);
}

/* The main actions, at the end of the foot; a quiet one may stand before them at its start. */
.ds-card-foot-end {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ds-space-3);
    margin-left: auto;
}

/* Shape comes from `.ds-tags`. */
.ds-card-tags {
    flex: 1;
}

.ds-card-head > .ds-card-title {
    flex: 1;
    min-width: 0;

    /* ⚠ `min-width: 0` alone is not enough: a name with no break opportunity is as wide as the whole string. */
    overflow-wrap: anywhere;

    /* The head already carries the gap below itself. */
    margin-bottom: 0;
}

.ds-card-meta {
    display: inline-flex;
    flex: none;
    align-items: center;

    /* A label's box, so the text centres against the first tag row. */
    min-height: var(--ds-label-height);
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
    white-space: nowrap;
}

/* Names a value, at the value's size. `.ds-card-meta` is the standalone small meta line. */
.ds-meta-label {
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-body);
    white-space: nowrap;
}

/* ═══ Facts ═══════════════════════════════════════════════════════════════
   Markup, one pair:   <p class="ds-fact"><span class="ds-fact-label">Status</span><span>Paused</span></p>
   Markup, a list:     <div class="ds-facts"><span class="ds-fact-label">Status</span><span>Paused</span>…</div>

   ⚠ Use `.ds-fact-label` here, never `.ds-meta-label`, which sets its own size and breaks the shared baseline.
   In `.ds-facts` the spans are direct children, which puts them in the grid. */

.ds-fact,
.ds-facts {
    min-width: 0;
    margin: 0;
    line-height: var(--ds-leading-body);
}

/* ⚠ Neither sets a font size: both halves inherit the container's. */

.ds-fact {
    display: flex;
    align-items: baseline;
    gap: var(--ds-space-3);
}

.ds-facts {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: baseline;
    gap: var(--ds-space-1) var(--ds-space-3);
}

.ds-fact-label {
    flex: none;
    overflow: hidden;
    color: var(--ds-color-text-muted);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ds-facts > .ds-fact-label {
    text-align: right;
}

.ds-card-list {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-5);
}

/* `--ds-clamp-lines` sets the depth (default 10). */
.ds-card-body--clamped {
    display: -webkit-box;
    overflow: hidden;
    -webkit-line-clamp: var(--ds-clamp-lines, 10);
    -webkit-box-orient: vertical;
}

/* A card inside a card: a border instead of the shadow. */
.ds-card--outlined {
    border: 1px solid var(--ds-color-border);
    box-shadow: none;
}

/* Small muted lines right under a card's title or head: a date, an id, an owner. The head's own gap is taken back. */
.ds-card-subtitle {
    margin: calc(-1 * var(--ds-space-4)) 0 var(--ds-space-4);
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
    line-height: var(--ds-leading-normal);
    overflow-wrap: anywhere;
}

.ds-card-subtitle + .ds-card-subtitle {
    margin-top: calc(0px - var(--ds-space-4));
}

/* A link in a subtitle reads as part of it until the pointer finds it; a failure reads as one. */
.ds-card-subtitle a,
.ds-table-subtitle a {
    color: inherit;
}

.ds-card-subtitle a.ds-link--failed,
.ds-table-subtitle a.ds-link--failed {
    color: var(--ds-color-danger-text);
}

/* The title and its subtitles as one column of a `.ds-card-head`, so a taller control beside them can't push the
   subtitles down. Markup: <div class="ds-card-head"><div class="ds-card-heading"><h3 class="ds-card-title">…</h3>
   <p class="ds-card-subtitle">…</p></div><button …></div> */
.ds-card-head > .ds-card-heading {
    flex: 1;
    min-width: 0;
}

/* One centre line: the title, its labels right after it, a control at the end. A `.ds-card-subtitle` follows the head.
   Markup: <div class="ds-card-head ds-card-head--line"><h3 class="ds-card-title">…</h3><span class="ds-label">…</span>
   <div class="ds-menu">…</div></div> */
.ds-card-head--line {
    align-items: center;
}

.ds-card-head--line > .ds-card-title {
    flex: 0 1 auto;
}

.ds-card-head--line > .ds-menu,
.ds-card-head--line > .ds-btn {
    margin-left: auto;
}

.ds-card-heading > .ds-card-title,
.ds-card-heading > .ds-card-subtitle {
    margin: 0;
}

.ds-card--clickable {
    cursor: pointer;
    transition: box-shadow var(--ds-transition-fast);
}

/* Separate rules, as at `.ds-dropdown-item-action`. */
.ds-card--clickable:hover {
    box-shadow: var(--ds-shadow-card), 0 0 0 1px var(--ds-color-border);
    outline: none;
}

.ds-card--clickable:focus-visible {
    box-shadow: var(--ds-shadow-card), 0 0 0 1px var(--ds-color-border);
    outline: none;
}

.ds-card-title {
    margin: 0 0 var(--ds-space-4);
    font-family: var(--ds-font-display);
    font-size: var(--ds-text-section);
    font-weight: var(--ds-weight-semibold);
    line-height: var(--ds-leading-tight);
    color: var(--ds-color-text);
}

/* ═══ Input ═══════════════════════════════════════════════════════════════
   ⚠ The focus and error states are ours, pending the design. */

/* 1px, so the caption and the field read as one control. */
.ds-field {
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.ds-field-label {
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-meta);
    color: var(--ds-color-text-muted);
}

/* Breaks anywhere: ids and paths have nothing to wrap at. */
.ds-field-static {
    padding: var(--ds-space-2) 0;
    color: var(--ds-color-text-secondary);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-body);
    overflow-wrap: anywhere;
}

.ds-field-hint,
.ds-field-error {
    margin: var(--ds-space-1) 0 0;
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-meta);
    line-height: var(--ds-leading-normal);
    white-space: pre-line;
    overflow-wrap: anywhere;
}

.ds-field-hint { color: var(--ds-color-text-muted); }

.ds-field-error { color: var(--ds-color-danger-text); }

/* ═══ Fieldset ════════════════════════════════════════════════════════════
   No box: one inside a dialog reads as a second dialog. */
.ds-fieldset {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-3);
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
}

.ds-fieldset-head {
    display: flex;
    align-items: center;
    gap: var(--ds-space-3);
    min-height: var(--ds-control-height-xs);
}

.ds-fieldset-title {
    flex: 1;
    min-width: 0;
    color: var(--ds-color-text-secondary);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-body);
    font-weight: var(--ds-weight-medium);
}

.ds-fieldset-body {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-4);
}

.ds-input {
    box-sizing: border-box;
    width: 100%;
    min-height: var(--ds-control-height);
    padding: var(--ds-space-3) var(--ds-space-5);
    border: 2px solid var(--ds-color-border);
    border-radius: var(--ds-radius-sm);
    background: var(--ds-color-surface);
    color: var(--ds-color-text);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-body);
}

.ds-input::placeholder { color: var(--ds-color-text-muted); }

.ds-input:focus {
    outline: 2px solid var(--ds-accent-focus);
    outline-offset: -1px;
}

.ds-input:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}
.ds-input[aria-invalid="true"] { border-color: var(--ds-color-danger-text); }

/* The real <input> goes bare inside the `.ds-input` box:

     <span class="ds-input ds-input--affixed">
       <span class="ds-input-affix">$</span>
       <input class="ds-input-bare" type="number">
     </span> */
.ds-input--affixed {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
}

.ds-input-bare {
    flex: 1;
    min-width: 0;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    outline: none;
}

.ds-input-affix {
    flex: none;
    color: var(--ds-color-text-muted);
}

.ds-input-affix--suffix {
    margin-left: auto;
}

/* The box is the control, so it shows the focus. */
.ds-input--affixed:focus-within {
    outline: 2px solid var(--ds-accent-focus);
    outline-offset: -1px;
}

/* No stepper by default; `.ds-input--stepper` restores it.
   ⚠ `.ds-input-bare` must be listed too: it does not carry `.ds-input`. */
.ds-input[type="number"],
.ds-input-bare[type="number"] {
    appearance: textfield;
}

.ds-input[type="number"]::-webkit-outer-spin-button,
.ds-input[type="number"]::-webkit-inner-spin-button,
.ds-input-bare[type="number"]::-webkit-outer-spin-button,
.ds-input-bare[type="number"]::-webkit-inner-spin-button {
    margin: 0;
    appearance: none;
}

.ds-input--stepper[type="number"] {
    appearance: auto;
}

.ds-input--stepper[type="number"]::-webkit-outer-spin-button,
.ds-input--stepper[type="number"]::-webkit-inner-spin-button {
    appearance: auto;
}

/* ═══ Dropdown ════════════════════════════════════════════════════════════ */

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

.ds-dropdown-panel {
    position: absolute;
    z-index: var(--ds-z-popup);
    top: calc(100% + var(--ds-space-1));
    left: 0;
    box-sizing: border-box;
    min-width: 300px;
    padding: var(--ds-space-2);

    /* The filter and the footer repeat this radius, because they bleed to these corners. */
    border-radius: var(--ds-radius-panel);
    background: var(--ds-color-surface);
    box-shadow: var(--ds-shadow-dropdown), inset 0 0 0 1px var(--ds-bnw-900);
}

.ds-dropdown-panel[hidden] { display: none; }

/* ── Popover ─────────────────────────────────────────────────────────────
   Positioned in JS as `fixed`, so no scroller clips it. */
.ds-popover {
    position: fixed;
    z-index: var(--ds-z-popup);
    box-sizing: border-box;

    /* About 45 characters a line. */
    max-width: 320px;
    padding: var(--ds-space-4) var(--ds-space-5);
    border-radius: var(--ds-radius-panel);
    background: var(--ds-color-surface);
    box-shadow: var(--ds-shadow-dropdown), inset 0 0 0 1px var(--ds-bnw-900);
    line-height: var(--ds-leading-normal);
}

.ds-popover-title {
    margin: 0 0 var(--ds-space-2);
    font-family: var(--ds-font-display);
    font-size: var(--ds-text-body);
    font-weight: var(--ds-weight-medium);
}

.ds-popover-body {
    margin: 0;
    color: var(--ds-color-text-secondary);
}

.ds-popover-body + .ds-popover-body {
    margin-top: var(--ds-space-2);
}

/* A term that opens a popover on hover: the dotted underline says there is more to read. */
.ds-popover-anchor {
    text-decoration: underline dotted;
    text-decoration-color: var(--ds-color-text-muted);
    text-underline-offset: 3px;
    cursor: help;
}

.ds-popover-anchor:focus-visible {
    border-radius: var(--ds-radius-xs);
    outline: var(--ds-focus-ring-width) solid var(--ds-accent-focus);
}

/* ── Filterable dropdown ─────────────────────────────────────────────────
   Build it with createDropdown() in js/design-system.js.
   Only the list scrolls. The filter and the footer sit outside it, never sticky: a sticky one would bind to the
   dialog's scroller instead, and Safari pins it there, over the list. */

.ds-dropdown-filter {
    position: relative;
    display: block;
    margin: calc(-1 * var(--ds-space-2)) calc(-1 * var(--ds-space-2)) var(--ds-space-2);
    padding: var(--ds-space-2);

    /* Repeats the panel's radius: the panel cannot clip it without cutting the rows' focus rings. */
    border-radius: var(--ds-radius-panel) var(--ds-radius-panel) 0 0;
    border-bottom: 1px solid var(--ds-color-border);
    background: var(--ds-color-surface);
}

.ds-dropdown-filter > .ds-search-icon {
    position: absolute;
    top: 50%;
    left: calc(var(--ds-space-2) + var(--ds-space-4));
    color: var(--ds-color-text-muted);
    transform: translateY(-50%);
    pointer-events: none;
}

.ds-dropdown-filter > .ds-input {
    padding-left: calc(var(--ds-space-4) + var(--ds-icon-size) + var(--ds-space-2));

    border-radius: var(--ds-radius-pill);
    corner-shape: round;
}

.ds-dropdown-list {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-1);

    max-height: 40vh;
    overflow-y: auto;
}

/* ── List editor ─────────────────────────────────────────────────────────
   Built by createDropdown's `itemAction` / `footerAction`.
   The row wrapper exists because a button cannot nest in a button. */

.ds-dropdown-row {
    position: relative;
    display: flex;
    align-items: center;
}

.ds-dropdown-row > .ds-dropdown-item {
    flex: 1;
    min-width: 0;
}

/* Outside the option button; it carries the row's left padding, so the labels still line up. */
.ds-dropdown-item-check {
    flex: none;
    padding-left: var(--ds-space-3);
}

.ds-dropdown-row--check > .ds-dropdown-item {
    padding-left: var(--ds-space-3);
}

/* ⚠ Keyed on a modifier `createDropdown` sets: a `:has()` rule is dropped where `:has()` is unknown. */
.ds-dropdown-row--action > .ds-dropdown-item {
    padding-right: calc(var(--ds-control-height-sm) + var(--ds-space-2));
}

.ds-dropdown-item-action {
    position: absolute;

    z-index: 1;
    right: var(--ds-space-2);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--ds-control-height-sm);
    height: var(--ds-control-height-sm);
    padding: 0;
    border: 0;
    border-radius: var(--ds-radius-sm);
    background: transparent;
    color: var(--ds-color-text-secondary);
    cursor: pointer;

    /* ⚠ `opacity`, never `visibility: hidden`: a hidden element is not focusable.
       `pointer-events: none` stops the invisible button being clicked. */
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--ds-transition-fast);
}

/* ⚠ Its own rule: a list holding an unknown selector (`:focus-visible`) is dropped whole.
   `:focus-within` reveals the action to the keyboard. */
.ds-dropdown-row:hover > .ds-dropdown-item-action,
.ds-dropdown-row:focus-within > .ds-dropdown-item-action {
    opacity: 1;
    pointer-events: auto;
}

@media (hover: none) {
    /* Touch has no hover, so the action shows. */
    .ds-dropdown-item-action {
        opacity: 1;
        pointer-events: auto;
    }
}

.ds-dropdown-item-action:hover { background: var(--ds-bnw-900); }
.ds-dropdown-item-action:active { background: var(--ds-bnw-850); }

/* Bleeds to the panel edges, as the filter does. */
.ds-dropdown-footer {
    display: flex;
    margin: var(--ds-space-2) calc(-1 * var(--ds-space-2)) calc(-1 * var(--ds-space-2));
    padding: var(--ds-space-2);

    border-radius: 0 0 var(--ds-radius-panel) var(--ds-radius-panel);
    border-top: 1px solid var(--ds-color-border);
    background: var(--ds-color-surface);
}

.ds-dropdown-footer > .ds-btn {
    width: 100%;
}

.ds-dropdown-item-meta {
    margin-left: auto;
    padding-left: var(--ds-space-4);
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
}

/* Its own box, so the meta and the action align against the pair. `min-width: 0` lets a long id ellipsise. */
.ds-dropdown-item-text {
    display: flex;
    overflow: hidden;
    flex: 1;
    flex-direction: column;
    gap: var(--ds-space-1);
    min-width: 0;
}

.ds-dropdown-item-description {
    overflow: hidden;
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The space is reserved on every row and only the visibility flips. */

.ds-dropdown-item > .ds-checkbox-mark {
    margin-left: auto;
}

.ds-dropdown-empty {
    padding: var(--ds-space-3) var(--ds-space-5);
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-body);
}

.ds-dropdown-item {
    display: flex;
    align-items: center;
    gap: var(--ds-space-4);
    box-sizing: border-box;
    width: 100%;
    min-height: var(--ds-control-height);
    padding: var(--ds-space-3) var(--ds-space-5);
    border: 0;
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
    background: transparent;
    color: var(--ds-color-text);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-body);
    text-align: left;
    cursor: pointer;
}

/* Hover is half the tint of the selection; `.ds-menucell` uses the same pair. */
.ds-dropdown-item:hover { background: var(--ds-bnw-950); }

.ds-dropdown-item[aria-selected="true"] { background: var(--ds-bnw-900); }

/* The action sits outside the option, so the row drives the highlight. */
.ds-dropdown-row:hover > .ds-dropdown-item:not([aria-selected="true"]) {
    background: var(--ds-bnw-950);
}

/* Filled as one row, because the checkbox sits outside the option button. */
.ds-dropdown-row--check {
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
}

.ds-dropdown-row--check:hover { background: var(--ds-bnw-950); }
.ds-dropdown-row--check.ds-dropdown-row--selected { background: var(--ds-bnw-900); }

.ds-dropdown-row--check:hover > .ds-dropdown-item:not([aria-selected="true"]),
.ds-dropdown-row--check > .ds-dropdown-item[aria-selected="true"] {
    background: transparent;
}

/* ═══ Dialog ══════════════════════════════════════════════════════════════
   Composed from Card and Button. */

.ds-dialog {
    box-sizing: border-box;

    /* Fixed: the UA fixes only `dialog:modal`; a non-modal one would sit at the top of a long page. */
    position: fixed;
    width: min(520px, calc(100vw - 2 * var(--ds-space-6)));

    /* A column, so the head and the action row keep their size and the body scrolls. */
    display: flex;
    max-height: calc(100vh - 2 * var(--ds-dialog-inset));
    flex-direction: column;
    padding: var(--ds-space-6);
    border: 0;

    /* A dialog with nothing focusable in it takes focus itself, and it is not a control. */
    outline: none;
    border-radius: var(--ds-radius-card);
    background: var(--ds-color-surface);
    box-shadow: var(--ds-shadow-dropdown);
    color: var(--ds-color-text);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-body);

    animation: ds-dialog-in 160ms ease-out;
}

/* Only the scrolling body gives when the height ceiling binds. */
.ds-dialog > *:not(.ds-dialog-scroll) {
    flex: none;
}

.ds-dialog::backdrop {
    background: rgb(0 0 0 / 12%);
    backdrop-filter: blur(6px);
    animation: ds-backdrop-in 160ms ease-out;
}

@keyframes ds-dialog-in {
    from {
        transform: translateY(6px);
        opacity: 0;
    }

    to {
        transform: none;
        opacity: 1;
    }
}

@keyframes ds-backdrop-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .ds-dialog,
    .ds-dialog::backdrop { animation: none; }
}

.ds-dialog-title {
    margin: 0 0 var(--ds-space-4);
    font-family: var(--ds-font-display);
    font-size: var(--ds-text-dialog-title);
    font-weight: var(--ds-weight-semibold);

    /* Above 1, so a wrapped title keeps its descenders clear of the next line. */
    line-height: 1.3;
}

.ds-dialog-head {
    display: flex;
    align-items: center;
    gap: var(--ds-space-4);
    margin-bottom: var(--ds-space-4);
}

/* `align-items` is restated, or the column would centre the title horizontally. */
.ds-dialog-head--stacked {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--ds-space-1);
}

/* `user-select: all` lets one click take the whole id. `anywhere`, because an id has no spaces to wrap at. */
.ds-dialog-subtitle {
    max-width: 100%;
    margin: 0;
    overflow-wrap: anywhere;
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
    user-select: all;
}

/* Positioned against the dialog, so a long title cannot push it. */
.ds-dialog-close {
    position: absolute;
    top: var(--ds-space-5);
    right: var(--ds-space-5);
    width: var(--ds-control-height);
    height: var(--ds-control-height);
    padding: 0;
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
    background: var(--ds-bnw-900);
}

.ds-dialog-close:hover { background: var(--ds-bnw-850); }

.ds-dialog-head > .ds-dialog-title {
    flex: 1;
    min-width: 0;
    margin: 0;

    /* Room for the close button, which is positioned over this row. */
    padding-right: calc(var(--ds-control-height) + var(--ds-space-3));
}

/* The row reserves the close button's corner, so the control is not laid out under it. */
.ds-dialog-head--control {
    padding-right: calc(var(--ds-control-height) + var(--ds-space-3));
}

.ds-dialog-head--control > .ds-dialog-title {
    flex: 0 1 auto;
    padding-right: 0;
}

/* ⚠ No taller than the title's line: the close button shares the head's centre line only while
   the head is as tall as its title. Pass a `ds-btn-xs`-sized control. */
.ds-dialog-head-control {
    flex: none;
    max-height: var(--ds-control-height-xs);
    min-width: 0;
    margin-right: auto;
}

.ds-dialog-head-control > .ds-btn {
    max-height: 100%;
}

/* A control taller than the title's line overhangs it: negative block margins keep the head as tall as its title.
   ⚠ The `1.3` restates `.ds-dialog-title`'s line-height. */
.ds-dialog-head--controltall > .ds-dialog-head-control {
    max-height: none;
    margin-block: calc((var(--ds-text-dialog-title) * 1.3 - var(--ds-control-height)) / 2);
}

/* An empty `.ds-dialog-title` would otherwise hold a line's worth of space. */
.ds-dialog-head--controlonly > .ds-dialog-title {
    display: none;
}

.ds-dialog-head--controlonly > .ds-dialog-head-control {
    max-height: none;
}

/* Within the close button's reach, so it takes the same gutter. */
.ds-dialog-head > .ds-dialog-subtitle {
    padding-right: calc(var(--ds-control-height) + var(--ds-space-3));
}

/* The height is fixed, so the box never resizes while its content changes size.
   `--ds-dialog-scroll-height` overrides it. */
.ds-dialog-scroll {
    /* The dialog's max-height is the real limit. */
    --ds-dialog-scroll-height: 100vh;

    height: var(--ds-dialog-scroll-height);

    /* May shrink to nothing; an `auto` minimum would push the box past the window. */
    flex: 0 1 auto;
    min-height: 0;

    /* ⚠ A scroll container must never clip a focus ring. `overflow-y: auto` makes `overflow-x` compute to `auto` too.
       Controls in here draw their ring inset, and this gutter covers those that cannot; the negative margin
       keeps the scrollbar in place. The gutter fits the checkbox's ring: 2px of outline at 2px of offset. */
    padding-block: var(--ds-space-2);
    padding-inline: var(--ds-space-2);
    margin-inline: calc(-1 * var(--ds-space-2));
    overflow-y: auto;
    scroll-padding-block: var(--ds-space-6);
    line-height: var(--ds-leading-normal);
    overflow-wrap: anywhere;
}

/* The panes scroll on their own. Chrome would keep a redundant outer scroller keyboard-focusable. */
.ds-dialog-scroll--panes {
    overflow: hidden;
}

/* For a short, finished record: nothing in it resizes itself. */
.ds-dialog-scroll--auto {
    height: auto;
    max-height: var(--ds-dialog-scroll-height);
}

.ds-form {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-4);
}

/* The far end of the action row, where a destructive sibling belongs. */
.ds-dialog-action-start {
    margin-right: auto;
}

/* A read-only stand-in for a picker, for a user who may only ever see one value. */
.ds-topbar-label {
    display: inline-flex;
    align-items: center;
    min-height: var(--ds-control-height);
    padding: 0 var(--ds-space-4);
    color: var(--ds-color-text);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-button);
    font-weight: var(--ds-weight-medium);
}

/* The ring is drawn inside the box, like `.ds-input` (see the focus-ring rule on `.ds-dialog-scroll`). */
.ds-dropdown-field:focus-visible {
    outline-offset: -1px;
}

.ds-dropdown-field {
    justify-content: space-between;
    width: 100%;
}

.ds-field > .ds-dropdown {
    display: block;
}

/* ⚠ Never on `.ds-dialog-scroll` itself: rendered markdown would inherit it
   and turn every newline in its HTML into a line break. */
.ds-dialog-scroll--text {
    white-space: pre-wrap;
}

.ds-dialog--wide {
    width: min(760px, calc(100vw - 2 * var(--ds-space-6)));
}

/* For a record whose subject is a picture. */
.ds-dialog--wider {
    width: min(1140px, calc(100vw - 2 * var(--ds-dialog-inset)));
}

/* For a two-pane body. Fixed margins: the window less `--ds-dialog-inset` on each side. */
.ds-dialog--xwide {
    width: calc(100vw - 2 * var(--ds-dialog-inset));
}

.ds-dialog-body {
    margin: 0 0 var(--ds-space-6);
    line-height: var(--ds-leading-normal);
}

.ds-dialog > .ds-spinner {
    margin-inline: auto;
}

/* No margin: an empty <p> would still cost one. */
.ds-dialog-meta {
    margin: var(--ds-space-2) 0 0;
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
}

/* Wraps, so a row of many actions keeps every button pressable. */
.ds-dialog-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--ds-space-4);
}

/* Sticks to the bottom of the scrolling body (showForm), so a long form keeps its Save in reach.
   The padding and background stop the fields showing through. */
.ds-dialog-scroll .ds-dialog-actions {
    position: sticky;
    bottom: calc(-1 * var(--ds-space-2));
    padding-block: var(--ds-space-4) var(--ds-space-2);
    background: var(--ds-color-surface);
}

/* The body's gutter and the actions' own padding sit inside the dialog's padding, so the space under the buttons
   matches the space over the title. */
.ds-dialog-scroll:has(.ds-dialog-actions:last-child) {
    margin-bottom: calc(-1 * var(--ds-space-4));
}

/* A sibling of the scrolling body (showPopup's `actions`), so it keeps its place whatever the record's length. */
.ds-dialog-foot {
    margin-top: var(--ds-space-4);
}

/* ═══ Input family ════════════════════════════════════════════════════════
   Textarea and Select share `.ds-input`'s box.
   ⚠ The focus and error states are ours, pending the design. */

.ds-textarea {
    box-sizing: border-box;
    width: 100%;
    min-height: 96px;
    padding: var(--ds-space-3) var(--ds-space-5);
    border: 2px solid var(--ds-color-border);
    border-radius: var(--ds-radius-sm);
    background: var(--ds-color-surface);
    color: var(--ds-color-text);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-body);
    line-height: var(--ds-leading-normal);
    resize: vertical;
}

.ds-textarea::placeholder { color: var(--ds-color-text-muted); }

.ds-textarea:focus {
    outline: 2px solid var(--ds-accent-focus);
    outline-offset: -1px;
}

.ds-textarea:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

/* The caret is a real element, so it inherits currentColor. */

.ds-select {
    position: relative;
    display: block;
}

.ds-select > select {
    box-sizing: border-box;
    width: 100%;
    min-height: var(--ds-control-height);
    padding: var(--ds-space-3) calc(var(--ds-space-5) + var(--ds-icon-size)) var(--ds-space-3) var(--ds-space-5);
    border: 2px solid var(--ds-color-border);
    border-radius: var(--ds-radius-sm);
    background: var(--ds-color-surface);
    color: var(--ds-color-text);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-body);
    appearance: none;
    cursor: pointer;
}

.ds-select > select:focus {
    outline: 2px solid var(--ds-accent-focus);
    outline-offset: -1px;
}

.ds-select > select:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.ds-select-caret {
    position: absolute;
    top: 50%;
    right: var(--ds-space-3);
    transform: translateY(-50%);
    pointer-events: none;
}

.ds-search {
    position: relative;
    display: block;
}

.ds-search > .ds-input {
    padding-left: calc(var(--ds-space-4) + var(--ds-icon-size) + var(--ds-space-2));
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
}

.ds-search-icon {
    position: absolute;
    top: 50%;
    left: var(--ds-space-4);
    color: var(--ds-color-text-muted);
    transform: translateY(-50%);
    pointer-events: none;
}

/* ═══ Tag filter ══════════════════════════════════════════════════════════
   The chosen tags as removable chips, with an inline input that suggests the rest. */

.ds-tagfilter {
    position: relative;
    display: flex;
    flex-wrap: nowrap;
    box-sizing: border-box;
    align-items: center;
    gap: var(--ds-space-2);

    /* Never grows with its content; the tags scroll inside. */
    width: 100%;
    height: var(--ds-control-height);
    padding: 0 var(--ds-space-4);
    border: 2px solid var(--ds-color-border);
    border-radius: var(--ds-radius-sm);
    background: var(--ds-color-surface);
    cursor: text;
}

/* The condition toggle sits outside it, so it stays put. */
.ds-tagfilter-track {
    display: flex;
    flex: 1;
    align-items: center;
    gap: var(--ds-space-2);
    min-width: 0;
    height: 100%;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
}

.ds-tagfilter-track::-webkit-scrollbar {
    display: none;
}

.ds-tagfilter:focus-within {
    outline: 2px solid var(--ds-accent-focus);
    outline-offset: -1px;
}

/* The xs control's geometry, to fit a single-line field. */
.ds-tagfilter-chip {
    display: inline-flex;
    flex: none;
    align-items: center;
    gap: var(--ds-space-1);
    box-sizing: border-box;
    height: var(--ds-control-height-xs);

    /* The × end takes half the padding: a glyph carries its own margin. */
    padding: 0 calc(var(--ds-btn-pad-x-xs) / 2) 0 var(--ds-btn-pad-x-xs);
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
    background: var(--ds-color-info-bg);
    color: var(--ds-color-info-text);
    font-size: var(--ds-text-meta);
    font-weight: var(--ds-weight-medium);
    white-space: nowrap;
}

.ds-tagfilter-remove {
    display: inline-flex;
    flex: none;
    align-items: center;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    cursor: pointer;
    opacity: 0.7;
}

.ds-tagfilter-remove:hover { opacity: 1; }

/* Twice the box, which brings the × up to the label's size. */
.ds-tagfilter-remove > .ds-icon {
    width: calc(var(--ds-space-3) * 2);
    height: calc(var(--ds-space-3) * 2);
}

/* Inside the field, so it must not take the toolbar's control height.
   The extra qualifier beats the toolbar's rule, which comes later at the same specificity. */
.ds-tagfilter .ds-btn,
.ds-panel-toolbar .ds-tagfilter .ds-btn {
    flex: none;
    height: var(--ds-control-height-xs);
    min-height: 0;
    padding: 0 var(--ds-space-2);
    font-size: var(--ds-text-meta);
}

.ds-tagfilter-input {
    flex: none;
    min-width: 120px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--ds-color-text);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-body);
    outline: none;
}

.ds-tagfilter-input::placeholder { color: var(--ds-color-text-muted); }

.ds-tagfilter-panel {
    position: absolute;
    z-index: var(--ds-z-popup);
    top: calc(100% + var(--ds-space-1));

    /* Spans the field, so a field near the right edge keeps its panel on screen. */
    right: 0;
    left: 0;
    box-sizing: border-box;
    max-height: 40vh;
    padding: var(--ds-space-2);
    border-radius: var(--ds-radius-card);
    background: var(--ds-color-surface);
    box-shadow: var(--ds-shadow-dropdown), inset 0 0 0 1px var(--ds-bnw-900);
    overflow-y: auto;
}

.ds-tagfilter-panel[hidden] { display: none; }

/* ═══ Segmented control ═══════════════════════════════════════════════════
   The track is the sunken surface; on the sunken panel use `.ds-segmented--onsunken`. */

.ds-segmented {
    display: inline-flex;
    gap: var(--ds-space-1);
    box-sizing: border-box;
    max-width: 100%;
    padding: var(--ds-space-1);
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
    background: var(--ds-color-surface-sunken);

    /* ⚠ A flex or grid parent blockifies an `inline-flex` child and stretches it; these two lines stop that.
       `margin-inline: auto` would eat a flex row's free space. */
    align-self: center;
    justify-self: center;
}

.ds-segmented--onsunken { background: var(--ds-bnw-850); }

/* For a choice inside a heading, e.g. a form group's head. */
.ds-segmented--sm {
    gap: 2px;
    padding: 2px;
}

.ds-segmented--sm .ds-segmented-item {
    min-height: var(--ds-control-height-xs);
    padding: 0 var(--ds-space-3);
    font-size: var(--ds-text-meta);
}

.ds-segmented--sm .ds-segmented-item + .ds-segmented-item::before {
    left: -1px;
}

.ds-segmented-item {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    min-width: 0;
    min-height: var(--ds-control-height-sm);
    padding: 0 var(--ds-space-5);
    border: 0;
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
    background: transparent;
    color: var(--ds-bnw-600);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-button);
    font-weight: var(--ds-weight-medium);
    cursor: pointer;
    transition: background var(--ds-transition-fast), color var(--ds-transition-fast);
}

/* The ellipsis lives on the text: `text-overflow` does nothing on a flex container. */
.ds-segmented-label {
    overflow: hidden;
    min-width: 0;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* A divider between unselected segments; it disappears around the selected one. */
.ds-segmented-item + .ds-segmented-item::before {
    content: "";
    position: absolute;
    top: 25%;
    bottom: 25%;
    left: calc(-1 * var(--ds-space-1) / 2);
    width: 1px;
    background: var(--ds-color-border-strong);
}

.ds-segmented-item[aria-selected="true"]::before,
.ds-segmented-item[aria-selected="true"] + .ds-segmented-item::before {
    display: none;
}

.ds-segmented-item:hover { color: var(--ds-color-text); }

.ds-segmented-item[aria-selected="true"] {
    background: var(--ds-color-surface);
    color: var(--ds-color-text);
}

/* ═══ Property rows ═══════════════════════════════════════════════════════
   A key and value list. */

.ds-props {
    display: grid;
    grid-template-columns: minmax(120px, max-content) 1fr;
    gap: var(--ds-space-2) var(--ds-space-5);
    margin: 0;
}

.ds-prop-key {
    margin: 0;
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-body);
}

.ds-prop-value {
    margin: 0;
    color: var(--ds-color-text);
    font-size: var(--ds-text-body);
    overflow-wrap: anywhere;
    white-space: pre-wrap;
}

/* ═══ Image ground ════════════════════════════════════════════════════════
   For an image that may be transparent: on a checkerboard every mark meets a light square and a dark one.
   `background-color` under the gradients is what an opaque image's edges land on. */

.ds-image-ground {
    background-color: var(--ds-bnw-800);
    background-image:
        linear-gradient(45deg, var(--ds-color-surface) 25%, transparent 25%),
        linear-gradient(-45deg, var(--ds-color-surface) 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, var(--ds-color-surface) 75%),
        linear-gradient(-45deg, transparent 75%, var(--ds-color-surface) 75%);
    background-position: 0 0, 0 6px, 6px -6px, -6px 0;
    background-size: 12px 12px;
}

/* ═══ Prose ══════════════════════════════════════════════════════════════
   Authored content, so element selectors are right here, inside `.ds-prose`. */

.ds-prose {
    color: var(--ds-color-text);
    font-size: var(--ds-text-body);
    line-height: var(--ds-leading-normal);
    overflow-wrap: anywhere;
}

/* ── Preview actions: copy and download, on hover ─────────────────────────────────────────────
   Added by `attachPreviewActions`. `focus-within` reveals them to the keyboard. */
.ds-preview-hasactions {
    position: relative;
}

.ds-preview-actions {
    position: absolute;
    top: 0;
    right: 0;
    z-index: 1;
    display: flex;
    gap: var(--ds-space-1);
    opacity: 0;
    transition: opacity var(--ds-transition-fast, 0.12s) ease;

    /* At `opacity: 0` alone the buttons would still swallow the pointer. */
    pointer-events: none;
}

.ds-preview-hasactions:hover > .ds-preview-actions,
.ds-preview-hasactions:focus-within > .ds-preview-actions {
    opacity: 1;
    pointer-events: auto;
}

/* A surface behind them, since they overlay content. */
.ds-preview-action {
    background: var(--ds-color-surface);
    box-shadow: var(--ds-shadow-card);
}

.ds-preview-action:hover {
    background: var(--ds-bnw-950);
}

.ds-prose > :first-child { margin-top: 0; }
.ds-prose > :last-child { margin-bottom: 0; }

.ds-prose :where(h1, h2, h3, h4) {
    margin: var(--ds-space-5) 0 var(--ds-space-2);
    font-family: var(--ds-font-display);
    font-weight: var(--ds-weight-semibold);
    line-height: var(--ds-leading-tight);
}

.ds-prose :where(h1) { font-size: var(--ds-text-section); }
.ds-prose :where(h2) { font-size: var(--ds-text-nav); }

.ds-prose :where(h3, h4) {
    font-size: var(--ds-text-body);
}

.ds-prose :where(p) { margin: 0 0 var(--ds-space-3); }

.ds-prose :where(ul, ol) {
    margin: 0 0 var(--ds-space-3);
    padding-left: var(--ds-space-6);
}

.ds-prose :where(li) { margin-bottom: 0; }

/* A loose markdown list wraps every item in a <p>, whose bottom margin would double the gap. */
.ds-prose :where(li) > :where(p):only-child {
    margin-bottom: 0;
}

.ds-prose :where(a) { color: var(--ds-color-primary); }

/* Prose size; `.ds-code` elsewhere stays at meta size. */
.ds-prose :where(code) {
    padding: 0 var(--ds-space-1);
    border-radius: var(--ds-radius-xs);
    background: var(--ds-color-surface-sunken);
    font-family: var(--ds-font-mono);
    font-size: var(--ds-text-body);
}

.ds-prose :where(pre) {
    margin: 0 0 var(--ds-space-3);
    padding: var(--ds-space-4);
    border-radius: var(--ds-radius-md);
    background: var(--ds-color-surface-sunken);
    overflow-x: auto;
}

.ds-prose :where(pre code) {
    padding: 0;
    background: none;
}

/* The frontmatter block `renderMarkdown` splits off. Its `margin: 0` outranks the zero-specificity `pre` rule,
   so the margin is restated. `:not(:last-child)` keeps the last-child reset intact. */
.ds-prose > .ds-code {
    font-size: var(--ds-text-body);
}

.ds-prose > .ds-code:not(:last-child) {
    margin-bottom: var(--ds-space-3);
}

.ds-prose :where(blockquote) {
    margin: 0 0 var(--ds-space-3);
    padding-left: var(--ds-space-4);
    border-left: 2px solid var(--ds-color-border);
    color: var(--ds-color-text-secondary);
}

.ds-prose :where(hr) {
    margin: var(--ds-space-5) 0;
    border: 0;
    border-top: 1px solid var(--ds-color-border);
}

.ds-prose :where(table) {
    width: 100%;
    border-collapse: collapse;
    margin-bottom: var(--ds-space-3);
}

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

/* Capped by height, because line-clamp works on a single text box. The fade is added by script on overflow. */
.ds-prose--clamped {
    max-height: calc(var(--ds-clamp-lines, 10) * var(--ds-leading-normal) * var(--ds-text-body));
    overflow: hidden;
}

/* Added by script only when the content overflows (see markTruncation()). */
.ds-truncated {
    mask-image: linear-gradient(to bottom, #000 calc(100% - 2.5em), transparent);
}

/* ═══ Key-value tree ═════════════════════════════════════════════════════
   Each row is its own two-column grid, so a wrapped value hangs under its own first character:

       key1: value1
       key2222: value 2
                second line of value 2 */

.ds-kv {
    color: var(--ds-color-text);
    font-size: var(--ds-text-body);
    line-height: var(--ds-leading-normal);
}

.ds-kv-row {
    display: grid;
    grid-template-columns: max-content 1fr;
    column-gap: var(--ds-space-2);
    align-items: baseline;
}

/* A bare value has no key to hang under, and the key column (`max-content`) never shrinks. */
.ds-kv-row:not(:has(> .ds-kv-key)) {
    grid-template-columns: minmax(0, 1fr);
}

.ds-kv-row + .ds-kv-row,
.ds-kv-row + .ds-kv-group,
.ds-kv-group + .ds-kv-row,
.ds-kv-group + .ds-kv-group {
    margin-top: var(--ds-space-1);
}

.ds-kv-key {
    font-weight: var(--ds-weight-semibold);
    white-space: nowrap;
}

.ds-kv-cell {
    display: flex;
    min-width: 0;
    align-items: flex-start;
    gap: var(--ds-space-2);
}

.ds-kv-value {
    min-width: 0;
    overflow-wrap: anywhere;
    white-space: pre-wrap;
}

/* One line until opened. */
.ds-kv-cell:not(.is-open) > .ds-kv-value {
    display: -webkit-box;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 1;
}

/* `renderJson` shows it only where the value overflows, and re-measures when the width changes. */
.ds-kv-more {
    display: none;
    flex: none;
    align-items: center;

    /* One line tall, so the chevron sits on the value's first line. */
    height: calc(var(--ds-leading-normal) * 1em);

    /* The caret's box is twice what it occupies (see `.ds-kv-caret`); unclipped, its overhang adds a scrollbar. */
    overflow: clip;
    padding: 0;
    border: 0;
    background: none;
    color: var(--ds-color-text-muted);
    cursor: pointer;
}

.ds-kv-cell--more > .ds-kv-more {
    display: inline-flex;
}

/* The pointer only where the cell is a control. */
.ds-kv-cell--more,
.ds-kv-cell.is-open {
    cursor: pointer;
}

.ds-kv-cell.is-open > .ds-kv-more .ds-kv-caret {
    transform: rotate(90deg);
}

.ds-kv-group {
    display: block;
}

.ds-kv-summary {
    display: flex;
    align-items: baseline;
    gap: var(--ds-space-2);
    cursor: pointer;
    list-style: none;
}

.ds-kv-summary::-webkit-details-marker {
    display: none;
}

.ds-kv-summary:hover .ds-kv-key {
    color: var(--ds-color-primary);
}

/* Drawn at the icon size and laid out at half of it; the negative margin keeps its box 12px square. */
.ds-kv-caret {
    flex: none;
    align-self: center;
    width: var(--ds-icon-size);
    height: var(--ds-icon-size);
    margin: calc((var(--ds-space-4) - var(--ds-icon-size)) / 2);
    transition: transform var(--ds-transition-fast);
}

.ds-kv-group[open] > .ds-kv-summary .ds-kv-caret {
    transform: rotate(90deg);
}

/* Chrome hides <details> content through `::details-content`, which leaves the child's box measurable. */
.ds-kv-group:not([open]) > .ds-kv-nest {
    display: none;
}

.ds-kv-hint {
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
}

.ds-kv-group[open] > .ds-kv-summary .ds-kv-hint {
    display: none;
}

/* Padding between entries: a margin would cut the joining line on the left into dashes. */
.ds-kv-item {
    position: relative;
    margin-left: calc(-1 * var(--ds-space-2));
    padding: var(--ds-space-3) 0 var(--ds-space-3) var(--ds-space-2);
    border-left: 1px solid var(--ds-color-border-subtle);
}

/* Pseudo-elements, because a border is all-or-nothing across the box. */
.ds-kv-item::before,
.ds-kv-item:last-child::after {
    content: '';
    position: absolute;
    left: 0;
    width: 20%;
    height: 1px;
    background: var(--ds-color-border-subtle);
}

.ds-kv-item::before {
    top: 0;
}

.ds-kv-item:last-child::after {
    bottom: 0;
}

/* The outer two rules bound the list, with air on their outer side. */
.ds-kv-item:first-child {
    margin-top: var(--ds-space-1);
}

.ds-kv-item:last-child {
    margin-bottom: var(--ds-space-2);
}

.ds-kv-nest {
    padding-left: var(--ds-space-5);
}

.ds-kv-value--muted {
    color: var(--ds-color-text-muted);
}

.ds-kv--clamped {
    max-height: calc(var(--ds-clamp-lines, 10) * var(--ds-leading-normal) * var(--ds-text-body));
    overflow: hidden;
}

/* ═══ Code block ══════════════════════════════════════════════════════════ */

.ds-code {
    box-sizing: border-box;
    margin: 0;

    /* Wider than tall: monospace text starts hard against the left edge. */
    padding: var(--ds-space-4) var(--ds-space-5);
    border-radius: var(--ds-radius-md);
    background: var(--ds-color-surface-sunken);
    color: var(--ds-color-text);
    font-family: var(--ds-font-mono);
    font-size: var(--ds-text-meta);
    line-height: var(--ds-leading-normal);
    overflow: auto;

    /* `pre-wrap` keeps line breaks while a long line folds; `anywhere` handles a URL with nothing to break on. */
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* Opt out where the line structure matters more than fitting the box. Only then does the block scroll. */
.ds-code--nowrap {
    white-space: pre;
    overflow-wrap: normal;
}

.ds-code--clamped {
    max-height: calc(var(--ds-clamp-lines, 10) * var(--ds-leading-normal) * var(--ds-text-meta));
    overflow: hidden;
}

/* ═══ Chart ═══════════════════════════════════════════════════════════════
   Drawn as DOM: the bars are flex boxes, so they inherit the type ramp.
   ⚠ The palette (`--ds-chart-1…8`) is a colour-blind-safe set in a fixed order. */

.ds-chart {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-4);
}

.ds-chart-plot {
    box-sizing: border-box;
    display: flex;
    align-items: flex-end;
    gap: var(--ds-space-1);
    height: 180px;
    padding-top: var(--ds-space-4);
    border-bottom: 1px solid var(--ds-color-border);
}

.ds-chart-bar {
    display: flex;
    flex: 1;
    flex-direction: column-reverse;
    justify-content: flex-start;
    min-width: 0;
    border-radius: var(--ds-radius-xs) var(--ds-radius-xs) 0 0;
    overflow: hidden;
    cursor: default;
}

.ds-chart-bar:hover {
    outline: 1px solid var(--ds-color-border);
}

.ds-chart-segment {
    width: 100%;
}

/* A hairline between stacked segments, drawn inside the segment: a border would add to the percentage height.
   `column-reverse` puts the first child at the bottom, so the line sits on this segment's top edge. */
.ds-chart-segment:not(:last-child) {
    box-shadow: inset 0 1px 0 var(--ds-color-surface);
}

/* ─── Texture: the second dimension ─────────────────────────────────────────
   A qualifier keeps the series' hue and takes a hatch.
   The stripe is the surface colour, so it holds on a sunken card too. */
.ds-chart-hatch,
.ds-chart-hatch-alt {
    background-image: repeating-linear-gradient(
        var(--ds-chart-hatch-angle, 45deg),
        color-mix(in srgb, var(--ds-color-surface) 70%, transparent) 0 2px,
        transparent 2px 6px
    );
}

.ds-chart-hatch-alt {
    --ds-chart-hatch-angle: 135deg;
}

.ds-chart-axis {
    display: flex;
    gap: var(--ds-space-1);
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
}

.ds-chart-axis > * {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-align: center;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ds-chart-legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ds-space-2) var(--ds-space-5);
}

.ds-chart-legend-item {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
    font-size: var(--ds-text-meta);
}

.ds-chart-swatch {
    flex: none;
    width: var(--ds-space-4);
    height: var(--ds-space-4);
    border-radius: var(--ds-radius-xs);
}

/* ═══ Stat tile ═══════════════════════════════════════════════════════════ */

.ds-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: var(--ds-space-4);
}

.ds-stat {
    padding: var(--ds-space-4) var(--ds-space-5);
    border-radius: var(--ds-radius-card);
    background: var(--ds-color-surface);
}

.ds-stat-value {
    font-family: var(--ds-font-display);
    font-size: var(--ds-text-header);
    font-weight: var(--ds-weight-semibold);
    line-height: var(--ds-leading-tight);
}

.ds-stat-label {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
    margin-top: var(--ds-space-2);
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
}

/* A caption above the value or a label under it, never both. */
.ds-stat-caption {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
    margin-bottom: var(--ds-space-3);
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
    font-weight: var(--ds-weight-medium);
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

.ds-stat-note {
    margin-top: var(--ds-space-2);
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-body);
}

.ds-stat-note-ok { color: var(--ds-color-success-text); }
.ds-stat-note-warn { color: var(--ds-color-warning-text); }
.ds-stat-note-bad { color: var(--ds-color-danger-text); }

/* ── The capsule: a stat that rides in another row ────────────────────────
   The tint is a category and passes no verdict on the number. */
.ds-stat--capsule {
    position: relative;
    display: inline-flex;
    padding: 0;
    background: none;
}

.ds-stat--capsule > .ds-stat-value {
    /* A floor, so a row of numbers keeps one shape. Digits stay right-aligned under the caption. */

    /* border-box, so the floor is the pill; on content-box the padding would be added to it. */
    box-sizing: border-box;
    min-width: 50px;
    padding: var(--ds-space-1) var(--ds-space-4);
    text-align: right;
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
    background: var(--ds-bnw-900);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-body);
    font-weight: var(--ds-weight-semibold);
    line-height: var(--ds-leading-body);
}

/* Absolutely positioned, so a row of capsules keeps the height of its pills. */
.ds-stat--capsule > .ds-stat-label,
.ds-stat--capsule > .ds-stat-caption {
    position: absolute;

    /* A pixel in: the caption's last letter and the last digit carry different side bearings. */
    right: 1px;

    /* `line-height: 1` switches off the half-leading, which at this size is most of the gap. */
    bottom: calc(100% + 1px);
    margin: 0;
    padding-right: var(--ds-space-4);
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-micro);
    line-height: 1;
    font-weight: var(--ds-weight-regular);
    letter-spacing: normal;
    text-transform: lowercase;
    white-space: nowrap;
}

.ds-stat--capsule.ds-stat-info > .ds-stat-value {
    background: var(--ds-color-info-bg);
    color: var(--ds-color-info-text);
}

.ds-stat--capsule.ds-stat-success > .ds-stat-value {
    background: var(--ds-color-success-bg);
    color: var(--ds-color-success-text);
}

.ds-stat--capsule.ds-stat-warning > .ds-stat-value {
    background: var(--ds-color-warning-bg);
    color: var(--ds-color-warning-text);
}

/* ── The band: several tiles as one card ───────────────────────────────── */
.ds-stats-band {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));

    /* The seams are drawn outside each cell and cut at the card's edge. */
    overflow: hidden;
    border-radius: var(--ds-radius-card);
    background: var(--ds-color-surface);
    box-shadow: var(--ds-shadow-card);
}

/* ⚠ A 1px `gap` over a coloured container would show as a grey block in the last row's empty tracks,
   and `border-left` cancelled on `:first-child` fails when the grid wraps. */
.ds-stats-band > .ds-stat {
    padding: var(--ds-space-5) var(--ds-space-6);
    border-radius: 0;
    box-shadow:
        -1px 0 0 var(--ds-color-border),
        0 -1px 0 var(--ds-color-border);
}

/* ═══ Progress ════════════════════════════════════════════════════════════
   Determinate: set the fill's inline width. */

.ds-progress {
    overflow: hidden;
    width: 100%;
    height: var(--ds-space-2);
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
    background: var(--ds-color-border);
}

.ds-progress-bar {
    height: 100%;
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
    background: var(--ds-color-toggle-on);
    transition: width var(--ds-transition-normal);
}

/* Work that is running but cannot say how far: the fill slides instead of measuring. */
.ds-progress--indeterminate .ds-progress-bar {
    width: 35%;
    animation: ds-progress-slide 1.4s ease-in-out infinite;
}

@keyframes ds-progress-slide {
    from { transform: translateX(-100%); }
    to { transform: translateX(285%); }
}

@media (prefers-reduced-motion: reduce) {
    .ds-progress--indeterminate .ds-progress-bar { animation-duration: 3.5s; }
}

/* ═══ Drop target ═════════════════════════════════════════════════════════
   `dropTarget(element)`: while files are dragged over the element, an overlay covers it, naming what a drop does. */

.ds-drop-target:not(dialog) {
    position: relative;
}

.ds-drop-target--dropping::after {
    content: attr(data-drop-label);
    position: absolute;
    inset: var(--ds-space-2);
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 2px dashed var(--ds-color-primary);
    border-radius: var(--ds-radius-md);
    corner-shape: round;
    background: var(--ds-color-surface);
    color: var(--ds-color-primary);
    font-family: var(--ds-font-display);
    font-size: var(--ds-text-section);
    font-weight: var(--ds-weight-semibold);
    pointer-events: none;
}

/* ═══ File list ═══════════════════════════════════════════════════════════
     <ul class="ds-file-list"><li class="ds-file-row">name · size · status · remove, then a .ds-progress</li></ul>
   Rows stand on the surface with no fill of their own; the bar shows only once a transfer moves it. */

.ds-file-list {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-4);
    margin: 0;
    padding: 0;
    list-style: none;
}

.ds-file-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto auto;
    align-items: center;
    column-gap: var(--ds-space-3);
    row-gap: var(--ds-space-2);
    min-height: var(--ds-control-height-sm);
}

.ds-file-row > .ds-progress { grid-column: 1 / -1; }

/* The name gives way; the size, the status and the button keep their width. */
.ds-file-row-name {
    overflow: hidden;
    color: var(--ds-color-text);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ds-file-row-size,
.ds-file-row-status {
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
    font-variant-numeric: tabular-nums;
}

.ds-file-row-status:empty { display: none; }
.ds-file-row-status--success { color: var(--ds-color-success-text); }
.ds-file-row-status--danger { color: var(--ds-color-danger-text); }

/* ═══ Check tree ══════════════════════════════════════════════════════════
     <div class="ds-checktree"><ul class="ds-checktree-picked">…</ul><div class="ds-checktree-box">
       <ul role="tree"><li role="treeitem"><div class="ds-checktree-row">…</div><ul role="group">…</ul></li></ul></div></div>
   `createCheckTree`. A node under a ticked one shows ticked and greyed. */

.ds-checktree {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-2);
    min-width: 0;
}

.ds-checktree-picked,
.ds-checktree-tree,
.ds-checktree-group {
    margin: 0;
    padding: 0;
    list-style: none;
}

.ds-checktree-picked-row {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
    min-height: var(--ds-control-height-xs);
}

.ds-checktree-picked-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    color: var(--ds-color-text);
    font-size: var(--ds-text-body);
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Drawn by its border, as a field is. It clips, so the focus ring is drawn inside the row. */
.ds-checktree-box {
    box-sizing: border-box;
    max-height: calc(var(--ds-control-height-sm) * 8);
    padding: var(--ds-space-2);
    overflow-y: auto;
    border: 2px solid var(--ds-color-border);
    border-radius: var(--ds-radius-sm);
}

/* One step is the caret and its gap, so a child's caret stands under its parent's box. */
.ds-checktree-group {
    padding-left: calc(var(--ds-icon-size) + var(--ds-space-2));
}

.ds-checktree-row {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
    min-height: var(--ds-control-height-sm);
    padding: 0 var(--ds-space-3) 0 var(--ds-space-1);
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
    color: var(--ds-color-text);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-body);
    cursor: pointer;
    user-select: none;
}

.ds-checktree-row:hover { background: var(--ds-bnw-950); }

.ds-checktree-item:focus { outline: none; }

.ds-checktree-item:focus-visible > .ds-checktree-row {
    outline: var(--ds-focus-ring-width) solid var(--ds-accent-focus);
    outline-offset: calc(-1 * var(--ds-focus-ring-width));
}

.ds-checktree-item[aria-disabled="true"] > .ds-checktree-row {
    color: var(--ds-color-text-muted);
    cursor: default;
}

.ds-checktree-toggle {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: var(--ds-icon-size);
    height: var(--ds-icon-size);
    color: var(--ds-color-text-muted);
}

.ds-checktree-caret {
    width: var(--ds-icon-size);
    height: var(--ds-icon-size);
    transition: transform var(--ds-transition-fast);
}

.ds-checktree-item[aria-expanded="true"] > .ds-checktree-row .ds-checktree-caret {
    transform: rotate(90deg);
}

.ds-checktree-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Starts where a row's name does. */
.ds-checktree-note {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
    min-height: var(--ds-control-height-sm);
    padding-left: calc(var(--ds-space-1) + var(--ds-icon-size) + var(--ds-space-2));
    color: var(--ds-color-text-muted);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-meta);
}

/* ═══ Spinner ═════════════════════════════════════════════════════════════
   Indeterminate progress. */

.ds-spinner {
    display: block;
    box-sizing: border-box;
    width: var(--ds-icon-size);
    height: var(--ds-icon-size);
    border: 2px solid var(--ds-color-border);
    border-radius: var(--ds-radius-pill);
    corner-shape: round;

    border-top-color: var(--ds-color-toggle-on);
    animation: ds-spin 0.7s linear infinite;
}

.ds-spinner-lg {
    width: 36px;
    height: 36px;
    border-width: 3px;
}

/* An icon's circle is drawn at 18px inside its 24px box, so the ring steps under that. */
.ds-spinner-sm {
    width: 16px;
    height: 16px;
    border-width: 1.5px;
}

@keyframes ds-spin {
    to { transform: rotate(1turn); }
}

/* `aria-busy="true"` and a `.ds-spinner` child: the ring turns over the button, so the control keeps its width. */
.ds-btn[aria-busy="true"] {
    position: relative;
}

.ds-btn[aria-busy="true"] > *:not(.ds-spinner) {
    opacity: 0.25;
}

.ds-btn > .ds-spinner {
    position: absolute;
    top: 50%;
    left: 50%;
    margin: calc(var(--ds-icon-size) / -2) 0 0 calc(var(--ds-icon-size) / -2);
}

/* Slowed: stopping it would say "finished". */
@media (prefers-reduced-motion: reduce) {
    .ds-spinner { animation-duration: 2.4s; }
}

.ds-dialog-body--danger {
    color: var(--ds-color-danger-text);
}

/* ═══ Prompt ══════════════════════════════════════════════════════════════
   Markup:
     <form class="ds-prompt">
       <span class="ds-prompt-chips">…badges…</span>
       <textarea class="ds-prompt-input" rows="1"></textarea>
       <button class="ds-prompt-send">…icon…</button>
     </form> */

.ds-prompt {
    display: flex;
    align-items: center;
    gap: var(--ds-space-3);

    /* ⚠ border-box, so `--ds-prompt-height` is the bar's real height; the page has no universal reset. */
    box-sizing: border-box;
    min-height: var(--ds-prompt-height);

    /* Clear of the pill's curve. */
    padding: var(--ds-space-2) var(--ds-space-2) var(--ds-space-2)
        calc(var(--ds-space-5) + var(--ds-space-3));
    border: 0;
    border-radius: var(--ds-radius-pill);
    corner-shape: round;

    /* Opaque: `backdrop-filter` would make the bar a containing block for its `position: fixed` popovers. */
    background: var(--ds-color-surface);
    box-shadow: var(--ds-shadow-popover);
}

/* A modifier the component toggles: a one-line bar is taller than its field. */
.ds-prompt--multiline {
    align-items: flex-end;
}

/* The component grows it to its content. The growth is measured from the explicit `line-height`,
   and `display: block` removes the descender stripe under an inline-block textarea. */
.ds-prompt-input {
    display: block;
    flex: 1;
    min-width: 0;
    height: var(--ds-prompt-line);
    padding: 0;
    border: 0;
    background: none;
    color: var(--ds-color-text);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-nav);
    line-height: var(--ds-prompt-line);

    resize: none;
    outline: none;
    overflow-y: hidden;
}

.ds-prompt-input::placeholder {
    color: var(--ds-color-text-muted);
}

/* A disabled control dispatches no mouse events, even to its ancestors.
   `pointer-events: none` lets the tap land on the bar and bubble. */
.ds-prompt :disabled {
    pointer-events: none;
}

.ds-prompt-send {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 50px;
    height: 50px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    corner-shape: round;
    background: linear-gradient(100deg, var(--ds-bnw-950) 0%, var(--ds-hero-850) 100%);
    color: var(--ds-color-text);
    cursor: pointer;
}

.ds-prompt-send:hover {
    background: linear-gradient(100deg, var(--ds-bnw-1000) 0%, var(--ds-hero-800) 100%);
}

/* It stops the running turn, so it takes the danger fill. */
.ds-prompt-send--stop,
.ds-prompt-send--stop:hover {
    background: var(--ds-color-danger-default);
    color: var(--ds-color-text-on-accent);
}

.ds-prompt-send--stop:hover {
    background: var(--ds-color-danger-hover);
}

.ds-prompt-side {
    display: flex;
    flex: none;
    flex-direction: column;
    align-items: flex-end;

    /* `align-self`, because the send disc is centred at rest and only the side column is pinned. */
    align-self: flex-end;

    gap: 4px;
    padding: var(--ds-space-1) 0;
}

.ds-prompt-chips {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
}

.ds-prompt-chips:empty {
    display: none;
}

/* A block wrapper carries a line box taller than its pill; as a flex box it is exactly its pill. */
.ds-prompt-chips .ds-dropdown {
    display: flex;
}

.ds-prompt-tag {
    background: var(--ds-hero-850);
    color: var(--ds-hero-200);
}

/* Capped, so a long scope wraps into a readable block. */
.ds-prompt-refs {
    max-width: min(360px, calc(100vw - 2 * var(--ds-space-5)));
    padding: var(--ds-space-4);
}

.ds-prompt-refs-list {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-3);
}

.ds-prompt-refs-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ds-space-2);
}

/* ── Attachments ───────────────────────────────────────────────────────── */

.ds-prompt-attach {
    display: flex;
}

/* Never `display: none`: a hidden input still has to be clickable. */
.ds-prompt-file-input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

/* The ring is drawn inside: an outset ring on a pill reads as a second pill. */
.ds-prompt--dropping {
    outline: 2px dashed var(--ds-color-primary);
    outline-offset: -3px;
}

.ds-prompt-files {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-3);
}

.ds-prompt-files-list {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-1);
}

.ds-prompt-file {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
    min-height: var(--ds-control-height-sm);
    padding: 0 0 0 var(--ds-space-3);
    border-radius: var(--ds-radius-sm);
    background: var(--ds-color-surface-sunken);
    color: var(--ds-color-text);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-meta);
}

/* The name gives way; the size keeps its width. */
.ds-prompt-file-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ds-prompt-file-size {
    flex: none;
    color: var(--ds-color-text-muted);
    font-variant-numeric: tabular-nums;
}

.ds-prompt-file-drop {
    flex: none;
}

/* ═══ Conversation ════════════════════════════════════════════════════════
     <div class="ds-conversation">
       <div class="ds-conversation-thread">…the conversation frame…</div>
       <div class="brain-block">…why the prompt is disabled…</div>
       <div class="pending-answers">…the waiting questions…</div>
       <div class="ds-conversation-suggestion">…what to ask for…</div>
       <form class="ds-prompt">…</form>
     </div> */

.ds-conversation {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-3);
    min-height: 0;
}

/* Foot over thread: the messages scroll under the prompt.
   ⚠ The thread is an iframe, so the panel publishes the foot's height as `SET_BOTTOM_INSET`
   and the frame turns it into padding. The two go together. */
.ds-conversation--overlay {
    position: relative;
    gap: 0;

    /* The thread is out of flow, so the foot's rows would otherwise stack from the top. */
    justify-content: flex-end;
}

.ds-conversation--overlay > .ds-conversation-thread {
    position: absolute;
    inset: 0;
}

/* The inset is a margin: the thread is positioned against the container's padding box,
   and padding would inset the messages too. */
.ds-conversation--overlay > .brain-block,
.ds-conversation--overlay > .pending-answers,
.ds-conversation--overlay > .ds-prompt {
    position: relative;
    z-index: 1;
    margin-inline: var(--ds-space-4);
}

.ds-conversation--overlay > .ds-prompt {
    margin-bottom: var(--ds-space-4);
}

.ds-conversation--overlay > .brain-block,
.ds-conversation--overlay > .pending-answers {
    margin-bottom: var(--ds-space-3);
}

/* This column has no `gap`, so the suggestion has none to cancel. */
.ds-conversation--overlay > .ds-conversation-suggestion {
    margin-bottom: 0;
}

.ds-conversation-thread {
    display: flex;
    flex: 1;
    min-height: 0;
    overflow: hidden;
    border-radius: var(--ds-radius-card);
    background: var(--ds-color-surface);
}

.ds-conversation-thread > iframe {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
}

/* ⚠ Unscoped: a bare `.ds-prompt` outside a conversation panel shows the same strip. */
.brain-block {
    display: flex;
    align-items: center;
    gap: var(--ds-space-3);
    padding: var(--ds-space-3) var(--ds-space-4);
    border-radius: var(--ds-radius-card);
    background: var(--ds-color-surface);
    box-shadow: var(--ds-shadow-popover);
}

.brain-block.hidden {
    display: none;
}

.brain-block-text {
    flex: 1;
    color: var(--ds-color-text-secondary);
    font-size: var(--ds-text-meta);
    line-height: 1.35;
}

.brain-block-btn {
    flex: none;
}

/* The frame is display-only and hands the answering up here, beside the only field on screen. */
.ds-conversation .pending-answers {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-3);
    padding: var(--ds-space-4);
    border-radius: var(--ds-radius-card);
    background: var(--ds-color-surface);
    box-shadow: var(--ds-shadow-popover);
}

.ds-conversation .pending-answers.hidden {
    display: none;
}

/* ⚠ It floats over the thread: the host is `height: 0` and the button is positioned out of it,
   so showing and hiding it moves nothing. The host's one column `gap` is cancelled below.
   Hidden while the field has text in it, because a suggestion overwrites the prompt. */
.ds-conversation .ds-conversation-suggestion {
    position: relative;
    z-index: 2;
    flex: none;
    height: 0;
    margin-bottom: calc(-1 * var(--ds-space-3));
}

/* Clear of the prompt and its shadow. */
.ds-conversation .ds-conversation-suggestion > .ds-btn {
    position: absolute;
    bottom: 0;
    left: 50%;
    margin-bottom: var(--ds-space-5);

    /* Its own shadow, because it floats over text. */
    box-shadow: var(--ds-shadow-popover);
    transform: translateX(-50%);
}

.ds-conversation .ds-conversation-suggestion.hidden {
    display: none;
}

.ds-conversation .pending-answer {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-2);
}

.ds-conversation .pending-answer-question {
    color: var(--ds-color-text-secondary);
    font-size: var(--ds-text-meta);
    line-height: 1.35;
}

.ds-conversation .pending-answer-options {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--ds-space-2);
}

.ds-conversation .pending-answer-option {
    display: inline-flex;
    align-items: center;
    gap: var(--ds-space-2);
    padding: var(--ds-space-2) var(--ds-space-4);
    border: 1px solid var(--ds-color-border);
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
    background: var(--ds-color-surface);
    color: var(--ds-color-text);
    font: inherit;
    font-size: var(--ds-text-meta);
    cursor: pointer;
}

.ds-conversation .pending-answer-option:hover {
    border-color: var(--ds-accent-default);
    color: var(--ds-accent-default);
}

/* ═══ Toast ═══════════════════════════════════════════════════════════════ */

/* `showModal()` puts a dialog in the top layer, above every z-index, so the host is a `popover` (see `toast()`).
   The rest of this block undoes the UA's popover defaults. */
.ds-toasts {
    position: fixed;
    z-index: var(--ds-z-toast);
    inset: auto var(--ds-space-5) var(--ds-space-5) auto;
    display: flex;
    overflow: visible;
    width: auto;
    height: auto;
    max-width: min(420px, calc(100vw - 2 * var(--ds-space-5)));
    margin: 0;
    padding: 0;
    border: 0;
    background: transparent;
    flex-direction: column;
    gap: var(--ds-space-3);

    /* The host is shown for the life of the page, so an empty one must not swallow clicks. */
    pointer-events: none;
}

.ds-toast {
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: var(--ds-space-3);
    padding: var(--ds-space-4) var(--ds-space-5);
    border-radius: var(--ds-radius-card);
    background: var(--ds-color-surface);
    box-shadow: var(--ds-shadow-popover);
    color: var(--ds-color-text);
    font-size: var(--ds-text-body);

    border-left: var(--ds-space-1) solid var(--ds-color-toggle-on);
}

.ds-toast--success { border-left-color: var(--ds-color-success-text); }
.ds-toast--danger { border-left-color: var(--ds-color-danger-text); }

/* ═══ Empty state ═════════════════════════════════════════════════════════
   The header step for the title: a display heading overwhelms a panel. */

.ds-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--ds-space-4);
    padding: var(--ds-space-7) var(--ds-space-5);
    text-align: center;
}

/* ⚠ Alone in its container it takes the whole area and centres in it. Beside other content it claims no growth. */
.ds-empty:only-child {
    flex: 1 1 auto;
    justify-content: center;
}

.ds-empty-icon {
    width: calc(var(--ds-icon-size) * 2);
    height: calc(var(--ds-icon-size) * 2);
    color: var(--ds-color-text-muted);
}

.ds-empty-title {
    margin: 0;
    font-family: var(--ds-font-display);
    font-size: var(--ds-text-header);
    font-weight: var(--ds-weight-semibold);
    line-height: var(--ds-leading-tight);
    color: var(--ds-color-text);
}

.ds-empty-body {
    max-width: 52ch;
    margin: 0;
    color: var(--ds-color-text-secondary);
}

/* ═══ Section ═════════════════════════════════════════════════════════════
   The heading is the `h2` step, above a card title's 21px. */

/* ⚠ A block never spaces itself: the gap between sections belongs to the container. */
.ds-section-body {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-5);
}

/* Title and subtitle share a baseline, a control on the right is centred, and the head stands in from the cards. */
.ds-section-head {
    display: flex;
    align-items: baseline;
    gap: var(--ds-space-4);
    margin-bottom: var(--ds-space-5);
    padding-left: var(--ds-space-4);
}

.ds-section-title {
    margin: 0;
    font-family: var(--ds-font-display);
    font-size: var(--ds-text-header);
    font-weight: var(--ds-weight-semibold);
    line-height: var(--ds-leading-tight);
    color: var(--ds-color-text);
}

.ds-section-subtitle {
    min-width: 0;
    overflow: hidden;
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ds-section-action {
    align-self: center;
    margin-left: auto;
}

/* A segmented centres itself; in a section head it keeps to the title's baseline. */
.ds-section-head > .ds-section-control {
    align-self: baseline;
}

/* An action on what the control chose stands beside it, not at the far end. */
.ds-section-control ~ .ds-section-action {
    margin-left: 0;
}

/* A section sits on the sunken panel. `:where()` keeps this at zero specificity, so ds-btn-accent / -danger still win. */
.ds-section-action:where(.ds-btn:not(.ds-btn-accent, .ds-btn-danger, .ds-btn-ghost, .ds-btn-white)) {
    background: var(--ds-bnw-850);
}

.ds-section-action:where(.ds-btn:not(.ds-btn-accent, .ds-btn-danger, .ds-btn-ghost, .ds-btn-white)):hover {
    background: #dad9d8;
}

.ds-section-action:where(.ds-btn:not(.ds-btn-accent, .ds-btn-danger, .ds-btn-ghost, .ds-btn-white)):active {
    background: #c8c7c6;
}

/* ═══ Pipeline ════════════════════════════════════════════════════════════
   A step is a div with `role="button"`: its state slot may hold a control, and a button cannot nest in a button.
   Selection is an inset ring, so a step's box keeps its size between states. */

.ds-pipeline {
    display: flex;
    align-items: stretch;
    gap: var(--ds-space-2);
}

.ds-pipeline-step {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: var(--ds-space-2);
    box-sizing: border-box;
    min-width: 0;
    padding: var(--ds-space-4) var(--ds-space-5);
    border-radius: var(--ds-radius-md);
    background: var(--ds-color-surface);
    color: var(--ds-color-text);
    text-align: left;
}

.ds-pipeline-step[role="button"] {
    cursor: pointer;
    transition: box-shadow var(--ds-transition-fast);
}

.ds-pipeline-step[role="button"]:hover {
    box-shadow: inset 0 0 0 1px var(--ds-color-border);
}

.ds-pipeline-step[role="button"]:focus-visible {
    outline: 2px solid var(--ds-accent-focus);
    outline-offset: -2px;
}

.ds-pipeline-step--active,
.ds-pipeline-step--active:hover {
    box-shadow: inset 0 0 0 2px var(--ds-color-toggle-on);
}

.ds-pipeline-step--disabled {
    color: var(--ds-color-text-muted);
}

.ds-pipeline-label {
    display: block;
    overflow: hidden;
    min-width: 0;
    font-family: var(--ds-font-display);
    font-size: var(--ds-text-nav);
    font-weight: var(--ds-weight-medium);
    line-height: var(--ds-leading-control);
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* A control's min-height, so a step with a button is as tall as one showing a tag. */
.ds-pipeline-state {
    display: flex;
    align-items: center;
    min-height: var(--ds-control-height-sm);
}

/* Far right, where a state tag never is. */
.ds-pipeline-state > .ds-btn {
    margin-left: auto;
}

.ds-pipeline-arrow {
    align-self: center;
    flex: none;
    color: var(--ds-color-text-muted);
}

/* ═══ Web page preview ════════════════════════════════════════════════════
   The frame is laid out at 1/SCALE of the box and scaled down, so the page inside keeps its desktop layout.
   ⚠ Stored html is framed as srcdoc, a third-party page only as `--live`: most sites forbid cross-origin framing. */

.ds-webpage {
    display: flex;
    flex: 1;
    flex-direction: column;
    overflow: hidden;
    min-height: 0;

    /* The page inside brings its own background, so the border marks the window. */
    border: 1px solid var(--ds-color-border);

    /* The card radius would eat the page's top corners. */
    border-radius: var(--ds-radius-md);
    background: var(--ds-color-surface);
    box-shadow: var(--ds-shadow-card);
}

.ds-webpage-chrome {
    display: flex;
    flex: none;
    align-items: center;
    gap: var(--ds-space-4);
    padding: var(--ds-space-3) var(--ds-space-4);
    border-bottom: 1px solid var(--ds-color-border);
    background: var(--ds-color-surface-sunken);
}

.ds-webpage-dots {
    display: flex;
    flex: none;
    gap: var(--ds-space-2);
}

.ds-webpage-dots > i {
    width: 8px;
    height: 8px;
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
    background: var(--ds-color-border-strong);
}

.ds-webpage-addr {
    overflow: hidden;

    /* Takes the rest of the row, so whatever follows pins right. */
    flex: 1;
    min-width: 0;
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ds-webpage-open { flex: none; }

/* Never collapses: at 50% scale 360px of box is 720px of page. */
.ds-webpage-body {
    position: relative;
    flex: 1;
    overflow: hidden;
    min-height: var(--ds-webpage-min-height, 360px);
}

.ds-webpage-frame {
    position: absolute;
    top: 0;
    left: 0;
    width: calc(100% / var(--ds-webpage-scale, 0.5));
    height: calc(100% / var(--ds-webpage-scale, 0.5));
    border: 0;
    transform: scale(var(--ds-webpage-scale, 0.5));
    transform-origin: top left;
}

/* A live page renders at its own scale; the transform above is for stored markup written for a desktop width. */
.ds-webpage-frame--live {
    width: 100%;
    height: 100%;
    transform: none;
}

.ds-webpage-note {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--ds-space-4);

    /* border-box, or `height: 100%` plus the padding makes the note taller than the body it fills. */
    box-sizing: border-box;
    height: 100%;
    padding: var(--ds-space-6);
    color: var(--ds-color-text-secondary);
    text-align: center;
}

/* ═══ Ad preview ══════════════════════════════════════════════════════════
   The CTA is styled as a button and is part of the picture; pressing it does nothing. */

.ds-ad {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-4);
}

.ds-ad-text {
    margin: 0;
    white-space: pre-wrap;
}

.ds-ad-utm {
    margin: 0;
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
}

.ds-ad-utm-label { color: var(--ds-color-text-secondary); }

/* One click takes the whole tag. */
.ds-ad-utm-value { user-select: all; }

/* The full text is in every element's `title`. */
.ds-ad--compact .ds-ad-text {
    display: -webkit-box;
    overflow: hidden;
    -webkit-line-clamp: var(--ds-ad-text-lines, 3);
    -webkit-box-orient: vertical;
}

.ds-ad--compact .ds-ad-title,
.ds-ad--compact .ds-ad-subtitle {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* `contain`, never `cover`: a cropped ad is a different ad. */
.ds-ad-media {
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    aspect-ratio: var(--ds-ad-aspect, 1 / 1);
    border-radius: var(--ds-radius-md);

    /* The plate is `.ds-image-ground` (added by `adPreview()`); a `background` shorthand here would reset it. */
}

.ds-ad-media > img {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

/* A clip is drawn as its first frame, sized like the image it replaces. */
.ds-ad-media > video {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

.ds-ad-caption {
    display: flex;
    align-items: center;
    gap: var(--ds-space-4);
}

.ds-ad-copy {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: var(--ds-space-1);
    min-width: 0;
}

.ds-ad-title {
    margin: 0;
    font-size: var(--ds-text-button);
    font-weight: var(--ds-weight-semibold);
    line-height: var(--ds-leading-control);
}

.ds-ad-subtitle {
    margin: 0;
    color: var(--ds-color-text-secondary);
    font-size: var(--ds-text-meta);
}

.ds-ad-ctas {
    display: flex;
    flex: none;
    gap: var(--ds-space-2);
}

/* ── The tabbed form: the ad as two pages ────────────────────────────────
   The floor is 250px on the creative's short side; under it the page scrolls.
   `--ds-ad-w` / `--ds-ad-h` are the ratio's two numbers, set by `show()`. */

/* It fills its container, because the pages are sized against the room the ad has. */
.ds-ad--tabbed {
    height: 100%;
    min-height: 0;
}

.ds-ad-tabs {
    align-self: flex-start;
}

.ds-ad-panel {
    display: flex;
    min-height: 0;
    flex: 1 1 auto;
    flex-direction: column;
    gap: var(--ds-space-4);
}

/* `hidden` is `display: block` in every UA sheet, which the rule above beats. */
.ds-ad-panel[hidden] {
    display: none;
}

/* Copy can be any length, so its page is the scroller. */
.ds-ad-panel--description {
    overflow-y: auto;
}

.ds-ad-panel--banner {
    overflow: auto;
}

/* The page brings its own layout; this gives it room and a scroller. */
.ds-ad-panel--page {
    overflow: auto;
}

/* `size` containment makes `cqh` mean the height flexbox gave this row.
   The `min-height` is the floor read back through the ratio. */
.ds-ad-fit {
    display: flex;
    min-height: max(250px, calc(250px * var(--ds-ad-h, 1) / var(--ds-ad-w, 1)));
    align-items: center;
    justify-content: center;
    container-type: size;
    flex: 1 1 auto;
}

.ds-ad-fit > .ds-ad-media {
    /* Whichever bound binds first. Past 850px a creative is upscaled. */
    width: min(100cqw, 850px, calc(100cqh * var(--ds-ad-w, 1) / var(--ds-ad-h, 1)));
    min-width: max(250px, calc(250px * var(--ds-ad-w, 1) / var(--ds-ad-h, 1)));
    flex: none;
}

/* ═══ Sheet ═══════════════════════════════════════════════════════════════
   A full-screen <dialog>, for a record viewer that needs the whole window.
   Same element as .ds-dialog, different shape. */

.ds-dialog-sheet {
    width: 100vw;
    max-width: none;
    height: 100vh;
    max-height: none;
    padding: 0;
    border-radius: 0;
}

.ds-sheet-head {
    display: flex;
    align-items: center;
    gap: var(--ds-space-4);
    padding: var(--ds-space-5) var(--ds-space-6);
    border-bottom: 1px solid var(--ds-color-border);
}

.ds-sheet-body {
    height: calc(100% - 78px);
    padding: var(--ds-space-6);
    overflow: auto;
}

/* ═══ App shell ═══════════════════════════════════════════════════════════
     |<-- 229 -->|
     +-----------+--------------------------------------------+
     | project   |  Section title (30px)   [ Submenu v ]   (o)|  <- topbar
     |  switcher +--------------------------------------------+
     | nav item  |                                            |
     | nav item  |   content panel, r30, BnW-900              |
     | ......... |                                            |
     | profile   |                                            |
     +-----------+--------------------------------------------+
                                                12px gutter --^

   Below 900px the sidebar collapses into a drawer behind `.ds-app-menu-btn`. */

.ds-app {
    display: grid;
    grid-template-columns: var(--ds-sidebar-width) 1fr;
    grid-template-rows: 100%;
    height: 100%;
    background: var(--ds-color-surface);
}

/* ── Sidebar ─────────────────────────────────────────────────────────────── */

.ds-sidebar {
    display: flex;
    flex-direction: column;
    box-sizing: border-box; /* mobile sets an explicit width; padding must live inside it */
    min-height: 0;
    padding: 0 var(--ds-space-5) var(--ds-space-4);
}

/* ── Top band: project selector | section title ───────────────────────────
   The two rows sit in different grid columns, so CSS cannot baseline-align them.
   Both are the same fixed-height band, and each carries a zero-width strut at the title's font size. */

.ds-sidebar-head,
.ds-topbar {
    display: flex;
    box-sizing: border-box;
    align-items: baseline;
    min-height: var(--ds-topbar-height);
    padding-top: var(--ds-space-5);
}

.ds-sidebar-head::before,
.ds-topbar::before {
    content: "\200b"; /* zero-width space: generates a line box, shows nothing */
    width: 0;
    overflow: hidden;
    font-family: var(--ds-font-display);
    font-size: var(--ds-text-header);
    font-weight: var(--ds-weight-semibold);
    line-height: var(--ds-leading-tight);
}

.ds-sidebar-head { position: relative; }

.ds-sidebar-brand {
    display: flex;

    /* A flex container reports the baseline of its first baseline-aligned child,
       so the logo and the caret are centred out of the baseline group. */
    align-items: baseline;
    gap: var(--ds-space-2);

    /* The 2px outline comes off the padding, so the name's baseline does not move. */
    box-sizing: border-box;
    width: 100%;
    padding: calc(var(--ds-space-2) - 2px) calc(var(--ds-space-4) - 2px);
    border: 2px solid var(--ds-color-surface-sunken);
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
    background: transparent;
    color: var(--ds-color-text);
    font-family: var(--ds-font-display);
    font-size: var(--ds-text-nav);
    font-weight: var(--ds-weight-medium);
    cursor: pointer;
}

.ds-sidebar-brand:hover { background: var(--ds-bnw-950); }

.ds-sidebar-brand:disabled {
    cursor: default;
}

.ds-sidebar-brand:disabled:hover { background: transparent; }

.ds-sidebar-brand:disabled .ds-sidebar-brand-caret { visibility: hidden; }

/* The app's name at the head of the sidebar, where there is nothing to switch; starts on the nav icons' x. */
.ds-sidebar-title {
    overflow: hidden;
    padding: 0 var(--ds-space-4);
    color: var(--ds-color-text);
    font-family: var(--ds-font-display);
    font-size: var(--ds-text-nav);
    font-weight: var(--ds-weight-semibold);
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* An optical correction for the empty space the glyph carries on its right. */
.ds-sidebar-brand-caret {
    flex: none;
    align-self: center;
    margin-right: -3px;
}

/* An icon-sized box, so the name starts on the same x as the nav labels. */
.ds-sidebar-brand-logo {
    flex: none;
    align-self: center;
    box-sizing: content-box;
    width: 18px;
    height: 18px;
    padding: 0 calc((var(--ds-icon-size) - 18px) / 2);
    border-radius: var(--ds-radius-xs);
}

.ds-sidebar-brand-name {
    flex: 1;
    overflow: hidden;
    text-align: left;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* An empty span generates no line box; the zero-width space keeps the row's height until the name loads. */
.ds-sidebar-brand-name::before {
    content: "\200b";
}

.ds-nav {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: 0;
    min-height: 0;
    margin-top: var(--ds-space-5);
    overflow-y: auto;

    /* Firefox honours `scrollbar-width`; WebKit needs the pseudo-elements below. */
    scrollbar-width: thin;
    scrollbar-color: var(--ds-bnw-800) transparent;
}

/* WebKit draws custom scrollbars permanently, so the track disappears. */
.ds-nav::-webkit-scrollbar {
    width: 6px;
}

.ds-nav::-webkit-scrollbar-track {
    background: transparent;
}

.ds-nav::-webkit-scrollbar-thumb {
    border-radius: 3px;
    background: var(--ds-bnw-800);
}

.ds-nav-divider {
    height: 1px;
    margin: var(--ds-space-2) var(--ds-space-4);
    border: 0;
    background: var(--ds-color-border);
}

.ds-sidebar-foot { margin-top: var(--ds-space-4); }

/* ⚠ Its fill is the selected nav row's fill. The two are never in the same list. */
.ds-profile {
    display: flex;
    align-items: center;
    gap: var(--ds-space-3);
    box-sizing: border-box;
    width: 100%;
    padding: var(--ds-space-4) var(--ds-space-5) var(--ds-space-4) var(--ds-space-4);
    border: 0;
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
    background: var(--ds-color-surface-sunken);
    color: var(--ds-color-text);
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-body);
    text-align: left;
    cursor: pointer;
}

.ds-profile:hover { background: var(--ds-bnw-850); }

/* On the row's own fill, so the plate is the surface colour. */
.ds-profile-avatar {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border-radius: var(--ds-radius-pill);
    corner-shape: round;

    /* An outline, not a border: it takes no room from the picture and paints over it, so a white one keeps an edge. */
    outline: 1px solid var(--ds-color-border);
    outline-offset: -1px;
    background: var(--ds-color-surface);
    font-family: var(--ds-font-display);
    font-size: var(--ds-text-meta);
    font-weight: var(--ds-weight-semibold);
}

/* `corner-shape` doesn't inherit, and the universal squircle would square the picture off inside its round plate. */
.ds-profile-avatar > img {
    width: 100%;
    height: 100%;
    border-radius: inherit;
    corner-shape: round;
    object-fit: cover;
}

.ds-profile-body {
    min-width: 0;
    line-height: var(--ds-leading-control);
}

.ds-profile-name {
    display: block;
    overflow: hidden;
    font-weight: var(--ds-weight-medium);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ds-profile-role {
    display: block;
    color: var(--ds-color-text-muted);
    font-size: var(--ds-text-meta);
}

/* ── Nav item ────────────────────────────────────────────────────────────── */

.ds-nav-item {
    display: flex;
    align-items: center;
    gap: var(--ds-space-2);
    box-sizing: border-box;
    width: 100%;
    min-height: var(--ds-control-height);
    padding: 0 var(--ds-space-4);
    border: 0;
    border-radius: var(--ds-radius-pill);
    corner-shape: round;
    background: transparent;
    color: var(--ds-color-text);
    font-family: var(--ds-font-display);
    font-size: var(--ds-text-nav);
    font-weight: var(--ds-weight-medium);
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}

/* ⚠ `text-decoration` is restated: a nav row is an `<a>`, and `.ds-scope :where(a):hover` (0-2-0) outranks
   the `text-decoration: none` on `.ds-nav-item` (0-1-0). */
.ds-nav-item:hover {
    background: var(--ds-color-hover-faint);
    text-decoration: none;
}

.ds-nav-item[aria-current="page"] { background: var(--ds-color-surface-sunken); }

/* Two colours cannot be layered in `background`, so the translucent hover is a one-colour gradient
   image over the selected row's fill. */
.ds-nav-item[aria-current="page"]:hover {
    background-color: var(--ds-color-surface-sunken);
    background-image: linear-gradient(var(--ds-color-hover-faint), var(--ds-color-hover-faint));
}

.ds-nav-item-icon {
    flex: none;
    align-self: center;
    width: var(--ds-icon-size);
    height: var(--ds-icon-size);
}

/* Indented to the section label: row padding, icon and gap. */

.ds-nav-sub {
    display: flex;
    flex-direction: column;
    padding: var(--ds-space-1) 0 var(--ds-space-2);
    padding-left: calc(var(--ds-space-4) + var(--ds-icon-size) + var(--ds-space-2));
}

.ds-nav-subitem {
    display: flex;
    align-items: center;
    box-sizing: border-box;
    min-height: 24px;
    padding: var(--ds-space-1) 0;

    /* The muted text role: the design's grey for inactive sub-items is unreadable on white. */
    color: var(--ds-color-text-muted);
    font-family: var(--ds-font-display);
    font-size: var(--ds-text-body);
    font-weight: var(--ds-weight-medium);
    text-decoration: none;
}

/* `text-decoration` is restated for the reason given at `.ds-nav-item:hover`. */
.ds-nav-subitem:hover {
    color: var(--ds-color-text);
    text-decoration: none;
}

.ds-nav-subitem[aria-current="true"] { color: var(--ds-color-text); }

/* ── Main column ─────────────────────────────────────────────────────────── */

.ds-main {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
}

.ds-topbar {
    flex: none;
    gap: var(--ds-space-5);
    padding-right: var(--ds-space-6);
}

/* Every level of the chain (slot, dropdown, trigger) has to expose a text baseline; the tests check it. */
.ds-topbar-slot {
    display: flex;
    align-self: baseline;
    align-items: baseline;
    gap: var(--ds-space-3);
    min-width: 0;
}

.ds-topbar-slot > .ds-dropdown { align-self: baseline; }

/* A slot holding a spacer spans the topbar. The topbar's own spacer stops growing then,
   or the two would split the free room. */
.ds-topbar-slot:has(> .ds-topbar-spacer) { flex: 1; }

.ds-topbar:has(> .ds-topbar-slot > .ds-topbar-spacer) > .ds-topbar-spacer { flex: none; }

/* A box centres on the title; only text sits on its baseline. The slot becomes a line the height
   of the title's line box and centres its contents in it. Ghost triggers keep the baseline. */
.ds-topbar-slot:has(> .ds-btn:not(.ds-btn-ghost)),
.ds-topbar-slot:has(> .ds-menu > .ds-menu-trigger--lg) {
    align-self: flex-start;
    align-items: center;
    height: calc(var(--ds-text-header) * var(--ds-leading-tight));
}

/* A button placed straight into the row has no such line, so it is nudged by half the difference. */
.ds-topbar > .ds-btn:not(.ds-btn-ghost) {
    align-self: flex-start;
    margin-top: calc((var(--ds-text-header) * var(--ds-leading-tight) - var(--ds-control-height)) / 2);
}

/* `visibility`, so the row keeps its geometry while a screen mounts. */
.ds-topbar-slot[data-pending] {
    visibility: hidden;
}

/* A button with no text has no first baseline, so a zero-width space gives it one.
   The negative margin cancels the flex gap that first child opens before the icon. */
.ds-topbar > .ds-btn,
.ds-topbar > .ds-submenu > .ds-btn { align-self: baseline; }

/* The phantom takes the button's type size: a menu trigger's smaller font would put its baseline 2px off. */
.ds-topbar > .ds-btn::before,
.ds-topbar > .ds-submenu > .ds-btn::before,
.ds-menu-trigger--lg::before {
    content: "\200b";
    overflow: hidden;
    font-family: var(--ds-font-body);
    font-size: var(--ds-text-button);
    line-height: var(--ds-leading-control);
}

/* The negative margin matches each control's own gap: 6px on a button, 3px on a menu trigger. */
.ds-topbar > .ds-btn::before,
.ds-topbar > .ds-submenu > .ds-btn::before {
    margin-right: calc(-1 * var(--ds-space-2));
}

/* ⚠ The "…" trigger is in neither list: its fixed square box centres the glyph itself. */
.ds-menu-trigger--lg::before {
    margin-right: calc(-1 * var(--ds-space-1));
}

.ds-title {
    min-width: 0;
    margin: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    font-family: var(--ds-font-display);
    font-size: var(--ds-text-header);
    font-weight: var(--ds-weight-semibold);
    line-height: var(--ds-leading-tight);
    color: var(--ds-color-text);
    white-space: nowrap;
}

.ds-topbar-spacer { flex: 1; }

.ds-panel {
    flex: 1;
    min-height: 0;
    margin: 0 var(--ds-layout-gutter) var(--ds-layout-gutter) 0;
    border-radius: var(--ds-radius-panel);
    background: var(--ds-color-surface-sunken);
    overflow: auto;
    scroll-padding-block: var(--ds-space-6);
}

.ds-panel-inner {
    padding: var(--ds-space-6);
}

/* The column is height-constrained, so the content scrolls and the panel does not. */
.ds-panel--bare > .ds-panel-inner {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
    padding: 0;
}

.ds-panel--bare > .ds-panel-inner > * {
    flex: 1;
    min-height: 0;
}

/* `auto`: the rail carries its own width, so dragging it wider moves the split. */
.ds-panel-split {
    display: grid;
    grid-template-columns: auto 1fr;
    height: 100%;
    min-height: 0;
}

.ds-rail {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-3);
    box-sizing: border-box;
    width: var(--ds-rail-width);

    /* `resize` needs a non-visible overflow, which the scroll below provides. */
    min-width: 180px;
    max-width: 560px;
    resize: horizontal;
    min-height: 0;
    padding: var(--ds-space-6) var(--ds-space-5);
    border-right: 1px solid var(--ds-color-border);
    overflow-y: auto;
    scroll-padding-block: var(--ds-space-6);
}

/* On the sunken panel the default active fill is the panel's colour. */
.ds-rail .ds-menucell-active,
.ds-rail .ds-menucell[aria-current="true"] {
    background: var(--ds-color-surface);
}

.ds-rail .ds-menucell:hover {
    background: var(--ds-bnw-950);
}

/* A rail sits on the sunken panel. `:where()` keeps this at zero specificity, so ds-btn-accent / -danger still win. */
.ds-rail :where(.ds-btn:not(.ds-btn-accent, .ds-btn-danger, .ds-btn-ghost, .ds-btn-white)) {
    background: var(--ds-bnw-850);
}

.ds-rail :where(.ds-btn:not(.ds-btn-accent, .ds-btn-danger, .ds-btn-ghost, .ds-btn-white)):hover {
    background: #dad9d8;
}

.ds-rail :where(.ds-btn:not(.ds-btn-accent, .ds-btn-danger, .ds-btn-ghost, .ds-btn-white)):active {
    background: #c8c7c6;
}

.ds-rail-foot {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ds-space-2);
    margin-top: auto;
    padding-top: var(--ds-space-3);
    border-top: 1px solid var(--ds-color-border);
}

.ds-rail-foot:empty {
    display: none;
}

.ds-rail-foot > .ds-btn {
    flex: 1 1 auto;
}

/* A flex column would otherwise squeeze the rows to fit the rail. */
.ds-rail-list > * {
    flex: none;
}

.ds-rail-list {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-1);
    min-height: 0;
    overflow-y: auto;
}

/* The scroll container: vertical padding keeps content off the panel's rounded edges at both ends. */

.ds-panel-body {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}

/* The side inset matches `.ds-panel-content`'s 24. The toolbar is the scroller's sibling and pays for no scrollbar. */
.ds-panel-toolbar {
    display: flex;
    flex: none;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--ds-space-4);
    padding: var(--ds-space-6) var(--ds-space-6) var(--ds-space-4);
}

/* The search takes a fifth and the rest split equally. `min-width: 0` lets an item go narrower than its content. */
.ds-panel-toolbar > * {
    flex: 1;
    min-width: 0;
}

.ds-panel-toolbar > .ds-search {
    flex: 0 0 20%;
}

/* The extra pseudo-class outranks the rule above. */
.ds-panel-toolbar > .ds-search:only-child {
    flex: 1;
}

/* Controls at the width they need. Put a `.ds-topbar-spacer` where the row should break. */
.ds-panel-toolbar--natural > *,
.ds-panel-toolbar--natural > .ds-search {
    flex: none;
}

.ds-panel-toolbar--natural > .ds-topbar-spacer {
    flex: 1;
}

/* An input would otherwise grow 1px taller than a button.
   As a descendant selector this outranks the size modifiers, so the sizes are named (compare `.ds-tagfilter .ds-btn`).
   */
.ds-panel-toolbar .ds-input,
.ds-panel-toolbar .ds-btn:not(.ds-btn-xs, .ds-btn-sm, .ds-btn-lg) {
    height: var(--ds-control-height);
    min-height: var(--ds-control-height);
}

/* The toolbar already provides the top inset. */
.ds-panel-toolbar + .ds-panel-content {
    padding-top: 0;
}

.ds-panel-content {
    display: flex;

    /* It is the scroller, so its height is the space available. */
    flex: 1;
    flex-direction: column;
    gap: var(--ds-space-5);
    min-height: 0;

    /* The thumb plus the 1px that holds it off the panel's edge. The right padding below is derived from it. */
    --ds-panel-scrollbar: 7px;

    /* ⚠ The right side pays for the scrollbar out of its own padding, because `scrollbar-gutter: stable`
       always reserves that column. */
    padding: var(--ds-space-6);
    padding-right: calc(var(--ds-space-6) - var(--ds-panel-scrollbar));
    overflow-y: auto;

    scroll-padding-block: var(--ds-space-6);

    /* ⚠ `scrollbar-width` and `scrollbar-color` are set only in the `@supports` block below:
       either one makes Chrome ignore `::-webkit-scrollbar*`, where this scroller's geometry comes from. */
    scrollbar-gutter: stable;
}

/* ⚠ Defining `::-webkit-scrollbar` opts out of macOS overlay scrollbars. It is the only way to inset a track,
   and `scrollbar-gutter: stable` has already reserved the column. */
.ds-panel-content::-webkit-scrollbar {
    width: var(--ds-panel-scrollbar);
}

/* The 1px that keeps the thumb off the panel's edge comes out of the column's width. */
.ds-panel-content::-webkit-scrollbar-track {
    margin-block: var(--ds-space-6);
    margin-right: 1px;
    background: transparent;
}

.ds-panel-content::-webkit-scrollbar-thumb {
    border-radius: 3px;
    background: var(--ds-color-border-strong);
}

/* ⚠ Mutually exclusive: in Chrome 121+ any non-`auto` `scrollbar-width` or `scrollbar-color` disables
   every `::-webkit-scrollbar*` rule. Firefox knows neither pseudo-element, which is what this query selects. */
@supports not selector(::-webkit-scrollbar) {
    .ds-panel-content {
        scrollbar-width: thin;
        scrollbar-color: var(--ds-color-border-strong) transparent;
    }
}

/* A wider gap between sections than between cards. The container sets it. */
.ds-panel-content:has(> .ds-section) {
    gap: var(--ds-space-7);
}

/* ── Submenu: the sub-navigation dropdown next to the title ──────────────── */

.ds-submenu {
    position: relative;
    min-width: 0;
}

.ds-submenu > .ds-btn { max-width: 100%; }

.ds-submenu > .ds-btn > span:first-child {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ds-submenu-panel {
    position: absolute;
    z-index: var(--ds-z-popup);
    top: calc(100% + var(--ds-space-1));
    left: 0;
    box-sizing: border-box;
    min-width: 240px;
    max-height: 60vh;
    padding: var(--ds-space-2);
    border-radius: var(--ds-radius-card);
    background: var(--ds-color-surface);
    box-shadow: var(--ds-shadow-dropdown), inset 0 0 0 1px var(--ds-bnw-900);
    overflow-y: auto;
}

.ds-submenu-panel[hidden] { display: none; }

/* For a trigger pinned to the bottom of its column. */
.ds-submenu-panel--up {
    top: auto;
    bottom: calc(100% + var(--ds-space-1));
}

/* ── Mobile drawer ───────────────────────────────────────────────────────── */

.ds-app-menu-btn { display: none; }

.ds-scrim {
    position: fixed;
    z-index: calc(var(--ds-z-popup) - 1);
    inset: 0;
    background: rgb(37 36 35 / 40%);
}

.ds-scrim[hidden] { display: none; }

/* ── Phone ───────────────────────────────────────────────────────────────
   The page renders edge to edge (viewport-fit=cover), so every edge touching the device frame pads itself
   with env(safe-area-inset-*); max() keeps normal spacing where no insets are reported. */

@media (width <= 900px) {
    .ds-app { grid-template-columns: 1fr; }

    .ds-app-menu-btn { display: inline-flex; }

    .ds-sidebar {
        position: fixed;
        z-index: var(--ds-z-popup);
        top: 0;
        bottom: 0;
        left: 0;
        width: min(var(--ds-sidebar-width), 84vw);
        padding-top: max(var(--ds-space-6), env(safe-area-inset-top));
        padding-bottom: max(var(--ds-space-4), env(safe-area-inset-bottom));
        padding-left: max(var(--ds-space-5), env(safe-area-inset-left));
        background: var(--ds-color-surface);
        box-shadow: var(--ds-shadow-card);
        transform: translateX(-100%);
        transition: transform var(--ds-transition-normal);
    }

    .ds-app[data-menu="open"] .ds-sidebar { transform: translateX(0); }

    .ds-topbar {
        gap: var(--ds-space-3);
        min-height: calc(60px + env(safe-area-inset-top));
        padding-top: env(safe-area-inset-top);
        padding-right: max(var(--ds-space-4), env(safe-area-inset-right));
        padding-left: max(var(--ds-space-4), env(safe-area-inset-left));
    }

    .ds-title { font-size: var(--ds-text-section); }

    .ds-panel {
        margin-right: max(var(--ds-space-2), env(safe-area-inset-right));
        margin-bottom: max(var(--ds-space-2), env(safe-area-inset-bottom));
        margin-left: max(var(--ds-space-2), env(safe-area-inset-left));
        border-radius: var(--ds-radius-card);
    }

    .ds-panel-inner { padding: var(--ds-space-4); }

    .ds-panel-split {
        grid-template-rows: minmax(0, 2fr) minmax(0, 3fr);
        grid-template-columns: minmax(0, 1fr);
    }

    .ds-panel-split > .ds-rail {
        width: auto;
        max-width: none;
        border-right: 0;
        border-bottom: 1px solid var(--ds-color-border);
        resize: none;
    }

    .ds-panel-toolbar > *,
    .ds-panel-toolbar > .ds-search {
        flex: 1 1 100%;
    }

    /* Touch targets. Sub-items stay one step smaller. */
    .ds-nav-item,
    .ds-dropdown-item {
        min-height: var(--ds-control-height-lg);
    }

    .ds-nav-subitem {
        min-height: var(--ds-control-height);
    }

    .ds-submenu-panel {
        width: max-content;
        min-width: 0;
        max-width: calc(100vw - 2 * var(--ds-space-4));
    }
}

@media (width >= 901px) {
    .ds-scrim { display: none; }
}
