/* Amproteq Studio — component vocabulary.
 *
 * Everything a flow author reaches for through web/js/ui/kit.js lives here.
 * app.css owns the shell (rail, header regions, splash, grid); this file owns
 * the pieces flows compose screens out of — panels, buttons, tables, forms,
 * chips, the doors, toasts, the modal.
 *
 * Several classes here (.btn, .fld, .inp, .kv-grid, .tbl, .notes, .result*,
 * .eyebrow, .mk*) are the exact class names web/js/flows/numbers.js already
 * emits by hand. kit.js formalises them into functions; this file is their
 * one definition. Renaming any of them breaks a flow this file does not own —
 * so the rule for every change here is additive: new modifiers and new
 * components are welcome, the existing selectors are not renamed.
 *
 * No colour literal belongs in this file except where a gradient needs its
 * stops inline — tokens live in tokens.css.
 */

/* ── Eyebrow ────────────────────────────────────────────────────────────── */

.eyebrow {
  display: flex; align-items: center; gap: var(--sp-4);
  margin: 0 0 var(--sp-4);
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--cyan);
}
.eyebrow::before {
  content: '';
  width: 30px; height: 1px;
  background: linear-gradient(90deg, var(--cyan), transparent);
  flex: none;
}

/* ── Section head ───────────────────────────────────────────────────────── */

.section-head { margin: 0 0 var(--sp-6); }
.section-head-title {
  font-size: clamp(20px, 2.4vw, 28px);
  letter-spacing: var(--ls-tight);
}
.section-head-lead {
  margin: var(--sp-3) 0 0;
  max-width: 62ch;
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  color: var(--text-secondary);
}

/* ── Buttons ────────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  padding: 10px var(--sp-5);
  border-radius: var(--r-3);
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.015);
  font-family: var(--font-display);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  cursor: pointer;
  white-space: nowrap;
  line-height: var(--lh-tight);
  padding-block: calc(10px + var(--lh-trim));
  transition: border-color 250ms var(--ease), background 250ms var(--ease),
              box-shadow 250ms var(--ease), color 250ms var(--ease);
}
/* No transform on hover, anywhere. Contract §2: "a 1px border lift to
 * --border-neon plus a faint --plasma-wash; 250ms. Never a transform jump." */
.btn:hover:not(:disabled) { border-color: var(--border-neon); background: var(--plasma-wash); }

/* ── A disabled action must look DELIBERATE, not broken ─────────────────────
 *
 * `opacity: 0.4` on a gradient-filled primary button produces a smeared,
 * half-rendered thing that reads as a rendering failure — which is exactly
 * how the customer read `Full report`: "a dead button". A control that is off
 * because the product is not ready for it should look like a control that is
 * OFF, in full strength, and should say what would turn it on.
 *
 * So: no transparency. The disabled state drops the gradient entirely, keeps
 * a full-contrast hairline, and prints its `data-why` as the tooltip (see
 * chrome.js's enableReport, which writes that attribute). `.is-locked` adds a
 * small padlock before the label so the state carries without colour. */
.btn:disabled { cursor: not-allowed; }
.btn:disabled:not(.btn-primary) {
  color: var(--text-faint);
  border-color: var(--border-soft);
  background: none;
}
.btn-primary:disabled {
  background: none;
  border-color: var(--border-soft);
  color: var(--text-faint);
  box-shadow: none;
  font-weight: var(--fw-medium);
}
.btn.is-locked:disabled::before {
  content: '';
  width: 9px; height: 11px;
  flex: none;
  border: 1px solid currentColor;
  border-radius: 1px 1px 2px 2px;
  box-shadow: inset 0 -3px 0 -2px currentColor;
  /* The shackle: a half-ring above the body, drawn with a border so it
   * inherits the button's colour and needs no second asset. */
  background:
    radial-gradient(circle at 50% 100%, transparent 2.4px, currentColor 2.4px, currentColor 3.4px, transparent 3.4px)
    50% -3px / 100% 6px no-repeat;
}
/* Once the action is live the padlock has nothing to say. */
.btn.is-locked:not(:disabled)::before { content: none; }

/* The header row is one dense band; its two actions are sized for it. */
.btn-sm {
  padding: 0 var(--sp-4);
  height: 30px;
  font-size: var(--fs-sm);
  border-radius: var(--r-3);
}
.btn-icon { display: inline-grid; place-items: center; width: 15px; height: 15px; flex: none; }
.btn-icon svg { width: 100%; height: 100%; stroke: currentColor; fill: none; stroke-width: 1.8; }

/* The gradient is a signature, spent on the primary action only — never a
 * texture repeated across every button on a screen. */
.btn-primary {
  background: var(--grad-r);
  border-color: transparent;
  color: #03080f;
  font-weight: var(--fw-semibold);
  box-shadow: var(--shadow-neon);
}
/* The gradient is restated on hover: the generic `.btn:hover` above sets
 * `background: var(--plasma-wash)`, which replaced the gradient with a near-
 * black wash under near-black text — every primary button in the product went
 * dark and unreadable the moment the pointer reached it. */
.btn-primary:hover:not(:disabled) {
  background: var(--grad-r);
  border-color: transparent;
  box-shadow: 0 14px 40px -12px rgba(60, 204, 252, 0.72);
}

.btn-ghost { border-color: var(--border-soft); color: var(--text-dim); background: transparent; }
.btn-ghost:hover:not(:disabled) { background: rgba(255, 255, 255, 0.03); }

.btn-quiet {
  border-color: transparent;
  background: none;
  color: var(--text-secondary);
  padding-left: var(--sp-2); padding-right: var(--sp-2);
}
.btn-quiet:hover:not(:disabled) { color: var(--cyan); transform: none; }

.btn-row { display: flex; gap: var(--sp-3); flex-wrap: wrap; align-items: center; }

/* ── Pills and chips ────────────────────────────────────────────────────── */

.pill {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  line-height: var(--lh-mono);
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: var(--r-full);
  padding: 7px 12px;
}

/* A chip carries a state, so it shows one: a dot on the leading edge that is
 * faint when the slot is empty and cyan when it is filled. Reading the header
 * at a glance should not require reading the words. */
.chip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 11px;
  border: 1px solid var(--border);
  border-radius: var(--r-full);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.08em;
  line-height: 1.35;
  color: var(--text-dim);
  white-space: nowrap;
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.chip::before {
  content: '';
  width: 5px; height: 5px;
  border-radius: var(--r-full);
  background: currentColor;
  flex: none;
  opacity: 0.55;
}
.chip-muted { color: var(--text-faint); border-color: var(--border-soft); }
.chip-live {
  color: var(--cyan);
  border-color: var(--border-neon);
  background: var(--plasma-wash);
}
.chip-live::before { opacity: 1; box-shadow: 0 0 8px var(--plasma-glow); }

/* ── Badges ─────────────────────────────────────────────────────────────────
 * A verdict on a band: a grade, a state, a check that passed. Square-ish and
 * mono, so it never reads as a button.
 *
 * There is no brand-green tone, and as of V5 there is no green here at all.
 * `--signal` used to be #5fd69a on the argument that a status colour on a
 * status surface was exempt; contract V5 §5 closes that ("No green in the
 * palette. Green is another brand."), so the token is now cyan.
 *
 * The rgba literals below moved with it. They were `rgba(95, 214, 154, …)` —
 * the green spelled out by hand, which the token change alone would NOT have
 * corrected, so every `badge-good` would have kept a green border and a green
 * wash under cyan text. A colour written twice is a colour that only gets
 * fixed once. */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 8px;
  border: 1px solid currentColor;
  border-radius: var(--r-2);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.12em;
  line-height: var(--lh-mono);
  text-transform: uppercase;
  white-space: nowrap;
}
.badge-neutral { color: var(--text-faint); border-color: var(--border-soft); }
.badge-accent  { color: var(--cyan);   border-color: var(--border-neon); background: var(--plasma-wash); }
.badge-good    { color: var(--signal); border-color: var(--border-neon); background: var(--plasma-wash); }
.badge-warn    { color: var(--amber);  border-color: rgba(255, 182, 61, 0.38); background: rgba(255, 182, 61, 0.07); }
.badge-bad     { color: var(--red);    border-color: rgba(255, 107, 107, 0.38); background: rgba(255, 107, 107, 0.07); }

/* ── Micro-label ────────────────────────────────────────────────────────────
 * The one small label in the product: mono, uppercase, .2em, 11px. Every
 * figure's caption uses it, which is most of what makes a dense screen read
 * as deliberate rather than cramped. */

.micro-label {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.2em;
  line-height: var(--lh-mono);
  text-transform: uppercase;
  color: var(--text-faint);
}

/* ── Panels ─────────────────────────────────────────────────────────────────
 * The one surface every module reaches for: --bg-surface-2, a 1px hairline,
 * a 12px radius. Not a box with a heavy shadow — an instrument-panel face. */

.panel {
  border: 1px solid var(--border-soft);
  border-radius: var(--r-5);
  background: var(--bg-surface-2);
  padding: var(--sp-6);
  transition: border-color var(--dur) var(--ease);
}
.panel + .panel { margin-top: var(--sp-5); }

.panel-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-4);
  margin-bottom: var(--sp-2);
}
.panel-title {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: var(--sp-4);
}
.panel-head .panel-title { margin-bottom: 0; }
.panel-actions { display: flex; gap: var(--sp-2); flex: none; }
.panel-sub { margin: 0 0 var(--sp-4); font-size: var(--fs-base); line-height: var(--lh-relaxed); color: var(--text-secondary); }
.panel-body { display: grid; gap: var(--sp-4); }
.panel-body:empty { display: none; }

.panel-empty { text-align: left; }

/* ── Mark glyph + quantity ──────────────────────────────────────────────────
 * ◆ primary, ▸ derived, ○ unconfirmed. The product's main claim — every
 * number that reaches the screen carries one. */

.mk { font-style: normal; font-size: var(--fs-sm); margin-left: 2px; cursor: help; }
.mk-p { color: var(--cyan); }
.mk-d { color: var(--text-dim); }
.mk-u { color: var(--text-faint); }

.mark-key {
  list-style: none;
  margin: var(--sp-6) 0 0;
  padding: 0;
  display: grid;
  gap: var(--sp-3);
  font-size: var(--fs-base);
  color: var(--text-faint);
}
.mark-key li { display: flex; align-items: baseline; gap: var(--sp-3); }

.quantity { display: inline-flex; align-items: baseline; gap: 1px; cursor: help; white-space: nowrap; }
.quantity-v {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
/* The unit rides behind the figure, smaller and cyan, so a column of
 * quantities reads as numbers with units rather than as a wall of text. */
.quantity-u {
  font-family: var(--font-mono);
  font-size: 0.82em;
  letter-spacing: var(--ls-wide);
  color: var(--cyan);
  margin-left: 4px;
}
/* A quantity the source does not report. qNull() renders here: an em dash and
 * its reason, never a zero standing in for an unknown. */
.quantity.is-null .quantity-v { color: var(--text-faint); }

/* ── Big number — CORRECTED, 20 September 2026 ───────────────────────────────
 *
 * The customer, on the screen most people will use:
 *
 *   "the numbers are too big ... it looks really unprofessional"
 *
 * Measured: `R 2 490 163` rendered at clamp(28px, 5vh, 48px) — ~40px tall and
 * ~380px wide — in gradient-filled mono with --ls-tight tracking. It was the
 * least informative thing on the screen and by far the largest.
 *
 * Build contract V5 §3 is the fix, and it is not a taste call:
 *   · cap at --fs-figure, which tops out at 26px
 *   · tabular mono, font-variant-numeric: tabular-nums
 *   · NO letter spacing — a number 380px wide cannot be compared against the
 *     one beside it, which is the entire job of a figure on an instrument
 *   · no gradient text-fill; the gradient is a signature spent on the primary
 *     button and the rail indicator, and mono at 26px turns it to mud
 *
 * The class names are unchanged, so every existing call site in four streams
 * is corrected without touching a line of their code. `.is-gradient` is kept
 * as a no-op selector rather than removed, for the same reason. */

.big-number { display: grid; gap: var(--sp-1); justify-items: start; }
.big-number-value {
  display: flex;
  align-items: baseline;
  gap: 5px;
  font-family: var(--font-mono);
  font-size: var(--fs-figure);
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1, 'zero' 1;
  letter-spacing: var(--ls-figure);
  color: var(--text);
  line-height: var(--lh-mono);
  padding-block: var(--lh-trim);
  white-space: nowrap;
}
/* Deliberately inert. Four streams pass `gradient: true`; the declaration is
 * gone, the selector stays so nothing throws and nothing has to change. */
.big-number-value.is-gradient { color: var(--text); background: none; }
.big-number-figure { font-variant-numeric: tabular-nums; }
.big-number-unit {
  margin-left: 0;
  font-size: 0.62em;
  color: var(--cyan);
  letter-spacing: var(--ls-wide);
  -webkit-text-fill-color: var(--cyan);
}
.big-number-label {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.2em;
  line-height: var(--lh-mono);
  text-transform: uppercase;
  color: var(--text-faint);
}

/* ══════════════════════════════════════════════════════════════════════════
   THE DENSITY VOCABULARY
   Built for web/js/ui/kit.js. Contract V5 §3: a screen more than a third
   empty at 1600x950 is unfinished, and the remedy is more information in more
   columns at the SAME SIZE — never a larger font.

   Every figure class below shares one type ramp (--fs-figure / -md / -sm),
   one numeric rendering (tabular mono, no tracking) and one null state (em
   dash in --not-modelled, never a zero). That is what makes a wall of these
   read as one instrument rather than as six people's separate guesses.
═══════════════════════════════════════════════════════════════════════════ */

/* ── The instrument figure ─────────────────────────────────────────────────
 * kit.figure(). One headline number per screen, in a labelled frame carrying
 * its unit, its period, its provenance mark and its movement. */

.figure { display: grid; gap: 3px; justify-items: start; min-width: 0; }

.figure-label {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.2em;
  line-height: var(--lh-mono);
  text-transform: uppercase;
  color: var(--text-faint);
}

.figure-line {
  display: flex;
  align-items: baseline;
  gap: 5px;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1, 'zero' 1;
  letter-spacing: var(--ls-figure);
  line-height: var(--lh-mono);
  padding-block: var(--lh-trim);
  white-space: nowrap;
  max-width: 100%;
}
.figure-v { font-size: var(--fs-figure); font-weight: var(--fw-medium); color: var(--text); }
.figure-u {
  font-size: var(--fs-micro);
  letter-spacing: var(--ls-wide);
  text-transform: none;
  color: var(--cyan);
}
.figure.is-null .figure-v { color: var(--not-modelled); }

.figure-foot { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.figure-period {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.1em;
  line-height: var(--lh-mono);
  color: var(--text-faint);
}
.figure-note {
  margin-top: 2px;
  max-width: 46ch;
  font-size: var(--fs-prose);
  line-height: var(--lh-normal);
  color: var(--text-secondary);
}

/* The hero mark is a 2px gradient rule above the frame, NOT a bigger number.
 * A screen says "this is the figure" by framing it, not by shouting it. */
.figure-hero {
  padding: var(--sp-4) var(--sp-5);
  border: 1px solid var(--border-soft);
  border-radius: var(--r-4);
  background: var(--bg-surface);
  position: relative;
  overflow: hidden;
}
.figure-hero::before {
  content: '';
  position: absolute; inset: 0 0 auto 0; height: 2px;
  background: var(--grad);
}
.figure-quiet .figure-v { font-size: var(--fs-figure-md); color: var(--text-dim); }

/* ── Delta ─────────────────────────────────────────────────────────────────
 * Loss warm, gain cool, no change grey. Contract V5 §3. The three states are
 * distinguished by COLOUR AND GLYPH, never colour alone. */

.delta {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  padding: 2px 7px;
  border-radius: var(--r-2);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-variant-numeric: tabular-nums;
  line-height: var(--lh-mono);
  letter-spacing: 0.04em;
  white-space: nowrap;
}
.delta-arrow { font-style: normal; font-size: 0.95em; }
.delta-l { color: var(--text-faint); letter-spacing: 0.08em; }
.delta-gain { color: var(--gain); background: var(--gain-wash); }
.delta-loss { color: var(--loss); background: var(--loss-wash); }
.delta-none { color: var(--not-modelled); background: var(--not-modelled-wash); }

/* ── Metric card ───────────────────────────────────────────────────────────
 * The unit a dense screen is built out of. `--col-metric` is the narrowest a
 * card may be before a seven-digit rand figure wraps. */

.metric-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--col-metric), 1fr));
  gap: 1px;
  background: var(--border-soft);
  border: 1px solid var(--border-soft);
  border-radius: var(--r-4);
  overflow: hidden;
}

.metric {
  display: grid;
  gap: 3px;
  justify-items: start;
  align-content: start;
  padding: clamp(9px, 1.4vh, 13px) var(--sp-4);
  min-width: 0;
  text-align: left;
  background: var(--bg-surface-2);
  border: 0;
  font: inherit;
  color: inherit;
  transition: background var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
button.metric { cursor: pointer; }
button.metric:hover { background: var(--bg-surface-3); }
.metric.is-selected { background: var(--plasma-wash); box-shadow: inset 2px 0 0 var(--cyan); }
.metric-accent .metric-v { color: var(--cyan); }
.metric-warn .metric-v { color: var(--amber); }
.metric-quiet .metric-v { color: var(--text-dim); }

.metric-label {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.18em;
  line-height: var(--lh-mono);
  text-transform: uppercase;
  color: var(--text-faint);
  /* A label that wraps to three lines wrecks the grid's rhythm; one that is
   * cut off mid-word is worse. Two lines, then ellipsis, with the full text
   * available on the title attribute the card already carries. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  max-width: 100%;
}

.metric-line {
  display: flex;
  align-items: baseline;
  gap: 4px;
  max-width: 100%;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1, 'zero' 1;
  letter-spacing: var(--ls-figure);
  line-height: var(--lh-mono);
  padding-block: var(--lh-trim);
  white-space: nowrap;
}
.metric-v { font-size: var(--fs-figure-md); font-weight: var(--fw-medium); color: var(--text); }
.metric-u { font-size: var(--fs-micro); color: var(--cyan); letter-spacing: var(--ls-wide); }
.metric.is-null .metric-v { color: var(--not-modelled); }
.metric-hint {
  font-size: var(--fs-prose);
  line-height: var(--lh-normal);
  color: var(--text-faint);
  max-width: 100%;
}

/* ── Sparkline ─────────────────────────────────────────────────────────── */

.spark { display: block; line-height: 0; height: 18px; }
.spark svg { display: block; overflow: visible; }
.spark-line { fill: none; stroke: var(--cyan); stroke-width: 1.2; vector-effect: non-scaling-stroke; }
.spark-fill { fill: rgba(60, 204, 252, 0.12); stroke: none; }
.spark-zero { stroke: var(--border); stroke-width: 1; stroke-dasharray: 2 2; vector-effect: non-scaling-stroke; }
.spark-warm .spark-line { stroke: var(--loss); }
.spark-warm .spark-fill { fill: var(--loss-wash); }
.spark-quiet .spark-line { stroke: var(--text-faint); }
.spark-quiet .spark-fill { fill: rgba(150, 190, 255, 0.06); }
/* The reserved host form — an empty `.metric-spark` a caller mounts into.
 * It keeps its box so the grid does not reflow when the chart arrives. */
.metric-spark { width: 100%; min-height: 18px; }

/* ── Definition rows ───────────────────────────────────────────────────────
 * Twelve of these stack in a 420px column and stay readable. One line each
 * when there is no note; the note is a second line, indented under the key. */

.deflist {
  --deflist-cols: 1;
  display: grid;
  grid-template-columns: repeat(var(--deflist-cols), minmax(0, 1fr));
  gap: 0 var(--sp-6);
  align-content: start;
}

.defrow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 0 var(--sp-4);
  padding: 5px 0;
  border-bottom: 1px solid var(--border-soft);
  transition: background var(--dur-fast) var(--ease);
}
.defrow:hover { background: rgba(255, 255, 255, 0.016); }
.deflist > .defrow:last-child { border-bottom: 0; }

.defrow-k {
  min-width: 0;
  font-size: var(--fs-prose);
  line-height: var(--lh-normal);
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}
.defrow-v {
  display: flex;
  align-items: baseline;
  gap: 4px;
  justify-self: end;
  font-family: var(--font-mono);
  font-size: var(--fs-figure-sm);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1, 'zero' 1;
  letter-spacing: var(--ls-figure);
  line-height: var(--lh-mono);
  color: var(--text);
  white-space: nowrap;
}
.defrow-u { font-size: var(--fs-micro); color: var(--cyan); letter-spacing: var(--ls-wide); }
.defrow-v.is-null .defrow-n { color: var(--not-modelled); }
.defrow-note {
  grid-column: 1 / -1;
  margin-top: 1px;
  font-size: var(--fs-micro);
  line-height: 1.45;
  color: var(--text-faint);
}
/* A total is the row the eye should land on: heavier rule above, brighter
 * figure, no hover tint. */
.defrow-total {
  border-top: 1px solid var(--border);
  border-bottom: 0;
  margin-top: 2px;
  padding-top: var(--sp-3);
}
.defrow-total .defrow-k { color: var(--text-dim); }
.defrow-total .defrow-v { color: var(--cyan); font-weight: var(--fw-medium); }
.defrow-total:hover { background: none; }
.defrow-quiet .defrow-k, .defrow-quiet .defrow-v { color: var(--text-faint); }

/* ── Comparison table ──────────────────────────────────────────────────────
 * A table the customer PICKS a row out of. The engine's recommendation and
 * the customer's selection are different claims and are drawn differently:
 * the recommendation is a cyan tint that is always there, the selection is a
 * filled dot and a left bar. */

.cmp { min-width: 0; }
.cmp-tbl {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-prose);
}
.cmp-cap {
  caption-side: top;
  text-align: left;
  padding-bottom: var(--sp-3);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.18em;
  line-height: var(--lh-mono);
  text-transform: uppercase;
  color: var(--text-faint);
}
.cmp-tbl th {
  position: sticky;
  top: 0;
  z-index: 1;
  text-align: left;
  padding: var(--sp-2) var(--sp-3);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: var(--fw-regular);
  letter-spacing: 0.12em;
  line-height: var(--lh-mono);
  text-transform: uppercase;
  color: var(--text-faint);
  background: var(--bg-surface-2);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.cmp-tbl th.n, .cmp-tbl td.n {
  text-align: right;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1, 'zero' 1;
}
.cmp-tbl td {
  padding: var(--sp-3);
  border-bottom: 1px solid var(--border-soft);
  color: var(--text-dim);
  white-space: nowrap;
}
.cmp-tbl td.n { color: var(--text); }
.cmp-tbl tbody tr:last-child td { border-bottom: 0; }

.cmp-row { cursor: pointer; transition: background var(--dur-fast) var(--ease); }
.cmp-row:hover:not(.is-disabled) { background: rgba(255, 255, 255, 0.025); }
.cmp-row:focus-visible { outline: 2px solid var(--cyan); outline-offset: -2px; }

th.cmp-pick, td.cmp-pick { width: 26px; padding-left: var(--sp-3); padding-right: 0; }
.cmp-dot {
  display: block;
  width: 11px; height: 11px;
  border: 1px solid var(--border);
  border-radius: var(--r-full);
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.cmp-row.is-selected .cmp-dot {
  border-color: var(--cyan);
  box-shadow: inset 0 0 0 2.5px var(--bg-surface-2), inset 0 0 0 9px var(--cyan);
}

/* The engine's opinion: a tint and a caret, always visible, never mistaken
 * for the customer's choice. */
.cmp-row.is-rec td { background: rgba(60, 204, 252, 0.045); }
.cmp-row.is-rec td:nth-child(2)::before {
  content: '\25B8';
  margin-right: 5px;
  color: var(--cyan);
}
/* The customer's choice. */
.cmp-row.is-selected td { background: var(--plasma-wash); color: var(--text); }
.cmp-row.is-selected td:first-child { box-shadow: inset 2px 0 0 var(--cyan); }
.cmp-row.is-disabled { cursor: not-allowed; opacity: 0.45; }

/* ── Split panel ───────────────────────────────────────────────────────────
 * Title bar · body · footnote. EVERY ROW IS `auto`. A fractional row resolves
 * to ZERO when the content exceeds the container — the trap that has bitten
 * this codebase four times and printed panels on top of each other. */

.split-panel {
  display: grid;
  grid-template-rows: auto auto auto;
  align-content: start;
  min-width: 0;
  border: 1px solid var(--border-soft);
  border-radius: var(--r-5);
  background: var(--bg-surface-2);
  overflow: hidden;
}
.split-panel.is-accent { border-color: var(--border); }
.split-panel.is-accent .sp-head { background: var(--plasma-wash); }

.sp-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-5);
  border-bottom: 1px solid var(--border-soft);
  background: rgba(255, 255, 255, 0.014);
}
.sp-title {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: var(--fw-regular);
  letter-spacing: 0.2em;
  line-height: var(--lh-mono);
  padding-block: 0;
  text-transform: uppercase;
  color: var(--text-dim);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sp-actions { display: flex; gap: var(--sp-2); flex: none; align-items: center; }
.sp-sub {
  padding: var(--sp-3) var(--sp-5) 0;
  font-size: var(--fs-prose);
  line-height: var(--lh-normal);
  color: var(--text-secondary);
  max-width: 78ch;
}
.sp-body { padding: var(--sp-4) var(--sp-5); display: grid; gap: var(--sp-4); align-content: start; min-width: 0; }
.sp-body:empty { display: none; }
.sp-foot {
  padding: var(--sp-3) var(--sp-5);
  border-top: 1px solid var(--border-soft);
  background: rgba(0, 0, 0, 0.16);
}
.sp-foot[hidden] { display: none; }
.sp-foot-t, .prov-s, .prov-n {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  line-height: 1.5;
  color: var(--text-faint);
  overflow-wrap: anywhere;
}

/* ── Chart frame ───────────────────────────────────────────────────────────
 * The host has a hard pixel floor in CSS so it is never zero before JS runs,
 * and kit.chartFrame() writes the working height from the measured width and
 * a declared aspect ratio. Nothing about a chart's height comes from a
 * flexible track. */

.chart-frame { display: grid; gap: var(--sp-2); min-width: 0; }
.chart-host {
  min-height: var(--chart-h-sm);
  width: 100%;
  min-width: 0;
}
.chart-caption {
  font-size: var(--fs-micro);
  line-height: 1.5;
  color: var(--text-faint);
  max-width: 72ch;
}

/* ── Stat strip ────────────────────────────────────────────────────────────
 * Four to eight figures in a header band, separated by hairlines rather than
 * boxes, so the strip reads as one instrument. */

.stat-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  border: 1px solid var(--border-soft);
  border-radius: var(--r-4);
  background: var(--bg-surface-2);
  overflow: hidden;
}
.ss-cell {
  flex: 1 1 var(--col-metric);
  min-width: 0;
  padding: clamp(8px, 1.3vh, 12px) var(--sp-5);
  border-left: 1px solid var(--border-soft);
}
.ss-cell:first-child { border-left: 0; }
.ss-line {
  display: flex;
  align-items: baseline;
  gap: 4px;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1, 'zero' 1;
  letter-spacing: var(--ls-figure);
  line-height: var(--lh-mono);
  padding-block: var(--lh-trim);
  white-space: nowrap;
}
.ss-v { font-size: var(--fs-figure-md); font-weight: var(--fw-medium); color: var(--text); }
.ss-u { font-size: var(--fs-micro); color: var(--cyan); letter-spacing: var(--ls-wide); }
.ss-l {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.18em;
  line-height: var(--lh-mono);
  text-transform: uppercase;
  color: var(--text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ss-cell.is-null .ss-v { color: var(--not-modelled); }
.ss-accent .ss-v { color: var(--cyan); }
.ss-warn .ss-v { color: var(--amber); }

/* ── Chart tooltip ─────────────────────────────────────────────────────────
 * Built by charts.js's `tooltipHtml()`. ECharts renders a tooltip into its own
 * absolutely-positioned div, so these rules have to be global — they cannot
 * be scoped to a panel.
 *
 * A tooltip carries the ARGUMENT, not just the value under the cursor: the
 * rows, then the movement named in WORDS (loss / gain / no change, never a
 * bare signed number), then whatever the engine said about that step. The
 * note wraps; the tooltip's own `extraCssText` sets the 420px it wraps at. */

.ctip { padding: 9px 11px; min-width: 150px; }
.ctip-h {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.1em;
  line-height: var(--lh-mono);
  color: var(--text-dim);
  padding-bottom: 6px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--border-soft);
}
.ctip-rows { display: grid; gap: 3px; }
.ctip-r {
  display: grid;
  grid-template-columns: 8px minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 7px;
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  line-height: 1.5;
}
.ctip-r.is-dim { color: var(--text-faint); }
.ctip-dot {
  width: 7px; height: 7px;
  border-radius: 2px;
  display: block;
  transform: translateY(-1px);
}
.ctip-dot-none { background: var(--border); }
.ctip-l { color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctip-v {
  color: var(--text);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1, 'zero' 1;
  white-space: nowrap;
}

.ctip-d {
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 7px;
  padding-top: 6px;
  border-top: 1px solid var(--border-soft);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-variant-numeric: tabular-nums;
  line-height: 1.5;
  color: var(--text-secondary);
}
.ctip-d i { font-style: normal; color: var(--text-faint); }
.ctip-d b { font-weight: var(--fw-medium); }
.ctip-gain b { color: var(--gain); }
.ctip-loss b { color: var(--loss); }
.ctip-none b { color: var(--not-modelled); }

.ctip-flag {
  display: inline-block;
  margin-top: 7px;
  padding: 2px 7px;
  border: 1px solid var(--border-soft);
  border-radius: var(--r-2);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  line-height: var(--lh-mono);
  text-transform: uppercase;
  color: var(--amber);
}
.ctip-n {
  margin-top: 7px;
  font-family: var(--font-body);
  font-size: var(--fs-prose);
  line-height: 1.5;
  color: var(--text-secondary);
  white-space: normal;
}

/* ── The empty chart ───────────────────────────────────────────────────────
 * charts.js's `emptyChart()`. An empty box says the chart is broken; this
 * names the engine path that produced nothing and says, in as many words,
 * that the series has not been synthesised. */

.chart-empty {
  display: grid;
  gap: var(--sp-2);
  align-content: center;
  justify-items: start;
  height: 100%;
  min-height: var(--chart-h-sm);
  padding: var(--sp-5);
  border: 1px dashed var(--border-soft);
  border-radius: var(--r-4);
  background: repeating-linear-gradient(
    -45deg,
    transparent 0 7px,
    rgba(150, 190, 255, 0.022) 7px 14px
  );
}
.chart-empty.is-warn { border-color: rgba(255, 182, 61, 0.3); }
.chart-empty-t {
  font-family: var(--font-display);
  font-size: var(--fs-base);
  line-height: var(--lh-tight);
  padding-block: var(--lh-trim);
  color: var(--text-dim);
}
.chart-empty-r { font-size: var(--fs-prose); line-height: var(--lh-normal); color: var(--text-secondary); max-width: 62ch; }
.chart-empty-f { font-size: var(--fs-micro); line-height: 1.6; color: var(--text-faint); max-width: 62ch; }
.chart-empty-f code {
  font-family: var(--font-mono);
  padding: 1px 5px;
  border: 1px solid var(--border-soft);
  border-radius: var(--r-2);
  background: var(--bg-deep);
  color: var(--cyan);
  overflow-wrap: anywhere;
}

/* ── Time-of-use legend ────────────────────────────────────────────────────
 * The one legend the product shares. Peak / standard / off-peak are the same
 * three colours on every screen (tokens.css), so a panel that prints a TOU
 * breakdown and a chart that bands its background must not draw their own. */

.tou-key { display: flex; flex-wrap: wrap; gap: var(--sp-4); align-items: center; }
.tou-key-i {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.12em;
  line-height: var(--lh-mono);
  text-transform: uppercase;
  color: var(--text-faint);
}
.tou-key-i::before { content: ''; width: 10px; height: 3px; border-radius: 2px; flex: none; }
.tou-peak::before { background: var(--tou-peak); }
.tou-standard::before { background: var(--tou-standard); }
.tou-offpeak::before { background: var(--tou-offpeak); }

/* ── Provenance footer ─────────────────────────────────────────────────── */

.prov {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  flex-wrap: wrap;
  min-width: 0;
}
.prov .mk { margin-left: 0; flex: none; }
.prov-s { flex: 1 1 24ch; min-width: 0; }
.prov-n { color: var(--text-secondary); }

/* ── Stat grid ──────────────────────────────────────────────────────────── */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 1px;
  background: var(--border-soft);
  border: 1px solid var(--border-soft);
  border-radius: var(--r-5);
  overflow: hidden;
}
.stat {
  background: var(--bg-surface-2);
  padding: clamp(10px, 1.8vh, 16px);
  transition: background var(--dur) var(--ease);
}
.stat:hover { background: var(--bg-surface-3); }
.stat-value {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-figure-md);
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1, 'zero' 1;
  color: var(--text);
  /* --ls-tight on a tabular figure defeats the point of tabular: the columns
   * stop lining up. Figures never track. */
  letter-spacing: var(--ls-figure);
  line-height: var(--lh-mono);
  padding-block: var(--lh-trim);
  white-space: nowrap;
}
.stat-unit { margin-left: 4px; font-size: var(--fs-micro); color: var(--cyan); letter-spacing: var(--ls-wide); }
.stat-label {
  margin-top: 5px;
  font-family: var(--font-mono);
  /* Was 9px. The contract's floor for a mono micro-label is 11px, and at
   * 9px with .18em tracking this was decorative rather than readable. */
  font-size: var(--fs-micro);
  letter-spacing: 0.2em;
  line-height: var(--lh-mono);
  text-transform: uppercase;
  color: var(--text-faint);
}
.stat-hint { margin-top: var(--sp-1); font-size: var(--fs-prose); color: var(--text-faint); line-height: var(--lh-normal); }

/* ── Key/value list ─────────────────────────────────────────────────────── */

/* Hairline rows rather than a bordered block: in a locked viewport the two
 * heavy rules and the 24px margins above and below cost more height than the
 * content did. Each row now carries its own rule and lights up on hover, so a
 * long list stays readable across without a zebra stripe. */
.kv-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 0 var(--sp-5);
  margin: var(--sp-4) 0;
}
.kv-k, .kv-v {
  padding: clamp(4px, 0.9vh, 8px) 0;
  border-bottom: 1px solid var(--border-soft);
  transition: color var(--dur-fast) var(--ease);
}
.kv-grid > .kv-k:nth-last-child(2),
.kv-grid > .kv-v:last-child { border-bottom: 0; }

.kv-k {
  font-size: var(--fs-base);
  color: var(--text-secondary);
  min-width: 0;
}
.kv-v {
  font-family: var(--font-mono);
  font-size: var(--fs-base);
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--text);
  white-space: nowrap;
}

/* ── Data table ─────────────────────────────────────────────────────────────
 * A 20-row financial table without looking like a spreadsheet: hairline row
 * rules, mono right-aligned numerics, a highlighted recommended row, and a
 * sticky header once the table scrolls. The sticky rule is harmless with no
 * scrolling ancestor, so it is safe on every table including a 4-row one. */

/* kit.js's dataTable() emits `tbl-scroll scroll-region`, so a long table is
 * the contract's ONE permitted scroll — an explicitly unbounded list, with a
 * visible scrollbar, inside a pane that itself fits. The `.scroll-region`
 * rules above supply the overflow and the scrollbar; this only bounds it. */
.tbl-scroll { max-height: 100%; min-height: 0; }
.tbl {
  width: 100%;
  border-collapse: collapse;
  margin: var(--sp-4) 0 var(--sp-7);
  background: var(--bg-surface);
  border-radius: var(--r-4);
}
.tbl th {
  position: sticky;
  top: 0;
  z-index: 1;
  text-align: left;
  padding: var(--sp-3) var(--sp-4);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-regular);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--text-faint);
  background: var(--bg-surface-2);
  border-bottom: 1px solid var(--border-soft);
}
.tbl th.n { text-align: right; }
.tbl td {
  padding: var(--sp-4);
  font-size: var(--fs-base);
  color: var(--text-dim);
  border-bottom: 1px solid var(--border-soft);
}
.tbl td:first-child { color: var(--text); }
.tbl tr:last-child td { border-bottom: 0; }
.tbl tbody tr { transition: background var(--dur-fast) var(--ease); }
.tbl tbody tr:hover { background: rgba(255, 255, 255, 0.018); }
.tbl td.n { font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right; color: var(--text); }
.tbl tr.is-rec td { color: var(--cyan); }
.tbl tr.is-rec td:first-child::before { content: '\25B8'; margin-right: 6px; }

/* ── Tabs ───────────────────────────────────────────────────────────────── */

.tabs { display: grid; gap: var(--sp-4); }
.tabs-list { display: flex; gap: var(--sp-6); border-bottom: 1px solid var(--border-soft); }
.tab {
  position: relative;
  padding: var(--sp-3) 0;
  background: none; border: 0; cursor: pointer;
  font-family: var(--font-display);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  color: var(--text-faint);
  transition: color var(--dur-fast) var(--ease);
}
.tab::after {
  content: '';
  position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
  background: var(--grad-r);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur) var(--ease);
}
.tab:hover { color: var(--text-dim); }
.tab[aria-selected='true'] { color: var(--text); }
.tab[aria-selected='true']::after { transform: scaleX(1); }
.tab-panels:empty { display: none; }

/* ── Segmented control ──────────────────────────────────────────────────── */

.segmented {
  display: inline-flex;
  padding: 3px;
  border: 1px solid var(--border-soft);
  border-radius: var(--r-4);
  background: var(--bg-surface);
  gap: 2px;
}
.segmented-opt {
  padding: 6px var(--sp-4);
  border: 0; border-radius: var(--r-3);
  background: none; cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--text-faint);
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.segmented-opt:hover { color: var(--text-dim); }
.segmented-opt.is-active { color: var(--text); background: var(--bg-elevated); }

/* ── Forms ──────────────────────────────────────────────────────────────── */

.fld-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--sp-5);
  margin: var(--sp-6) 0;
}
.fld { display: grid; gap: var(--sp-2); }
.fld-l {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--text-dim);
}
.fld-h { font-size: var(--fs-sm); color: var(--text-faint); line-height: var(--lh-normal); }

.inp {
  width: 100%;
  padding: 11px var(--sp-4);
  background: var(--bg-surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--r-3);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--fs-md);
  transition: border-color var(--dur-fast) var(--ease);
}
.inp:focus { border-color: var(--cyan); outline: none; }
select.inp { cursor: pointer; }

.inp-suffix-wrap { position: relative; display: flex; align-items: center; }
.inp-suffix-wrap .inp { padding-right: 44px; }
.inp-suffix {
  position: absolute; right: var(--sp-4);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--text-faint);
  pointer-events: none;
}

/* ── Skeleton ───────────────────────────────────────────────────────────────
 * MOVED. `.skeleton`, `.skeleton-line`, `.skeleton-block` and the shimmer now
 * live in motion.css, alongside every other keyframe, and kit.js's
 * `skeleton()` delegates to motion.js's so there is one implementation of
 * each. The rules that used to be here were a second, slightly different copy
 * — same class names, a different sweep — and whichever stylesheet loaded
 * last won. Do not re-add them here. */

/* ── Scroll region ──────────────────────────────────────────────────────────
 * THE ONE EXEMPTION FROM THE SCROLL LAW (contract §1). Nothing in this
 * product scrolls except an element carrying this class, and it is for a
 * genuinely unbounded list only: a search-results dropdown, a table of 80
 * tariffs. It is NOT an escape hatch for a panel that does not fit — that
 * panel gets denser, goes sideways, or segments.
 *
 * The scrollbar is deliberately VISIBLE. A hidden scrollbar on a region that
 * scrolls is a piece of content with no affordance to reach it, which the
 * contract rules out in the same sentence. */

/* CORRECTED 20 September 2026: `overflow-x: hidden` was a CLIP wearing the
 * name of a scroll.
 *
 * Measured by Stream D: an eight-column bill comparison, 724px of table
 * inside a 446px box on the site host. Four columns were simply gone — no
 * scrollbar, no fade, nothing on screen to say anything was missing. That is
 * the same family of fault as a `1fr` row resolving to zero: content
 * disappears and the interface reports success.
 *
 * A region named `scroll-region` scrolls. Both axes, with the scrollbar
 * visible on both, because a hidden scrollbar on a region that scrolls is
 * content with no affordance to reach it. If a caller wants a hard clip, that
 * is a different class and it should have to say so. */
.scroll-region {
  overflow: auto;
  min-height: 0;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--border-neon) transparent;
}
.scroll-region::-webkit-scrollbar { width: 9px; height: 9px; }
.scroll-region::-webkit-scrollbar-track { background: transparent; }
.scroll-region::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: var(--r-full);
  border: 3px solid transparent;
  background-clip: content-box;
}
.scroll-region::-webkit-scrollbar-thumb:hover { background: var(--border-neon); background-clip: content-box; }

/* ── Leading trim ───────────────────────────────────────────────────────────
 * For any text outside h1-h4 that sets a line-height tighter than its font's
 * glyph box — below ~1.40 on Jost, ~1.28 on JetBrains Mono. Without it the
 * element really does overflow itself and contract §1's self-check flags it.
 * See the long note on --lh-trim in tokens.css. */

.lh-trim { padding-block: var(--lh-trim); }

/* ── Empty state ────────────────────────────────────────────────────────────
 * One of the two things a customer sees when the product cannot do what they
 * asked, so it should look designed rather than like a paragraph that lost its
 * panel. A bordered, dashed block with a glyph, a title, the reason, and — the
 * part that earns trust — the engine path that produced nothing, in mono,
 * followed by the sentence "It has not been synthesised."
 *
 * Contract V5 §1: where a field is null, that is the engine declining to
 * guess. Render the absence with its reason; never substitute a default and
 * never hide the row. */

.empty-state {
  display: grid;
  gap: var(--sp-3);
  justify-items: start;
  max-width: 62ch;
  padding: var(--sp-6);
  border: 1px dashed var(--border-soft);
  border-radius: var(--r-5);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.012), transparent 60%),
    var(--bg-surface);
}
.empty-state-glyph {
  width: 22px; height: 22px;
  stroke: var(--text-faint);
  fill: none;
  stroke-width: 1.3;
}
.empty-missing .empty-state-glyph { stroke: var(--amber); }
.empty-blocked .empty-state-glyph { stroke: var(--red); }
.empty-waiting .empty-state-glyph { stroke: var(--cyan); }

.empty-state-title {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  line-height: var(--lh-tight);
  padding-block: var(--lh-trim);
  color: var(--text);
}
.empty-state-body { font-size: var(--fs-base); line-height: var(--lh-relaxed); color: var(--text-secondary); }
.empty-state-field {
  font-size: var(--fs-prose);
  line-height: var(--lh-normal);
  color: var(--text-faint);
}
.empty-state-field code {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  padding: 2px 6px;
  border: 1px solid var(--border-soft);
  border-radius: var(--r-2);
  background: var(--bg-deep);
  color: var(--cyan);
  overflow-wrap: anywhere;
}
.empty-state-detail { font-size: var(--fs-micro); line-height: 1.55; color: var(--text-faint); }
.empty-state-action { margin-top: var(--sp-2); }

/* Inside a panel the block already has a surface around it, so it drops its
 * own border and simply sits in the space. */
.panel-body > .empty-state,
.sp-body > .empty-state {
  padding: var(--sp-5) 0;
  border: 0;
  background: none;
}

/* ── Progress ring ──────────────────────────────────────────────────────── */

.progress-ring-wrap { position: relative; display: inline-grid; place-items: center; }
.progress-ring-track { stroke: var(--border-soft); fill: none; stroke-width: 4; }
.progress-ring-bar {
  stroke: var(--cyan); fill: none; stroke-width: 4; stroke-linecap: round;
  transform-origin: 50% 50%; transform: rotate(-90deg);
  transition: stroke-dashoffset var(--dur-slow) var(--ease);
}
.progress-ring-label {
  position: absolute;
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-variant-numeric: tabular-nums;
  line-height: var(--lh-mono);
  color: var(--text-dim);
}

/* ── Notes list ─────────────────────────────────────────────────────────────
 * "What this figure rests on" — the caveats are part of the answer, so they
 * read as a list, not a disclaimer bolted to the bottom. */

.notes {
  margin: var(--sp-4) 0 var(--sp-7);
  padding-left: var(--sp-5);
  display: grid;
  gap: var(--sp-3);
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  color: var(--text-secondary);
}

/* ── Copy button ────────────────────────────────────────────────────────── */

.copy-btn {
  padding: 5px 10px;
  border: 1px solid var(--border-soft);
  border-radius: var(--r-2);
  background: none;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  line-height: var(--lh-mono);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--text-faint);
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.copy-btn:hover { color: var(--text-dim); border-color: var(--border); }
.copy-btn.is-done { color: var(--cyan); border-color: var(--border-neon); }

/* ── The numbers flow result card ─────────────────────────────────────────
 * Owned by web/js/flows/numbers.js, styled here. */

/* WAS `max-width: 940px`. A well-meant reading cap that pinned a form to 940px
 * and centred it in a 1 524px column — measured at 22 % of the fast door's
 * screen inked, which is most of why contract V5 §0 item 4 says every screen
 * is 60–80 % empty. A reading measure is right for prose and wrong for an
 * instrument: the remedy for a wide screen is more columns at the same size,
 * not a narrower one. */
.numbers { max-width: 1400px; margin: 0 auto; }

/* ── Flow headings ──────────────────────────────────────────────────────────
 * `.doors-h` and `.doors-note` are emitted by web/js/flows/bill.js and
 * web/js/flows/numbers.js and had NO RULE IN ANY STYLESHEET — they rendered
 * as browser-default serif-ish `<h2>` and `<p>`, which is why two screens'
 * opening lines did not look like the rest of the product. Defined here
 * because components.css is where a class two flows share belongs. */

.doors-h {
  font-family: var(--font-display);
  font-size: clamp(19px, 2.4vh, 26px);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-tight);
  line-height: var(--lh-tight);
  padding-block: var(--lh-trim);
  color: var(--text);
  margin: 0 0 var(--sp-3);
}
.doors-note {
  max-width: 82ch;
  margin: 0 0 var(--sp-5);
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  color: var(--text-secondary);
}
/* A note that follows another note is a second paragraph, not a second
 * block: it loses the top gap so the two read as one passage. */
.doors-note + .doors-note { margin-top: calc(var(--sp-5) * -1 + var(--sp-3)); }

.result { margin-top: var(--sp-8); }
.result-err {
  padding: var(--sp-5);
  border: 1px solid var(--red);
  border-radius: var(--r-3);
  color: var(--text-dim);
}

.result-head {
  padding: var(--sp-7);
  border: 1px solid var(--border);
  border-radius: var(--r-5);
  background: var(--bg-surface);
  position: relative;
  overflow: hidden;
}
.result-head::after {
  content: '';
  position: absolute; inset: 0 0 auto 0; height: 2px;
  background: var(--grad);
}
.result-sku {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  letter-spacing: var(--ls-tight);
  margin-bottom: var(--sp-3);
}

/* THIS IS THE NUMBER THE CUSTOMER COMPLAINED ABOUT.
 *
 * Measured on #/numbers at 1600x950: `R 2 490 163` at clamp(30px, 4.6vw,
 * 52px) — 52px tall, 380px wide, gradient-filled — on a screen whose next
 * largest element was 17px. "The numbers are too big ... it looks really
 * unprofessional" was about this rule.
 *
 * Capped at --fs-figure per contract V5 §3, tabular, untracked, and the
 * gradient withdrawn. The markup belongs to web/js/flows/numbers.js (Stream
 * D); the rule is here, so the fix lands without touching their file. */
.result-big { display: flex; align-items: baseline; gap: var(--sp-4); flex-wrap: wrap; }
.result-big b {
  font-family: var(--font-mono);
  font-size: var(--fs-figure);
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1, 'zero' 1;
  letter-spacing: var(--ls-figure);
  line-height: var(--lh-mono);
  padding-block: var(--lh-trim);
  color: var(--text);
  background: none;
  -webkit-text-fill-color: currentColor;
}
.result-big span { font-size: var(--fs-prose); color: var(--text-secondary); }

/* ── The landing ────────────────────────────────────────────────────────────
 * The first thing anyone sees, and the screen the customer judged "incredibly
 * basic": three flat grey cards under the line "Three ways in."
 *
 * It is now one viewport, never scrolled, laid out in three rows — a headline
 * block, two instrument doors, and a quiet third entry beside the privacy
 * note. Behind it a canvas draws the marketing site's receding ground plane
 * (ambientField() in motion.js); in front of it nothing moves until you point
 * at it.
 *
 * Everything vertical is sized against `vh`, and the door description's line
 * count steps DOWN at short viewports rather than being clipped — a card that
 * silently loses its last sentence at 720px is the failure mode this whole
 * section exists to avoid. */

.start {
  position: relative;
  height: 100%;
  min-height: 0;
  overflow: hidden;
  --door-lines: 5;
}

.start-field {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  opacity: 0.66;
}

/* The scrim. The ground plane is a backdrop, and a backdrop that competes
 * with the sentence in front of it is a decoration that has been allowed to
 * win. This darkens hardest at the top and bottom — behind the headline and
 * behind the privacy line — and lets the field through in the band where the
 * two doors sit, which are opaque surfaces anyway. */
.start::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    rgba(5, 7, 12, 0.93) 0%,
    rgba(5, 7, 12, 0.60) 26%,
    rgba(5, 7, 12, 0.18) 52%,
    rgba(5, 7, 12, 0.72) 88%,
    rgba(5, 7, 12, 0.92) 100%
  );
}

.start-inner {
  position: relative;
  z-index: 1;
  height: 100%;
  min-height: 0;
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: var(--stage-pad-y) var(--stage-pad-x);
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  gap: clamp(12px, 2.4vh, 30px);
}
.start-inner > * { min-height: 0; }

/* ── Landing: the headline block ────────────────────────────────────────── */

.start-head { display: grid; gap: clamp(6px, 1.2vh, 14px); justify-items: start; }
.start-head .eyebrow { margin: 0; }

.start-h {
  font-size: var(--fs-display);
  font-weight: 300;
  letter-spacing: -0.028em;
  color: var(--text);
}
/* The one place on this screen the gradient runs through type. Spending it
 * twice would make it decoration rather than a signature. */
.start-h em {
  font-style: normal;
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.start-lead {
  max-width: 62ch;
  font-size: clamp(var(--fs-prose), 1.7vh, 15px);
  line-height: var(--lh-relaxed);
  color: var(--text-secondary);
}

/* ── Landing: the two instrument doors ──────────────────────────────────── */

.start-doors {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(12px, 1.4vw, 22px);
  min-height: 0;
}

/* Flex column rather than a grid with a 1fr row: with a 1fr row the slack
 * lands INSIDE the description box, which reads as a paragraph that stopped
 * early. As a flex column with `margin-top: auto` on the call to action, the
 * head/title/description/facts group sits together at the top and the slack
 * falls in one clean band above the CTA, where it reads as deliberate. */
.door {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: clamp(6px, 1.1vh, 13px);
  text-align: left;
  padding: clamp(14px, 2.4vh, 26px);
  min-height: 0;
  border: 1px solid var(--border-soft);
  border-radius: var(--r-5);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.004)),
    var(--bg-surface-2);
  cursor: pointer;
  overflow: hidden;
  /* No transform on hover. The contract is explicit: a 1px border lift plus a
   * faint wash, 250ms — never a jump. A card that leaps 3px under the cursor
   * is the single cheapest-looking thing an interface can do. */
  transition: border-color 250ms var(--ease), background 250ms var(--ease),
              box-shadow 250ms var(--ease);
}

/* The pointer-tracked wash. boot.js writes --mx/--my on pointermove and only
 * ever writes custom properties — nothing in that path reads layout. */
.door::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(
    380px circle at var(--mx, 50%) var(--my, 0%),
    var(--plasma-wash),
    transparent 62%
  );
  opacity: 0;
  transition: opacity 250ms var(--ease);
  pointer-events: none;
}

/* The gradient hairline on the top edge, drawn left to right. */
.door::after {
  content: '';
  position: absolute; inset: 0 0 auto 0;
  height: 2px;
  background: var(--grad);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-slow) var(--ease);
}

.door:hover, .door:focus-visible {
  border-color: var(--border-neon);
  box-shadow: 0 22px 48px -30px rgba(0, 0, 0, 0.9);
}
.door:hover::before, .door:focus-visible::before { opacity: 1; }
.door:hover::after, .door:focus-visible::after { transform: scaleX(1); }
.door:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
.door > * { position: relative; z-index: 1; }

/* The instrument glyph, as a watermark rather than a corner icon.
 *
 * It is anchored to the bottom-right of the door and sized in `vh`, which
 * does two jobs at once: it gives the brand a piece of technical drawing on
 * the card, and it fills the slack a two-sentence description leaves in a
 * tall box — the band of nothing above the call to action that made these
 * read as empty. It is out of flow, so it contributes no height and cannot
 * push the door past the viewport. */
.door-glyph {
  position: absolute;
  right: clamp(8px, 1.4vw, 20px);
  bottom: clamp(6px, 1.2vh, 16px);
  width: clamp(62px, 11.5vh, 112px);
  height: clamp(62px, 11.5vh, 112px);
  z-index: 0;
  color: var(--cyan);
  opacity: 0.13;
  pointer-events: none;
  transition: opacity var(--dur-slow) var(--ease);
}
.door-glyph svg { width: 100%; height: 100%; display: block; }
.door:hover .door-glyph, .door:focus-visible .door-glyph { opacity: 0.26; }

.door-n {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--ls-wider);
  color: var(--text-faint);
  line-height: var(--lh-mono);
  padding-block: var(--lh-trim);
}

.door-t {
  font-family: var(--font-display);
  font-size: clamp(18px, 2.7vh, 25px);
  font-weight: 400;
  letter-spacing: var(--ls-tight);
  line-height: var(--lh-tight);
  padding-block: var(--lh-trim);
  color: var(--text);
}

.door-d {
  flex: 0 1 auto;
  min-height: 0;
  font-size: clamp(var(--fs-prose), 1.62vh, 14px);
  line-height: 1.55;
  color: var(--text-secondary);
  /* Clamped rather than clipped: the count steps down with the viewport (see
   * the height queries at the end of this section) so the paragraph always
   * ends on a whole line instead of being sliced mid-word. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--door-lines);
  line-clamp: var(--door-lines);
  overflow: hidden;
}

/* The capability strip: what this door actually does, in mono, as facts. */
.door-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding-top: clamp(2px, 0.6vh, 8px);
  border-top: 1px solid var(--border-soft);
}
.door-facts span {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: var(--lh-mono);
  padding: 5px 8px;
  border-radius: var(--r-2);
  background: rgba(150, 190, 255, 0.055);
  color: var(--text-faint);
  white-space: nowrap;
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.door:hover .door-facts span { color: var(--text-secondary); background: rgba(150, 190, 255, 0.09); }

.door-go {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  line-height: var(--lh-mono);
  padding-block: var(--lh-trim);
  color: var(--cyan);
}
.door-go i {
  font-style: normal;
  transition: transform var(--dur) var(--ease);
}
.door:hover .door-go i, .door-min:hover .door-go i { transform: translateX(4px); }

/* ── Landing: the third, quieter entry ──────────────────────────────────── */

.start-foot {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.8fr);
  align-items: center;
  gap: clamp(12px, 1.6vw, 26px);
}

.door-min {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(10px, 1.4vw, 20px);
  text-align: left;
  padding: clamp(10px, 1.6vh, 16px) clamp(12px, 1.4vw, 20px);
  border: 1px solid var(--border-soft);
  border-radius: var(--r-5);
  background: var(--bg-surface-2);
  cursor: pointer;
  overflow: hidden;
  transition: border-color 250ms var(--ease), background 250ms var(--ease);
}
.door-min:hover, .door-min:focus-visible {
  border-color: var(--border-neon);
  background: var(--bg-surface-3);
}
.door-min:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }

/* `.door-go` pins itself to the bottom of a tall door; inside the short,
 * horizontal third entry there is no bottom to pin to. */
.door-min .door-go { margin-top: 0; }

.door-min-body { display: grid; gap: 3px; min-width: 0; }
.door-min-t {
  font-family: var(--font-display);
  font-size: clamp(14px, 1.9vh, 17px);
  letter-spacing: var(--ls-tight);
  line-height: var(--lh-tight);
  padding-block: var(--lh-trim);
  color: var(--text);
}
.door-min-d {
  font-size: var(--fs-prose);
  line-height: 1.5;
  color: var(--text-faint);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

.start-note {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font-size: var(--fs-prose);
  line-height: 1.5;
  color: var(--text-faint);
}

/* Short viewports give the doors fewer lines of prose rather than clipping
 * the paragraph. 810px and 720px are the two heights this is verified at. */
@media (max-height: 800px) { .start { --door-lines: 4; } }
@media (max-height: 700px) { .start { --door-lines: 3; } }
@media (max-height: 620px) {
  .start { --door-lines: 2; }
  .start-lead { display: none; }
}

/* Below the two-column breakpoint the doors stack, and the third entry and
 * the note stop competing for the same row. */
@media (max-width: 720px) {
  .start-doors { grid-template-columns: minmax(0, 1fr); }
  .start-foot { grid-template-columns: minmax(0, 1fr); }
  .door-facts { display: none; }
}

/* Phone.
 *
 * At 375x720 the full landing does not fit, and because `.start` clips its
 * overflow it did not SAY so — it quietly ate the privacy line and both door
 * descriptions while the scroll self-check still reported clean. A check that
 * passes because the content was cut off is the exact failure the scroll law
 * is supposed to prevent, so this is a real layout, not a smaller one:
 * summarise and drill (contract §1). The doors keep their number, their name
 * and their call to action — enough to choose between two things — and the
 * prose they lose is prose the next screen repeats anyway. */
@media (max-width: 600px) {
  .start-inner { padding: var(--sp-4); gap: var(--sp-4); }
  .start-h { font-size: clamp(23px, 4.4vh, 31px); }
  .start-lead { display: none; }

  .door { padding: var(--sp-4) var(--sp-5); gap: var(--sp-2); }
  .door-d { display: none; }
  .door-glyph { width: 56px; height: 56px; opacity: 0.16; }

  /* The third entry wraps its call to action onto its own line rather than
   * squeezing three columns into 375px and truncating the middle one. */
  .door-min { grid-template-columns: auto minmax(0, 1fr); row-gap: var(--sp-2); }
  .door-min .door-go { grid-column: 1 / -1; justify-content: flex-start; }
  .door-min-d { -webkit-line-clamp: 3; line-clamp: 3; }
}

/* ── Stage error ────────────────────────────────────────────────────────────
 * The other thing a customer sees when the product breaks. It used to be a
 * paragraph, a second paragraph and a raw <pre> stack trace in a box, left
 * against the edge of an otherwise empty 1500px column — which reads as the
 * application having fallen over rather than as a screen that could not be
 * opened.
 *
 * It is now a framed, centred block in the product's own register: a warning
 * glyph, what was being opened, why it failed, the technical detail folded
 * away behind a <details> for whoever wants it, and a working way back. The
 * detail is still there in full — an error message that hides what actually
 * went wrong helps nobody — but it does not lead. */

.flow-error {
  max-width: 64ch;
  margin: clamp(24px, 6vh, 72px) auto 0;
  display: grid;
  gap: var(--sp-4);
  justify-items: start;
  padding: var(--sp-7);
  border: 1px solid var(--border-soft);
  border-radius: var(--r-6);
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(255, 182, 61, 0.05), transparent 62%),
    var(--bg-surface-2);
  box-shadow: var(--shadow-lg);
}
.flow-error-glyph {
  width: 26px; height: 26px;
  stroke: var(--amber);
  fill: none;
  stroke-width: 1.3;
}
.flow-error .eyebrow { margin: 0; color: var(--amber); }
.flow-error .eyebrow::before { background: linear-gradient(90deg, var(--amber), transparent); }
.flow-error-title {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  line-height: var(--lh-tight);
  padding-block: var(--lh-trim);
  letter-spacing: var(--ls-tight);
  color: var(--text);
}
.flow-error-body { font-size: var(--fs-base); line-height: var(--lh-relaxed); color: var(--text-secondary); }

.flow-error-more { width: 100%; }
.flow-error-more > summary {
  cursor: pointer;
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.16em;
  line-height: var(--lh-mono);
  text-transform: uppercase;
  color: var(--text-faint);
  transition: color var(--dur-fast) var(--ease);
}
.flow-error-more > summary::-webkit-details-marker { display: none; }
.flow-error-more > summary::before { content: '\25B8'; transition: transform var(--dur-fast) var(--ease); }
.flow-error-more[open] > summary::before { transform: rotate(90deg); }
.flow-error-more > summary:hover { color: var(--text-dim); }

.flow-error pre {
  margin: var(--sp-3) 0 0;
  padding: var(--sp-4);
  border: 1px solid var(--border-soft);
  border-radius: var(--r-3);
  background: var(--bg-deep);
  color: var(--text-faint);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  line-height: 1.55;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 220px;
  overflow: auto;
}

/* ── Toasts ─────────────────────────────────────────────────────────────── */

.toast-stack {
  position: fixed;
  right: var(--sp-6); bottom: var(--sp-6);
  z-index: var(--z-toast);
  display: grid; gap: var(--sp-3);
  max-width: 380px;
}
.toast {
  padding: var(--sp-4) var(--sp-5);
  border: 1px solid var(--border);
  border-left-width: 2px;
  border-radius: var(--r-3);
  background: var(--bg-elevated);
  box-shadow: var(--shadow-lg);
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  color: var(--text-dim);
  animation: toast-in var(--dur-slow) var(--ease);
}
@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}
.toast-info { border-left-color: var(--cyan); }
.toast-warn { border-left-color: var(--amber); }
.toast-err  { border-left-color: var(--red); }
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
}

/* ── Modal ──────────────────────────────────────────────────────────────── */

.modal-backdrop {
  position: fixed; inset: 0;
  z-index: var(--z-modal);
  display: grid; place-items: center;
  padding: var(--sp-6);
  background: rgba(5, 7, 12, 0.72);
  backdrop-filter: blur(6px);
  animation: modal-fade var(--dur) var(--ease);
}
@keyframes modal-fade { from { opacity: 0; } to { opacity: 1; } }

/* The modal itself does not scroll — contract §1 names it explicitly. It was
 * `overflow: auto` on the whole dialog, which scrolls the TITLE AND THE
 * ACTIONS out of view: a long form would push its own Save button off the
 * top, which is the worst version of this defect because the control you need
 * is the one that disappears.
 *
 * Three rows instead: the head and the actions are pinned, and only the body
 * between them can scroll — and only because modal.js marks it a
 * `.scroll-region`, with a visible scrollbar, inside a dialog that itself
 * fits the viewport. */
.modal {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  width: min(560px, 100%);
  max-height: min(80vh, 720px);
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--r-6);
  background: var(--bg-surface-2);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-7);
  animation: modal-in var(--dur-slow) var(--ease);
}
@keyframes modal-in {
  from { opacity: 0; transform: translateY(10px) scale(0.98); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .modal-backdrop, .modal { animation: none; }
}

.modal-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-5); margin-bottom: var(--sp-5); }
.modal-title { font-family: var(--font-display); font-size: var(--fs-xl); letter-spacing: var(--ls-tight); margin: 0; }
.modal-close {
  flex: none;
  width: 28px; height: 28px;
  display: grid; place-items: center;
  border: 1px solid var(--border-soft);
  border-radius: var(--r-full);
  background: none; cursor: pointer;
  color: var(--text-dim);
  font-size: 16px; line-height: 1;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.modal-close:hover { color: var(--text); border-color: var(--border); }
.modal-head, .modal-actions { flex: none; }
.modal-body {
  min-height: 0;
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  color: var(--text-dim);
  /* Padded on the right so a scrollbar, when there is one, does not sit on
   * top of the text it is scrolling. */
  padding-right: var(--sp-2);
  margin-right: calc(var(--sp-2) * -1);
}
.modal-actions { display: flex; justify-content: flex-end; gap: var(--sp-3); margin-top: var(--sp-6); }

body.has-modal { overflow: hidden; }

/* ── SI symbols keep their case ────────────────────────────────────────────
 *
 * Case carries meaning in an SI symbol. `kWh` is a kilowatt-hour; `KWH` is
 * not a unit at all, and `mW` and `MW` differ by a factor of a billion. The
 * product's micro-labels are uppercase by design — 103 rules across fourteen
 * stylesheets — and wherever one of them met a unit it destroyed it:
 *
 *     450 000 KWH A MONTH · 1 600 KVA · MEGAFLEX <500V
 *     PER KWH        MONTH'S KWH        GHI · KWH/M²
 *
 * The design is not wrong; applying it to a symbol is. Every unit the kit
 * renders already sits in its own span, so one rule protects all of them, and
 * `.si` is here for the ad-hoc case — a unit written inline in a string that
 * an uppercase label will otherwise swallow.
 *
 * Reach for `.si` rather than deleting the uppercase: the label is meant to
 * shout, the symbol is meant to be correct, and only one of those is
 * negotiable. */
.quantity-u,
.big-number-unit,
.stat-unit,
.figure-u,
.metric-u,
.defrow-u,
.ss-u,
.am-unit,
.si {
  text-transform: none;
}

/* ── Negative money (contract V6 §3) ────────────────────────────────────────
 * "Negative money is not drawn in the gain colour." An NPV of −R 1,69 bn was
 * printed in white beside positive figures in cyan, which read as a gain.
 * kit.js marks every rendered negative amount `.is-neg` (see isNegativeMoney
 * and signTone there); this paints it in the loss colour wherever it lands.
 * `-webkit-text-fill-color` because a figure may carry a gradient text fill,
 * which would otherwise win over `color`. */
.quantity.is-neg .quantity-v,
.figure.is-neg .figure-v,
.metric.is-neg .metric-v,
.defrow.is-neg .defrow-n,
.stat.is-neg .stat-value,
.big-number.is-neg .big-number-figure,
.ss-cell.is-neg .ss-v,
.kv-v.is-neg,
td.is-neg,
.is-neg-text {
  color: var(--loss) !important;
  -webkit-text-fill-color: var(--loss);
}
.big-number.is-neg .big-number-value { background: none; -webkit-text-fill-color: var(--loss); color: var(--loss); }

/* ══════════════════════════════════════════════════════════════════════════
   V7 START — docs/EXPERIENCE_V7.md §4.1
   One headline, three tight doors, the four worked examples, nothing else.
   The only screen locked to the viewport; nothing on it scrolls (at a phone
   width it does, rather than clip). Overrides the V6 landing rules above.
═══════════════════════════════════════════════════════════════════════════ */

.start {
  height: 100%;
  overflow: hidden;
  background:
    radial-gradient(1100px 520px at 12% -12%, rgba(24, 152, 252, 0.13), transparent 62%),
    radial-gradient(900px 480px at 96% 112%, rgba(60, 204, 252, 0.09), transparent 60%);
}
.start::before { content: none; }

.start-inner {
  max-width: 1180px;
  height: 100%;
  padding: clamp(20px, 4.5vh, 56px) var(--v7-pad, 40px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(20px, 3.6vh, 40px);
}

.start-head { display: flex; flex-direction: column; gap: 12px; }
.start-h {
  margin: 0;
  padding: 0;
  font: 400 clamp(28px, 4.6vh, 38px)/1.15 var(--font-heading);
  letter-spacing: -0.015em;
  color: var(--text);
  text-wrap: balance;
}
.start-lead {
  margin: 0;
  max-width: 640px;
  font-size: clamp(14.5px, 2vh, 16px);
  line-height: 1.6;
  color: var(--text-dim);
}

.start-doors { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }

.door {
  gap: 8px;
  padding: clamp(16px, 2.6vh, 22px) 22px;
  border-radius: 16px;
  border: 1px solid rgba(150, 190, 255, 0.11);
  background: linear-gradient(180deg, rgba(20, 30, 45, 0.78), rgba(11, 17, 27, 0.78));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035);
  overflow: hidden;
}
.door:hover, .door:focus-visible { border-color: rgba(60, 204, 252, 0.42); box-shadow: 0 18px 40px -28px rgba(0, 0, 0, 0.9); }
.door-glyph {
  position: static;
  width: 30px;
  height: 30px;
  opacity: 1;
  color: var(--cyan);
  margin-bottom: 4px;
}
.door:hover .door-glyph, .door:focus-visible .door-glyph { opacity: 1; }
.door-t {
  font: 500 19px/1.3 var(--font-heading);
  letter-spacing: -0.005em;
  padding: 0;
  color: var(--text);
}
.door-d {
  display: block;
  -webkit-line-clamp: unset;
  line-clamp: unset;
  font-size: 14px;
  line-height: 1.55;
  color: var(--text-secondary);
}
.door-go {
  margin-top: auto;
  padding: 6px 0 0;
  font: 500 14px/1.3 var(--font-body);
  letter-spacing: 0;
  text-transform: none;
  color: var(--cyan);
}
.door-note {
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--text-faint);
}

.start-examples { display: flex; flex-direction: column; gap: 12px; }
.start-examples-h {
  margin: 0;
  font: 500 14px/1.3 var(--font-body);
  color: var(--text-secondary);
}
.start-example-cards .v7-example { min-height: 0; padding: 14px 16px; gap: 6px; }
.start-example-cards .v7-example-name { font-size: 15.5px; }
.v7-example-err { font-size: 12.5px; line-height: 1.4; color: var(--loss); }

@media (max-width: 900px) {
  .start-doors { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .door-glyph { display: none; }
}
@media (max-width: 600px) {
  :root[data-route='start'] .app-center { overflow: auto; }
  :root[data-route='start'] .stage { height: auto; }
  .start { height: auto; overflow: visible; }
  .start-inner { height: auto; justify-content: flex-start; padding: 20px; }
}
/* V7 Start, continued: the lede is part of the answer on every width (the V6
 * phone rule above hid it), and the section fills the stage so the content is
 * centred in the viewport rather than in a box its own height. */
.start-lead { display: block; }
:root[data-route='start'] .stage { display: block; }

/* V7 Start: the headline and one line-art illustration side by side (the lead's
 * review: the top quarter read unfinished). The illustration is decoration —
 * aria-hidden, no text, gone below 900 px — and its one moving part, the flow
 * along the cable, stops for anyone who asked for less motion. */
.start-head { display: grid; grid-template-columns: minmax(0, 1fr) clamp(340px, 34vw, 470px); align-items: center; justify-items: stretch; gap: clamp(24px, 3vw, 48px); }
.start-copy { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.start-art { color: var(--cyan); min-width: 0; }
.start-art svg { display: block; width: 100%; height: auto; overflow: visible; }
.start-art path, .start-art circle { stroke: currentColor; stroke-width: 1.6; vector-effect: non-scaling-stroke; }
.start-art .sa-ground { stroke: rgba(150, 190, 255, 0.16); }
.start-art .sa-sun { color: var(--amber); stroke: var(--amber); }
.start-art .sa-sun path, .start-art .sa-sun circle { stroke: var(--amber); opacity: 0.85; }
.start-art .sa-light { stroke: rgba(255, 182, 61, 0.35); stroke-dasharray: 2 6; }
.start-art .sa-array path { stroke: var(--cyan); }
.start-art .sa-box path { stroke: var(--cyan-lt); }
.start-art .sa-box .sa-ribs { stroke: rgba(127, 226, 254, 0.35); }
.start-art .sa-site path { stroke: var(--text-secondary); }
.start-art .sa-cable { stroke: rgba(60, 204, 252, 0.28); }
.start-art .sa-flow {
  stroke: var(--cyan-lt);
  stroke-width: 2;
  stroke-dasharray: 10 26;
  filter: drop-shadow(0 0 4px rgba(60, 204, 252, 0.8));
  animation: sa-flow 2.4s linear infinite;
}
.start-art .sa-node { fill: var(--bg-deep); stroke: var(--cyan); }
@keyframes sa-flow { to { stroke-dashoffset: -36; } }
@media (prefers-reduced-motion: reduce) { .start-art .sa-flow { animation: none; } }

.start-jump { display: none; }
@media (max-width: 900px) {
  .start-head { grid-template-columns: minmax(0, 1fr); }
  .start-art { display: none; }
}
@media (max-width: 600px) {
  .start-jump {
    display: inline-flex; align-items: center; gap: 6px; align-self: flex-start;
    margin-top: 2px; padding: 8px 14px; border-radius: 999px;
    border: 1px solid var(--v7-line-strong, rgba(150, 190, 255, 0.18));
    background: none; color: var(--cyan-lt);
    font: 500 13.5px/1.2 var(--font-body); cursor: pointer;
  }
  .start-jump i { font-style: normal; }
}

/* ── V7 numbers door (flows/numbers.js) ─────────────────────────────────── */
.nm7-layout { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); gap: 20px; align-items: start; }
@media (max-width: 1000px) { .nm7-layout { grid-template-columns: minmax(0, 1fr); } }
.nm7-form { gap: 20px; padding: 24px 26px; }
.nm7-form .v7-h2 { font-size: 18px; }
.nm7-more { background: rgba(8, 12, 20, 0.4); }
.nm7-more .v7-drawer-summary { padding: 13px 16px; font-size: 14px; }
.nm7-more .v7-drawer-body { padding: 4px 16px 16px; }
.nm7-status { margin: 0; font-size: 13.5px; line-height: 1.45; color: var(--text-secondary); max-width: 460px; }
.nm7-status.is-err { color: var(--loss); }
.nm7-list { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 10px; }
.nm7-list li { position: relative; padding-left: 18px; font-size: 14.5px; line-height: 1.5; color: var(--text-dim); }
.nm7-list li::before { content: ''; position: absolute; left: 0; top: 0.62em; width: 6px; height: 6px; border-radius: 50%; background: var(--cyan); }
.nm7-link { color: var(--cyan-lt); text-decoration: none; font-weight: 500; }
.nm7-link:hover { text-decoration: underline; }

/* ══ V7: toasts, dialogs and the route-error block ══════════════════════════
 * The same surfaces, radius and type as the V7 kit, so a message or a dialog
 * belongs to the product rather than to an older layer of it. */

.toast-stack { right: 24px; bottom: 24px; gap: 10px; max-width: 400px; }
.toast {
  padding: 12px 16px 12px 18px;
  border: 1px solid var(--v7-line-strong, rgba(150, 190, 255, 0.18));
  border-left: 3px solid var(--cyan);
  border-radius: 12px;
  background: linear-gradient(180deg, #111a28, #0b111b);
  box-shadow: 0 18px 40px -20px rgba(0, 0, 0, 0.85);
  font: 400 14px/1.5 var(--font-body);
  color: var(--text-dim);
}
.toast-warn { border-left-color: var(--amber); }
.toast-err { border-left-color: var(--loss); }

.modal {
  border-radius: 16px;
  border: 1px solid var(--v7-line-strong, rgba(150, 190, 255, 0.18));
  background: linear-gradient(180deg, #0f1826, #0a0f19);
  padding: 22px 24px;
}
.modal-title { font: 500 19px/1.3 var(--font-heading); letter-spacing: 0; color: var(--text); }
.modal-close { width: 34px; height: 34px; font-size: 20px; color: var(--text-secondary); border-color: var(--v7-line-strong, rgba(150, 190, 255, 0.18)); }
.modal-body { font: 400 14.5px/1.6 var(--font-body); }

.flow-error {
  max-width: 620px;
  margin: clamp(32px, 8vh, 96px) auto 0;
  gap: 12px;
  padding: 28px 30px;
  border-radius: 16px;
  border: 1px solid var(--v7-line, rgba(150, 190, 255, 0.09));
  background: linear-gradient(180deg, rgba(20, 30, 45, 0.78), rgba(11, 17, 27, 0.78));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035);
}
.flow-error .eyebrow {
  font: 600 12px/1 var(--font-body);
  letter-spacing: 0.1em;
  color: var(--amber);
}
.flow-error .eyebrow::before { content: none; }
.flow-error-title { font: 400 24px/1.25 var(--font-heading); letter-spacing: -0.01em; padding: 0; }
.flow-error-body { font-size: 15px; line-height: 1.6; color: var(--text-dim); }
.flow-error-more > summary {
  font: 500 13px/1.3 var(--font-body);
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-secondary);
}
.flow-error .btn-row { margin-top: 8px; }
