/* re:target app stylesheet.

   Three layers, in this order:
     1. Base      — reset, element defaults, layout primitives, a11y helpers
     2. Components — ported VERBATIM from the marketing site so both surfaces
                     share one vocabulary. Sources:
                       ReTargetWebsite/src/styles/base.css
                       ReTargetWebsite/src/styles/components.css
                       ReTargetWebsite/src/styles/product.css
                       ReTargetWebsite/src/pages/demo.astro (.run-complete)
                     Change these together with the site or the two drift.
                     Two design-system components the site never built —
                     LockedPanel and Tag, because it has no entitlement
                     boundary and no report filters — are translated from
                     their JSX in designsystem/components/ instead, and say so
                     where they sit.
     3. App       — the shell and the screens the site has no equivalent for.

   House rule inherited from the site: nothing here reaches for a base ramp
   (--pine-500, --clay-200, …). Semantic aliases only — that is what makes the
   dark scope work on the sidebar. The three documented exceptions are the
   sidebar background, the .steps done-marker and the .report-header band, each
   of which the site itself hardcodes to --pine-*.

   Focus is always `outline`, never `box-shadow`: a shadow ring is clipped by
   the `overflow:hidden` on .data-table, and is trivially overridden by an
   inline style. */

/* ════════════════════════════════════════════════════════════════
   1. BASE
   ════════════════════════════════════════════════════════════════ */

*,*::before,*::after{box-sizing:border-box}

html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}

body{
  margin:0;
  background:var(--surface-page);
  color:var(--text-body);
  font-family:var(--font-sans);
  font-size:var(--fs-body-m);
  line-height:var(--lh-body-m);
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overflow-x:hidden;
}

h1,h2,h3,h4,h5,h6{margin:0;color:var(--text-strong);font-weight:var(--fw-semibold);text-wrap:balance}
h1{font-size:var(--fs-heading-l);line-height:var(--lh-heading-l);letter-spacing:var(--ls-heading-l)}
h2{font-size:var(--fs-heading-s);line-height:var(--lh-heading-s);letter-spacing:var(--ls-heading-s)}
h3{font-size:var(--fs-body-l);line-height:var(--lh-body-l)}
p{margin:0;text-wrap:pretty}
ul,ol{margin:0;padding:0}
img,svg,video{max-width:100%;height:auto}

a{
  color:var(--text-link);
  text-decoration:underline;
  text-underline-offset:3px;
  text-decoration-thickness:1px;
  text-decoration-color:color-mix(in oklch,currentColor 40%,transparent);
  transition:color var(--dur-fast) var(--ease-standard);
}
a:hover{color:var(--text-link-hover);text-decoration-color:currentColor}

:where(a,button,input,select,textarea,summary,[tabindex]):focus-visible{
  outline:2px solid var(--focus-ring);
  outline-offset:2px;
}
:focus-visible{outline-color:var(--focus-ring)}

code,kbd,samp,pre{font-family:var(--font-mono);font-size:.92em}
pre{margin:0;white-space:pre-wrap;word-break:break-word}
::selection{background:var(--ember-200);color:var(--clay-900)}
[data-theme="dark"] ::selection{background:var(--ember-700);color:var(--white)}
hr{border:0;border-top:1px solid var(--border-hairline);margin:var(--space-7) 0}

button,input,select,textarea{font:inherit;color:inherit}

/* ── Layout primitives ─────────────────────────────────────── */
.prose{max-width:var(--measure-prose)}
/* A note inside a card: no margin of its own, the card's stack spaces it. */
.prose--muted{margin:0;color:var(--text-muted)}
.narrow{max-width:var(--measure-narrow)}
/* Space above one block that follows a sibling the layout does not stack.
   Classes rather than the inline `style="margin-top:…"` they replace, which
   the CSP (A1/D4, #199) refuses. */
.space-above-4{margin-top:var(--space-4)}
.space-above-5{margin-top:var(--space-5)}
/* The inline icon sprite (templates/_icons.html): symbols only, never drawn. */
.icon-sprite{display:none}

.stack{display:flex;flex-direction:column}
/* The base <hr> carries 32px of its own margin, which is right in prose and
   wrong here: a stack already owns the space between its children. */
.stack > hr{margin:0}
.stack-2{gap:var(--space-2)}
.stack-3{gap:var(--space-3)}
.stack-4{gap:var(--space-4)}
.stack-5{gap:var(--space-5)}
.stack-6{gap:var(--space-6)}

/* minmax(0,1fr), never a bare 1fr: 1fr floors the track at min-content and one
   long value is enough to push a narrow page into horizontal scroll. */
.grid-cards{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--space-4)}
@media (min-width:640px){.grid-cards{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (min-width:1024px){.grid-cards--3{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media (min-width:1100px){.grid-cards--4{grid-template-columns:repeat(4,minmax(0,1fr))}}

/* ── Accessibility helpers ─────────────────────────────────── */
.visually-hidden{
  position:absolute;
  width:1px;height:1px;
  padding:0;margin:-1px;
  overflow:hidden;
  clip-path:inset(50%);
  white-space:nowrap;
  border:0;
}

.skip-link{
  position:absolute;
  left:var(--gutter-page);
  top:0;
  z-index:100;
  transform:translateY(-150%);
  padding:var(--space-3) var(--space-5);
  background:var(--surface-card);
  color:var(--text-strong);
  border:1px solid var(--border-control);
  border-radius:0 0 var(--radius-sm) var(--radius-sm);
  box-shadow:var(--shadow-popover);
  font-size:var(--fs-body-m);
  font-weight:var(--fw-semibold);
  text-decoration:none;
  transition:transform var(--dur-fast) var(--ease-out);
}
.skip-link:focus{transform:translateY(0)}
main:focus{outline:none}

/* Monospace for anything the product measured — MIDs, counts, scores,
   timestamps. The mono/sans split is the type system. */
.mono{font-family:var(--font-mono)}
.mono-s{font-family:var(--font-mono);font-size:var(--fs-mono-s);line-height:var(--lh-mono-s)}

.eyebrow{
  font-family:var(--font-mono);
  font-size:var(--fs-eyebrow);
  line-height:var(--lh-eyebrow);
  letter-spacing:var(--ls-eyebrow);
  text-transform:uppercase;
  color:var(--text-muted);
}

/* ════════════════════════════════════════════════════════════════
   2. COMPONENTS — ported from the marketing site
   ════════════════════════════════════════════════════════════════ */

/* ── Icon ──────────────────────────────────────────────────── */
.icon{display:block;flex:none}

/* ── Button ────────────────────────────────────────────────── */
.btn{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:8px;
  height:var(--control-h-md);
  padding-inline:var(--space-5);
  border:1px solid transparent;
  border-radius:var(--radius-sm);
  font-family:var(--font-sans);
  font-size:var(--fs-body-m);
  font-weight:var(--fw-semibold);
  letter-spacing:-.005em;
  line-height:1;
  white-space:nowrap;
  text-decoration:none;
  cursor:pointer;
  transition:var(--transition-control),transform var(--dur-instant) var(--ease-standard);
}
.btn:hover{text-decoration:none}

.btn--sm{height:var(--control-h-sm);padding-inline:var(--space-3);font-size:var(--fs-body-s);gap:6px}
.btn--lg{height:var(--control-h-lg);padding-inline:var(--space-6);font-size:var(--fs-body-l);gap:10px}
.btn--full{display:flex;width:100%}

.btn--primary{background:var(--action-primary);color:var(--action-primary-text);box-shadow:var(--shadow-raised)}
.btn--primary:hover{background:var(--action-primary-hover);color:var(--action-primary-text)}
.btn--primary:active{background:var(--action-primary-press);transform:translateY(1px)}

.btn--secondary{background:var(--action-secondary);color:var(--action-secondary-text);box-shadow:var(--shadow-raised)}
.btn--secondary:hover{background:var(--action-secondary-hover);color:var(--action-secondary-text)}
.btn--secondary:active{background:var(--action-secondary-press);transform:translateY(1px)}

.btn--outline{background:var(--surface-card);color:var(--text-strong);border-color:var(--border-control)}
.btn--outline:hover{background:var(--surface-inset);border-color:var(--border-strong);color:var(--text-strong)}
.btn--outline:active{transform:translateY(1px)}
[data-theme="dark"] .btn--outline{background:transparent;color:var(--text-strong)}
[data-theme="dark"] .btn--outline:hover{background:var(--surface-inset)}

.btn--ghost{background:transparent;color:var(--text-body)}
.btn--ghost:hover{background:var(--surface-inset);color:var(--text-strong)}

.btn--danger{background:var(--brick-600);color:var(--white);box-shadow:var(--shadow-raised)}
.btn--danger:hover{background:var(--brick-700);color:var(--white)}

.btn[disabled],.btn[aria-disabled="true"]{opacity:.45;cursor:not-allowed;transform:none}

/* A control that has to read as a link. Signing out retires a session, so it
   is a POST and therefore a <button> — but it is the quietest thing on the
   page it sits on, and a third button there would compete with the two that
   matter. `.btn--ghost` is not this: it is a button with no border and no
   background, which renders as bold text rather than as a link. */
.link-button{
  border:0;
  padding:0;
  background:none;
  color:var(--text-link);
  text-decoration:underline;
  text-underline-offset:3px;
  text-decoration-thickness:1px;
  text-decoration-color:color-mix(in oklch,currentColor 40%,transparent);
  cursor:pointer;
  transition:color var(--dur-fast) var(--ease-standard);
}
.link-button:hover{color:var(--text-link-hover);text-decoration-color:currentColor}

.btn__spinner{animation:rt-spin 900ms linear infinite}
@keyframes rt-spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.btn__spinner{animation-duration:2400ms}}

/* ── Badge ─────────────────────────────────────────────────── */
.badge{
  display:inline-flex;
  align-items:center;
  gap:5px;
  padding:3px 8px;
  border-radius:var(--radius-xs);
  background:var(--status-neutral-bg);
  color:var(--status-neutral-fg);
  font-family:var(--font-sans);
  font-size:var(--fs-caption);
  font-weight:var(--fw-semibold);
  letter-spacing:.01em;
  line-height:1.35;
  white-space:nowrap;
}
.badge--ok{background:var(--status-ok-bg);color:var(--status-ok-fg)}
.badge--warn{background:var(--status-warn-bg);color:var(--status-warn-fg)}
.badge--block{background:var(--status-block-bg);color:var(--status-block-fg)}
.badge--info{background:var(--status-info-bg);color:var(--status-info-fg)}
.badge--ember{background:var(--surface-ember-soft);color:var(--text-ember)}
/* E8. A badge that is a link — the "estimate locked" marker on the category
   page and the run page. A badge is a label, so the underline every link
   carries reads as noise inside the pill, and the pill plus the lock glyph
   already say it is a control. The underline comes back on hover, which is
   where a reader is asking whether it is one. */
a.badge{text-decoration:none}
a.badge:hover{text-decoration:underline}
.badge--mono{font-family:var(--font-mono);font-weight:var(--fw-medium);letter-spacing:0}
/* The one badge that is a page's headline rather than a label on something
   else: the complexity band on an item (D6). Size is the whole difference —
   the tone ramp and the geometry are the family's. */
.badge--lg{padding:6px 14px;font-size:var(--fs-body-l);letter-spacing:0}
/* Neutral badge: labels a value without scoring it. The unresolved-reference
   list uses it for the target type — those rows are diagnostics, and
   .badge--block painted the whole table as failure. */
.badge--muted{background:var(--surface-sunken);color:var(--text-muted)}

/* `.pill` predates the port and is what report.html and delta.html render. The fate→colour mapping is documented in
   docs/DESIGN_TOKENS.md, so it stays exactly as it was. New markup uses
   `.badge`; these two are visually near-identical by design. */
.pill{
  display:inline-block;
  font-size:var(--fs-caption);
  border-radius:var(--radius-pill);
  padding:2px var(--space-3);
  font-weight:var(--fw-semibold);
  background:var(--status-info-bg);
  color:var(--status-info-fg);
}
.pill.ok{background:var(--status-ok-bg);color:var(--status-ok-fg)}
.pill.warn{background:var(--status-warn-bg);color:var(--status-warn-fg)}
.pill.ember{background:var(--surface-ember-soft);color:var(--text-ember)}
.pill.err{background:var(--status-block-bg);color:var(--status-block-fg)}
.pill.neutral{background:var(--status-neutral-bg);color:var(--status-neutral-fg)}

/* ── Card ──────────────────────────────────────────────────── */
.card{
  border-radius:var(--radius-lg);
  padding:var(--space-6);
  background:var(--surface-card);
  border:1px solid var(--border-hairline);
}
.card--pad-5{padding:var(--space-5)}
.card--pad-4{padding:var(--space-4)}
.card--raised{box-shadow:var(--shadow-raised)}
.card--floating{box-shadow:var(--shadow-card)}
.card--sunken{background:var(--surface-sunken)}
.card--accent{background:var(--surface-accent-soft);border-color:var(--border-accent)}
/* The action at the foot of a card in a grid of peers. Cards in a row stretch
   to the tallest, so without this a plan whose description runs to one line
   puts its button level with the middle of the card beside it. `margin-top`
   inside a flex column is the same trick `.app-sidebar__foot` uses. */
.card__foot{margin-top:auto;padding-top:var(--space-2)}

/* Buttons side by side, wrapping onto a second line rather than off the card.
   The same three declarations `.run-complete__actions`, `.app-header__actions`
   and `.connect__actions` each carry privately; new callers use this one. */
.btn-row{display:flex;flex-wrap:wrap;gap:var(--space-3)}

/* ── Callout ───────────────────────────────────────────────── */
.callout{
  display:flex;
  gap:var(--space-3);
  padding:var(--space-4);
  border-radius:var(--radius-md);
  background:var(--status-info-bg);
  border:1px solid color-mix(in oklch,var(--status-info-fg) 26%,transparent);
}
.callout__icon{color:var(--status-info-fg);margin-top:1px;flex:none}
.callout__body{display:flex;flex-direction:column;gap:3px;min-width:0}
.callout__title{font-size:var(--fs-body-m);font-weight:var(--fw-semibold);color:var(--text-strong)}
.callout__text{font-size:var(--fs-body-m);color:var(--text-body);line-height:1.55}
.callout__text code{
  font-size:var(--fs-mono-s);
  padding:1px 4px;
  border-radius:var(--radius-xs);
  background:var(--surface-inset);
  color:var(--text-strong);
  word-break:break-word;
}
.callout--warn{background:var(--status-warn-bg);border-color:color-mix(in oklch,var(--status-warn-fg) 30%,transparent)}
.callout--warn .callout__icon{color:var(--status-warn-fg)}
/* Raw diagnostic text inside a callout: available, not shouted. The summary
   stays inline-sized so the callout keeps its shape when collapsed. */
.callout__detail{margin-top:var(--space-2);font-size:var(--fs-body-s)}
.callout__detail > summary{cursor:pointer;color:var(--text-muted);width:fit-content}
.callout__detail > code{
  display:block;
  margin-top:var(--space-2);
  font-family:var(--font-mono);
  font-size:var(--fs-mono-s);
  line-height:1.5;
  color:var(--text-muted);
  overflow-wrap:anywhere;
}

.callout--block{background:var(--status-block-bg);border-color:color-mix(in oklch,var(--status-block-fg) 30%,transparent)}
.callout--block .callout__icon{color:var(--status-block-fg)}
.callout--ok{background:var(--status-ok-bg);border-color:color-mix(in oklch,var(--status-ok-fg) 26%,transparent)}
.callout--ok .callout__icon{color:var(--status-ok-fg)}
.callout--security{background:var(--surface-accent-soft);border-color:var(--border-accent)}
.callout--security .callout__icon{color:var(--text-accent)}

/* ── Logotype ──────────────────────────────────────────────── */
.logotype{display:inline-flex;flex-direction:column;justify-content:center;gap:4px;min-height:var(--target-min);text-decoration:none}
.logotype:hover{text-decoration:none}
.logotype__word{
  display:inline-flex;
  align-items:baseline;
  color:var(--text-strong);
  font-size:var(--logo-size,21px);
  line-height:1;
  letter-spacing:-.03em;
  font-weight:var(--fw-semibold);
  font-family:var(--font-sans);
}
.logotype__re,.logotype__colon{font-family:var(--font-mono);font-weight:var(--fw-medium);letter-spacing:-.02em}
.logotype__colon{color:var(--text-ember);padding:0 .04em}
.logotype__tagline{
  font-family:var(--font-mono);
  font-size:max(10px,calc(var(--logo-size,21px) * .42));
  letter-spacing:.12em;
  text-transform:uppercase;
  color:var(--text-muted);
}
/* The two sizes the templates set. They were inline `style="--logo-size:…"`
   attributes, which the CSP (A1/D4, #199) refuses; the custom property is
   still the one knob, set here instead. */
.logotype--rail{--logo-size:19px}
.logotype--auth{--logo-size:24px}

/* ── Form controls ─────────────────────────────────────────── */
.field{display:flex;flex-direction:column;gap:6px;min-width:0}
/* Two form rows side by side above 560px, stacked below. Was
   `.connect__pair`, defined in the connect block and used by the one form
   there; E3's signup form wants the same thing for first/last name, and two
   rules doing one job is how they stop agreeing about the breakpoint. */
.field-pair{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--space-4)}
@media (min-width:560px){.field-pair{grid-template-columns:1fr 1fr}}
.field__label{font-size:var(--fs-body-m);font-weight:var(--fw-semibold);color:var(--text-strong)}
.field__required{color:var(--text-ember);margin-left:3px}
/* Block, not `inline-flex`. Flex makes an item of every child INCLUDING each
   bare text run, so a hint reading "code sent to <strong>you@co.com</strong>.
   Enter it below" laid out as three side-by-side columns, the first of them
   one word per line. Every hint in the app is a sentence with a link or a
   <strong> in it, so every one of them was doing this (D13). */
.field__hint,.field__error{
  display:block;
  font-size:var(--fs-body-s);
  line-height:var(--lh-body-s);
  color:var(--text-muted);
}
/* The error is the one that does want flex, for the icon beside it — and
   `flex-start`, not `center`, or a 13px glyph floats down the middle of a
   three-line message. Its sentence is wrapped in a <span> in `ui/_field.html`
   so it stays one item however much markup an error message carries. */
.field__error{
  display:flex;
  align-items:flex-start;
  gap:5px;
  color:var(--status-block-fg);
  font-weight:var(--fw-medium);
}
/* Half the leading. The glyph is set at the type size, so half of what the
   line box adds above and below the text is what puts the two on one centre. */
.field__error .icon{margin-top:calc((var(--lh-body-s) - 1) * .5em)}
/* A link inside an error is part of the sentence, not a pine-green word in the
   middle of a brick-red one. The underline is what marks it — which is the
   accessible answer anyway, since colour alone never was. */
.field__error a{color:inherit;text-decoration-color:currentColor}
.field__error a:hover{color:inherit;text-decoration-thickness:2px}

/* D16 — the browser's answer to "does this look right", under the field it is
   about. Muted by default and pine when the shape matches: this is advice, not
   a verdict, so the affirmative case is the quiet colour the app uses for a
   fact rather than the loud one it uses for success — only Test connection can
   say a credential works, and a green tick here would be claiming it had. */
.field__shape{
  display:block;
  font-size:var(--fs-body-s);
  line-height:var(--lh-body-s);
  color:var(--text-muted);
}
.field__shape--ok{color:var(--text-accent)}
.field__shape code{
  font-family:var(--font-mono);
  font-size:var(--fs-mono-s);
  color:var(--text-strong);
}
/* A glyph sitting in a line of text rather than in a flex row built for it.
   `.icon` is `display:block`, which is right inside the buttons, callouts and
   pills that own their layout and wrong in a heading or a sentence, where it
   drops the words onto the next line — the receipt's tick did that above "With
   sales", and the request form's envelope above the line it belongs to.

   Declared here rather than beside `.card__foot`, because `.field__hint` is
   `display:block` and is a single class too: with one selector each, source
   order is what decides, and the hint is the sentence most likely to want an
   icon in it. */
.with-icon{display:flex;align-items:center;gap:var(--space-2)}
.with-icon--start{align-items:flex-start}
.with-icon--start > .icon{margin-top:3px}

/* The ring goes on the bordered wrapper, not the bare <input> inside it. */
.input{
  display:flex;
  align-items:center;
  gap:8px;
  height:var(--control-h-md);
  padding:0 12px;
  border-radius:var(--radius-sm);
  background:var(--surface-card);
  border:1px solid var(--border-control);
  box-shadow:var(--shadow-inset-well);
  color:var(--text-muted);
  min-width:0;
  transition:var(--transition-control);
}
.input:has(input:focus-visible),.input:has(textarea:focus-visible){outline:2px solid var(--focus-ring);outline-offset:2px}
.input:has(input:focus),.input:has(textarea:focus){border-color:var(--border-strong)}
/* Louder than a focus ring, because it has to outrank one. A 1px brick border
   against the 2px ember outline beside it meant the autofocused *valid* field
   shouted and the refused address whispered (D13). The colour is restated for
   the focused case: `.input:has(input:focus)` is the more specific selector
   and would otherwise repaint the border `--border-strong` the moment the
   cursor arrived. The ember ring is an `outline` and still sits outside all
   of this. */
.input--invalid,.input--invalid:has(input:focus),.input--invalid:has(textarea:focus){
  border-width:2px;
  border-color:var(--status-block-fg);
  background:var(--status-block-bg);
}
.input--lg{height:var(--control-h-lg)}
.input--sm{height:var(--control-h-sm)}
.input input{
  flex:1;
  min-width:0;
  border:0;
  outline:none;
  background:transparent;
  color:var(--text-strong);
  font-family:var(--font-sans);
  /* 16px stops iOS Safari zooming the viewport on focus. */
  font-size:16px;
}
.input--mono input,.input--mono textarea{font-family:var(--font-mono)}
.input__suffix{font-family:var(--font-mono);font-size:var(--fs-mono-s);color:var(--text-muted)}
.input:has(input:disabled),.input:has(textarea:disabled){background:var(--surface-sunken)}

/* A multi-line control in the same wrapper. The wrapper is a fixed-height,
   vertically-centred flex row built for one-line controls, and a <textarea>
   dropped into it overflowed in both directions — on the purchase-request form
   it sat on top of the field above it and the button below. So the wrapper
   stops being a row when it holds one: `height:auto` lets it take the
   textarea's height, `align-items:stretch` makes the control fill it, and the
   12px side padding moves onto the control so the text starts where an
   <input>'s does and the scrollbar stays inside the border.

   `resize:vertical` rather than `both`: horizontal resize drags the control
   out of the card it sits in, and there is nothing to gain by it. */
.input:has(textarea){height:auto;align-items:stretch;padding:0}
.input textarea{
  flex:1;
  min-width:0;
  min-height:calc(var(--control-h-md) * 2);
  padding:10px 12px;
  border:0;
  outline:none;
  background:transparent;
  color:var(--text-strong);
  font-family:var(--font-sans);
  /* 16px stops iOS Safari zooming the viewport on focus. */
  font-size:16px;
  line-height:var(--lh-body-m);
  resize:vertical;
}

.select{position:relative;display:flex;align-items:center;min-width:0}
.select select{
  appearance:none;
  width:100%;
  height:var(--control-h-md);
  padding:0 34px 0 12px;
  border-radius:var(--radius-sm);
  border:1px solid var(--border-control);
  background:var(--surface-card);
  box-shadow:var(--shadow-inset-well);
  color:var(--text-strong);
  font-family:var(--font-sans);
  font-size:16px;
  cursor:pointer;
  transition:var(--transition-control);
}
.select--sm select{height:var(--control-h-sm);font-size:var(--fs-body-s);padding-right:28px}
.select__chevron{position:absolute;right:12px;color:var(--text-muted);pointer-events:none}
.select--sm .select__chevron{right:8px}
/* E7 — a select rendered by `{% field %}` shows a refusal the way `.input`
   does: a 2px border in the block colour on the block ground. */
.select--invalid select{border-width:2px;border-color:var(--status-block-fg);background:var(--status-block-bg)}

/* `position:relative` is load bearing, not decoration: without a positioned
   ancestor the absolutely positioned input resolves against the initial
   containing block, escapes any scrolling ancestor's clip, and adds its own
   height to that ancestor's scrollable overflow. Found in D2's modal, where it
   made the box scrollable and let the browser scroll the header — and its
   close button — out of the frame. Fixed in the site's components.css too. */
.checkbox{position:relative;display:flex;gap:10px;align-items:flex-start;cursor:pointer;min-height:var(--target-min)}
.checkbox input{position:absolute;width:1px;height:1px;opacity:0;margin:0}
.checkbox__box{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  flex:none;
  width:18px;height:18px;
  margin-top:3px;
  border-radius:var(--radius-xs);
  border:1px solid var(--border-control);
  background:var(--surface-card);
  color:var(--action-primary-text);
  transition:var(--transition-control);
}
.checkbox__box .icon{opacity:0}
.checkbox input:checked + .checkbox__box{background:var(--text-accent);border-color:var(--text-accent)}
.checkbox input:checked + .checkbox__box .icon{opacity:1;color:var(--white)}
.checkbox input:focus-visible + .checkbox__box{outline:2px solid var(--focus-ring);outline-offset:2px}
.checkbox input:disabled ~ *{opacity:.5}
.checkbox__text{display:flex;flex-direction:column;gap:2px}
.checkbox__label{font-size:var(--fs-body-m);color:var(--text-strong)}
.checkbox__description{font-size:var(--fs-body-s);color:var(--text-muted)}

/* ── ComplexityBand ────────────────────────────────────────
   One word, on a three-step scale. Steps up to the band are filled in the
   band's color; the rest sit on its track. */
.complexity-band{
  display:flex;
  flex-direction:column;
  gap:var(--space-3);
  min-width:180px;
}
.complexity-band__readout{display:flex;flex-direction:column;gap:2px}
.complexity-band__label{
  font-family:var(--font-mono);
  font-size:var(--fs-mono-s);
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--text-muted);
}
.complexity-band__value{
  font-size:40px;
  font-weight:var(--fw-semibold);
  letter-spacing:-.03em;
  line-height:1.1;
  color:var(--band-fg);
}
.complexity-band--l .complexity-band__value{font-size:clamp(40px,6vw,56px)}
.complexity-band__scale{
  display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));
  gap:var(--space-1);
  list-style:none;
}
.complexity-band__step{
  padding-top:var(--space-2);
  border-top:6px solid var(--band-track);
  font-family:var(--font-mono);
  font-size:var(--fs-mono-s);
  color:var(--text-muted);
}
.complexity-band__step[data-on]{border-top-color:var(--band-fg);color:var(--text-body)}
/* App additions (D18). The site sets --band-fg/--band-track in an inline
   style; the app allows none, so the tone is a modifier. `--none` is a band the
   scan did not produce — muted, and no step filled, so it cannot read as Low.
   The reason sits under the scale because a band is never printed without
   the sentence that justifies it (C7). */
.complexity-band--low{--band-fg:var(--score-ready-fg);--band-track:var(--score-ready-track)}
.complexity-band--medium{--band-fg:var(--score-mid-fg);--band-track:var(--score-mid-track)}
.complexity-band--high{--band-fg:var(--score-low-fg);--band-track:var(--score-low-track)}
.complexity-band--none{--band-fg:var(--status-neutral-fg);--band-track:var(--surface-sunken)}
.complexity-band__reason{color:var(--text-body);font-size:var(--fs-body-s);line-height:var(--lh-body-s);max-width:var(--measure-narrow)}

/* ── StatTile ──────────────────────────────────────────────── */
.stat-tile{
  display:flex;
  flex-direction:column;
  gap:var(--space-2);
  padding:var(--space-4);
  background:var(--surface-card);
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-md);
}
.stat-tile__label{
  display:flex;
  align-items:center;
  gap:6px;
  color:var(--text-muted);
  font-size:var(--fs-body-s);
  font-weight:var(--fw-medium);
}
.stat-tile__row{display:flex;align-items:baseline;gap:5px;flex-wrap:wrap}
.stat-tile__value{
  font-family:var(--font-mono);
  font-size:26px;
  font-weight:var(--fw-medium);
  letter-spacing:-.02em;
  color:var(--text-strong);
  line-height:1.1;
}
.stat-tile--ok .stat-tile__value{color:var(--status-ok-fg)}
.stat-tile--warn .stat-tile__value{color:var(--status-warn-fg)}
.stat-tile--block .stat-tile__value{color:var(--status-block-fg)}
/* Every tone `band_tone` can return has a rule here (D4). An unbanded tile
   holds an em dash, and a dash in the same weight as a real figure reads as
   one. */
.stat-tile--muted .stat-tile__value{color:var(--text-muted)}
.stat-tile__unit{font-size:var(--fs-body-s);color:var(--text-muted)}
/* E8. A tile whose figure this plan does not include. It keeps its place in the
   row — a gap where a tile was reads as a scan that stopped costing the estate
   — and the dashed rule is the same "not yours yet" the LockedPanel border
   states.

   D14 moves the lock off the figure. It stood *where the number goes*, which
   made the tile read as one that had failed to produce a number rather than one
   whose number is behind a boundary: nothing on it said what the missing thing
   was. The value row now renders the same em dash and unit every unestimated
   tile does, and the lock is the icon on the footnote — the sentence that says
   which silence this is, turned into the control that ends it. Ember, because
   it is the one action on the tile and ember is what the design system paints
   an action in. */
.stat-tile--locked{border-style:dashed}
.stat-tile__lock{
  display:inline-flex;
  align-items:center;
  gap:5px;
  min-height:var(--target-min);
  color:var(--text-ember);
  text-decoration:none;
}
.stat-tile__lock:hover{color:var(--text-strong);text-decoration:underline}
.stat-tile__lock:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}
.stat-tile__lock .icon{flex:none}
.stat-tile__delta{font-family:var(--font-mono);font-size:var(--fs-mono-s);color:var(--text-muted)}
.stat-tile__footnote{font-size:var(--fs-caption);color:var(--text-muted)}

/* ── DataTable ─────────────────────────────────────────────── */
.data-table{
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-md);
  overflow:hidden;
  background:var(--surface-card);
}
.data-table table{width:100%;border-collapse:collapse;font-size:var(--fs-body-m)}
/* A card heading followed straight by a table — the comparison page's pattern.
   Without this the table's top border sits on the heading's baseline. */
.card > h2 + .data-table{margin-top:var(--space-4)}
.data-table caption{
  caption-side:top;
  text-align:left;
  padding:12px 14px;
  color:var(--text-muted);
  font-size:var(--fs-body-s);
}
.data-table th{
  padding:12px 14px;
  text-align:left;
  border-bottom:1px solid var(--border-hairline);
  background:var(--surface-inset);
  color:var(--text-muted);
  font-family:var(--font-mono);
  font-size:var(--fs-mono-s);
  font-weight:var(--fw-medium);
  letter-spacing:.04em;
  text-transform:uppercase;
  white-space:nowrap;
}
.data-table td{padding:12px 14px;color:var(--text-body);vertical-align:middle;font-weight:var(--fw-regular)}
.data-table tbody tr{border-bottom:1px solid var(--border-hairline)}
.data-table tbody tr:last-child{border-bottom:0}
/* D15 — a link in a cell is the way into the row and was a 22px line of text.
   Not the inline links in prose, which WCAG 2.5.8 exempts and which growing
   would break the line they sit in: this is the standalone kind. */
.data-table td a{display:inline-flex;align-items:center;min-height:var(--target-min)}
.data-table [data-strong]{color:var(--text-strong);font-weight:var(--fw-medium)}
.data-table [data-mono]{font-family:var(--font-mono)}
.data-table [data-nowrap]{white-space:nowrap}
.data-table [data-align="right"]{text-align:right}
.data-table--dense th,.data-table--dense td{padding:8px 12px}
.data-table__empty{
  display:flex;
  align-items:center;
  justify-content:center;
  gap:8px;
  padding:var(--space-8);
  color:var(--text-muted);
  font-size:var(--fs-body-m);
}

/* Below md the table restacks as label/value pairs rather than scrolling
   sideways. Every <td> needs a data-label for this to read. */
@media (max-width:767px){
  .data-table thead{
    position:absolute;
    width:1px;height:1px;
    overflow:hidden;
    clip-path:inset(50%);
    white-space:nowrap;
  }
  .data-table tbody tr{display:block;padding:var(--space-3) 0}
  .data-table td{
    display:flex;
    justify-content:space-between;
    align-items:baseline;
    gap:var(--space-4);
    padding:5px 14px;
    text-align:left !important;
  }
  .data-table td::before{
    content:attr(data-label);
    flex:none;
    color:var(--text-muted);
    font-family:var(--font-mono);
    font-size:var(--fs-mono-s);
    letter-spacing:.04em;
    text-transform:uppercase;
  }
  .data-table td[data-strong]{display:block;padding-top:var(--space-2);font-size:var(--fs-body-l)}
  .data-table td[data-strong]::before{display:none}
}

/* ── Delta (D11) ────────────────────────────────────────────────
   One movement between two scans, rendered by `{% delta_change %}`. It
   replaces `▲ +3` / `▼ -3` / `=`: a dingbat is a glyph the app does not
   control the font coverage of, and it was also the only thing other than
   colour saying which way a figure had moved. The icon is decorative, the
   sign on the figure carries direction in text, and a visually-hidden word
   carries the judgement — so the tone below is the third statement of the
   same fact rather than the only one (WCAG 1.4.1).

   `--flat` and `--na` are defined tones, not a missing modifier. */
.delta{display:inline-flex;align-items:center;gap:3px;color:var(--text-faint)}
.delta__figure{font-family:var(--font-mono);font-size:var(--fs-mono-s);font-weight:var(--fw-medium)}
.delta--better{color:var(--status-ok-fg)}
.delta--worse{color:var(--status-block-fg)}
.delta--flat{color:var(--text-faint)}
.delta--na{color:var(--text-faint);font-size:var(--fs-caption)}

/* The comparison's baseline picker. The <label> is the sentence the control
   completes, so it wraps with it rather than sitting in a column of its own. */
.delta-compare{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-3)}
.delta-compare__label{margin:0}
/* The select submits on change when there is script to do it. The button is
   what makes the control work when there is not — and it is display:none
   before the first paint, not after it, because ui.js sets `html.js` in
   <head> without defer. */
html.js .delta-compare__go{display:none}

/* ── ProgressSteps ─────────────────────────────────────────────
   The heart of the run page. Four states via data-state, exactly as the
   marketing site's simulated run drives them: pending → active → done, or
   partial when a step failed but the scan carried on. */
.steps{display:flex;flex-direction:column;gap:var(--space-4);list-style:none;margin:0;padding:0}
.steps__item{display:flex;gap:12px;align-items:flex-start}
.steps__marker{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  flex:none;
  width:22px;height:22px;
  margin-top:1px;
  border-radius:var(--radius-pill);
  border:1px solid var(--border-default);
  background:transparent;
  color:var(--text-muted);
  font-family:var(--font-mono);
  font-size:11px;
  transition:var(--transition-control);
}
.steps__item[data-state="done"] .steps__marker{background:var(--pine-500);border-color:var(--pine-500);color:var(--white)}
.steps__item[data-state="active"] .steps__marker{background:var(--action-primary);border-color:var(--action-primary);color:var(--action-primary-text)}
.steps__item[data-state="partial"] .steps__marker{background:var(--status-warn-bg);border-color:var(--status-warn-fg);color:var(--status-warn-fg)}
.steps__text{display:flex;flex-direction:column;gap:2px;min-width:0}
.steps__label{font-size:var(--fs-body-m);font-weight:var(--fw-medium);color:var(--text-muted)}
.steps__item[data-state="done"] .steps__label,
.steps__item[data-state="partial"] .steps__label{color:var(--text-body)}
.steps__item[data-state="active"] .steps__label{color:var(--text-strong);font-weight:var(--fw-semibold)}
.steps__detail{font-size:var(--fs-body-s);color:var(--text-muted);display:flex;align-items:center;gap:5px}
.steps__item[data-state="partial"] .steps__detail{color:var(--status-warn-fg);font-weight:var(--fw-medium)}
.steps__spin{animation:rt-spin 900ms linear infinite}
@media (prefers-reduced-motion:reduce){.steps__spin{animation:none}}

/* ── Tabs ──────────────────────────────────────────────────── §6
   Full ARIA pattern or none at all. This is the full one: aria-controls,
   real tabpanels, roving tabindex and arrow-key navigation, all in the island.
   Server-side every panel renders visible, so the page is complete with JS off. */
.tabs{
  display:flex;
  gap:var(--space-6);
  border-bottom:1px solid var(--border-hairline);
  overflow-x:auto;
  scrollbar-width:none;
}
.tabs::-webkit-scrollbar{display:none}
.tab{
  display:inline-flex;
  align-items:center;
  gap:7px;
  min-height:var(--target-min);
  padding:0 0 12px;
  border:0;
  background:transparent;
  border-bottom:2px solid transparent;
  margin-bottom:-1px;
  color:var(--text-muted);
  font-family:var(--font-sans);
  font-size:var(--fs-body-m);
  font-weight:var(--fw-medium);
  white-space:nowrap;
  cursor:pointer;
  transition:var(--transition-control);
}
.tab[aria-selected="true"]{
  border-bottom-color:var(--action-primary);
  color:var(--text-strong);
  font-weight:var(--fw-semibold);
}
.tab__count{font-family:var(--font-mono);font-size:var(--fs-mono-s);color:var(--text-muted)}
/* D19 — three tabs with icons and counts are wider than a 390px column. The
   widget's tablist scrolls rather than wraps, so on a phone the icons go and
   the gap closes until the last tab is on screen rather than behind the edge. */
@media (max-width:479px){
  .tabs:not(.tabs--nav){gap:var(--space-4)}
  .tabs:not(.tabs--nav) .tab .icon{display:none}
}
.tabpanel:focus{outline:none}
/* With JS off nothing is hidden — each panel gets its own visible heading. */
html:not(.js) .tabs{display:none}
html:not(.js) .tabpanel__heading{
  font-size:var(--fs-heading-m);
  letter-spacing:var(--ls-heading-m);
  margin-bottom:var(--space-4);
}
html.js .tabpanel__heading{
  position:absolute;
  width:1px;height:1px;
  overflow:hidden;
  clip-path:inset(50%);
  white-space:nowrap;
}

/* A <nav> of URLs, not panels of one document, so it survives JS being off —
   which `html:not(.js) .tabs{display:none}` above would not allow. */
html:not(.js) .tabs--nav{display:flex}
/* Wraps rather than scrolling, at every width. The ported bar hides its
   scrollbar (`scrollbar-width:none`), so an entry that overflows is an entry
   with no affordance pointing at it — five destinations already measured wider
   than a 390px viewport, and D5 put ten on the report pages. Wrapping is the
   only degradation here that never hides one. The tighter gap is for the same
   reason: --space-6 over nine gaps is 216px spent on whitespace. */
.tabs--nav{flex-wrap:wrap;overflow-x:visible;gap:var(--space-1) var(--space-5)}
/* The hairline stays drawn at every width, wrapped or not. It used to go with
   the mobile block because wrap and the zeroed border shipped together; now
   that the bar wraps wherever it has to, dropping the border on one side of a
   breakpoint would leave the bar delimited at 1280px and not at 760px for no
   reason a reader could see. A wrapped row's `aria-current` accent sits under
   its own entry, which is what it means. */
@media (max-width:767px){
  .tabs--nav .tab{min-height:38px;padding-bottom:6px;margin-bottom:0}
}
/* Scoped to the nav: in the widget a tab is a <button>, which needs neither,
   and an unscoped hover colour would be app behaviour the site's tabs lack. */
.tabs--nav .tab{text-decoration:none}
.tabs--nav .tab:hover{color:var(--text-strong);text-decoration:none}
.tab[aria-current="page"]{
  border-bottom-color:var(--action-primary);
  color:var(--text-strong);
  font-weight:var(--fw-semibold);
}

/* ── Report categories (D14) ───────────────────────────────────
   D5 merged the report's five categories into the scan bar and marked the
   boundary with a 1px divider, which the mobile block then hid — so below
   768px the bar was ten flat peers over four rows and the score ring the
   report leads with started below the fold. They are a level down from the
   five views of a scan, so they are a control on the level they belong to and
   the divider is gone with the segment it delimited.

   A segmented row of links, sunk into its own strip so it reads as a control
   over the page rather than a second navigation bar over the scan. */
.report-cats{
  display:flex;
  align-items:center;
  gap:var(--space-3);
  flex-wrap:wrap;
}
.report-cats__row{
  display:flex;
  flex-wrap:wrap;
  gap:2px;
  padding:3px;
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-md);
  background:var(--surface-sunken);
}
.report-cats__item{
  display:inline-flex;
  align-items:center;
  min-height:var(--target-min);
  padding:0 var(--space-3);
  border-radius:var(--radius-sm);
  color:var(--text-muted);
  font-size:var(--fs-body-s);
  font-weight:var(--fw-medium);
  text-decoration:none;
  transition:var(--transition-control);
}
.report-cats__item:hover{background:var(--surface-inset);color:var(--text-strong);text-decoration:none}
.report-cats__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}
/* The selected segment is a filled chip, not an underline: the row has a
   ground of its own, and an underline inside a bordered strip reads as a
   second bar rather than as the chosen one of five. */
.report-cats__item[aria-current="page"]{
  background:var(--surface-card);
  border:1px solid var(--border-hairline);
  padding:0 calc(var(--space-3) - 1px);
  color:var(--text-strong);
  font-weight:var(--fw-semibold);
  box-shadow:var(--shadow-raised);
}
/* The <select> is the same five URLs for a width the row cannot be read at,
   and it navigates on change — so it exists only where script can do that.
   Without the marker the row of links is what renders, at every width. */
/* Label, select, Go — on one row at 390px, so the select takes the slack and
   the two fixed things keep their size. */
.report-cats__picker{display:none;align-items:center;gap:var(--space-2)}
.report-cats__picker .field__label{white-space:nowrap;flex:none}
.report-cats__picker .select{flex:1;min-width:0}
.report-cats__picker .btn{flex:none}
@media (max-width:767px){
  html.js .report-cats__row{display:none}
  html.js .report-cats__picker{display:flex}
}

/* Panel content (D9). Under `.run-sections` each of these blocks was a
   `<section>` and the space between them was the parent's flex gap; inside a
   panel they are plain siblings, so the stacking has to be restated here.
   Flex rather than sibling margins because `.tabpanel__heading` is
   `position:absolute` while the island is running — out of flex flow entirely,
   so it adds no gap — and static when the island is blocked, where the heading
   and its own margin are what turn the set back into a document. */
.tabpanel{display:flex;flex-direction:column;gap:var(--space-4);padding-top:var(--space-5)}
/* `[hidden]` is a user-agent rule at the lowest specificity there is, so the
   line above beats it and the island's `panel.hidden = true` stops hiding
   anything: every panel paints at once and the tab set quietly stops
   switching. Any author rule that gives a panel a `display` has to restate
   this one. */
.tabpanel[hidden]{display:none}
/* The gap is the spacing. A child's own stacking margin would add to it. */
.tabpanel > *{margin-top:0;margin-bottom:0}
.tabpanel > .subsection-title{margin-top:var(--space-2)}

/* ── LockedPanel ───────────────────────────────────────────────
   Translated from designsystem/components/product/LockedPanel.jsx, which the
   marketing site never built — it has no paid subset to draw a boundary
   around. Dashed edge and the blurred real content are the point: the
   entitlement boundary is visible rather than absent (readme.md §borders). */
/* One grid cell holding both layers, rather than an overlay absolutely
   positioned over a preview (D15).

   The overlay used to be out of flow, so the panel's height came from the
   blurred preview alone — and at 390px the reason wraps to five lines, which
   made the overlay taller than the box it was stretched to fill. A column flex
   container shrinks its items when it runs out of main axis, so what gave way
   was the one thing with a fixed height: the "See the plans" button, measured
   at 127x16 against the 24px floor. Sharing a cell makes the row as tall as
   the taller layer, so there is nothing to shrink — and the overlay still
   paints over the preview, because in a grid two items in one cell stack in
   DOM order. */
.locked-panel{
  position:relative;
  display:grid;
  grid-template-columns:minmax(0,1fr);
  min-height:180px;
  border:1px dashed var(--border-default);
  border-radius:var(--radius-lg);
  background:var(--surface-locked);
  overflow:hidden;
}
.locked-panel__preview{
  grid-area:1/1;
  padding:var(--space-5);
  filter:blur(5px);
  opacity:.5;
  pointer-events:none;
  user-select:none;
}
/* Positioned, and above the preview. The preview's `filter` and `opacity` make
   it a stacking context, which paints after every non-positioned block — DOM
   order or not — so the overlay, its text and its upgrade button were drawn
   under the faded preview and read as greyed out (Şirin, 2026-10-22). */
.locked-panel__overlay{
  position:relative;
  z-index:1;
  grid-area:1/1;
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:var(--space-3);
  padding:var(--space-5);
  text-align:center;
  background:linear-gradient(to bottom,color-mix(in oklch,var(--surface-page) 55%,transparent),var(--surface-page));
}
/* Nothing in the overlay gives way: the grid above means it never has to, and
   a flex item that can shrink is how the button became 16px tall in the first
   place. */
.locked-panel__overlay > *{flex:none}
.locked-panel__seal{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:34px;
  height:34px;
  border-radius:var(--radius-pill);
  background:var(--surface-ember-soft);
  color:var(--text-ember);
}
.locked-panel__text{display:flex;flex-direction:column;gap:4px;max-width:380px}
.locked-panel__title{font-size:var(--fs-heading-s);color:var(--text-strong)}
.locked-panel__reason{font-size:var(--fs-body-s);color:var(--text-muted)}
/* E16 — the upgrade and the call. Centred like the text above them, so the
   pair still reads as one block when a phone wraps it onto two lines. */
.locked-panel__overlay .btn-row{justify-content:center}
/* E13 — a locked panel's preview when what is locked is a list of things the
   offer adds: method, the same lines for every tenant. */
.locked-list{
  display:flex;
  flex-direction:column;
  gap:var(--space-2);
  margin:0;
  padding-left:var(--space-5);
  font-size:var(--fs-body-m);
  color:var(--text-body);
}

/* ── Tag ───────────────────────────────────────────────────────
   Translated from designsystem/components/core/Tag.jsx. Mono, because a tag
   states a value the product measured or filtered on — `fate:manual_rewrite`,
   `bu:523000123` — and the mono/sans split is the type system.
   In the app a tag is a link, not a span: the report filters are URLs. */
.tag{
  display:inline-flex;
  align-items:center;
  gap:6px;
  height:26px;
  padding:0 10px;
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-pill);
  background:transparent;
  color:var(--text-body);
  font-family:var(--font-mono);
  font-size:var(--fs-mono-s);
  white-space:nowrap;
  text-decoration:none;
  transition:var(--transition-control);
}
.tag:not(.tag--active):hover{background:var(--surface-inset);color:var(--text-body)}
.tag:hover{text-decoration:none}
.tag--active{
  border-color:var(--border-accent);
  background:var(--surface-accent-soft);
  color:var(--text-accent);
}
/* The remove affordance sits inside the pill, so the right inset closes up.
   24px square, not the JSX's 12px icon in 2px of padding: this is the one
   shipped use of Tag and it is a filter on a phone, so WCAG 2.5.8's floor
   (--target-min) applies. It fits inside the 26px pill with a pixel to spare. */
.tag--removable{padding-right:1px}
.tag__remove{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:var(--target-min);
  height:var(--target-min);
  border:0;
  border-radius:var(--radius-pill);
  background:transparent;
  color:inherit;
  text-decoration:none;
  cursor:pointer;
}
.tag__remove:hover{color:inherit}

/* ── Report header ─────────────────────────────────────────────
   The dark band at the top of a readiness report, from
   ReTargetWebsite/src/components/product/ReportHeader.astro.

   One deviation, and it is the UI kit's rather than an invention: the site's
   band is full-bleed with its own gutter and a centred `__inner`, while the
   app's sits inside `.app-main`'s column, so it carries the card radius and
   its own padding and the inner wrapper is dropped —
   designsystem/ui_kits/app/Report.jsx draws it exactly this way.

   `data-theme="dark"` is set on the block in the markup, so every alias inside
   resolves to the dark scope: the ring's band tokens, the badge tones and the
   lede colour flip together. --pine-950 is the third base-ramp exception noted
   at the top of this file — the site's own component hardcodes it, and so does
   .app-sidebar for the same band-on-dark reason. */
.report-header{
  background:var(--pine-950);
  background-image:var(--grid-dark);
  background-size:var(--grid-size);
  border-radius:var(--radius-lg);
  padding:var(--space-6);
  display:flex;
  flex-wrap:wrap;
  gap:var(--space-6);
  align-items:center;
}
.report-header__copy{
  flex:1 1 320px;
  display:flex;
  flex-direction:column;
  gap:var(--space-3);
}
.report-header__meta{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap}
/* The one interpretive sentence on the page (D5), so it is set at reading size
   and measured — not at figure weight, which would compete with the ring. */
.report-header__lede{
  font-size:var(--fs-body-l);
  line-height:var(--lh-body-l);
  letter-spacing:var(--ls-body-l);
  color:var(--text-body);
  max-width:58ch;
}
@media (min-width:1024px){
  .report-header{padding:var(--space-7);gap:var(--space-8)}
}
/* D19 — the site's ReportHeader: the band on the left, the business unit and
   the reason beside it, wrapping under it on a phone. */
.report-header__band{flex:0 1 260px;min-width:0}
.report-header__title{
  font-size:clamp(22px,4vw,var(--fs-heading-l));
  line-height:var(--lh-heading-l);
  letter-spacing:var(--ls-heading-l);
  color:var(--text-strong);
  overflow-wrap:anywhere;
}

/* ── Activity states (D19) ─────────────────────────────────────
   The key to the inventory table's five state columns: a badge, the total and
   one line each, in the table's own column order. Five across where there is
   room, then wrapping — the labels carry the meaning, so a narrower grid
   loses nothing. Orphaned is the good news, so it carries the accent. */
.state-key{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:var(--space-3);margin:0}
.state-key__item{
  display:flex;flex-direction:column;gap:var(--space-2);
  padding:var(--space-4);
  background:var(--surface-card);
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-md);
  min-width:0;
}
.state-key__item--accent{border-color:var(--border-accent);background:var(--surface-accent-soft)}
.state-key__head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);margin:0}
.state-key__count{
  font-family:var(--font-mono);
  font-size:var(--fs-body-l);
  font-weight:var(--fw-medium);
  color:var(--text-strong);
}
.state-key__note{margin:0;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-muted)}

/* D26 — the key folds under its own heading: reference, not news. */
.usage-key{display:flex;flex-direction:column}
.usage-key__summary{
  display:flex;align-items:center;gap:var(--space-2);
  min-height:var(--target-min);
  cursor:pointer;list-style:none;
  font-weight:var(--fw-medium);color:var(--text-strong);
}
.usage-key__summary::-webkit-details-marker{display:none}
.usage-key__summary:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px;border-radius:2px}
.usage-key__chevron{color:var(--text-muted);transform:rotate(-90deg);transition:transform var(--dur-fast) var(--ease-standard)}
.usage-key[open] .usage-key__chevron{transform:none}
.usage-key[open] > .state-key{margin:var(--space-4) 0 var(--space-3)}

/* D26 — an item's evidence under its row on a category list. */
.item-evidence__toggle{display:block;margin-top:4px;min-height:var(--target-min);font-size:var(--fs-body-s);font-weight:var(--fw-regular)}
.item-evidence > td{background:var(--surface-inset)}
/* `hidden` is the user agent's weakest rule, and the phone layout makes every
   row a block; both controls stay hidden until the script reveals them. */
.item-evidence__toggle[hidden],.data-table tbody tr.item-evidence[hidden]{display:none}
.item-evidence__body{display:flex;flex-direction:column;gap:var(--space-3);font-size:var(--fs-body-s);color:var(--text-muted)}
.item-evidence__body .card{padding:var(--space-4)}
.item-evidence__more{align-self:flex-start;display:inline-flex;align-items:center;min-height:var(--target-min)}
/* On a phone the evidence is the whole row, not a labelled half of one. */
@media (max-width:767px){
  .data-table tbody tr.item-evidence > td{display:block;padding:var(--space-3) 14px}
  .data-table tbody tr.item-evidence > td::before{display:none}
}

/* D26 — two views of one page, as two links: a segmented pair, the current
   one filled. */
.view-switch{
  display:inline-flex;align-self:flex-start;gap:2px;
  padding:2px;
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-md);
  background:var(--surface-inset);
}
.view-switch__item{
  display:inline-flex;align-items:center;
  min-height:32px;padding:0 var(--space-4);
  border-radius:var(--radius-sm);
  color:var(--text-muted);
  font-size:var(--fs-body-s);font-weight:var(--fw-medium);
  text-decoration:none;
}
.view-switch__item:hover{color:var(--text-strong);text-decoration:none}
.view-switch__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:1px}
.view-switch__item[aria-current="page"]{background:var(--surface-card);color:var(--text-strong)}

/* A section's one control, beside its heading: a link that reads as one. */
.section-head__action{display:inline-flex;align-items:center;min-height:var(--target-min);font-size:var(--fs-body-s)}

/* An activity type under the automation activities row: indented, quieter,
   and smaller, so the category rows still read as the table's spine. */
.data-table__subrow td{font-size:var(--fs-body-s)}
.data-table__subrow td[data-strong]{padding-left:28px;font-weight:var(--fw-regular);color:var(--text-body)}
.data-table__na{color:var(--text-faint)}
.report-inventory .band__driver{max-width:32ch}

/* The inventory table is nine columns: below 1024px it restacks the way every
   .data-table does below 768px, rather than clipping a column inside the
   card's `overflow:hidden`. */
@media (min-width:768px) and (max-width:1023px){
  .data-table--wide thead{
    position:absolute;
    width:1px;height:1px;
    overflow:hidden;
    clip-path:inset(50%);
    white-space:nowrap;
  }
  .data-table--wide tbody tr{display:block;padding:var(--space-3) 0}
  .data-table--wide td{
    display:flex;
    justify-content:space-between;
    align-items:baseline;
    gap:var(--space-4);
    padding:5px 14px;
    text-align:left !important;
  }
  .data-table--wide td::before{
    content:attr(data-label);
    flex:none;
    color:var(--text-muted);
    font-family:var(--font-mono);
    font-size:var(--fs-mono-s);
    letter-spacing:.04em;
    text-transform:uppercase;
  }
  .data-table--wide td[data-strong]{display:block;padding-top:var(--space-2);font-size:var(--fs-body-l)}
  .data-table--wide td[data-strong]::before{display:none}
}
/* A table's total: the last body row, so it restacks with the others. */
.data-table__total{background:var(--surface-inset)}
.data-table__total td{font-weight:var(--fw-medium);color:var(--text-strong)}

/* ── Decision card ─────────────────────────────────────────────
   From ReTargetWebsite/src/components/product/ReportBody.astro, where the
   same list is the "watch items" panel. The class names stay the site's so
   the two stylesheets remain diffable; the app's heading calls these
   DECISIONS, which is the vocabulary rule in issue D5 — a migration is never
   blocked, it just needs decisions, so the warn tone here is deliberate and
   .callout--block is never reached for. */
.watch-list{
  display:flex;
  flex-direction:column;
  gap:var(--space-3);
  list-style:none;
  margin:0;
  padding:0;
}
.watch{display:flex;gap:var(--space-4)}
.watch__icon{color:var(--status-warn-fg);margin-top:2px;flex:none}
.watch__text{display:flex;flex-direction:column;gap:5px;flex:1;min-width:0}
.watch__head{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap}
.watch__title{font-size:var(--fs-heading-s);letter-spacing:var(--ls-heading-s)}
/* D15 — a decision card's title is the way into the item, and a card title is
   a standalone target rather than a word in a sentence. 118x22 measured. */
.watch__title a{display:inline-flex;align-items:center;min-height:var(--target-min)}
.watch__body{font-size:var(--fs-body-m);color:var(--text-muted)}
/* E13 — the items under a decision, for a tenant whose offer lists them. A
   link per line, each line a standalone tap target. */
.watch__items{
  display:flex;
  flex-direction:column;
  gap:var(--space-1);
  list-style:none;
  margin:var(--space-2) 0 0;
  padding:0;
  font-size:var(--fs-body-m);
}
.watch__items li{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);min-height:var(--target-min);overflow-wrap:anywhere}
.watch__more{font-size:var(--fs-body-s);color:var(--text-muted)}

/* ════════════════════════════════════════════════════════════════
   3. APP — the shell and screens the marketing site has no twin for
   ════════════════════════════════════════════════════════════════ */

/* ── App tokens (D18) ──────────────────────────────────────────
   tokens.css is the site's file verbatim, so what the app alone needs is
   declared here, after it. Same `:root` specificity, later in the cascade, so
   these win at every width — including over the site's md-up section rhythm.

   --section-y/-tight: the tighter 40/30 rhythm f984f4a chose for the app at
   all widths (the site runs 64/48, then 96/64 from md up). An app screen is a
   working surface, not a page of sections to scroll through.
   --surface-locked: the locked-panel hatch; the site has no entitlement
   boundary to draw.
   --ring-focus/-inverse: carried from the older export so nothing breaks
   mid-port. New work uses `outline:2px solid var(--focus-ring)` — a shadow ring
   is clipped by the `overflow:hidden` on .data-table. */
:root{
  --section-y:40px;
  --section-y-tight:30px;
  --surface-locked:repeating-linear-gradient(45deg,transparent,transparent 6px,rgba(28,23,19,.03) 6px,rgba(28,23,19,.03) 12px);
  --ring-focus:0 0 0 2px var(--surface-card),0 0 0 4px var(--focus-ring);
  --ring-focus-inverse:0 0 0 2px var(--pine-900),0 0 0 4px var(--focus-ring-inverse);
}

/* ── AppShell ──────────────────────────────────────────────────
   Per designsystem/ui_kits/app/AppShell.jsx: a 240px dark pine rail beside a
   sticky page header. The rail carries data-theme="dark" in the markup, so
   every semantic alias inside it resolves to the dark scope. */
.app-shell{display:flex;min-height:100vh;background:var(--surface-page)}

.app-sidebar{
  width:240px;
  flex:none;
  background:var(--pine-950);
  background-image:var(--grid-dark);
  background-size:var(--grid-size);
  border-right:1px solid var(--border-hairline);
  display:flex;
  flex-direction:column;
  gap:var(--space-7);
  padding:var(--space-5) var(--space-4);
  position:sticky;
  top:0;
  height:100vh;
}
.app-sidebar__brand{padding:0 var(--space-2)}
.app-nav{display:flex;flex-direction:column;gap:2px}
.app-nav__item{
  display:flex;
  align-items:center;
  gap:10px;
  height:36px;
  padding:0 var(--space-2);
  border:0;
  border-radius:var(--radius-sm);
  background:transparent;
  color:var(--pine-200);
  font-family:var(--font-sans);
  font-size:var(--fs-body-s);
  font-weight:var(--fw-medium);
  text-decoration:none;
  transition:var(--transition-control);
}
.app-nav__item:hover{background:var(--surface-inset);color:var(--clay-050);text-decoration:none}
.app-nav__item[aria-current="page"]{
  background:var(--surface-accent-soft);
  color:var(--clay-050);
  font-weight:var(--fw-semibold);
}
.app-sidebar__foot{margin-top:auto;display:flex;flex-direction:column;gap:var(--space-3)}
.app-sidebar__env{
  padding:var(--space-3);
  border-radius:var(--radius-md);
  background:var(--surface-inset);
  display:flex;
  flex-direction:column;
  gap:6px;
}
.app-sidebar__env-label{
  font-family:var(--font-mono);
  font-size:var(--fs-caption);
  letter-spacing:.1em;
  text-transform:uppercase;
  color:var(--pine-400);
}
.app-sidebar__env-value{font-size:var(--fs-body-s);color:var(--clay-050)}
/* ── User menu ─────────────────────────────────────────────────
   E6. The rail's footer is one disclosure: profile, plan, admin and sign out.
   `<details>` carries the open state, so nothing here needs a class toggled by
   script — `[open]` is the selector, and the menu works with JS blocked. */
.user-menu{position:relative;border-radius:var(--radius-md)}
/* nowrap + the min-width:0 below: a long email must ellipsise inside the
   240px rail, not push the avatar onto its own line. */
.user-menu__trigger{
  display:flex;
  align-items:center;
  flex-wrap:nowrap;
  gap:9px;
  padding:var(--space-2);
  min-width:0;
  border-radius:var(--radius-md);
  cursor:pointer;
  transition:var(--transition-control);
  list-style:none;
}
/* The second half of hiding the marker: `list-style` covers every current
   engine, this covers the Safari versions that still draw a ::-webkit triangle
   regardless. */
.user-menu__trigger::-webkit-details-marker{display:none}
.user-menu__trigger:hover{background:var(--surface-inset)}
.user-menu__trigger:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}
.user-menu--current .user-menu__trigger{background:var(--surface-accent-soft)}
.user-menu__chevron{flex:none;color:var(--pine-400);transition:transform var(--dur-fast) var(--ease-standard)}
.user-menu[open] .user-menu__chevron{transform:rotate(180deg)}

/* Opens upward: the trigger is pinned to the bottom of a full-height rail, so
   there is nothing below it to open into. */
.user-menu__panel{
  position:absolute;
  bottom:calc(100% + var(--space-2));
  left:0;
  right:0;
  z-index:30;
  padding:var(--space-2);
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-md);
  background:var(--pine-900);
  box-shadow:var(--shadow-modal);
  display:flex;
  flex-direction:column;
  gap:var(--space-2);
}
.user-menu__identity{
  display:flex;
  flex-direction:column;
  gap:1px;
  padding:var(--space-2);
  min-width:0;
}
.user-menu__identity-name,.user-menu__identity-email{
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.user-menu__identity-name{font-size:var(--fs-body-s);font-weight:var(--fw-semibold);color:var(--clay-050)}
.user-menu__identity-email{font-family:var(--font-mono);font-size:10px;color:var(--pine-400)}
.user-menu__group{display:flex;flex-direction:column;gap:2px;padding-top:var(--space-2);border-top:1px solid var(--border-hairline)}
.user-menu__item{
  display:flex;
  align-items:center;
  gap:10px;
  width:100%;
  height:34px;
  padding:0 var(--space-2);
  border:0;
  border-radius:var(--radius-sm);
  background:transparent;
  color:var(--pine-200);
  font-family:var(--font-sans);
  font-size:var(--fs-body-s);
  font-weight:var(--fw-medium);
  text-align:left;
  text-decoration:none;
  cursor:pointer;
  transition:var(--transition-control);
}
.user-menu__item:hover{background:var(--surface-inset);color:var(--clay-050);text-decoration:none}
.user-menu__item:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}
.user-menu__item[aria-current="page"]{background:var(--surface-accent-soft);color:var(--clay-050);font-weight:var(--fw-semibold)}

.app-user__avatar{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:28px;height:28px;
  flex:none;
  border-radius:var(--radius-pill);
  background:var(--pine-700);
  color:var(--clay-050);
  font-family:var(--font-mono);
  font-size:11px;
  text-transform:uppercase;
}
.app-user__text{min-width:0;flex:1;display:flex;flex-direction:column}
.app-user__name,.app-user__meta{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.app-user__name{font-size:var(--fs-body-s);color:var(--clay-050)}
.app-user__meta{font-family:var(--font-mono);font-size:10px;color:var(--pine-400)}

.app-body{flex:1;min-width:0;display:flex;flex-direction:column}
.app-header{
  position:sticky;
  top:0;
  z-index:20;
  background:color-mix(in oklch,var(--surface-page) 90%,transparent);
  backdrop-filter:blur(8px);
  border-bottom:1px solid var(--border-hairline);
  padding:var(--space-5) var(--space-8);
  display:flex;
  align-items:flex-end;
  flex-wrap:wrap;
  gap:var(--space-6);
}
/* `flex:1` alone let the actions win the row outright: two header buttons at
   390px left ~50px for the title, which wrapped the report's subtitle over six
   lines. The basis makes the text claim a row of its own before it is squeezed
   (D5, mobile pass) and still grow to fill one on a wide screen. */
.app-header__text{flex:1 1 260px;min-width:0;display:flex;flex-direction:column;gap:3px}
.app-header__title{font-size:var(--fs-heading-m);line-height:var(--lh-heading-m);letter-spacing:var(--ls-heading-m)}
.app-header__subtitle{font-size:var(--fs-body-s);color:var(--text-muted)}
.app-header__actions{display:flex;flex-wrap:wrap;gap:var(--space-3)}
.app-main{padding:var(--space-7) var(--space-8) var(--space-11);max-width:1240px;width:100%}

/* ── Mobile shell (D15) ────────────────────────────────────────
   The two controls the bar below 1024px carries: the drawer trigger on the
   left and the account chip on the right. 44px, which is the floor for a
   thumb and nearly twice what the 28px avatar gave; the issue's "32px logo
   bar" cannot hold a 44px target, and the target is the half that was
   reported broken, so the bar is as tall as its controls.

   Hidden by default and shown only inside the media query below, and only
   with `html.has-dialog` — a trigger for a dialog the browser cannot show is
   a dead control, and the rail's own nav and `<details>` menu are what render
   instead. */
.app-sidebar__control{
  display:none;
  align-items:center;
  justify-content:center;
  flex:none;
  width:44px;height:44px;
  padding:0;
  border:0;
  border-radius:var(--radius-md);
  background:transparent;
  color:var(--pine-200);
  cursor:pointer;
  transition:var(--transition-control);
}
.app-sidebar__control:hover{background:var(--surface-inset);color:var(--clay-050)}
.app-sidebar__control:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}
.app-sidebar__chip[aria-expanded="true"],
.app-sidebar__chip:hover{background:var(--surface-inset)}

/* Below lg the rail becomes a bar and everything on it moves into the two
   sheets below. The scroll container this used to be — `overflow-x:auto` with
   `scrollbar-width:none` — measured 551px of content in a 390px viewport with
   nothing on screen to say so, put a third of the primary navigation
   off-screen, and clipped the account panel it also pinned inside itself. */
@media (max-width:1023px){
  .app-shell{flex-direction:column}
  .app-sidebar{
    width:100%;
    height:auto;
    position:static;
    flex-direction:row;
    align-items:center;
    flex-wrap:wrap;
    gap:var(--space-3);
    padding:var(--space-2) var(--space-3);
    border-right:0;
    border-bottom:1px solid var(--border-hairline);
  }
  /* The brand takes the middle and pushes the account control to the far
     edge, whichever of the two forms of it is rendered. */
  .app-sidebar__brand{padding:0;margin-right:auto}
  /* The bar's tagline only. Unscoped, this also hid the auth card's — the one
     cold surface where the tagline says what the product is (D18). */
  .app-sidebar .logotype__tagline{display:none}
  .app-sidebar__env{display:none}
  .app-sidebar__foot{margin-top:0;flex-direction:row;align-items:center;gap:var(--space-2)}

  /* With `<dialog>`: two controls, and the rail's own nav and menu are what
     the drawer and the sheet hold instead of a second copy. */
  html.has-dialog .app-sidebar__control{display:inline-flex}
  html.has-dialog .app-sidebar > .app-nav,
  html.has-dialog .app-sidebar .user-menu{display:none}

  /* Without it: the bar keeps them, laid out as a wrapping row. Wrapping is
     the whole fix — the old bar had the same three links and scrolled them
     out of reach instead. */
  html:not(.has-dialog) .app-nav{flex-direction:row;flex-wrap:wrap;gap:var(--space-1)}
  html:not(.has-dialog) .user-menu__trigger .app-user__text{display:none}

  .app-header{padding:var(--space-5) var(--space-5)}
  .app-main{padding:var(--space-6) var(--space-5) var(--space-9)}
}

/* ── Sheet (D15) ───────────────────────────────────────────────
   The drawer and the account sheet: one component, two edges. Both are
   `<dialog>` shown with `showModal()`, so the margin reset and the explicit
   inset are what turn the UA's centred `fit-content` box into a panel anchored
   to an edge of the viewport.

   `display:none` above lg rather than only unopened: a dialog nothing can open
   is still in the accessibility tree the moment someone's script opens it, and
   the rail is the navigation at that width. */
.sheet{
  margin:0;
  padding:0;
  max-width:none;
  max-height:none;
  border:0;
  background:var(--surface-page);
  color:var(--text-body);
  box-shadow:var(--shadow-modal);
  overscroll-behavior:contain;
}
.sheet[open]{display:flex;flex-direction:column}
.sheet::backdrop{background:rgba(20,16,9,.52)}
.sheet--side{
  position:fixed;
  inset:0 auto 0 0;
  width:min(300px,86vw);
  height:100%;
  border-right:1px solid var(--border-hairline);
  border-radius:0 var(--radius-lg) var(--radius-lg) 0;
}
.sheet--bottom{
  position:fixed;
  inset:auto 0 0 0;
  width:100%;
  max-height:min(80vh,560px);
  border-top:1px solid var(--border-hairline);
  border-radius:var(--radius-lg) var(--radius-lg) 0 0;
}
.sheet__head{
  display:flex;
  flex:none;
  align-items:center;
  gap:var(--space-4);
  padding:var(--space-4) var(--space-4) var(--space-3);
  border-bottom:1px solid var(--border-hairline);
}
.sheet__title{
  flex:1;
  min-width:0;
  font-size:var(--fs-body-m);
  font-weight:var(--fw-semibold);
  letter-spacing:0;
  color:var(--text-strong);
}
.sheet__close{flex:none;min-width:44px;min-height:44px}
/* `min-height:0` beats the automatic minimum a flex item gets, which would
   otherwise hold the body at content height and make the sheet the scrolling
   box instead of this one — the same correction `.modal__body` carries. */
.sheet__body{
  flex:1;
  min-height:0;
  display:flex;
  flex-direction:column;
  gap:var(--space-2);
  padding:var(--space-3) var(--space-3) var(--space-5);
  overflow-y:auto;
}
/* A row in a sheet is a row for a thumb. Both the nav items and the account
   items are 34-36px in the rail, where they are a mouse target. */
.sheet .app-nav__item,
.sheet .user-menu__item{height:auto;min-height:44px;padding:0 var(--space-3)}
@media (min-width:1024px){
  .sheet,.sheet[open]{display:none}
}

/* Auth pages (login/signup) opt out of the shell entirely. */
.auth-shell{
  min-height:100vh;
  display:flex;
  align-items:center;
  justify-content:center;
  padding:var(--space-6);
  background:var(--surface-page);
  background-image:var(--grid-light);
  background-size:var(--grid-size);
}
.auth-card{width:100%;max-width:420px;display:flex;flex-direction:column;gap:var(--space-5)}

/* Six characters, in a box the size of six characters. The row is the width of
   the card by default, which made a verification code look like a mistyped
   sentence — 150px is six mono glyphs at the tracking below plus the control's
   own padding. Centred on `.field` rather than on the input alone so the label
   and any error centre with it. */
.code-entry .field{align-items:center}
.code-entry .input{width:150px}
.code-entry .input input{
  text-align:center;
  letter-spacing:.3em;
  /* Tracking is applied after the last glyph too, so without this the code
     sits half a letter-space left of centre. */
  text-indent:.3em;
}

/* The way out, stated as one sentence. The control inside it is a <button>
   because signing out is a POST, and a <form> may not sit inside a <p> — the
   parser closes the paragraph at the start tag — so the two are siblings on a
   row rather than one paragraph. */
.auth-footnote{
  display:flex;
  flex-wrap:wrap;
  align-items:baseline;
  gap:6px;
  font-size:var(--fs-body-s);
  line-height:var(--lh-body-s);
  color:var(--text-muted);
}

/* ── Legal links (E12) ─────────────────────────────────────────
   Terms · Privacy · DPA · Security, at the foot of the auth pages and of the
   account menu. Secondary by design: caption size, muted, no underline until
   hovered. Each link is still a 24px target — the row wraps rather than
   shrinking them, which is also what keeps 390px free of overflow. The
   separator is generated with empty alt text so a screen reader hears four
   links, not four links and three dots. */
.legal-links__list{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  list-style:none;
  font-size:var(--fs-caption);
  line-height:var(--lh-caption);
  color:var(--text-faint);
}
.legal-links__list li{display:inline-flex;align-items:center}
.legal-links__list li + li::before{content:"·" / "";padding:0 var(--space-1)}
.legal-links__link{
  display:inline-flex;
  align-items:center;
  min-height:var(--target-min);
  color:var(--text-muted);
  text-decoration:none;
}
.legal-links__link:hover{color:var(--text-link-hover);text-decoration:underline}
.legal-links--menu{padding:var(--space-1) var(--space-2) 0;border-top:1px solid var(--border-hairline)}

.auth-legal{
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:var(--space-1);
  text-align:center;
}
.auth-legal .legal-links__list{justify-content:center}
.auth-legal__partner{font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-faint)}

/* Error pages (D25): the auth card, one action, and the support address —
   which may be long enough to need breaking at 390px. */
.error-page__actions{display:flex;flex-wrap:wrap;gap:var(--space-3)}
.error-page__support a{overflow-wrap:anywhere}

/* ── Messages ──────────────────────────────────────────────── */
.messages{display:flex;flex-direction:column;gap:var(--space-3);list-style:none;margin-bottom:var(--space-5)}

/* ── First run (D16) ───────────────────────────────────────────
   The guided run, above the page it is guiding. An accent ground rather than a
   plain card: it is the one thing on the overview that is about what to do
   next rather than about what the estate holds, and it disappears for good at
   the first completed scan — so it can afford to be the loudest thing on the
   screen for the few minutes it exists. */
.first-run{
  background:var(--surface-accent-soft);
  border-color:var(--border-accent);
  padding:var(--space-5);
  margin-bottom:var(--space-6);
}
.first-run__lede{color:var(--text-muted);font-size:var(--fs-body-m);max-width:var(--measure-prose)}
/* The marker holds a digit until the step is done and a tick afterwards, and
   `.steps__marker` centres both — this only stops the glyph claiming a line of
   its own inside a box sized for a numeral. */
.first-run .steps__marker .icon{display:inline-block}

/* ── Business unit card ────────────────────────────────────── */
.bu-list{display:flex;flex-direction:column;gap:var(--space-4)}
.bu-card{display:flex;flex-direction:column;gap:var(--space-4)}
.bu-card__head{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap}
.bu-card__name{font-size:var(--fs-heading-s);letter-spacing:var(--ls-heading-s);margin-right:auto}
/* D15 — 79×22 measured, and the primary way into a business unit. The heading
   is the wrapper, the anchor inside it is the target. */
.bu-card__name a{display:inline-flex;align-items:center;min-height:var(--target-min)}
.bu-card__facts{
  display:flex;
  flex-wrap:wrap;
  gap:var(--space-2) var(--space-5);
  font-size:var(--fs-body-s);
  color:var(--text-muted);
}
/* D15 — `flex-wrap` and the break, together, are why the business unit page
   measured 425px of content in a 390px viewport: the auth endpoint is one
   unbreakable 44-character token in a mono `dd`, inside a nowrap flex row that
   could neither wrap the pair onto two lines nor break the token. `min-width:0`
   is the part without which the break never happens — a flex item's automatic
   minimum is its min-content size, which for an unbreakable string is the whole
   string. */
.bu-card__fact{display:flex;align-items:baseline;flex-wrap:wrap;gap:6px;min-width:0}
.bu-card__fact dt{color:var(--text-muted)}
.bu-card__fact dd{
  margin:0;
  min-width:0;
  font-family:var(--font-mono);
  color:var(--text-body);
  overflow-wrap:anywhere;
}
/* A value that is a link is a target, not a word in a sentence: "1 active" on
   the account page measured 62x17 and the scan count next to it 8x17, against
   the 24px floor `--target-min` states. */
.bu-card__fact dd a{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-height:var(--target-min);
  min-width:var(--target-min);
}
.bu-card__actions{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:var(--space-3);
  padding-top:var(--space-4);
  border-top:1px solid var(--border-hairline);
}
.bu-card__scan-form{display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap}
.bu-card__link{
  display:inline-flex;
  align-items:center;
  min-height:var(--target-min);
  margin-left:auto;
  font-size:var(--fs-body-s);
  white-space:nowrap;
}

/* A disclosure styled as a card — the "Add business unit" form. */
.disclosure{padding:0}
.disclosure > summary{
  display:flex;
  align-items:center;
  gap:var(--space-3);
  padding:var(--space-5) var(--space-6);
  cursor:pointer;
  font-size:var(--fs-heading-s);
  letter-spacing:var(--ls-heading-s);
  font-weight:var(--fw-semibold);
  color:var(--text-strong);
  list-style:none;
}
.disclosure > summary::-webkit-details-marker{display:none}
.disclosure > summary .icon{color:var(--text-muted);transition:transform var(--dur-fast) var(--ease-standard)}
.disclosure[open] > summary .icon{transform:rotate(180deg)}
.disclosure__body{padding:0 var(--space-6) var(--space-6)}

/* A disclosure nested inside a section rather than acting as the section.
   Lighter than the standalone card: its summary sits at body scale so it reads
   as a subsection heading, not a second page-level heading. */
.disclosure--sub{margin-top:var(--space-5);background:var(--surface-inset)}
.disclosure--sub > summary{
  padding:var(--space-4) var(--space-5);
  font-size:var(--fs-body-m);
  gap:var(--space-2);
}
.disclosure--sub .disclosure__body{padding:0 var(--space-5) var(--space-5)}

/* Django's {{ form.as_p }} output, brought into the design language without
   needing a form renderer. */
.form-rows p{display:flex;flex-direction:column;gap:6px;margin-bottom:var(--space-4)}
.form-rows label{font-size:var(--fs-body-m);font-weight:var(--fw-semibold);color:var(--text-strong)}
.form-rows input[type="text"],
.form-rows input[type="email"],
.form-rows input[type="password"],
.form-rows input[type="number"],
.form-rows select,
.form-rows textarea{
  width:100%;
  height:var(--control-h-md);
  padding:0 12px;
  border-radius:var(--radius-sm);
  background:var(--surface-card);
  border:1px solid var(--border-control);
  box-shadow:var(--shadow-inset-well);
  color:var(--text-strong);
  font-family:var(--font-sans);
  font-size:16px;
  transition:var(--transition-control);
}
.form-rows textarea{height:auto;padding:10px 12px}
.form-rows .helptext{font-size:var(--fs-body-s);color:var(--text-muted)}
.form-rows ul.errorlist{
  list-style:none;
  font-size:var(--fs-body-s);
  color:var(--status-block-fg);
  font-weight:var(--fw-medium);
}

/* ── Run page ──────────────────────────────────────────────────
   D26. The result strip, then the band and the archive side by side, then
   the run's activity beside where it ran. Each pair stacks below lg. */
.run__next,.run__grid{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--space-4)}
@media (min-width:768px){
  .run__next{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (min-width:1024px){
  .run__grid{grid-template-columns:minmax(0,1.1fr) minmax(0,0.9fr);align-items:start}
}
.run__card{align-self:start}
.run-card{display:flex;flex-direction:column;gap:var(--space-3)}
.run-card__title{font-size:var(--fs-heading-s);letter-spacing:var(--ls-heading-s)}
.run-card__note{font-size:var(--fs-body-s);color:var(--text-muted)}
.run-errors{margin:0}

/* The way back to the history, above a scan's title. */
.app-header__back{
  display:inline-flex;align-items:center;gap:4px;align-self:flex-start;
  min-height:var(--target-min);
  font-size:var(--fs-body-s);
  color:var(--text-muted);
  text-decoration:none;
}
.app-header__back:hover{color:var(--text-strong)}
.app-header__back-icon{transform:rotate(90deg)}

.run-facts{display:flex;flex-direction:column;gap:var(--space-4)}
.run-facts__title{font-size:var(--fs-heading-s);letter-spacing:var(--ls-heading-s)}
.run-facts__list{display:flex;flex-direction:column;gap:var(--space-3);margin:0}
.run-facts__row{display:flex;align-items:baseline;justify-content:space-between;gap:var(--space-4)}
.run-facts__row dt{font-size:var(--fs-body-s);color:var(--text-muted);flex:none}
.run-facts__row dd{
  margin:0;
  font-family:var(--font-mono);
  font-size:var(--fs-body-s);
  color:var(--text-strong);
  text-align:right;
  min-width:0;
  overflow-wrap:anywhere;
}
/* B12 — a row whose value is a list of names: the label above it, so neither
   is squeezed to a sliver beside the other on a phone. */
.run-facts__row--stacked{flex-direction:column;align-items:stretch;gap:var(--space-1)}
.run-facts__row--stacked dd{text-align:left}
/* A value that is a sentence, not a figure: the body face, quieter. */
.run-facts__row--note dd{font-family:var(--font-sans);color:var(--text-body)}
.run-facts__note{font-size:var(--fs-body-s);color:var(--text-muted)}

/* The run's step list, folded once the run has finished: a native
   <details>, so it opens and closes with no JavaScript and the keyboard. */
.run-steps__summary{
  display:flex;align-items:center;gap:var(--space-2);
  min-height:var(--target-min);
  cursor:pointer;list-style:none;
  font-size:var(--fs-body-m);font-weight:var(--fw-medium);color:var(--text-strong);
}
.run-steps__summary::-webkit-details-marker{display:none}
.run-steps__summary:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px;border-radius:2px}
.run-steps__chevron{color:var(--text-muted);transform:rotate(-90deg);transition:transform var(--dur-fast) var(--ease-standard)}
.run-steps[open] .run-steps__chevron{transform:none}
.run-steps__count{margin-left:auto;font-family:var(--font-mono);font-size:var(--fs-mono-s);color:var(--text-muted)}
.run-steps[open] > .steps{margin-top:var(--space-4)}
.section-head{
  display:flex;
  align-items:baseline;
  justify-content:space-between;
  gap:var(--space-4);
  margin-bottom:var(--space-4);
}
.section-head__title{font-size:var(--fs-heading-s);letter-spacing:var(--ls-heading-s)}
.section-head__meta{font-family:var(--font-mono);font-size:var(--fs-mono-s);color:var(--text-muted)}
/* D15 — a link in a section's meta slot is a real destination and was a 12px
   line of text: "All 4 scans →" measured 86×16 on the overview, against the
   24px floor `--target-min` states. The height is the target; the box is
   inline-flex so the line it sits on does not grow with it. */
.section-head__meta a{display:inline-flex;align-items:center;min-height:var(--target-min)}
.subsection-title{
  font-size:var(--fs-body-m);
  font-weight:var(--fw-semibold);
  color:var(--text-strong);
  margin-top:var(--space-5);
  margin-bottom:var(--space-3);
}
.note-list{
  display:flex;
  flex-direction:column;
  gap:var(--space-2);
  padding-left:var(--space-5);
  font-size:var(--fs-body-s);
  color:var(--text-muted);
}

/* Small print under a table: what was truncated, or why a table is empty. */
.table-note{
  font-size:var(--fs-body-s);
  color:var(--text-muted);
  margin-top:var(--space-3);
}

/* Readiness strip — the compact complexity summary above the report link. */
.readiness-strip{display:flex;align-items:center;gap:var(--space-4);flex-wrap:wrap}
.readiness-strip__band{
  font-family:var(--font-mono);
  font-size:30px;
  font-weight:var(--fw-medium);
  letter-spacing:-.02em;
  color:var(--text-strong);
  line-height:1;
}
.readiness-strip__label{margin-left:var(--space-2);font-size:var(--fs-body-s);color:var(--text-muted);font-weight:var(--fw-semibold)}
.readiness-strip__link{margin-left:auto}

@media print{
  .app-sidebar,.app-header,.skip-link{display:none !important}
  .app-shell{display:block}
  .app-main{padding:0;max-width:none}

  /* D10 — the two things .data-table does that paper cannot afford.
     `overflow:hidden` clips a table at the bottom of a sheet instead of
     letting the rest of it flow onto the next one, so a long table silently
     loses rows in a PDF. And the card-stack below is a `max-width` query with
     no media type, so it applies to print as well: A4 at 14mm margins is
     182mm — 688px — which is under the 767px
     breakpoint, so every printed table restacked into label/value pairs. Both
     are undone here rather than on the one page that noticed, because every
     page in the app can be printed. */
  .data-table{overflow:visible;border-radius:0}
  .data-table thead{position:static;width:auto;height:auto;overflow:visible;clip-path:none}
  .data-table tbody tr{display:table-row;padding:0}
  .data-table td{display:table-cell;padding:8px 10px;text-align:left}
  .data-table td[data-align="right"]{text-align:right}
  .data-table td::before{content:none}
  .data-table td[data-strong]{display:table-cell;padding-top:8px;font-size:inherit}
}

/* ── Report surfaces (D5) ──────────────────────────────────────
   Three rows the report pages had as inline `style="display:flex…"`
   attributes. The issue counted 48 of those on the report alone; naming the
   three shapes they were spelling out is what took the count to zero. */
.scan-nav{margin-bottom:var(--space-5)}
/* A row of controls that describe the list under them — the fate chips and the
   category's own readiness. Wraps, because a filter row that scrolls hides
   filters. */
.filter-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);margin:0}
.pager{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-3)}
.pager__position{font-size:var(--fs-body-s);color:var(--text-muted)}
/* D18 — the search above a paged table. The box takes what the row leaves and
   wraps under its label width on a phone, so the button never pushes the row
   past 390px. */
.table-controls{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);margin:0}
.table-controls .input{flex:1 1 220px;max-width:360px}

/* The table's own header strip and footer: the controls that act on a table,
   and the count of what it holds, inside its border rather than floating
   above and below it as separate cards. */
.data-table__toolbar{
  display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:var(--space-3);
  padding:10px 12px;
  border-bottom:1px solid var(--border-hairline);
  background:var(--surface-inset);
}
.data-table__toolbar .table-controls{flex:1 1 280px}
/* Chips under the search, so neither squeezes the other. */
.data-table__toolbar .filter-row{flex:1 1 100%}
.data-table__foot{
  display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:var(--space-3);
  padding:10px 12px;
  border-top:1px solid var(--border-hairline);
}
.data-table__count{font-size:var(--fs-body-s);color:var(--text-muted)}

/* ── Scan history timeline (D26) ───────────────────────────────
   One white card: its header carries the two filters, side by side and close
   to each other, and the scans sit under the day they were created. */
.scan-history{padding:0;overflow:visible}
.scan-history[aria-busy="true"] .timeline{opacity:.6}
.scan-history__head{
  display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-3) var(--space-4);
  padding:var(--space-4) var(--space-5);
  border-bottom:1px solid var(--border-hairline);
}
.scan-history__title{font-size:var(--fs-heading-s);letter-spacing:var(--ls-heading-s);margin-right:auto}
.scan-history__title:focus{outline:none}
.scan-history__title:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px;border-radius:2px}
.scan-history__filters{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2)}
.scan-history__foot{
  display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:var(--space-3);
  padding:10px var(--space-5);
  border-top:1px solid var(--border-hairline);
}

.timeline{display:flex;flex-direction:column;gap:var(--space-5);padding:var(--space-5)}
.timeline__date{
  margin:0 0 var(--space-2);
  font-family:var(--font-mono);font-size:var(--fs-mono-s);font-weight:var(--fw-medium);
  color:var(--text-muted);
}
.timeline__list{display:flex;flex-direction:column;gap:var(--space-2);margin:0;padding:0;list-style:none}

/* The whole card is the link. The grid keeps the status and the count in
   their own columns on a wide screen and folds them under the name on a
   phone, so a long business unit name wraps rather than pushing them off. */
.scan-card{
  display:grid;
  grid-template-columns:minmax(0,1fr) auto auto 16px;
  align-items:center;
  gap:var(--space-2) var(--space-4);
  min-height:56px;
  padding:var(--space-3) var(--space-4);
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-md);
  background:var(--surface-card);
  color:var(--text-body);
  text-decoration:none;
  transition:var(--transition-control);
}
.scan-card:hover{border-color:var(--border-strong);background:var(--surface-inset);text-decoration:none}
.scan-card:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}
.scan-card__main{display:flex;flex-direction:column;gap:2px;min-width:0}
.scan-card__name{font-weight:var(--fw-semibold);color:var(--text-strong);overflow-wrap:anywhere}
.scan-card__meta{font-size:var(--fs-body-s);color:var(--text-muted)}
.scan-card__items{font-size:var(--fs-body-s);color:var(--text-muted);text-align:right;white-space:nowrap}
.scan-card__items .mono{color:var(--text-strong)}
.scan-card__chevron{color:var(--text-muted);transform:rotate(-90deg)}
@media (max-width:599px){
  .scan-card{
    grid-template-columns:minmax(0,1fr) auto 16px;
    grid-template-areas:"main main chevron" "status items chevron";
  }
  .scan-card__main{grid-area:main}
  .scan-card__status{grid-area:status}
  .scan-card__items{grid-area:items}
  .scan-card__chevron{grid-area:chevron}
}

/* A compact filter: the name and the value in force on one quiet trigger,
   the choices as a list of links under it. Hairline and no shadow, like every
   other surface. */
.filter-menu{position:relative}
.filter-menu__trigger{
  display:inline-flex;align-items:center;gap:6px;
  min-height:32px;
  padding:0 10px;
  border:1px solid var(--border-control);
  border-radius:var(--radius-sm);
  background:var(--surface-card);
  font-size:var(--fs-body-s);
  cursor:pointer;
  list-style:none;
  transition:var(--transition-control);
}
.filter-menu__trigger::-webkit-details-marker{display:none}
.filter-menu__trigger:hover{border-color:var(--border-strong)}
.filter-menu__trigger:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}
.filter-menu__label{color:var(--text-muted)}
.filter-menu__value{color:var(--text-strong);font-weight:var(--fw-medium);max-width:16rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.filter-menu__chevron{color:var(--text-muted);transition:transform var(--dur-fast) var(--ease-standard)}
.filter-menu[open] .filter-menu__chevron{transform:rotate(180deg)}
.filter-menu__panel{
  position:absolute;
  top:calc(100% + 4px);
  left:0;
  z-index:30;
  min-width:max(100%,14rem);
  max-width:min(22rem,calc(100vw - 32px));
  max-height:min(20rem,60vh);
  overflow-y:auto;
  margin:0;
  padding:4px;
  list-style:none;
  border:1px solid var(--border-strong);
  border-radius:var(--radius-md);
  background:var(--surface-card);
}
.filter-menu__option{
  display:flex;align-items:center;gap:var(--space-2);
  min-height:36px;
  padding:0 var(--space-2);
  border-radius:var(--radius-sm);
  color:var(--text-body);
  font-size:var(--fs-body-s);
  text-decoration:none;
  overflow-wrap:anywhere;
}
.filter-menu__option:hover{background:var(--surface-inset);color:var(--text-strong);text-decoration:none}
.filter-menu__option:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}
.filter-menu__option[aria-current="true"]{color:var(--text-strong);font-weight:var(--fw-semibold)}
.filter-menu__check{display:inline-flex;width:14px;flex:none;color:var(--text-accent)}
/* On a phone the filters take their own row and wrap, so each menu starts at
   the card's left edge and its panel, capped at the viewport, opens inside it. */
@media (max-width:599px){
  .scan-history__filters{flex:1 1 100%}
}

/* An empty list's message and its one next step, centred in the card. */
.empty-state{
  display:flex;flex-direction:column;align-items:center;gap:var(--space-2);
  padding:var(--space-8) var(--space-5);
  text-align:center;
}
.empty-state__title{font-weight:var(--fw-semibold);color:var(--text-strong)}
.empty-state__body{color:var(--text-muted);margin-bottom:var(--space-2)}

/* ── Report item detail (D5, level 4) ──────────────────────────
   The band and the sentence that justifies it, in one block. C7 refuses to
   store a band with no stated driver — "a band with no stated reason is a
   vibe" — so the page renders them together rather than putting the reason
   somewhere a reader has to go and find. Days sit in the secondary position
   and are labelled an estimate; D6 makes that the rule on every surface. */
.item-verdict{display:flex;flex-wrap:wrap;gap:var(--space-5) var(--space-8);align-items:flex-start}
/* The headline claims the row and the secondary pairs wrap under it below md,
   so the band is the first thing read at every width rather than the first of
   three equal columns on a wide screen and the last on a narrow one. */
.item-verdict__headline{
  display:flex;
  flex-direction:column;
  gap:var(--space-2);
  flex:1 1 320px;
  min-width:0;
}
.item-verdict__secondary{
  display:flex;
  flex-wrap:wrap;
  gap:var(--space-4) var(--space-7);
  margin:0;
  flex:none;
}
.item-verdict__pair{display:flex;flex-direction:column;gap:var(--space-2);flex:none}
.item-verdict__pair dd{margin:0}
/* Secondary, and sized to say so: the same figure at the band's size would be
   two headlines competing, which is the reading D6 exists to prevent. */
.item-verdict__days{font-size:var(--fs-body-m);color:var(--text-strong)}

/* ── Band cell (D6) ────────────────────────────────────────────
   A band and the sentence that justifies it, as one cell of a list. C7 refuses
   to store a band with no stated reason, so the markup keeps them in a single
   element and a row cannot print the badge alone. The column caps its measure
   rather than the driver's line count: a truncated reason is the same defect
   as a missing one. */
.band{display:flex;flex-direction:column;align-items:flex-start;gap:var(--space-1);min-width:0}
.band__driver{
  font-size:var(--fs-caption);
  line-height:var(--lh-caption);
  color:var(--text-muted);
  max-width:38ch;
}
/* The headline variant, for the one surface where the band is what the page is
   about rather than one value in a row. Same block and the same markup — only
   the sizes change, so the two cannot drift into two different rules about
   whether the sentence is optional. */
.band--lg{gap:var(--space-3)}
.band--lg .band__driver{
  font-size:var(--fs-body-m);
  line-height:var(--lh-body-m);
  color:var(--text-body);
  max-width:var(--measure-prose);
}
/* In the mobile card-stack every cell is a label/value row, right-aligned
   against the card edge. A driver is three or four lines of prose and prose
   set ragged-left is the one thing that stack does worse than the table, so
   the cell wraps and the band takes the line under its own label instead —
   the same break from the pattern `[data-strong]` already makes for a row's
   title, and for the same reason. */
@media (max-width:767px){
  .data-table td.data-table__band{flex-wrap:wrap;row-gap:var(--space-2)}
  .data-table td.data-table__band .band{flex:1 1 100%;align-items:flex-start;text-align:left}
  .data-table td.data-table__band .band__driver{max-width:none}
}
/* And for the wide table, over the band of widths where it alone restacks. */
@media (min-width:768px) and (max-width:1023px){
  .data-table--wide td.data-table__band{flex-wrap:wrap;row-gap:var(--space-2)}
  .data-table--wide td.data-table__band .band{flex:1 1 100%;align-items:flex-start;text-align:left}
  .data-table--wide td.data-table__band .band__driver{max-width:none}
}

/* ── Secondary line in a cell (D8) ─────────────────────────────
   A row's second fact under its first: an external key, a hint, a reason. */
.step-type{
  display:block;
  font-size:var(--fs-caption);
  line-height:var(--lh-caption);
  color:var(--text-muted);
  font-weight:var(--fw-regular);
}

/* ── Plan strip (issue #7) ─────────────────────────────────────
   Persistent entitlement summary under the header. Values ride on tokens;
   visual polish stays with Şirin (UI authority). Never printed. */
.plan-strip{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:var(--space-3);
  padding:var(--space-3) var(--space-4);
  margin-bottom:var(--space-5);
  border-radius:var(--radius-sm);
  background:var(--surface-card);
  border:1px solid var(--border-control);
}
.plan-strip--exhausted{border-color:var(--status-warn-fg)}
.plan-strip--blocked{border-color:var(--status-block-fg)}
.plan-strip__text{font-size:var(--fs-body-s);color:var(--text-strong)}
/* Wraps because the flags state on/off in words now (D4), which is wider than
   the colour-only pills it replaced — at 320px the row was the one part of the
   strip that could overflow instead of reflowing. */
.plan-strip__flags{display:flex;flex-wrap:wrap;gap:var(--space-2);margin-left:auto}
.plan-strip__cta{white-space:nowrap}
@media print{.plan-strip{display:none}}

/* ── Offer cards (E13) ─────────────────────────────────────────
   The billing page's three offers, after the site's pricing cards
   (ReTargetWebsite/src/pages/pricing.astro `.tier`): name and badge, the
   price line between hairlines, the list, the note, the action pinned to the
   foot. Flat white cards like every other card in the app — the site's
   inverse, floating featured card is a marketing treatment. */
.offer{display:flex;flex-direction:column}
.offer__head{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);flex-wrap:wrap}
.offer__name{font-size:var(--fs-heading-m);letter-spacing:var(--ls-heading-m)}
.offer__price{display:flex;flex-direction:column;gap:2px;padding-block:var(--space-2);border-block:1px solid var(--border-hairline)}
.offer__price-figure{font-size:var(--fs-heading-m);font-weight:var(--fw-semibold);color:var(--text-strong)}
.offer__cadence{font-family:var(--font-mono);font-size:var(--fs-mono-s);color:var(--text-muted)}
.offer__list{display:flex;flex-direction:column;gap:var(--space-3);list-style:none;margin:0;padding:0}
.offer__list li{display:flex;gap:var(--space-2);color:var(--text-body)}
.offer__list .icon{flex:none;margin-top:4px;color:var(--text-accent)}
.offer__note{font-size:var(--fs-body-s);color:var(--text-muted)}
/* The request form and its receipt: one column at reading width. Was an inline
   `style="max-width:560px"` on both. */
.form-card{max-width:560px}

/* ── Modal (D2) ────────────────────────────────────────────────
   Chrome around a page fetched into it. Only the parts the native <dialog>
   does not give us are here: it already provides the backdrop element, focus
   containment, Esc, and inertness of the document behind it.

   `display` is set on [open] only — putting it on `.modal` would beat the UA's
   `dialog:not([open]){display:none}` and render the empty shell on every page. */
.modal{
  width:min(96vw,980px);
  max-height:min(88vh,940px);
  padding:0;
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-lg);
  background:var(--surface-page);
  color:var(--text-body);
  box-shadow:var(--shadow-modal);
  overflow:hidden;
}
.modal[open]{display:flex;flex-direction:column}
.modal::backdrop{background:rgba(20,16,9,.42)}
/* The page behind a modal must not scroll under it. `:has` is already load
   bearing in this stylesheet (.input:has(input:focus)). The gutter is reserved
   permanently rather than only while the modal is open: taking the scrollbar
   away is what makes the page behind jump sideways as the dialog appears, and
   a gutter that comes and goes is the jump. */
html{scrollbar-gutter:stable}
html:has(dialog[open]){overflow:hidden}

.modal__head{
  display:flex;
  flex:none;
  align-items:center;
  gap:var(--space-4);
  padding:var(--space-5) var(--space-6);
  border-bottom:1px solid var(--border-hairline);
  background:var(--surface-card);
}
.modal__title{
  flex:1;
  min-width:0;
  font-size:var(--fs-heading-s);
  letter-spacing:var(--ls-heading-s);
  color:var(--text-strong);
}
.modal__close{flex:none}
/* `min-height:0` beats the automatic minimum a flex item gets, which would
   otherwise hold the body at content height and make `.modal` the scrolling
   box instead of this one. */
.modal__body{
  flex:1;
  min-height:0;
  padding:var(--space-6);
  overflow-y:auto;
  overscroll-behavior:contain;
}
/* Between showModal() and the fragment arriving. A height, so the box does not
   snap open from nothing, and no spinner: on a same-origin fetch of a page
   this size the wait is usually under a frame, and a spinner that flashes for
   80ms reads as a fault. */
.modal--loading .modal__body{min-height:180px}

/* ── Connect a business unit (D2) ──────────────────────────────
   Rendered by both /business-units/add/ and the modal above it, from one
   fragment. Per designsystem/ui_kits/app/ConnectBU.jsx: credentials on the
   left, what we ask for and the way out on the right. */
.connect__cols{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--space-6);align-items:start}
@media (min-width:1024px){
  .connect__cols{grid-template-columns:minmax(0,1.1fr) minmax(0,0.9fr)}
}
.connect__heading{font-size:var(--fs-heading-s);letter-spacing:var(--ls-heading-s)}
.connect__lede{font-size:var(--fs-body-s);color:var(--text-muted);line-height:1.55}
.connect__lede strong{color:var(--text-body);font-weight:var(--fw-semibold)}
.connect__actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-3)}
.connect__note{
  display:inline-flex;
  align-items:center;
  gap:5px;
  font-size:var(--fs-body-s);
  color:var(--text-faint);
}
.connect__aside-heading{font-size:var(--fs-body-m);font-weight:var(--fw-semibold);color:var(--text-strong)}
.connect__aside-text{font-size:var(--fs-body-s);color:var(--text-muted);line-height:1.55}

/* D16 — `.steps--inline` went with the four-step strip it was written for.
   It laid the form's own steps out as a row above the form, under a container
   query because the column is ~700px on the page and ~470px inside the modal;
   the strip is gone, and a modifier nothing renders is a rule that will be
   wrong the first time someone reaches for it.

   `--tight` is what the side panel wants instead: the same column the base
   component already is, closed up, because five instruction steps beside a
   form are a list to read rather than a progress indicator to scan. */
.steps--tight{gap:var(--space-3)}
.steps--tight .steps__label{font-size:var(--fs-body-s);color:var(--text-strong)}
.steps--tight .steps__detail{font-size:var(--fs-caption);line-height:1.5}

/* Four facts, each one enforced in code or a deployment fact — never a claim
   this app cannot keep. */
.fact-grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3);margin:0}
.fact-grid__item{
  display:flex;
  flex-direction:column;
  gap:2px;
  padding:var(--space-3);
  border-radius:var(--radius-sm);
  background:var(--surface-card);
  border:1px solid var(--border-hairline);
}
.fact-grid__item dt{font-size:var(--fs-caption);color:var(--text-muted)}
.fact-grid__item dd{
  margin:0;
  font-family:var(--font-mono);
  font-size:var(--fs-body-s);
  color:var(--text-strong);
  overflow-wrap:anywhere;
}

/* ── MetadataArchive (B9) ──────────────────────────────────── */
/* The archive's controls are the run summary's header action; what is left
   here is the locked panel's preview of them. */
.archive__card{display:flex;flex-direction:column;gap:var(--space-4)}
.archive__lede{font-size:var(--fs-body-m);color:var(--text-body);line-height:1.55;max-width:66ch}
/* ── Cluster map (D7) ──────────────────────────────────────────
   The dependency graph as a picture: one box per (layer × community), drawn
   on the server as static SVG with no library. Every coordinate comes from
   `dashboard.graph_map`; this file only paints.

   The fill/stroke pairs are the SEMANTIC tokens, never the base ramps, so the
   map flips with `[data-theme="dark"]` like everything else — and the five
   tone modifiers are the same five `fate_tone` returns, which is what keeps a
   box and the badge on the row below it from disagreeing about which of two
   fates is heavier. `dashboard.tests_classes.ToneTests` sweeps them. */
.cluster-map{margin:0}
.cluster-map__frame{
  /* A map wider than the content column scrolls rather than shrinking its own
     labels past reading size. Most scans are narrower than the column and the
     svg's max-width keeps them at natural size instead of stretching. */
  overflow-x:auto;
  padding:var(--space-4);
  background:var(--surface-card);
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-md);
}
.cluster-map__svg{display:block;max-width:100%;height:auto;margin-inline:auto}
/* The map's own numbers, over it. */
.cluster-map__stats{
  display:flex;flex-wrap:wrap;gap:var(--space-2) var(--space-5);
  list-style:none;margin:0 0 var(--space-3);padding:0;
  font-size:var(--fs-body-s);color:var(--text-muted);
}
.cluster-map__stat{font-family:var(--font-mono);font-weight:var(--fw-medium);color:var(--text-strong)}
/* A layer's band: the column read as a stage of the move. */
.cluster-map__band{fill:var(--surface-inset);stroke:var(--border-hairline);stroke-width:1}
.cluster-map__narrow{display:none}
.cluster-map__caption{margin-top:var(--space-3)}
.cluster-map__legend{
  font-size:var(--fs-body-s);
  color:var(--text-body);
  max-width:var(--measure-prose);
  margin:0;
}

/* Column headings — the migration axis. Mono because they report a position,
   which is the same reason table headers and counts are mono. */
.cluster-map__column{
  font-family:var(--font-mono);
  font-size:11px;
  letter-spacing:.06em;
  text-transform:uppercase;
  fill:var(--text-muted);
}
.cluster-map__column-note{
  font-family:var(--font-sans);
  font-size:11px;
  fill:var(--text-faint);
}

/* An arrow points at what a group depends on, so it runs against the columns'
   migration order. Hairline weight by default — the map's subject is the
   boxes, and edges drawn as heavily as the things they join produce the
   hairball D7 refused. */
.cluster-map__arrow{
  fill:none;stroke:var(--border-strong);stroke-linecap:round;stroke-opacity:.8;
  transition:opacity var(--dur-fast) var(--ease-standard),stroke var(--dur-fast) var(--ease-standard);
}
.cluster-map__arrow--cycle{stroke:var(--status-warn-fg);stroke-dasharray:5 4}
.cluster-map__head{fill:var(--border-strong)}
.cluster-map__head--cycle{fill:var(--status-warn-fg)}

.cluster-box{transition:opacity var(--dur-fast) var(--ease-standard)}
.cluster-box__body{
  fill:var(--surface-sunken);
  stroke:var(--border-default);
  stroke-width:1.25;
  transition:stroke var(--dur-fast) var(--ease-standard);
}
.cluster-box__label{
  font-family:var(--font-sans);
  font-size:12px;
  font-weight:var(--fw-semibold);
  fill:var(--text-strong);
}
.cluster-box__count{
  font-family:var(--font-mono);
  font-size:11px;
  fill:var(--text-muted);
}
/* The fate bar along a box's foot: each segment one outcome's share. */
.cluster-bar--ok{fill:var(--status-ok-fg)}
.cluster-bar--warn{fill:var(--status-warn-fg)}
.cluster-bar--ember{fill:var(--text-ember)}
.cluster-bar--block{fill:var(--status-block-fg)}
.cluster-bar--muted{fill:var(--border-strong)}

/* Tracing (static/js/cluster-map.js): one group, its arrows and the groups
   at their other ends stay; the rest of the map steps back. */
.cluster-map__svg.is-tracing .cluster-box{opacity:.35}
.cluster-map__svg.is-tracing .cluster-map__arrow{opacity:.12}
.cluster-map__svg.is-tracing .cluster-box.is-related{opacity:1}
.cluster-map__svg.is-tracing .cluster-map__arrow.is-related{opacity:1;stroke:var(--text-strong);stroke-opacity:1}
@media (prefers-reduced-motion:reduce){
  .cluster-box,.cluster-map__arrow{transition:none}
}

/* The key to the tints, under the map. */
.cluster-key{
  display:flex;flex-wrap:wrap;gap:var(--space-2) var(--space-5);
  list-style:none;margin:var(--space-3) 0 0;padding:0;
  font-size:var(--fs-body-s);color:var(--text-body);
}
.cluster-key__item{display:inline-flex;align-items:center;gap:var(--space-2)}
.cluster-key__swatch{width:14px;height:14px;border-radius:4px;border:1.25px solid;flex:none}
.cluster-key__swatch--ok{background:var(--status-ok-bg);border-color:var(--status-ok-fg)}
.cluster-key__swatch--warn{background:var(--status-warn-bg);border-color:var(--status-warn-fg)}
.cluster-key__swatch--ember{background:var(--surface-ember-soft);border-color:var(--text-ember)}
.cluster-key__swatch--block{background:var(--status-block-bg);border-color:var(--status-block-fg)}
.cluster-key__swatch--muted{background:var(--surface-sunken);border-color:var(--border-default)}
/* A linked box is a control: it says so on hover and it keeps the 2px ember
   ring on focus, which is never removed anywhere in this product. */
/* `text-decoration:none` has to sit on the ANCHOR. A decoration set on an
   ancestor is painted across its descendants and cannot be switched off by
   them, so the same declaration on `.cluster-map a text` changed nothing and
   every box kept the underline a link carries. */
.cluster-map a{cursor:pointer;text-decoration:none}
.cluster-map a:hover .cluster-box__body{stroke:var(--border-strong);stroke-width:2}
.cluster-map a:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}

/* The five tones `fate_tone` can return. Background and border only — the
   label stays `--text-strong` so a tint never decides legibility. */
.cluster-box--ok .cluster-box__body{fill:var(--status-ok-bg);stroke:var(--status-ok-fg)}
.cluster-box--warn .cluster-box__body{fill:var(--status-warn-bg);stroke:var(--status-warn-fg)}
.cluster-box--ember .cluster-box__body{fill:var(--surface-ember-soft);stroke:var(--text-ember)}
.cluster-box--block .cluster-box__body{fill:var(--status-block-bg);stroke:var(--status-block-fg)}
.cluster-box--muted .cluster-box__body{fill:var(--surface-sunken);stroke:var(--border-default)}
/* D26 — the group the list under the map is narrowed to. */
.cluster-box.is-selected .cluster-box__body{stroke:var(--text-strong);stroke-width:2.5}

/* Below this the map would be scaled to unreadable text, so it is replaced by
   a sentence pointing at the rows rather than left as a smear. The tables
   below carry every dependency the map aggregates — which is what makes
   hiding it honest rather than lossy. Print keeps the picture: an A4 sheet is
   wider than this breakpoint and the map is the page's one screenshot. */
@media (max-width:719px){
  .cluster-map__frame,.cluster-map__legend,.cluster-map__stats,.cluster-key{display:none}
  .cluster-map__narrow{
    display:block;
    margin:0;
    padding:var(--space-4);
    background:var(--surface-card);
    border:1px solid var(--border-hairline);
    border-radius:var(--radius-md);
    font-size:var(--fs-body-s);
    color:var(--text-muted);
  }
}
@media print{
  .cluster-map__frame{display:block;overflow:visible;break-inside:avoid}
  .cluster-map__narrow{display:none}
}

/* ── Docs (G1) ─────────────────────────────────────────────────
   The in-app guides: a guide nav beside the content once there is room for
   both, one column below that. A container query rather than a media query,
   because the room that matters is the main column's — at a 1024px window the
   rail has already taken 240px of it. Below 820px the content comes first and
   the guide nav follows it: on a phone the guide is what was asked for. */
.docs{container-type:inline-size}
.docs__layout{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--space-7);align-items:start}
.docs__main{min-width:0}
.docs__layout--index .docs-nav{display:none}
@container (min-width:820px){
  .docs__layout{grid-template-columns:220px minmax(0,1fr);gap:var(--space-9)}
  .docs__main{grid-column:2;grid-row:1}
  .docs-nav,.docs__layout--index .docs-nav{display:flex;grid-column:1;grid-row:1;position:sticky;top:var(--space-12)}
}

.docs-search{width:min(360px,100%)}
.docs-search .input{width:100%}
.docs-search__key{
  display:none;
  padding:0 6px;
  border:1px solid var(--border-control);
  border-radius:var(--radius-xs);
  line-height:1.5;
}
.docs__shortcut-hint{display:none}
html.has-docs-shortcut .docs-search__key{display:inline-block}
html.has-docs-shortcut .docs__shortcut-hint{display:inline}
.docs__shortcut-hint kbd,.docs__lede kbd{
  padding:1px 6px;
  border:1px solid var(--border-control);
  border-radius:var(--radius-xs);
  color:var(--text-strong);
}
.docs__lede{font-size:var(--fs-body-m);color:var(--text-muted);max-width:var(--measure-prose)}

.docs-nav{display:flex;flex-direction:column;gap:var(--space-5)}
.docs-nav__group{display:flex;flex-direction:column;gap:2px}
.docs-nav__heading{padding:0 var(--space-2) var(--space-1)}
.docs-nav__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}
.docs-nav__item{
  display:flex;
  align-items:center;
  gap:var(--space-2);
  min-height:32px;
  padding:6px var(--space-2);
  border-radius:var(--radius-sm);
  color:var(--text-body);
  font-size:var(--fs-body-s);
  text-decoration:none;
}
.docs-nav__item:hover{background:var(--surface-inset);color:var(--text-strong);text-decoration:none}
.docs-nav__item[aria-current="page"]{
  background:var(--surface-card);
  box-shadow:inset 0 0 0 1px var(--border-hairline);
  color:var(--text-strong);
  font-weight:var(--fw-semibold);
}

/* Cards and search hits: a whole card is the link, so the hover is the card's. */
.docs__cards{
  list-style:none;
  margin:0;
  padding:0;
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(260px,1fr));
  gap:var(--space-4);
}
.docs__cards > li{display:flex}
.docs-card{
  display:flex;
  flex:1;
  flex-direction:column;
  gap:var(--space-2);
  padding:var(--space-5);
  background:var(--surface-card);
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-lg);
  color:var(--text-body);
  text-decoration:none;
  transition:var(--transition-control);
}
.docs-card:hover{border-color:var(--border-control);text-decoration:none;color:var(--text-body)}
.docs-card__group{display:flex;align-items:center;gap:var(--space-2);color:var(--text-muted)}
.docs-card__title{
  font-size:var(--fs-heading-s);
  line-height:var(--lh-heading-s);
  letter-spacing:var(--ls-heading-s);
  font-weight:var(--fw-semibold);
  color:var(--text-strong);
}
.docs-card__summary{font-size:var(--fs-body-s);line-height:var(--lh-body-s);color:var(--text-muted);text-wrap:pretty}
.docs-card__read,.docs-card__path{font-family:var(--font-mono);font-size:var(--fs-mono-s);color:var(--text-faint)}
.docs-card__read{margin-top:auto;padding-top:var(--space-2)}
.docs-card--hit{max-width:760px}
.docs-card mark{background:var(--amber-100);color:var(--text-strong);border-radius:2px;padding:0 1px}
.docs__count{font-family:var(--font-mono);font-size:var(--fs-mono-s);font-weight:var(--fw-regular);letter-spacing:0;color:var(--text-muted)}
.docs-empty{
  display:flex;
  flex-direction:column;
  align-items:flex-start;
  gap:var(--space-3);
  max-width:760px;
  background-image:var(--grid-light);
  background-size:var(--grid-size);
  color:var(--text-faint);
}
.docs-empty__title{font-size:var(--fs-heading-s);font-weight:var(--fw-semibold);color:var(--text-strong)}
.docs-empty__text{font-size:var(--fs-body-s);color:var(--text-muted)}

/* One guide. 720px is a reading measure for body text at this size, and the
   tables and screenshots inside keep to it so the page has one edge. */
.docs-guide{display:flex;flex-direction:column;gap:var(--space-5);max-width:720px}
.docs-guide__head{
  display:flex;
  flex-direction:column;
  gap:var(--space-3);
  padding-bottom:var(--space-5);
  border-bottom:1px solid var(--border-hairline);
}
.docs-guide__path{font-family:var(--font-mono);font-size:var(--fs-mono-s);color:var(--text-faint);overflow-wrap:anywhere}
.docs-guide__title{font-size:var(--fs-heading-l);line-height:var(--lh-heading-l);letter-spacing:var(--ls-heading-l)}
.docs-guide__summary{font-size:var(--fs-body-l);line-height:var(--lh-body-l);color:var(--text-body)}
.docs-guide__meta{display:flex;flex-wrap:wrap;gap:var(--space-2)}
.docs-guide__meta .badge{gap:6px}
.docs-guide__body{
  display:flex;
  flex-direction:column;
  gap:var(--space-5);
  font-size:var(--fs-body-m);
  line-height:var(--lh-body-m);
  color:var(--text-body);
}
.docs-guide__heading{
  margin-top:var(--space-4);
  scroll-margin-top:var(--space-13);
  font-size:var(--fs-heading-s);
  line-height:var(--lh-heading-s);
  letter-spacing:var(--ls-heading-s);
}
.docs-guide__list{margin:0;padding-left:20px;display:flex;flex-direction:column;gap:var(--space-2)}
.docs-guide__body strong{color:var(--text-strong);font-weight:var(--fw-semibold)}
.docs-guide__body :not(pre) > code{
  padding:1px 5px;
  border-radius:var(--radius-xs);
  background:var(--surface-sunken);
  color:var(--text-strong);
  overflow-wrap:anywhere;
}
.docs-guide__body .data-table [data-numeric]{white-space:nowrap;font-variant-numeric:tabular-nums}

.docs-steps{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--space-4)}
.docs-steps__item{display:grid;grid-template-columns:28px minmax(0,1fr);gap:var(--space-3);scroll-margin-top:var(--space-13)}
.docs-steps__marker{
  display:flex;
  align-items:center;
  justify-content:center;
  width:28px;height:28px;
  border-radius:var(--radius-pill);
  border:1px solid var(--border-control);
  background:var(--surface-card);
  font-family:var(--font-mono);
  font-size:var(--fs-mono-s);
  color:var(--text-strong);
}
.docs-steps__text{display:flex;flex-direction:column;gap:2px;padding-top:3px;min-width:0}
.docs-steps__title{font-weight:var(--fw-semibold);color:var(--text-strong)}
.docs-steps__detail{color:var(--text-muted)}
.docs-steps-side{display:flex;flex-wrap:wrap;align-items:flex-start;gap:var(--space-6)}
.docs-steps-side__steps{flex:1 1 260px;min-width:0}
.docs-steps-side__shot{flex:0 1 280px;min-width:220px}

.docs-shot{margin:var(--space-2) 0;display:flex;flex-direction:column;gap:var(--space-2)}
.docs-shot__frame{
  display:block;
  align-self:flex-start;
  max-width:100%;
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-lg);
  overflow:hidden;
  background:var(--surface-card);
  padding:var(--space-2);
}
.docs-shot--round .docs-shot__frame{padding:0;cursor:zoom-in}
.docs-shot--bare{margin-top:calc(-1 * var(--space-3))}
.docs-shot--bare .docs-shot__frame{border:0;padding:0;background:none;border-radius:0}
.docs-shot img{display:block;width:100%;height:auto}
.docs-shot__caption{font-size:var(--fs-body-s);line-height:var(--lh-body-s);color:var(--text-muted)}

.docs-next{
  display:flex;
  align-items:center;
  gap:var(--space-4);
  margin-top:var(--space-5);
  padding:var(--space-4) var(--space-5);
  background:var(--surface-card);
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-lg);
  color:var(--text-muted);
  text-decoration:none;
}
.docs-next:hover{border-color:var(--border-control);text-decoration:none;color:var(--text-muted)}
.docs-next__text{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}
.docs-next__title{font-size:var(--fs-body-m);font-weight:var(--fw-semibold);color:var(--text-strong)}

/* The Docs entry in the rail and the drawer. In the drawer it sits under a
   rule, below the three sections it is not one of. Below 1024px with
   `<dialog>` the bar shows only its two controls, so the copy in the rail's
   footer steps aside for the one in the drawer. */
.app-nav__rule{display:block;height:1px;margin:var(--space-2) 0;background:var(--border-hairline)}
@media (max-width:1023px){
  html.has-dialog .app-sidebar__foot > .app-nav__item--docs{display:none}
  .app-header__actions:has(.docs-search){flex:1 1 100%}
  .docs-search{width:100%}
}
/* A restacked cell is a flex row of label and value; one wrapper keeps a
   value with **bold** in it from being split into columns at every tag. */
.docs-cell{min-width:0;text-align:left}
/* "/" means nothing on a touch keyboard, so the hint is for pointer devices. */
@media (hover:none){
  html.has-docs-shortcut .docs-search__key,
  html.has-docs-shortcut .docs__shortcut-hint{display:none}
}

/* D20 — the account page's Branding section. The stored logo is shown on a
   sunken tile, so the edges of a transparent PNG are visible, at about the
   height the executive PDF prints it. */
.brand-preview{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-4)}
.brand-preview__image{
  display:block;
  max-width:100%;
  max-height:80px;
  padding:var(--space-3);
  background:var(--surface-sunken);
  border:1px solid var(--border-hairline);
  border-radius:var(--radius-md);
}
/* A file input in the `.input` wrapper. The wrapper's fixed height is a text
   box's; the native chooser button needs its own room, drawn as an outline
   button so it does not arrive in each browser's default grey. */
.input:has(input[type="file"]){height:auto;min-height:var(--control-h-md);padding:var(--space-2) 12px}
.input input[type="file"]::file-selector-button{
  margin-right:var(--space-3);
  padding:6px 12px;
  border:1px solid var(--border-control);
  border-radius:var(--radius-sm);
  background:var(--surface-card);
  color:var(--text-strong);
  font:inherit;
  cursor:pointer;
}

/* ── Edit a business unit (D23) ───────────────────────────────
   One card at reading width: four boxes and a verdict, nothing beside them.
   The connect page keeps its two columns because its right half is the
   trust exercise; an edit has nothing new to promise. */
.bu-edit{max-width:680px}

/* ── Busy buttons (D24) ───────────────────────────────────────
   A pressed "Test connection" says so: the spinner replaces its icon and the
   label becomes its busy label (`data-busy-label`). Opacity stays full while
   it works — `.btn[disabled]`'s 45% is for a control you may not press, and
   this one you just did. */
.btn[aria-busy="true"],.btn[aria-busy="true"][disabled]{opacity:1;cursor:progress}
.btn[aria-busy="true"] .btn__spinner{flex:none}
.callout[data-connection-result]:focus{outline:2px solid var(--ember-500);outline-offset:2px}

/* ── Migration plans (2026-10-22) ─────────────────────────────
   The list reuses the business unit card: a plan list is one card per
   business unit. Archived plans fold under it; the create form sits at
   reading width like the edit form. */
.plan-archive > summary{
  display:inline-flex;align-items:center;min-height:var(--target-min);
  gap:var(--space-1);cursor:pointer;color:var(--text-muted);font-size:var(--fs-body-s);list-style:none;
}
.plan-archive > summary::-webkit-details-marker{display:none}
.plan-archive > summary .icon{transform:rotate(-90deg);transition:transform var(--dur-fast) var(--ease-standard)}
.plan-archive[open] > summary .icon{transform:none}
.plan-archive__list{margin:var(--space-2) 0 0;padding-left:var(--space-5);display:flex;flex-direction:column;gap:var(--space-2)}
.plan-archive__list .step-type{margin-left:var(--space-2)}
.plan-create{max-width:680px}

/* The plan page's filters: one row of small controls that wraps on a phone. */
.plan-filters{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--space-3)}
.plan-filters__field{margin:0;min-width:0}
.plan-filters__search{flex:1 1 220px}

/* The progress card (planning/_plan_progress.html, P2): two figures with a
   meter each, then the decision bar and decision × status. The meter is a
   native <progress>, so its value is announced without a script. */
.plan-figures{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--space-4);margin-bottom:var(--space-5)}
.plan-figure{display:flex;flex-direction:column;gap:var(--space-2);min-width:0}
.plan-figure__label{color:var(--text-muted);font-size:var(--fs-body-s);font-weight:var(--fw-medium)}
.plan-figure__row{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--space-2)}
.plan-figure__value{font-family:var(--font-mono);font-size:26px;font-weight:var(--fw-medium);letter-spacing:-.02em;line-height:1.1;color:var(--text-strong)}
.plan-figure__unit{font-size:var(--fs-body-s);color:var(--text-muted)}
.plan-figure__share{margin-left:auto;font-family:var(--font-mono);font-size:var(--fs-body-s);color:var(--text-strong)}
.plan-meter{display:block;width:100%;height:8px;border:0;border-radius:4px;background:var(--clay-100);overflow:hidden;appearance:none;-webkit-appearance:none}
.plan-meter::-webkit-progress-bar{background:var(--clay-100);border-radius:4px}
.plan-meter::-webkit-progress-value{background:var(--pine-500);border-radius:4px}
.plan-meter::-moz-progress-bar{background:var(--pine-500);border-radius:4px}
.plan-matrix{margin-top:var(--space-5)}
.plan-progress__types{margin-top:var(--space-3)}
.plan-progress__types .data-table{margin-top:var(--space-2)}
@media (max-width:479px){
  .plan-figures{grid-template-columns:1fr}
}

/* The decision bar (planning/_plan_bar.html). Four fixed decision colours
   from the token ramps, validated with the dataviz checks: CVD and
   normal-vision separation pass; the brand's muted pine and the deliberately
   grey "leave behind" read low-chroma, and amber is under 3:1 on white — so
   the legend carries every count in text, each segment has a tooltip, and a
   table gives the figures to a screen reader. Undecided is the empty track. */
.plan-legend{display:flex;flex-wrap:wrap;gap:var(--space-2) var(--space-5);margin:0 0 var(--space-4);padding:0;list-style:none;font-size:var(--fs-body-s);color:var(--text-body)}
.plan-legend__item{display:inline-flex;align-items:center;gap:var(--space-2)}
.plan-legend__count{font-family:var(--font-mono);color:var(--text-strong)}
.plan-swatch{display:inline-block;width:12px;height:12px;border-radius:3px}
.plan-swatch--move,.plan-bar__segment--move{background:var(--pine-500);fill:var(--pine-500)}
.plan-swatch--rebuild,.plan-bar__segment--rebuild{background:var(--amber-500);fill:var(--amber-500)}
.plan-swatch--leave,.plan-bar__segment--leave{background:var(--clay-600);fill:var(--clay-600)}
.plan-swatch--undecided,.plan-bar__segment--undecided{background:var(--clay-200);fill:var(--clay-200)}
.plan-bar__segment{stroke:var(--surface-card);stroke-width:2px}
.plan-bars{display:flex;flex-direction:column;gap:var(--space-3)}
.plan-bar{display:grid;grid-template-columns:minmax(0,180px) 1fr 4ch;align-items:center;gap:var(--space-3);font-size:var(--fs-body-s);color:var(--text-body)}
.plan-bar__label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.plan-bar__track{display:block;width:100%;height:12px;border-radius:3px;background:var(--clay-100)}
.plan-bar__total{font-family:var(--font-mono);text-align:right;color:var(--text-strong)}
.plan-bar--total{font-weight:var(--fw-medium);color:var(--text-strong)}
.plan-bar--total .plan-bar__track{height:16px}
@media (max-width:767px){
  .plan-bar{grid-template-columns:1fr 4ch}
  .plan-bar__label{grid-column:1 / -1}
}

/* The plan's items: a header row wherever the metadata type changes. */
.data-table tbody tr.plan-table__group{background:var(--surface-inset)}
.plan-table__group th{text-transform:none;letter-spacing:0;font-family:var(--font-sans);font-size:var(--fs-body-s);color:var(--text-strong);font-weight:var(--fw-semibold)}
@media (max-width:767px){
  .data-table tbody tr.plan-table__group{display:block;padding:0}
  .plan-table__group th{display:block}
}

/* The plan's edits (planning/_plan_table.html, js/plan-edit.js). One save bar
   for the whole table, never a button per row; with script it floats at the
   bottom of the viewport while there are unsaved rows, and a changed row
   carries an ember edge so it can be found again before saving. */
.plan-table__pick{width:44px}
.plan-table .select{display:inline-flex;max-width:280px}
.data-table tbody tr.is-dirty td:first-child{box-shadow:inset 3px 0 0 var(--ember-600)}
.data-table tbody tr.is-refused{background:var(--status-block-bg)}
.plan-table .field__error{display:flex;margin-top:var(--space-1)}
.plan-savebar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--space-3);padding:var(--space-3) var(--space-4);border:1px solid var(--border-hairline);border-radius:var(--radius-md);background:var(--surface-card)}
.plan-savebar[hidden]{display:none}
.plan-savebar--floating{position:sticky;bottom:var(--space-4);z-index:5;border-color:var(--border-strong)}
.plan-savebar__count{font-size:var(--fs-body-s);color:var(--text-body)}
/* An item's comment thread (P2), folded under its name in the plan table. */
.plan-comments{margin-top:var(--space-1);font-weight:var(--fw-regular)}
.plan-comments > summary{display:inline-flex;align-items:center;gap:var(--space-1);min-height:var(--target-min);cursor:pointer;list-style:none;color:var(--text-muted);font-size:var(--fs-body-s)}
.plan-comments > summary::-webkit-details-marker{display:none}
.plan-comments > summary .icon{transform:rotate(-90deg);transition:transform var(--dur-fast) var(--ease-standard)}
.plan-comments[open] > summary .icon{transform:none}
.plan-comments[open]{display:flex;flex-direction:column;align-items:flex-start;gap:var(--space-2);max-width:520px}
.plan-comments__thread{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:var(--space-3);width:100%}
.plan-comments__item{padding-left:var(--space-3);border-left:2px solid var(--border-hairline)}
.plan-comments__meta{margin:0;font-size:var(--fs-body-s);color:var(--text-muted)}
.plan-comments__body{margin:var(--space-1) 0 0;font-size:var(--fs-body-s);color:var(--text-body);overflow-wrap:anywhere}
.plan-comments__input{width:100%}

/* Every action on the plan's table lands on its Items heading (P2), clear of
   the sticky page header. */
.plan-items__title{scroll-margin-top:calc(var(--space-13) + var(--space-4))}

/* Rows per page beside the pager (P2). */
.plan-pagination{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:var(--space-3)}
.plan-page-size{display:flex;align-items:center;gap:var(--space-2)}
.plan-page-size__label{font-size:var(--fs-body-s);color:var(--text-muted)}
html.js .plan-page-size__go{display:none}

/* The selection toolbar (P2) sits inside the table's border: what ticking
   does, how many are ticked, and — only without script — one decision for
   the ticked items. The "apply to N" offer appears in the cell that changed. */
.plan-selection__text{margin:0;font-size:var(--fs-body-s);color:var(--text-body)}
.plan-selection__fallback{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2) var(--space-4)}
.plan-selection__fallback .select select{min-width:180px;max-width:280px}
.plan-selection__option{display:inline-flex;align-items:center;gap:var(--space-2);min-height:var(--target-min);font-size:var(--fs-body-s)}
.plan-selection [hidden]{display:none}
.plan-apply{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);margin-top:var(--space-2)}
.plan-apply__note{flex:1 1 100%;font-size:var(--fs-body-s);color:var(--text-muted)}
@media (max-width:767px){
  .plan-table__pick{width:auto}
  .plan-table td:has(> .plan-apply){flex-wrap:wrap}
  .plan-apply{flex:1 1 100%;justify-content:flex-end}
}
