/* ============================================================
   STELLARIA DESIGN SYSTEM — COMPONENTS
   Reusable, project-agnostic component classes.
   Depends on: tokens.css, base.css
   ============================================================ */


/* ── BUTTONS ─────────────────────────────────────────────────
   Six semantic variants. Primary owns the main path; secondary
   is the institutional second-action chip; ghost is a transparent
   tertiary; danger is destructive only; quiet is a text-only link
   button; icon is a square 36×36 for icon-only triggers.
   Disabled state is opacity + cursor — never a different fill. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: var(--f-display);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.005em;
  padding: 12px 22px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--c-white);
  cursor: pointer;
  white-space: nowrap;
  /* Sharp by default. tokens.css ships --r-1 = 2px; we use 0
     so buttons read fully institutional. */
  border-radius: 0;
  transition: background var(--dur-fast) var(--ease-tactical),
              border-color var(--dur-fast) var(--ease-tactical),
              color var(--dur-fast) var(--ease-tactical),
              opacity var(--dur-fast) var(--ease-tactical);
}
.btn:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
/* Buttons rendered as <a class="btn"> — the link cascade in base.css
   can otherwise repaint hover/active text in --c-accent-hi, which is
   the primary button's background. Restate inheritance here. */
a.btn:hover,
a.btn:active,
a.btn:focus { color: inherit; }

/* Primary — filled accent. The single most-emphasised action. */
.btn--primary {
  background: var(--c-accent);
  color: var(--c-white);
}
.btn--primary:hover  { background: var(--c-accent-hi); color: var(--c-white); }
.btn--primary:active { background: var(--c-accent-lo); color: var(--c-white); }

/* Secondary — surface-tinted with a hairline. For "the other action"
   beside a primary (e.g. Cancel beside Submit). Use this rather than
   ghost when the action sits inside a panel; ghost reads as floating. */
.btn--secondary {
  background: var(--c-surface);
  border-color: var(--c-border);
  color: var(--c-white);
}
.btn--secondary:hover {
  background: var(--c-surface-2);
  border-color: var(--c-border-h);
}
.btn--secondary:active {
  background: var(--c-surface-2);
  border-color: var(--c-white);
}

/* Ghost — transparent + hairline. The "no commitment" tertiary
   action; reads as floating, used in toolbars and over imagery. */
.btn--ghost {
  border-color: color-mix(in oklch, var(--c-white) 22%, transparent);
  color: color-mix(in oklch, var(--c-white) 85%, transparent);
}
.btn--ghost:hover {
  border-color: var(--c-white);
  color: var(--c-white);
}
.btn--ghost:active {
  border-color: var(--c-white);
  color: var(--c-white);
  background: color-mix(in oklch, var(--c-white) 6%, transparent);
}

/* Danger — destructive actions only. Delete, revoke, terminate.
   Never use for warnings, errors, or non-destructive emphasis. */
.btn--danger {
  background: transparent;
  border-color: color-mix(in oklch, var(--c-danger) 60%, transparent);
  color: var(--c-danger);
}
.btn--danger:hover {
  background: color-mix(in oklch, var(--c-danger) 12%, transparent);
  border-color: var(--c-danger);
  color: var(--c-white);
}
.btn--danger:active {
  background: var(--c-danger);
  border-color: var(--c-danger);
  color: var(--c-white);
}
/* Filled-danger modifier for the rare confirmation dialog where the
   destructive action must dominate (e.g. "Permanently delete"). */
.btn--danger.btn--filled {
  background: var(--c-danger);
  color: var(--c-white);
  border-color: var(--c-danger);
}

/* Quiet — text-only with no chrome. The lightest possible action,
   for "Learn more", "View all", inline edit triggers. Pair with an
   arrow glyph to disambiguate from prose. */
.btn--quiet {
  padding: 6px 0;
  background: transparent;
  border: none;
  color: var(--c-fg-dim);
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.10em;
  text-transform: uppercase;
}
.btn--quiet:hover  { color: var(--c-white); }
.btn--quiet:active { color: var(--c-accent-hi); }

/* Icon — square 36×36 for icon-only triggers. Hairline border,
   icon inherits currentColor so it follows the button's text color.
   Always pair with aria-label. */
.btn--icon {
  width: 36px;
  height: 36px;
  padding: 0;
  border-color: var(--c-border);
  color: var(--c-fg-dim);
}
.btn--icon:hover  { border-color: var(--c-border-h); color: var(--c-white); }
.btn--icon:active { border-color: var(--c-white); color: var(--c-white); }
.btn--icon svg { width: 16px; height: 16px; display: block; }

/* Size modifiers — compose with any variant. */
.btn--large {
  font-size: 15px;
  padding: 16px 28px;
}
.btn--sm {
  font-size: 12px;
  padding: 7px 14px;
  letter-spacing: 0.02em;
}

/* Disabled — opacity + cursor on every variant. Never a different
   fill colour, which would mislead the eye into reading the disabled
   primary as a secondary in some other state. */
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.40;
  cursor: not-allowed;
  pointer-events: none;
}


/* ── NAV ────────────────────────────────────────────────────
   Top bar with brand mark on the left, links centered, CTA right.
   60px tall, transparent background, sticky-ready. */

.nav {
  position: sticky;
  top: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  height: 60px;
  padding: 0 var(--gutter);
  border-bottom: 1px solid transparent;
  transition: background var(--dur-mid) var(--ease-tactical),
              border-color var(--dur-mid) var(--ease-tactical);
}
.nav.is-scrolled {
  background: rgba(10, 13, 18, 0.72);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom-color: rgba(255, 255, 255, 0.06);
}
.nav__brand {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}
.nav__logo {
  height: 32px;
  width: auto;
  display: block;
}
.nav__links {
  display: flex;
  gap: var(--sp-6);
  list-style: none;
  margin: 0 auto;
}
.nav__links a {
  font-family: var(--f-display);
  font-size: 14px;
  font-weight: 500;
  color: var(--c-fg-dim);
  transition: color var(--dur-fast) var(--ease-tactical);
}
.nav__links a:hover { color: var(--c-white); }
.nav__links a[aria-current="page"] { color: var(--c-accent); }

.nav__cta {
  font-family: var(--f-display);
  font-size: 13px;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.85);
  border: 1px solid rgba(255, 255, 255, 0.22);
  padding: 8px 16px;
  transition: border-color var(--dur-fast), color var(--dur-fast);
}
.nav__cta:hover { border-color: var(--c-white); color: var(--c-white); }


/* ── EYEBROWS, CHIPS, STAMPS ────────────────────────────────
   Three small label patterns that signal section, product, or
   status. Each uses mono type, uppercase, tracked out. */

/* Tactical mono label (status, telemetry) */
.label {
  font-family: var(--f-mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: color-mix(in oklch, var(--c-white) 55%, transparent);
}
.label--accent { color: var(--c-accent); }

/* Chip — accent-bordered label for product/engine names */
.chip {
  font-family: var(--f-mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--c-accent);
  padding: 5px 10px;
  border: 1px solid color-mix(in oklch, var(--c-accent) 50%, transparent);
  background: color-mix(in oklch, var(--c-accent) 8%, transparent);
  display: inline-block;
}

/* Stamp — tactical orange overlay for image flags or detections */
.stamp {
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--c-stamp);
  padding: 4px 8px;
  background: rgba(10, 13, 18, 0.72);
  border: 1px solid rgba(255, 106, 36, 0.7);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  display: inline-block;
}


/* ── IMAGE SLOT ─────────────────────────────────────────────
   Placeholder pattern for any imagery. Diagonal stripes + corner
   brackets + an optional inline tag. Replaces itself when a real
   image loads (add `.is-loaded`). Use with object-fit cover when
   wrapping a real <img>. */

.img-slot {
  position: relative;
  width: 100%;
  background:
    repeating-linear-gradient(135deg, transparent 0 18px, color-mix(in oklch, var(--c-white) 3%, transparent) 18px 19px),
    linear-gradient(180deg, #0a0f1a 0%, #06090f 100%);
  border: 1px solid color-mix(in oklch, var(--c-white) 8%, transparent);
  overflow: hidden;
  min-height: 220px;
}
.img-slot--16x9 { aspect-ratio: 16 / 9; }
.img-slot--21x9 { aspect-ratio: 21 / 9; }
.img-slot--4x5 { aspect-ratio: 4 / 5; }
.img-slot--square { aspect-ratio: 1 / 1; }

/* Corner brackets */
.img-slot::before, .img-slot::after {
  content: '';
  position: absolute;
  width: 18px;
  height: 18px;
  border: 1px solid color-mix(in oklch, var(--c-white) 25%, transparent);
}
.img-slot::before { top: 12px; left: 12px;  border-right: none; border-bottom: none; }
.img-slot::after  { top: 12px; right: 12px; border-left:  none; border-bottom: none; }

/* Inline tag describing the slot */
.img-slot__tag {
  position: absolute;
  bottom: 14px;
  left: 14px;
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: color-mix(in oklch, var(--c-white) 55%, transparent);
}

/* Fitted media inside the slot (img or video) */
.img-slot__media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* When a real asset loads, hide the placeholder chrome */
.img-slot.is-loaded::before,
.img-slot.is-loaded::after,
.img-slot.is-loaded .img-slot__tag { display: none; }


/* ── STATS ──────────────────────────────────────────────────
   Large tabular numeral, mono label, optional accent delta. */

.stat {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 20px 24px;
  background: var(--c-surface);
  border: 1px solid var(--c-border);
}
.stat__num {
  font-family: var(--f-display);
  font-size: clamp(36px, 4vw, 56px);
  font-weight: 600;
  line-height: 0.92;
  letter-spacing: -0.03em;
  color: var(--c-white);
  font-variant-numeric: tabular-nums;
}
.stat__num small {
  font-size: 0.5em;
  color: color-mix(in oklch, var(--c-white) 60%, transparent);
  font-weight: 500;
  margin-left: 4px;
}
.stat__label {
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: color-mix(in oklch, var(--c-white) 50%, transparent);
}
.stat__delta {
  font-family: var(--f-mono);
  font-size: 11px;
  color: color-mix(in oklch, var(--c-accent) 90%, transparent);
}


/* ── SPEC STRIP ─────────────────────────────────────────────
   Horizontal grid of mono key/value cells. 4 across desktop,
   2 across tablet, 1 across mobile. Use to ground a hero with
   hard data. */

.spec-strip {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  background: var(--c-border);
  border: 1px solid var(--c-border);
}
@media (max-width: 720px) { .spec-strip { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 420px) { .spec-strip { grid-template-columns: 1fr; } }

.spec-strip > div {
  background: var(--c-bg);
  padding: 18px 22px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
  /* Centre label and value within each cell. The spec strip reads
     as a structured row of facts rather than a left-aligned form,
     so the centred axis lets the eye sweep horizontally without
     re-anchoring on every column. */
  text-align: center;
  align-items: center;
}
.spec-strip dt {
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: color-mix(in oklch, var(--c-white) 45%, transparent);
}
.spec-strip dd {
  margin: 0;
  font-family: var(--f-display);
  font-size: 16px;
  font-weight: 500;
  color: var(--c-white);
  letter-spacing: -0.005em;
}


/* ── SCALE LADDER ────────────────────────────────────────────
   Compact data viz for showing progressive values. Each row is
   label | bar (proportional fill) | value. The featured row gets
   a glow treatment to mark the maximum. */

.ladder {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: clamp(20px, 2.5vw, 28px);
  border: 1px solid var(--c-border);
  background:
    linear-gradient(135deg, color-mix(in oklch, var(--c-accent) 4%, transparent), transparent 70%),
    var(--c-bg);
  position: relative;
}
.ladder::before, .ladder::after {
  content: '';
  position: absolute;
  width: 14px;
  height: 14px;
  border: 1px solid color-mix(in oklch, var(--c-white) 25%, transparent);
}
.ladder::before { top: 8px;  left: 8px;  border-right: none; border-bottom: none; }
.ladder::after  { top: 8px;  right: 8px; border-left:  none; border-bottom: none; }

.ladder__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: color-mix(in oklch, var(--c-white) 50%, transparent);
  padding-bottom: 10px;
  margin-bottom: 6px;
  border-bottom: 1px solid color-mix(in oklch, var(--c-white) 8%, transparent);
}
.ladder__head b { color: var(--c-white); font-weight: 500; }

.ladder-row {
  display: grid;
  grid-template-columns: 44px 1fr 60px;
  gap: 14px;
  align-items: center;
  font-family: var(--f-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
}
.ladder-row__scale {
  color: var(--c-white);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}
.ladder-row__bar {
  position: relative;
  height: 6px;
  background: color-mix(in oklch, var(--c-white) 5%, transparent);
  overflow: hidden;
}
.ladder-row__bar::after {
  content: '';
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--w, 50%);
  background: var(--c-accent);
}
.ladder-row__value {
  color: color-mix(in oklch, var(--c-white) 65%, transparent);
  text-align: right;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-size: 10px;
}
.ladder-row--featured .ladder-row__scale { color: var(--c-accent); }
.ladder-row--featured .ladder-row__bar::after {
  background: linear-gradient(90deg, var(--c-accent), color-mix(in oklch, var(--c-accent) 40%, white));
  box-shadow: 0 0 12px color-mix(in oklch, var(--c-accent) 50%, transparent);
}


/* ── PIPELINE ───────────────────────────────────────────────
   3+ connected steps with dot markers and a horizontal connector
   line. Use to express any sequence: tile, reconstruct, verify. */

.pipeline {
  position: relative;
  display: grid;
  /* Default 3 steps. Override per-instance with `style="--pipeline-cols: 4"`
     or pair with `.pipeline--4`. The connector line auto-spans because it
     uses `left: 4px; right: 4px`, independent of column count. */
  grid-template-columns: repeat(var(--pipeline-cols, 3), minmax(0, 1fr));
  gap: clamp(24px, 4vw, 56px);
  /* No padding-top: the connector line and the dot centres must share
     the same vertical reference (y = 7px from the pipeline's top edge,
     which is the centre of the 14×14 dot at top:0 of each step). */
}
.pipeline--4 { --pipeline-cols: 4; }
.pipeline--5 { --pipeline-cols: 5; }
.pipeline::before {
  content: '';
  position: absolute;
  top: 7px;
  left: 4px;
  right: 4px;
  height: 1px;
  background: linear-gradient(90deg,
    transparent 0,
    color-mix(in oklch, var(--c-white) 18%, transparent) 8%,
    color-mix(in oklch, var(--c-white) 18%, transparent) 92%,
    transparent 100%);
  pointer-events: none;
}
@media (max-width: 900px) {
  .pipeline { grid-template-columns: 1fr; gap: 32px; }
  .pipeline::before { display: none; }
}

.pipeline__step {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding-top: 28px;
  position: relative;
}
.pipeline__step::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--c-accent);
  outline: 4px solid var(--c-bg);
}
@media (max-width: 900px) {
  .pipeline__step { padding-top: 0; padding-left: 28px; }
  .pipeline__step::before { top: 4px; }
}
.pipeline__num {
  font-family: var(--f-mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--c-accent);
}
.pipeline__step h3 {
  font-family: var(--f-display);
  font-size: 22px;
  font-weight: 500;
  color: var(--c-white);
  margin: 0;
  letter-spacing: -0.015em;
  max-width: 22ch;
}
.pipeline__step p {
  color: var(--c-fg-dim);
  font-size: 14px;
  line-height: 1.6;
  margin: 0;
  max-width: 38ch;
}


/* ── BENTO ──────────────────────────────────────────────────
   Asymmetric tile composition. A featured tile gets the accent
   rail. Customise grid-column spans per tile. */

.bento {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 1px;
  background: var(--c-border);
  border: 1px solid var(--c-border);
}
@media (max-width: 900px) { .bento { grid-template-columns: 1fr; } }

.bento__tile {
  background: var(--c-bg);
  padding: clamp(20px, 3vw, 36px);
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-height: 200px;
  transition: background var(--dur-mid) var(--ease-standard);
}
.bento__tile:hover { background: color-mix(in oklch, var(--c-bg) 96%, white); }
.bento__tile--span-7 { grid-column: span 7; }
.bento__tile--span-5 { grid-column: span 5; }
.bento__tile--span-12 { grid-column: 1 / -1; }
@media (max-width: 900px) { .bento__tile { grid-column: 1 / -1 !important; } }

.bento__tile--featured {
  background:
    linear-gradient(135deg, color-mix(in oklch, var(--c-accent) 6%, transparent), transparent 60%),
    var(--c-bg);
  border-left: var(--border-3) solid var(--c-accent);
}


/* ── COMPARISON SLIDER ──────────────────────────────────────
   Before/after image comparison with draggable handle. Wrap two
   .compare__pic elements inside .compare__viewport so picture
   clipping happens inside while the handle can extend past the
   slider edges. Drive interaction with JS that updates the
   --before-w custom property. */

.compare {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: var(--c-bg);
  border: 1px solid var(--c-border);
  user-select: none;
  touch-action: pan-y;
  cursor: ew-resize;
}
.compare__viewport {
  position: absolute;
  inset: 0;
  overflow: hidden;
}
.compare__pic {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
}
.compare__pic--after { /* full width, sits behind */ }
.compare__pic--before {
  width: 50%;
  will-change: width;
}
.compare__handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 2px;
  background: var(--c-white);
  transform: translateX(-50%);
  pointer-events: none;
  z-index: 2;
  will-change: left;
}
.compare__grip {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--c-white);
  background: rgba(10, 13, 18, 0.85);
  box-shadow: 0 0 0 6px rgba(10, 13, 18, 0.35);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--c-white);
  font-family: var(--f-mono);
  font-size: 18px;
  pointer-events: none;
}
.compare__grip::before { content: '⇄'; }
.compare__label {
  position: absolute;
  bottom: 16px;
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--c-white);
  padding: 6px 10px;
  background: rgba(10, 13, 18, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.14);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  pointer-events: none;
  z-index: 1;
}
.compare__label--left { left: 16px; }
.compare__label--right { right: 16px; }


/* ── FOOTER ─────────────────────────────────────────────────
   Minimal footer with brand, links, copy line. */

.footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-3);
  padding: var(--sp-6) var(--gutter);
  border-top: 1px solid var(--c-border);
  font-family: var(--f-display);
  font-size: 13px;
  color: var(--c-fg-muted);
}
.footer__links {
  display: flex;
  gap: var(--sp-4);
  list-style: none;
}
.footer__links a:hover { color: var(--c-white); }


/* ── TAGS ───────────────────────────────────────────────────
   Mono chip used for source, status, role, severity, confidence,
   and generic semantic states. The base reads as a neutral outline;
   modifiers tint the text, border, and background in lockstep using
   color-mix so every chip stays on the same translucent recipe.
   Reach for the family that matches the meaning, not just the hue:
   - Source/state: --accent / --scan / --alert / --stamp / --dorm
   - Severity:     --p1 / --p2 / --p3 / --p4
   - Semantic UI:  --success / --warn / --info / --neutral
   - Domain role:  --role-*
   - Confidence:   --conf-hi / --conf-mid / --conf-lo
   Pair with a text label — never communicate state with color alone. */

.tag {
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  padding: 3px 7px;
  border: var(--border-1) solid color-mix(in oklch, var(--c-white) 18%, transparent);
  color: color-mix(in oklch, var(--c-white) 70%, transparent);
  background: transparent;
  /* inline-flex so a child .dot or icon aligns to the visual centre
     of the chip rather than its baseline. The gap replaces ad-hoc
     margin-right hacks on inline dots. */
  display: inline-flex;
  align-items: center;
  gap: 6px; /* allow-raw */
  line-height: 1;
}

/* Source / state (existing app vocabulary) */
.tag.tag--accent {
  color: var(--c-accent);
  border-color: color-mix(in oklch, var(--c-accent) 50%, transparent);
  background: var(--c-accent-wash);
}
.tag.tag--scan {
  color: var(--c-scan);
  border-color: color-mix(in oklch, var(--c-scan) 55%, transparent);
  background: color-mix(in oklch, var(--c-scan) 10%, transparent);
}
.tag.tag--alert {
  color: var(--c-alert);
  border-color: color-mix(in oklch, var(--c-alert) 55%, transparent);
  background: color-mix(in oklch, var(--c-alert) 10%, transparent);
}
.tag.tag--stamp {
  color: var(--c-stamp);
  border-color: color-mix(in oklch, var(--c-stamp) 55%, transparent);
  background: color-mix(in oklch, var(--c-stamp) 10%, transparent);
}
.tag.tag--dorm {
  color: var(--c-dorm);
  border-color: color-mix(in oklch, var(--c-dorm) 55%, transparent);
  background: color-mix(in oklch, var(--c-dorm) 10%, transparent);
}
/* "All sources agree" — pinned at the top of the source ladder. */
.tag.tag--all {
  color: var(--c-gold);
  border-color: color-mix(in oklch, var(--c-gold) 55%, transparent);
  background: color-mix(in oklch, var(--c-gold) 12%, transparent);
  font-weight: 500;
}
/* Verification states for an in-progress pipeline row. */
.tag.tag--review {
  color: var(--c-stamp);
  border-color: color-mix(in oklch, var(--c-stamp) 55%, transparent);
  background: color-mix(in oklch, var(--c-stamp) 10%, transparent);
}
.tag.tag--rejected {
  color: var(--c-alert);
  border-color: color-mix(in oklch, var(--c-alert) 55%, transparent);
  background: color-mix(in oklch, var(--c-alert) 10%, transparent);
}
.tag.tag--verified {
  color: var(--c-accent);
  border-color: color-mix(in oklch, var(--c-accent) 50%, transparent);
  background: var(--c-accent-wash);
}

/* Severity ladder (aliases over the tactical hues) */
.tag.tag--p1 {
  color: var(--c-alert);
  border-color: color-mix(in oklch, var(--c-alert) 55%, transparent);
  background: color-mix(in oklch, var(--c-alert) 10%, transparent);
}
.tag.tag--p2 {
  color: var(--c-stamp);
  border-color: color-mix(in oklch, var(--c-stamp) 55%, transparent);
  background: color-mix(in oklch, var(--c-stamp) 10%, transparent);
}
.tag.tag--p3 {
  color: var(--c-scan);
  border-color: color-mix(in oklch, var(--c-scan) 55%, transparent);
  background: color-mix(in oklch, var(--c-scan) 10%, transparent);
}
.tag.tag--p4 {
  color: var(--c-dorm);
  border-color: color-mix(in oklch, var(--c-dorm) 55%, transparent);
  background: color-mix(in oklch, var(--c-dorm) 10%, transparent);
}

/* Semantic UI states */
.tag.tag--success {
  color: var(--c-success);
  border-color: color-mix(in oklch, var(--c-success) 55%, transparent);
  background: color-mix(in oklch, var(--c-success) 10%, transparent);
}
.tag.tag--warn {
  color: var(--c-warn);
  border-color: color-mix(in oklch, var(--c-warn) 55%, transparent);
  background: color-mix(in oklch, var(--c-warn) 10%, transparent);
}
.tag.tag--info {
  color: var(--c-info);
  border-color: color-mix(in oklch, var(--c-info) 55%, transparent);
  background: color-mix(in oklch, var(--c-info) 10%, transparent);
}
.tag.tag--neutral {
  color: var(--c-neutral);
  border-color: color-mix(in oklch, var(--c-neutral) 55%, transparent);
  background: color-mix(in oklch, var(--c-neutral) 10%, transparent);
}

/* Domain role (aircraft) */
.tag.tag--role-fighter {
  color: var(--c-role-fighter);
  border-color: color-mix(in oklch, var(--c-role-fighter) 55%, transparent);
  background: color-mix(in oklch, var(--c-role-fighter) 10%, transparent);
}
.tag.tag--role-bomber {
  color: var(--c-role-bomber);
  border-color: color-mix(in oklch, var(--c-role-bomber) 55%, transparent);
  background: color-mix(in oklch, var(--c-role-bomber) 10%, transparent);
}
.tag.tag--role-transport {
  color: var(--c-role-transport);
  border-color: color-mix(in oklch, var(--c-role-transport) 55%, transparent);
  background: color-mix(in oklch, var(--c-role-transport) 10%, transparent);
}
.tag.tag--role-helo {
  color: var(--c-role-helo);
  border-color: color-mix(in oklch, var(--c-role-helo) 55%, transparent);
  background: color-mix(in oklch, var(--c-role-helo) 10%, transparent);
}
.tag.tag--role-civil {
  color: var(--c-role-civil);
  border-color: color-mix(in oklch, var(--c-role-civil) 55%, transparent);
  background: color-mix(in oklch, var(--c-role-civil) 10%, transparent);
}
.tag.tag--role-unknown {
  color: var(--c-role-unknown);
  border-color: color-mix(in oklch, var(--c-role-unknown) 55%, transparent);
  background: color-mix(in oklch, var(--c-role-unknown) 10%, transparent);
}

/* Confidence buckets */
.tag.tag--conf-hi {
  color: var(--c-accent);
  border-color: color-mix(in oklch, var(--c-accent) 50%, transparent);
  background: var(--c-accent-wash);
}
.tag.tag--conf-mid {
  color: var(--c-conf-mid);
  border-color: color-mix(in oklch, var(--c-conf-mid) 55%, transparent);
  background: color-mix(in oklch, var(--c-conf-mid) 10%, transparent);
}
.tag.tag--conf-lo {
  color: var(--c-conf-lo);
  border-color: color-mix(in oklch, var(--c-conf-lo) 45%, transparent);
  background: color-mix(in oklch, var(--c-conf-lo) 8%, transparent);
}


/* ── TOGGLE CHIPS ───────────────────────────────────────────
   Mono filter chip used to flip a list view (e.g. show / hide
   "review" or "rejected" items). Looks and feels like a tag, but
   carries a leading dot, optional trailing count badge, and an
   on/off state. Off = hollow outline; on = solid coloured pill.
   Use with `aria-pressed` so screen readers communicate state.

   Variants ship for the in-pipeline verification states; build
   project-specific filters by extending `.toggle` with your own
   tone class (use `--c-stamp` / `--c-alert` / `--c-accent` etc).

   Pair with `.toggles` (a wrapping cluster) when stacking several. */

.toggles {
  display: inline-flex;
  gap: 4px;
  flex-wrap: wrap;
  align-items: center;
}
.toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  padding: 3px 8px 3px 7px;
  border: var(--border-1) solid color-mix(in oklch, var(--c-white) 18%, transparent);
  background: transparent;
  color: color-mix(in oklch, var(--c-white) 50%, transparent);
  cursor: pointer;
  user-select: none;
  line-height: 1;
  transition:
    background var(--dur-fast) var(--ease-tactical),
    color var(--dur-fast) var(--ease-tactical),
    border-color var(--dur-fast) var(--ease-tactical);
}
.toggle:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
.toggle__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.85;
  display: inline-block;
}
.toggle__count {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  padding: 0 5px;
  margin-left: 2px;
  background: color-mix(in oklch, currentColor 14%, transparent);
  border-radius: 999px;
}
.toggle:hover {
  color: var(--c-white);
  border-color: color-mix(in oklch, var(--c-white) 35%, transparent);
}

/* On states — match the tag tones so a "review" toggle and a
   "review" tag read as the same colour family. */
.toggle--review.is-on,
.toggle--stamp.is-on {
  color: var(--c-stamp);
  border-color: color-mix(in oklch, var(--c-stamp) 55%, transparent);
  background: color-mix(in oklch, var(--c-stamp) 10%, transparent);
}
.toggle--review.is-on:hover,
.toggle--stamp.is-on:hover {
  background: color-mix(in oklch, var(--c-stamp) 16%, transparent);
}
.toggle--rejected.is-on,
.toggle--alert.is-on {
  color: var(--c-alert);
  border-color: color-mix(in oklch, var(--c-alert) 55%, transparent);
  background: color-mix(in oklch, var(--c-alert) 10%, transparent);
}
.toggle--rejected.is-on:hover,
.toggle--alert.is-on:hover {
  background: color-mix(in oklch, var(--c-alert) 16%, transparent);
}
.toggle--accent.is-on {
  color: var(--c-accent);
  border-color: color-mix(in oklch, var(--c-accent) 55%, transparent);
  background: var(--c-accent-wash);
}
.toggle--scan.is-on {
  color: var(--c-scan);
  border-color: color-mix(in oklch, var(--c-scan) 55%, transparent);
  background: color-mix(in oklch, var(--c-scan) 10%, transparent);
}
.toggle--dorm.is-on {
  color: var(--c-dorm);
  border-color: color-mix(in oklch, var(--c-dorm) 55%, transparent);
  background: color-mix(in oklch, var(--c-dorm) 10%, transparent);
}
.toggle--gold.is-on {
  color: var(--c-gold);
  border-color: color-mix(in oklch, var(--c-gold) 55%, transparent);
  background: color-mix(in oklch, var(--c-gold) 12%, transparent);
}


/* ── SOURCE TOGGLES ─────────────────────────────────────────
   Variant of `.toggle` for picking between detector passes /
   data sources / channels. Each source gets a pre-tinted "on"
   look (no `.is-on` needed — the source class itself toggles
   tone), and an `.is-off` modifier crosses the label out. */

.source-toggles { display: inline-flex; gap: 4px; flex-wrap: wrap; }
.source-toggle {
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  padding: 3px 8px;
  border: 1px solid;
  background: transparent;
  cursor: pointer;
  line-height: 1;
  transition:
    background var(--dur-fast) var(--ease-tactical),
    color var(--dur-fast) var(--ease-tactical),
    border-color var(--dur-fast) var(--ease-tactical),
    opacity var(--dur-fast) var(--ease-tactical);
}
.source-toggle:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
.source-toggle--accent,
.source-toggle.src-yolo {
  color: var(--c-accent);
  border-color: color-mix(in oklch, var(--c-accent) 55%, transparent);
  background: color-mix(in oklch, var(--c-accent) 10%, transparent);
}
.source-toggle--stamp,
.source-toggle.src-rex {
  color: var(--c-stamp);
  border-color: color-mix(in oklch, var(--c-stamp) 55%, transparent);
  background: color-mix(in oklch, var(--c-stamp) 10%, transparent);
}
.source-toggle--scan {
  color: var(--c-scan);
  border-color: color-mix(in oklch, var(--c-scan) 55%, transparent);
  background: color-mix(in oklch, var(--c-scan) 10%, transparent);
}
.source-toggle--dorm,
.source-toggle.src-moondream {
  color: var(--c-dorm);
  border-color: color-mix(in oklch, var(--c-dorm) 55%, transparent);
  background: color-mix(in oklch, var(--c-dorm) 10%, transparent);
}
.source-toggle.is-off,
.source-toggle.off {
  background: transparent;
  border-color: color-mix(in oklch, var(--c-white) 18%, transparent);
  color: color-mix(in oklch, var(--c-white) 35%, transparent);
  text-decoration: line-through;
  opacity: 0.7;
}


/* ── THUMB ──────────────────────────────────────────────────
   Small fixed-size media chip used as a row preview, list
   thumbnail, or detail-panel bug. Lighter-weight than `.img-slot`
   (no placeholder chrome). Default 48×48; size variants below. */

.thumb {
  width: 48px;
  height: 48px;
  object-fit: cover;
  border: var(--border-1) solid var(--c-border);
  background: var(--c-bg);
  display: block;
  flex-shrink: 0;
}
.thumb--xs { width: 32px; height: 32px; }
.thumb--sm { width: 40px; height: 40px; }
.thumb--md { width: 48px; height: 48px; } /* default */
.thumb--lg { width: 64px; height: 64px; }
.thumb--xl { width: 96px; height: 96px; }
.thumb--square { aspect-ratio: 1 / 1; height: auto; }
.thumb--wide { aspect-ratio: 16 / 9; height: auto; }


/* ── LIST · ROW ─────────────────────────────────────────────
   Grid-based row pattern for telemetry feeds and detection
   lists. Compose with `.list-row__index`, `.list-row__main`,
   `.list-row__side`. Status rails attach via modifiers — they
   light a coloured left border + faint surface tint without
   changing layout. Selection wins visually. */

.list {
  list-style: none;
  margin: 0;
  padding: 0;
  flex: 1;
  overflow-y: auto;
}
.list-row {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr) auto;
  gap: var(--sp-1);
  padding: 7px var(--sp-2);
  border-bottom: var(--border-1) solid var(--c-border);
  cursor: pointer;
  align-items: center;
  transition: background var(--dur-fast) var(--ease-tactical);
}
.list-row:hover {
  background: color-mix(in oklch, var(--c-white) 3%, transparent);
}
.list-row.is-selected {
  background: var(--c-accent-wash);
  border-left: var(--border-3) solid var(--c-accent);
  padding-left: calc(var(--sp-2) - var(--border-3));
}
/* Status rails — they DON'T win against selection (the !important
   would lock the user in); selection just falls through normally. */
.list-row.is-rejected:not(.is-selected) {
  opacity: 0.6;
  border-left: var(--border-3) solid var(--c-alert);
  padding-left: calc(var(--sp-2) - var(--border-3));
  background: color-mix(in oklch, var(--c-alert) 4%, transparent);
}
.list-row.is-rejected:not(.is-selected) .list-row__title {
  color: color-mix(in oklch, var(--c-white) 65%, transparent);
}
.list-row.is-review:not(.is-selected):not(.is-rejected) {
  border-left: var(--border-3) solid var(--c-stamp);
  padding-left: calc(var(--sp-2) - var(--border-3));
  background: color-mix(in oklch, var(--c-stamp) 4%, transparent);
}
.list-row.is-warn:not(.is-selected):not(.is-rejected) {
  border-left: var(--border-3) solid var(--c-warn);
  padding-left: calc(var(--sp-2) - var(--border-3));
  background: color-mix(in oklch, var(--c-warn) 4%, transparent);
}

.list-row__index {
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.06em;
  color: color-mix(in oklch, var(--c-white) 45%, transparent);
  font-variant-numeric: tabular-nums;
  align-self: center;
}
.list-row__main {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.list-row__title {
  font-family: var(--f-display);
  font-size: var(--t-sm);
  font-weight: 400;
  color: var(--c-white);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  letter-spacing: -0.005em;
}
.list-row__title.is-dim {
  color: color-mix(in oklch, var(--c-white) 55%, transparent);
}
.list-row__meta {
  display: flex;
  gap: var(--sp-1);
  align-items: center;
  flex-wrap: wrap;
}
.list-row__side {
  text-align: right;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
}
.list-row__value {
  font-family: var(--f-mono);
  font-size: var(--t-sm);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  color: var(--c-white);
}
.list-row__value.is-dim {
  color: color-mix(in oklch, var(--c-white) 55%, transparent);
}
.list-row__sub {
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.04em;
  color: color-mix(in oklch, var(--c-white) 40%, transparent);
  font-variant-numeric: tabular-nums;
}
/* Add-a-thumb modifier — slot a 48px preview before the main column. */
.list-row--with-thumb {
  grid-template-columns: 24px 48px minmax(0, 1fr) auto;
}


/* ── NAV · APP VARIANT ──────────────────────────────────────
   Applied as `.nav.nav--app`. Strips the marketing-page sticky
   behaviour and gives a fixed-height (48px) tactical bar with a
   subtle blurred backdrop and a hairline rule beneath. Use for
   dashboard / watch-floor shells where the nav is decoration on
   top of working surfaces, not the dominant element.

   The brand-line uses `.nav__product` to badge the product name
   off to the side of the mark. */

.nav.nav--app {
  position: relative;
  height: 48px;
  /* Inside a flex-column shell (e.g. .app) the nav otherwise gets
     shrunk to content height, since flex items default to
     flex-shrink: 1. Pin the bar at its configured height. */
  flex-shrink: 0;
  padding: 0 var(--sp-3);
  background: color-mix(in oklch, var(--c-bg) 70%, transparent);
  border-bottom: var(--border-1) solid var(--c-border);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  z-index: auto;
}
.nav.nav--app .nav__brand {
  gap: 10px;
  color: var(--c-white);
}
.nav.nav--app .nav__logo { height: 22px; }
.nav.nav--app .nav__product {
  font-family: var(--f-mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: color-mix(in oklch, var(--c-white) 55%, transparent);
  padding-left: 10px;
  margin-left: 10px;
  border-left: var(--border-1) solid var(--c-border);
}


/* ── FORM CONTROLS ──────────────────────────────────────────
   Radio buttons, checkboxes, and selects styled to match the
   tactical aesthetic — square corners, mono labels, restrained
   accent on focus/check. All controls keep `accent-color` set
   so the native check/dot picks up the brand colour without
   reaching for invisible-input + custom-dot tricks. Use these
   anywhere — they cooperate with React's controlled-input
   pattern (no JS hooks required). */

/* Generic card primitive for shadcn parity. Use this for simple
   contained content; use `.bento__tile`, `.stat`, or `.detail`
   when the component has a more specific role. */
.card {
  background: var(--c-surface);
  border: var(--border-1) solid var(--c-border);
  padding: var(--sp-3);
  color: var(--c-fg-dim);
}
.card__header {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: var(--sp-2);
}
.card__title {
  margin: 0;
  font-family: var(--f-display);
  font-size: var(--t-h4);
  font-weight: 500;
  letter-spacing: -0.015em;
  line-height: 1.15;
  color: var(--c-white);
}
.card__description {
  margin: 0;
  font-size: var(--t-sm);
  line-height: 1.5;
  color: var(--c-fg-dim);
}
.card__content {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.card__footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-1);
  margin-top: var(--sp-3);
}

.form-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-family: var(--f-display);
  font-size: var(--t-sm);
  color: var(--c-fg-dim);
}
.form-row__label {
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: color-mix(in oklch, var(--c-white) 55%, transparent);
}

.input,
.textarea {
  width: 100%;
  border: var(--border-1) solid var(--c-border);
  background: var(--c-surface);
  color: var(--c-white);
  font-family: var(--f-display);
  font-size: var(--t-sm);
  line-height: 1.4;
  outline: none;
  transition:
    background-color var(--dur-fast) var(--ease-tactical),
    border-color var(--dur-fast) var(--ease-tactical);
}
.input::placeholder,
.textarea::placeholder {
  color: color-mix(in oklch, var(--c-white) 35%, transparent);
}
.input:hover,
.textarea:hover {
  border-color: var(--c-border-h);
}
.input:focus-visible,
.textarea:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
  border-color: color-mix(in oklch, var(--c-accent) 60%, transparent);
}
.input:disabled,
.textarea:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.input {
  min-height: 38px;
  padding: 8px 12px;
}
.textarea {
  min-height: 108px;
  resize: vertical;
  padding: 10px 12px;
}

/* Group of radios laid out horizontally (default) or vertically. */
.radio-group {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  align-items: center;
}
.radio-group--stack {
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}

/* Custom-drawn 16×16 SHARP square indicators. The native input is
   visually hidden but kept in the tab order for accessibility; the
   `::before` is the box, the `::after` is the inner mark. Sharp by
   default — radio uses a filled inner square, not a circle. */
.radio,
.checkbox {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--f-display);
  font-size: var(--t-sm);
  color: var(--c-fg-dim);
  cursor: pointer;
  user-select: none;
  line-height: 1.2;
  position: relative;
  padding-left: 24px;
  min-height: 20px;
}
/* Hide the native input but keep it focusable. */
.radio input[type="radio"],
.checkbox input[type="checkbox"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  margin: 0;
  cursor: pointer;
}
/* The custom box — 16×16, hairline, sharp corners. */
.radio::before,
.checkbox::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  border: 1px solid var(--c-border);
  background: transparent;
  transition:
    border-color var(--dur-fast) var(--ease-tactical),
    background var(--dur-fast) var(--ease-tactical);
}
.radio:hover,
.checkbox:hover { color: var(--c-white); }
.radio:hover::before,
.checkbox:hover::before { border-color: var(--c-border-h); }

/* Checked state — accent border + accent fill. */
.radio:has(input:checked),
.checkbox:has(input:checked) { color: var(--c-white); }
.radio:has(input:checked)::before,
.checkbox:has(input:checked)::before {
  border-color: var(--c-accent);
  background: var(--c-accent);
}

/* Radio inner mark — a filled 6×6 SQUARE (not a circle). Sharp by
   default; a circle here would contradict the rest of the system. */
.radio:has(input:checked)::after {
  content: "";
  position: absolute;
  left: 5px;
  top: 50%;
  transform: translateY(-50%);
  width: 6px;
  height: 6px;
  background: var(--c-white);
}

/* Checkbox inner mark — a checkmark SVG drawn in white. */
.checkbox:has(input:checked)::after {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='1.8' stroke-linecap='square'><path d='M3.5 8.5l3 3 6-7'/></svg>");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 14px 14px;
}

/* Focus ring lifts to the box, not the (invisible) native control. */
.radio:focus-within::before,
.checkbox:focus-within::before {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}

/* Disabled — opacity + cursor; same contract as buttons. */
.radio:has(input:disabled),
.checkbox:has(input:disabled) { opacity: 0.40; cursor: not-allowed; }

/* Tactical-mono variant: small uppercase tracked label, used in
   filter rows and dense control surfaces. */
.radio--mono,
.checkbox--mono {
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/* Pill variant — wraps the radio in a chip outline so it visually
   matches `.toggle` chips when sitting in the same control strip.
   Pair with `aria-pressed`-style state via :has(input:checked). */
.radio.radio--pill,
.checkbox.checkbox--pill {
  padding: 4px 10px;
  border: var(--border-1) solid color-mix(in oklch, var(--c-white) 18%, transparent);
  background: transparent;
  color: color-mix(in oklch, var(--c-white) 55%, transparent);
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  transition:
    background var(--dur-fast) var(--ease-tactical),
    color var(--dur-fast) var(--ease-tactical),
    border-color var(--dur-fast) var(--ease-tactical);
}
.radio.radio--pill input,
.checkbox.checkbox--pill input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 0;
  height: 0;
}
.radio.radio--pill:has(input:checked),
.checkbox.checkbox--pill:has(input:checked) {
  color: var(--c-accent);
  border-color: color-mix(in oklch, var(--c-accent) 50%, transparent);
  background: var(--c-accent-wash);
}
.radio.radio--pill:hover,
.checkbox.checkbox--pill:hover {
  color: var(--c-white);
  border-color: color-mix(in oklch, var(--c-white) 35%, transparent);
}
.radio.radio--pill:focus-within,
.checkbox.checkbox--pill:focus-within {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}

/* Native select, restyled. The chevron is drawn with a CSS
   background SVG so we never ship an extra asset. Width is
   driven by the parent — set max-width on .form-row for layout. */
.select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  font-family: var(--f-mono);
  font-size: 11px;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--c-white);
  background-color: var(--c-surface);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23ffffff' stroke-opacity='0.7' stroke-width='1.4'><path d='M1 1.5l5 5 5-5'/></svg>");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 12px 8px;
  border: var(--border-1) solid var(--c-border);
  padding: 8px 32px 8px 12px;
  cursor: pointer;
  outline: none;
  transition:
    background-color var(--dur-fast) var(--ease-tactical),
    border-color var(--dur-fast) var(--ease-tactical);
}
.select:hover {
  border-color: var(--c-border-h);
}
.select:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
  border-color: color-mix(in oklch, var(--c-accent) 60%, transparent);
}
.select:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.select option {
  background: var(--c-bg);
  color: var(--c-white);
}
/* Compact select for inline use (table cells, filter strips). */
.select--sm {
  font-size: 10px;
  padding: 5px 26px 5px 9px;
  background-position: right 8px center;
  background-size: 10px 6px;
}


/* ── ICON LANGUAGE ──────────────────────────────────────────
   Stellaria's visual symbols are deliberately spare: corner
   brackets, dots, hairline rails, dashed borders, diagonal-
   stripe placeholder fills. These primitives are reusable as
   utility classes so any new component can speak the same
   visual language without re-inventing markup.

   - `.bracket-frame` — wraps any block in two top-corner brackets.
   - `.dot` — a 6px round indicator. Pair tones with `.dot--accent`
     etc. or set `color` on the parent.
   - `.rail-l` — a left-side accent rail (3px) without changing
     the box layout. Use it to mark featured rows or tiles.
   - `.dashed` — a quick "in-progress / unverified" border.
   - `.stripes-bg` — the diagonal-stripe placeholder background
     used in the dropzone and image slot, exposed as a class. */

.bracket-frame {
  position: relative;
}
.bracket-frame::before,
.bracket-frame::after {
  content: '';
  position: absolute;
  width: 14px;
  height: 14px;
  border: var(--border-1) solid color-mix(in oklch, var(--c-white) 25%, transparent);
}
.bracket-frame::before {
  top: 8px;
  left: 8px;
  border-right: none;
  border-bottom: none;
}
.bracket-frame::after {
  top: 8px;
  right: 8px;
  border-left: none;
  border-bottom: none;
}
.bracket-frame--bottom::before,
.bracket-frame--bottom::after {
  top: auto;
  bottom: 8px;
}
.bracket-frame--bottom::before { border-bottom: none; border-top: none; border-right: none; border-left: var(--border-1) solid color-mix(in oklch, var(--c-white) 25%, transparent); }
.bracket-frame--all::before,
.bracket-frame--all::after { content: none; } /* override below */
.bracket-frame--all { /* four-corner bracket frame */ }

.dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}
.dot--lg { width: 10px; height: 10px; }
.dot--sm { width: 4px; height: 4px; }
.dot--accent  { color: var(--c-accent); }
.dot--scan    { color: var(--c-scan); }
.dot--alert   { color: var(--c-alert); }
.dot--stamp   { color: var(--c-stamp); }
.dot--dorm    { color: var(--c-dorm); }
.dot--success { color: var(--c-success); }
.dot--warn    { color: var(--c-warn); }
.dot--gold    { color: var(--c-gold); }
.dot--muted   { color: color-mix(in oklch, var(--c-white) 35%, transparent); }
/* Pulsing live indicator. Use sparingly — one on screen at a time.
   Opacity-only pulse on the dot itself, no scaling glow ring. The
   glow halo (a 1px scaled border) read as decorative neon against
   the institutional canvas, so it has been removed in favour of a
   tight oscillation that the eye registers as activity, not effect.
   Keyframes live in motion.css (`ds-pulse`). */
.dot--live {
  background: var(--c-alert);
  animation: ds-pulse 1.6s var(--ease-standard) infinite;
}
@media (prefers-reduced-motion: reduce) {
  .dot--live { animation: none; opacity: 1; }
}

.rail-l {
  border-left: var(--border-3) solid var(--c-accent);
  padding-left: var(--sp-2);
}
.rail-l--alert  { border-left-color: var(--c-alert); }
.rail-l--stamp  { border-left-color: var(--c-stamp); }
.rail-l--scan   { border-left-color: var(--c-scan); }
.rail-l--gold   { border-left-color: var(--c-gold); }
.rail-l--muted  { border-left-color: var(--c-border); }

.dashed {
  border: var(--border-1) dashed color-mix(in oklch, var(--c-white) 25%, transparent);
}
.dashed--accent { border-color: color-mix(in oklch, var(--c-accent) 55%, transparent); }
.dashed--alert  { border-color: color-mix(in oklch, var(--c-alert)  55%, transparent); }
.dashed--stamp  { border-color: color-mix(in oklch, var(--c-stamp)  55%, transparent); }

.stripes-bg {
  background:
    repeating-linear-gradient(135deg, transparent 0 18px,
      color-mix(in oklch, var(--c-white) 3%, transparent) 18px 19px),
    var(--c-surface);
}


/* ── DROPZONE ───────────────────────────────────────────────
   File-drop / file-pick area. Diagonal stripes + corner
   brackets; on `.is-dragging` the border lights to accent and
   the surface lifts. Children: an `.eyebrow` (Stage label), an
   `<h2>` (instruction), a `<p>` (helper copy), `.dropzone-actions`
   wrapping the CTA, optional `<span class="label">` and
   `<span class="error">` for selected file / error state. */

.dropzone {
  position: relative;
  width: min(460px, 100%);
  padding: var(--sp-4) var(--sp-3) var(--sp-3);
  text-align: center;
  background:
    repeating-linear-gradient(135deg, transparent 0 18px,
      color-mix(in oklch, var(--c-white) 3%, transparent) 18px 19px),
    var(--c-surface);
  border: var(--border-1) solid var(--c-border);
  transition:
    border-color var(--dur-mid) var(--ease-tactical),
    background-color var(--dur-mid) var(--ease-tactical);
}
.dropzone.is-dragging,
.dropzone.dragging {
  border-color: var(--c-accent);
  background:
    linear-gradient(135deg, var(--c-accent-wash), transparent 60%),
    var(--c-surface-2);
}
/* Top-corner brackets only — matches the watch-floor frame language. */
.dropzone::before, .dropzone::after {
  content: '';
  position: absolute;
  width: 14px;
  height: 14px;
  border: var(--border-1) solid color-mix(in oklch, var(--c-white) 25%, transparent);
}
.dropzone::before { top: 10px; left: 10px;  border-right: none; border-bottom: none; }
.dropzone::after  { top: 10px; right: 10px; border-left:  none; border-bottom: none; }

.dropzone .eyebrow { margin-bottom: 6px; }
.dropzone h2 {
  font-size: var(--t-h4);
  font-weight: 500;
  letter-spacing: -0.008em;
  margin-bottom: 4px;
  max-width: none;
}
.dropzone p {
  margin-inline: auto;
  max-width: 40ch;
  font-size: var(--t-sm);
  line-height: 1.5;
}
.dropzone__actions,
.dropzone .dropzone-actions { margin-top: var(--sp-2); }
.dropzone .label { display: block; margin-top: var(--sp-2); }
.dropzone__error,
.dropzone .error {
  margin-top: var(--sp-2);
  color: var(--c-alert);
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.06em;
}
/* Slightly tighter primary button so the CTA doesn't dominate. */
.dropzone .btn--primary { padding: 10px 20px; font-weight: 500; }


/* ── MAP CONTROLS · OPENLAYERS OVERRIDES ────────────────────
   `.map-wrap` is the positioned container; restyles OpenLayers'
   default `.ol-zoom` and `.ol-attribution` widgets so they read
   as Stellaria chips on dark canvas. Drop the wrapper around any
   <div class="map"> rendered by ol/Map and the controls inherit.
   `.empty-overlay` is a centered mono caption shown before tiles
   load (e.g. "no source selected"). */

.map-wrap {
  position: relative;
  background: var(--c-bg);
  overflow: hidden;
}
.map-wrap .map { position: absolute; inset: 0; }
.map-wrap .empty-overlay {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: color-mix(in oklch, var(--c-white) 55%, transparent);
}

/* Restyle OpenLayers' built-in +/- zoom controls. */
.map-wrap .ol-zoom {
  top: var(--sp-2);
  left: var(--sp-2);
  background: transparent;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.map-wrap .ol-zoom button {
  width: 28px;
  height: 28px;
  background: color-mix(in oklch, var(--c-bg) 80%, transparent);
  color: var(--c-white);
  border: var(--border-1) solid var(--c-border);
  font-family: var(--f-mono);
  font-size: 14px;
  font-weight: 400;
  line-height: 1;
  border-radius: 0;
  margin: 0;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  transition: border-color var(--dur-fast) var(--ease-tactical),
              color var(--dur-fast) var(--ease-tactical);
}
.map-wrap .ol-zoom button:hover {
  color: var(--c-accent);
  border-color: var(--c-border-h);
  background: color-mix(in oklch, var(--c-bg) 80%, transparent);
}
.map-wrap .ol-attribution {
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  background: color-mix(in oklch, var(--c-bg) 70%, transparent);
  color: color-mix(in oklch, var(--c-white) 55%, transparent);
  border-top: var(--border-1) solid var(--c-border);
  border-left: var(--border-1) solid var(--c-border);
  border-radius: 0;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.map-wrap .ol-attribution a { color: var(--c-fg-dim); }
.map-wrap .ol-attribution a:hover { color: var(--c-white); }


/* ── SIDE PANEL ─────────────────────────────────────────────
   Vertical panel for filter strips, lists, and detail content.
   `.side` is the column wrapper. `.side-head` is the panel header
   (mono eyebrow, count, optional row of toggles). Use
   `.side-head--stack` when the head needs two rows (title row +
   filters row), with `.side-head-row` and `.side-head-filters`
   inside. Pair with `.list` for the body. */

.side {
  display: flex;
  flex-direction: column;
  background: var(--c-surface-2);
  overflow: hidden;
}
.side-head {
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: color-mix(in oklch, var(--c-white) 50%, transparent);
  padding: 10px var(--sp-2);
  border-bottom: var(--border-1) solid var(--c-border);
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.side-head .count { color: var(--c-accent); }
.side-head .count.is-empty,
.side-head .count.empty {
  color: color-mix(in oklch, var(--c-white) 35%, transparent);
}

/* Two-row variant: title row on top, filter cluster below. */
.side-head.side-head--stack {
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  padding: 10px var(--sp-2);
}
.side-head-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.side-head-filters { gap: var(--sp-2); flex-wrap: wrap; }


/* ── PROGRESS TIMELINE ──────────────────────────────────────
   Vertical pipeline of stages, mono labels, leading status dot,
   trailing index. Each `.stage-row` carries one of `.is-active`,
   `.is-done`, `.is-error` (or the older bare class names) to
   light its dot and text. Slot inside `.progress` for the
   bordered container. Pair with `.progress-error` for a status
   line in the error case. */

.progress {
  padding: 8px var(--sp-2);
  border-bottom: var(--border-1) solid var(--c-border);
}
.stage-row {
  display: grid;
  grid-template-columns: 14px 1fr auto;
  align-items: center;
  gap: 8px;
  padding: 3px 0;
  font-family: var(--f-mono);
  font-size: 11px;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: color-mix(in oklch, var(--c-white) 45%, transparent);
}
.stage-row.is-active,
.stage-row.active { color: var(--c-white); }
.stage-row.is-done,
.stage-row.done   { color: color-mix(in oklch, var(--c-white) 75%, transparent); }
.stage-row.is-error,
.stage-row.error  { color: var(--c-alert); }
.stage-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: color-mix(in oklch, var(--c-white) 18%, transparent);
  outline: 3px solid color-mix(in oklch, var(--c-bg) 90%, transparent);
}
.stage-row.is-active .stage-dot,
.stage-row.active .stage-dot { background: var(--c-accent); }
.stage-row.is-done .stage-dot,
.stage-row.done   .stage-dot { background: color-mix(in oklch, var(--c-white) 70%, transparent); }
.stage-row.is-error .stage-dot,
.stage-row.error  .stage-dot { background: var(--c-alert); }
.stage-row .stage-num {
  text-align: right;
  font-family: var(--f-mono);
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  color: color-mix(in oklch, var(--c-white) 55%, transparent);
}
.progress .progress-error {
  margin-top: var(--sp-2);
  color: var(--c-alert);
  font-family: var(--f-mono);
  font-size: var(--t-xs);
}


/* ── DETAIL PANEL ───────────────────────────────────────────
   Vertical scroll surface that pairs with `.side` to render a
   focused record. Composes from purpose-named primitives so a
   project can rearrange them (header → crop → status → meta →
   description → geometry strip), or skip pieces it doesn't need.

   Scroll lives on `.detail` itself; the parent should constrain
   its height (e.g. job page shell uses `height: 100%`). */

.detail {
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow-y: auto;
}

/* Header strip: back button, optional spacer, optional score / id. */
.detail-header {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 8px var(--sp-2);
  border-bottom: var(--border-1) solid var(--c-border);
}
.detail-back {
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: color-mix(in oklch, var(--c-white) 75%, transparent);
  border: var(--border-1) solid var(--c-border);
  padding: 5px 10px;
  cursor: pointer;
  background: transparent;
  transition:
    color var(--dur-fast) var(--ease-tactical),
    border-color var(--dur-fast) var(--ease-tactical);
}
.detail-back:hover { color: var(--c-white); border-color: var(--c-border-h); }
.detail-spacer { flex: 1; }
.detail-score {
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: color-mix(in oklch, var(--c-white) 55%, transparent);
}

/* Crop / preview slot — diagonal-stripe canvas, max-height capped
   so it doesn't overwhelm the panel. Use `.detail-crop-tag` for
   the corner mono caption (resolution / lens / scale). */
.detail-crop-wrap {
  position: relative;
  background:
    repeating-linear-gradient(135deg, transparent 0 18px,
      color-mix(in oklch, var(--c-white) 3%, transparent) 18px 19px),
    var(--c-bg);
  border-bottom: var(--border-1) solid var(--c-border);
  min-height: 160px;
  max-height: 240px;
  overflow: hidden;
  display: grid;
  place-items: center;
}
.detail-crop {
  max-width: 100%;
  max-height: 240px;
  object-fit: contain;
  display: block;
}
.detail-crop-tag {
  position: absolute;
  bottom: 12px; left: 14px;
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: color-mix(in oklch, var(--c-white) 65%, transparent);
}

/* Inline status pill (pending / rejected / etc). Tactical hue
   tint matches the status palette. Pair with a text label — never
   communicate state with colour alone. */
.detail-status {
  margin: 8px var(--sp-2);
  padding: 6px 10px;
  font-family: var(--f-mono);
  font-size: 11px;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  border: var(--border-1) solid;
}
.detail-status.is-pending,
.detail-status.pending {
  color: var(--c-scan);
  border-color: color-mix(in oklch, var(--c-scan) 50%, transparent);
  background: color-mix(in oklch, var(--c-scan) 8%, transparent);
}
.detail-status.is-rejected,
.detail-status.rejected {
  color: var(--c-alert);
  border-color: color-mix(in oklch, var(--c-alert) 50%, transparent);
  background: color-mix(in oklch, var(--c-alert) 8%, transparent);
}
.detail-status.is-review,
.detail-status.review {
  color: var(--c-stamp);
  border-color: color-mix(in oklch, var(--c-stamp) 50%, transparent);
  background: color-mix(in oklch, var(--c-stamp) 8%, transparent);
}

/* 2-column compact spec strip variant, used inline within the
   detail panel for paired key/value cells (e.g. operator + flag).
   For a free-form 4-up grid use `.spec-strip` instead. */
.detail-meta {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1px;
  background: var(--c-border);
  border-bottom: var(--border-1) solid var(--c-border);
}
.detail-meta-cell {
  background: var(--c-bg);
  padding: 10px var(--sp-2);
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.detail-meta-label {
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: color-mix(in oklch, var(--c-white) 45%, transparent);
}
.detail-meta-value {
  font-family: var(--f-display);
  font-size: var(--t-sm);
  font-weight: 400;
  color: var(--c-white);
  letter-spacing: -0.005em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Body prose inside the detail panel. Keep at body size, dim
   colour, and constrained measure. */
.detail-desc {
  padding: 0 var(--sp-2) var(--sp-3);
  font-family: var(--f-display);
  font-size: var(--t-sm);
  font-weight: 400;
  color: var(--c-fg-dim);
  line-height: 1.5;
  max-width: var(--measure-body);
}

/* Geometry / numeric strip — re-uses the spec-strip aesthetic in
   2 columns. Use `.detail-geom-cell.full` to make a row span both
   columns (e.g. WGS84 coordinate). */
.detail-geom {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  background: var(--c-border);
  border-top: var(--border-1) solid var(--c-border);
}
.detail-geom-cell {
  background: var(--c-bg);
  padding: 8px var(--sp-2);
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.detail-geom-cell.full,
.detail-geom-cell.is-full { grid-column: 1 / -1; }
.detail-geom dt {
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: color-mix(in oklch, var(--c-white) 45%, transparent);
}
.detail-geom dd {
  margin: 0;
  font-family: var(--f-mono);
  font-size: var(--t-sm);
  color: var(--c-white);
  font-variant-numeric: tabular-nums lining-nums;
}


/* ── SECTION EYEBROW ────────────────────────────────────────
   Mono label used to title sub-sections inside a panel
   (e.g. "Predictions", "Geometry"). Smaller and quieter than
   `<h3>`; pair below `.detail-meta` and above `.predictions`. */

.section-eyebrow {
  font-family: var(--f-mono);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: color-mix(in oklch, var(--c-white) 50%, transparent);
  padding: var(--sp-2) var(--sp-2) 4px;
  margin: 0;
}


/* ── EMPTY STATE ────────────────────────────────────────────
   Plain mono caption used inside a list / panel when there's
   nothing to render yet. Quiet, padded, no border. */

.empty {
  padding: var(--sp-3);
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.06em;
  color: color-mix(in oklch, var(--c-white) 50%, transparent);
}


/* === TABS ==================================================
   Horizontal tablist with hairline divider beneath the active
   trigger. Active uses a 2px accent under-rail; inactive sits
   in fg-muted. Composition:

     <div class="tabs">
       <div class="tabs__list" role="tablist">
         <button class="tabs__trigger" role="tab"
                 aria-selected="true" aria-controls="p1" id="t1">…</button>
         <button class="tabs__trigger" role="tab"
                 aria-selected="false" aria-controls="p2" id="t2">…</button>
       </div>
       <div class="tabs__panel" role="tabpanel"
            aria-labelledby="t1" id="p1">…</div>
     </div>

   Keyboard: ArrowLeft / ArrowRight cycles triggers, Home / End
   jumps to first / last. JS owns focus + aria-selected updates;
   CSS only paints the state. */

.tabs {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  width: 100%;
}
.tabs__list {
  display: flex;
  gap: var(--sp-3);
  align-items: stretch;
  border-bottom: var(--border-1) solid var(--c-border);
  overflow-x: auto;
  scrollbar-width: thin;
}
.tabs__trigger {
  appearance: none;
  background: transparent;
  border: 0;
  border-bottom: var(--border-2) solid transparent;
  margin-bottom: -1px; /* overlap the list rule so active under-rail meets it */
  padding: 10px 2px;
  min-height: 44px;
  font-family: var(--f-mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--c-fg-muted);
  cursor: pointer;
  white-space: nowrap;
  transition:
    color var(--dur-fast) var(--ease-tactical),
    border-color var(--dur-fast) var(--ease-tactical);
}
.tabs__trigger:hover {
  color: var(--c-white);
}
.tabs__trigger:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
.tabs__trigger[aria-selected="true"],
.tabs__trigger.is-active {
  color: var(--c-white);
  border-bottom-color: var(--c-accent);
}
.tabs__trigger[aria-disabled="true"],
.tabs__trigger:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.tabs__panel {
  outline: none;
  padding-top: var(--sp-1);
  color: var(--c-fg-dim);
  font-size: var(--t-sm);
  line-height: var(--lh-base);
}
.tabs__panel[hidden] { display: none; }
.tabs__panel:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}


/* === DIALOG / MODAL ========================================
   Centered hairline panel over a dimmed page. Variants change
   the max-width only; vertical scroll lives inside `.dialog__body`
   if content exceeds 80vh. Composition:

     <div class="dialog" role="dialog" aria-modal="true"
          aria-labelledby="dlg-title">
       <div class="dialog__backdrop"></div>
       <div class="dialog__panel">
         <header class="dialog__header">
           <h2 id="dlg-title">…</h2>
           <button class="dialog__close" aria-label="Close">×</button>
         </header>
         <div class="dialog__body">…</div>
         <footer class="dialog__footer">…</footer>
       </div>
     </div>

   Owner JS: ESC closes; clicking backdrop closes; focus trapped
   inside `.dialog__panel`; restore focus to invoking trigger
   on close. */

.dialog {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: grid;
  place-items: center;
  padding: var(--sp-3);
}
.dialog[hidden] { display: none; }
.dialog__backdrop {
  position: absolute;
  inset: 0;
  z-index: var(--z-modal-backdrop);
  background: oklch(0.05 0.01 260 / 0.6);
  animation: ds-dialog-fade var(--dur-mid) var(--ease-tactical);
}
.dialog__panel {
  position: relative;
  z-index: var(--z-modal);
  width: 100%;
  max-width: 480px;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  background: var(--c-elev-3);
  border: var(--border-hairline);
  border-radius: var(--r-2);
  animation: ds-dialog-rise var(--dur-mid) var(--ease-tactical);
}
.dialog--lg .dialog__panel { max-width: 640px; }
.dialog--xl .dialog__panel { max-width: 840px; }
.dialog__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-bottom: var(--border-1) solid var(--c-border);
}
.dialog__header h1,
.dialog__header h2,
.dialog__header h3 {
  margin: 0;
  font-family: var(--f-display);
  font-size: var(--t-h4);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-heading);
  color: var(--c-white);
}
.dialog__body {
  padding: var(--sp-3);
  overflow-y: auto;
  font-size: var(--t-sm);
  line-height: var(--lh-base);
  color: var(--c-fg-dim);
  flex: 1 1 auto;
}
.dialog__footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-1);
  padding: var(--sp-2) var(--sp-3);
  border-top: var(--border-1) solid var(--c-border);
}
.dialog__close {
  appearance: none;
  background: transparent;
  border: var(--border-1) solid transparent;
  width: 28px;
  height: 28px;
  min-width: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--f-mono);
  font-size: 18px;
  line-height: 1;
  color: var(--c-fg-muted);
  cursor: pointer;
  transition:
    color var(--dur-fast) var(--ease-tactical),
    border-color var(--dur-fast) var(--ease-tactical);
}
.dialog__close:hover {
  color: var(--c-white);
  border-color: var(--c-border);
}
.dialog__close:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
@keyframes ds-dialog-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes ds-dialog-rise {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .dialog__backdrop,
  .dialog__panel { animation: none; }
}


/* === TOAST =================================================
   Bottom-right column of stacked, hairline-bordered notices
   with a small status dot. Auto-dismiss runs a CSS animation
   that fades + collapses after 5s; reduced-motion freezes the
   dismiss so the user can read at their own pace.

     <div class="toast-region" role="region" aria-label="Notifications" aria-live="polite">
       <div class="toast toast--info" role="status">
         <span class="toast__dot" aria-hidden="true"></span>
         <div class="toast__body">
           <strong class="toast__title">Saved</strong>
           <span class="toast__msg">Your changes are live.</span>
         </div>
       </div>
     </div>

   Variants: --info (default), --success, --warn, --alert.
   Pair the dot tone with a text label; never communicate state
   with colour alone. JS owns insertion order + removal after
   the animation ends. */

.toast-region {
  position: fixed;
  right: var(--sp-3);
  bottom: var(--sp-3);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  width: min(360px, calc(100vw - var(--sp-4)));
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  display: grid;
  grid-template-columns: 14px 1fr auto;
  align-items: flex-start;
  gap: var(--sp-1);
  padding: var(--sp-2);
  background: var(--c-elev-2);
  border: var(--border-1) solid var(--c-border);
  border-radius: var(--r-2);
  font-family: var(--f-display);
  font-size: var(--t-sm);
  color: var(--c-fg-dim);
  animation:
    ds-toast-in var(--dur-mid) var(--ease-tactical) both,
    ds-toast-out var(--dur-mid) var(--ease-tactical) 5s forwards;
}
.toast__dot {
  width: 8px;
  height: 8px;
  /* Pushes the 8px dot down so its centre aligns with the title's
     optical centre (14px text at 1.6 line-height ≈ 11px optical
     centre; dot half-height is 4px, so margin-top 7px lands it). */
  margin-top: 7px; /* allow-raw */
  border-radius: var(--r-pill);
  background: var(--c-info);
  display: inline-block;
}
.toast__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.toast__title {
  font-family: var(--f-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-sm);
  color: var(--c-white);
  letter-spacing: var(--ls-body);
}
.toast__msg {
  font-size: var(--t-xs);
  color: var(--c-fg-muted);
  line-height: var(--lh-base);
}
.toast__close {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--c-fg-muted);
  font-family: var(--f-mono);
  font-size: 14px;
  line-height: 1;
  width: 24px;
  height: 24px;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-tactical);
}
.toast__close:hover { color: var(--c-white); }
.toast__close:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
.toast--success .toast__dot { background: var(--c-success); }
.toast--warn    .toast__dot { background: var(--c-warn); }
.toast--alert   .toast__dot { background: var(--c-alert); }
.toast--info    .toast__dot { background: var(--c-info); }

@keyframes ds-toast-in {
  from { opacity: 0; transform: translateX(8px); }
  to   { opacity: 1; transform: translateX(0); }
}
@keyframes ds-toast-out {
  from { opacity: 1; transform: translateX(0); max-height: 200px; }
  to   { opacity: 0; transform: translateX(8px); max-height: 0;
         margin: 0; padding-top: 0; padding-bottom: 0;
         border-width: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
}


/* === TOOLTIP ===============================================
   Small mono caption attached to an interactive parent. The
   parent declares `data-tooltip` (the label) or wraps a
   `.tooltip` child; CSS reveals on `:hover` and `:focus-within`.
   Position via `--tt-x` / `--tt-y` custom properties (defaults
   centered below). JS may set --tt-x/--tt-y for collision-aware
   placement.

     <button class="btn" aria-describedby="tt1">Run</button>
     <span id="tt1" role="tooltip" class="tooltip">Process the queue</span>

   Or the data-attribute path:

     <button class="btn" data-tooltip="Process the queue">Run</button>

   Pair text label + tooltip — never use tooltip as the only
   accessible name. */

.tooltip {
  position: absolute;
  z-index: var(--z-overlay);
  left: var(--tt-x, 50%);
  top: var(--tt-y, calc(100% + 6px));
  transform: translateX(-50%);
  font-family: var(--f-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--c-white);
  background: var(--c-elev-3);
  border: var(--border-1) solid var(--c-border);
  border-radius: var(--r-1);
  padding: 6px 8px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-tactical);
}
.tooltip[data-show="true"],
:hover > .tooltip,
:focus-visible > .tooltip,
:focus-within > .tooltip { opacity: 1; }

/* Data-attribute variant: any element with [data-tooltip] gets
   a generated bubble via ::after. */
[data-tooltip] {
  position: relative;
}
[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  left: var(--tt-x, 50%);
  top: var(--tt-y, calc(100% + 6px));
  transform: translateX(-50%);
  z-index: var(--z-overlay);
  font-family: var(--f-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--c-white);
  background: var(--c-elev-3);
  border: var(--border-1) solid var(--c-border);
  border-radius: var(--r-1);
  padding: 6px 8px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-tactical);
}
[data-tooltip]:hover::after,
[data-tooltip]:focus-visible::after { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .tooltip,
  [data-tooltip]::after { transition: none; }
}


/* === ACCORDION =============================================
   Native <details>/<summary> for the no-JS path. Hairline rule
   between items; the chevron rotates 90° when [open]. ARIA is
   provided by the underlying elements; nothing extra required.

     <div class="accordion">
       <details class="accordion__item">
         <summary class="accordion__trigger">Section title</summary>
         <div class="accordion__panel">…</div>
       </details>
     </div>

   For exclusive (one-open) behaviour, give all items the same
   `name` attribute (HTML5). Keyboard parity is built in. */

.accordion {
  border-top: var(--border-1) solid var(--c-border);
  border-bottom: var(--border-1) solid var(--c-border);
}
.accordion__item {
  border-bottom: var(--border-1) solid var(--c-border);
}
.accordion__item:last-child { border-bottom: 0; }

.accordion__trigger {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  padding: var(--sp-2) 0;
  min-height: 44px;
  font-family: var(--f-display);
  font-size: var(--t-sm);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-body);
  color: var(--c-white);
  transition: color var(--dur-fast) var(--ease-tactical);
}
.accordion__trigger::-webkit-details-marker { display: none; }
.accordion__trigger::marker { content: ''; }
.accordion__trigger::before {
  content: '';
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  border-right: var(--border-1) solid var(--c-fg-muted);
  border-bottom: var(--border-1) solid var(--c-fg-muted);
  transform: rotate(-45deg);
  transition: transform var(--dur-fast) var(--ease-tactical);
}
.accordion__item[open] > .accordion__trigger::before {
  transform: rotate(45deg);
}
.accordion__trigger:hover { color: var(--c-accent); }
.accordion__trigger:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
.accordion__panel {
  padding: 0 0 var(--sp-2) calc(8px + var(--sp-1));
  color: var(--c-fg-dim);
  font-size: var(--t-sm);
  line-height: var(--lh-base);
}
@media (prefers-reduced-motion: reduce) {
  .accordion__trigger::before { transition: none; }
}


/* === BREADCRUMB ============================================
   Linear path of crumbs separated by "/" in mono fg-muted. The
   final item is the current page: white, no underline, no link.

     <nav class="breadcrumb" aria-label="Breadcrumb">
       <ol>
         <li class="breadcrumb__item"><a href="/">Home</a></li>
         <li class="breadcrumb__separator" aria-hidden="true">/</li>
         <li class="breadcrumb__item"><a href="/team">Team</a></li>
         <li class="breadcrumb__separator" aria-hidden="true">/</li>
         <li class="breadcrumb__item" aria-current="page">Profile</li>
       </ol>
     </nav>

   Keep it linear; never wrap mid-crumb. */

.breadcrumb {
  font-family: var(--f-mono);
  font-size: 11px;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--c-fg-muted);
}
.breadcrumb ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.breadcrumb__item {
  display: inline-flex;
  align-items: center;
}
.breadcrumb__item a {
  color: var(--c-fg-muted);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-tactical);
}
.breadcrumb__item a:hover { color: var(--c-white); }
.breadcrumb__item a:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
.breadcrumb__item[aria-current="page"] {
  color: var(--c-white);
  cursor: default;
}
.breadcrumb__separator {
  color: var(--c-fg-muted);
  user-select: none;
}


/* === PAGINATION ============================================
   Equal-width 32px squares for numbered pages, hairline border;
   the current page is accent-bordered with the accent wash.
   Prev / Next get a wider footprint with their text label.

     <nav class="pagination" aria-label="Pagination">
       <a class="pagination__item pagination__item--prev" href="?p=1">Prev</a>
       <a class="pagination__item" href="?p=1">1</a>
       <a class="pagination__item pagination__item--current"
          href="?p=2" aria-current="page">2</a>
       <span class="pagination__ellipsis" aria-hidden="true">…</span>
       <a class="pagination__item pagination__item--next" href="?p=3">Next</a>
     </nav>

   ARIA: outer <nav> takes aria-label, current item takes
   aria-current="page". */

.pagination {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  font-family: var(--f-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
}
.pagination__item {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 32px;
  padding: 0 6px;
  border: var(--border-1) solid var(--c-border);
  background: transparent;
  color: var(--c-fg-dim);
  text-decoration: none;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition:
    background var(--dur-fast) var(--ease-tactical),
    color var(--dur-fast) var(--ease-tactical),
    border-color var(--dur-fast) var(--ease-tactical);
}
.pagination__item:hover {
  color: var(--c-white);
  border-color: var(--c-border-h);
}
.pagination__item:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
.pagination__item--prev,
.pagination__item--next {
  padding: 0 var(--sp-2);
  text-transform: uppercase;
  letter-spacing: 0.16em;
}
.pagination__item--current,
.pagination__item[aria-current="page"] {
  color: var(--c-accent);
  border-color: color-mix(in oklch, var(--c-accent) 60%, transparent);
  background: var(--c-accent-wash);
  cursor: default;
}
.pagination__item[aria-disabled="true"] {
  opacity: 0.4;
  pointer-events: none;
}
.pagination__ellipsis {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 32px;
  color: var(--c-fg-muted);
  user-select: none;
}


/* === SKELETON ==============================================
   Placeholder slab with a subtle shimmer gradient. Use during
   loading to occupy the layout space the real content will fill.
   Reduced-motion freezes it to a flat tinted surface.

     <div class="skeleton skeleton--text"></div>
     <div class="skeleton skeleton--block" style="height: 160px;"></div>

   ARIA: wrap loading region in a [role="status"][aria-live="polite"]
   with a visually-hidden "Loading…" label. */

.skeleton {
  display: block;
  background:
    linear-gradient(
      90deg,
      color-mix(in oklch, var(--c-white) 4%, transparent) 0%,
      color-mix(in oklch, var(--c-white) 10%, transparent) 50%,
      color-mix(in oklch, var(--c-white) 4%, transparent) 100%
    );
  background-size: 200% 100%;
  border-radius: var(--r-1);
  animation: ds-skeleton-shimmer 1.4s var(--ease-linear) infinite;
}
.skeleton--text {
  height: 1em;
  width: 60%;
}
.skeleton--block {
  width: 100%;
  height: 100%;
  min-height: 80px;
}
.skeleton--circle {
  border-radius: var(--r-pill);
  aspect-ratio: 1 / 1;
}
@keyframes ds-skeleton-shimmer {
  from { background-position: 200% 0; }
  to   { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .skeleton {
    animation: none;
    background:
      color-mix(in oklch, var(--c-white) 6%, transparent);
  }
}


/* === AVATAR ================================================
   Square portrait or initials block. Square is the default
   (radius `--r-2`); the `.avatar--circle` modifier exists but
   is documented as a discouraged exception against the
   institutional aesthetic. Sizing reads from a `--size` custom
   property so authors can override per-instance; the size
   modifiers below just set that property.

     <span class="avatar"><img src="…" alt="Lena R."></span>
     <span class="avatar avatar--lg" aria-label="Lena R.">LR</span>
     <span class="avatar avatar--sm avatar--circle"
           aria-hidden="true">LR</span>

   ARIA: when rendering an <img>, the alt is the accessible name.
   When rendering initials, give the wrapper aria-label with the
   full name. */

.avatar {
  --size: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--size);
  height: var(--size);
  flex-shrink: 0;
  background: var(--c-surface);
  border: var(--border-1) solid var(--c-border);
  border-radius: var(--r-2);
  font-family: var(--f-mono);
  font-size: calc(var(--size) * 0.36);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--c-white);
  overflow: hidden;
  user-select: none;
}
.avatar img,
.avatar svg {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.avatar--sm { --size: 24px; }
.avatar--lg { --size: 48px; }
.avatar--xl { --size: 64px; }
/* Discouraged. Stellaria's institutional voice prefers square
   portraits; reach for this only when a project's existing
   data pipeline already surfaces circular avatars. */
.avatar--circle { border-radius: var(--r-pill); }


/* === SWITCH ================================================
   Track + thumb pair for binary on/off. Composes a real
   <button role="switch"> with aria-checked so screen readers
   announce state correctly.

     <button class="switch" role="switch" aria-checked="false"
             aria-label="Telemetry uplink">
       <span class="switch__track" aria-hidden="true">
         <span class="switch__thumb"></span>
       </span>
     </button>

   The class itself wires up the visual; the [aria-checked="true"]
   attribute lights the track to accent. Click handler in JS
   toggles the attribute. */

.switch {
  appearance: none;
  background: transparent;
  border: 0;
  padding: 6px;
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  /* hit-target padding above + 20px track gives 32px hit
     vertically; pair with a label so the row reaches 44px. */
}
.switch:focus-visible { outline: none; }
.switch:focus-visible .switch__track {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
.switch:disabled,
.switch[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
}
/* Sharp by default — rectangle track, square thumb. The pill
   shape is ergonomic shorthand for "I will toggle"; in this
   system the same affordance reads through the off/on color
   shift, the thumb translation, and the aria-checked state.
   No pill. No round thumb. */
.switch__track {
  position: relative;
  width: 36px;
  height: 18px;
  border-radius: 0;
  border: var(--border-1) solid var(--c-border);
  background: var(--c-surface);
  transition:
    background var(--dur-fast) var(--ease-tactical),
    border-color var(--dur-fast) var(--ease-tactical);
}
.switch__thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 12px;
  height: 12px;
  border-radius: 0;
  background: var(--c-fg-dim);
  transition:
    transform var(--dur-fast) var(--ease-tactical),
    background var(--dur-fast) var(--ease-tactical);
}
.switch[aria-checked="true"] .switch__track {
  background: var(--c-accent);
  border-color: var(--c-accent);
}
.switch[aria-checked="true"] .switch__thumb {
  transform: translateX(18px);
  background: var(--c-white);
}
@media (prefers-reduced-motion: reduce) {
  .switch__track,
  .switch__thumb { transition: none; }
}


/* === COMMAND / COMBOBOX ====================================
   Keyboard-driven command palette. Mono input bar at top, a
   scrollable list of items beneath. Each item carries
   aria-selected="true" while highlighted (cursor or hover).

     <div class="command" role="combobox" aria-haspopup="listbox"
          aria-expanded="true" aria-owns="cmd-list">
       <input class="command__input" type="text"
              placeholder="Type a command…" aria-controls="cmd-list"
              aria-autocomplete="list">
       <ul class="command__list" id="cmd-list" role="listbox">
         <li class="command__item" role="option"
             aria-selected="true">Open file…</li>
         <li class="command__item" role="option">Run task…</li>
       </ul>
     </div>

   Keyboard: ArrowUp / ArrowDown moves selection, Enter chooses.
   JS owns selection state; CSS paints aria-selected. */

.command {
  display: flex;
  flex-direction: column;
  background: var(--c-elev-2);
  border: var(--border-1) solid var(--c-border);
  border-radius: var(--r-2);
  overflow: hidden;
  width: min(100%, 480px);
}
.command__input {
  appearance: none;
  width: 100%;
  border: 0;
  border-bottom: var(--border-1) solid var(--c-border);
  background: transparent;
  color: var(--c-white);
  font-family: var(--f-mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  padding: var(--sp-2);
  min-height: 44px;
  outline: none;
}
.command__input::placeholder {
  color: var(--c-fg-muted);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  font-size: 11px;
}
.command__input:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: -2px;
}
.command__list {
  list-style: none;
  margin: 0;
  padding: 4px 0;
  max-height: 320px;
  overflow-y: auto;
}
.command__item {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 8px var(--sp-2);
  min-height: 36px;
  font-family: var(--f-display);
  font-size: var(--t-sm);
  color: var(--c-fg-dim);
  cursor: pointer;
  transition:
    background var(--dur-fast) var(--ease-tactical),
    color var(--dur-fast) var(--ease-tactical);
}
.command__item:hover,
.command__item[aria-selected="true"] {
  background: var(--c-accent-wash);
  color: var(--c-white);
}
.command__item[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
}
.command__group-label {
  padding: 8px var(--sp-2) 4px;
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--c-fg-muted);
}
.command__shortcut {
  margin-left: auto;
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  color: var(--c-fg-muted);
}
.command__empty {
  padding: var(--sp-3) var(--sp-2);
  text-align: center;
  font-family: var(--f-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--c-fg-muted);
}


/* === HOVER-CARD ============================================
   Like tooltip but a richer panel: avatar + meta + body. Wraps
   a trigger; reveals on `:hover` / `:focus-within` of the
   `.hover-card` parent. Use sparingly — only when a tooltip
   is too small to communicate the payload.

     <span class="hover-card" tabindex="0" aria-haspopup="dialog">
       @lena
       <span class="hover-card__panel" role="dialog">
         <span class="avatar avatar--lg">LR</span>
         <strong class="hover-card__name">Lena Reyes</strong>
         <span class="hover-card__meta">Imagery, Berlin</span>
         <p class="hover-card__body">…</p>
       </span>
     </span>

   ARIA: panel is role="dialog"; trigger has aria-haspopup. */

.hover-card {
  position: relative;
  display: inline-block;
}
.hover-card:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
.hover-card__panel {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: var(--z-overlay);
  width: max-content;
  max-width: 320px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: var(--sp-2);
  background: var(--c-elev-3);
  border: var(--border-1) solid var(--c-border);
  border-radius: var(--r-2);
  font-family: var(--f-display);
  font-size: var(--t-sm);
  color: var(--c-fg-dim);
  opacity: 0;
  pointer-events: none;
  transform: translateY(-2px);
  transition:
    opacity var(--dur-fast) var(--ease-tactical),
    transform var(--dur-fast) var(--ease-tactical);
}
.hover-card:hover > .hover-card__panel,
.hover-card:focus-within > .hover-card__panel {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}
.hover-card__name {
  font-family: var(--f-display);
  font-size: var(--t-sm);
  font-weight: var(--fw-medium);
  color: var(--c-white);
  letter-spacing: var(--ls-body);
}
.hover-card__meta {
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--c-fg-muted);
}
.hover-card__body {
  margin: 0;
  font-size: var(--t-sm);
  line-height: var(--lh-base);
  color: var(--c-fg-dim);
}
@media (prefers-reduced-motion: reduce) {
  .hover-card__panel { transition: opacity var(--dur-fast) var(--ease-tactical); transform: none; }
}


/* ── LISTBOX ────────────────────────────────────────────────
   A sharp, fully-styleable replacement for the native <select>.
   Built on <details>/<summary> for the no-JS path: trigger flips
   `[open]` to reveal the panel. Wrap in a <div role="combobox">
   and add `aria-expanded`/`aria-controls` via JS for full ARIA.

   Markup:
     <details class="listbox" name="aperture">
       <summary class="listbox__trigger">
         <span class="listbox__value">SAR · X-band</span>
         <svg class="listbox__chevron" aria-hidden="true">…</svg>
       </summary>
       <ul class="listbox__panel" role="listbox">
         <li class="listbox__option" role="option" aria-selected="true">SAR · X-band</li>
         <li class="listbox__option" role="option">EO · multispectral</li>
         <li class="listbox__option" role="option">IR · LWIR</li>
       </ul>
     </details>
*/

.listbox {
  position: relative;
  display: inline-block;
  width: 100%;
}
.listbox > summary { list-style: none; }
.listbox > summary::-webkit-details-marker { display: none; }
.listbox > summary::marker { content: ""; }

.listbox__trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  width: 100%;
  padding: 8px 12px;
  border: 1px solid var(--c-border);
  background: var(--c-surface);
  color: var(--c-white);
  font-family: var(--f-mono);
  font-size: 11px;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  cursor: pointer;
  border-radius: 0;
  transition:
    border-color var(--dur-fast) var(--ease-tactical),
    background var(--dur-fast) var(--ease-tactical);
}
.listbox__trigger:hover { border-color: var(--c-border-h); }
.listbox__trigger:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}

.listbox__value {
  flex: 1;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.listbox__chevron {
  width: 12px;
  height: 8px;
  color: color-mix(in oklch, var(--c-white) 70%, transparent);
  flex-shrink: 0;
  transition: transform var(--dur-fast) var(--ease-tactical);
}
.listbox[open] > summary .listbox__chevron { transform: rotate(180deg); }
.listbox[open] > summary { border-color: var(--c-accent); }

/* Panel — hairline, sharp, no shadow. Anchored below trigger. */
.listbox__panel {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  margin: 0;
  padding: 4px;
  list-style: none;
  background: var(--c-bg);
  border: 1px solid var(--c-border-h);
  border-radius: 0;
  z-index: var(--z-overlay);
  max-height: 280px;
  overflow-y: auto;
  /* Subtle entry — opacity only, no scale/translate to stay institutional. */
  animation: ds-fade-in var(--dur-fast) var(--ease-tactical) both;
}

.listbox__option {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 8px 12px;
  font-family: var(--f-mono);
  font-size: 11px;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--c-fg-dim);
  cursor: pointer;
  border-left: 2px solid transparent;
  transition:
    background var(--dur-fast) var(--ease-tactical),
    color var(--dur-fast) var(--ease-tactical),
    border-color var(--dur-fast) var(--ease-tactical);
}
.listbox__option:hover {
  background: var(--c-surface);
  color: var(--c-white);
}
.listbox__option[aria-selected="true"],
.listbox__option--selected {
  background: var(--c-accent-wash);
  border-left-color: var(--c-accent);
  color: var(--c-white);
}
.listbox__option[aria-disabled="true"] {
  opacity: 0.40;
  cursor: not-allowed;
}

/* Group separator inside the panel — hairline rule with mono caption. */
.listbox__group-label {
  padding: 6px 12px 4px;
  font-family: var(--f-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--c-fg-muted);
  border-top: 1px solid var(--c-border);
  margin-top: 4px;
}
.listbox__group-label:first-child { border-top: 0; margin-top: 0; }

/* Compact size — matches `.select--sm`. */
.listbox--sm .listbox__trigger,
.listbox--sm .listbox__option {
  font-size: 10px;
  padding: 6px 10px;
}

@media (prefers-reduced-motion: reduce) {
  .listbox__panel { animation: none; }
  .listbox__chevron { transition: none; }
}


/* ── SLIDER ────────────────────────────────────────────────
   Sharp 12×12 square thumb on a 2px hairline track. The fill
   left of the thumb is the institutional accent. The native
   <input type="range"> drives state; cross-browser styling is
   forked between WebKit/Blink and Firefox via vendor pseudos.

   Set `style="--value: 65%"` (or wire from JS) to paint the
   filled portion of the track. Pair with `.slider__value` for
   a tabular-nums readout.

   Markup (basic):
     <div class="slider">
       <input class="slider__input" type="range" min="0" max="100" value="65" style="--value: 65%">
       <output class="slider__value">65 %</output>
     </div>

   Markup (labeled with min/max):
     <div class="slider slider--labeled">
       <label class="slider__label" for="acq">Acquisition rate</label>
       <input id="acq" class="slider__input" type="range" min="0" max="100" value="40" style="--value: 40%">
       <span class="slider__min">0</span>
       <output class="slider__value">40 Hz</output>
       <span class="slider__max">100</span>
     </div>
*/

.slider {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  color: var(--c-fg-muted);
  letter-spacing: 0.10em;
}

.slider__input {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  height: 24px; /* hit area; the track is the visible 2px */
  background: transparent;
  cursor: pointer;
  margin: 0;
  padding: 0;
  /* The author sets --value: 0% to 100% to drive the fill width. */
  --value: 0%;
}

/* WebKit / Blink track + thumb */
.slider__input::-webkit-slider-runnable-track {
  height: 2px;
  background: linear-gradient(
    to right,
    var(--c-accent) 0,
    var(--c-accent) var(--value, 0%),
    var(--c-border) var(--value, 0%),
    var(--c-border) 100%
  );
  border: 0;
}
.slider__input::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 12px;
  height: 12px;
  background: var(--c-accent);
  border: 1px solid var(--c-white);
  border-radius: 0;
  margin-top: -5px; /* (track 2px - thumb 12px) / 2 */
  cursor: ew-resize;
  transition: transform var(--dur-fast) var(--ease-tactical);
}
.slider__input:hover::-webkit-slider-thumb { transform: scale(1.15); }
.slider__input:active::-webkit-slider-thumb { background: var(--c-accent-hi); }

/* Firefox track + thumb (Firefox supports a real progress pseudo) */
.slider__input::-moz-range-track {
  height: 2px;
  background: var(--c-border);
  border: 0;
}
.slider__input::-moz-range-progress {
  height: 2px;
  background: var(--c-accent);
}
.slider__input::-moz-range-thumb {
  width: 12px;
  height: 12px;
  background: var(--c-accent);
  border: 1px solid var(--c-white);
  border-radius: 0;
  cursor: ew-resize;
  transition: transform var(--dur-fast) var(--ease-tactical);
}
.slider__input:hover::-moz-range-thumb { transform: scale(1.15); }

/* Focus — outline draws on the thumb itself via the input outline,
   so we can keep it sharp without per-vendor box-shadow. */
.slider__input:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 6px;
}

/* Disabled */
.slider__input:disabled {
  opacity: 0.40;
  cursor: not-allowed;
}
.slider__input:disabled::-webkit-slider-thumb { background: var(--c-fg-muted); }
.slider__input:disabled::-moz-range-thumb     { background: var(--c-fg-muted); }

/* Tabular-nums readout to the right of the track. */
.slider__value {
  color: var(--c-white);
  font-variant-numeric: tabular-nums lining-nums;
  letter-spacing: 0.06em;
  min-width: 5ch;
  text-align: right;
}

/* Labeled variant — adds a top label row and min/max bookends. */
.slider--labeled {
  grid-template-columns: auto 1fr auto;
  grid-template-rows: auto auto;
  column-gap: var(--sp-2);
  row-gap: 4px;
}
.slider--labeled .slider__label {
  grid-column: 1 / -1;
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--c-fg-muted);
}
.slider--labeled .slider__min,
.slider--labeled .slider__max {
  font-size: 10px;
  color: var(--c-fg-muted);
  letter-spacing: 0.10em;
}
.slider--labeled .slider__min { grid-row: 2; grid-column: 1; }
.slider--labeled .slider__input { grid-row: 2; grid-column: 2; }
.slider--labeled .slider__max { grid-row: 2; grid-column: 3; }
.slider--labeled .slider__value {
  grid-row: 1;
  grid-column: -2 / -1;
  justify-self: end;
  text-transform: none;
}

/* Ticks variant — overlay a row of hairline tick marks under the track.
   Set `--ticks: 10` to control density. */
.slider--ticks {
  --ticks: 10;
}
.slider--ticks .slider__input {
  background-image:
    linear-gradient(to right, var(--c-border) 1px, transparent 1px);
  background-size: calc(100% / var(--ticks)) 6px;
  background-position: 0 calc(50% + 6px);
  background-repeat: repeat-x;
}

/* Range (dual-thumb) variant — two stacked inputs sit on the same
   2px hairline track. Each input's own per-track gradient fill is
   suppressed (otherwise both inputs paint 0→value and the result
   reads as a thick blob). The accent fill between the two thumbs
   is drawn explicitly via `.slider__track-fill`, anchored vertically
   to the same 2px line, never the full container height. */
.slider--range {
  position: relative;
  height: 24px;
}
.slider--range .slider__input {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: transparent;
}
.slider--range .slider__input::-webkit-slider-thumb { pointer-events: auto; }
.slider--range .slider__input::-moz-range-thumb     { pointer-events: auto; }

/* Suppress per-input track fill — the explicit track + track-fill below
   own the visual. Without these overrides, each input paints its own
   0→value gradient on top of the others. */
.slider--range .slider__input::-webkit-slider-runnable-track {
  background: transparent;
}
.slider--range .slider__input::-moz-range-track    { background: transparent; }
.slider--range .slider__input::-moz-range-progress { background: transparent; }

.slider--range .slider__track {
  position: absolute;
  top: 50%;
  left: 0; right: 0;
  height: 2px;
  background: var(--c-border);
  transform: translateY(-50%);
  pointer-events: none;
}
.slider--range .slider__track-fill {
  position: absolute;
  top: 50%;
  height: 2px;
  left: var(--value-low, 0%);
  right: calc(100% - var(--value-high, 100%));
  background: var(--c-accent);
  transform: translateY(-50%);
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .slider__input::-webkit-slider-thumb,
  .slider__input::-moz-range-thumb { transition: none; }
}


/* === ICON =====================================================
   Sizing wrapper for inline SVG icons. Stroke + currentColor so
   icons inherit the parent text color. Three institutional
   sizes; default is 20px (body-text height). */

.icon {
  display: inline-block;
  width: var(--icon-md);
  height: var(--icon-md);
  flex-shrink: 0;
  vertical-align: -2px;
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icon-stroke);
  stroke-linecap: round;
  stroke-linejoin: round;
  color: inherit;
}
.icon--sm { width: var(--icon-sm); height: var(--icon-sm); }
.icon--lg { width: var(--icon-lg); height: var(--icon-lg); }
.icon--xl { width: 32px; height: 32px; }
.icon--muted  { color: var(--c-fg-muted); }
.icon--accent { color: var(--c-accent); }
.icon--alert  { color: var(--c-alert); }

/* When .icon is on an <img> element, the SVG renders in isolation so
   `stroke="currentColor"` resolves to its own root color (black).
   On dark canvas this is invisible. Filter inverts to near-white so
   the icon surfaces against the panel. Override the filter in light
   theme since the original black stroke already reads against light
   surfaces. */
img.icon {
  filter: var(--icon-img-filter, invert(0.92));
}
[data-theme="light"] img.icon {
  --icon-img-filter: none;
}

/* === ICON · MASK PATTERN =========================================
   Canonical pattern for icons that must honour currentColor and
   theme switching without inline SVG. The element is a transparent
   span; the SVG path acts as a mask, the background-color fills
   the shape with whatever the parent's color is.

     <span class="icon-mask" style="--icon-src: url(assets/icons/bell.svg);"
           aria-hidden="true"></span>

   Use this in new code; the filter trick above is for legacy
   <img>-based icon markup. */
.icon-mask {
  display: inline-block;
  width: var(--icon-md);
  height: var(--icon-md);
  flex-shrink: 0;
  vertical-align: -2px;
  background-color: currentColor;
  -webkit-mask: var(--icon-src, none) center / contain no-repeat;
          mask: var(--icon-src, none) center / contain no-repeat;
}
.icon-mask--sm { width: var(--icon-sm); height: var(--icon-sm); }
.icon-mask--lg { width: var(--icon-lg); height: var(--icon-lg); }


/* === FIELD VALIDATION =========================================
   Form validation states layered on top of existing input,
   textarea, select primitives. Adds required marker, error
   and success border states, hint and error messages below
   inputs, and an error summary panel for failed forms. */

.field--required > .label::after {
  content: '';
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-left: var(--sp-1);
  background: var(--c-accent);
  vertical-align: middle;
}

.input--error,
.textarea--error,
.select--error {
  border-color: var(--c-alert);
}
.input--error:focus-visible,
.textarea--error:focus-visible,
.select--error:focus-visible {
  outline: 2px solid var(--c-alert);
  outline-offset: 2px;
  border-color: var(--c-alert);
}
.input--success { border-color: var(--c-success); }

.field__hint {
  display: block;
  margin-top: var(--sp-1);
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  color: var(--c-fg-muted);
  letter-spacing: var(--ls-body);
}
.field__error {
  display: block;
  margin-top: var(--sp-1);
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  color: var(--c-alert);
  letter-spacing: var(--ls-body);
}
.field__error[hidden] { display: none; }

.form-summary {
  padding: var(--sp-3);
  border: var(--border-1) solid var(--c-alert);
  background: color-mix(in oklch, var(--c-alert) 8%, transparent);
  border-radius: var(--r-2);
}
.form-summary > h2 {
  font-size: var(--t-h4);
  font-weight: var(--fw-semibold);
  color: var(--c-white);
  margin-bottom: var(--sp-2);
}
.form-summary > ul {
  list-style: none;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.form-summary a {
  color: var(--c-alert);
  border-bottom: 1px solid currentColor;
}
.form-summary a:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}


/* === DATA TABLE ===============================================
   Institutional data table. Sticky header row, sortable columns
   via .dt__sort buttons, density modifiers (dense, comfortable),
   numeric and actions cell variants, selected-row highlight. */

.dt {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--f-display);
  font-size: var(--t-sm);
  color: var(--c-fg-dim);
}
.dt thead th {
  position: sticky;
  top: 0;
  background: var(--c-elev-1);
  border-bottom: var(--border-1) solid var(--c-border);
  padding: var(--sp-2) var(--sp-3);
  text-align: left;
  font-weight: var(--fw-medium);
  color: var(--c-fg-muted);
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: var(--ls-mono-cap);
  text-transform: uppercase;
  white-space: nowrap;
}
.dt tbody td {
  padding: var(--sp-2) var(--sp-3);
  border-bottom: var(--border-1) solid var(--c-border);
  color: var(--c-fg-dim);
}
.dt tbody tr {
  transition: background var(--dur-fast) var(--ease-tactical);
}
.dt tbody tr:hover {
  background: color-mix(in oklch, var(--c-white) 3%, transparent);
}
.dt tbody tr.is-selected {
  background: var(--c-accent-wash);
}
.dt--dense thead th,
.dt--dense tbody td {
  padding: var(--sp-1) var(--sp-2);
  font-size: var(--t-xs);
}
.dt--comfortable thead th,
.dt--comfortable tbody td {
  padding: var(--sp-3) var(--sp-4);
}
.dt__sort {
  appearance: none;
  background: transparent;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
}
.dt__sort:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
.dt__sort[aria-sort="ascending"]::after,
.dt__sort[aria-sort="descending"]::after {
  content: '';
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-bottom: 4px solid var(--c-accent);
}
.dt__sort[aria-sort="descending"]::after {
  transform: rotate(180deg);
}
.dt__cell--num {
  text-align: right;
  font-variant-numeric: tabular-nums lining-nums;
  font-family: var(--f-mono);
}
.dt__cell--actions {
  width: 1%;
  white-space: nowrap;
}
.dt__row--header {
  background: var(--c-elev-2);
}
.dt__empty {
  padding: var(--sp-6);
  text-align: center;
  color: var(--c-fg-muted);
}
@media (max-width: 720px) {
  .dt { font-size: var(--t-xs); }
  .dt thead th,
  .dt tbody td { padding: var(--sp-1) var(--sp-2); }
}
@media (prefers-reduced-motion: reduce) {
  .dt tbody tr { transition: none; }
}


/* === DROPDOWN MENU ============================================
   Anchored menu surface for actions and selections. Use ARIA
   menu pattern: role="menu" on the list, role="menuitem" on
   each item. Toggle .menu__list[hidden] from JS. */

.menu {
  position: relative;
  display: inline-block;
}
.menu__list {
  position: absolute;
  top: calc(100% + var(--sp-1));
  left: 0;
  min-width: 180px;
  padding: var(--sp-1) 0;
  margin: 0;
  background: var(--c-elev-2);
  border: var(--border-1) solid var(--c-border);
  border-radius: var(--r-2);
  list-style: none;
  z-index: var(--z-overlay);
}
.menu__list[hidden] { display: none; }
.menu__item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-3);
  font-family: var(--f-display);
  font-size: var(--t-sm);
  color: var(--c-fg-dim);
  cursor: pointer;
  user-select: none;
}
.menu__item:hover,
.menu__item:focus-visible,
.menu__item[aria-selected="true"] {
  background: var(--c-accent-wash);
  color: var(--c-white);
  outline: none;
}
.menu__item--danger { color: var(--c-alert); }
.menu__item--disabled,
.menu__item[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
}
.menu__shortcut {
  margin-left: auto;
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  color: var(--c-fg-muted);
}
.menu__separator {
  height: 1px;
  background: var(--c-border);
  margin: var(--sp-1) 0;
  border: 0;
}


/* === POPOVER ==================================================
   Richer than a tooltip. Holds avatars, mini forms, and meta
   blocks. Position via JS-set --pop-x / --pop-y custom properties
   on the element. Toggle hidden attribute to show or hide. */

.popover {
  position: absolute;
  top: var(--pop-y, 100%);
  left: var(--pop-x, 0);
  z-index: var(--z-overlay);
  min-width: 240px;
  max-width: 360px;
  padding: var(--sp-3);
  background: var(--c-elev-2);
  border: var(--border-1) solid var(--c-border);
  border-radius: var(--r-2);
  color: var(--c-fg-dim);
}
.popover[hidden] { display: none; }
.popover__header {
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  color: var(--c-fg-muted);
  margin-bottom: var(--sp-2);
  text-transform: uppercase;
  letter-spacing: var(--ls-mono-cap);
}
.popover__body {
  font-family: var(--f-display);
  font-size: var(--t-sm);
  line-height: var(--lh-base);
  color: var(--c-fg-dim);
}
.popover__footer {
  margin-top: var(--sp-2);
  padding-top: var(--sp-2);
  border-top: var(--border-1) solid var(--c-border);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}


/* === KBD ======================================================
   Inline keyboard shortcut indicator. Mono, tight, capped with
   a thicker bottom border that reads as a key edge. Chain with
   adjacent .kbd for chord shortcuts (Cmd K). */

.kbd {
  display: inline-block;
  padding: 2px 6px; /* allow-raw */
  min-width: 1.5em;
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  color: var(--c-fg-dim);
  background: var(--c-surface);
  border: var(--border-1) solid var(--c-border);
  border-bottom-width: 2px;
  border-radius: var(--r-1);
  text-align: center;
  line-height: 1;
  vertical-align: 1px;
  white-space: nowrap;
}
.kbd + .kbd { margin-left: var(--sp-1); }


/* === CODE BLOCK ===============================================
   Pre-formatted code surface. Header strip holds a language label
   and a copy button; content is mono, line-height base, horizontal
   scroll for long lines. No syntax highlighting at the CSS layer. */

.code-block {
  position: relative;
  background: var(--c-bg);
  border: var(--border-1) solid var(--c-border);
  border-radius: var(--r-2);
  overflow: hidden;
  margin-block: var(--sp-3);
}
.code-block__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-1) var(--sp-3);
  background: var(--c-elev-1);
  border-bottom: var(--border-1) solid var(--c-border);
}
.code-block__label {
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  color: var(--c-fg-muted);
  text-transform: uppercase;
  letter-spacing: var(--ls-mono-cap);
}
.code-block__copy {
  appearance: none;
  background: transparent;
  border: 0;
  padding: var(--sp-1) var(--sp-2);
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  color: var(--c-fg-muted);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-tactical);
}
.code-block__copy:hover { color: var(--c-white); }
.code-block__copy:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
.code-block__content {
  display: block;
  padding: var(--sp-3);
  overflow-x: auto;
  font-family: var(--f-mono);
  font-size: var(--t-sm);
  color: var(--c-fg-dim);
  line-height: var(--lh-base);
  white-space: pre;
}
@media (prefers-reduced-motion: reduce) {
  .code-block__copy { transition: none; }
}


/* === SPINNER ==================================================
   Indeterminate progress indicator. Use only when the wait is
   unbounded; for known durations use the determinate .progress
   bar. Sized to match icon tokens. */

.spinner {
  display: inline-block;
  width: var(--icon-md);
  height: var(--icon-md);
  border: 2px solid var(--c-border);
  border-top-color: var(--c-accent);
  border-radius: 50%;
  animation: ds-spinner 800ms linear infinite;
  vertical-align: -3px;
}
.spinner--sm { width: var(--icon-sm); height: var(--icon-sm); border-width: 2px; }
.spinner--lg { width: var(--icon-lg); height: var(--icon-lg); border-width: 2.5px; }
.spinner--xl { width: 40px; height: 40px; border-width: 3px; }
.spinner--accent { border-color: var(--c-accent-wash); border-top-color: var(--c-accent); }
.spinner--alert  { border-color: color-mix(in oklch, var(--c-alert) 25%, transparent); border-top-color: var(--c-alert); }

@keyframes ds-spinner {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation: none;
    border-top-color: var(--c-accent);
  }
}


/* === ALERT =====================================================
   Inline persistent notice. Hairline panel with a 3px accent
   rail on the left so the severity reads at a glance without a
   filled background. Variants tint the rail color only. */

.alert {
  display: grid;
  grid-template-columns: 20px 1fr auto;
  gap: var(--sp-2);
  padding: var(--sp-3);
  background: var(--c-surface);
  border: var(--border-1) solid var(--c-border);
  border-left: var(--border-3) solid var(--c-accent);
  /* Top-align so the icon sits beside the title line in multi-line
     alerts, not floating in the body's geometric middle. */
  align-items: start;
}
.alert__icon {
  width: 20px;
  height: 20px;
  color: var(--c-fg-muted);
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Nudge down 2px so a 20px icon's optical centre aligns with the
     title's optical centre, not its line-box top. */
  margin-top: 2px; /* allow-raw */
}
.alert__icon svg,
.alert__icon img { width: 100%; height: 100%; }
.alert__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  min-width: 0;
}
.alert__title {
  font-family: var(--f-display);
  font-size: var(--t-sm);
  font-weight: var(--fw-medium);
  color: var(--c-white);
  letter-spacing: var(--ls-body);
}
.alert__msg {
  font-family: var(--f-display);
  font-size: var(--t-sm);
  color: var(--c-fg-dim);
  line-height: var(--lh-base);
}
.alert__actions {
  display: flex;
  gap: var(--sp-2);
  margin-top: var(--sp-1);
}
.alert__close {
  appearance: none;
  background: transparent;
  border: 0;
  padding: 0;
  width: 20px;
  height: 20px;
  color: var(--c-fg-muted);
  font-family: var(--f-mono);
  font-size: var(--t-sm);
  cursor: pointer;
  line-height: 1;
  transition: color var(--dur-fast) var(--ease-tactical);
}
.alert__close:hover { color: var(--c-white); }
.alert__close:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
.alert--info    { border-left-color: var(--c-info); background: color-mix(in oklch, var(--c-info) 6%, var(--c-surface)); }
.alert--info    .alert__icon { color: var(--c-info); }
.alert--success { border-left-color: var(--c-success); background: color-mix(in oklch, var(--c-success) 6%, var(--c-surface)); }
.alert--success .alert__icon { color: var(--c-success); }
.alert--warn    { border-left-color: var(--c-warn); background: color-mix(in oklch, var(--c-warn) 6%, var(--c-surface)); }
.alert--warn    .alert__icon { color: var(--c-warn); }
.alert--danger  { border-left-color: var(--c-alert); background: color-mix(in oklch, var(--c-alert) 6%, var(--c-surface)); }
.alert--danger  .alert__icon { color: var(--c-alert); }
@media (prefers-reduced-motion: reduce) {
  .alert__close { transition: none; }
}


/* === PROGRESS BAR ==============================================
   Determinate horizontal bar. 4px hairline rail by default, 8px
   with --lg. Set --value: 42% on the root to drive the fill.
   Indeterminate mode runs a looping slug; reduced-motion parks
   the slug at 30% so the state still reads. */

.progress-bar {
  position: relative;
  width: 100%;
  height: 4px; /* allow-raw */
  background: color-mix(in oklch, var(--c-border) 50%, transparent);
  border-radius: var(--r-0);
  overflow: hidden;
}
.progress-bar__fill {
  position: absolute;
  inset: 0 auto 0 0;
  height: 100%;
  width: var(--value, 0%);
  background: var(--c-accent);
  transition: width var(--dur-mid) var(--ease-tactical);
}
.progress-bar--lg { height: 8px; } /* allow-raw */
/* Indeterminate uses a sweeping gradient on the track itself so
   the bar is always visible inside the rail. The earlier translate
   approach moved a 30% fill off-screen on both sides; that is fine
   for a desktop browser at 60 fps but reads as "empty" at any
   single frame capture. Gradient-sweep keeps motion legible. */
.progress-bar--indeterminate {
  background:
    linear-gradient(
      90deg,
      color-mix(in oklch, var(--c-border) 50%, transparent) 0%,
      color-mix(in oklch, var(--c-border) 50%, transparent) 30%,
      var(--c-accent) 50%,
      color-mix(in oklch, var(--c-border) 50%, transparent) 70%,
      color-mix(in oklch, var(--c-border) 50%, transparent) 100%);
  background-size: 200% 100%;
  animation: ds-progress-bar-sweep 1.4s linear infinite;
}
.progress-bar--indeterminate .progress-bar__fill { display: none; }
@keyframes ds-progress-bar-sweep {
  from { background-position: 100% 0; }
  to   { background-position: -100% 0; }
}
.progress-bar--success .progress-bar__fill { background: var(--c-success); }
.progress-bar--warn    .progress-bar__fill { background: var(--c-warn); }
.progress-bar--alert   .progress-bar__fill { background: var(--c-alert); }
.progress-bar__label {
  display: block;
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: var(--ls-mono-cap);
  text-transform: uppercase;
  color: var(--c-fg-muted);
  margin-bottom: var(--sp-1);
}
@media (prefers-reduced-motion: reduce) {
  .progress-bar__fill { transition: none; }
  .progress-bar--indeterminate {
    animation: none;
    background: color-mix(in oklch, var(--c-border) 50%, transparent);
  }
}


/* === STEPPER ===================================================
   Horizontal multi-step indicator. Each step is a numbered node
   with a label; the connector line between nodes fills in as
   prior steps complete. Use --vertical to flip the axis. */

.stepper {
  display: flex;
  gap: clamp(16px, 3vw, 32px); /* allow-raw */
  position: relative;
  margin: 0;
  padding: 0;
  list-style: none;
}
.stepper__step {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  align-items: flex-start;
  flex: 1;
  position: relative;
  min-width: 0;
}
.stepper__num {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border: var(--border-1) solid var(--c-border);
  background: var(--c-bg);
  color: var(--c-fg-muted);
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  font-weight: var(--fw-medium);
  line-height: 1;
  /* Geist Mono's intrinsic metrics put digit caps slightly above
     the em-box centre; this 1px nudge realigns the optical centre. */
  padding-top: 1px; /* allow-raw */
  font-variant-numeric: tabular-nums;
  position: relative;
  z-index: 1;
  transition: color var(--dur-fast) var(--ease-tactical),
              background var(--dur-fast) var(--ease-tactical),
              border-color var(--dur-fast) var(--ease-tactical);
}
.stepper__label {
  display: block;
  font-family: var(--f-display);
  font-size: var(--t-sm);
  color: var(--c-fg-dim);
  letter-spacing: var(--ls-body);
}
/* Horizontal connector: starts at the bubble's right edge, extends
   past the parent's `gap` so the line meets the next step's bubble
   without an air gap. The `clamp` mirrors the .stepper `gap` value. */
.stepper__step:not(:last-child)::after {
  content: '';
  position: absolute;
  top: 12px; /* allow-raw */
  left: 24px; /* allow-raw — right edge of the 24px num bubble */
  right: calc(-1 * clamp(16px, 3vw, 32px));
  height: 1px;
  background: var(--c-border);
  z-index: 0;
}
.stepper__step--current .stepper__num {
  border-color: var(--c-accent);
  color: var(--c-accent);
}
.stepper__step--current .stepper__label { color: var(--c-white); }
.stepper__step--done .stepper__num {
  background: var(--c-accent);
  color: var(--c-on-accent);
  border-color: var(--c-accent);
}
.stepper__step--done::after { background: var(--c-accent); }
.stepper__step:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
.stepper--vertical {
  flex-direction: column;
  gap: var(--sp-3);
}
.stepper--vertical .stepper__step { flex-direction: row; gap: var(--sp-2); align-items: flex-start; }
/* Label container inside a vertical step must expand to fill the
   remaining row width or labels truncate next to the num bubble. */
.stepper--vertical .stepper__step > div { flex: 1; min-width: 0; }
/* Vertical connector: drops from the bubble's bottom edge through
   the parent's `gap` (var(--sp-3)) so the line meets the next
   bubble's top without an air gap. */
.stepper--vertical .stepper__step:not(:last-child)::after {
  top: 24px; /* allow-raw — bottom edge of the 24px num bubble */
  left: 12px; /* allow-raw */
  right: auto;
  bottom: calc(-1 * var(--sp-3));
  width: 1px;
  height: auto;
}
/* Description sub-text below the label inside a step. */
.stepper__desc {
  display: block;
  margin: 2px 0 0; /* allow-raw */
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  color: var(--c-fg-muted);
  letter-spacing: 0.04em; /* allow-raw */
  max-width: 60ch;
}
@media (prefers-reduced-motion: reduce) {
  .stepper__num { transition: none; }
}


/* === STAT TREND ================================================
   Extends the existing .stat block with a trend row. The arrow
   glyph is colored by direction; the delta inside it is the
   numeric change (e.g. "+12%"). The spark is a wrapper for a
   user-supplied inline SVG sparkline. */

.stat__trend {
  display: inline-flex;
  align-items: center;
  gap: 2px; /* allow-raw */
  margin-top: var(--sp-1);
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
}
/* Trend arrows are Lucide icons painted via CSS mask, so the
   indicator inherits the parent's tone and stays on the bundled
   icon system (no unicode shapes). */
.stat__trend::before {
  content: '';
  display: inline-block;
  width: 12px; /* allow-raw */
  height: 12px; /* allow-raw */
  margin-right: 2px; /* allow-raw */
  background-color: currentColor;
  -webkit-mask: var(--trend-icon, none) center / contain no-repeat;
          mask: var(--trend-icon, none) center / contain no-repeat;
  vertical-align: -1px; /* allow-raw */
}
/* SVG paths inlined as data URLs so the mask is same-origin and
   renders from file:// pages (cross-origin mask URLs are blocked). */
.stat__trend--up   { color: var(--c-success); --trend-icon: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 12 7-7 7 7'/%3E%3Cpath d='M12 19V5'/%3E%3C/svg%3E"); }
.stat__trend--down { color: var(--c-alert);   --trend-icon: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 5v14'/%3E%3Cpath d='m19 12-7 7-7-7'/%3E%3C/svg%3E"); }
.stat__trend--flat { color: var(--c-fg-muted); --trend-icon: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14'/%3E%3C/svg%3E"); }
.stat__spark {
  display: inline-block;
  width: 60px;
  height: 16px;
  margin-top: var(--sp-1);
  color: var(--c-accent);
}
.stat__spark svg { width: 100%; height: 100%; display: block; }


/* === KPI =======================================================
   Headline metric panel. One number, one label, an optional
   delta and sparkline, a mono footer for context. Sized for
   dashboard grids; the value scales fluidly with viewport. */

.kpi {
  display: grid;
  gap: var(--sp-2);
  padding: var(--sp-4);
  background: var(--c-surface);
  border: var(--border-1) solid var(--c-border);
}
.kpi__label {
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  text-transform: uppercase;
  letter-spacing: var(--ls-mono-cap);
  color: var(--c-fg-muted);
}
.kpi__value {
  font-family: var(--f-display);
  font-size: clamp(28px, 4vw, 48px); /* allow-raw */
  font-weight: var(--fw-medium);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-heading);
  color: var(--c-white);
  font-variant-numeric: tabular-nums;
}
.kpi__delta {
  display: inline-flex;
  align-items: center;
  gap: 4px; /* allow-raw */
  font-family: var(--f-mono);
  font-size: var(--t-sm);
  color: var(--c-fg-muted);
  font-variant-numeric: tabular-nums;
}
/* Lucide arrows via mask, same as .stat__trend. The literal
   unicode triangles previously used in markup are removed; the
   pseudo-element owns the indicator. */
.kpi__delta::before {
  content: '';
  display: inline-block;
  width: 14px; /* allow-raw */
  height: 14px; /* allow-raw */
  margin-right: 2px; /* allow-raw */
  background-color: currentColor;
  -webkit-mask: var(--delta-icon, none) center / contain no-repeat;
          mask: var(--delta-icon, none) center / contain no-repeat;
  vertical-align: -2px; /* allow-raw */
}
.kpi__delta--up   { color: var(--c-success); --delta-icon: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 12 7-7 7 7'/%3E%3Cpath d='M12 19V5'/%3E%3C/svg%3E"); }
.kpi__delta--down { color: var(--c-alert);   --delta-icon: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 5v14'/%3E%3Cpath d='m19 12-7 7-7-7'/%3E%3C/svg%3E"); }
.kpi__spark {
  height: 32px;
  margin-top: var(--sp-2);
  color: var(--c-accent);
}
.kpi__spark svg { width: 100%; height: 100%; display: block; }
.kpi__footer {
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  color: var(--c-fg-muted);
  padding-top: var(--sp-2);
  border-top: var(--border-1) solid var(--c-border);
}


/* === INPUT NUM =================================================
   Number stepper. Hairline field with a centered tabular value
   and minus/plus chrome on either edge. Focus paints on the
   container via :focus-within; button rings are inset so they
   stay inside the field's hairline. */

.input-num {
  display: inline-flex;
  height: 32px;
  border: var(--border-1) solid var(--c-border);
  background: var(--c-bg);
  align-items: stretch;
}
.input-num:focus-within { border-color: var(--c-accent); }
.input-num__btn {
  appearance: none;
  width: 28px;
  background: transparent;
  border: 0;
  color: var(--c-fg-dim);
  font-family: var(--f-mono);
  font-size: var(--t-sm);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color var(--dur-fast) var(--ease-tactical),
              background var(--dur-fast) var(--ease-tactical);
}
.input-num__btn:first-child { border-right: var(--border-1) solid var(--c-border); }
.input-num__btn:last-child  { border-left:  var(--border-1) solid var(--c-border); }
.input-num__btn:hover {
  color: var(--c-white);
  background: var(--c-surface);
}
.input-num__btn:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: -2px; /* allow-raw */
}
.input-num__btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.input-num__input {
  flex: 1;
  background: transparent;
  border: 0;
  padding: 0 var(--sp-2);
  font-family: var(--f-mono);
  font-size: var(--t-sm);
  color: var(--c-white);
  font-variant-numeric: tabular-nums;
  text-align: center;
  min-width: 56px;
}
.input-num__input:focus-visible { outline: none; }
.input-num__input::-webkit-outer-spin-button,
.input-num__input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.input-num__input[type='number'] { -moz-appearance: textfield; }
@media (prefers-reduced-motion: reduce) {
  .input-num__btn { transition: none; }
}


/* === TAG INPUT =================================================
   Multi-value entry. Chips render inline alongside the text
   cursor; users type to add, click \00d7  to remove. Container
   shows focus via :focus-within so the inner input has no ring. */

.tag-input {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 6px; /* allow-raw */
  padding: 6px 8px; /* allow-raw */
  min-height: 36px;
  border: var(--border-1) solid var(--c-border);
  background: var(--c-bg);
  align-items: center;
}
.tag-input:focus-within { border-color: var(--c-accent); }
.tag-input__chip {
  display: inline-flex;
  align-items: center;
  gap: 4px; /* allow-raw */
  padding: 2px 6px; /* allow-raw */
  background: var(--c-accent-wash);
  color: var(--c-white);
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.06em;
}
.tag-input__remove {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--c-fg-muted);
  cursor: pointer;
  font-family: var(--f-mono);
  font-size: 14px; /* allow-raw */
  line-height: 1;
  padding: 0 2px; /* allow-raw */
  transition: color var(--dur-fast) var(--ease-tactical);
}
.tag-input__remove:hover { color: var(--c-white); }
.tag-input__remove:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 1px;
}
.tag-input__input {
  background: transparent;
  border: 0;
  padding: 0;
  min-width: 80px;
  flex: 1;
  font-family: var(--f-display);
  font-size: var(--t-sm);
  color: var(--c-white);
}
.tag-input__input:focus-visible { outline: none; }
@media (prefers-reduced-motion: reduce) {
  .tag-input__remove { transition: none; }
}


/* === MULTISELECT ===============================================
   Inline-block field that opens a checklist. The trigger shows
   selected items as chips; the list anchors to the field with a
   small gap and floats above page content via --z-overlay. */

.multiselect {
  position: relative;
  display: inline-block;
  min-width: 200px;
}
.multiselect__trigger {
  appearance: none;
  width: 100%;
  min-height: 36px;
  padding: 6px 32px 6px 8px; /* allow-raw */
  border: var(--border-1) solid var(--c-border);
  background: var(--c-surface);
  color: var(--c-white);
  font-family: var(--f-display);
  font-size: var(--t-sm);
  cursor: pointer;
  text-align: left;
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px; /* allow-raw */
  align-items: center;
  position: relative;
}
.multiselect__trigger::after {
  content: '\25be';
  position: absolute;
  right: 8px; /* allow-raw */
  top: 50%;
  transform: translateY(-50%);
  color: var(--c-fg-muted);
  font-family: var(--f-mono);
  font-size: 10px; /* allow-raw */
  line-height: 1;
}
.multiselect__trigger:hover { border-color: var(--c-border-h); }
.multiselect__trigger:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
.multiselect__chip {
  display: inline-flex;
  align-items: center;
  padding: 2px 6px; /* allow-raw */
  background: var(--c-accent-wash);
  color: var(--c-white);
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.06em;
}
.multiselect__placeholder {
  color: var(--c-fg-muted);
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.multiselect__list {
  position: absolute;
  top: calc(100% + 4px); /* allow-raw */
  left: 0;
  right: 0;
  max-height: 280px;
  overflow-y: auto;
  padding: var(--sp-1) 0;
  margin: 0;
  background: var(--c-elev-2);
  border: var(--border-1) solid var(--c-border);
  z-index: var(--z-overlay);
  list-style: none;
}
.multiselect__list[hidden] { display: none; }
.multiselect__item {
  padding: var(--sp-1) var(--sp-3);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--f-display);
  font-size: var(--t-sm);
  color: var(--c-fg-dim);
  cursor: pointer;
}
.multiselect__item::before {
  content: '';
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  display: inline-block;
  background-color: transparent;
}
/* SVG path inlined as data URL so the mask works from file:// pages
   (cross-origin mask URLs are blocked on file://). */
.multiselect__item[aria-selected='true']::before {
  background-color: var(--c-accent);
  -webkit-mask: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / contain no-repeat;
}
.multiselect__item:hover,
.multiselect__item:focus-visible {
  background: var(--c-accent-wash);
  color: var(--c-white);
  outline: none;
}


/* === INPUT GROUP ===============================================
   Field with addons on either side. Prefix and suffix carry
   units, tokens, or icons; they share the field's hairline and
   sit on --c-surface so they read as chrome, not content. */

.input-group {
  display: inline-flex;
  align-items: stretch;
  min-height: 36px;
  border: var(--border-1) solid var(--c-border);
  background: var(--c-bg);
}
.input-group:focus-within { border-color: var(--c-accent); }
.input-group__prefix,
.input-group__suffix {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--sp-2);
  background: var(--c-surface);
  color: var(--c-fg-muted);
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.06em;
  white-space: nowrap;
}
.input-group__prefix { border-right: var(--border-1) solid var(--c-border); }
.input-group__suffix { border-left:  var(--border-1) solid var(--c-border); }
.input-group__input {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: 0;
  padding: 0 var(--sp-2);
  font-family: var(--f-display);
  font-size: var(--t-sm);
  color: var(--c-white);
}
.input-group__input::placeholder { color: var(--c-fg-muted); }
.input-group__input:focus-visible { outline: none; }


/* === DATEPICKER ================================================
   Field that opens a month calendar. Days render in a 7-column
   grid with hairline gaps; today wears the accent color, the
   selected day fills with accent, in-range days carry the
   accent wash. */

.datepicker {
  position: relative;
  display: inline-block;
}
.datepicker__field {
  cursor: pointer;
}
.datepicker__cal {
  position: absolute;
  top: calc(100% + var(--sp-1));
  left: 0;
  width: 280px;
  padding: var(--sp-3);
  background: var(--c-elev-2);
  border: var(--border-1) solid var(--c-border);
  z-index: var(--z-overlay);
}
.datepicker__cal[hidden] { display: none; }
.datepicker__nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--sp-2);
}
.datepicker__nav-btn {
  appearance: none;
  background: transparent;
  border: 0;
  padding: var(--sp-1);
  cursor: pointer;
  color: var(--c-fg-dim);
  font-family: var(--f-mono);
  font-size: var(--t-sm);
  line-height: 1;
  transition: color var(--dur-fast) var(--ease-tactical);
}
.datepicker__nav-btn:hover { color: var(--c-white); }
.datepicker__nav-btn:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
.datepicker__month-label {
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: var(--ls-mono-cap);
  text-transform: uppercase;
  color: var(--c-white);
}
.datepicker__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 1px;
}
.datepicker__weekday {
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  letter-spacing: var(--ls-mono-cap);
  text-transform: uppercase;
  color: var(--c-fg-muted);
  text-align: center;
  padding: 4px 0; /* allow-raw */
}
.datepicker__day {
  appearance: none;
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid transparent;
  font-family: var(--f-mono);
  font-size: var(--t-sm);
  color: var(--c-fg-dim);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-tactical),
              background var(--dur-fast) var(--ease-tactical),
              border-color var(--dur-fast) var(--ease-tactical);
}
.datepicker__day:hover {
  border-color: var(--c-border-h);
  color: var(--c-white);
}
.datepicker__day:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: -2px; /* allow-raw */
}
.datepicker__day--today { color: var(--c-accent); }
.datepicker__day--selected {
  background: var(--c-accent);
  color: var(--c-on-accent);
  border-color: var(--c-accent);
}
.datepicker__day--out-of-month {
  color: var(--c-fg-muted);
  opacity: 0.5;
}
.datepicker__day--in-range {
  background: var(--c-accent-wash);
  color: var(--c-white);
}
.datepicker__day:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}
@media (prefers-reduced-motion: reduce) {
  .datepicker__nav-btn,
  .datepicker__day { transition: none; }
}


/* === TIMELINE ==================================================
   Vertical event log. A hairline rail runs the full height; each
   event hangs a node off it. Modifier classes tint the node for
   current state or alert priority. */

.timeline {
  position: relative;
  padding-left: var(--sp-4);
  list-style: none;
  margin: 0;
}
.timeline::before {
  content: '';
  position: absolute;
  left: 7px; /* allow-raw */
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--c-border);
}
.timeline__event {
  position: relative;
  padding-bottom: var(--sp-3);
}
.timeline__event::before {
  content: '';
  position: absolute;
  left: calc(-1 * var(--sp-4) + 3px); /* allow-raw */
  top: 4px; /* allow-raw */
  width: 9px;
  height: 9px;
  background: var(--c-bg);
  border: 1px solid var(--c-fg-muted);
  border-radius: var(--r-pill);
  box-sizing: border-box;
}
.timeline__event--current::before {
  background: var(--c-accent);
  border-color: var(--c-accent);
}
.timeline__event--alert::before {
  background: var(--c-alert);
  border-color: var(--c-alert);
}
.timeline__event--success::before {
  background: var(--c-success);
  border-color: var(--c-success);
}
.timeline__time {
  display: block;
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  color: var(--c-fg-muted);
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
  margin-bottom: 2px; /* allow-raw */
}
.timeline__title {
  display: block;
  font-family: var(--f-display);
  font-size: var(--t-sm);
  color: var(--c-white);
  font-weight: var(--fw-medium);
}
.timeline__body {
  display: block;
  font-family: var(--f-display);
  font-size: var(--t-xs);
  color: var(--c-fg-dim);
  margin-top: 2px; /* allow-raw */
  line-height: var(--lh-base);
}
.timeline__actor {
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  color: var(--c-fg-muted);
}


/* === TREE ======================================================
   Hierarchical list with expand/collapse toggles. Toggle glyph
   rotates 90deg when the item is open; selection paints a wash
   on the row. Reduced-motion stills the rotation. */

.tree {
  list-style: none;
  margin: 0;
  padding: 0;
  font-family: var(--f-display);
  font-size: var(--t-sm);
}
.tree__item { display: block; }
.tree__row {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 4px var(--sp-1); /* allow-raw */
  color: var(--c-fg-dim);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-tactical),
              color var(--dur-fast) var(--ease-tactical);
}
.tree__row:hover {
  background: color-mix(in oklch, var(--c-white) 3%, transparent);
  color: var(--c-white);
}
.tree__row:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: -2px; /* allow-raw */
}
.tree__item--selected > .tree__row {
  background: var(--c-accent-wash);
  color: var(--c-white);
}
.tree__toggle {
  appearance: none;
  width: 14px;
  height: 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  color: var(--c-fg-muted);
  font-family: var(--f-mono);
  font-size: 10px; /* allow-raw */
  line-height: 1;
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
}
.tree__toggle::before {
  content: '';
  display: inline-block;
  width: 12px; /* allow-raw */
  height: 12px; /* allow-raw */
  background-color: currentColor;
  /* Inline data URL so the mask works from file:// pages. */
  -webkit-mask: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E") center / contain no-repeat;
  transition: transform var(--dur-fast) var(--ease-tactical);
}
/* Empty toggle slot for leaf items so indentation matches branches. */
.tree__toggle--placeholder {
  display: inline-block;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}
.tree__toggle--placeholder::before { content: none; }
.tree__item[aria-expanded='true'] > .tree__row > .tree__toggle::before {
  transform: rotate(90deg);
}
.tree__label { flex: 1; min-width: 0; }
.tree__icon {
  width: 14px;
  height: 14px;
  color: var(--c-fg-muted);
  flex-shrink: 0;
}
.tree__children {
  list-style: none;
  margin: 0;
  padding-left: var(--sp-3);
  display: none;
}
.tree__item[aria-expanded='true'] > .tree__children { display: block; }
@media (prefers-reduced-motion: reduce) {
  .tree__row,
  .tree__toggle::before { transition: none; }
}


/* === AVATAR GROUP ==============================================
   Overlapping avatar stack. Each subsequent avatar slides
   leftward to overlap its predecessor; a hairline matching the
   page ground cuts the silhouette so the stack reads cleanly.
   The __more chip is the +N overflow indicator. */

.avatar-group {
  display: inline-flex;
  align-items: center;
}
.avatar-group > .avatar {
  border: 2px solid var(--c-bg);
}
.avatar-group > .avatar:not(:first-child) {
  margin-left: -8px; /* allow-raw */
}
.avatar-group__more {
  background: var(--c-surface);
  color: var(--c-fg-muted);
  font-family: var(--f-mono);
  font-size: var(--t-xs);
}


/* === HEATMAP CALENDAR ==========================================
   53-column year heatmap (one column per week, seven rows for
   days). Cell intensity is set via [data-level=1..5] mixing the
   accent over a near-empty wash. */

.heat-cal {
  display: grid;
  grid-template-columns: repeat(53, 12px);
  grid-auto-rows: 12px;
  gap: 2px; /* allow-raw */
  padding: var(--sp-3);
  background: var(--c-surface);
  border: var(--border-1) solid var(--c-border);
  overflow-x: auto;
}
.heat-cal__cell {
  width: 12px;
  height: 12px;
  background: color-mix(in oklch, var(--c-white) 4%, transparent);
  border-radius: var(--r-0);
}
.heat-cal__cell[data-level='1'] { background: color-mix(in oklch, var(--c-accent) 18%, transparent); }
.heat-cal__cell[data-level='2'] { background: color-mix(in oklch, var(--c-accent) 35%, transparent); }
.heat-cal__cell[data-level='3'] { background: color-mix(in oklch, var(--c-accent) 55%, transparent); }
.heat-cal__cell[data-level='4'] { background: color-mix(in oklch, var(--c-accent) 78%, transparent); }
.heat-cal__cell[data-level='5'] { background: var(--c-accent); }
.heat-cal__cell:hover {
  outline: 1px solid var(--c-white);
  outline-offset: 0;
}
.heat-cal__cell:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 1px;
}
.heat-cal__legend {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  font-family: var(--f-mono);
  font-size: var(--t-xs);
  color: var(--c-fg-muted);
  margin-top: var(--sp-2);
  letter-spacing: 0.06em;
}


/* === BADGE COUNT / DOT =========================================
   Small overlay indicator for notification counts or unread
   state. Pair with [data-badge] on the parent to absolutely
   position the badge in the top-right corner of an icon button
   or similar trigger. */

.badge--count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 4px; /* allow-raw */
  background: var(--c-accent);
  color: var(--c-on-accent);
  font-family: var(--f-mono);
  font-size: 9px; /* allow-raw */
  font-weight: var(--fw-medium);
  line-height: 1;
  letter-spacing: 0;
  border-radius: var(--r-pill);
  font-variant-numeric: tabular-nums;
}
.badge--dot {
  display: inline-block;
  min-width: 6px;
  width: 6px;
  height: 6px;
  padding: 0;
  background: var(--c-accent);
  border-radius: var(--r-pill);
}
.badge--count.badge--alert,
.badge--dot.badge--alert     { background: var(--c-alert); }
.badge--count.badge--warn,
.badge--dot.badge--warn      { background: var(--c-warn); }
.badge--count.badge--success,
.badge--dot.badge--success   { background: var(--c-success); }
[data-badge] { position: relative; }
[data-badge] > .badge--count,
[data-badge] > .badge--dot {
  position: absolute;
  top: -4px; /* allow-raw */
  right: -4px; /* allow-raw */
}


/* === DIALOG ALERT ==============================================
   Severity modifier on .dialog. Repaints the panel rail, the
   header title, and the primary CTA in the alert color so a
   destructive confirmation reads as such at first glance. */

.dialog--alert .dialog__panel {
  border-left: var(--border-3) solid var(--c-alert);
}
.dialog--alert .dialog__header h1,
.dialog--alert .dialog__header h2,
.dialog--alert .dialog__header h3 {
  color: var(--c-alert);
}
.dialog--alert .dialog__footer .btn--primary {
  background: var(--c-alert);
  border-color: var(--c-alert);
  color: var(--c-on-accent);
}
.dialog--alert .dialog__footer .btn--primary:hover {
  background: color-mix(in oklch, var(--c-alert), var(--c-white) 12%);
  border-color: color-mix(in oklch, var(--c-alert), var(--c-white) 12%);
}
.dialog--alert .dialog__footer .btn--primary:focus-visible {
  outline: 2px solid var(--c-alert);
  outline-offset: 2px;
}


/* === DRAWER ====================================================
   Side panel that slides in from the screen edge. --right is
   the default; --left flips the anchor. Use --lg for a 480px
   variant. The backdrop dims page content and sits one z-step
   below the drawer. */

.drawer {
  position: fixed;
  top: 0;
  bottom: 0;
  right: 0;
  width: 320px;
  background: var(--c-elev-2);
  border-left: var(--border-1) solid var(--c-border);
  z-index: var(--z-modal);
  display: flex;
  flex-direction: column;
  transform: translateX(100%);
  transition: transform var(--dur-mid) var(--ease-tactical);
}
.drawer--right { right: 0; left: auto; transform: translateX(100%); }
.drawer--left {
  left: 0;
  right: auto;
  border-left: 0;
  border-right: var(--border-1) solid var(--c-border);
  transform: translateX(-100%);
}
.drawer--lg { width: 480px; }
.drawer[aria-hidden='false'] { transform: translateX(0); }
.drawer__backdrop {
  position: fixed;
  inset: 0;
  background: oklch(0.05 0.01 260 / 0.6);
  z-index: calc(var(--z-modal) - 1);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-mid) var(--ease-tactical);
}
.drawer__backdrop[aria-hidden='false'] {
  opacity: 1;
  pointer-events: auto;
}
.drawer__header {
  padding: var(--sp-3);
  border-bottom: var(--border-1) solid var(--c-border);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-2);
}
.drawer__title {
  font-family: var(--f-display);
  font-size: var(--t-h4);
  font-weight: var(--fw-medium);
  color: var(--c-white);
  letter-spacing: var(--ls-heading);
  margin: 0;
}
.drawer__body {
  flex: 1;
  padding: var(--sp-3);
  overflow-y: auto;
}
.drawer__footer {
  padding: var(--sp-3);
  border-top: var(--border-1) solid var(--c-border);
  display: flex;
  gap: var(--sp-2);
  justify-content: flex-end;
}
.drawer__close {
  appearance: none;
  background: transparent;
  border: 0;
  padding: 0;
  width: 24px;
  height: 24px;
  color: var(--c-fg-muted);
  font-family: var(--f-mono);
  font-size: var(--t-body);
  line-height: 1;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-tactical);
}
.drawer__close:hover { color: var(--c-white); }
.drawer__close:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .drawer,
  .drawer__backdrop,
  .drawer__close { transition: none; }
}


/* === SHEET =====================================================
   Bottom-up panel. Caps at 80vh and grows from the bottom edge.
   The handle is decorative; consumers may make it a drag target
   in JS. Mobile-first pattern for filter/menu surfaces. */

.sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 80vh;
  background: var(--c-elev-2);
  border-top: var(--border-1) solid var(--c-border);
  z-index: var(--z-modal);
  display: flex;
  flex-direction: column;
  transform: translateY(100%);
  transition: transform var(--dur-mid) var(--ease-tactical);
}
.sheet[aria-hidden='false'] { transform: translateY(0); }
.sheet__handle {
  width: 32px;
  height: 3px; /* allow-raw */
  background: var(--c-fg-muted);
  border-radius: var(--r-pill);
  margin: var(--sp-1) auto;
  opacity: 0.5;
  flex-shrink: 0;
}
.sheet__header {
  padding: 0 var(--sp-3) var(--sp-2);
  border-bottom: var(--border-1) solid var(--c-border);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-2);
}
.sheet__title {
  font-family: var(--f-display);
  font-size: var(--t-h4);
  font-weight: var(--fw-medium);
  color: var(--c-white);
  letter-spacing: var(--ls-heading);
  margin: 0;
}
.sheet__body {
  flex: 1;
  padding: var(--sp-3);
  overflow-y: auto;
}
/* Compact action row for sheet bodies. Wider hit target than a
   ghost .btn but tighter padding than the canonical button height
   so a 5-action sheet does not stretch unreasonably tall. */
.sheet__action {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  padding: var(--sp-2) var(--sp-3);
  background: transparent;
  border: 0;
  text-align: left;
  font-family: var(--f-display);
  font-size: var(--t-sm);
  color: var(--c-fg-dim);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-tactical),
              color var(--dur-fast) var(--ease-tactical);
}
.sheet__action:hover {
  background: color-mix(in oklch, var(--c-white) 4%, transparent);
  color: var(--c-white);
}
.sheet__action:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: -2px; /* allow-raw */
}
.sheet__action--danger { color: var(--c-alert); }
.sheet__action--danger:hover { color: var(--c-alert); background: color-mix(in oklch, var(--c-alert) 8%, transparent); }
@media (prefers-reduced-motion: reduce) {
  .sheet__action { transition: none; }
}

.sheet__footer {
  padding: var(--sp-3);
  border-top: var(--border-1) solid var(--c-border);
  display: flex;
  gap: var(--sp-2);
  justify-content: flex-end;
}
@media (prefers-reduced-motion: reduce) {
  .sheet { transition: none; }
}


/* === CONTEXT MENU ==============================================
   Right-click menu. Positioned via --cx / --cy custom properties
   set by JS to the pointer coordinates. Relies on the existing
   .menu__list block for the actual chrome. */

.context-menu {
  position: fixed;
  left: var(--cx, 0);
  top: var(--cy, 0);
  z-index: var(--z-overlay);
}
.context-menu[hidden] { display: none; }


/* === SPLIT PANE ================================================
   Two-pane container with a draggable hairline handle. Default
   axis is horizontal (side-by-side); --vertical stacks them.
   The first pane sets its size via --split-size; the second
   pane fills the remainder. Handle is keyboard-focusable. */

.split-pane {
  display: flex;
  width: 100%;
  height: 100%;
  min-height: 0;
  min-width: 0;
}
.split-pane__pane {
  min-width: 0;
  min-height: 0;
  overflow: auto;
}
.split-pane__pane:first-child {
  width: var(--split-size, 50%);
  flex-shrink: 0;
}
.split-pane__pane:last-child { flex: 1; }
.split-pane__handle {
  appearance: none;
  flex-shrink: 0;
  width: 4px; /* allow-raw */
  border: 0;
  padding: 0;
  background: var(--c-border);
  cursor: col-resize;
  transition: background var(--dur-fast) var(--ease-tactical);
}
.split-pane__handle:hover,
.split-pane__handle:focus-visible {
  background: var(--c-accent);
  outline: none;
}
.split-pane--vertical { flex-direction: column; }
.split-pane--vertical .split-pane__pane:first-child {
  width: 100%;
  height: var(--split-size, 50%);
}
.split-pane--vertical .split-pane__handle {
  width: 100%;
  height: 4px; /* allow-raw */
  cursor: row-resize;
}
@media (prefers-reduced-motion: reduce) {
  .split-pane__handle { transition: none; }
}
