/* =============================================================================
   ds.css : Under Vulcan portal design layer (dimensions + components)
   =============================================================================
   Ported from Base Station (weygand-team) src/styles/ds.css at commit 7c5adcfb
   (2026-10-15): same token names, same ds-* class names, same rules. The
   palette is NOT here: it lives in uv-theme.css, which binds the colour tokens
   this file reads. Nothing in this file may name a literal colour; every
   neutral is rgba(var(--ink), a) and every role is a token. `npm run lint:theme`
   enforces it.

   Rules the classes encode (full list: the 2026-10-16 portal design spec, b0):
     1. One frame (.ds-frame / -read / -full) and one header (.ds-head) per page.
     2. One primary button per view. Brand red is the BRAND, never the data: the
        logo, the 3px top rule, the title tick, the primary pill button (filled
        --brand-fill so its white label passes AA), the 2px active-nav underline,
        focus rings, selection edges, the "New" chip, the footer mark.
        Never a number, a status, a link colour, a table cell or a chart fill.
     3. Danger (--danger) colours one word or icon. Never a fill.
     4. One status signal per item (.ds-status), one box per item.
     5. Every wait is designed: .ds-skel, .ds-empty, .ds-banner. No browser dialogs.
     6. Type: tokens only, 12px minimum, tabular numbers, right-aligned money.

   Classes, not Astro components: the finance and Pathway pages build their DOM
   at runtime, which Astro's scoped styles cannot reach. Mobile is the base case;
   min-width queries (768, 1024) densify.

   New in this port (not in Base Station yet, offer back to weygand-team):
   .ds-chart, .ds-inline, .ds-chat, .ds-impersonate.
   ============================================================================= */

:root {
  /* ---- Spacing. 4px base. -------------------------------------------------- */
  --space-0: 0;
  --space-1: 0.25rem;   /*  4 */
  --space-2: 0.5rem;    /*  8 */
  --space-3: 0.75rem;   /* 12 */
  --space-4: 1rem;      /* 16 */
  --space-5: 1.5rem;    /* 24 */
  --space-6: 2rem;      /* 32 */
  --space-7: 3rem;      /* 48 */
  --space-8: 4rem;      /* 64 */

  /* ---- Type scale. Minimum 12px; at most 5 sizes on a page. ---------------- */
  --text-xs:   0.75rem;    /* 12 meta, column labels, sub-lines */
  --text-sm:   0.8125rem;  /* 13 secondary lines, field labels */
  --text-btn:  0.875rem;   /* 14 buttons */
  --text-base: 0.9375rem;  /* 15 body */
  --text-md:   1rem;       /* 16 emphasis, inputs (iOS zoom floor) */
  --text-lg:   1.125rem;   /* 18 card and dialog titles */
  --text-xl:   1.375rem;   /* 22 section titles */
  --text-stat: 1.75rem;    /* 28 stat numbers */
  --text-title: 1.375rem;  /* THE page title: 22 phone, 24 from 1024 */

  --leading-tight: 1.25;
  --leading-snug:  1.4;
  --leading-normal: 1.6;

  --weight-normal: 400;
  --weight-medium: 500;
  --weight-semi:   600;
  --weight-bold:   700;

  /* ---- Radii --------------------------------------------------------------- */
  --radius-xs: 4px;
  --radius-sm: 6px;
  --radius-md: 10px;   /* card */
  --radius-lg: 14px;   /* sheet */
  --radius-xl: 20px;
  --radius-pill: 999px;

  /* ---- Controls. --tap-min is the floor for anything a thumb has to hit and
          never shrinks; --control-h is what densifies for a mouse. ------------ */
  --tap-min: 44px;
  --control-h: 44px;
  --control-h-sm: 36px;
  --control-h-lg: 52px;
  --input-size: 1rem;   /* 16px: iOS Safari zooms a focused input under 16px */

  /* ---- Layers -------------------------------------------------------------- */
  --z-base: 1;
  --z-sticky: 50;
  --z-nav: 100;
  --z-dropdown: 200;
  --z-sheet: 300;
  --z-toast: 400;

  /* ---- Motion -------------------------------------------------------------- */
  --dur-fast: 120ms;
  --dur: 200ms;
  --dur-slow: 320ms;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);

  /* ---- Measure and frame --------------------------------------------------- */
  --measure: 68ch;
  --page-max: 1440px;
  --frame-wide: var(--page-max);   /* lists, tables, dashboards, records */
  --frame-read: 760px;             /* forms, settings, reading */
  --gutter: var(--space-4);        /* 16 phone, 24 from 768, 32 from 1024 */

  /* ---- Shell heights ------------------------------------------------------- */
  --bar-h: 56px;
  --subnav-h: 44px;
  --tabbar-h: 60px;
  --imp-h: 36px;

  /* The V-mark from public/logo-main.svg as a mask (colour comes from the element). */
  --mark-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='125 125 500 500'%3E%3Cpath fill-rule='evenodd' d='M602.28 125H147.73c-12.5 0-22.73 10.23-22.73 22.73v454.55c0 12.5 10.23 22.72 22.73 22.72h454.55c12.5 0 22.72-10.22 22.72-22.72V147.73C625 135.23 614.78 125 602.28 125zM500 540.9c0 3.76-3.07 6.83-6.82 6.83H256.83c-3.75 0-6.83-3.07-6.83-6.83v-54.52c0-3.76 3.08-6.83 6.83-6.83h236.35c3.75 0 6.82 3.07 6.82 6.83V540.9zM524.5 211.38l-90.52 220.47c-1.69 4.11-5.69 6.79-10.13 6.79h-95.42c-4.41 0-8.38-2.64-10.09-6.7l-92.81-220.31c-1.82-4.33 1.3-9.15 5.94-9.16l82.66-.08c2.69 0 5.11 1.65 6.08 4.16l50.59 126.51c1.52 3.79 6.89 3.79 8.4 0l50.55-126.53c.98-2.5 3.39-4.14 6.07-4.15l82.7-.1C523.14 202.27 526.28 207.05 524.5 211.38z'/%3E%3C/svg%3E");
}
@media (min-width: 768px) { :root { --gutter: var(--space-5); } }
@media (min-width: 1024px) { :root { --gutter: var(--space-6); --text-title: 1.5rem; } }

/* Desktop densifies. --tap-min does not move. */
@media (pointer: fine) {
  :root {
    --control-h: 36px;
    --control-h-sm: 30px;
    --control-h-lg: 44px;
  }
}

/* =============================================================================
   BASE
   ============================================================================= */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html {
  font-family: var(--font-body);
  font-size: 100%;
  background: var(--bg);
  color: var(--text);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
body {
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--bg);
  color: var(--text);
  font-size: var(--text-base);
  line-height: var(--leading-snug);
  -webkit-font-smoothing: antialiased;
}
main { display: block; }
img, svg { max-width: 100%; }
img { height: auto; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: var(--link); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 2px; }
h1, h2, h3, h4 { font-family: inherit; font-weight: var(--weight-semi); line-height: var(--leading-tight); }
table { border-collapse: collapse; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.hidden { display: none !important; }
[hidden] { display: none !important; }
.ds-skip {
  position: absolute; left: var(--space-3); top: -100px; z-index: var(--z-toast);
  padding: var(--space-2) var(--space-3); background: var(--bg-elevated); color: var(--text);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
}
.ds-skip:focus { top: var(--space-2); }

/* =============================================================================
   LAYOUT PRIMITIVES
   ============================================================================= */

/* Section header inside a page: title on the left, count/action on the right. */
.ds-section {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin: var(--space-6) 0 var(--space-3);
}
.ds-section:first-child { margin-top: 0; }
.ds-section-title {
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-subtle);
  margin: 0;
}
/* 2026-09-27: wraps (the row wraps first, then the text) instead of pushing a
   phone page sideways when the meta is long. */
.ds-section-meta { font-size: var(--text-xs); color: var(--text-subtle); min-width: 0; }

/* Vertical rhythm without margin collapsing surprises. */
.ds-stack > * + * { margin-top: var(--space-3); }
.ds-stack-sm > * + * { margin-top: var(--space-2); }
.ds-stack-lg > * + * { margin-top: var(--space-5); }

/* Horizontal group that wraps instead of overflowing. */
.ds-cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.ds-cluster-between { justify-content: space-between; }

/* Auto-fit grid. One column on a phone, as many as fit after that — no
   breakpoint bookkeeping per page. */
.ds-grid {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: 1fr;
}
@media (min-width: 640px) {
  .ds-grid { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
  .ds-grid-wide { grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); }
  .ds-grid-narrow { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
}

/* =============================================================================
   BUTTONS
   One element, four intents, three sizes. Everything is at least --tap-min
   tall on touch even when it renders small.
   ============================================================================= */

.ds-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-h);
  padding: 0 var(--space-4);
  border-radius: var(--radius-btn);
  border: 1px solid var(--rule);
  background: rgba(var(--ink), 0.04);
  color: var(--text);
  font-family: inherit;
  font-size: var(--text-btn);
  font-weight: var(--weight-semi);
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
  /* Stops the grey flash on tap in iOS Safari. */
  -webkit-tap-highlight-color: transparent;
}
.ds-btn:hover { background: rgba(var(--ink), 0.08); border-color: var(--border-strong); }
.ds-btn:active { transform: translateY(1px); }
.ds-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ds-btn[disabled], .ds-btn[aria-disabled="true"] {
  opacity: 0.45; cursor: not-allowed; pointer-events: none;
}

/* Primary. One per view, not one per card — a screen where every row has a
   solid brand red button has no primary action at all. */
.ds-btn-primary {
  background: var(--accent-fill);
  border-color: var(--accent-fill);
  color: var(--on-accent);
}
.ds-btn-primary:hover { background: var(--brand-hover); border-color: var(--brand-hover); }
.ds-btn-primary:active { background: var(--brand-press); border-color: var(--brand-press); }

.ds-btn-ghost { background: transparent; border-color: transparent; color: var(--text-muted); }
.ds-btn-ghost:hover { background: rgba(var(--ink), 0.06); color: var(--text); }

.ds-btn-danger { border-color: var(--danger); color: var(--danger); background: transparent; }
.ds-btn-danger:hover { background: var(--danger-tint); border-color: var(--danger); color: var(--danger); }

.ds-btn-sm { min-height: var(--control-h-sm); padding: 0 var(--space-3); font-size: var(--text-sm); }
.ds-btn-lg { min-height: var(--control-h-lg); padding: 0 var(--space-5); font-size: var(--text-md); }
.ds-btn-block { display: flex; width: 100%; }

/* Icon-only. The visual box may be small; the hit box never is. */
.ds-btn-icon {
  padding: 0;
  width: var(--control-h);
  min-width: var(--control-h);
  border-radius: var(--radius-btn);
}
.ds-btn-icon::after {
  content: "";
  position: absolute;
  inset: 50% auto auto 50%;
  width: var(--tap-min); height: var(--tap-min);
  transform: translate(-50%, -50%);
}
.ds-btn-icon { position: relative; }

/* =============================================================================
   CARDS
   ============================================================================= */

.ds-card {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  position: relative;
}
.ds-card-link {
  display: block;
  text-decoration: none;
  color: inherit;
  transition: border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.ds-card-link:hover { border-color: var(--border-strong); }
@media (hover: hover) and (pointer: fine) {
  .ds-card-link:hover { transform: translateY(-1px); }
}

.ds-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.ds-card-title {
  font-size: var(--text-lg);
  font-weight: var(--weight-semi);
  line-height: var(--leading-tight);
  color: var(--text);
  margin: 0;
}
.ds-card-sub { font-size: var(--text-sm); color: var(--text-muted); margin: var(--space-1) 0 0; }
.ds-card-foot {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--hairline);
}
/* Key/value rows inside a card — the single most-reinvented pattern here. */
.ds-kv { display: flex; justify-content: space-between; gap: var(--space-3); font-size: var(--text-sm); padding: var(--space-1) 0; }
.ds-kv dt, .ds-kv-k { color: var(--text-subtle); }
.ds-kv dd, .ds-kv-v { color: var(--text); margin: 0; text-align: right; }

/* =============================================================================
   STATUS PILLS AND BADGES
   ============================================================================= */

.ds-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: var(--weight-semi);
  letter-spacing: 0.02em;
  line-height: 1.5;
  white-space: nowrap;
  background: rgba(var(--ink), 0.07);
  color: var(--text-muted);
  border: 1px solid transparent;
}
.ds-pill-ok     { background: var(--positive-tint); color: var(--positive); }
.ds-pill-warn   { background: var(--warning-tint); color: var(--warning); }
.ds-pill-danger { background: var(--danger-tint); color: var(--danger); }
.ds-pill-info   { background: var(--info-tint); color: var(--info); }
.ds-pill-accent { background: rgba(var(--ink), 0.06); color: var(--text); border-color: var(--accent); }

/* A count on a tab or nav item. */
.ds-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px; height: 18px;
  padding: 0 5px;
  border-radius: var(--radius-pill);
  background: var(--text);   /* an ink count, not red: red stays out of the data */
  color: var(--bg);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  line-height: 1;
}
.ds-badge-quiet { background: rgba(var(--ink), 0.12); color: var(--text-muted); }

/* Monospaced identifiers — job numbers, parcel ids. Reads as data, not prose. */
.ds-mono {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.02em;
  color: var(--text-muted);
}

/* =============================================================================
   HORIZONTAL RAIL
   For the stage rail, filter chips, anything that overflows sideways on a
   phone. Two things every hand-rolled version in this repo forgets: an edge
   fade so the user knows there IS more, and scroll-snap so a flick lands on
   an item instead of mid-label.
   ============================================================================= */

.ds-rail {
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  padding-bottom: var(--space-2);
  /* Fade both edges. mask-image is the only way to do this that survives an
     arbitrary background, including the tenant palettes. */
  -webkit-mask-image: linear-gradient(to right, transparent 0, rgba(var(--ink), 1) 16px, rgba(var(--ink), 1) calc(100% - 16px), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, rgba(var(--ink), 1) 16px, rgba(var(--ink), 1) calc(100% - 16px), transparent 100%);
  scroll-padding-inline: var(--space-4);
}
.ds-rail > * { scroll-snap-align: start; flex: 0 0 auto; }
.ds-rail::-webkit-scrollbar { height: 4px; }
.ds-rail::-webkit-scrollbar-thumb { background: rgba(var(--ink), 0.18); border-radius: var(--radius-pill); }
/* Nothing overflows on a wide screen, so drop the mask — it dims real content. */
@media (min-width: 900px) {
  .ds-rail { -webkit-mask-image: none; mask-image: none; flex-wrap: wrap; overflow-x: visible; }
}

/* A rail item that behaves like a filter chip / stage step. */
.ds-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: 0 var(--space-3);
  border-radius: var(--radius-pill);
  border: 1px solid var(--rule);
  background: transparent;
  color: var(--text-muted);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  cursor: pointer;
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.ds-chip:hover { background: rgba(var(--ink), 0.06); color: var(--text); }
.ds-chip[aria-pressed="true"], .ds-chip.is-active {
  background: rgba(var(--ink), 0.10);
  border-color: var(--border-strong);
  color: var(--text);
}
.ds-chip-count { font-variant-numeric: tabular-nums; color: var(--text-subtle); }
/* A filter row that sits above a dense list. --tap-min is right for a stage
   rail a crew thumbs through, but six 44px pills above a list of 44px rows
   made the filters as heavy as the work. --control-h-sm still stays 36px on
   touch (see the pointer:fine block), so it remains thumb-sized there. */
.ds-chip-sm { min-height: var(--control-h-sm); padding: 0 var(--space-3); gap: var(--space-1); font-size: var(--text-xs); }
.ds-chip[aria-pressed="true"] .ds-chip-count { color: var(--text); }

/* A rail option that exists but holds nothing.
   Added for the PM stage rail: fifteen of the pipeline's stages are real and
   several are empty on any given day, and rendering all sixteen chips at equal
   weight is what made that rail unreadable. An empty stage must stay visible —
   "a stage the work passes through" is information — but it must not compete
   with the stages that have work in them. Still selectable, still 44px of
   thumb. */
/* Quiet = subtle text and a faint edge. Never whole-control opacity: that drops the label under 4.5:1. */
.ds-chip-quiet { color: var(--text-subtle); border-color: var(--border); }
.ds-chip-quiet:hover, .ds-chip-quiet[aria-pressed="true"] { color: var(--text); border-color: var(--border-strong); }

/* A chip at text scale rather than control scale. The active-filter row sits
   directly under a search box and repeats for each filter in force, so a 44px
   chip there costs more vertical rhythm than the filters are worth. The hit box
   is restored to --tap-min by .ds-tap, which is why this may shrink. */
.ds-chip-sm {
  min-height: auto;
  padding: var(--space-1) var(--space-2);
  font-size: var(--text-xs);
  border-radius: var(--radius-sm);
}

/* =============================================================================
   TABS
   ============================================================================= */

.ds-tabs {
  display: flex;
  gap: var(--space-4);
  border-bottom: 1px solid var(--rule);
  overflow-x: auto;
  scrollbar-width: none;
}
.ds-tabs::-webkit-scrollbar { display: none; }
.ds-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: 0 var(--space-1);
  border: 0;
  border-bottom: 2px solid transparent;
  background: none;
  color: var(--text-subtle);
  font-family: inherit;
  font-size: var(--text-base);
  font-weight: var(--weight-semi);
  cursor: pointer;
  white-space: nowrap;
}
.ds-tab:hover { color: var(--text); }
.ds-tab[aria-selected="true"], .ds-tab.is-active {
  color: var(--text);
  border-bottom-color: var(--accent);
}

/* Segmented control (Cards | List). */
.ds-seg {
  display: inline-flex;
  padding: 2px;
  border-radius: var(--radius-md);
  background: rgba(var(--ink), 0.06);
  border: 1px solid var(--rule);
}
.ds-seg-item {
  min-height: var(--control-h-sm);
  padding: 0 var(--space-3);
  border: 0;
  border-radius: calc(var(--radius-md) - 2px);
  background: transparent;
  color: var(--text-subtle);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-semi);
  cursor: pointer;
}
.ds-seg-item[aria-pressed="true"], .ds-seg-item.is-active {
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--elev-1);
}

/* =============================================================================
   FORM FIELDS
   ============================================================================= */

.ds-field { display: block; margin-bottom: var(--space-3); }
.ds-label {
  display: block;
  font-size: var(--text-sm);
  font-weight: var(--weight-semi);
  color: var(--text-muted);
  margin-bottom: var(--space-1);
}
.ds-input, .ds-select, .ds-textarea {
  display: block;
  width: 100%;
  min-height: var(--control-h);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--rule);
  border-radius: var(--radius-md);
  background: rgba(var(--ink), 0.04);
  color: var(--text);
  font-family: inherit;
  /* 16px floor — see --input-size. Never override this smaller. */
  font-size: var(--input-size);
  line-height: 1.4;
}
.ds-textarea { min-height: calc(var(--control-h) * 2.5); resize: vertical; }
.ds-input::placeholder, .ds-textarea::placeholder { color: var(--text-subtle); }
.ds-input:focus, .ds-select:focus, .ds-textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
  border-color: var(--accent);
}
.ds-input[aria-invalid="true"] { border-color: var(--danger); }
.ds-hint { font-size: var(--text-xs); color: var(--text-subtle); margin-top: var(--space-1); }
.ds-error { font-size: var(--text-xs); color: var(--danger); margin-top: var(--space-1); }

/* Checkbox/radio with a label that is itself the target. */
.ds-check {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  cursor: pointer;
  font-size: var(--text-base);
  color: var(--text);
}
.ds-check input { width: 20px; height: 20px; accent-color: var(--accent); flex: 0 0 auto; }

/* Search field with the icon inside. */
.ds-search { position: relative; }
.ds-search .ds-input { padding-left: calc(var(--space-4) + 20px); }
.ds-search-icon {
  position: absolute;
  left: var(--space-3);
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-subtle);
  pointer-events: none;
  line-height: 0;
}

/* Filter bar: a scrollable rail of controls on a phone, a wrapping row above. */
.ds-filters { display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center; }
.ds-filters > .ds-select { width: auto; min-width: 0; flex: 1 1 140px; }

/* =============================================================================
   TABLE → CARDS
   A data table that becomes a stack of labelled cards under 768px instead of
   overflowing off the side of a phone. Every <td> needs data-label="Column".
   Where a table genuinely must stay a table on a phone (a wide numeric grid),
   wrap it in .ds-table-scroll instead.
   ============================================================================= */

.ds-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.ds-table th {
  text-align: left;
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-subtle);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--rule);
  white-space: nowrap;
}
.ds-table td {
  padding: var(--space-3);
  border-bottom: 1px solid var(--hairline);
  color: var(--text);
  vertical-align: top;
}
.ds-table tbody tr:hover { background: rgba(var(--ink), 0.03); }
.ds-table .ds-num { text-align: right; font-variant-numeric: tabular-nums; }

.ds-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.ds-table-scroll .ds-table { min-width: 640px; }

@media (max-width: 767px) {
  .ds-table.ds-table-cards,
  .ds-table.ds-table-cards thead,
  .ds-table.ds-table-cards tbody,
  .ds-table.ds-table-cards tr,
  .ds-table.ds-table-cards td { display: block; width: 100%; }
  .ds-table.ds-table-cards thead { position: absolute; left: -9999px; }
  .ds-table.ds-table-cards tr {
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius-lg);
    padding: var(--space-3);
    margin-bottom: var(--space-3);
  }
  .ds-table.ds-table-cards td {
    display: flex;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-1) 0;
    border: 0;
  }
  .ds-table.ds-table-cards td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    font-size: var(--text-xs);
    font-weight: var(--weight-semi);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-subtle);
  }
  /* A cell marked data-primary is the card's title: full width, no label. */
  .ds-table.ds-table-cards td[data-primary] {
    display: block;
    font-size: var(--text-lg);
    font-weight: var(--weight-semi);
    padding-bottom: var(--space-2);
  }
  .ds-table.ds-table-cards td[data-primary]::before { content: none; }
  .ds-table.ds-table-cards .ds-num { text-align: right; }
}


/* =============================================================================
   EMPTY AND LOADING STATES
   An empty state with no next action is a dead end, so .ds-empty has a slot
   for one. A "Loading…" string is a blank screen on rural LTE, so there are
   skeletons.
   ============================================================================= */

.ds-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-2);
  padding: var(--space-7) var(--space-4);
  border: 1px dashed var(--rule);
  border-radius: var(--radius-lg);
  color: var(--text-muted);
}
/* The brand mark in ink at 12%: every empty state carries it, decorative only. */
.ds-empty::before {
  content: "";
  flex: none;
  width: 40px;
  height: 40px;
  margin-bottom: var(--space-1);
  background: rgba(var(--ink), 0.12);
  -webkit-mask: var(--mark-mask) center / contain no-repeat;
  mask: var(--mark-mask) center / contain no-repeat;
}
.ds-empty-title { font-size: var(--text-lg); font-weight: var(--weight-semi); color: var(--text); margin: 0; }
.ds-empty-body { font-size: var(--text-sm); margin: 0; max-width: 42ch; }
.ds-empty .ds-btn { margin-top: var(--space-2); }

.ds-skeleton { display: block; }
.ds-skel {
  display: block;
  border-radius: var(--radius-sm);
  background: rgba(var(--ink), 0.07);
  position: relative;
  overflow: hidden;
}
.ds-skel::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(var(--ink), 0.10), transparent);
  animation: ds-shimmer 1.4s ease-in-out infinite;
}
@keyframes ds-shimmer { to { transform: translateX(100%); } }
.ds-skel-line { height: 0.75rem; margin-bottom: var(--space-2); }
.ds-skel-line-sm { width: 40%; }
.ds-skel-line-md { width: 70%; }
.ds-skel-card { height: 96px; border-radius: var(--radius-lg); margin-bottom: var(--space-3); }
/* A skeleton shaped like a .ds-row, for the queues that render rows on a
   desktop and cards on a phone. A skeleton that does not match the layout it
   replaces makes the page jump when the data lands, which is the reason the
   placeholder exists at all — so it grows to card height under 768px, exactly
   as .ds-row does. */
.ds-skel-row { height: 52px; border-radius: var(--radius-sm); margin-bottom: 1px; }
@media (max-width: 767px) {
  .ds-skel-row { height: 88px; border-radius: var(--radius-lg); margin-bottom: var(--space-2); }
}
/* A skeleton chip, for a rail that is the page's primary navigation. */
.ds-skel-chip { height: var(--tap-min); width: 7rem; border-radius: var(--radius-pill); flex: 0 0 auto; }

/* Screen-reader-only announcement region for async results. */
.ds-sr {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* =============================================================================
   SHEET / DIALOG
   Bottom sheet on a phone (thumb-reachable), centred dialog on a desktop.
   Same markup, same class.
   ============================================================================= */

/* [hidden] must win over display:flex — without this a scrim rendered with
   the hidden attribute (the documented way to close it) still covered the
   page. */
.ds-scrim[hidden] { display: none; }
/* The same trap, everywhere: many ds-* components set display:flex/grid, which
   beats the UA's [hidden]{display:none}. .ds-banner shipped without its
   companion rule and rendered hidden errors as empty red boxes; four pages
   patched it locally. One rule for every design-system class (2026-09-28). */
[class*="ds-"][hidden] { display: none !important; }
.ds-scrim {
  position: fixed; inset: 0;
  background: var(--scrim);
  z-index: var(--z-sheet);
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
@media (min-width: 768px) { .ds-scrim { align-items: center; } }

.ds-sheet {
  width: 100%;
  max-height: 88vh;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  padding: var(--space-4);
  padding-bottom: max(var(--space-4), env(safe-area-inset-bottom));
  box-shadow: var(--elev-3);
  animation: ds-sheet-up var(--dur) var(--ease-out);
}
@media (min-width: 768px) {
  .ds-sheet {
    max-width: 560px;
    border-radius: var(--radius-lg);
    padding: var(--space-5);
    animation: ds-sheet-in var(--dur) var(--ease-out);
  }
}
@keyframes ds-sheet-up { from { transform: translateY(100%); } }
@keyframes ds-sheet-in { from { transform: scale(0.97); opacity: 0; } }

.ds-sheet-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); margin-bottom: var(--space-3);
}
.ds-sheet-title { font-size: var(--text-xl); font-weight: var(--weight-semi); margin: 0; color: var(--text); }
/* The grab handle tells a phone user this is draggable/dismissible. */
.ds-sheet-grip {
  width: 36px; height: 4px; border-radius: var(--radius-pill);
  background: rgba(var(--ink), 0.20);
  margin: 0 auto var(--space-3);
}
@media (min-width: 768px) { .ds-sheet-grip { display: none; } }

/* Action row: stacked and full-width on a phone, inline right on desktop. */
.ds-actions { display: flex; flex-direction: column-reverse; gap: var(--space-2); margin-top: var(--space-4); }
.ds-actions > .ds-btn { width: 100%; }
@media (min-width: 768px) {
  .ds-actions { flex-direction: row; justify-content: flex-end; }
  .ds-actions > .ds-btn { width: auto; }
}

/* =============================================================================
   TOAST
   ============================================================================= */

.ds-toasts {
  position: fixed;
  left: 50%; transform: translateX(-50%);
  bottom: max(var(--space-4), env(safe-area-inset-bottom));
  z-index: var(--z-toast);
  display: flex; flex-direction: column; gap: var(--space-2);
  width: min(92vw, 420px);
  pointer-events: none;
}
.ds-toast {
  pointer-events: auto;
  display: flex; align-items: flex-start; gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--rule);
  background: var(--surface-float);
  backdrop-filter: blur(8px);
  color: var(--text);
  font-size: var(--text-sm);
  box-shadow: var(--elev-3);
  animation: ds-toast-in var(--dur) var(--ease-out);
}
@keyframes ds-toast-in { from { transform: translateY(8px); opacity: 0; } }
.ds-toast-ok { border-color: var(--positive); }
.ds-toast-error { border-color: var(--danger); }

/* =============================================================================
   STATS
   ============================================================================= */

.ds-stat { display: flex; flex-direction: column; gap: 2px; }
.ds-stat-value {
  font-size: var(--text-stat);
  font-weight: var(--weight-semi);
  line-height: 1;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.ds-stat-label {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-subtle);
}
.ds-stat-sub { font-size: var(--text-xs); color: var(--text-muted); }

/* =============================================================================
   UTILITIES
   ============================================================================= */

.ds-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.ds-clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.ds-measure { max-width: var(--measure); }
.ds-num { font-variant-numeric: tabular-nums; }
.ds-muted { color: var(--text-muted); }
.ds-subtle { color: var(--text-subtle); }

/* Enlarge a small control's hit area without changing its look. */
.ds-tap { position: relative; }
.ds-tap::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: max(100%, var(--tap-min));
  height: max(100%, var(--tap-min));
  transform: translate(-50%, -50%);
}

@media (max-width: 767px) { .ds-desktop-only { display: none !important; } }
@media (min-width: 768px) { .ds-mobile-only { display: none !important; } }

/* An affordance that only exists on :hover is invisible on a touch screen.
   Wrap those in .ds-hover-reveal — it stays visible wherever hover is not a
   real input. */
.ds-hover-reveal { opacity: 1; }
@media (hover: hover) and (pointer: fine) {
  .ds-hover-reveal { opacity: 0; transition: opacity var(--dur-fast) var(--ease-out); }
  *:hover > .ds-hover-reveal, .ds-hover-reveal:focus-within { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .ds-sheet, .ds-toast { animation: none; }
  .ds-skel::after { animation: none; }
  .ds-btn, .ds-card-link, .ds-chip { transition: none; }
}

/* =============================================================================
   ROWS — dense lists that are cards on a phone
   =============================================================================
   The second half of the same answer. A queue of 27 jobs should be 27 scannable
   rows on a desktop, not 27 cards with their own borders and 16px of padding
   each. .ds-rows renders rows above 768px and the familiar card stack below it,
   from one set of markup.

   Restraint rules encoded here, because the current screens break all three:
     1. ONE status signal per item. A left colour rail OR a pill — never both,
        or the eye has nothing to rank.
     2. ONE solid/primary button per VIEW. A list where every row has a filled
        brand red button has no primary action at all; row actions are ghost or
        text, and the solid button lives in the detail pane.
     3. ONE box per item. Section rule + card border + inner table border is
        three nested frames for one fact. Rows have a hairline and nothing else.
   ============================================================================= */

.ds-rows { display: flex; flex-direction: column; }

.ds-row {
  display: grid;
  grid-template-columns: 3px minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-3);
  border-bottom: 1px solid var(--hairline);
  color: inherit;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--dur-fast) var(--ease-out);
}
.ds-row:hover { background: rgba(var(--ink), 0.04); }
/* Selection is not status: a quiet fill plus a 2px accent edge, so the row
   whose detail is on the right is findable at a glance in a long list. It
   is an inset shadow, not the rail, so it never competes with a status. */
.ds-row[aria-current="true"], .ds-row.is-selected {
  background: rgba(var(--ink), 0.08);
  box-shadow: inset 2px 0 0 var(--accent);
}
/* The rail is optional. Most queues have no status worth a colour on every
   row (restraint rule 1), and a transparent 3px column still indented every
   title by 15px. Without a .ds-row-rail child the row is main + end. */
.ds-row:not(:has(> .ds-row-rail)) { grid-template-columns: minmax(0, 1fr) auto; }
.ds-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* The 3px rail is the item's single status signal. Default: no signal at all —
   most rows are just fine, and colouring them all is the same as colouring
   none. */
.ds-row-rail { align-self: stretch; border-radius: var(--radius-pill); background: transparent; }
.ds-row-rail-ok     { background: var(--positive); }
.ds-row-rail-warn   { background: var(--warning); }
.ds-row-rail-danger { background: var(--danger); }
.ds-row-rail-info   { background: var(--info); }
.ds-row-rail-accent { background: var(--accent); }

.ds-row-main { min-width: 0; }
.ds-row-title {
  font-size: var(--text-base);
  font-weight: var(--weight-semi);
  color: var(--text);
  line-height: var(--leading-tight);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ds-row-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  margin-top: 2px;
  font-size: var(--text-xs);
  color: var(--text-subtle);
  min-width: 0;
}
/* Separator between meta facts, so the markup needs no literal bullets. */
/* Boxed facts (tags, badges, pills, chips) carry their own edge; the gap
   separates them. A ::before bullet would render INSIDE the box. */
.ds-row-meta > * + *:not([class*="tag"]):not([class*="badge"]):not([class*="pill"]):not([class*="chip"])::before {
  content: "·"; margin-right: var(--space-2); color: var(--text-subtle);
}

.ds-row-end {
  display: flex; align-items: center; gap: var(--space-2);
  text-align: right;
  font-size: var(--text-sm);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Below 768px the same rows become the card stack that works on a phone. */
@media (max-width: 767px) {
  .ds-rows { gap: var(--space-2); }
  .ds-row {
    grid-template-columns: 3px minmax(0, 1fr);
    grid-template-areas: "rail main" "rail end";
    align-items: start;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius-lg);
    padding: var(--space-3);
    row-gap: var(--space-1);
  }
  .ds-row-rail { grid-area: rail; }
  .ds-row-main { grid-area: main; }
  .ds-row-end {
    grid-area: end;
    justify-content: flex-start;
    text-align: left;
    /* The grid's own gap already separates it; a margin on top of that left
       a lone date floating ~20px under its title. */
    margin-top: 0;
  }
  .ds-row-title { white-space: normal; }
  .ds-row:not(:has(> .ds-row-rail)) {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "main" "end";
  }
}

/* =============================================================================
   FOUNDATION (2026-09-27) — the components DESIGN-SYSTEM.md §4 adds
   =============================================================================
   Source: /home/may/audit-2026-09-27/DESIGN-SYSTEM.md (the spec) and its
   mockups. Reference: docs/design-system.md; live: /team/style-guide/.

   The rules every class below encodes, so a page cannot break them by using
   the classes as intended:
     1. ONE frame and ONE header per page: .ds-frame(-read|-full) + .ds-head.
        The title is --text-title (22 phone / 24 desktop) and nothing else.
     2. GOLD IS FOR INTERACTION ONLY: the one primary button, the active tab /
        nav underline, the current step, selection edges, focus rings, links.
        Never a status, a heading, a rule or a KPI fill.
     3. ONE status signal per item: .ds-status (dot + word) OR .ds-age (red /
        amber text) OR a .ds-row-rail — pick one.
     4. RED IS FOR THE CRITICAL FEW: overdue past threshold, failed, blocking.
        If more than ~10% of a list is red, the threshold is wrong.
     5. ONE box per item: hairlines and section rules, not cards in cards.
     6. Every wait is designed: .ds-skel while loading, .ds-empty with a next
        step when empty, .ds-banner in plain words with Retry on error, and a
        dialog.ds-dialog instead of alert()/confirm().

   Mobile is the unqualified case; min-width queries densify. Breakpoints are
   768 and 1024 only (1280 for the widest grids). Where a rule is phone-only
   because the desktop layout is the natural one (a table that folds), it uses
   max-width: 767px, matching .ds-table-cards and .ds-rows above.
   ============================================================================= */

/* ---- FRAME ------------------------------------------------------------------
   Every page picks exactly one. The Shell's top bar uses .ds-frame too, so the
   logo, the page title and the first column share one left edge.
     .ds-frame                 1440 content + gutters: list, dashboard, record
     .ds-frame.ds-frame-read   760 content: form, settings, reading
     .ds-frame.ds-frame-full   100%: workspace, map, canvas
   The frame owns the horizontal gutters only; .ds-head owns the top space and
   .ds-frame-pad adds the bottom breathing room a scrolling page needs. */
.ds-frame {
  width: 100%;
  max-width: calc(var(--frame-wide) + 2 * var(--gutter));
  margin-inline: auto;
  padding-left: max(var(--gutter), env(safe-area-inset-left));
  padding-right: max(var(--gutter), env(safe-area-inset-right));
}
.ds-frame-read { max-width: calc(var(--frame-read) + 2 * var(--gutter)); }
.ds-frame-full { max-width: none; }
.ds-frame-pad { padding-bottom: var(--space-8); }

/* ---- PAGE HEADER -----------------------------------------------------------
   [← Back]                                          .ds-head-back (records only)
   EYEBROW · ID                                      .ds-head-eyebrow (optional)
   Title                    [⋯] [Secondary] [Primary] .ds-head-title + -actions
   meta · meta · meta                                .ds-head-meta
   [.ds-steps | .ds-tabs]                            optional, under the row
   At most one primary and two secondaries; the rest go in a .ds-menu behind
   the ⋯ .ds-iconbtn. On a phone, mark secondaries .ds-desktop-only and repeat
   the primary in a .ds-actionbar on record pages. */
.ds-head { padding: var(--space-5) 0 var(--space-4); }
.ds-head-back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: var(--tap-min);
  margin: calc(var(--space-3) * -1) 0 var(--space-1);
  font-size: var(--text-sm);
  color: var(--text-muted);
  text-decoration: none;
}
.ds-head-back:hover { color: var(--text); }
.ds-head-back svg { width: 14px; height: 14px; flex: none; }
.ds-head-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}
.ds-head-id { flex: 1 1 0; min-width: 0; }
.ds-head-eyebrow {
  margin: 0 0 2px;
  font-size: var(--text-xs);
  font-weight: var(--weight-semi);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-subtle);
}
.ds-head-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-title);
  font-weight: var(--weight-semi);
  line-height: var(--leading-tight);
  color: var(--text);
  overflow-wrap: anywhere;
}
.ds-head-title::after {
  content: "";
  display: block;
  width: 40px;
  height: 3px;
  margin-top: var(--space-2);
  background: var(--brand);
}
.ds-head-meta {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.ds-head-actions { display: flex; align-items: center; gap: var(--space-2); flex: none; }
.ds-head > .ds-tabs, .ds-head > .ds-steps, .ds-head > .ds-seg { margin-top: var(--space-4); }
@media (min-width: 768px) {
  .ds-head-row { flex-wrap: wrap; align-items: center; gap: var(--space-3) var(--space-4); }
  .ds-head-id { flex: 1 1 320px; }
}
/* Compact header for a panel (map / workspace index): 18px, no top padding. */
.ds-head-compact { padding: var(--space-3) 0; }
.ds-head-compact .ds-head-title { font-size: var(--text-lg); }

/* ---- SECTION NAV (shell slot, directly under the top bar) -------------------
   Replaces ScorecardsNav, FinanceNav, the PM tabs, map view tabs and roadmap
   tabs. Links, not buttons: each tab is a page. Never above the page title.
   Mark the current one aria-current="page". */
.ds-subnav {
  background: var(--bg);
  border-bottom: 1px solid var(--hairline-strong);
  overflow-x: auto;
  scrollbar-width: none;
}
.ds-subnav::-webkit-scrollbar { display: none; }
.ds-subnav-inner { display: flex; gap: var(--space-1); height: var(--subnav-h); }
.ds-subnav a {
  display: flex;
  align-items: center;
  padding: 0 var(--space-3);
  white-space: nowrap;
  color: var(--text-muted);
  font-size: var(--text-sm);
  font-weight: var(--weight-semi);
  text-decoration: none;
  box-shadow: inset 0 -2px 0 transparent;
}
.ds-subnav a:first-child { margin-left: calc(var(--space-3) * -1); }
.ds-subnav a:hover { color: var(--text); }
.ds-subnav a[aria-current="page"], .ds-subnav a.is-active {
  color: var(--text);
  box-shadow: inset 0 -2px 0 var(--accent);
}

/* ---- ICON BUTTON ------------------------------------------------------------
   36px visual (30 for -sm), 44px hit box always. For nav chrome, prev/next,
   the ⋯ row menu, map controls. Give it an aria-label. .ds-dot is an unread
   marker (accent: it is a thing to act on). */
.ds-iconbtn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.ds-iconbtn::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: var(--tap-min);
  height: var(--tap-min);
  transform: translate(-50%, -50%);
}
.ds-iconbtn:hover, .ds-iconbtn[aria-expanded="true"] { background: rgba(var(--ink), 0.06); color: var(--text); }
.ds-iconbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ds-iconbtn[disabled] { opacity: 0.45; cursor: not-allowed; }
.ds-iconbtn svg { width: 18px; height: 18px; }
.ds-iconbtn-sm { width: 30px; height: 30px; }
.ds-iconbtn-sm svg { width: 16px; height: 16px; }
/* Outlined variant for a floating control group on a map canvas. */
.ds-iconbtn-float { background: var(--surface); border: 1px solid var(--hairline-strong); box-shadow: var(--elev-1); }
.ds-iconbtn-float:hover { background: var(--surface); border-color: var(--border-strong); }
.ds-iconbtn .ds-dot {
  position: absolute;
  top: 6px; right: 6px;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 2px var(--bg);
}
/* A vertical stack of icon buttons floating on a canvas (top-right). */
.ds-iconbtn-group { display: inline-flex; flex-direction: column; gap: var(--space-1); }

/* ---- AVATAR (initials) ------------------------------------------------------
   One neutral fill for everyone: a per-person rainbow is status noise. */
.ds-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: rgba(var(--ink), 0.10);
  color: var(--text);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: 0.02em;
  line-height: 1;
  text-transform: uppercase;
  vertical-align: middle;
}
.ds-avatar-sm { width: 22px; height: 22px; letter-spacing: 0; }
/* Avatar + name, the owner cell in a table or a row. */
.ds-person { display: inline-flex; align-items: center; gap: var(--space-2); min-width: 0; font-size: var(--text-xs); color: var(--text-muted); }

/* ---- MENU (overflow ⋯, row menu, avatar menu, More) -------------------------
   Wrap trigger + menu in .ds-menu-wrap; toggle the menu's [hidden] and the
   trigger's aria-expanded. Items are <a> or <button>, 44px touch / 36px mouse.
   .ds-menu-start opens leftwards-aligned (for a trigger on the left edge). */
.ds-menu-wrap { position: relative; display: inline-flex; }
.ds-menu {
  position: absolute;
  top: calc(100% + var(--space-1));
  right: 0;
  z-index: var(--z-dropdown);
  min-width: 200px;
  max-width: min(320px, calc(100vw - 2 * var(--gutter)));
  padding: var(--space-1);
  background: var(--surface);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--elev-3);
}
.ds-menu[hidden] { display: none; }
.ds-menu-start { right: auto; left: 0; }
.ds-menu a, .ds-menu button {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: var(--control-h);
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text);
  font: inherit;
  font-size: var(--text-sm);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.ds-menu a:hover, .ds-menu button:hover, .ds-menu a:focus-visible, .ds-menu button:focus-visible { background: rgba(var(--ink), 0.06); outline: none; }
.ds-menu .ds-menu-danger { color: var(--danger); }
.ds-menu hr { border: 0; border-top: 1px solid var(--hairline); margin: var(--space-1) 0; }
.ds-menu-label {
  padding: var(--space-2) var(--space-3) var(--space-1);
  font-size: var(--text-xs);
  font-weight: var(--weight-semi);
  color: var(--text-subtle);
}

/* ---- TOOLBAR / FILTER BAR ---------------------------------------------------
   Order: search → up to 3 quiet selects → .ds-toolbar-end (group/sort select,
   view .ds-seg, "Filters" .ds-btn with a .ds-badge count opening a .ds-sheet).
   On a phone the search is full width and the controls after it sit in ONE
   horizontally scrolling line — wrap them in .ds-toolbar-rail. Never three
   rows of controls. Active filters go in .ds-filterchips underneath, only
   while any filter is active. */
.ds-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding-bottom: var(--space-3);
}
.ds-toolbar > .ds-search { flex: 1 1 100%; }
.ds-toolbar .ds-input { min-height: var(--control-h); }
.ds-toolbar-rail {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 1 1 100%;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.ds-toolbar-rail::-webkit-scrollbar { display: none; }
.ds-toolbar-rail > * { flex: none; }
.ds-toolbar-end { margin-left: auto; display: flex; align-items: center; gap: var(--space-2); flex: none; }
@media (min-width: 768px) {
  .ds-toolbar > .ds-search { flex: 1 1 280px; max-width: 420px; }
  .ds-toolbar .ds-input { font-size: var(--text-sm); }
  .ds-toolbar-rail { flex: 1 1 auto; overflow: visible; flex-wrap: wrap; }
}
/* A select that reads as a control, not a form field: no fill, hairline. */
.ds-select-quiet {
  min-height: var(--control-h);
  padding: 0 calc(var(--space-6) + var(--space-1)) 0 var(--space-3);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-sm);
  background-color: transparent;
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 15px) 50%, calc(100% - 11px) 50%;
  background-size: 4px 4px;
  background-repeat: no-repeat;
  color: var(--text);
  font: inherit;
  font-size: var(--input-size);
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
}
.ds-select-quiet:hover { border-color: var(--border-strong); }
.ds-select-quiet:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
@media (min-width: 768px) { .ds-select-quiet { font-size: var(--text-sm); } }
/* The removable chips for the filters in force. */
.ds-filterchips { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); padding-bottom: var(--space-3); }
.ds-filterchips .ds-chip { padding-right: var(--space-1); }
.ds-chip-remove {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px; height: 20px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--text-subtle);
  font: inherit;
  line-height: 1;
  cursor: pointer;
}
.ds-chip-remove::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: var(--tap-min); height: var(--tap-min);
  transform: translate(-50%, -50%);
}
.ds-chip-remove:hover { background: rgba(var(--ink), 0.10); color: var(--text); }

/* ---- STATUS STRIP (the status filter) ---------------------------------------
   Text segments with quiet counts. AT MOST 7 visible; the rest behind a "More"
   segment that opens a .ds-menu. The selected one is a quiet fill, not brand red —
   it is a filter state, and the one accent on the page belongs to the primary
   action. Counts: <b> or .ds-strip-count. */
.ds-strip {
  display: flex;
  gap: var(--space-1);
  margin-bottom: var(--space-2);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--hairline);
  overflow-x: auto;
  scrollbar-width: none;
}
.ds-strip::-webkit-scrollbar { display: none; }
.ds-strip > button, .ds-strip > a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  min-height: var(--tap-min);
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-semi);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.ds-strip > :hover { background: rgba(var(--ink), 0.05); color: var(--text); }
.ds-strip > [aria-pressed="true"], .ds-strip > [aria-current="true"], .ds-strip > .is-active {
  background: rgba(var(--ink), 0.08);
  color: var(--text);
}
.ds-strip b, .ds-strip .ds-strip-count { font-weight: var(--weight-normal); color: var(--text-subtle); font-variant-numeric: tabular-nums; }
@media (pointer: fine) { .ds-strip > button, .ds-strip > a { min-height: 32px; } }

/* ---- DATA TABLE: .ds-table.ds-table-data ------------------------------------
   The list archetype's table. Builds on .ds-table (keep both classes).
     th[aria-sort="none|ascending|descending"]  sortable header (put a <button>
                                                inside for keyboard users)
     .ds-num                                    money/counts: right, tabular
     .ds-cell-title / .ds-cell-sub              identity cell: 15px title + sub
     tr.ds-group > td[colspan]                  group header; <span> for count/sum
     tr.ds-total                                a total row (records)
     .ds-row-act                                row action, revealed on hover
                                                (always visible on touch)
     tr[data-href]                              whole row opens the record (JS)
   Phone (<768): the table folds to stacked rows. Per cell:
     data-phone="meta"  inline on the sub line, joined with " · "
     data-phone="end"   pinned top-right (money)
     data-phone="hide"  dropped
     (none)             its own line (the identity cell)
   A genuinely numeric grid that must stay a grid on a phone: .ds-table-scroll. */
.ds-table-data th {
  position: sticky;
  top: var(--ds-table-sticky-top, var(--nav-height, 0px));
  z-index: 1;
  background: var(--bg);
  font-weight: var(--weight-semi);
  letter-spacing: 0;
  text-transform: none;
  border-bottom-color: var(--hairline-strong);
}
.ds-table-data th[aria-sort] { color: var(--text-muted); cursor: pointer; user-select: none; }
.ds-table-data th[aria-sort] > button {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.ds-table-data th[aria-sort]:hover { color: var(--text); }
.ds-table-data th[aria-sort="ascending"], .ds-table-data th[aria-sort="descending"] { color: var(--text); }
.ds-table-data th[aria-sort="descending"]::after { content: " ↓"; }
.ds-table-data th[aria-sort="ascending"]::after { content: " ↑"; }
.ds-table-data th.ds-num { text-align: right; }
.ds-table-data td { padding: var(--space-2) var(--space-3); vertical-align: middle; }
.ds-table-data tbody tr:hover { background: transparent; }
.ds-table-data tbody tr:hover td { background: rgba(var(--ink), 0.03); }
.ds-table-data tr[data-href], .ds-table-data tr.is-link { cursor: pointer; }
.ds-table-data tr[aria-selected="true"] td { background: rgba(var(--ink), 0.06); }
.ds-table-data tr[aria-selected="true"] td:first-child { box-shadow: inset 2px 0 0 var(--accent); }
.ds-table-data .ds-num { white-space: nowrap; }
.ds-cell-title { font-size: var(--text-base); font-weight: var(--weight-semi); color: var(--text); line-height: var(--leading-tight); }
.ds-cell-sub { margin-top: 2px; font-size: var(--text-xs); color: var(--text-subtle); }
.ds-table-data tr.ds-group td {
  padding-top: var(--space-5);
  background: transparent;
  border-bottom-color: var(--hairline-strong);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-subtle);
  cursor: default;
}
.ds-table-data tr.ds-group td span {
  margin-left: var(--space-2);
  font-weight: var(--weight-normal);
  letter-spacing: 0;
  text-transform: none;
  font-variant-numeric: tabular-nums;
}
.ds-table-data tr.ds-total td {
  border-top: 1px solid var(--hairline-strong);
  border-bottom: 0;
  font-weight: var(--weight-semi);
}
.ds-table-data tr.ds-total .ds-num { font-size: var(--text-md); }
.ds-table-data tr.ds-group:hover td, .ds-table-data tr.ds-total:hover td { background: transparent; }
@media (hover: hover) and (pointer: fine) {
  .ds-table-data .ds-row-act { opacity: 0; transition: opacity var(--dur-fast) var(--ease-out); }
  .ds-table-data tr:hover .ds-row-act, .ds-table-data tr:focus-within .ds-row-act { opacity: 1; }
}
@media (max-width: 767px) {
  .ds-table-data thead { display: none; }
  .ds-table-data, .ds-table-data tbody, .ds-table-data tr, .ds-table-data td { display: block; }
  .ds-table-data tr {
    position: relative;
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--hairline);
  }
  .ds-table-data td { padding: 0; border: 0; }
  /* Room for the pinned money on the right. */
  .ds-table-data td:first-child { padding-right: calc(var(--space-8) + var(--space-5)); }
  .ds-table-data td[data-phone="hide"] { display: none; }
  .ds-table-data td[data-phone="end"] { position: absolute; top: var(--space-3); right: 0; padding: 0; }
  .ds-table-data td[data-phone="meta"] { display: inline; font-size: var(--text-xs); color: var(--text-subtle); }
  .ds-table-data td[data-phone="meta"] + td[data-phone="meta"]::before { content: " · "; }
  .ds-table-data tbody tr:hover td { background: transparent; }
  /* Cells are blocks here, so selection paints the row, not each cell. */
  .ds-table-data tr[aria-selected="true"] { background: rgba(var(--ink), 0.06); box-shadow: inset 2px 0 0 var(--accent); padding-left: var(--space-3); }
  .ds-table-data tr[aria-selected="true"] td { background: transparent; }
  .ds-table-data tr[aria-selected="true"] td:first-child { box-shadow: none; }
  .ds-table-data tr.ds-group { padding: var(--space-5) 0 var(--space-2); border-bottom-color: var(--hairline-strong); }
  .ds-table-data tr.ds-group td { padding: 0; }
  .ds-table-data tr.ds-total { border-bottom: 0; border-top: 1px solid var(--hairline-strong); }
  .ds-table-data tr.ds-total td { border: 0; }
}

/* Inside .ds-table-scroll the scroll container is the sticky reference, so the header sits at 0, not at the page nav height
   (otherwise it covers the first rows). Only a table that scrolls with the page uses --nav-height. */
.ds-table-scroll .ds-table-data th, .ds-table-scroll .ds-table th { top: 0; }

/* Group header for a .ds-rows list (the same look as tr.ds-group). */
.ds-rows-group {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-5) var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--hairline-strong);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-subtle);
}
.ds-rows-group span { font-weight: var(--weight-normal); letter-spacing: 0; text-transform: none; font-variant-numeric: tabular-nums; }
.ds-rows-group:first-child { padding-top: var(--space-2); }
@media (max-width: 767px) { .ds-rows-group { padding-inline: 0; border-bottom: 0; } }

/* ---- INLINE STATUS: dot + word ----------------------------------------------
   The ONE status signal for a row. Neutral dot by default (most states are
   neutral). -ok for terminal success, -info for in-progress, -warn for at
   risk, -danger for the critical few (and only -danger colours the word). */
.ds-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-sm);
  color: var(--text-muted);
  white-space: nowrap;
}
.ds-status::before {
  content: "";
  flex: none;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: rgba(var(--ink), 0.28);
}
.ds-status-ok::before     { background: var(--positive); }
.ds-status-info::before   { background: var(--info); }
.ds-status-warn::before   { background: var(--warning); }
.ds-status-danger::before { background: var(--danger); }
.ds-status-danger { color: var(--danger); font-weight: var(--weight-semi); }

/* ---- KEY / VALUE LIST -------------------------------------------------------
   A <dl> grid for record facts: the side panel's Customer / Property / Dates.
   Replaces a card per fact. .ds-kv (above) stays for one pair per row inside a
   card. overflow-wrap:anywhere stops an email address wrapping mid-word into a
   190px box — it wraps only when it has to. */
.ds-kvl {
  display: grid;
  grid-template-columns: minmax(88px, max-content) minmax(0, 1fr);
  gap: var(--space-2) var(--space-4);
  margin: 0;
  font-size: var(--text-sm);
}
.ds-kvl dt { color: var(--text-subtle); }
.ds-kvl dd { margin: 0; min-width: 0; color: var(--text); overflow-wrap: anywhere; }

/* ---- RECORD LAYOUT + PANEL SECTIONS -----------------------------------------
   Main column + a 340px side panel from 1024. On a phone one column; give the
   one side block that must come first (the clock, the map strip) the
   .ds-first-phone class. Side-panel blocks are .ds-panel-sec: a rule between
   sections, a caps title, a ghost "Edit" — never a card per fact. */
.ds-record { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-6); }
.ds-record > * { min-width: 0; }
@media (max-width: 1023px) { .ds-record > .ds-first-phone { order: -1; } }
@media (min-width: 1024px) {
  .ds-record { grid-template-columns: minmax(0, 1fr) 340px; gap: var(--space-7); }
}
.ds-panel-sec { padding: var(--space-4) 0; border-top: 1px solid var(--hairline); }
.ds-panel-sec:first-child { padding-top: 0; border-top: 0; }
.ds-panel-sec-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  min-height: var(--control-h-sm);
  margin-bottom: var(--space-2);
}
.ds-panel-sec-title, .ds-panel-sec-head > h3 {
  margin: 0;
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-subtle);
}

/* ---- STEPS (stage progress) -------------------------------------------------
   <ol class="ds-steps"> of <li>; .is-done for passed stages, aria-current=
   "step" for the current one (the accent: it is where the action is). */
.ds-steps {
  display: flex;
  gap: var(--space-1);
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-xs);
  color: var(--text-subtle);
}
.ds-steps li {
  flex: 1 1 0;
  min-width: 0;
  padding-top: var(--space-2);
  border-top: 3px solid rgba(var(--ink), 0.10);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ds-steps li.is-done { border-top-color: rgba(var(--ink), 0.35); color: var(--text-muted); }
.ds-steps li[aria-current="step"] { border-top-color: var(--accent); color: var(--text); font-weight: var(--weight-semi); }

/* ---- TIMELINE (activity, emails, portal events) -----------------------------
   <ol class="ds-timeline"><li> avatar · .ds-tl-what (+ <small>) · <time>. */
.ds-timeline { margin: 0; padding: 0; list-style: none; }
.ds-timeline > li {
  position: relative;
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr) auto;
  gap: var(--space-3);
  padding-bottom: var(--space-4);
  font-size: var(--text-sm);
}
.ds-timeline > li::before {
  content: "";
  position: absolute;
  top: 26px; bottom: 2px; left: 10.5px;
  width: 1px;
  background: var(--hairline-strong);
}
.ds-timeline > li:last-child { padding-bottom: 0; }
.ds-timeline > li:last-child::before { display: none; }
.ds-tl-what { min-width: 0; color: var(--text); overflow-wrap: anywhere; }
.ds-tl-what small { display: block; margin-top: 2px; font-size: var(--text-xs); color: var(--text-subtle); }
.ds-timeline time, .ds-tl-time { font-size: var(--text-xs); color: var(--text-subtle); white-space: nowrap; font-variant-numeric: tabular-nums; }
/* A dot instead of an avatar, for system events. */
.ds-tl-dot { width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; }
.ds-tl-dot::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: rgba(var(--ink), 0.35); }

/* ---- STAT ROW (dashboard numbers) -------------------------------------------
   <dl class="ds-statrow"><div><dt>Label</dt><dd>$38.2k<small>of $96k</small></dd></div>…
   At most 6 numbers. 2 columns on a phone, auto-fit from 768. Plain numbers:
   no pastel fills, no coloured values — a number that needs a colour is a
   status, and gets a .ds-status or .ds-age next to it. .ds-stat (above) stays
   for a single tile inside a card. */
.ds-statrow {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4) var(--space-5);
  margin: 0;
  padding: var(--space-3) 0;
}
@media (min-width: 768px) { .ds-statrow { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); } }
.ds-statrow > div { min-width: 0; }
.ds-statrow dt { font-size: var(--text-xs); color: var(--text-subtle); }
.ds-statrow dd {
  margin: 2px 0 0;
  font-size: var(--text-stat);
  font-weight: var(--weight-semi);
  line-height: var(--leading-tight);
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.ds-statrow dd small { margin-left: var(--space-1); font-size: var(--text-xs); font-weight: var(--weight-normal); color: var(--text-subtle); }

/* ---- BANNER (inline error / warning / notice) -------------------------------
   Plain words and a next step: "QuickBooks isn't connected on this
   environment. Connect it in Settings." + [Retry] — never "API 500". A
   hairline role border, not a pink fill. role="alert" for errors that
   appeared after an action; role="status" otherwise. */
.ds-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-sm);
  background: rgba(var(--ink), 0.03);
  color: var(--text);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
}
.ds-banner-danger { border-color: var(--danger); }
.ds-banner-warn { border-color: var(--warning); }
.ds-banner-info { border-color: var(--info); }
.ds-banner b, .ds-banner strong { font-weight: var(--weight-semi); }
.ds-banner-body { flex: 1 1 240px; min-width: 0; }
.ds-banner-body p { margin: 0; }
.ds-banner-body p + p { margin-top: 2px; color: var(--text-muted); }
.ds-banner-actions { display: flex; gap: var(--space-2); margin-left: auto; }

/* ---- STATES: empty / loading / inline --------------------------------------
   .ds-empty (above) is the full empty state: title, one sentence, one next
   step. .ds-empty-inline is the quiet one-liner for an empty sub-list inside a
   record ("No files yet"). Loading is always a skeleton shaped like the
   content it replaces; announce it with aria-busy="true" on the container. */
.ds-empty-inline { margin: 0; padding: var(--space-2) 0; font-size: var(--text-sm); color: var(--text-subtle); }
.ds-skel-title { height: 1.5rem; width: 14rem; max-width: 60%; margin-bottom: var(--space-2); }
.ds-skel-avatar { width: 28px; height: 28px; border-radius: 50%; flex: none; }
.ds-skel-stat { height: 2.75rem; }
/* A table-shaped skeleton: put .ds-skel-line spans in the cells of a few
   <tr class="ds-skel-tr"> so the columns do not jump when rows land. */
.ds-skel-tr td { padding-block: var(--space-3); }
.ds-skel-tr .ds-skel-line { margin: 0; }

/* ---- ACTION BAR (phone primary) ---------------------------------------------
   Sticky at the bottom on phones, above the tab bar when there is one. The
   same primary also sits in .ds-head-actions with .ds-desktop-only. */
.ds-actionbar {
  position: sticky;
  bottom: env(safe-area-inset-bottom);
  z-index: var(--z-sticky);
  display: flex;
  gap: var(--space-2);
  padding: var(--space-3) 0;
  background: linear-gradient(to top, var(--bg) 70%, transparent);
}
.ds-has-tabbar .ds-actionbar { bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom)); }
.ds-actionbar > .ds-btn { flex: 1 1 0; }
.ds-actionbar > .ds-btn-primary { flex-grow: 2; }
@media (min-width: 768px) { .ds-actionbar { display: none; } }

/* ---- DIALOG (confirm / alert / small forms) ---------------------------------
   Native <dialog class="ds-dialog"> opened with showModal(): Esc, focus trap
   and the top layer come free. Replaces window.confirm()/alert(). For the
   common confirm, use the helper public/shared/ds-dialog.js:
     if (await dsConfirm({ title: 'Delete this entry?', body: '…',
                           confirm: 'Delete', danger: true })) { … }
   Markup: .ds-dialog-title, .ds-dialog-body, then a .ds-actions row. */
dialog.ds-dialog {
  width: min(480px, calc(100vw - 2 * var(--space-4)));
  max-height: min(88vh, 720px);
  margin: auto;
  padding: var(--space-5);
  overflow-y: auto;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--elev-3);
}
dialog.ds-dialog[open] { animation: ds-sheet-in var(--dur) var(--ease-out); }
dialog.ds-dialog::backdrop { background: var(--scrim); }
.ds-dialog-wide { width: min(640px, calc(100vw - 2 * var(--space-4))); }
.ds-dialog-title {
  margin: 0 0 var(--space-2);
  font-size: var(--text-lg);
  font-weight: var(--weight-semi);
  line-height: var(--leading-tight);
  color: var(--text);
}
.ds-dialog-body { margin: 0; font-size: var(--text-base); line-height: var(--leading-snug); color: var(--text-muted); overflow-wrap: anywhere; }
.ds-dialog .ds-actions { margin-top: var(--space-5); }

/* ---- RESPONSIVE HELPERS at the 1024 break -----------------------------------
   .ds-desktop-only / .ds-mobile-only (above) switch at 768. These switch at
   1024, where the shell swaps tab bar for top-bar nav. */
@media (max-width: 1023px) { .ds-wide-only { display: none !important; } }
@media (min-width: 1024px) { .ds-narrow-only { display: none !important; } }

@media (prefers-reduced-motion: reduce) {
  dialog.ds-dialog[open] { animation: none; }
  .ds-table-data .ds-row-act { transition: none; }
}

/* =============================================================================
   SHELL (portal): top bar, section subnav, phone tab bar, impersonation strip
   =============================================================================
   Markup lives in src/layouts/PortalLayoutV2.astro; behaviour in public/portal/shell.js.
   Role is set on <html data-role="tenant|owner|admin"> by a blocking inline
   script before first paint, so exactly one item set shows and there is no
   flash of the wrong nav. An empty data-role (signed out) shows no nav at all.
   ============================================================================= */
body {
  --has-sub: 0;
  --has-imp: 0;
  --nav-height: calc(var(--rule-brand-h) + var(--bar-h) + var(--subnav-h) * var(--has-sub) + var(--imp-h) * var(--has-imp) + env(safe-area-inset-top, 0px));
}
html[data-role="admin"] body[data-subnav] { --has-sub: 1; }
html[data-impersonating] body { --has-imp: 1; }

.ds-shell-head {
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  background: var(--bg);
  padding-top: env(safe-area-inset-top);
}
.ds-bar { border-top: var(--rule-brand-h) solid var(--brand); border-bottom: 1px solid var(--border-strong); }
.ds-bar-inner { display: flex; align-items: center; gap: var(--space-5); height: var(--bar-h); }
.ds-bar-brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--tap-min);
  color: var(--text);
  white-space: nowrap;
}
.ds-bar-brand:hover { text-decoration: none; }
/* The logo lockup (public/logo-main.svg, logo-white.svg in dark) replaces the old text wordmark. */
.ds-brand-logo { --logo-h: 36px; display: block; height: var(--logo-h); width: auto; max-width: none; margin-left: calc(var(--logo-h) * -0.1667); }   /* the SVG carries 125/750 of padding: align the mark to the gutter */
.ds-brand-logo-dark { display: none; }
html[data-theme="dark"] .ds-brand-logo-light { display: none; }
html[data-theme="dark"] .ds-brand-logo-dark { display: block; }
.ds-brand-product {
  padding-left: var(--space-3);
  border-left: 1px solid var(--border-strong);
  color: var(--text-subtle);
  font-size: var(--text-xs);
  font-weight: var(--weight-semi);
  letter-spacing: 0.1em;
  line-height: 1.6;
  text-transform: uppercase;
}
@media (max-width: 479px) {
  .ds-brand-logo { --logo-h: 28px; }
  .ds-brand-product { display: none; }
}
.ds-bar-nav { display: none; }
.ds-bar-end { margin-left: auto; display: flex; align-items: center; gap: var(--space-2); }
html[data-role=""] .ds-bar-end { display: none; }
.ds-bar-help { display: none; }
/* Phone: the admin tab bar is full (5 tabs), so Help is an icon in the bar. */
.ds-bar-helpicon { display: none; }
@media (max-width: 1023px) {
  html[data-role="admin"] .ds-bar-helpicon { display: inline-flex; }
  .ds-tabbar[data-for="admin"] > button { display: none; }
}
.ds-bar .ds-iconbtn { width: var(--tap-min); height: var(--tap-min); }
@media (min-width: 1024px) {
  .ds-bar .ds-iconbtn { width: 36px; height: 36px; }
  .ds-bar-help { display: inline-flex; }
  .ds-bar-nav { align-items: stretch; gap: var(--space-1); height: 100%; }
  html[data-role="tenant"] .ds-bar-nav[data-for="tenant"],
  html[data-role="owner"] .ds-bar-nav[data-for="owner"],
  html[data-role="admin"] .ds-bar-nav[data-for="admin"] { display: flex; }
  .ds-bar-nav a {
    display: flex;
    align-items: center;
    padding: 0 var(--space-3);
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-weight: var(--weight-semi);
    text-decoration: none;
    box-shadow: inset 0 -2px 0 transparent;
  }
  .ds-bar-nav a:hover { color: var(--text); }
  .ds-bar-nav a[aria-current="page"] { color: var(--text); box-shadow: inset 0 -2px 0 var(--accent); }
}

/* Section subnav: admin only; links, not buttons; each tab is a page. */
.ds-subnav { display: none; }
html[data-role="admin"] .ds-subnav[data-for="admin"] { display: block; }
.ds-subnav a { flex: none; }
/* Scroll cue: shell.js sets .has-more / .has-less from the scroll position; the edge that still has content fades out. */
.ds-subnav.has-more { -webkit-mask-image: linear-gradient(to right, rgba(var(--ink), 1) calc(100% - 32px), transparent); mask-image: linear-gradient(to right, rgba(var(--ink), 1) calc(100% - 32px), transparent); }
.ds-subnav.has-less { -webkit-mask-image: linear-gradient(to left, rgba(var(--ink), 1) calc(100% - 32px), transparent); mask-image: linear-gradient(to left, rgba(var(--ink), 1) calc(100% - 32px), transparent); }
.ds-subnav.has-more.has-less { -webkit-mask-image: linear-gradient(to right, transparent, rgba(var(--ink), 1) 32px, rgba(var(--ink), 1) calc(100% - 32px), transparent); mask-image: linear-gradient(to right, transparent, rgba(var(--ink), 1) 32px, rgba(var(--ink), 1) calc(100% - 32px), transparent); }

/* Impersonation: a persistent strip, not a coloured bar that changes the nav. */
.ds-impersonate {
  display: none;
  height: var(--imp-h);
  align-items: center;
  border-bottom: 1px solid var(--warning);
  background: var(--warning-tint);
  color: var(--text);
  font-size: var(--text-sm);
}
html[data-impersonating] .ds-impersonate { display: flex; }
.ds-impersonate-inner { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); width: 100%; }
.ds-impersonate b { font-weight: var(--weight-semi); }

/* Phone tab bar: 60px + safe area. Hidden from 1024 (the bar has the nav). */
.ds-tabbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: var(--z-nav);
  display: none;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  height: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  padding-bottom: env(safe-area-inset-bottom);
  background: var(--bg);
  border-top: 1px solid var(--border-strong);
}
@media (max-width: 1023px) {
  html[data-role="tenant"] .ds-tabbar[data-for="tenant"],
  html[data-role="owner"] .ds-tabbar[data-for="owner"],
  html[data-role="admin"] .ds-tabbar[data-for="admin"] { display: grid; }
}
.ds-tabbar a, .ds-tabbar > button {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  border: 0;
  background: none;
  color: var(--text-subtle);
  font-size: var(--text-xs);
  font-weight: var(--weight-semi);
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.ds-tabbar svg { width: 22px; height: 22px; }
.ds-tabbar a[aria-current="page"] { color: var(--text); }
.ds-tabbar a[aria-current="page"]::before {
  content: "";
  position: absolute;
  top: 0; left: 30%; right: 30%;
  height: 2px;
  background: var(--accent);
}
.ds-has-tabbar { padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom)); }
@media (min-width: 1024px) { .ds-has-tabbar { padding-bottom: 0; } }

/* Avatar menu: theme control sits inside .ds-menu. */
.ds-menu .ds-seg { display: flex; margin: var(--space-1) var(--space-2) var(--space-2); }
.ds-menu .ds-seg-item {
  flex: 1 1 0;
  width: auto;
  justify-content: center;
  min-height: var(--control-h-sm);
  padding: 0 var(--space-2);
  color: var(--text-subtle);
  font-weight: var(--weight-semi);
}
.ds-menu .ds-seg-item[aria-pressed="true"] { background: var(--bg-elevated); color: var(--text); box-shadow: var(--elev-1); }
.ds-menu-who { padding: var(--space-2) var(--space-3); font-size: var(--text-sm); color: var(--text); overflow-wrap: anywhere; }
.ds-menu-who small { display: block; margin-top: 2px; font-size: var(--text-xs); color: var(--text-subtle); }

/* =============================================================================
   CHAT (Help drawer): one component for tenant and admin
   =============================================================================
   Desktop: 380px drawer on the right, opened from Help in the bar (it no
   longer floats over table content). Phone: full-height sheet. Esc closes,
   focus is trapped while open and returns to the Help button.
   ============================================================================= */
.ds-chat {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  z-index: var(--z-sheet);
  display: flex;
  flex-direction: column;
  width: min(380px, 100vw);
  background: var(--bg-elevated);
  border-left: 1px solid var(--border-strong);
  box-shadow: var(--elev-3);
  animation: ds-chat-in var(--dur) var(--ease-out);
}
@media (max-width: 767px) { .ds-chat { width: 100vw; border-left: 0; } }
/* From 768 the drawer starts below the bar, so Help and the avatar menu stay visible and usable; the scrim dims
   only the page and closes the drawer on click. On a phone the drawer is the whole screen and needs no scrim. */
@media (min-width: 768px) { .ds-chat { top: var(--nav-height); } }
.ds-chat-scrim { position: fixed; inset: var(--nav-height) 0 0 0; z-index: calc(var(--z-sheet) - 1); background: var(--scrim); }
@media (max-width: 767px) { .ds-chat-scrim { display: none !important; } }
@keyframes ds-chat-in { from { transform: translateX(24px); opacity: 0; } }
.ds-chat-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4);
  padding-top: max(var(--space-4), env(safe-area-inset-top));
  border-bottom: 1px solid var(--border);
}
.ds-chat-title { font-size: var(--text-lg); font-weight: var(--weight-semi); color: var(--text); }
.ds-chat-sub { margin-top: 2px; font-size: var(--text-sm); color: var(--text-muted); }
.ds-chat-ctx {
  display: inline-flex;
  margin-top: var(--space-2);
  padding: 2px var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  color: var(--text-subtle);
}
.ds-chat-log {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  overscroll-behavior: contain;
}
.ds-chat-msg { display: flex; }
.ds-chat-msg-user { justify-content: flex-end; }
.ds-chat-bubble {
  max-width: 88%;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--text-base);
  line-height: var(--leading-snug);
  color: var(--text);
  overflow-wrap: anywhere;
}
.ds-chat-msg-bot .ds-chat-bubble { background: var(--bg-sunken); }
.ds-chat-msg-user .ds-chat-bubble { background: var(--bg-elevated); border: 1px solid var(--border-strong); }
.ds-chat-bubble p + p { margin-top: var(--space-2); }
.ds-chat-bubble ul { margin: var(--space-2) 0 0; padding-left: var(--space-5); }
.ds-chat-bubble li { margin-top: 2px; }
.ds-chat-bubble code { font-family: var(--font-mono); font-size: var(--text-xs); padding: 0 var(--space-1); border-radius: var(--radius-xs); background: rgba(var(--ink), 0.08); }
.ds-chat-caret::after {
  content: "";
  display: inline-block;
  width: 7px; height: 1em;
  margin-left: 2px;
  vertical-align: text-bottom;
  background: var(--text-muted);
  animation: ds-caret 1s steps(2, start) infinite;
}
@keyframes ds-caret { to { visibility: hidden; } }
.ds-chat-err { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--danger); }
.ds-chat-starters { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: 0 var(--space-4) var(--space-3); }
.ds-chat-form {
  display: flex;
  align-items: flex-end;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  padding-bottom: max(var(--space-3), env(safe-area-inset-bottom));
  border-top: 1px solid var(--border);
}
.ds-chat-form .ds-textarea { flex: 1 1 auto; min-height: var(--control-h); max-height: 120px; resize: none; }

/* =============================================================================
   INLINE EDIT: value is text; hover shows a hairline; click or Tab makes it an
   input; Enter or blur saves; Esc reverts. States are classes on .ds-inline:
   is-dirty (accent hairline), is-saving (spinner), is-saved (check), is-error
   (danger hairline; put the message in a .ds-hint under the row).
   For tables of <= 100 numeric/text rows. More than 3 fields or a destructive
   effect opens a .ds-sheet instead.
   ============================================================================= */
.ds-inline {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  max-width: 100%;
  min-height: var(--control-h-sm);
  padding: 0 var(--space-2);
  border: 1px solid transparent;
  border-radius: var(--radius-xs);
  color: inherit;
  cursor: text;
  font-variant-numeric: tabular-nums;
}
.ds-inline:hover { border-color: var(--border-strong); }
.ds-inline:focus-within { border-color: var(--accent); background: var(--bg-elevated); }
.ds-inline.is-dirty { border-color: var(--accent); }
.ds-inline.is-error { border-color: var(--danger); }
.ds-inline-input {
  min-width: 0;
  width: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: var(--input-size);
  text-align: inherit;
  outline: none;
}
.ds-inline.ds-num { text-align: right; }
.ds-inline.is-saving::after {
  content: "";
  flex: none;
  width: 12px; height: 12px;
  border: 2px solid var(--border-strong);
  border-top-color: var(--text-muted);
  border-radius: 50%;
  animation: ds-spin 0.7s linear infinite;
}
.ds-inline.is-saved::after { content: "\2713"; flex: none; font-size: var(--text-xs); color: var(--positive); }
@keyframes ds-spin { to { transform: rotate(360deg); } }

/* =============================================================================
   CHART FRAME: title sentence, unit line, svg, source line, optional table.
   Grey first, one accent (--chart-1). Marks: .ds-ch-accent, .ds-ch-g1/g2/g3.
   Sign by position: negative values sit below a visible .ds-ch-zero line.
   SVG text must stay >= 12px on screen: draw the svg at the container's pixel width
   (viewBox = width x height, no scaling), as the style guide chart does. A fixed viewBox
   scaled down to a phone shrinks every label.
   ============================================================================= */
.ds-chart { margin: 0; position: relative; min-width: 0; }
.ds-chart-title { margin: 0; font-size: var(--text-md); font-weight: var(--weight-semi); line-height: var(--leading-tight); color: var(--text); }
.ds-chart-unit { margin: 2px 0 0; font-size: var(--text-xs); color: var(--text-subtle); }
.ds-chart-plot { margin-top: var(--space-3); }
.ds-chart-plot svg { display: block; width: 100%; height: auto; }
.ds-chart-plot text { font-family: inherit; font-size: var(--text-xs); fill: var(--text-subtle); font-variant-numeric: tabular-nums; }
.ds-chart-plot text.ds-ch-label { fill: var(--text); font-weight: var(--weight-semi); }
.ds-chart-src { margin: var(--space-2) 0 0; font-size: var(--text-xs); color: var(--text-subtle); }
.ds-chart-toggle { margin-top: var(--space-2); }
.ds-ch-accent { fill: var(--chart-1); stroke: var(--bg-elevated); stroke-width: 1; }
.ds-ch-g1 { fill: var(--chart-g1); stroke: var(--bg-elevated); stroke-width: 1; }
.ds-ch-g2 { fill: var(--chart-g2); stroke: var(--bg-elevated); stroke-width: 1; }
.ds-ch-g3 { fill: var(--chart-g3); }
.ds-ch-line-accent { fill: none; stroke: var(--chart-1); stroke-width: 2; }
.ds-ch-line-g1 { fill: none; stroke: var(--chart-g1); stroke-width: 2; }
.ds-ch-line-g2 { fill: none; stroke: var(--chart-g2); stroke-width: 2; }
.ds-ch-grid { stroke: var(--border); stroke-width: 1; }
.ds-ch-zero { stroke: var(--border-strong); stroke-width: 1.5; }
.ds-chart-tip {
  position: absolute;
  z-index: var(--z-dropdown);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
  box-shadow: var(--elev-2);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}
.ds-legend { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); margin-top: var(--space-2); font-size: var(--text-xs); color: var(--text-muted); }
.ds-legend i { display: inline-block; width: 10px; height: 10px; margin-right: var(--space-1); border-radius: 2px; vertical-align: -1px; }

/* Link-style text button (an inline "Show details" that is not navigation). */
.ds-link { padding: 0; border: 0; background: none; color: var(--link); font: inherit; cursor: pointer; }
.ds-link:hover { text-decoration: underline; text-underline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  .ds-chat { animation: none; }
  .ds-inline.is-saving::after { animation: none; }
  .ds-chat-caret::after { animation: none; }
}

/* =============================================================================
   BRAND: footer, "New" chip, type badge, sign-in photo panel
   The logo, 3px rule, title tick and pill buttons are above (bar, head, btn).
   ============================================================================= */

/* Short pages keep the footer at the bottom of the viewport. */
body { display: flex; flex-direction: column; }
.ds-page, .ds-auth-wrap { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; }
.ds-page > main, .ds-auth-wrap > main { flex: 1 0 auto; min-width: 0; }

.ds-foot { flex: none; border-top: 1px solid var(--border); }
.ds-foot-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-3);
  padding-top: var(--space-4);
  padding-bottom: var(--space-4);
  font-size: var(--text-sm);
  color: var(--text-subtle);
}
.ds-foot-mark { flex: none; width: 20px; height: 20px; fill: var(--brand); }
.ds-foot-end { margin-left: auto; }
.ds-foot a { color: var(--text-subtle); text-decoration: underline; text-underline-offset: 2px; }
.ds-foot a:hover { color: var(--text); }
@media (max-width: 767px) { .ds-foot-end { margin-left: 0; flex-basis: 100%; } }

/* "New": the only red text in the product. Brand tint behind, --brand-text on top (5.0:1+ in both themes). */
.ds-chip-new {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--brand-tint);
  color: var(--brand-text);
  font-size: var(--text-xs);
  font-weight: var(--weight-semi);
  line-height: 1.6;
  white-space: nowrap;
}

/* A static word tag (document type, kind). Not a control: .ds-chip is the control. */
.ds-tag {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--space-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  line-height: 1.6;
  white-space: nowrap;
}

/* Sign in / verify: form left, the brand photo right from 1024. The photo is a CSS background on a
   div that is display:none below 1024, so a phone never requests the image. */
.ds-auth-photo { display: none; }
@media (min-width: 1024px) {
  .ds-auth-wrap { flex-direction: row; }
  .ds-auth-wrap > main { flex: 0 0 min(100%, 640px); display: flex; align-items: center; }
  .ds-auth-wrap > main > .ds-frame { padding-bottom: var(--space-7); }
  .ds-auth-photo {
    display: flex;
    flex: 1 1 0;
    min-width: 0;
    align-items: flex-end;
    padding: var(--space-7);
    background: linear-gradient(to top, var(--scrim), transparent 55%), url("../vulcan-banner.jpg") center / cover no-repeat;
  }
}
.ds-auth-tagline {
  max-width: 14ch;
  color: var(--on-accent);
  font-size: 2rem;
  font-weight: var(--weight-semi);
  line-height: var(--leading-tight);
}

/* =============================================================================
   DOCUMENT CENTER: the card on Home / Overview and the grouped Documents page
   ============================================================================= */
.ds-doccard { display: flex; flex-direction: column; gap: var(--space-3); }
.ds-doccard-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.ds-doccard-title { font-size: var(--text-lg); font-weight: var(--weight-semi); margin: 0; }
.ds-doccard-meta { margin: 0; font-size: var(--text-sm); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.ds-doccard-list { list-style: none; border-top: 1px solid var(--border); }
.ds-doccard-list li { display: flex; align-items: center; gap: var(--space-3); min-height: var(--tap-min); padding: var(--space-1) 0; border-bottom: 1px solid var(--border); }
.ds-doccard-list li > div { flex: 1 1 0; min-width: 0; }
.ds-doccard-name { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); font-weight: var(--weight-medium); }
.ds-doccard-name > span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-doccard-sub { font-size: var(--text-xs); color: var(--text-subtle); }
.ds-doccard-empty { margin: 0; font-size: var(--text-sm); color: var(--text-subtle); }
.ds-doccard-foot { display: flex; gap: var(--space-2); flex-wrap: wrap; }

.ds-docs-tools { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); margin-bottom: var(--space-4); }
.ds-docs-tools .ds-search { flex: 1 1 240px; max-width: 420px; }
.ds-docs-tools .ds-select { width: auto; min-width: 150px; }
.ds-docs-count { margin: 0 0 var(--space-2); font-size: var(--text-sm); color: var(--text-muted); }
.ds-docs-name { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.ds-docs-panel { margin-bottom: var(--space-5); }
.ds-docs-tools .ds-seg { display: inline-flex; align-items: center; margin-left: auto; }
.ds-docs-groupby { padding: 0 var(--space-2); font-size: var(--text-xs); font-weight: var(--weight-semi); color: var(--text-subtle); white-space: nowrap; }
.ds-table-data tr.ds-group + tr.ds-group td { padding-top: var(--space-2); }
.ds-nowrap { white-space: nowrap; }
.ds-nav-count { margin-left: var(--space-1); color: var(--text-subtle); font-variant-numeric: tabular-nums; }
/* Signed out (sign-in, verify) has no tab bar, so no room is reserved for one. */
html[data-role=""] .ds-has-tabbar { padding-bottom: 0; }
@media (pointer: coarse) {
  .ds-foot a { display: inline-flex; align-items: center; min-height: var(--tap-min); }
  .ds-docs-tools .ds-seg-item { min-height: var(--tap-min); }
}
