/* ============================================================================
   DCC COMPONENTS  ·  v2.0  ·  11 Sep 2026
   ----------------------------------------------------------------------------
   The full component and interaction layer. Loaded after dcc-tokens.css and
   before styles.css, which is then stripped down to view-specific layout.

   This replaces, as one system:
     · 4 card systems          (.card, .tile, .dm-metric, .cp-chart-card)
     · 6 input systems         (.login input, .field select, .inv-input,
                                .rae-cell textarea, .inv-search, .rae-title)
     · 6 overlay systems       (.drawer, .modal-dialog, .confirm-box,
                                .notif-panel, .fsv, .job-hover)
     · 3 scrims, 5 shadows, 14 transition durations, 2 easings
   with one of each.

   THE INTERACTION MODEL — the whole thing in five rules
   ------------------------------------------------------------------------
   1. EVERY interactive surface answers in under 120ms. A control that looks
      clickable and does nothing on hover reads as a picture of a control. The
      old stylesheet had no hover state at all on any input, and the four
      photo-add buttons' entire hover state was an undefined token.
   2. MOTION SHOWS WHERE SOMETHING CAME FROM. A drawer rises from the row that
      opened it; a toast comes from the edge it lives on; a menu grows from its
      button. Motion that does not explain an origin is decoration, and
      decoration is what "AI-generated" looks like.
   3. NOTHING MOVES ON HOVER. Hover changes surface, border and ink — never
      position or size. A card that lifts 2px under the pointer makes a dense
      table shimmer as the mouse crosses it. Press is where movement lives,
      because a press is a physical act.
   4. ONE EASING, THREE DURATIONS. --ease for everything that enters or
      responds; --ease-out for anything leaving. 110 / 170 / 260ms. A product
      with fourteen durations has no rhythm, and rhythm is most of what
      separates "designed" from "assembled".
   5. TRANSITION NAMED PROPERTIES, NEVER `transition: .15s`. The bare form
      animates every animatable property including width, height and
      background-position — it is why panels resize in visible steps. Six
      instances of it shipped in v1.188.5.

   Colour never appears as a literal here. If a value is missing, it belongs in
   dcc-tokens.css, not in this file.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   0 · MOTION
   The motion tokens — --dur-fast/base/slow, --ease, --ease-out, --ease-emph,
   --rise, --rise-sm — live in dcc-tokens.css §6, with every other token. This
   file only consumes them. Nothing below declares a token; if a value is
   missing here, it belongs there.
   ------------------------------------------------------------------------ */

/* ---------------------------------------------------------------------------
   1 · FOCUS
   One ring, on every interactive thing, visible on every surface.

   BUG THIS FIXES: styles.css sets `outline:none` inside `.login input:focus`,
   `.field select:focus`, `.inv-input:focus` and `.rae-cell textarea:focus`,
   while a later rule sets `outline:2px` on the bare `input:focus-visible`.
   `.login input:focus` is specificity (0,2,1); `input:focus-visible` is
   (0,1,1) — so the `outline:none` wins, and a keyboard user gets NO visible
   focus ring on the sign-in form or on any inventory, risk-assessment or
   settings field. The ring below is scoped with :where() (specificity 0,0,0 for
   the selector list) plus a real selector, so it stays overridable — but the
   `outline:none` declarations must come out of styles.css in the same change.
   ------------------------------------------------------------------------ */
:where(a, button, input, select, textarea, summary, [tabindex], [role="button"],
       .nav-item, .tab, .btn):focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}
/* Pointer users do not get the ring; keyboard users always do. */
:where(a, button, input, select, textarea):focus:not(:focus-visible) { outline: none; }

.skip-link {
  position: absolute; left: -9999px; top: var(--s-2); z-index: 1300;
  background: var(--brand); color: var(--text-on-brand);
  padding: var(--s-2) var(--s-4); border-radius: var(--r-md);
  font-size: var(--t-base); font-weight: var(--fw-semi); text-decoration: none;
}
.skip-link:focus { left: var(--s-3); }

/* ---------------------------------------------------------------------------
   2 · BUTTONS
   Four variants, one geometry. Hover changes surface and border; press moves.
   ------------------------------------------------------------------------ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  font-family: var(--font-body); font-size: var(--t-sm); font-weight: var(--fw-semi);
  padding: 8px 13px; border-radius: var(--r-md); border: 1px solid transparent;
  white-space: nowrap; cursor: pointer; user-select: none;
  transition: background-color var(--dur-fast) var(--ease),
              border-color     var(--dur-fast) var(--ease),
              color            var(--dur-fast) var(--ease),
              box-shadow       var(--dur-fast) var(--ease),
              transform        var(--dur-fast) var(--ease);
}
.btn svg { width: 14px; height: 14px; stroke: currentColor; fill: none;
           stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; flex: none; }
/* The press. 1px is enough — it is felt more than seen, which is the point. */
.btn:active:not(:disabled) { transform: translateY(1px); }

.btn-primary { background: var(--brand); color: var(--text-on-brand);
               box-shadow: 0 1px 2px rgba(0,0,0,.18); }
.btn-primary:hover:not(:disabled) { background: var(--brand-hover);
               box-shadow: 0 2px 8px var(--brand-tint); }
.btn-primary:disabled { background: var(--surface-3); color: var(--text-3);
               box-shadow: none; cursor: not-allowed; }

.btn-quiet { background: transparent; border-color: var(--line); color: var(--text-2); }
.btn-quiet:hover:not(:disabled) { background: var(--surface-2); color: var(--text);
               border-color: var(--line-strong); }
.btn-quiet:disabled { color: var(--text-3); border-color: var(--line-soft); cursor: not-allowed; }

/* Destructive actions are quiet until you are on them, then they are not. A
   red button sitting at rest on the screen is noise; a red button under your
   pointer is a warning. */
.btn-danger { background: var(--bad-tint); border-color: var(--bad-line); color: var(--bad-text); }
.btn-danger:hover:not(:disabled) { background: var(--bad-solid); border-color: var(--bad-solid);
               color: var(--text-on-status); }

.btn-bare { background: transparent; color: var(--text-3); padding: 6px 9px; }
.btn-bare:hover:not(:disabled) { color: var(--brand-text); background: var(--surface-2); }

.btn-sm { padding: 5px 10px; font-size: var(--t-xs); }
.btn-sm svg { width: 12px; height: 12px; }
.btn-block { width: 100%; }

/* An icon-only control still needs a name. */
.icon-btn {
  width: 32px; height: 32px; border-radius: var(--r-md); display: grid; place-items: center;
  color: var(--text-2); background: transparent; border: 1px solid transparent;
  cursor: pointer; position: relative;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.icon-btn:hover { background: var(--surface-2); color: var(--text); }
.icon-btn:active { transform: translateY(1px); }
.icon-btn svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.8; }
.icon-btn .badge-dot {
  position: absolute; top: 5px; right: 5px; width: 6px; height: 6px; border-radius: 50%;
  background: var(--bad); border: 1.5px solid var(--bg);
}

/* ---------------------------------------------------------------------------
   3 · FIELDS
   One input treatment for text, select, textarea, search and inline edit.

   Replaces `.login input`, `.field select`, `.inv-input`, `.rae-cell textarea`,
   `.inv-search input` and `.rae-head input.rae-title` — six treatments with
   radii of 9, 9, 9, 8, 7 and 6px and font sizes of 14, 14, 13.5, 13 and 13.
   Three of them hardcode `color:#fff`, which is three of the 76 `#fff`
   instances that stop the light theme working.
   ------------------------------------------------------------------------ */
.field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.field > label,
.field-label {
  font-size: var(--t-micro); font-weight: var(--fw-semi); text-transform: uppercase;
  letter-spacing: var(--ls-caps); color: var(--text-3);
  transition: color var(--dur-fast) var(--ease);
}
.field:focus-within > label { color: var(--brand-text); }

.input, .field input, .field select, .field textarea {
  width: 100%; font-family: var(--font-body); font-size: var(--t-base);
  color: var(--text); background: var(--bg-sunk);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 9px 11px;
  transition: border-color var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
/* A hover state on an input is not decoration: it is how you find out the
   thing is editable before you commit to clicking it. Six input systems in
   v1.188.5, not one of them had one. */
.input:hover:not(:disabled):not(:focus),
.field input:hover:not(:disabled):not(:focus),
.field select:hover:not(:disabled):not(:focus),
.field textarea:hover:not(:disabled):not(:focus) {
  border-color: var(--line-strong); background: var(--surface-2);
}
.input:focus, .field input:focus, .field select:focus, .field textarea:focus {
  outline: none;                                  /* replaced by the ring below */
  border-color: var(--brand-line);
  background: var(--surface);
  box-shadow: 0 0 0 3px var(--brand-tint);
}
/* Keyboard focus keeps the real ring on top of the soft one — see §1. */
.input:focus-visible, .field input:focus-visible,
.field select:focus-visible, .field textarea:focus-visible {
  outline: 2px solid var(--focus-ring); outline-offset: 1px;
}
.input::placeholder, .field input::placeholder, .field textarea::placeholder {
  color: var(--text-3); opacity: 1;
}
.input:disabled, .field input:disabled, .field select:disabled, .field textarea:disabled {
  background: var(--surface-2); color: var(--text-3); cursor: not-allowed; border-color: var(--line-soft);
}
.field textarea { resize: vertical; min-height: 76px; line-height: var(--lh-base); }
.field select { appearance: none; padding-right: 30px; cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 15px) calc(50% + 1px), calc(100% - 10px) calc(50% + 1px);
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; }

/* Error state. The field says what is wrong, in place, next to the field —
   never only in a banner at the top of a form the user has scrolled past. */
.field.is-invalid input, .field.is-invalid select, .field.is-invalid textarea {
  border-color: var(--bad-line); background: var(--bad-tint);
}
.field.is-invalid input:focus, .field.is-invalid textarea:focus {
  box-shadow: 0 0 0 3px var(--bad-tint); border-color: var(--bad);
}
.field.is-invalid > label { color: var(--bad-text); }
.field .hint { font-size: var(--t-xs); color: var(--text-3); line-height: var(--lh-snug); }
.field.is-invalid .hint { color: var(--bad-text); }
.field .hint svg { width: 12px; height: 12px; vertical-align: -2px; margin-right: 3px;
  stroke: currentColor; fill: none; stroke-width: 2; }

/* Search — a field with an affordance inside it. */
.search { position: relative; display: flex; align-items: center; }
.search svg { position: absolute; left: 10px; width: 15px; height: 15px; pointer-events: none;
  color: var(--text-3); stroke: currentColor; fill: none; stroke-width: 1.8;
  transition: color var(--dur-fast) var(--ease); }
.search input { padding-left: 32px; }
.search:focus-within svg { color: var(--brand-text); }

/* Checkboxes, radios and switches all take the brand accent. */
.chk, .rad { display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  font-size: var(--t-base); color: var(--text-2); }
.chk input, .rad input { accent-color: var(--brand); width: 15px; height: 15px; flex: none; cursor: pointer; }
.chk:hover, .rad:hover { color: var(--text); }

.switch { position: relative; display: inline-flex; align-items: center; cursor: pointer; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch .sl {
  width: 34px; height: 19px; border-radius: var(--r-full); background: var(--surface-3);
  border: 1px solid var(--line); position: relative; flex: none;
  transition: background-color var(--dur-base) var(--ease), border-color var(--dur-base) var(--ease);
}
.switch .sl::before {
  content: ''; position: absolute; top: 2px; left: 2px; width: 13px; height: 13px;
  border-radius: 50%; background: var(--text-2);
  transition: transform var(--dur-base) var(--ease-emph), background-color var(--dur-base) var(--ease);
}
.switch input:checked + .sl { background: var(--brand); border-color: var(--brand); }
.switch input:checked + .sl::before { transform: translateX(15px); background: var(--text-on-brand); }
.switch input:focus-visible + .sl { outline: 2px solid var(--focus-ring); outline-offset: 2px; }

/* ---------------------------------------------------------------------------
   4 · CARDS AND SURFACES
   One card. Variants say what KIND of thing it is, never just how it looks.

   Border, fill, radius and shadow each say "separate object". Spending all four
   on every block flattens the hierarchy — which is why the current dashboard,
   with a bordered rounded shadowed card around every single panel, reads flat
   despite all the effort in it.
   ------------------------------------------------------------------------ */
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); overflow: hidden; min-width: 0;
}
/* Only cards that DO something respond to the pointer. A static panel that
   highlights on hover is lying about being clickable. */
.card.is-interactive {
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
.card.is-interactive:hover { border-color: var(--line-strong); background: var(--surface-2); }
.card.is-interactive:active { transform: translateY(1px); }

/* Severity is a rail, not a tint. A tinted card does not scale — twenty amber
   cards is a wall of amber and nothing stands out. A 3px rail at the edge
   scales to a hundred rows and still reads at a glance. */
.card.sev-bad  { box-shadow: inset 3px 0 0 var(--bad); }
.card.sev-warn { box-shadow: inset 3px 0 0 var(--warn); }
.card.sev-info { box-shadow: inset 3px 0 0 var(--info); }

.card-h {
  display: flex; align-items: center; gap: var(--s-3);
  padding: 12px var(--s-4); border-bottom: 1px solid var(--line-soft);
}
.card-h h3 {
  font-family: var(--font-display); font-size: var(--t-sm); font-weight: var(--fw-bold);
  text-transform: uppercase; letter-spacing: var(--ls-caps); color: var(--text-2);
  white-space: nowrap;
}
.card-h .sub { font-size: var(--t-sm); color: var(--text-3); }
.card-h .right { margin-left: auto; display: flex; gap: 6px; align-items: center; }
.card-b { padding: var(--s-4); }
.card-f {
  display: flex; justify-content: flex-end; gap: var(--s-2);
  padding: 12px var(--s-4); border-top: 1px solid var(--line-soft); background: var(--bg-sunk);
}

/* The metric tile. Sibling of .card, not a separate system. */
.metric {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 14px var(--s-4); position: relative; min-width: 0;
  display: flex; flex-direction: column;
}
.metric.is-interactive { cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease); }
.metric.is-interactive:hover { border-color: var(--line-strong); background: var(--surface-2); }
.m-lab {
  font-family: var(--font-mono); font-size: var(--t-micro); letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-3); font-weight: var(--fw-semi);
  display: flex; align-items: center; gap: 6px;
}
.m-lab svg { width: 13px; height: 13px; stroke: currentColor; fill: none; stroke-width: 1.8; }
.m-fig {
  font-family: var(--font-display); font-weight: var(--fw-black); font-size: var(--t-hero);
  line-height: 1.05; margin-top: var(--s-2); color: var(--text-hi);
  font-variant-numeric: proportional-nums;   /* tabular figures look loose at size */
}
.m-fig small { font-size: var(--t-lg); font-weight: var(--fw-semi); color: var(--text-3); margin-left: 5px; }
.m-delta { font-size: var(--t-xs); margin-top: 6px; display: flex; align-items: center; gap: 5px;
  color: var(--text-3); }
.m-delta.is-up   { color: var(--ok-text); }
.m-delta.is-down { color: var(--bad-text); }
.m-delta.is-attn { color: var(--warn-text); }
.m-delta svg { width: 12px; height: 12px; stroke: currentColor; fill: none; stroke-width: 2.2; }
/* The comparison period lives in the tile, not in a tooltip. */
.m-basis { font-family: var(--font-mono); font-size: var(--t-micro); color: var(--text-3); margin-top: 3px; }
.m-corner { position: absolute; right: var(--s-4); top: var(--s-3); }

/* A cluster of metrics is ONE instrument, not four cards adrift. */
.metric-cluster {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 0;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); overflow: hidden;
}
.metric-cluster > .metric { border: none; border-right: 1px solid var(--line-soft); border-radius: 0; }
.metric-cluster > .metric:last-child { border-right: none; }

/* ---------------------------------------------------------------------------
   5 · PILLS
   Status is colour PLUS a word. Always. See DCC-UI-REVIEW §6.
   ------------------------------------------------------------------------ */
.pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--font-mono); font-size: var(--t-micro); font-weight: var(--fw-semi);
  padding: 2px 7px; border-radius: var(--r-full); border: 1px solid transparent;
  white-space: nowrap; letter-spacing: .02em; text-transform: uppercase;
}
.pill i { width: 5px; height: 5px; border-radius: 50%; background: currentColor; flex: none; }
.pill.ok    { color: var(--ok-text);    background: var(--ok-tint);    border-color: var(--ok-line); }
.pill.warn,
.pill.warning { color: var(--warn-text); background: var(--warn-tint); border-color: var(--warn-line); }
.pill.bad,
.pill.blocked { color: var(--bad-text);  background: var(--bad-tint);  border-color: var(--bad-line); }
.pill.info  { color: var(--info-text);  background: var(--info-tint);  border-color: var(--info-line); }
.pill.mut   { color: var(--text-3);     background: var(--surface-2);  border-color: var(--line); }
.pill.brand { color: var(--brand-text); background: var(--brand-tint); border-color: var(--brand-line); }

/* ---------------------------------------------------------------------------
   6 · TABLES
   A table is the densest thing on the screen and the most-read. Every pixel of
   chrome it does not spend is a row the operator can see.
   ------------------------------------------------------------------------ */
.tbl { width: 100%; border-collapse: collapse; font-size: var(--t-base); }
.tbl th {
  text-align: left; font-family: var(--font-mono); font-size: var(--t-micro);
  text-transform: uppercase; letter-spacing: var(--ls-caps); color: var(--text-3);
  font-weight: var(--fw-semi); padding: 8px var(--s-4);
  border-bottom: 1px solid var(--line); white-space: nowrap;
}
.tbl td {
  padding: 9px var(--s-4); border-bottom: 1px solid var(--line-soft);
  font-variant-numeric: tabular-nums; vertical-align: middle;
}
.tbl tbody tr:last-child td { border-bottom: none; }
.tbl tbody tr.is-clickable { cursor: pointer; }
/* The row responds; it does not move. A table that shifts under the pointer as
   you scan down it is unusable, and this is the single commonest place a
   "delightful" hover ruins a dense screen. */
.tbl tbody tr.is-clickable {
  transition: background-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.tbl tbody tr.is-clickable:hover { background: var(--surface-2); box-shadow: inset 2px 0 0 var(--brand); }
.tbl tbody tr.is-clickable:active { background: var(--surface-3); }
.c-main { font-weight: var(--fw-semi); color: var(--text); }
.c-sub  { font-family: var(--font-mono); font-size: var(--t-xs); color: var(--text-3);
          margin-top: 2px; white-space: nowrap; }
/* Sticky heads need a real background or rows show through them as they pass. */
.tbl.is-sticky thead th { position: sticky; top: var(--topbar-h); background: var(--surface); z-index: 2; }
.tbl-wrap { overflow-x: auto; }

/* ---------------------------------------------------------------------------
   7 · NAVIGATION
   ------------------------------------------------------------------------ */
.nav-item {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px;
  border-radius: var(--r-sm); color: var(--text-2);
  font-size: var(--t-base); font-weight: var(--fw-medium); text-decoration: none;
  position: relative;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.nav-item:hover { background: var(--surface-2); color: var(--text); }
.nav-item.on { color: var(--text-hi); font-weight: var(--fw-semi); background: var(--surface-2); }
/* The active marker slides between items rather than blinking on and off — the
   one place motion genuinely helps orientation. */
.nav-item.on::before {
  content: ''; position: absolute; left: 0; top: 6px; bottom: 6px; width: 2px;
  background: var(--brand); border-radius: 0 2px 2px 0;
  animation: nav-mark var(--dur-base) var(--ease-emph);
}
@keyframes nav-mark { from { transform: scaleY(0); } to { transform: scaleY(1); } }
.nav-item .ni-ic { width: 16px; height: 16px; flex: none; opacity: .8; }
.nav-item .ni-ic svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.7; }
.nav-item.on .ni-ic { color: var(--brand); opacity: 1; }
.nav-item .ct { margin-left: auto; font-family: var(--font-mono); font-size: var(--t-micro); color: var(--text-3); }
.nav-item .ct.is-alert { color: var(--bad-text); }

.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--line); margin-bottom: var(--s-4); flex-wrap: wrap; }
.tab {
  padding: 8px 13px; font-size: var(--t-base); font-weight: var(--fw-semi);
  color: var(--text-2); border-bottom: 2px solid transparent; margin-bottom: -1px;
  background: none; cursor: pointer;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.tab:hover { color: var(--text); border-bottom-color: var(--line-strong); }
.tab.on { color: var(--text-hi); border-bottom-color: var(--brand); }

/* ---------------------------------------------------------------------------
   8 · OVERLAYS
   One scrim, one elevation, one entrance, one exit.

   v1.188.5 shipped three scrims (rgba(0,0,0,.55) no blur; rgba(5,7,9,.6)
   blur 2px; rgba(6,9,14,.66) blur 3px), five shadows and six panel treatments
   with radii of 16, 16, 14, 11 and 10px. They are all the same idea.
   ------------------------------------------------------------------------ */
.scrim, .modal-scrim, .side-scrim {
  position: fixed; inset: 0; background: var(--scrim);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-base) var(--ease);
}
.scrim.on, .modal-scrim, .side-scrim.on { opacity: 1; pointer-events: auto; }

/* The shared panel. Drawer, modal, confirm, notification panel and hover card
   are all this, at different widths. */
.overlay-panel {
  background: var(--surface-overlay); border: 1px solid var(--line-strong);
  border-radius: var(--r-lg); box-shadow: var(--shadow-overlay);
  display: flex; flex-direction: column; overflow: hidden;
}

/* Entrance: a short rise and a barely-there scale. The old drawer travelled
   the full viewport height (`translate(-50%, calc(-100% - 96px))`) in 280ms,
   which reads as an object flying in rather than a panel appearing where you
   asked for it. */
@keyframes panel-in {
  from { opacity: 0; transform: translateY(var(--rise)) scale(.985); }
  to   { opacity: 1; transform: none; }
}
@keyframes panel-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(var(--rise-sm)) scale(.99); }
}
.overlay-panel.is-open    { animation: panel-in  var(--dur-base) var(--ease) both; }
/* An exit animation is not polish. Without one the panel vanishes between two
   frames and the eye loses track of where the content went — which is why
   closing a drawer in v1.188.5 feels like the page jumped. */
.overlay-panel.is-closing { animation: panel-out var(--dur-fast) var(--ease-out) both; }

/* Centred by margin, NOT by `left:50%; transform:translateX(-50%)`.
   The entrance keyframe ends at `transform: none`, which silently cancels a
   centring transform — the panel animates into place and then jumps right by
   half its own width. The shipping drawer centres with a transform AND
   animates its transform, which is the same collision waiting to happen the
   moment its animation gains an end state. Margin centring has no such
   interaction with the transform channel. */
.drawer {
  position: fixed; top: var(--s-7); left: 0; right: 0;
  margin-inline: auto;
  width: 560px; max-width: 96vw; max-height: calc(100vh - var(--s-7) * 2);
  z-index: 50;
}
.drawer.wide { width: min(1100px, 94vw); }
.modal-root {
  position: fixed; inset: 0; z-index: 120; display: none;
  align-items: flex-start; justify-content: center; padding: 64px var(--s-6) var(--s-6);
}
.modal-root.on { display: flex; }
.modal-dialog { position: relative; width: 520px; max-width: 96vw; max-height: 86vh; }
.modal-dialog.wide { width: min(760px, 94vw); }

.overlay-h {
  padding: var(--s-5) var(--s-5) var(--s-4); border-bottom: 1px solid var(--line-soft);
  position: relative; flex: none;
}
.overlay-h .cat {
  font-size: var(--t-micro); color: var(--brand-text); text-transform: uppercase;
  letter-spacing: var(--ls-eyebrow); font-weight: var(--fw-bold);
}
.overlay-h h3 {
  font-family: var(--font-display); font-size: var(--t-xl); font-weight: var(--fw-black);
  margin: 6px 0 4px; padding-right: 68px; text-wrap: balance;
}
.overlay-h .sub { font-family: var(--font-mono); font-size: var(--t-sm); color: var(--text-3); }
.overlay-b { padding: var(--s-4) var(--s-5); overflow-y: auto; flex: 1; overscroll-behavior: contain; }
.overlay-f {
  display: flex; justify-content: flex-end; gap: var(--s-2); flex-wrap: wrap;
  padding: var(--s-3) var(--s-5); border-top: 1px solid var(--line-soft);
  background: var(--bg-sunk); flex: none;
}
.overlay-x {
  position: absolute; top: var(--s-4); right: var(--s-4);
  width: 30px; height: 30px; border-radius: var(--r-sm); display: grid; place-items: center;
  color: var(--text-3); background: transparent; border: 1px solid transparent; cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.overlay-x:hover { background: var(--surface-2); color: var(--text); }
.overlay-expand { right: 56px; }
.overlay-expand:hover { color: var(--brand-text); border-color: var(--line); }

/* REMOVED, deliberately: `.drawer::after` and `.fsv.on::after` painted the DCC
   mark at 6% opacity in the bottom-right corner of every drawer and every
   full-screen view, on top of `.app-watermark` doing the same on the page
   behind it. That is the UI-4 mistake — one brand mark per screen — repeated
   inside the panel. It also sits over the last row of any scrolling list. */
.drawer::after, .fsv.on::after { content: none; }

/* Notifications and hover cards are the same panel, smaller. */
.popover {
  position: fixed; z-index: 60; width: 360px; max-width: 92vw; max-height: 70vh;
  background: var(--surface-overlay); border: 1px solid var(--line-strong);
  border-radius: var(--r-lg); box-shadow: var(--shadow-overlay); overflow-y: auto;
  opacity: 0; transform: translateY(calc(var(--rise-sm) * -1)); pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
  transform-origin: top right;
}
.popover.on { opacity: 1; transform: none; pointer-events: auto; }

/* Toast. It lives at the bottom, so it arrives from the bottom.
   BUG THIS FIXES: `.toast-wrap` is anchored `bottom:24px` and the animation is
   `@keyframes slideup{from{transform:translateY(-10px)}}` — named "slideup",
   travelling DOWN, into a stack that grows upward. */
.toast-wrap {
  position: fixed; bottom: var(--s-6); left: 50%; transform: translateX(-50%);
  z-index: 130; display: flex; flex-direction: column-reverse; gap: var(--s-2);
  align-items: center; pointer-events: none;
}
.toast {
  pointer-events: auto; display: flex; align-items: center; gap: 10px;
  background: var(--surface-overlay); border: 1px solid var(--line-strong);
  border-left: 3px solid var(--ok); border-radius: var(--r-md);
  padding: 11px var(--s-4); box-shadow: var(--shadow-overlay);
  font-size: var(--t-base); font-weight: var(--fw-medium); color: var(--text);
  animation: toast-in var(--dur-base) var(--ease) both;
}
.toast.bad  { border-left-color: var(--bad); }
.toast.warn { border-left-color: var(--warn); }
.toast.is-leaving { animation: toast-out var(--dur-fast) var(--ease-out) both; }
@keyframes toast-in  { from { opacity: 0; transform: translateY(var(--rise)); } to { opacity: 1; transform: none; } }
@keyframes toast-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(var(--rise-sm)); } }
.toast svg { width: 16px; height: 16px; flex: none; stroke: currentColor; fill: none; stroke-width: 2; }

/* ---------------------------------------------------------------------------
   9 · FEEDBACK AND EMPTY STATES
   ------------------------------------------------------------------------ */
.spinner {
  width: 20px; height: 20px; border: 2px solid var(--line);
  border-top-color: var(--brand); border-radius: 50%;
  animation: spin .7s linear infinite; margin: var(--s-7) auto;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* A skeleton reserves the FINAL height, so nothing reflows when data lands.
   The shimmer travels once per 1.4s, slowly — a fast shimmer on a dense page
   is a vestibular problem and reads as a loading screen from a phone game. */
.skel {
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface-3) 37%, var(--surface-2) 63%);
  background-size: 400% 100%; border-radius: var(--r-xs);
  animation: skel 1.4s var(--ease) infinite; min-height: 12px;
}
@keyframes skel { from { background-position: 100% 0; } to { background-position: 0 0; } }

/* An empty state offers the next action. "Nothing here" is a dead end. */
.empty {
  display: grid; place-items: center; gap: var(--s-3); padding: var(--s-7) var(--s-5);
  color: var(--text-3); font-size: var(--t-base); text-align: center;
}
.empty .empty-actions { display: flex; gap: var(--s-2); flex-wrap: wrap; justify-content: center; }

/* ---------------------------------------------------------------------------
   10 · REDUCED MOTION
   Anyone who has asked their OS to stop moving things gets a still interface.
   Opacity still changes — that is how they know something appeared.
   ------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important; scroll-behavior: auto !important;
  }
  .overlay-panel.is-open { animation: none; opacity: 1; transform: none; }
  .btn:active, .icon-btn:active, .card.is-interactive:active { transform: none; }
  .skel { animation: none; background: var(--surface-2); }
  .spinner { animation: spin 1.4s linear infinite; }   /* the one exception: a
     still spinner communicates nothing at all. Slowed, not stopped. */
}
