/* ============================================================================
   White Sand OS — Convergence primitives  (FE-06)
   ----------------------------------------------------------------------------
   The small shared layer the new convergence surfaces are built from:
   Operating Space navigation, Department Consoles, Activity Workspaces and
   entity context. It does not replace css/components.css — that library keeps
   every existing screen exactly as it is, and this file is additive.

   THREE RULES THIS FILE OBEYS, ALL ENFORCED BY tests/designSystem.test.mjs
   1. EVERY selector is scoped by a `.ds-` class. Nothing here can match an
      element that has not opted in, so loading this file last changes nothing
      that renders today. (Keyframes are `ds-`-prefixed for the same reason:
      @keyframes names are global, so an unprefixed one would silently replace
      the shell's.)
   2. COLOR comes only from var(--ds-…). No hex, rgb() or hsl() literal appears
      below — that is what makes all seven themes work without a second palette.
   3. LOGICAL PROPERTIES ONLY. No margin/padding-left|right, no left/right, no
      text-align: left|right, no float. The shell renders Arabic RTL and English
      LTR from the same CSS (i18n-core.js flips <html dir>), so a physical
      direction is always a bug here.

   ADOPTION IS INCREMENTAL. A new surface uses these classes. An existing screen
   keeps its own classes until it is deliberately migrated — never by rewrite.
   See docs/frontend-convergence/DESIGN_SYSTEM.md.
   ========================================================================== */

/* ============================================================================
   1. PAGE FRAME
   The outermost shell of a convergence surface: a capped, centred column with
   a header band and a body. `min-inline-size: 0` on the body is what lets a
   wide table scroll inside its own wrapper instead of pushing the page.
   ========================================================================== */
.ds-page {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-5);
  inline-size: 100%;
  max-inline-size: var(--ds-layout-max);
  margin-inline: auto;
  padding-inline: var(--ds-layout-gutter);
  padding-block: var(--ds-space-6) var(--ds-space-10);
  color: var(--ds-color-text-primary);
  font-family: var(--ds-font-body);
  font-size: var(--ds-text-md);
  line-height: var(--ds-leading-normal);
}
.ds-page--narrow { max-inline-size: var(--ds-layout-narrow); }
.ds-page--full { max-inline-size: none; }

.ds-page-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--ds-space-4);
  min-inline-size: 0;
}
.ds-page-heading { min-inline-size: 0; }
.ds-page-title {
  margin: 0;
  font-size: var(--ds-text-2xl);
  font-weight: var(--ds-weight-bold);
  line-height: var(--ds-leading-tight);
  letter-spacing: var(--ds-tracking-tight);
  color: var(--ds-color-text-primary);
}
.ds-page-subtitle {
  margin-block: var(--ds-space-1) 0;
  max-inline-size: 62ch;
  font-size: var(--ds-text-sm);
  color: var(--ds-color-text-secondary);
}
.ds-page-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--ds-space-2);
}
.ds-page-body {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-4);
  min-inline-size: 0;
}

/* ============================================================================
   2. PANEL — the standard bounded container (a console card, a workspace pane)
   ========================================================================== */
.ds-panel {
  display: flex;
  flex-direction: column;
  min-inline-size: 0;
  background: var(--ds-color-surface-raised);
  border: 1px solid var(--ds-color-border-default);
  border-radius: var(--ds-radius-md);
  box-shadow: var(--ds-elevation-1);
}
.ds-panel--flush { box-shadow: var(--ds-elevation-0); }
.ds-panel--raised { box-shadow: var(--ds-elevation-2); }

.ds-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--ds-space-3);
  padding: var(--ds-space-3) var(--ds-space-4);
  border-block-end: 1px solid var(--ds-color-border-default);
  min-inline-size: 0;
}
.ds-panel-title {
  margin: 0;
  min-inline-size: 0;
  font-size: var(--ds-text-lg);
  font-weight: var(--ds-weight-bold);
  line-height: var(--ds-leading-tight);
  color: var(--ds-color-text-primary);
}
.ds-panel-subtitle {
  margin: 0;
  font-size: var(--ds-text-xs);
  color: var(--ds-color-text-secondary);
}
.ds-panel-actions {
  display: flex;
  align-items: center;
  gap: var(--ds-space-2);
  flex-wrap: wrap;
}
.ds-panel-body {
  min-inline-size: 0;
  padding: var(--ds-space-4);
}
.ds-panel-body--flush { padding: var(--ds-space-0); }
.ds-panel-body--scroll {
  overflow: auto;
  overscroll-behavior: contain;
}
.ds-panel-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--ds-space-2);
  flex-wrap: wrap;
  padding: var(--ds-space-3) var(--ds-space-4);
  border-block-start: 1px solid var(--ds-color-border-default);
}

/* ============================================================================
   3. SECTION HEADER — a labelled division inside a panel or page body
   ========================================================================== */
.ds-section {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-3);
  min-inline-size: 0;
}
.ds-section-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--ds-space-3);
  min-inline-size: 0;
}
.ds-section-title {
  margin: 0;
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-bold);
  line-height: var(--ds-leading-tight);
  letter-spacing: var(--ds-tracking-wide);
  text-transform: uppercase;
  color: var(--ds-color-text-secondary);
}
.ds-section-description {
  margin: 0;
  font-size: var(--ds-text-sm);
  color: var(--ds-color-text-secondary);
}
.ds-section-actions {
  display: flex;
  align-items: center;
  gap: var(--ds-space-2);
}

/* ============================================================================
   4. TABLE WRAPPER
   The horizontal scroll lives INSIDE the wrapper. `max-inline-size: 100%` plus
   `min-inline-size: 0` on the ancestors above stops a wide table from ever
   widening the document — the shell's <body> is overflow:hidden and the real
   scroll container is .app-main, so a table that escaped its wrapper would
   break the whole frame, not just itself.
   ========================================================================== */
.ds-table-wrap {
  inline-size: 100%;
  max-inline-size: 100%;
  min-inline-size: 0;
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-inline: contain;
  background: var(--ds-color-surface-raised);
  border: 1px solid var(--ds-color-border-default);
  border-radius: var(--ds-radius-md);
}
.ds-table-wrap--flush {
  border: 0;
  border-radius: var(--ds-radius-xs);
  background: none;
}
.ds-table {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: var(--ds-text-sm);
  color: var(--ds-color-text-primary);
}
.ds-table caption {
  padding: var(--ds-space-2) var(--ds-row-padding-inline);
  text-align: start;
  font-size: var(--ds-text-xs);
  color: var(--ds-color-text-secondary);
}
.ds-table th,
.ds-table td {
  padding-block: var(--ds-row-padding-block);
  padding-inline: var(--ds-row-padding-inline);
  text-align: start;
  vertical-align: middle;
}
.ds-table thead th {
  position: sticky;
  inset-block-start: 0;
  z-index: 1;
  background: var(--ds-color-surface-hover);
  border-block-end: 1px solid var(--ds-color-border-default);
  font-size: var(--ds-text-xs);
  font-weight: var(--ds-weight-bold);
  letter-spacing: var(--ds-tracking-wide);
  color: var(--ds-color-text-secondary);
  white-space: nowrap;
}
.ds-table tbody td { border-block-end: 1px solid var(--ds-color-border-subtle); }
.ds-table tbody tr:last-child td { border-block-end: 0; }
.ds-table tbody tr:hover { background: var(--ds-color-surface-hover); }
/* Numerals stay LTR and tabular inside an RTL table. */
.ds-table .ds-num,
.ds-table th.ds-num,
.ds-table td.ds-num {
  text-align: end;
  font-family: var(--ds-font-numeric);
  font-variant-numeric: tabular-nums;
  direction: ltr;
  unicode-bidi: isolate;
}
.ds-table-sort {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-1);
  background: none;
  border: 0;
  padding: var(--ds-space-0);
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-align: start;
}

/* ============================================================================
   5. FORM ROW
   ========================================================================== */
.ds-form {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-4);
  min-inline-size: 0;
}
.ds-form-row {
  display: flex;
  flex-direction: column;
  gap: var(--ds-space-1);
  min-inline-size: 0;
}
.ds-form-row--inline {
  flex-direction: row;
  align-items: center;
  gap: var(--ds-space-3);
  flex-wrap: wrap;
}
.ds-form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--ds-space-3);
  min-inline-size: 0;
}
.ds-form-label {
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-semibold);
  color: var(--ds-color-text-secondary);
}
.ds-form-required { color: var(--ds-color-status-danger-fg); }
.ds-form-control {
  inline-size: 100%;
  min-inline-size: 0;
  block-size: var(--ds-control-height);
  min-block-size: var(--ds-tap-target-min);
  padding-inline: var(--ds-space-3);
  background: var(--ds-color-surface-raised);
  border: 1px solid var(--ds-color-border-strong);
  border-radius: var(--ds-radius-sm);
  color: var(--ds-color-text-primary);
  font-family: inherit;
  font-size: var(--ds-text-md);
  transition: border-color var(--ds-duration-fast) var(--ds-ease-standard),
              box-shadow var(--ds-duration-fast) var(--ds-ease-standard);
}
textarea.ds-form-control {
  block-size: auto;
  min-block-size: calc(var(--ds-control-height) * 2.2);
  padding-block: var(--ds-space-3);
  resize: vertical;
  line-height: var(--ds-leading-normal);
}
.ds-form-control::placeholder { color: var(--ds-color-text-muted); }
.ds-form-control:hover { border-color: var(--ds-color-text-muted); }
.ds-form-control:disabled {
  background: var(--ds-color-surface-sunken);
  color: var(--ds-color-text-secondary);
  cursor: not-allowed;
}
.ds-form-control[aria-invalid="true"] { border-color: var(--ds-color-status-danger-border); }
.ds-form-hint {
  font-size: var(--ds-text-xs);
  color: var(--ds-color-text-secondary);
}
/* An error is never color-alone: the field is aria-invalid and the message is
   text, read by assistive tech through aria-describedby. */
.ds-form-error {
  display: flex;
  align-items: center;
  gap: var(--ds-space-1);
  font-size: var(--ds-text-xs);
  font-weight: var(--ds-weight-semibold);
  color: var(--ds-color-text-primary);
}
.ds-form-error::before {
  content: "";
  inline-size: var(--ds-space-2);
  block-size: var(--ds-space-2);
  border-radius: var(--ds-radius-pill);
  background: var(--ds-color-status-danger-fg);
  flex-shrink: 0;
}

/* ============================================================================
   6. BUTTON — needed by the command bar, modal footer and state blocks
   ========================================================================== */
.ds-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ds-space-2);
  block-size: var(--ds-control-height);
  min-block-size: var(--ds-tap-target-min);
  padding-inline: var(--ds-space-4);
  background: var(--ds-color-surface-hover);
  color: var(--ds-color-text-primary);
  border: 1px solid var(--ds-color-border-strong);
  border-radius: var(--ds-radius-sm);
  font-family: inherit;
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-semibold);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--ds-duration-fast) var(--ds-ease-standard),
              border-color var(--ds-duration-fast) var(--ds-ease-standard);
}
.ds-button:hover { background: var(--ds-color-surface-overlay); }
.ds-button:disabled { opacity: .5; cursor: not-allowed; }
/* The primary action fills with accent-STRONG, not accent: the label clears
   4.5:1 there in every theme, and only 4.12:1 (2.81:1 in hc) on accent. */
.ds-button--primary {
  background: var(--ds-color-accent-strong);
  color: var(--ds-color-text-on-accent);
  border-color: transparent;
}
.ds-button--primary:hover { background: var(--ds-color-accent-hover); }
.ds-button--ghost {
  background: none;
  border-color: transparent;
}
.ds-button--ghost:hover { background: var(--ds-color-surface-hover); }
.ds-button--danger {
  background: var(--ds-color-status-danger-surface);
  color: var(--ds-color-text-primary);
  border-color: var(--ds-color-status-danger-border);
}
.ds-button--sm {
  block-size: var(--ds-control-height-sm);
  min-block-size: var(--ds-control-height-sm);
  padding-inline: var(--ds-space-3);
  font-size: var(--ds-text-xs);
}
.ds-button--lg {
  block-size: var(--ds-control-height-lg);
  padding-inline: var(--ds-space-6);
  font-size: var(--ds-text-md);
}

/* ============================================================================
   7. STATUS CHIP
   One class per state the Activity Workspaces actually produce (the vocabulary
   is enumerated in js/design/tokens.js STATUS_STATES, which is read straight
   from js/workspace/*.js). The LABEL is always --ds-color-text-primary on the
   tone surface — >= 10.4:1 in every theme — while the tone shows in the dot and
   the border. Status is therefore never color-alone: there is always a word.
   ========================================================================== */
.ds-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--ds-space-1);
  max-inline-size: 100%;
  padding-block: 2px;
  padding-inline: var(--ds-space-2);
  background: var(--ds-color-status-neutral-surface);
  border: 1px solid var(--ds-color-status-neutral-border);
  border-radius: var(--ds-radius-pill);
  color: var(--ds-color-text-primary);
  font-size: var(--ds-text-xs);
  font-weight: var(--ds-weight-bold);
  line-height: var(--ds-leading-snug);
  white-space: nowrap;
}
.ds-chip-dot {
  inline-size: 6px;
  block-size: 6px;
  border-radius: var(--ds-radius-pill);
  background: var(--ds-color-status-neutral-fg);
  flex-shrink: 0;
}
.ds-chip-label {
  overflow: hidden;
  text-overflow: ellipsis;
}

/* --- tones -------------------------------------------------------------- */
.ds-chip--success { background: var(--ds-color-status-success-surface); border-color: var(--ds-color-status-success-border); }
.ds-chip--success .ds-chip-dot { background: var(--ds-color-status-success-fg); }
.ds-chip--warning { background: var(--ds-color-status-warning-surface); border-color: var(--ds-color-status-warning-border); }
.ds-chip--warning .ds-chip-dot { background: var(--ds-color-status-warning-fg); }
.ds-chip--danger { background: var(--ds-color-status-danger-surface); border-color: var(--ds-color-status-danger-border); }
.ds-chip--danger .ds-chip-dot { background: var(--ds-color-status-danger-fg); }
.ds-chip--info { background: var(--ds-color-status-info-surface); border-color: var(--ds-color-status-info-border); }
.ds-chip--info .ds-chip-dot { background: var(--ds-color-status-info-fg); }
.ds-chip--neutral { background: var(--ds-color-status-neutral-surface); border-color: var(--ds-color-status-neutral-border); }
.ds-chip--neutral .ds-chip-dot { background: var(--ds-color-status-neutral-fg); }

/* --- WOA states -> tone ------------------------------------------------- */
/* success: workspaceState ready · coverage complete · freshness fresh ·
   readiness PASS · link CANONICAL · command outcome ok */
.ds-chip--ready,
.ds-chip--complete,
.ds-chip--fresh,
.ds-chip--pass,
.ds-chip--canonical,
.ds-chip--ok {
  background: var(--ds-color-status-success-surface);
  border-color: var(--ds-color-status-success-border);
}
.ds-chip--ready .ds-chip-dot,
.ds-chip--complete .ds-chip-dot,
.ds-chip--fresh .ds-chip-dot,
.ds-chip--pass .ds-chip-dot,
.ds-chip--canonical .ds-chip-dot,
.ds-chip--ok .ds-chip-dot { background: var(--ds-color-status-success-fg); }

/* warning: workspaceState degraded/stale · coverage windowed ·
   command outcome conflict/validation/network/stale_item */
.ds-chip--degraded,
.ds-chip--stale,
.ds-chip--windowed,
.ds-chip--conflict,
.ds-chip--validation,
.ds-chip--network,
.ds-chip--stale-item {
  background: var(--ds-color-status-warning-surface);
  border-color: var(--ds-color-status-warning-border);
}
.ds-chip--degraded .ds-chip-dot,
.ds-chip--stale .ds-chip-dot,
.ds-chip--windowed .ds-chip-dot,
.ds-chip--conflict .ds-chip-dot,
.ds-chip--validation .ds-chip-dot,
.ds-chip--network .ds-chip-dot,
.ds-chip--stale-item .ds-chip-dot { background: var(--ds-color-status-warning-fg); }

/* danger: workspaceState error/unavailable/unauthorized/not_found ·
   readiness BLOCKED · link MISSING · command outcome forbidden */
.ds-chip--error,
.ds-chip--unavailable,
.ds-chip--unauthorized,
.ds-chip--not-found,
.ds-chip--blocked,
.ds-chip--missing,
.ds-chip--forbidden {
  background: var(--ds-color-status-danger-surface);
  border-color: var(--ds-color-status-danger-border);
}
.ds-chip--error .ds-chip-dot,
.ds-chip--unavailable .ds-chip-dot,
.ds-chip--unauthorized .ds-chip-dot,
.ds-chip--not-found .ds-chip-dot,
.ds-chip--blocked .ds-chip-dot,
.ds-chip--missing .ds-chip-dot,
.ds-chip--forbidden .ds-chip-dot { background: var(--ds-color-status-danger-fg); }

/* neutral: workspaceState loading/empty · coverage & freshness unknown ·
   link UNKNOWN_LINK/NOT_APPLICABLE/HIDDEN_BY_SCOPE · outcome not_confirmed */
.ds-chip--loading,
.ds-chip--empty,
.ds-chip--unknown,
.ds-chip--unknown-link,
.ds-chip--not-applicable,
.ds-chip--hidden-by-scope,
.ds-chip--not-confirmed {
  background: var(--ds-color-status-neutral-surface);
  border-color: var(--ds-color-status-neutral-border);
}
.ds-chip--loading .ds-chip-dot,
.ds-chip--empty .ds-chip-dot,
.ds-chip--unknown .ds-chip-dot,
.ds-chip--unknown-link .ds-chip-dot,
.ds-chip--not-applicable .ds-chip-dot,
.ds-chip--hidden-by-scope .ds-chip-dot,
.ds-chip--not-confirmed .ds-chip-dot { background: var(--ds-color-status-neutral-fg); }

/* A state that is not merely unknown but UNVERIFIABLE (the source could not be
   reached) also gets a dashed edge, so "we cannot confirm this" never reads as
   a clean result at a glance. Mirrors .qw-strip[data-readiness="UNAVAILABLE"]. */
.ds-chip--unavailable,
.ds-chip--hidden-by-scope { border-style: dashed; }

/* ============================================================================
   8. COMMAND BAR — the sticky action strip above a workspace or console
   ========================================================================== */
.ds-commandbar {
  position: sticky;
  inset-block-start: 0;
  z-index: var(--ds-z-sticky);
  display: flex;
  align-items: center;
  gap: var(--ds-space-2);
  flex-wrap: wrap;
  min-inline-size: 0;
  padding: var(--ds-space-2) var(--ds-space-3);
  background: var(--ds-color-surface-raised);
  border: 1px solid var(--ds-color-border-default);
  border-radius: var(--ds-radius-md);
}
.ds-commandbar-title {
  min-inline-size: 0;
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--ds-text-md);
  font-weight: var(--ds-weight-bold);
  color: var(--ds-color-text-primary);
}
.ds-commandbar-meta {
  display: flex;
  align-items: center;
  gap: var(--ds-space-2);
  flex-wrap: wrap;
  font-size: var(--ds-text-xs);
  color: var(--ds-color-text-secondary);
}
.ds-commandbar-actions {
  display: flex;
  align-items: center;
  gap: var(--ds-space-2);
  flex-wrap: wrap;
}
.ds-commandbar-separator {
  inline-size: 1px;
  block-size: var(--ds-space-5);
  background: var(--ds-color-border-default);
  flex-shrink: 0;
}

/* ============================================================================
   9. TOAST — an inverted ink chip, bottom-centre, above the modal layer
   ========================================================================== */
.ds-toast-host {
  position: fixed;
  inset-block-end: var(--ds-space-5);
  inset-inline: 0;
  z-index: var(--ds-z-toast);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--ds-space-2);
  pointer-events: none;
}
.ds-toast {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: var(--ds-space-3);
  min-inline-size: 280px;
  max-inline-size: min(440px, 92vw);
  padding: var(--ds-space-3) var(--ds-space-4);
  background: var(--ds-color-surface-inverse);
  color: var(--ds-color-text-inverse);
  border-radius: var(--ds-radius-md);
  border-inline-start: 3px solid transparent;
  box-shadow: var(--ds-elevation-overlay);
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-semibold);
  animation: ds-pop var(--ds-duration-base) var(--ds-ease-emphasized);
}
.ds-toast-message { flex: 1 1 auto; min-inline-size: 0; }
.ds-toast--success { border-inline-start-color: var(--ds-color-status-success-fg); }
.ds-toast--warning { border-inline-start-color: var(--ds-color-status-warning-fg); }
.ds-toast--danger { border-inline-start-color: var(--ds-color-status-danger-fg); }
.ds-toast--info { border-inline-start-color: var(--ds-color-status-info-fg); }

/* ============================================================================
   10. MODAL
   ========================================================================== */
.ds-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--ds-z-overlay);
  display: grid;
  place-items: center;
  padding: var(--ds-space-4);
  background: var(--ds-color-scrim);
  animation: ds-fade-in var(--ds-duration-base) var(--ds-ease-standard);
}
.ds-modal {
  position: relative;
  z-index: var(--ds-z-modal);
  display: flex;
  flex-direction: column;
  inline-size: min(560px, 96vw);
  max-block-size: 90vh;
  min-inline-size: 0;
  background: var(--ds-color-surface-overlay);
  border: 1px solid var(--ds-color-border-strong);
  border-radius: var(--ds-radius-lg);
  box-shadow: var(--ds-elevation-3);
  animation: ds-pop var(--ds-duration-base) var(--ds-ease-emphasized);
}
.ds-modal--lg { inline-size: min(860px, 96vw); }
.ds-modal--xl { inline-size: min(1100px, 96vw); }
.ds-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ds-space-3);
  padding: var(--ds-space-4) var(--ds-space-5);
  border-block-end: 1px solid var(--ds-color-border-default);
}
.ds-modal-title {
  margin: 0;
  font-size: var(--ds-text-lg);
  font-weight: var(--ds-weight-bold);
  color: var(--ds-color-text-primary);
}
.ds-modal-body {
  padding: var(--ds-space-5);
  overflow: auto;
  overscroll-behavior: contain;
  min-inline-size: 0;
}
.ds-modal-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--ds-space-2);
  flex-wrap: wrap;
  padding: var(--ds-space-3) var(--ds-space-5);
  border-block-start: 1px solid var(--ds-color-border-default);
}
.ds-modal-close {
  display: grid;
  place-items: center;
  inline-size: var(--ds-control-height-sm);
  block-size: var(--ds-control-height-sm);
  background: none;
  border: 0;
  border-radius: var(--ds-radius-sm);
  color: var(--ds-color-text-secondary);
  cursor: pointer;
  flex-shrink: 0;
}
.ds-modal-close:hover {
  background: var(--ds-color-surface-hover);
  color: var(--ds-color-text-primary);
}

/* ============================================================================
   11. STANDARD STATE BLOCKS
   The nine states in js/workspace/workspaceState.js STANDARD_STATES render as
   one of two shapes:
     .ds-state  — REPLACES the pane content  (loading, empty, error,
                  unauthorized, not_found, unavailable)
     .ds-banner — sits ABOVE content the pane still shows  (degraded, stale)
   The split is deliberate and is the GATE-06 contract: a degraded or stale pane
   must keep showing what it has, with the caveat visible, instead of rendering
   a clean "empty day" that reads as a confirmed zero.
   ========================================================================== */
.ds-state {
  display: grid;
  place-items: center;
  gap: var(--ds-space-3);
  padding: var(--ds-space-10) var(--ds-space-6);
  text-align: center;
  min-inline-size: 0;
  color: var(--ds-color-text-secondary);
}
.ds-state-icon {
  display: grid;
  place-items: center;
  inline-size: var(--ds-space-12);
  block-size: var(--ds-space-12);
  border-radius: var(--ds-radius-pill);
  background: var(--ds-color-status-neutral-surface);
  color: var(--ds-color-status-neutral-fg);
}
.ds-state-title {
  margin: 0;
  font-size: var(--ds-text-lg);
  font-weight: var(--ds-weight-bold);
  color: var(--ds-color-text-primary);
}
/* Body copy in a state block is `secondary`, never `muted`: muted only clears
   3:1 and this is a sentence the operator has to read. */
.ds-state-message {
  margin: 0;
  max-inline-size: 46ch;
  font-size: var(--ds-text-sm);
  color: var(--ds-color-text-secondary);
}
.ds-state-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--ds-space-2);
  flex-wrap: wrap;
}
.ds-state--loading .ds-state-icon { color: var(--ds-color-accent); }
.ds-state--empty .ds-state-icon {
  background: var(--ds-color-status-neutral-surface);
  color: var(--ds-color-status-neutral-fg);
}
.ds-state--error .ds-state-icon {
  background: var(--ds-color-status-danger-surface);
  color: var(--ds-color-status-danger-fg);
}
.ds-state--degraded .ds-state-icon {
  background: var(--ds-color-status-warning-surface);
  color: var(--ds-color-status-warning-fg);
}
/* An unverifiable pane is edged, so it cannot be mistaken for a confirmed empty. */
.ds-state--unavailable {
  border: 1px dashed var(--ds-color-status-danger-border);
  border-radius: var(--ds-radius-md);
}
.ds-state--unavailable .ds-state-icon {
  background: var(--ds-color-status-danger-surface);
  color: var(--ds-color-status-danger-fg);
}

.ds-spinner {
  inline-size: var(--ds-space-8);
  block-size: var(--ds-space-8);
  border: 3px solid var(--ds-color-border-default);
  border-block-start-color: var(--ds-color-accent);
  border-radius: var(--ds-radius-pill);
  animation: ds-spin var(--ds-duration-slow) linear infinite;
}
.ds-skeleton {
  background: var(--ds-color-surface-hover);
  border-radius: var(--ds-radius-xs);
  animation: ds-pulse 1.3s var(--ds-ease-standard) infinite;
}

.ds-banner {
  display: flex;
  align-items: flex-start;
  gap: var(--ds-space-2);
  flex-wrap: wrap;
  min-inline-size: 0;
  padding: var(--ds-space-2) var(--ds-space-3);
  margin-block-end: var(--ds-space-3);
  background: var(--ds-color-status-neutral-surface);
  border-radius: var(--ds-radius-sm);
  border-inline-start: 3px solid var(--ds-color-status-neutral-border);
  color: var(--ds-color-text-primary);
  font-size: var(--ds-text-sm);
  font-weight: var(--ds-weight-semibold);
  line-height: var(--ds-leading-snug);
}
.ds-banner-message { flex: 1 1 auto; min-inline-size: 0; }
.ds-banner--degraded,
.ds-banner--stale,
.ds-banner--warning {
  background: var(--ds-color-status-warning-surface);
  border-inline-start-color: var(--ds-color-status-warning-border);
}
.ds-banner--danger,
.ds-banner--error {
  background: var(--ds-color-status-danger-surface);
  border-inline-start-color: var(--ds-color-status-danger-border);
}
.ds-banner--info {
  background: var(--ds-color-status-info-surface);
  border-inline-start-color: var(--ds-color-status-info-border);
}
.ds-banner--success {
  background: var(--ds-color-status-success-surface);
  border-inline-start-color: var(--ds-color-status-success-border);
}

/* ============================================================================
   12. UTILITIES
   ========================================================================== */
.ds-visually-hidden {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
/* Reveals on focus — for skip links and "jump to" affordances. */
.ds-visually-hidden-focusable:not(:focus):not(:focus-within) {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.ds-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-inline-size: 0;
}
.ds-num {
  font-family: var(--ds-font-numeric);
  font-variant-numeric: tabular-nums;
  direction: ltr;
  unicode-bidi: isolate;
}

/* ============================================================================
   13. FOCUS
   Every interactive primitive shows the same two-stop ring on keyboard focus.
   css/base.css applies a global `:focus-visible { outline:none; box-shadow:
   var(--ring); border-radius: var(--r-xs) }` — the border-radius in that rule
   would square off a pill or a card corner on focus, so each group below
   re-asserts its own radius. Pointer interaction stays quiet (:focus-visible).
   ========================================================================== */
.ds-button:focus-visible,
.ds-form-control:focus-visible,
.ds-modal-close:focus-visible,
.ds-table-sort:focus-visible,
.ds-commandbar-actions > :focus-visible,
.ds-state-actions > :focus-visible,
.ds-visually-hidden-focusable:focus-visible {
  outline: none;
  box-shadow: var(--ds-focus-ring);
  border-radius: var(--ds-radius-sm);
}
.ds-chip:focus-visible,
.ds-chip-label:focus-visible {
  outline: none;
  box-shadow: var(--ds-focus-ring);
  border-radius: var(--ds-radius-pill);
}
/* Windows High Contrast / forced-colors drops author colors, box-shadow
   included — fall back to a real outline so the ring survives. */
@media (forced-colors: active) {
  .ds-button:focus-visible,
  .ds-form-control:focus-visible,
  .ds-modal-close:focus-visible,
  .ds-table-sort:focus-visible,
  .ds-chip:focus-visible {
    outline: var(--ds-focus-ring-width) solid CanvasText;
    outline-offset: var(--ds-focus-ring-offset);
  }
}

/* ============================================================================
   14. KEYFRAMES — all `ds-`-prefixed. @keyframes names are GLOBAL: an
   unprefixed `pop`/`spin`/`fade-in` here would silently replace the shell's
   own animations for every existing screen.
   ========================================================================== */
@keyframes ds-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes ds-pop { from { opacity: 0; transform: scale(.96); } to { opacity: 1; transform: scale(1); } }
@keyframes ds-spin { to { transform: rotate(360deg); } }
@keyframes ds-pulse { 50% { opacity: .45; } }

@media (prefers-reduced-motion: reduce) {
  .ds-spinner,
  .ds-skeleton,
  .ds-toast,
  .ds-modal,
  .ds-modal-overlay { animation: none; }
}

/* ============================================================================
   15. NARROW VIEWPORTS
   ========================================================================== */
@media (max-width: 760px) {
  .ds-page {
    padding-inline: var(--ds-space-4);
    padding-block: var(--ds-space-4) var(--ds-space-8);
  }
  .ds-page-actions { inline-size: 100%; }
  .ds-form-grid { grid-template-columns: 1fr; }
  .ds-toast { min-inline-size: 0; inline-size: min(440px, 92vw); }
}
