/* ══════════════════════════════════════════════════════════════════════════
   NIX components — the rules for every class components.js puts on the DOM.

   Load it after nix-ds.css, wherever the builders are mounted. In v0 these
   rules lived in Agent Base's styles.css, and when the system became its own
   repository they stayed behind: every builder rendered raw, and nothing said
   so. Gate law 9 now fails a builder class with no rule here or in nix-ds.css.

   Tokens only, like the rest of the system: colour, spacing, radius, type,
   motion, shadow and stacking all come from nix-ds.css, so both themes follow.
   The few literal lengths below are STRUCTURAL — the size of a box, not a step
   of a scale — and each one says why it is a number.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── § 7.1 Button ─────────────────────────────────────────────────────────
   The shared classes carry colour; .store-get-btn carries shape and size.
   v0 painted .store-get-btn with its own colour after them, so primary and
   ghost came out identical and "the one action of a view" looked like every
   other. Here the variant decides the colour and the shape never does.     */
.btn-primary {
  background: var(--action-bg);
  color: var(--action-text);
  transition: background-color var(--dur-fast) var(--ease-out);
}
.btn-primary:hover { background: var(--action-bg-hover); }

/* A control on a card is not the card's hover: --surface-control sits above
   both the card's rest and its hover, so the button never matches the row.
   The label is --text: --text-muted on the control is 3.86:1 in dark, under
   AA, and a label that has to be hovered to be read is not a label. So the
   hover cannot brighten the ink; it lifts the control instead, and every
   background it could step to is a card's rest or hover. */
.btn-ghost {
  background: var(--surface-control);
  color: var(--text);
  transition: box-shadow var(--dur-fast) var(--ease-out);
}
.btn-ghost:hover { box-shadow: var(--shadow-raised); }

.store-get-btn {
  /* 84px: the width of "$29.00", so Get and a price are the same button. */
  min-width: 84px;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  white-space: nowrap;
}
/* Disabled is opacity, the system's one value for it — never a colour. */
.store-get-btn:disabled { opacity: 0.6; cursor: default; }
.store-hero-btn { padding: var(--space-3) var(--space-5); font-size: var(--text-base); }

/* The button plus the line an install refusal is written on. */
.store-get { display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-1); }
.store-card-error { margin: 0; font-size: var(--text-xs); color: var(--danger-text); }
/* Under a Get button the red fails: an action lands on a card (--surface-fill,
   4.40:1 in dark; --surface-fill-strong on hover, 3.42:1) or on the window
   (--surface-fill-strong, 3.42:1). So wherever .store-get puts it, the refusal
   is written in --text; it is announced by the words and its place under the
   button, not by a hue. A field's error, on a panel, keeps the red.
   contrast.mjs PLACED asserts each of those grounds. */
.store-get .store-card-error { color: var(--text); }

/* ── § 7.2 Field ──────────────────────────────────────────────────────────
   The .ds-field recipe: a fill, never a hairline. Focus is the system's ring;
   invalid is the message under it and aria-invalid, never a red border.     */
.ds-field-row { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.sysset-label { font-size: var(--text-sm); color: var(--text); }
.sysset-input {
  width: 100%;
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--surface-fill);
  color: var(--text);
  font-size: var(--text-sm);
  text-align: left;
}
.sysset-input::placeholder { color: var(--text-faint); }
.sysset-input:disabled { opacity: 0.6; cursor: default; }
.sysset-hint { margin: 0; font-size: var(--text-xs); color: var(--text-faint); text-align: left; }

/* DS-2 variants. A compact field for an inline row; a strong fill for a field
   that sits straight on a panel (sign-in), whose placeholder is muted because
   faint on fill-strong is under AA; a mono value (slugs, links); a label that
   wraps its control and rests muted over it; and a prefix that ellipses
   before it can squeeze the input (it stops at 55% of the row). */
.ds-field-row--compact { flex: 1; min-width: var(--field-min-sm); }
.sysset-input--compact { padding: var(--space-1) var(--space-2); }
.sysset-input--strong {
  padding: var(--space-2) var(--space-3);
  background: var(--surface-fill-strong);
  color: var(--text);
}
.sysset-input--strong::placeholder { color: var(--text-muted); }
.sysset-input--mono { font-family: var(--font-mono); }
.sysset-label--wrapped { display: flex; flex-direction: column; gap: var(--space-1); color: var(--text-muted); }
.sysset-label--wrapped .sysset-input { font-size: var(--text-base); }
.sysset-affix { display: flex; align-items: center; gap: var(--space-1); min-width: 0; }
.sysset-affix .sysset-input { flex: 1; min-width: 0; width: auto; }
.sysset-prefix {
  flex: none;
  max-width: 55%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-faint);
}
/* Form layout: a stack at the form measure, a pair that wraps to one column
   under two field widths, a tight stack (sign-in) and an inline row of
   compact fields and their button. */
.sysset-fields { display: grid; gap: var(--space-4); max-width: var(--measure-form); }
.sysset-fields--pair {
  grid-template-columns: repeat(auto-fit, minmax(var(--field-min), 1fr));
  gap: var(--space-3);
  max-width: none;
}
.sysset-fields--tight { gap: var(--space-2); }
.sysset-fields--inline { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); max-width: none; }

/* ── § 7.5 Mark ───────────────────────────────────────────────────────────
   The initial, in three placements. Each ground is one rung away from what
   holds it, or the mark stops existing.                                     */
.store-mark-letter {
  color: var(--text-muted);
  font-size: var(--text-lg);
  /* 1, not a leading token: the letter is a glyph centred in a box, and any
     leading above the em box pushes it off the box's middle. */
  line-height: 1;
}
/* A well SINKS: the panel showing through the card, one rung below it in
   radius and in surface. */
.store-card-mark {
  /* 56px, the card's mark column (below), stated on the well too so a mark
     placed outside a card is the same square and not a sliver. */
  width: 56px;
  aspect-ratio: 1;
  border-radius: var(--radius-xs);
  background: var(--surface-panel);
  display: grid;
  place-items: center;
}
.store-creator-art {
  width: 100%;
  /* 160px: v0's widest creator column. A tile is a portrait, not a banner: in
     a row of one it stops here instead of filling the pane. */
  max-width: 160px;
  aspect-ratio: 3 / 4;
  border-radius: var(--radius-chip);
  background: var(--surface-fill);
  display: grid;
  place-items: center;
  transition: background-color var(--dur-fast) var(--ease-out);
}
.store-creator-art .store-mark-letter { font-size: var(--text-2xl); }
.store-hero-mark {
  position: absolute;
  top: var(--space-7);
  left: 0;
  right: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
}
/* Three rungs of the ramp: past that a letterform stops being a mark and
   competes with the agent's own name. --mark-ground is signed toward the
   text, so it is a shadow of the same depth in both themes. */
.store-hero-mark .store-mark-letter { font-size: calc(var(--text-2xl) * 4); color: var(--mark-ground); }
/* DS-2: the avatar — a person, not a listing, so a circle in the accent with
   the initial in its contrast ink. Larger where the pointer is a finger. */
.store-mark-avatar {
  width: var(--avatar-size);
  aspect-ratio: 1;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: var(--radius-pill);
  background: var(--accent-solid);
  color: var(--accent-contrast);
}
.store-mark-avatar .store-mark-letter { color: var(--accent-contrast); font-size: var(--text-2xs); }
@media (pointer: coarse) {
  .store-mark-avatar { width: var(--avatar-size-touch); }
  .store-mark-avatar .store-mark-letter { font-size: var(--text-xs); }
}

/* ── § 7.10 Eyebrow · 7.12 Status dot · 7.13 Verified tick ────────────── */
.store-section-title {
  margin: 0;
  font-size: var(--text-sm);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-muted);
}
/* DS-3: the panel's headings. An xs size (group, chapter and drawer
   headings); a faint tone and a violet one (words, so --accent-2-text); and a
   row, for a leading dot (a .ds-dot--lg, the drawer's colour) and a trailing
   note pushed to the end (a chapter's time range), which reads as a note, not
   as a heading: no caps, no tracking. */
.store-section-title--xs { font-size: var(--text-xs); }
.store-section-title--faint { color: var(--text-faint); }
.store-section-title--accent-2 { color: var(--accent-2-text); }
.store-section-title--row { display: flex; align-items: center; gap: var(--space-2); }
.store-section-title-trail {
  margin-inline-start: auto;
  color: var(--text-faint);
  text-transform: none;
  letter-spacing: normal;
}
.ds-dot-row { display: inline-flex; align-items: center; gap: var(--space-2); }
/* --text-muted, not --text-faint: faint is under AA on a card, and this is
   the line that says who made the thing about to be installed. */
.store-card-author { font-size: var(--text-xs); color: var(--text-muted); }
.store-verified { color: var(--text-muted); }

/* ── § 7.4 Row card ───────────────────────────────────────────────────────
   Rest is --surface-fill and hover is the step above it. A bare state layer
   would REPLACE the fill instead of layering on it.                         */
.store-card {
  display: grid;
  /* 56px: the mark's column — the well is square, so this is its size. */
  grid-template-columns: 56px minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--surface-fill);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out);
}
.store-card:hover { background: var(--surface-fill-strong); }
/* DS-2. A card with no mark, or with a trailing count or chevron, is a flex
   row: the body takes the room and the trailing parts keep their size. On a
   page ground a row rests on the panel (one rung up) and hovers to the fill. */
.store-card--plain { display: flex; align-items: center; }
.store-card--plain .store-card-body { flex: 1; }
.store-card--on-page { border-radius: var(--radius-chip); background: var(--surface-panel); }
.store-card--on-page:hover { background: var(--surface-fill); }
.store-card-count { flex: none; font-size: var(--text-xs); color: var(--text-muted); }
.store-card-chevron { flex: none; width: var(--icon-md); height: var(--icon-md); color: var(--text-faint); }
.store-card-body { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
/* One line, then an ellipsis: names are author-supplied and a grid stretches
   every card to the tallest. */
.store-card-name {
  margin: 0;
  font-size: var(--text-base);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Two lines, then an ellipsis, for the same reason. */
.store-card-summary {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ── § 7.6 Portrait tile ──────────────────────────────────────────────────
   The picture IS the tile: no card around it. */
.store-creator {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
}
.store-creator:hover .store-creator-art { background: var(--surface-fill-strong); }
.store-creator-name { font-size: var(--text-base); text-align: center; }

/* ── § 7.7 The window ─────────────────────────────────────────────────────
   One agent at the size of the room, copy at the bottom centre, and the
   chrome floating ON it rather than in a band above it.                     */
.store-hero {
  position: relative;
  /* 600px: the website's hero, so the same agent is the same size in the two
     places it is sold. */
  min-height: 600px;
  background: var(--surface-fill-strong);
  border-radius: var(--radius-xl);
  padding: var(--space-7);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  gap: var(--space-3);
  overflow: hidden;
  cursor: pointer;
}
.store-hero-detail { cursor: default; }
.store-hero-body {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  text-align: center;
}
.store-hero-name {
  margin: 0;
  max-width: 20ch;
  font-size: var(--text-2xl);
  line-height: var(--leading-tight);
  text-wrap: balance;
}
.store-hero-summary { margin: 0; max-width: 60ch; font-size: var(--text-base); color: var(--text-muted); }
/* DS-3: the bare stage (onboarding): no ground, no watermark, the copy
   centred in the room at the form measure, the title in the display weight,
   the summary a step down. The builder fades the copy in (.ds-fade). */
.store-hero--bare {
  min-height: 100%;
  justify-content: center;
  padding: var(--space-6) var(--space-4);
  background: none;
  border-radius: 0;
  overflow: visible;
  cursor: default;
}
.store-hero--bare .store-hero-body { width: 100%; max-width: var(--measure-form); }
.store-hero--bare .store-hero-name { font-weight: var(--weight-display); }
.store-hero--bare .store-hero-summary { font-size: var(--text-sm); }

/* The way back. A floating control: an overlay surface, allowed to cast. */
.store-back {
  /* 32px: the height of a filter pill beside it, so the two chrome controls
     float at one height. */
  width: 32px;
  height: 32px;
  border-radius: var(--radius-pill);
  background: var(--surface-overlay);
  box-shadow: var(--shadow-overlay);
  color: var(--text);
  display: grid;
  place-items: center;
}
/* The state layer goes OVER the overlay surface, not in place of it: a bare
   translucent background would let the window show through the control. */
.store-back:hover { background-image: linear-gradient(var(--state-hover), var(--state-hover)); }

/* On the window, both chrome controls float in its top edge. Anywhere else
   (a filter row on its own) they sit in the flow. */
.store-hero > .store-back { position: absolute; top: var(--space-3); left: var(--space-3); }
.store-hero > .store-filters {
  position: absolute;
  top: var(--space-3);
  left: 50%;
  transform: translateX(-50%);
  max-width: calc(100% - 2 * var(--space-3));
}

/* ── § 7.8 Filter row ─────────────────────────────────────────────────────
   Pills, one current, scrolling sideways rather than wrapping. An OVERLAY
   surface, not the page's, so it does not read as a hole punched through
   what it floats on — and a scroller with no scrollbar fades its last pill
   instead of guillotining it.                                               */
.store-filters {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  width: fit-content;
  max-width: 100%;
  padding: var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--surface-overlay);
  box-shadow: var(--shadow-overlay);
  overflow-x: auto;
  -webkit-mask-image: var(--fade-mask-x);
  mask-image: var(--fade-mask-x);
}
.store-filter {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  color: var(--text-muted);
  font-size: var(--text-sm);
  white-space: nowrap;
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.store-filter:hover { color: var(--text); }
.store-filter.is-active { background: var(--state-hover); color: var(--text); }
/* DS-2: chips and a segmented control. They WRAP — a handful of options, not
   a shelf — so the row drops the overlay pill, its shadow and its fade. Each
   option carries its own ground; the current one inverts like primary. */
.store-filters--chips, .store-filters--segmented {
  flex-wrap: wrap;
  width: auto;
  padding: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  overflow: visible;
  -webkit-mask-image: none;
  mask-image: none;
}
.store-filter--chip {
  padding: var(--space-1) var(--space-3);
  background: var(--surface-fill);
  color: var(--text-muted);
  font-size: var(--text-xs);
}
.store-filter--chip:hover { background: var(--surface-fill-strong); color: var(--text); }
.store-filter--chip.is-active { background: var(--action-bg); color: var(--action-text); }
/* DS-3 (the DS-2 nit, review D7): a chip worn by a <select>. The sheet's
   select:not(.pane-select) is (0,1,1) and outranked .store-filter--chip, so
   the select took --text and its own padding; this is (0,1,1) in a later
   sheet, so the chip's muted ink and padding win, with room for the chevron
   (--icon-sm, the 16px select:not() reserves). Hover and .is-active still
   win at (0,2,0); their background shorthand drops the chevron (SPEC). */
select.store-filter--chip {
  padding: var(--space-1) calc(var(--space-3) + var(--icon-sm)) var(--space-1) var(--space-3);
  color: var(--text-muted);
}
.store-filter--segment {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--surface-fill-strong);
  color: var(--text-muted);
  font-size: var(--text-xs);
}
.store-filter--segment:hover { background: var(--surface-control); color: var(--text); }
.store-filter--segment.is-active { background: var(--action-bg); color: var(--action-text); }

/* ── § 7.9 Facts list ─────────────────────────────────────────────────────
   Label and value, read rather than pressed: nothing here looks like a chip
   that filters something.                                                   */
.store-facts {
  /* 420px: a list of pairs, not a banner. Uncapped, a label and its value
     drift to opposite ends of whatever column they land in. */
  max-width: 420px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--surface-fill);
}
.store-fact {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
}
.store-fact-label { font-size: var(--text-sm); color: var(--text-muted); }
.store-fact-value { font-size: var(--text-base); text-align: right; }
/* DS-2: a list with no box of its own, and a row with a trailing action (the
   similar memories). The title is text and ellipses; the action carries the
   press; the value is a small faint note beside it. */
.store-facts--plain { max-width: none; padding: 0; background: none; }
.store-fact--action { align-items: center; font-size: var(--text-sm); }
.store-fact--action .store-fact-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text);
}
.store-fact--action .store-fact-value { font-size: var(--text-2xs); color: var(--text-faint); text-align: start; overflow-wrap: anywhere; max-width: 40%; }

.store-version {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--surface-fill);
}
.store-version-head { display: flex; justify-content: space-between; gap: var(--space-2); }
.store-version-name { font-size: var(--text-base); }
.store-version-date { font-size: var(--text-sm); color: var(--text-muted); }
.store-version-notes { margin: 0; font-size: var(--text-sm); color: var(--text-muted); }
/* DS-6: the ruled row (a memory's versions) sits on the page with a hairline
   above it, not on a card; its body scrolls; the restore action ends the head. */
.store-version--ruled { position: relative; padding: var(--space-3) 0; border-radius: 0; background: none; }
.store-version--ruled::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: var(--rule-width);
  background: var(--state-hover);
  pointer-events: none;
}
.store-version--ruled .store-version-head { justify-content: flex-start; align-items: center; gap: var(--space-3); overflow-wrap: anywhere; }
.store-version--ruled .store-version-name, .store-version--ruled .store-version-date { font-size: var(--text-xs); color: var(--text-faint); }
.store-version-action { margin-inline-start: auto; flex: none; }
.store-version-body {
  margin: var(--space-1) 0 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: var(--scroll-max-sm);
  overflow: auto;
  color: var(--text-muted);
}

/* ── § 7.18 Skeleton, empty, error ────────────────────────────────────── */
.stack { display: flex; flex-direction: column; gap: var(--space-3); }
.store-status {
  margin: 0;
  padding: var(--space-6) 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
  text-align: center;
}
/* DS-3: the panel's notices. --inline is the line under a control (the
   panel's .aviso): xs, start-aligned, and it keeps one line's height when it
   is empty so the layout does not jump as a message comes and goes. Three
   tones — faint, violet (a confirmation: "copied") and danger — and a boxed
   warning on the soft danger fill, which ignores the tones (it comes after
   them); DS-8 (1.9.0) set its words in --text, the builder twin of
   .ds-notice--danger. --centred sits in its container's free space (an empty
   list), balanced. */
.store-status--inline {
  padding: 0;
  min-height: calc(var(--text-xs) * var(--leading-base));
  font-size: var(--text-xs);
  text-align: start;
  overflow-wrap: anywhere;
}
.store-status--faint { color: var(--text-faint); }
.store-status--accent-2 { color: var(--accent-2-text); }
.store-status--danger { color: var(--danger-text); }
.store-status--boxed {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--danger-soft);
  color: var(--text);
  text-align: start;
}
.store-status--centred { margin: auto; padding: var(--space-6); text-wrap: balance; }

/* ── § 7.14 Dialog ────────────────────────────────────────────────────────
   A scrim and a panel. No colour of its own: the panel is the system's, the
   shadow is the modal's, and it stacks at --z-dialog.                       */
/* The pair is one node so a caller can insert and remove it whole, and never
   leave an invisible scrim behind over an unclickable page. */
.ds-dialog-mount { display: contents; }
.confirm-scrim { position: fixed; inset: 0; z-index: var(--z-dialog); background: var(--scrim); }
.confirm-dialog {
  position: fixed;
  z-index: calc(var(--z-dialog) + 1);
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  /* 360px: a question and two buttons, never a form. */
  width: min(360px, calc(100vw - 2 * var(--space-5)));
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  background: var(--surface-panel);
  box-shadow: var(--shadow-modal);
}
.confirm-dialog h2 { margin: 0; font-size: var(--text-md); color: var(--text); }
.confirm-dialog p { margin: 0; font-size: var(--text-sm); color: var(--text-muted); }
.confirm-actions { display: flex; justify-content: flex-end; gap: var(--space-2); }
/* DS-3: the same row, public — a group of buttons that wraps from the start,
   or centred (the pairing sheet). */
.confirm-actions--wrap { flex-wrap: wrap; justify-content: flex-start; }
.confirm-actions--centred { justify-content: center; }
/* DS-3: the in-view sheet. It covers the VIEW that holds it (its nearest
   positioned ancestor), not the window: a page-coloured sheet with a head
   (close + title) and a body that scrolls. Its scrim stacks under it by DOM
   order, not by --z-dialog. Its own classes, so .confirm-dialog's h2 and p
   rules cannot restyle what it holds. */
.confirm-scrim--sheet { position: absolute; z-index: auto; }
.confirm-sheet {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  border-radius: var(--radius-lg);
  background: var(--surface-page);
}
.confirm-sheet-head { display: flex; align-items: center; gap: var(--space-3); flex: none; }
.confirm-sheet-title { margin: 0; min-width: 0; font-size: var(--text-lg); font-weight: var(--weight-display); }
.confirm-sheet-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
/* DS-11 (1.11.0): the Blur Layer dialog({ layer: true }) emits, the builder
   twin of .ds-layer and its parts (SPEC, DS-11 Twins), held equal to them by
   scripts/ds11-blank-palette-layer.test.mjs: a card with no ground and no
   cast over a plane the consumer recedes (.ds-recede). */
.confirm-layer {
  position: fixed;
  inset: 0;
  z-index: var(--z-dialog);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: calc(var(--header-height) + var(--space-6)) var(--space-6) var(--space-7);
  overflow-y: auto;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-layer) var(--ease-settle), transform var(--dur-recede) var(--ease-out), filter var(--dur-layer) var(--ease-settle);
}
.confirm-layer.is-open { opacity: 1; pointer-events: auto; }
.confirm-layer--top { z-index: calc(var(--z-dialog) + 1); }
.confirm-layer-card {
  width: min(var(--measure-layer), 100%);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4);
  animation: ds-rise var(--dur-settle) var(--ease-settle) both;
}
.confirm-layer-title {
  margin: 0;
  font-size: var(--text-band);
  font-weight: var(--weight-display);
  letter-spacing: var(--tracking-band);
  line-height: var(--leading-tight);
  text-transform: uppercase;
  text-align: center;
  color: var(--text);
  overflow-wrap: anywhere;
}
.confirm-layer-body {
  margin: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4);
  font-size: var(--text-base);
  line-height: var(--leading-base);
  color: var(--text-muted);
  text-align: center;
  overflow-wrap: anywhere;
}
/* The destructive commit, scoped to the dialog so it adds no variant to the
   shared set: danger text on the soft danger fill. v0 wrote this against
   .ds-btn--primary, which the builder never emits, so it never matched. */
.confirm-dialog .btn-primary.is-danger { background: var(--danger-soft); color: var(--danger-text); }
/* Hover lifts rather than filling with --danger: a label on the solid red is
   4.18:1 in light, under AA. The rule above already outranks .btn-primary:hover,
   so the fill stays put. */
.confirm-dialog .btn-primary.is-danger { transition: box-shadow var(--dur-fast) var(--ease-out); }
.confirm-dialog .btn-primary.is-danger:hover { box-shadow: var(--shadow-raised); }
