/* ===========================================================================
   RealEstateValuationApp — Phase 1 design system
   MMeoni Consulting S.r.l.

   Token values in the brand block are copied verbatim from
   ../../LinkidinPost/linkedin-agent-workshop-main/app/public/styles.css
   so the two apps read as one suite.

   RULE: no hex literal appears anywhere outside the two :root token blocks.
   Components are built only from tokens.
   =========================================================================== */

/* ===========================================================================
   Fonts — embedded locally, never a CDN link.

   An export generated offline, or from index.html opened as a file, used to
   fall back to system fonts silently and leave the document looking like a
   different firm's. The document is the deliverable, so the type is part of
   the app. See fonts/README.md for provenance and licence (all three are
   SIL OFL 1.1).

   Cinzel and Montserrat are single variable files covering their weight
   axis, hence the range in font-weight. DM Mono has no variable cut.
   =========================================================================== */

@font-face {
  font-family: "Cinzel";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/cinzel-latin-var.woff2") format("woff2");
}

@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/montserrat-latin-var.woff2") format("woff2");
}

@font-face {
  font-family: "DM Mono";
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("fonts/dm-mono-latin-300.woff2") format("woff2");
}

@font-face {
  font-family: "DM Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/dm-mono-latin-400.woff2") format("woff2");
}

@font-face {
  font-family: "DM Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/dm-mono-latin-500.woff2") format("woff2");
}

/* --- Light palette (complete) ------------------------------------------- */
:root {
  color-scheme: light;

  /* Brand constants — identical in both themes, never redefined.
     AssetMANDATUM palette (brand/assetmandatum-assets/README.md), landed
     Session 06j: warm charcoal + champagne replaces navy + gold. --navy and
     --gold keep their names (widely referenced) but carry the new hex. */
  --navy: #2c2826; /* charcoal */
  --navy-secondary: #3d3733; /* lighter charcoal, section backgrounds */
  --gold: #b8956a; /* champagne */
  --gold-light: #d4bfa0; /* lighter champagne, hover states */
  --gold-dark: #9a7d5a; /* bronze */
  --text-light: #f4f1ec; /* bone, text on charcoal */

  /* Semantic surfaces and ink */
  --page: #f4f1ec; /* bone */
  --surface: #fbf8f3; /* paper */
  --surface-muted: #f4efe6; /* linen */
  --surface-sunken: #ede6d8;
  --ink: #2c2826; /* charcoal */
  --ink-soft: #4a443f;
  --muted: #6b6660; /* stone */
  --line: #e5e0d8; /* hairline */
  --line-strong: #d0c8ba;

  /* Form fields — inputs must LOOK like inputs (spec/05 section 4).
     --field-bg stays white, deliberately: on the new paper (#fbf8f3)
     surface a white field is now MORE distinct than on the old white
     surface, not less — checked on screen, Session 06j. */
  --field-bg: #ffffff;
  --field-border: #9c8b6c;
  --field-ink: #2c2826;

  /* Verdict / status. --pass and --fail are UNCHANGED — Michele's ruling,
     18 Sep 2026: the verdict colours are a signal learned by repetition,
     not decoration, and stay exactly as they are through the rebrand.
     --strategic is darkened from #b85f16 to clear 4.5:1 text contrast on
     the new bone/paper surfaces (was 3.98:1 on bone; now 4.66:1) — same
     hue, minimal adjustment, Session 06j. */
  --pass: #237a57;
  --pass-soft: #e7f1eb;
  --fail: #b42318;
  --fail-soft: #fbeceb;
  --strategic: #a8560f;
  --strategic-soft: #fdf2e7;

  --focus: #0a66c2;
  /* Legible against the charcoal topbar, where --fail is too dark to read.
     Re-checked on charcoal Session 06j: 7.10:1, unchanged. */
  --save-failed-ink: #e8a49a;
  /* VR-BENCH-09, Session 06x — the open-item orange, tuned to read on the
     navy verdict rail exactly as --save-failed-ink is tuned for charcoal.
     --strategic itself is a 4.66:1 ink for bone/paper and disappears
     against navy; this is the same hue carried up to 7.7:1 there. The
     register is unchanged: orange means an open item, never a failure. */
  --strategic-ink: #e8b877;
  --shadow: 0 14px 36px rgba(44, 40, 38, 0.1);
  --shadow-soft: 0 2px 10px rgba(44, 40, 38, 0.06);

  /* Typography */
  --font-display: "Cinzel", "Times New Roman", Georgia, serif;
  --font-body: "Montserrat", ui-sans-serif, system-ui, -apple-system,
    BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono: "DM Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo,
    Consolas, monospace;

  --step--1: 0.8125rem;
  --step-0: 0.9375rem;
  --step-1: 1.0625rem;
  --step-2: 1.375rem;
  --step-3: clamp(1.625rem, 1.2rem + 1.4vw, 2.125rem);
  --step-4: clamp(2rem, 1.3rem + 2.6vw, 3rem);

  /* Space */
  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 24px;
  --s6: 32px;
  --s7: 48px;
  --s8: 64px;
  --s9: 96px;

  --radius: 2px;
  --rule: 1px;
  --measure: 34rem;

  --motion: 160ms cubic-bezier(0.2, 0.6, 0.2, 1);
  --motion-slow: 260ms cubic-bezier(0.2, 0.6, 0.2, 1);
}

/* --- Dark palette: semantic tokens only, brand constants untouched ------- */
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;

    /* Retinted Session 06j: warm-charcoal-cast, matched lightness to the
       previous blue-cast values, not the brand's light-surface tokens
       (those are for the light theme only). */
    --page: #131110;
    --surface: #23201e;
    --surface-muted: #2c2826;
    --surface-sunken: #1a1716;
    --ink: #f4f1ec;
    --ink-soft: #dddad8;
    --muted: #aea6a2;
    --line: #3d3835;
    --line-strong: #57504c;

    --field-bg: #191615;
    --field-border: #5d5551;
    --field-ink: #f4f1ec;

    --pass: #4fb08a;
    --pass-soft: #102420;
    --fail: #e58176;
    --fail-soft: #2b1512;
    --strategic: #dc9a52;
    --strategic-soft: #2b1d0d;

    --focus: #7cb3ec;
    --shadow: 0 14px 36px rgba(0, 0, 0, 0.55);
    --shadow-soft: 0 2px 10px rgba(0, 0, 0, 0.4);
  }
}

/* --- Reduced motion: zero the tokens, so every transition obeys ---------- */
@media (prefers-reduced-motion: reduce) {
  :root {
    --motion: 1ms linear;
    --motion-slow: 1ms linear;
  }

  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}

/* =========================================================================
   Base
   ========================================================================= */

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

html {
  min-height: 100%;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--page);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--step-0);
  font-weight: 400;
  line-height: 1.55;
  display: flex;
  flex-direction: column;
}

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

button {
  cursor: pointer;
}

[hidden] {
  display: none !important;
}

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Figures are always DM Mono with tabular numerals. */
.num,
.computed,
.field-input,
td.num,
th.num,
.kpi-value,
.verdict-figure {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* =========================================================================
   Topbar and brand mark
   ========================================================================= */

.topbar {
  background: var(--navy);
  border-bottom: 2px solid var(--gold);
  color: var(--text-light);
  padding: var(--s4) 0;
}

.topbar-inner,
.shell {
  width: min(1160px, calc(100vw - var(--s6)));
  margin: 0 auto;
}

.topbar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s5);
  flex-wrap: wrap;
}

.brandmark {
  display: flex;
  flex-direction: column;
  gap: var(--s1);
  position: relative;
}

/* Session 06j: the wordmark is an inline SVG (wordmark.js), coloured by
   currentColor so it takes --gold-light on the charcoal topbar, matching
   the old .brandmark-name's ink weight. Height fixed, width auto — the
   source viewBox is 500x110. */
.brandmark-wordmark {
  color: var(--gold-light);
  width: 168px;
  height: 37px;
}

.brandmark-wordmark svg {
  display: block;
  width: 100%;
  height: 100%;
}

.brandmark-rule {
  width: 40px;
  height: var(--rule);
  background: var(--gold);
}

.brandmark-tag {
  font-size: var(--step--1);
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--gold-light);
  font-weight: 300;
}

/* Session 06j: the Beta disclaimer chip — Michele's instruction, 18 Sep
   2026: "Beta = version, not for distribution nor for legal
   responsibility." A visual marker with the full statement in its
   tooltip, not a sentence on the topbar. DRAFT wording, his to correct. */
.brandmark-beta {
  position: absolute;
  top: 0;
  right: -44px;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--gold-light);
  border: var(--rule) solid var(--gold-dark);
  border-radius: var(--radius);
  padding: 1px 5px;
  cursor: help;
}

/* Branding modes. Session 05 owns the full three-mode export logic; the
   markup must never hard-code the MMeoni Consulting mark where it cannot
   be hidden.
   Selector updated Session 06j — .brandmark-name became .brandmark-wordmark
   when the topbar mark moved from text to the inline SVG. Still dormant:
   nothing in app.js sets data-brand today. */
:root[data-brand="neutral"] .brandmark-wordmark,
:root[data-brand="client"] .brandmark-wordmark {
  opacity: 0.8;
}

:root[data-brand="neutral"] .brandmark-tag,
:root[data-brand="client"] .brandmark-tag {
  display: none;
}

.topbar-status {
  display: flex;
  align-items: center;
  gap: var(--s5);
}

.save-indicator {
  display: inline-flex;
  align-items: center;
  font-size: var(--step--1);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--gold-light);
  font-weight: 500;
  min-height: 1em;
  opacity: 0;
  transition: opacity var(--motion-slow);
}

.save-indicator[data-status="saved"],
.save-indicator[data-status="failed"] {
  opacity: 1;
  color: var(--save-failed-ink);
}

.mode-indicator {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--step--1);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--gold-light);
  font-weight: 500;
}

.mode-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--gold);
}

.mode-indicator[data-mode="file"] .mode-dot {
  background: var(--gold-dark);
}

/* =========================================================================
   Session 05e — Workspace. Direction B, "The Instrument" (spec/07): the
   four-step wizard is replaced by one continuous workspace — an index rail,
   a dense work area, and a verdict rail that never leaves the screen. At
   phone widths (<768px) the same DOM becomes Direction C's arrangement: the
   index rail collapses to a horizontal strip, panels read as cards, and the
   verdict rail becomes a bottom-anchored, thumb-reachable bar. One app, two
   arrangements of the same components — never a narrowed desktop layout.
   768px is the measured handover between them (spec/07, "not decided here").
   ========================================================================= */

.workspace {
  display: block;
}

/* Session 05f — panels had no visual separation at desktop/tablet width;
   only the phone card treatment further down bounded them. "Meshed up" was
   Michele's word for it. A divider at every width, independent of the
   phone-only card styling layered on top of this below 768px. */
.ws-panel {
  padding-bottom: var(--s7);
  margin-bottom: var(--s7);
  border-bottom: var(--rule) solid var(--line);
}

.ws-panel:last-child {
  border-bottom: 0;
}

.index-rail-inner {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}

.index-rail-item {
  appearance: none;
  background: none;
  border: 0;
  border-left: 2px solid transparent;
  text-align: left;
  padding: var(--s2) var(--s4);
  color: var(--muted);
  font-size: var(--step--1);
  letter-spacing: 1px;
  text-transform: uppercase;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--motion), border-color var(--motion);
}

.index-rail-item:hover {
  color: var(--ink);
}

.index-rail-item[aria-current="true"] {
  color: var(--ink);
  border-left-color: var(--gold);
}

@media (min-width: 768px) {
  .workspace {
    display: grid;
    grid-template-columns: 180px minmax(0, 1fr) 300px;
    column-gap: var(--s7);
    align-items: start;
    width: min(1400px, calc(100vw - var(--s6)));
    margin: 0 auto;
    padding: var(--s7) 0 var(--s9);
  }

  .index-rail {
    position: sticky;
    top: var(--s6);
    max-height: calc(100vh - var(--s7) * 2);
    overflow-y: auto;
  }

  #verdict-rail {
    position: sticky;
    top: var(--s6);
    max-height: calc(100vh - var(--s7) * 2);
    overflow-y: auto;
  }
}

@media (min-width: 768px) and (max-width: 1180px) {
  /* Tablet: the workspace still holds (spec/07), but the index rail
     collapses to a horizontal strip above the content so the two side
     columns don't crowd out the work area. */
  .workspace {
    grid-template-columns: minmax(0, 1fr) 280px;
    grid-template-areas: "index index" "content verdict";
  }
  .index-rail {
    grid-area: index;
    position: static;
    max-height: none;
    border-bottom: var(--rule) solid var(--line);
    padding-bottom: var(--s4);
    margin-bottom: var(--s4);
  }
  .index-rail-inner {
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--s5);
  }
  .index-rail-item {
    border-left: 0;
    border-bottom: 2px solid transparent;
    padding: var(--s2) var(--s1);
  }
  .index-rail-item[aria-current="true"] {
    border-left-color: transparent;
    border-bottom-color: var(--gold);
  }
  .workspace-content { grid-area: content; }
  .verdict-rail { grid-area: verdict; }
}

/* --- Phone (<768px): Direction C, adopted wholesale --------------------- */
@media (max-width: 767px) {
  /* spec/05 section 7: 44px minimum touch target, verified rather than
     assumed. Every interactive control that fell short at phone width. */
  .btn,
  .index-rail-item,
  .warning-dismiss,
  .inclusion-add,
  #btn-log {
    min-height: 44px;
  }

  /* Session 06p — the 390px Exchange Rates overlap (open since 06n-b).
     The rail held BOTH the nav strip and #fx-panel (moved there for
     desktop, 05n), and the whole rail was sticky — so a ~300px Exchange
     Rates card rode pinned over Market Context as the content scrolled.
     The rail dissolves at phone width (`display: contents` — same DOM,
     Direction C's own arrangement): only the STRIP stays sticky, carrying
     the background/border the rail used to; the fx panel becomes a normal
     card at the END of the stack (flex `order` on the workspace), where a
     rarely-consulted panel belongs on a phone — it scrolls, never rides. */
  .workspace {
    display: flex;
    flex-direction: column;
  }
  .index-rail {
    display: contents;
  }
  .index-rail-inner {
    order: 0;
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--surface);
    border-bottom: var(--rule) solid var(--line);
    flex-direction: row;
    gap: var(--s5);
    padding: var(--s3) var(--s4);
    overflow-x: auto;
    scrollbar-width: thin;
  }
  .workspace-content { order: 1; }
  #fx-panel {
    order: 2;
    padding: 0 var(--s4) var(--s5);
  }
  .index-rail-item {
    border-left: 0;
    border-bottom: 2px solid transparent;
    padding: var(--s2) var(--s1);
  }
  .index-rail-item[aria-current="true"] {
    border-left-color: transparent;
    border-bottom-color: var(--gold);
  }

  .workspace-content {
    padding: var(--s5) var(--s4) calc(var(--s9) + 96px);
  }

  /* One question group per card (Direction C), not the desktop workspace
     narrowed — its own arrangement of the same panels. */
  .ws-panel {
    background: var(--surface);
    border: var(--rule) solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-soft);
    padding: var(--s5);
    margin-bottom: var(--s5);
  }

  /* The verdict rail becomes the thumb-zone bar: fixed to the bottom,
     collapsed to its headline by default, expandable by tapping it. */
  #verdict-rail {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 20;
    max-height: 40vh;
    overflow-y: auto;
    border-radius: 0;
    margin-bottom: 0;
  }
  .verdict-rail-body {
    max-height: 0;
    overflow: hidden;
    transition: max-height var(--motion-slow);
  }
  .verdict-rail[data-expanded="true"] .verdict-rail-body {
    max-height: 60vh;
    overflow-y: auto;
  }
}

/* =========================================================================
   Layout
   ========================================================================= */

main {
  flex: 1 0 auto;
  padding: var(--s7) 0 var(--s9);
}

.screen {
  animation: screen-in var(--motion-slow) both;
}

@keyframes screen-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.screen-head {
  margin-bottom: var(--s6);
}

/* Session 05k-b, Task 7.1 — reachable from every panel, not just the last. */
.screen-head-home {
  margin-bottom: var(--s3);
  padding-left: 0;
}

.eyebrow {
  font-size: var(--step--1);
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--gold-dark);
  font-weight: 600;
  margin: 0 0 var(--s2);
}

h1.screen-title {
  font-family: var(--font-display);
  font-size: var(--step-3);
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  margin: 0 0 var(--s3);
  line-height: 1.15;
}

.screen-lede {
  max-width: var(--measure);
  color: var(--muted);
  margin: 0;
  font-weight: 300;
}

/* Section title with the thin gold rule beneath — brand signature. */
.section {
  margin-bottom: var(--s7);
}

.section-title {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: 600;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  margin: 0 0 var(--s3);
  padding-bottom: var(--s3);
  border-bottom: var(--rule) solid var(--line);
  position: relative;
}

.section-title::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 48px;
  height: var(--rule);
  background: var(--gold);
}

/* Session 06e, Task 1 — the landing screen's "Saved Scenarios" head, with
   Import/New Scenario moved up beside it (Michele: reachable without
   scrolling past a fifty-row list). .section-title keeps its own gold
   underline (::after, above); this row just gives it a button-row partner
   on the same line, wrapping to two lines rather than the actions-bar's
   own layout below 768px. */
.section-title-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s3);
}

.section-title-row .section-title {
  flex: 1 1 auto;
  margin-bottom: 0;
}

.section-title-row .btn-row {
  margin-bottom: var(--s3);
}

@media (max-width: 768px) {
  .section-title-row {
    flex-direction: column;
    align-items: stretch;
  }

  .section-title-row .btn-row {
    justify-content: stretch;
  }

  .section-title-row .btn {
    flex: 1 1 auto;
  }
}

/* Session 06i, VR-PERSIST-16. The Active/Closed/Deleted selector, placed
   under New Scenario per Michele's ask — a plain .btn-row so it wraps and
   stretches the same way every other one on this screen already does at
   narrow widths (no separate media-query block needed). The pressed state
   uses the gold underline treatment rather than a filled button, so the
   row reads as a filter, not as three more actions competing with Import/
   New Scenario above it. */
.landing-filter-row {
  margin-bottom: var(--s5);
}

.landing-filter-btn[aria-pressed="true"] {
  color: var(--gold-dark);
  border-color: var(--gold);
  font-weight: 500;
}

.section-note {
  color: var(--muted);
  font-size: var(--step--1);
  font-weight: 300;
  max-width: var(--measure);
  margin: 0 0 var(--s5);
}

/* =========================================================================
   Forms — single column always (NN/g, spec/05 section 7)
   ========================================================================= */

.form {
  display: flex;
  flex-direction: column;
  gap: var(--s5);
  max-width: var(--measure);
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}

/* Session 06t, VR-INPUT-05 — the smart Location field's suggestion
   dropdown anchors to this wrapper, not the page. */
.location-field-wrap {
  position: relative;
}

.location-suggest-list {
  position: absolute;
  z-index: 5;
  /* Explicit top, not left to the browser's "static position" fallback —
     that placed it overlapping the LABEL instead of below the field,
     caught live in the 06t device-mode pass. Anchored to the wrap's own
     bottom edge (which DOES include the hint line in its height, since
     this list is itself position:absolute and contributes none) — always
     below every normal-flow line in the field, never above or overlapping
     any of them. */
  top: 100%;
  left: 0;
  right: 0;
  margin: 2px 0 0;
  padding: 0;
  list-style: none;
  background: var(--field-bg);
  border: var(--rule) solid var(--field-border);
  border-radius: var(--radius);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
  max-height: 14rem;
  overflow-y: auto;
}

@media (min-width: 768px) {
  .location-suggest-list {
    left: 12rem;
  }
}

.location-suggest-item {
  padding: var(--s3);
  font-size: var(--step-0);
  cursor: pointer;
}

.location-suggest-item:hover,
.location-suggest-item:focus {
  background: var(--surface-muted);
}

.location-country-prompt {
  margin-top: var(--s3);
}

.location-country-prompt-text {
  margin: 0 0 var(--s2);
}

/* Session 06y, VR-INPUT-05. What the field stored when it differed from what
   the reader typed ("napoli, italy" accepted as "Naples, Italy"). A thin gold
   rule, NOT the open-item orange and NOT a validation warning: nothing here
   is wrong or outstanding — the app tidied something and is saying so. The
   two registers must stay distinguishable at a glance, which is the same
   reasoning that kept VR-BENCH-09's chip off a correctly sourced figure. */
.location-corrected-note {
  margin: var(--s2) 0 0;
  padding-left: var(--s3);
  border-left: 2px solid var(--gold);
}

@media (min-width: 768px) {
  /* Sits under the control, not under the label column — the same offset
     .location-suggest-list uses, so the two line up with the field. */
  .location-corrected-note {
    margin-left: 12rem;
  }
}

/* Session 05e, Direction B's field primitive: label column (label + an
   optional provenance sub-line) beside a control column, dense enough for
   the max: (spec/07). Phone keeps the stacked single-column form. */
@media (min-width: 768px) {
  .field {
    display: grid;
    grid-template-columns: 12rem minmax(0, 1fr);
    column-gap: var(--s5);
    row-gap: var(--s1);
    align-items: start;
  }
  .field-label-col {
    padding-top: var(--s3);
  }
  /* Session 05k-b. A field's converted-line and +Rate Composition
     disclosure are appended as a THIRD/FOURTH child of .field, after
     .field-label-col and .field-control-col — grid auto-placement without
     this rule drops a child with no other override into the 12rem label
     column. .inclusion has its own dedicated width rule below (Task 7.2 —
     it needs to SPAN, not just avoid the label column); this general rule
     is what keeps the FX converted-line correctly placed. */
  .field > *:nth-child(n + 3) {
    grid-column: 1 / -1;
  }
}

.field-label {
  font-size: var(--step--1);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--ink-soft);
}

.field-sublabel {
  margin: var(--s1) 0 0;
  display: flex;
  align-items: center;
  gap: var(--s2);
  /* Session 06w — the age line (VR-BENCH-08) joins the badge here and is a
     sentence, not a chip. Without wrapping it becomes a flex item squeezed
     to min-content in a 192px label column and sets one word per line. */
  flex-wrap: wrap;
}

.field-required {
  color: var(--gold-dark);
}

.field-computed .field-computed-value {
  padding-top: var(--s3);
}

/* Prefix / suffix adornments sit inside the field frame. */
.field-control {
  display: flex;
  align-items: stretch;
  background: var(--field-bg);
  border: var(--rule) solid var(--field-border);
  border-radius: var(--radius);
  transition: border-color var(--motion), box-shadow var(--motion);
  max-width: 100%;
}

.field-control:focus-within {
  border-color: var(--gold-dark);
  box-shadow: 0 0 0 3px var(--surface-muted);
}

.field-affix {
  display: flex;
  align-items: center;
  padding: 0 var(--s3);
  background: var(--surface-muted);
  color: var(--muted);
  font-size: var(--step--1);
  font-weight: 500;
  letter-spacing: 0.4px;
  white-space: nowrap;
  user-select: none;
}

.field-affix-prefix {
  border-right: var(--rule) solid var(--field-border);
}

/* =========================================================================
   Per-line-item currency — VR-FX-01 (Session 07)

   The affix that used to hold a static glyph now holds the control. Session
   05e owns the redesign of this; here it inherits the affix's own metrics so
   it sits in the frame exactly where the glyph did.
   ========================================================================= */

.field-affix-currency {
  padding: 0;
}

.currency-chip {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-variant-numeric: tabular-nums;
  padding: 0 var(--s3);
  height: 100%;
  cursor: pointer;
}

.currency-chip:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: -2px;
}

/* A line not in the project's reporting currency is flagged before its
   number is read (spec/07). Colour is not the only signal — the code itself
   changes, which is the actual information. */
.field-affix-currency.is-foreign {
  background: var(--gold-light);
}

.field-affix-currency.is-foreign .currency-chip {
  color: var(--gold-dark);
  font-weight: 600;
}

/* The converted line: figure, rate and as-of date together, directly under
   the field, so a rate is never further away than the number it produced. */
.fx-converted {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-variant-numeric: tabular-nums;
  color: var(--gold-dark);
  margin-top: var(--s2);
}

.fx-unpriced {
  margin-top: var(--s2);
}

/* --- The rate panel on Step 1 ------------------------------------------- */

.fx-panel {
  border: var(--rule) solid var(--line);
  border-left: 3px solid var(--gold);
  border-radius: var(--radius);
  background: var(--surface);
  padding: var(--s5);
  margin-top: var(--s5);
  max-width: var(--measure);
}

.fx-panel-title {
  font-size: var(--step--1);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  font-weight: 600;
  margin: 0 0 var(--s3);
}

.fx-status {
  font-size: var(--step--1);
  margin: 0 0 var(--s3);
  font-weight: 500;
}

.fx-status-ok { color: var(--pass); }
.fx-status-stale { color: var(--strategic); }
.fx-status-missing { color: var(--fail); }

.fx-rates {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--s2) var(--s4);
  margin: 0 0 var(--s4);
  font-size: var(--step--1);
}

.fx-rates dt {
  font-weight: 600;
  color: var(--ink-soft);
}

.fx-rates dd {
  margin: 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.fx-rates .fx-none {
  color: var(--fail);
  font-family: var(--font-body);
}

.fx-manual {
  border-top: var(--rule) solid var(--line);
  margin-top: var(--s4);
  padding-top: var(--s4);
  display: flex;
  flex-direction: column;
  gap: var(--s4);
}

.fx-manual-lead {
  font-size: var(--step--1);
  color: var(--muted);
  margin: 0;
}

.fx-manual-field {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}

/* The override confirmation. The rule requires an explicit act, so this is a
   real checkbox with a real label, not an implicit acceptance. */
.fx-manual-confirm {
  display: flex;
  gap: var(--s3);
  align-items: flex-start;
  font-size: var(--step--1);
  line-height: 1.5;
  color: var(--ink-soft);
  cursor: pointer;
}

.fx-manual-confirm input {
  margin-top: 3px;
  flex: 0 0 auto;
}

.field-affix-suffix {
  border-left: var(--rule) solid var(--field-border);
}

/* Session 05e / Direction C — 56px steppers on exit cap, ADR and occupancy
   only (spec/07): "a figure can be nudged and the verdict watched without a
   keyboard ever opening" — a phone affordance, not a desktop one. Session
   05f: hidden by default and shown only inside the phone media query below.
   05e's own comment claimed they were "quiet on desktop", but at a fixed
   72px (two 36px buttons) inside a 144px control they crushed the input
   down to 33px and clipped the value being typed — measured, not assumed. */
.field-stepper {
  display: none;
  border-left: var(--rule) solid var(--field-border);
}

.field-stepper-btn {
  appearance: none;
  border: 0;
  background: var(--surface-muted);
  color: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: var(--step-0);
  font-weight: 600;
  width: 2.25rem;
  cursor: pointer;
  transition: background-color var(--motion);
}

.field-stepper-btn:hover {
  background: var(--gold-light);
}

.field-stepper-btn + .field-stepper-btn {
  border-left: var(--rule) solid var(--field-border);
}

@media (max-width: 767px) {
  .field-stepper {
    display: flex;
  }
  .field-stepper-btn {
    width: 56px;
    height: 56px;
    font-size: var(--step-2);
  }
}

.field-input,
.field-select,
.field-textarea {
  flex: 1 1 auto;
  min-width: 0;
  background: transparent;
  border: 0;
  padding: var(--s3);
  color: var(--field-ink);
  font-size: var(--step-0);
}

/* Session 05f, Michele's standing alignment rule: numbers right-aligned,
   single-line text left (the default above), multi-line justified.
   `inputmode="decimal"` is the existing hook `buildField()` already sets on
   every numeric field (spec/05 section 3), so this needs no JS change. */
.field-input[inputmode="decimal"] {
  text-align: right;
  /* VR-DOC-07, Session 07c (amended) — a defensive floor so a numeric
     input can never collapse below a legible width, whatever frame it
     sits in. The frame-level fix below (chip-bearing w-sm) is the actual
     cause; this is the input's own guard against the same failure mode
     recurring somewhere neither of us has looked yet. */
  min-width: 3.5ch;
}

.field-input:focus,
.field-select:focus,
.field-textarea:focus {
  outline: none;
}

.field-textarea {
  font-family: var(--font-body);
  resize: vertical;
  min-height: 5.5rem;
  line-height: 1.6;
  text-align: justify;
}

.field-select {
  appearance: none;
  cursor: pointer;
  font-family: var(--font-body);
  background: transparent;
}

.field-control-select {
  position: relative;
}

.field-control-select::after {
  content: "";
  position: absolute;
  right: var(--s4);
  top: 50%;
  width: 7px;
  height: 7px;
  border-right: var(--rule) solid var(--muted);
  border-bottom: var(--rule) solid var(--muted);
  transform: translateY(-70%) rotate(45deg);
  pointer-events: none;
}

.field-select {
  padding-right: var(--s7);
}

/* Field widths sized to content, not the container (spec/05 section 3). */
.w-sm {
  width: 9rem;
}
.w-md {
  width: 15rem;
}
.w-lg {
  width: 22rem;
}
.w-full {
  width: 100%;
}

/* VR-DOC-07, Session 07c (amended) — a chip in the affix slot (duration
   or currency) competes with .field-input for a FIXED frame width, and
   .field-input's own min-width: 0 (above) lets it lose that fight and
   collapse to a sliver rather than clip the chip: Michele's Hold Period
   showed one digit of "12.5" inside w-sm's 9rem frame for exactly this
   reason. Spec/05 section 3 says a field's width is sized to its
   content — a hard `width` cannot honour that once the content varies by
   which unit the chip has selected, so a chip-bearing w-sm frame is
   widened to a value verified (in-browser, across the widths this
   project checks) to hold both the widest chip option ("months") and a
   multi-digit decimal value without clipping either. Every currency-chip
   field already sits on w-md or w-lg (never w-sm) and is untouched; this
   rule exists so a future one that lands on w-sm is protected too,
   rather than the fix being specific to the one field that surfaced it. */
.w-sm:has(.field-affix-duration),
.w-sm:has(.field-affix-currency) {
  width: 12.5rem;
}

.field-hint {
  font-size: var(--step--1);
  color: var(--muted);
  font-weight: 300;
}

/* Session 06t, findings 3+8/answer 5 — the pre-run cost notice, directly
   below the key/model choice: two short lines read as one notice (tight
   spacing between them), the full floor-estimate sentence beneath, quieter
   still, since it is detail a reader consults rather than the headline. */
.research-cost-notice {
  margin: var(--s3) 0 var(--s4);
}

.research-cost-notice-line {
  margin: 0;
}

.research-cost-notice-line + .research-cost-notice-line {
  margin-top: 2px;
}

/* VR-EXPORT-08, Session 06y — the evaluation copy saying, plainly, that it
   does not run live research. Deliberately NOT the warning register: nothing
   here is wrong or outstanding, and an orange box would read as a fault in
   the app rather than a decision about the build. A quiet bordered note in
   the ordinary surface colours, with the first line carrying the weight. */
.research-off-notice {
  margin: 0 0 var(--s4);
  padding: var(--s3) var(--s4);
  background: var(--surface-muted);
  border-left: 3px solid var(--gold);
  border-radius: var(--radius);
}

.research-off-title {
  margin: 0 0 var(--s2);
  font-weight: 600;
}

.research-cost-notice-detail {
  margin-top: var(--s2);
  font-size: var(--step--1);
  opacity: 0.85;
}

/* Session 06t, finding 1 — "Change model" sits beside the field it acts
   on, mirroring "Change Key"'s own spacing next to the key summary. */
.research-model-picker {
  margin-bottom: var(--s3);
}

.research-model-change {
  margin-top: var(--s2);
}

/* Session 05c, defect 5 — a research call can take minutes; static text
   reading "Running…" is indistinguishable from a frozen app. The state
   change (running / done / failed) is the information and is carried by
   text and colour alone, so it survives with the animation off. The
   animation itself is decoration, gated by the project's existing global
   prefers-reduced-motion rule (styles.css, near the top), which forces
   every animation to one 1ms frame — no separate media query needed here. */
.research-status-running {
  display: inline-block;
  color: var(--gold-dark);
  font-weight: 500;
  animation: research-breathe 1.6s ease-in-out infinite;
}

@keyframes research-breathe {
  0%, 100% { opacity: 0.7; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.05); }
}

.research-status-done {
  color: var(--pass);
  font-weight: 500;
}

.research-status-failed {
  color: var(--fail);
  font-weight: 500;
}

/* Errors: adjacent to the field, constructive, never blocking. */
.field-error {
  font-size: var(--step--1);
  color: var(--fail);
  font-weight: 500;
  display: flex;
  gap: var(--s2);
  align-items: flex-start;
}

.field-error::before {
  content: "!";
  font-family: var(--font-mono);
  font-weight: 700;
  flex: 0 0 auto;
}

.field-control.is-invalid {
  border-color: var(--fail);
}

/* Session 05k-e, Task 9, Test 7 note 4 — an impossible computed geometry
   (Green Space) reads distinctly from the ordinary em dash of no input at
   all. Same red as .field-error, applied to a .computed span rather than
   a field-level error line. */
.computed.field-error-text {
  color: var(--fail);
}

.computed.field-error-text::before {
  color: var(--fail);
}

/* Advisory plausibility warning — dismissible, never blocks (spec/05 s.3). */
.field-warning {
  font-size: var(--step--1);
  color: var(--strategic);
  font-weight: 500;
  display: flex;
  gap: var(--s2);
  align-items: flex-start;
}

.field-warning::before {
  content: "?";
  font-family: var(--font-mono);
  font-weight: 700;
}

.warning-dismiss {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 1px solid currentColor;
  color: inherit;
  font-size: inherit;
  font-weight: 600;
  padding: 0;
  cursor: pointer;
  flex: 0 0 auto;
  margin-left: auto;
}

.warning-dismiss:hover {
  opacity: 0.7;
}

/* =========================================================================
   Computed values — NEVER an <input> (spec/05 section 4)
   ========================================================================= */

/* Session 05e — cost per m² and the built ratio, read-only rows in the
   Property & Cost panel. */
.computed-row-group {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  margin-top: var(--s5);
  margin-bottom: var(--s5);
  padding-top: var(--s5);
  border-top: var(--rule) solid var(--line);
  max-width: var(--measure);
}

/* Session 06t, finding 9 — Michele's standing alignment rule (numbers
   right, single-line text left) applied to the Property & Cost panel's
   computed readouts: Cost per m², Built Ratio, Construction Cost per m²,
   Total Development Cost and the pre-opening/costs-of-sale figures beside
   them were rendering left-aligned like ordinary prose. */
.computed-row-group .field-computed-value {
  text-align: right;
}

/* Session 06u, Cowork's finding 10 — Michele's answer: the standing rule
   (text left, numbers right, percentages centred) holds inside a KPI
   block too, not only in a table. Green space "0%" centres; the ratio
   "4.75×" beside it stays right with the other numbers. Tagged by
   buildComputedField() off the rendered value itself. */
.computed-row-group .field-computed-value-pct {
  text-align: center;
}

.computed {
  display: inline-flex;
  align-items: baseline;
  gap: var(--s2);
  color: var(--ink);
  cursor: default;
  border: 0;
  background: none;
  padding: 0;
}

.computed::before {
  content: "\0192"; /* ƒ — marks a derived figure, reveals arithmetic on hover */
  font-family: var(--font-display);
  font-size: 0.85em;
  color: var(--gold-dark);
  font-style: italic;
}

.computed[data-placeholder="true"] {
  color: var(--muted);
}

/* Session 05k-e — the two-way area schedule (Sellable / BOH / Circulation).
   Both sides of a row are real inputs (spec/05 section 4's "never an
   input" rule bends deliberately here, per Michele's own "can override"
   instruction) — the derived side is marked, not disabled. */
.area-row {
  margin-bottom: var(--s4);
}

.area-row-title {
  font-size: var(--step--1);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--ink-soft);
  margin: 0 0 var(--s2);
}

.area-row-grid {
  display: flex;
  gap: var(--s3);
  flex-wrap: wrap;
}

.area-row-field {
  display: flex;
  flex-direction: column;
  gap: var(--s1);
  flex: 1 1 140px;
}

.area-row-field-label {
  font-size: var(--step--2);
  color: var(--muted);
}

.area-row-field-control {
  max-width: 100%;
}

/* The derived side — shown, always overridable, visually quieter than the
   side the user actually typed, per the ƒ-marker convention above. */
.area-row-field-derived .area-row-field-label::after {
  content: " · derived";
  font-style: italic;
}

.area-row-field-derived .field-input {
  color: var(--muted);
}

.area-row-field-derived .field-input:focus {
  color: var(--ink);
}

.area-row-field-tbv .field-input {
  color: var(--strategic);
}

.area-row-field-tbv .field-input::placeholder {
  color: var(--strategic);
  opacity: 0.75;
}

/* Session 05k-d — the cross-unit area check's open item (VR-UNIT-10/-11),
   silent with no building reference on record but stated forward, not
   invisible. */
.unit-area-note {
  font-size: var(--step--1);
  color: var(--muted);
  margin-top: var(--s4);
}

.unit-area-note-tbv {
  color: var(--strategic);
}

/* Session 05k-e, VR-METH-12 — the one-time notice on reopening a scenario
   that predates this session, the first time a Brand Commission is
   entered. Orange (the "To be verified and confirmed" register, CLAUDE.md,
   9 Sep), dismissible, never blocking. */
.cost-of-sale-notice {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  flex-wrap: wrap;
  background: var(--strategic-soft);
  border: var(--rule) solid var(--strategic);
  border-radius: var(--radius);
  padding: var(--s3) var(--s4);
  margin: 0 0 var(--s4);
}

.cost-of-sale-notice-text {
  color: var(--strategic);
  font-size: var(--step--1);
  margin: 0;
  flex: 1 1 auto;
}

/* Unsourced figures are badged, never blended in (spec/05 section 5). */
.badge {
  display: inline-block;
  font-family: var(--font-body);
  font-size: 0.6875rem;
  letter-spacing: 1px;
  text-transform: uppercase;
  font-weight: 600;
  padding: 2px var(--s2);
  border-radius: var(--radius);
  border: var(--rule) solid var(--line-strong);
  color: var(--muted);
  background: var(--surface-muted);
  white-space: nowrap;
}

.badge-estimate {
  border-color: var(--strategic);
  color: var(--strategic);
  background: var(--strategic-soft);
}

.badge-pending {
  border-style: dashed;
}

/* A field still linked to a benchmark preset — gone the instant it's edited. */
.badge-benchmark {
  border-color: var(--gold-dark);
  color: var(--gold-dark);
  background: var(--gold-light);
  cursor: help;
}

/* =========================================================================
   Buttons
   ========================================================================= */

.btn {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  padding: var(--s3) var(--s5);
  border-radius: var(--radius);
  border: var(--rule) solid transparent;
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  transition: background-color var(--motion), color var(--motion),
    border-color var(--motion), transform var(--motion);
}

.btn:active {
  transform: translateY(1px);
}

.btn-primary {
  background: var(--navy);
  color: var(--text-light);
  border-color: var(--navy);
}

.btn-primary:hover {
  background: var(--navy-secondary);
  border-color: var(--gold);
}

.btn-gold {
  background: var(--gold);
  color: var(--navy);
  border-color: var(--gold);
}

.btn-gold:hover {
  background: var(--gold-light);
}

.btn-ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--line-strong);
}

.btn-ghost:hover {
  border-color: var(--gold-dark);
  color: var(--gold-dark);
}

.btn-quiet {
  background: none;
  border-color: transparent;
  color: var(--muted);
  padding: var(--s2) var(--s3);
  letter-spacing: 1px;
}

.btn-quiet:hover {
  color: var(--ink);
}

.btn-danger {
  background: none;
  border-color: transparent;
  color: var(--fail);
  padding: var(--s2) var(--s3);
  letter-spacing: 1px;
}

.btn-danger:hover {
  background: var(--fail-soft);
}

.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

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

.btn-row-end {
  justify-content: flex-end;
}

.actions-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  align-items: center;
  justify-content: space-between;
  margin-top: var(--s7);
  padding-top: var(--s5);
  border-top: var(--rule) solid var(--line);
  /* Session 05k-b, Task 7.4 reserved room here for the status lines,
     which hung out of flow beneath the buttons. Session 06u, finding 19:
     they are in normal flow now (.actions-status below), inside this same
     bar, so the bar sizes itself around them and no reserved band is
     needed. Kept at a plain --s5 so the bar still has a bottom edge of
     its own rather than ending flush against the next section. */
  padding-bottom: var(--s5);
}

/* Session 05g, defect 4 — the Save Scenario version, directly under the
   button that produced it.
   Session 05k-b, Task 7.4 — made structural. .btn-row's align-items:center
   was centring this whole flex COLUMN against its single-line siblings
   (Start New / Export JSON / Export PDF), which put the button itself half
   the status line's height above them — reserving the status line's height
   (05i-b) stopped a CLICK shifting it but never fixed this baseline offset.
   Third report. Taking the status line out of flow with position:absolute
   means .btn-with-status's height is now just the button's height, so the
   flex parent centres it exactly like every sibling — regardless of what
   text the status line carries or whether it is empty.
   Session 06e, Task 2 — the same recentring defect, one layer up. D2
   (05n) put #copy-pdf-path IN normal flow below Export PDF (Michele:
   revealed on click, not always visible), so when renderPdfPathStatus()
   un-hides it, THIS wrapper itself grows taller — and .btn-row's
   align-items:center then recentres the wrapper against its single-line
   siblings (Start New / Export JSON), lifting the Export PDF button by
   half the copy button's height. align-self:flex-start pins every
   .btn-with-status wrapper to the row's top edge regardless of its own
   height, so Export PDF sits at the same pixel whether or not the copy
   button is shown. Harmless for the Save Scenario wrapper (its status
   lines are already position:absolute and never change its height). */
.btn-with-status {
  position: relative;
  align-self: flex-start;
}

/* Session 06i, Item 3 — 06e's align-self:flex-start above pins EVERY
   .btn-with-status wrapper to the row's top edge, but it does nothing for
   a PLAIN sibling button that is not wrapped in one — Start New and
   Export JSON are exactly that, and .btn-row's own align-items:center
   (above) still recentres THEM against the now-taller row the instant the
   copy-path button reveals, dropping both by half the height delta.
   Live-measured: 366px -> 389px, a ~23px drop, confirming this is real and
   not merely theoretical. Pinning the whole row to flex-start removes the
   "taller row recentres its shorter siblings" mechanism outright, for
   every child at once — harmless when every child is the same height (the
   pre-reveal case), which is when this rule makes no visible difference. */
.actions-bar .btn-row-end {
  align-items: flex-start;
}

/* D2, Session 05n — Michele: Copy Folder Path belongs BELOW Export PDF,
   never beside it. It is a normal-flow sibling of the Export PDF button
   inside .btn-with-status (a block <div>, default display), but a
   <button> is inline-block by default, so the two buttons rendered side
   by side once #pdf-path-status stopped being between them in flow (it
   is position:absolute, out of flow, and does not separate them). Forcing
   this one onto its own line is the fix — no change to Export PDF or the
   wrapper. */
.pdf-path-copy {
  display: block;
  margin-top: var(--s2);
}

/* Session 06i, Item 3, second half of the same defect. "Copy folder path"
   (revealed) is intrinsically wider than the collapsed "Export PDF"
   button, so the wrapper's own natural width grows the instant it
   reveals — live-measured: Save Scenario's left edge moved from 734px to
   762px, a ~28px rightward shift, purely from its PRECEDING sibling
   getting wider. Reserving the wrapper's width up front, whether or not
   the button is hidden, means the reveal changes visibility only, never
   layout size — nothing after it ever has to move again. 184px covers the
   measured 179px "Copy folder path" button plus a small margin; if that
   label's text ever changes, re-measure and adjust this number. */
.btn-with-status:has(#copy-pdf-path) {
  min-width: 184px;
}

/* Session 05i-b, defect (Results/6b): this line is empty until Save
   Scenario is clicked, then fills with "Saved as vN". Formerly reserved
   its height in flow; now positioned below the button so it cannot ever
   displace it (Task 7.4 above). */
.save-scenario-status {
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: var(--s2);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  line-height: 1.55;
  color: var(--muted);
  white-space: nowrap;
}

/* Session 06e, Task 2 — #pdf-path-status is the one .save-scenario-status
   line that carries a long, unbroken path (data/projects/<slug>/
   <slug>_screening_<date>_v1.pdf), not a short word like "Saved as v3".
   05k-b's position:absolute stopped it pushing the layout below it, but
   white-space:nowrap plus no max-width let a long real slug run leftward
   out of .btn-with-status and over the Exchange Rates panel next to it.
   Giving it its own width to wrap inside — right:0 keeps it pinned to the
   Export PDF button's trailing edge, so it grows leftward, wrapping,
   instead of overflowing past a right edge it doesn't have. break-word
   wraps a path with no spaces to break at; the filename itself is never
   truncated, only wrapped onto another line. */
#pdf-path-status {
  /* An explicit width, not max-width. .btn-with-status (the containing
     block for this position:absolute element, since it's the nearest
     positioned ancestor) is only as wide as the Export PDF button itself
     (~180px) — with left:auto and only max-width set, the CSS
     shrink-to-fit algorithm bounds "available width" to that 180px
     containing block BEFORE max-width ever gets a chance to apply, so
     the long path still had nothing to wrap into. An explicit width
     makes the box genuinely wider than its narrow positioned ancestor;
     right:0 still anchors its right edge to the button above it, so it
     grows leftward. */
  width: min(24rem, calc(100vw - var(--s6) * 2));
  /* pre-line: keeps the deliberate "\n" between "Point ... at:" and the
     path itself (app.js), while still wrapping normally within the width
     above — unlike nowrap's parent rule, which never breaks at all. */
  white-space: pre-line;
  overflow-wrap: break-word;
  word-break: break-word;
  text-align: right;
}

/* D1, Session 05n — Michele: the save-version marker and the disk-folder
   path were painted at the identical coordinate, both being independent
   .save-scenario-status lines (each position:absolute; top:100%; right:0).
   This wrapper carries that ONE absolute position; the two lines inside
   it revert to normal flow and stack, each still legible on its own row. */
.save-status-stack {
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: var(--s2);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--s1);
}

.save-status-stack .save-scenario-status {
  position: static;
  margin-top: 0;
}

/* Session 06u, Michele's finding 19 — the one status region for the whole
   actions bar, replacing the two separately-positioned ones that painted
   over each other (see the index.html comment for the measurements).
   flex-basis:100% puts it on its own line in the wrapping .actions-bar
   flex row, below every button rather than beside any of them. In NORMAL
   FLOW: the bar grows to fit, so a long sentence wraps instead of
   overlapping, and nothing after the bar is ever covered. */
.actions-status {
  flex-basis: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--s1);
  margin-top: var(--s3);
}

/* These three <p>s keep .save-scenario-status for their type styling, but
   inside this block they are ordinary flow content: no absolute position,
   no nowrap (VR-PERSIST-22's save outcome is a full sentence now), and a
   width they can actually wrap inside. Right-aligned under the buttons
   they describe — these are status lines, not figures, so this is
   position rather than the numeric alignment rule. */
.actions-status .save-scenario-status {
  position: static;
  margin-top: 0;
  max-width: min(46rem, 100%);
  white-space: normal;
  text-align: right;
  overflow-wrap: break-word;
}

/* #pdf-path-status carries a long unbroken path and keeps its deliberate
   line break (app.js writes a "\n" into it). Its own rule below sets an
   explicit width for the old absolute context; inside this block the
   max-width above governs, so only the wrapping behaviour is restated. */
.actions-status #pdf-path-status {
  width: auto;
  white-space: pre-line;
}

@media (max-width: 640px) {
  .actions-status {
    align-items: stretch;
  }

  .actions-status .save-scenario-status {
    text-align: left;
    max-width: 100%;
  }
}

/* =========================================================================
   Tables — right-align figures, headers match content, no vertical rules
   ========================================================================= */

.table-wrap {
  overflow-x: auto;
  border: var(--rule) solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--step-0);
}

thead th {
  position: sticky;
  top: 0;
  background: var(--surface-muted);
  border-bottom: var(--rule) solid var(--line-strong);
  padding: var(--s3) var(--s4);
  text-align: left;
  font-size: var(--step--1);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--ink-soft);
  white-space: nowrap;
  z-index: 1;
}

thead th.num {
  text-align: right;
}

tbody td {
  padding: var(--s4);
  border-bottom: var(--rule) solid var(--line);
  vertical-align: baseline;
}

tbody tr:last-child td {
  border-bottom: 0;
}

tbody tr:hover {
  background: var(--surface-muted);
}

td.num {
  text-align: right;
  white-space: nowrap;
  /* Session 05k-e, Task 9, Test 7 note 11 — Michele: "make it Excellent in
     look." Tabular numerals were the concrete, fixable half of "hard to
     read": proportional digits meant a column of money figures never
     lined up vertically row to row, the single biggest readability tax on
     a numbers table. Same font already used for every other figure in
     this app (KPI strip, inclusion amounts). */
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* CLAUDE.md standing instruction, 9 Sep: text left, numbers right,
   percentages centred. Doc exports already had this (.doc-table th.num.pct
   etc.); the live screen did not — Session 05k's sensitivity grid is the
   first on-screen table with a percentage column. */
td.num.pct,
th.num.pct {
  text-align: center;
}

/* Session 05k — the exit cap x ADR sensitivity grid. Shading pairs colour
   with a glyph (spec/05 section 6: "never colour alone"); the base cell is
   boxed, never merely highlighted, so it reads correctly in mono print
   too. */
.sens-cell-pass {
  background: var(--pass-soft);
  color: var(--pass);
}
.sens-cell-fail {
  background: var(--fail-soft);
  color: var(--fail);
}
.sens-cell-base {
  outline: 2px solid var(--gold);
  outline-offset: -2px;
}
.sens-legend {
  display: flex;
  gap: var(--s5);
  margin-top: var(--s3);
  font-size: var(--step--1);
  color: var(--muted);
}
.sens-breakeven {
  margin-top: var(--s3);
  font-size: var(--step-0);
}

/* Perspective group header. Totals are struck within a group, never across
   groups — there is deliberately no grand total row. */
td.row-group {
  background: var(--surface-sunken);
  font-size: var(--step--1);
  letter-spacing: 1.4px;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--gold-dark);
  padding: var(--s3) var(--s4);
}

tbody tr:hover td.row-group {
  background: var(--surface-sunken);
}

tbody tr.row-total td {
  border-top: var(--rule) solid var(--line-strong);
  font-weight: 600;
  background: var(--surface-muted);
}

.cell-primary {
  font-weight: 600;
}

/* Task 6b(d), Session 05k-f — a Results-table row opens the unit for
   editing. Underline only on hover/focus, not permanently, so the table
   keeps its plain-text density; still a real, keyboard-reachable <a>. */
.cell-primary-link {
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

.cell-primary-link:hover,
.cell-primary-link:focus-visible {
  text-decoration: underline;
  color: var(--gold-dark);
}

.cell-primary-link:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.cell-sub {
  display: block;
  font-size: var(--step--1);
  color: var(--muted);
  font-weight: 300;
}

/* Session 05h, VR-METH-07 — an exit value cell stacks the primary
   (GOP-capitalised or sale-proceeds) figure with its method caption, then
   one line per multiple-derived cross-check, each also naming its method.
   Never rendered as a single blended number. */
.exit-value-stack {
  display: flex;
  flex-direction: column;
  gap: var(--s1);
  align-items: flex-end;
}

.exit-value-primary {
  display: block;
}

.exit-value-alt {
  display: block;
  padding-top: var(--s1);
  border-top: 1px dashed var(--gold-light);
}

.exit-value-alt .cell-sub {
  display: inline;
}

.table-actions {
  display: flex;
  gap: var(--s1);
  justify-content: flex-end;
}

/* Empty state */
.empty {
  border: var(--rule) dashed var(--line-strong);
  border-radius: var(--radius);
  padding: var(--s7) var(--s5);
  text-align: center;
  color: var(--muted);
}

.empty-title {
  font-family: var(--font-display);
  font-size: var(--step-1);
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--ink);
  margin: 0 0 var(--s2);
}

.empty p {
  margin: 0 auto var(--s5);
  max-width: 30rem;
  font-weight: 300;
}

/* Inline confirm-in-place — replaces confirm(), which is banned. */
.confirm-inline {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--step--1);
  color: var(--fail);
  font-weight: 500;
}

/* =========================================================================
   Business unit blocks — rendered from state.units[]
   ========================================================================= */

.unit-list {
  display: flex;
  flex-direction: column;
  gap: var(--s5);
}

/* Session 05i-b — the same visual vocabulary as td.row-group in the Results
   table, so grouping units by perspective on the input screen and on
   Results reads as one pattern rather than two. */
.unit-group-header {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s3);
  background: var(--surface-sunken);
  border: var(--rule) solid var(--line);
  border-radius: var(--radius);
  font-size: var(--step--1);
  letter-spacing: 1.4px;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--gold-dark);
  padding: var(--s3) var(--s4);
}

.unit-group-heading-text {
  flex: 0 0 auto;
}

/* Task 6b(a), Session 05k-f — Michele, 13 Sep: the perspective heading
   row is now the container, with its own "+ Add Business Unit" control
   on the right, so a unit is created directly into the right
   perspective, as the chosen type, in one action. */
.unit-group-add {
  margin-left: auto;
  flex: 0 0 auto;
}

.unit-group-add-select {
  font-size: var(--step--1);
  letter-spacing: normal;
  text-transform: none;
  font-weight: 500;
  font-family: var(--font-body);
  color: var(--ink);
  background: var(--surface);
  border: var(--rule) solid var(--field-border);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  cursor: pointer;
}

.unit {
  border: var(--rule) solid var(--line);
  border-top: 2px solid var(--gold);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-soft);
}

/* Session 05e — units collapse to a line row (Direction B): name,
   perspective and exit value on the summary, the fields inside. The whole
   header is the toggle target — a real 44px+ hit area, not just a chevron. */
.unit-head {
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s4) var(--s5);
  border-bottom: var(--rule) solid var(--line);
  background: var(--surface-muted);
  flex-wrap: wrap;
  cursor: pointer;
  min-height: 44px;
}

.unit[data-open="false"] .unit-head {
  border-bottom-color: transparent;
}

.unit-index {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--gold-dark);
  font-weight: 500;
}

.unit-summary {
  flex: 1 1 auto;
  font-size: var(--step--1);
  font-weight: 500;
  color: var(--ink-soft);
}

/* Task 6b(a), Session 05k-f — the only remaining way to change a unit's
   perspective, now that the form's own Perspective select is gone. Sits
   on the header row itself, stopPropagation'd in app.js so opening or
   using it never triggers the header's own collapse toggle. */
.unit-move {
  flex: 0 0 auto;
}

.unit-move-select {
  font-size: var(--step--1);
  font-family: var(--font-body);
  color: var(--ink-soft);
  background: var(--surface);
  border: var(--rule) solid var(--field-border);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  cursor: pointer;
}

.unit-chevron {
  width: 0.6rem;
  height: 0.6rem;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: rotate(45deg);
  transition: transform var(--motion);
  flex: 0 0 auto;
}

.unit[data-open="false"] .unit-chevron {
  transform: rotate(-45deg);
}

.unit-body {
  padding: var(--s5);
  display: flex;
  flex-direction: column;
  gap: var(--s5);
}

.unit-typeline {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s5);
}

.unit-blurb {
  font-size: var(--step--1);
  color: var(--muted);
  font-weight: 300;
  max-width: var(--measure);
  border-left: 2px solid var(--gold-light);
  padding-left: var(--s4);
  margin: 0;
}

.unit-fields {
  display: flex;
  flex-direction: column;
  gap: var(--s5);
}

/* VR-PERSIST-03 — a unit whose type is not recognised is kept and shown
   read-only, values exactly as saved, rather than dropped. */
.unit-raw-fields {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  background: var(--surface-2, var(--cream));
  border: 1px solid var(--border, var(--gold-light));
  border-radius: var(--radius-sm, 4px);
  padding: var(--s4);
  overflow-x: auto;
  white-space: pre;
  margin: 0;
}

/* Session 05e — one benchmark picker per target field (memory.md,
   2026-09-06: hand use overruled the one-shot picker Session 05c chose).
   The picker HOLDS its applied selection as the select's own value; the
   applied styling lives in the box itself via [data-applied="true"], not
   in a confirmation line beneath it — that line is gone. */
.benchmark-picker {
  padding: var(--s3) var(--s4);
  margin: calc(var(--s2) * -1) 0 0;
  border: var(--rule) dashed var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface-muted);
  transition: border-color var(--motion), background-color var(--motion);
}

@media (min-width: 768px) {
  .benchmark-picker {
    /* Aligned under the control column of the field it targets — the same
       12rem label width + gap the field grid itself uses (spec/07). */
    margin-left: calc(12rem + var(--s5));
  }
}

.benchmark-picker[data-applied="true"] {
  border-style: solid;
  border-color: var(--gold-dark);
  background: var(--gold-light);
}

.benchmark-picker-label {
  display: block;
  font-size: var(--step--1);
  letter-spacing: 1px;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--gold-dark);
  margin-bottom: var(--s2);
}

.benchmark-picker-select {
  font-family: var(--font-body);
}

/* VR-BENCH-07, Session 06r — an expired record stays visible, greyed, so a
   thinned picker explains itself rather than looking broken; the option's
   own text already carries "expired, refresh from research". */
.benchmark-picker-select option:disabled {
  color: var(--muted);
  font-style: italic;
}

.benchmark-picker[data-applied="true"] .benchmark-picker-select {
  color: var(--gold-dark);
  font-weight: 600;
}

.benchmark-picker-hint {
  margin: var(--s2) 0 0;
}

.fieldgroup-title {
  font-size: var(--step--1);
  letter-spacing: 1.6px;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--gold-dark);
  margin: var(--s3) 0 0;
  padding-bottom: var(--s2);
  border-bottom: var(--rule) solid var(--line);
}

/* Market research — reference context only, never a computed figure. */
.research-panel {
  margin-top: var(--s5);
  padding-top: var(--s5);
  border-top: var(--rule) solid var(--line);
  display: flex;
  flex-direction: column;
  gap: var(--s4);
}

.research-result {
  border: var(--rule) solid var(--line);
  border-left: 3px solid var(--gold-dark);
  border-radius: var(--radius);
  background: var(--surface);
  padding: var(--s4);
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}

/* Session 05j — the research body used to be one undifferentiated
   pre-wrap blob (.research-body, kept below as the parse-failure
   fallback); the normal path is now the structured render. */
.research-body {
  font-size: var(--step--1);
  color: var(--ink-soft);
  white-space: pre-wrap;
  max-height: 22rem;
  overflow-y: auto;
  font-family: var(--font-body);
  line-height: 1.6;
}

.research-benchmark-count {
  font-weight: 600;
  color: var(--ink);
}

.research-sections {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  max-height: 30rem;
  overflow-y: auto;
}

.research-section {
  padding-top: var(--s3);
  border-top: var(--rule) solid var(--line);
}

.research-section:first-child {
  padding-top: 0;
  border-top: 0;
}

.research-text {
  font-size: var(--step--1);
  color: var(--ink-soft);
  line-height: 1.6;
  margin: 0 0 var(--s3);
  white-space: pre-wrap;
}

.research-list {
  font-size: var(--step--1);
  color: var(--ink-soft);
  line-height: 1.6;
  margin: 0 0 var(--s3);
  padding-left: 1.2em;
}

.research-list li {
  margin-bottom: var(--s2);
}

/* Session 05j — the brief editor: shown in BOTH server and file:// modes
   (Task 5, interaction defect 1), replacing the old read-only <pre>. */
.research-brief-editor {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  align-items: flex-start;
}

.research-brief-textarea {
  width: 100%;
  min-height: 16rem;
  resize: vertical;
  background: var(--surface-sunken);
  border: var(--rule) solid var(--line);
  border-radius: var(--radius);
  padding: var(--s4);
  font-family: var(--font-mono);
  font-size: var(--step--1);
  line-height: 1.6;
  color: var(--ink);
}

.research-brief-textarea:disabled {
  color: var(--muted);
  cursor: not-allowed;
}

/* A comparable or a benchmark figure this app could not qualify — marked,
   never dropped (VR-RESEARCH-02, VR-RESEARCH-03). Reuses the --strategic
   amber token .field-warning already uses, so "To be verified and
   confirmed" reads as the same register everywhere it appears on screen. */
tr.row-incomplete td {
  color: var(--strategic);
  background: var(--strategic-soft);
}

.field-note-orange {
  font-size: var(--step--1);
  color: var(--strategic);
  margin: var(--s2) 0 0;
}

/* VR-BENCH-08 — a figure's age under its own date, inside the Source-date
   cell rather than in a column of its own: six columns already fill a
   768px table, and a seventh would push it into a horizontal scroll. */
.cell-age {
  display: block;
  font-size: var(--step--2);
  color: var(--muted);
  white-space: normal;
}

.cell-age-older {
  color: var(--strategic);
}

.cell-source {
  font-size: var(--step--2);
  max-width: 18rem;
  white-space: normal;
  color: var(--muted);
}

/* VR-BENCH-08, Session 06w — a figure's age, beside its badge on the
   provenance sub-line. Deliberately NOT a .badge: a badge is an uppercase
   fixed chip with `white-space: nowrap`, and this is a sentence that must
   be free to wrap at 390px rather than push its own field row sideways. */
.badge-age {
  /* Its own line under the badge, full width of the label column, free to
     wrap — never a flex item competing with the chip beside it. */
  flex-basis: 100%;
  font-family: var(--font-body);
  font-size: var(--step--2);
  line-height: 1.4;
  color: var(--muted);
}

/* Past twelve months it reads as older data — a distinction the reader can
   see without reading, in the same orange the app uses for an open item.
   Not a failure colour: an old figure with a real source is old, not
   suspect (CLAUDE.md, "unknowns are stated forward, not condemned"). */
.badge-age-older {
  color: var(--strategic);
}

/* VR-BENCH-09, Session 06x — the loud "older data" label, Michele's word,
   in the shape he approved in plan mode. A FILLED chip in the open-item
   orange register beside the provenance badge, with the dated age line
   (.badge-age above) reading underneath it: a word and a shape, never
   colour alone (spec/05 section 6).

   Reserved for a figure past the TWO-YEAR validity window — never for one
   merely past the twelve-month "older data" band, which keeps the quiet
   .badge-age-older line and no chip. One chip, one meaning.

   Deliberately NOT the failure register: an old figure carrying a real
   source is old, not wrong (CLAUDE.md, 9 Sep — stated forward, never
   condemned). And deliberately not a marked input frame, which is how this
   app already shows a FAILED validation; Michele rejected that shape for
   exactly that reason. */
.badge-older-data {
  border-color: var(--strategic);
  color: var(--strategic);
  background: var(--strategic-soft);
}

/* A researched figure is visibly distinct from a bundled one on sight,
   wherever a benchmark badge appears (Session 05j — Task 7). */
.badge-researched {
  border-color: var(--pass);
  color: var(--pass);
  background: var(--pass-soft);
  cursor: help;
}

/* Session 05i — "+ more detail" disclosure for a group of fields (Building
   Size, Development), generalised from the +Rate Composition pattern below.
   Deliberately its own class rather than reusing .inclusion: the visual
   language matches, but this holds plain fields, not an items/buffer list. */
/* Session 05k-e, Task 9, Test 8 note 7 — Michele: "Building Size" and
   "Development" MUST be the same size as Rate Composition. The three
   disclosure panels were built by three different sessions (05e Rate
   Composition, 05i the two building panels), each with its own COPY of
   the same border/padding/background/summary declarations — the exact
   shape that drifts. Fixed once, on a shared class, rather than sized
   per panel: `.disclosure-panel` is the single source for every `<details>`
   panel of this kind (`tpl-inclusion` and `tpl-field-disclosure` in
   index.html both carry it now, alongside their own class for
   panel-specific rules below). */
.disclosure-panel {
  border: var(--rule) solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-sunken);
  padding: var(--s3) var(--s4);
}

.disclosure-panel > summary {
  cursor: pointer;
  font-size: var(--step--1);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--ink-soft);
  list-style: none;
}

.disclosure-panel > summary::-webkit-details-marker {
  display: none;
}

.disclosure-panel > summary::before {
  content: "+";
  font-family: var(--font-mono);
  color: var(--gold-dark);
  margin-right: var(--s2);
}

.disclosure-panel[open] > summary::before {
  content: "\2013";
}

.field-disclosure {
  margin-top: var(--s4);
}

/* Session 05k-b, Task 7.3. #property-detail-disclosures (the "Building
   Size" / "Development" disclosures) had no CSS at all, so no bottom
   margin — #form-cost's Land Cost field butted straight against
   "Development — Same Building after" with nothing between them. */
#property-detail-disclosures {
  margin-bottom: var(--s6);
}

.field-disclosure-body {
  padding-top: var(--s4);
  display: flex;
  flex-direction: column;
  gap: var(--s4);
}

/* Session 05k-e, Task 9, Test 7 note 6 — Michele: a warning inside this
   panel disappeared the moment the panel was collapsed. Visible on the
   summary REGARDLESS of `[open]`, unlike the body it summarises — a red
   dot, not colour alone (spec/05: never colour alone), so it reads in
   mono print too. General rule: any `<details>` panel with an internal
   warning can carry `.has-warning-badge` (setDisclosureWarningBadge()). */
.has-warning-badge > summary::after {
  content: "\2731 warning";
  margin-left: var(--s3);
  color: var(--fail);
  font-size: var(--step--2);
  letter-spacing: 0.5px;
  text-transform: none;
}

.inclusion-body {
  padding-top: var(--s4);
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}

.inclusion-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}

.inclusion-empty {
  font-size: var(--step--1);
  color: var(--muted);
  font-weight: 300;
}

/* Session 05i, defect (a) — narrowed to the input's own control column
   (grid-column: 2), which fixed the auto-placement bug (falling into the
   12rem label column) but left the box only as wide as a single money
   field: ~328px against the "Development — the same building, after the
   scheme" disclosure's ~824px (that disclosure sits outside any .field
   grid, so it is never column-constrained). Michele reported this twice
   (Property & Development Costs, Business Units — the same .inclusion in
   the same grid, so one fix closes both) as "too small — make it the same
   width as the Development dropdown." Session 05k-b, Task 7.2: spans both
   columns instead, matching every other full-width element in the panel.
   Phone (<768px) is flex, not grid, and is unaffected. */
@media (min-width: 768px) {
  .field > .inclusion {
    grid-column: 1 / -1;
  }
}

/* Session 05i, defect (c): visible column headings so a sighted user can
   tell what the first box is for — the aria-labels on the inputs stay for
   assistive tech, this is the on-screen equivalent. Mirrors .inclusion-row's
   flex layout so the headings sit directly above their columns. */
.inclusion-row-heading {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--step--2);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
}

.inclusion-heading-label {
  flex: 1 1 auto;
  min-width: 0;
}

.inclusion-heading-amount {
  flex: 0 0 auto;
  width: 8rem;
  text-align: right;
}

/* Session 05i-b — the row wraps as two chunks (label, then amount+remove)
   once the label can no longer hold a sensible minimum width alongside the
   fixed-width controls. This reacts to the ROW'S own available width, which
   is what actually varies (verdict rail open/closed, tablet vs desktop) —
   not a viewport breakpoint, which was the wrong instrument here (memory.md,
   Michele, 10 Sep: "ALL drop-downs have the same issue"). */
.inclusion-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
}

.inclusion-input {
  border: var(--rule) solid var(--field-border);
  background: var(--field-bg);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  font-size: var(--step--1);
  color: var(--field-ink);
  font-family: var(--font-body);
}

.inclusion-input-label {
  flex: 1 1 10rem;
  min-width: 8rem;
}

.inclusion-input-amount {
  flex: 0 0 auto;
  /* Session 05k-e — Michele, Test 8 review: "the value field is too
     small." Widened once, on the shared class, not per instance. */
  width: 10rem;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* Session 05k-e, VR-FX-04 — an inclusion item's or the buffer's own
   currency control. Same visual language as .currency-chip, framed like
   the other inclusion-row controls rather than sitting inside a money
   field's affix (an inclusion item has no field of its own). */
.inclusion-currency {
  /* D4, Session 05n — Michele: the currency selects sized independently
     per row (a native <select>'s width follows its currently SELECTED
     option's text, and "—" (unset) is narrower than a 3-letter code) —
     so every row's own .inclusion-row-controls ate a different amount of
     width, leaving Description (.inclusion-input-label, flex-grow:1) a
     different, unpredictable remainder each time. One fixed width for
     every currency select; whatever that recovers goes to Description,
     which is the field that actually needs it. */
  flex: 0 0 4.25rem;
  width: 4.25rem;
  border: var(--rule) solid var(--field-border);
  background: var(--field-bg);
  border-radius: var(--radius);
  padding: var(--s2) var(--s3);
  font-size: var(--step--1);
  font-family: var(--font-mono);
  color: var(--ink-soft);
  cursor: pointer;
}

/* An unset currency (VR-FX-05 — never assumed to be the reporting
   currency) is the same open-item orange as every other "To be verified
   and confirmed" state in this app — now on the SELECT ITSELF (which is
   blank, not the long TBV string) plus the badge beside it. */
.inclusion-currency.is-unset {
  color: var(--strategic);
  border-color: var(--strategic);
  background: var(--strategic-soft);
}

/* The buffer's currency select sits inside the money field's own affix
   slot (field-affix-currency), which already frames it — strip the
   inclusion-row-style border/background so it reads as one control with
   the amount field beside it, matching buildCurrencyChip()'s look. */
.field-affix-currency .inclusion-currency {
  border: 0;
  background: transparent;
  padding: 0 var(--s3);
  height: 100%;
}

.field-affix-currency .inclusion-currency.is-unset {
  color: var(--strategic);
  background: var(--strategic-soft);
}

/* Task 2, Session 05k-f — the unset-currency marker moves from an option
   INSIDE the dropdown (which stretched every currency select to ~28
   characters wide, squeezing the amount field beside it) to a badge
   BESIDE the control. Same open-item orange register, same full wording
   ("To be verified and confirmed", CLAUDE.md 9 Sep) — only its position
   changes. No `title` tooltip (spec/05 — a phone has no hover). */
.inclusion-currency-field {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
}

.inclusion-currency-badge {
  font-size: var(--step--1);
  color: var(--strategic);
  background: var(--strategic-soft);
  border: var(--rule) solid var(--strategic);
  border-radius: var(--radius);
  padding: var(--s1) var(--s2);
  white-space: nowrap;
}

.inclusion-remove {
  flex: 0 0 auto;
  font-size: var(--step--1);
  padding: var(--s2) var(--s2);
}

/* The amount input and its Remove button move as one unit. Fixed content
   width (never shrinks) — so when the row doesn't have room for label +
   this group side by side, the WHOLE group wraps to its own line under the
   label, rather than the Remove button alone spilling past the edge. */
.inclusion-row-controls {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex: 0 0 auto;
}

.inclusion-add {
  align-self: flex-start;
  font-size: var(--step--1);
  padding: var(--s2) var(--s3);
}

/* The declared buffer is a plain inline control, not a nested .field — see
   the comment in buildInclusion() in app.js for why that distinction matters. */
.inclusion-buffer {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  padding-top: var(--s2);
  border-top: var(--rule) solid var(--line);
}

.inclusion-buffer-label {
  font-size: var(--step--1);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--ink-soft);
}

/* Task 1b, Session 05k-f — Michele, Test 9: "the Declared Buffer also
   have no input no more." A fixed 12rem forced the currency select (then
   stretched to ~28 characters by the TBV option, Task 2) to take the
   whole row, squeezing the amount input to ~0 width — confirmed live
   before this fix. The currency affix now stays its own natural width
   (a 3-character ISO code) and the amount input grows to fill whatever
   space remains, at every width this app supports — never a fixed total
   that a wide sibling can exhaust again. */
.inclusion-buffer-control {
  width: auto;
  min-width: 14rem;
  flex: 1 1 auto;
}
/* `.field-input`'s own base rule (flex: 1 1 auto; min-width: 0) already
   grows the amount input to fill whatever space the (now compact)
   currency affix leaves — no override needed here. */

/* The buffer's currency control and its unset-currency badge (Task 2) sit
   on one row, beside each other — the badge never squeezed inside the
   currency affix itself, which has no room for a sentence. */
.inclusion-buffer-control-row {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
}

/* VR-DBL-03 — a breakdown larger than the whole it decomposes. Reuses
   .field-error's red/exclamation treatment, spaced off the buffer above it. */
.inclusion-overflow {
  margin-top: var(--s3);
}

/* =========================================================================
   KPI strip — one number, one comparison, one visual. 4-6 max.
   ========================================================================= */

/* Rules are drawn as insets on the cells rather than as a coloured grid gap.
   A gap-coloured background fills the empty cells of a partial final row —
   five KPIs across four columns leaves three blocks of solid rule colour. */
.kpi-strip {
  display: grid;
  /* VR-DOC-07, Session 05k-d: was minmax(200px, 1fr). Venice's nine-figure
     amounts (EUR 109,895,262) were silently truncated by this width
     combined with .kpi-value's necessary nowrap and this grid's overflow:
     hidden — auto-fit decides how many columns fit, not how text fits
     inside one, and CSS has no shrink-to-fit. 260px is spec/05 section 7's
     own upper bound for a KPI card (200-280px), chosen with headroom for
     the widest figure this app is now known to produce. See .kpi-value's
     font clamp below for the belt-and-braces case beyond that. */
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 0;
  background: var(--surface);
  border: var(--rule) solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  margin-bottom: var(--s6);
}

.kpi {
  background: var(--surface);
  padding: var(--s5);
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  box-shadow: inset -1px -1px 0 var(--line);
  /* VR-DOC-07 — establishes a query container so .kpi-value can size
     itself to the cell it actually has, not to the viewport. */
  container-type: inline-size;
}

.kpi-label {
  font-size: var(--step--1);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
}

.kpi-value {
  /* VR-DOC-07, Session 05k-d — the app must never show part of a figure.
     260px alone still clips an amount long enough (a ten-figure sum, or a
     currency whose ordinary magnitudes run larger, e.g. IDR/VND), so the
     figure additionally scales itself down to the cell it is actually in,
     via a container-query length (cqi) clamped between a legible floor and
     the standard --step-2 ceiling — never smaller than necessary, never
     truncated regardless of how large the number gets. */
  font-size: clamp(0.85rem, 7.2cqi, var(--step-2));
  font-weight: 500;
  line-height: 1.1;
  color: var(--ink);
  /* Session 05c, defect 2: formatMoney() joins the currency code and figure
     with a non-breaking space, so this content is never more than one word.
     nowrap is redundant with that in the common case and a hard guarantee
     against a future formatter regressing back to a plain space. Kept
     deliberately (05k-d): the fix here is column width plus a shrinking
     font, not allowing the currency code and figure to separate. */
  white-space: nowrap;
}

.kpi-compare {
  font-size: var(--step--1);
  color: var(--muted);
  font-weight: 300;
}

/* VR-UNIT-03 — the currency was changed under figures already entered. Same
   advisory register as .warnings below: it asks, it never blocks, and it
   never converts anything. */
.currency-notice {
  border: var(--rule) solid var(--line);
  border-left: 3px solid var(--strategic);
  border-radius: var(--radius);
  background: var(--surface);
  padding: var(--s5);
  margin-top: var(--s5);
  max-width: var(--measure);
}

.currency-notice-text {
  font-size: var(--step--1);
  font-weight: 300;
  margin: 0 0 var(--s3);
}

.currency-notice-q {
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--strategic);
  margin: 0 0 var(--s3);
}

.currency-notice-choices {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
}

/* E2, Session 05n, VR-RESEARCH-06 — visible from every panel, not only
   Market Context, so a research run's true state (in flight, or failed
   while this browser was away) is never invisible after a reload. Same
   card shape as .currency-notice; the left-rule colour carries the two
   states apart, same convention as .research-status-running/-failed. */
#research-run-status {
  margin: 0 0 var(--s5);
}

.research-run-note {
  border: var(--rule) solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  padding: var(--s4) var(--s5);
  margin: 0;
  font-size: var(--step--1);
  font-weight: 300;
}

.research-run-note-pending {
  border-left: 3px solid var(--gold-dark);
  color: var(--gold-dark);
}

.research-run-note-error {
  border-left: 3px solid var(--fail);
  color: var(--fail);
}

/* VR-RESEARCH-13, Session 06t — a record left over from an earlier launch:
   quiet ink, never the fail-red of a live failure, so a past outcome is
   never mistaken for the current one. */
.research-run-note-history {
  border-left: 3px solid var(--line);
  color: var(--ink-soft);
}

/* Guards. Advisory, never blocking — an analyst deliberately modelling a
   distressed asset must not be stopped (spec/05 section 3). */
.warnings {
  border: var(--rule) solid var(--line);
  border-left: 3px solid var(--strategic);
  border-radius: var(--radius);
  background: var(--surface);
  padding: var(--s5);
  margin-bottom: var(--s6);
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}

.warnings > p {
  margin: 0;
}

/* =========================================================================
   Verdict — a stamped judgment, never a bare traffic light (spec/05 s.6)
   ========================================================================= */

.verdict {
  background: var(--navy);
  color: var(--text-light);
  border-radius: var(--radius);
  border-left: 4px solid var(--gold);
  padding: var(--s6);
  margin-bottom: var(--s6);
  position: relative;
  overflow: hidden;
}

/* Applies to the verdict CARD — a hairline pinned to its bottom edge is
   correct there. `#verdict-rail` also carries the `.verdict` class and
   inherited this rule unscoped, but it is ALSO a scroll container
   (`overflow-y: auto`), so "the bottom edge of the box" lands wherever the
   content happens to be taller than the rail — a floating line with no
   fixed position on screen (Session 06h, D3; Michele's two screenshots, 17
   Sep, are the same line at two different scroll offsets). The override
   below is the second equal-specificity collision on this element (05e was
   the first, `.panel`/`.ws-panel` comment above) — scoped by id this time
   so it cannot lose the ordering fight again. */
.verdict::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: var(--rule);
  background: var(--gold);
}

/* The verdict RAIL — a scroll container, never the card. No bottom
   hairline: see the comment above `.verdict::after`. */
#verdict-rail::after {
  content: none;
}

.verdict[data-state="pass"] {
  border-left-color: var(--pass);
}

.verdict[data-state="fail"] {
  border-left-color: var(--fail);
}

.verdict[data-state="strategic"] {
  border-left-color: var(--strategic);
}

.verdict-eyebrow {
  font-size: var(--step--1);
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--gold-light);
  font-weight: 600;
  margin: 0 0 var(--s3);
}

/* Word + shape, never colour alone (WCAG 1.4.1, spec/05 section 6). */
.verdict-headline {
  display: flex;
  align-items: center;
  gap: var(--s4);
  margin: 0 0 var(--s4);
  flex-wrap: wrap;
}

.verdict-word {
  font-family: var(--font-display);
  font-size: var(--step-4);
  font-weight: 700;
  letter-spacing: 3px;
  text-transform: uppercase;
  line-height: 1;
  margin: 0;
}

.verdict-glyph {
  width: 2.25rem;
  height: 2.25rem;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  font-family: var(--font-mono);
  font-size: var(--step-1);
  font-weight: 700;
  border: 2px solid currentColor;
}

.verdict[data-state="pass"] .verdict-word,
.verdict[data-state="pass"] .verdict-glyph {
  color: var(--pass);
}

.verdict[data-state="fail"] .verdict-word,
.verdict[data-state="fail"] .verdict-glyph {
  color: var(--fail);
}

.verdict[data-state="strategic"] .verdict-word,
.verdict[data-state="strategic"] .verdict-glyph {
  color: var(--strategic);
}

/* Pass = circle, fail = square, strategic = diamond. Shape carries meaning. */
.verdict[data-state="pass"] .verdict-glyph {
  border-radius: 50%;
}

.verdict[data-state="strategic"] .verdict-glyph {
  transform: rotate(45deg);
}

.verdict[data-state="strategic"] .verdict-glyph > span {
  transform: rotate(-45deg);
}

/* Threshold, margin and break-even are mandatory on every verdict. */
.verdict-tests {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--s5);
  margin-top: var(--s5);
  padding-top: var(--s5);
  border-top: var(--rule) solid var(--navy-secondary);
}

/* The Blue Box — Session 05i-b. Same grid mechanics as .verdict-tests
   (auto-fit collapses to one column when the rail is too narrow for two,
   so this never overflows the rail or the phone bar). */
.verdict-figures-caption {
  font-size: var(--step--1);
  letter-spacing: 1px;
  color: var(--gold-light);
  font-weight: 300;
  margin: var(--s5) 0 0;
  padding-top: var(--s5);
  border-top: var(--rule) solid var(--navy-secondary);
}

.verdict-figures-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--s4);
  margin-top: var(--s3);
}

/* Session 05k-e, Task 9, Test 7/8 note 8, VR-DOC-08 — Michele: "its
   readable only when is 2 columns. We need to lock together the name and
   the value for each reference." A label separating from its own figure
   across a reflow or a column break is the same class of defect as a
   figure cut off by its cell (VR-DOC-07): the reader sees something that
   is not the number. Stated as a rule and applied to every label+value
   card on screen, not only the Developer Margin box Michele named —
   `break-inside: avoid` keeps each card intact wherever a browser or a
   print engine might otherwise fragment it. */
.verdict-figure-item,
.kpi {
  break-inside: avoid;
}

.verdict-figure-item .verdict-margin {
  margin-top: var(--s1);
}

/* Session 05k-e, Task 8 — every figure's description is tap-to-reveal, a
   real button (never a `title` tooltip a phone cannot hover). Reset to
   look exactly like the old plain label+value stack until tapped, so the
   restructure changes NOTHING visually for a figure nobody has opened. */
.verdict-figure-toggle {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
  width: 100%;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.verdict-figure-toggle::after {
  content: "\0192"; /* ƒ — same "traceable" marker the screen's computed values carry */
  font-family: var(--font-display);
  font-size: 0.7em;
  font-style: italic;
  color: var(--gold-light);
  margin-left: var(--s1);
  opacity: 0.7;
}

.verdict-figure-toggle:hover .verdict-figure,
.verdict-figure-toggle:focus-visible .verdict-figure {
  text-decoration: underline;
  text-decoration-color: var(--gold-light);
}

.verdict-figure-toggle:focus-visible {
  outline: 2px solid var(--gold-light);
  outline-offset: 2px;
}

.verdict-test-label {
  font-size: var(--step--1);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--gold-light);
  font-weight: 600;
  margin: 0 0 var(--s1);
}

.verdict-figure {
  font-size: var(--step-1);
  color: var(--text-light);
  display: block;
}

.verdict-margin {
  font-size: var(--step--1);
  color: var(--gold-light);
  font-weight: 300;
}

.verdict-breakeven {
  margin: var(--s5) 0 0;
  padding-top: var(--s5);
  border-top: var(--rule) solid var(--navy-secondary);
  color: var(--gold-light);
  font-weight: 300;
  max-width: var(--measure);
}

/* Session 06u, Cowork's finding 7 — the rail's reason line was the only
   PROSE in the rail set in the figure font, and it read as a code block
   beside the caveats directly beneath it (which carry no font-family and
   so inherit the body font). It is a sentence, not a figure: body font,
   same as those caveats. Nothing else in the rail changes — the figures
   themselves (.verdict-figure) stay monospace with their tabular
   numerals, which is what that font is in the app for. VR-VERDICT-02 has
   since made this line longer (it now names both gates and the fix), so
   it also gets a prose line-height rather than the default. */
.verdict-rollup {
  font-family: var(--font-body);
  font-size: var(--step--1);
  line-height: 1.55;
  color: var(--gold-light);
}

/* VR-BENCH-09, Session 06x — surface 2 of three. The verdict says what it
   rests on, where the verdict is READ, in the same open-item orange the
   field's own chip uses. Directly under the roll-up and above the caveats:
   it qualifies the conclusion, so it sits with the conclusion. Wraps
   freely — it names every figure and its age, so it is a sentence, not a
   chip. */
.verdict-older-data {
  font-family: var(--font-body);
  font-size: var(--step--1);
  line-height: 1.55;
  font-weight: 500;
  color: var(--strategic-ink);
  margin: var(--s3) 0 0;
}

/* Session 05g, defect 1 — the doubt beside the conclusion. Reuses
   --save-failed-ink, the one warning tone already tuned to read against
   this same navy panel, rather than inventing a second one. */
.verdict-caveats {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  margin-top: var(--s4);
}

/* Session 05l, Task 8 (VR-DOC-11) — replaces the old .verdict-caveat, which
   printed every warning's full text a second time. Now: a count, then one
   real link per warning (its first sentence only) jumping to the full text
   in #warnings, which is where it lives exactly once. */
.verdict-caveats-count {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--save-failed-ink);
  margin: 0;
}

.verdict-caveats-list {
  list-style: none;
  margin: var(--s2) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}

.verdict-caveat-link {
  display: block;
  font-size: var(--step--1);
  color: var(--save-failed-ink);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* VR-VERDICT-01, Session 06z — a rail item whose destination is not on
   screen right now (a unit since removed, a field standing down under
   another field's setting). It still says what is wrong; it simply does
   not pretend to be a link. Same size and colour as the link beside it,
   without the underline that promises a jump. */
.verdict-caveat-plain {
  display: block;
  font-size: var(--step--1);
  color: var(--save-failed-ink);
}

/* Session 06u, Michele's finding 16 — a unit with more than one thing
   wrong is ONE item to check, headed by its name once, with its reasons
   beneath. The name is not a link (the reasons are); it is the heading
   that stops the same unit reading as two separate problems. */
.verdict-caveat-group-name {
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--save-failed-ink);
  margin: 0 0 var(--s1);
}

.verdict-caveats-sublist {
  list-style: none;
  margin: 0;
  padding: 0 0 0 var(--s3);
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  border-left: var(--rule) solid var(--save-failed-ink);
}

.verdict-caveat-link:hover,
.verdict-caveat-link:focus-visible {
  color: var(--text-light);
}

/* =========================================================================
   Session 05e — the verdict RAIL. Same five-part content model (glyph,
   word, per-test threshold, margin, break-even), the same .verdict base
   styling and data-state colouring above, but sized for a 300px column
   that never leaves the screen, and — at phone width — repositioned by
   CSS alone into a bottom-anchored, tappable, expandable bar. One DOM
   node, two arrangements (spec/07).
   ========================================================================= */

.verdict-rail {
  margin-bottom: 0;
  padding: var(--s5);
}

.verdict-rail .verdict-word {
  font-size: var(--step-2);
  letter-spacing: 1.5px;
}

.verdict-rail .verdict-glyph {
  width: 1.75rem;
  height: 1.75rem;
  font-size: var(--step-0);
}

/* `display:block` deliberately, not flex: the eyebrow and headline are two
   stacked lines of unequal length ("DEVELOPER SPV" vs "INCOMPLETE"), and a
   flex row split them into side-by-side columns fighting for width, which
   is what clipped "INCOMPLETE" against the chevron. The chevron is
   positioned absolutely instead, so the text column keeps the full width. */
.verdict-rail-toggle {
  appearance: none;
  background: none;
  border: 0;
  width: 100%;
  text-align: left;
  padding: 0;
  color: inherit;
  cursor: pointer;
  display: block;
  position: relative;
}

.verdict-rail-toggle > * {
  pointer-events: none;
}

.verdict-rail-toggle .verdict-eyebrow,
.verdict-rail-toggle .verdict-headline {
  margin-bottom: 0;
}

.verdict-rail-chevron {
  position: absolute;
  top: 0.3rem;
  right: 0;
  width: 0.55rem;
  height: 0.55rem;
  border-right: 2px solid var(--gold-light);
  border-bottom: 2px solid var(--gold-light);
  transform: rotate(45deg);
  transition: transform var(--motion);
  display: none;
}

.verdict-rail-body {
  margin-top: var(--s5);
}

@media (max-width: 767px) {
  .verdict-rail-toggle {
    padding-right: var(--s6);
  }
  .verdict-rail-chevron {
    display: block;
  }
  .verdict-rail[data-expanded="true"] .verdict-rail-chevron {
    transform: rotate(-135deg);
  }
  .verdict-rail-body {
    margin-top: var(--s4);
  }
}

/* Session 06q, VR-PERSIST-18 — "Leave a note". A sibling of
   #verdict-rail-toggle and .verdict-rail-body (index.html), so it never
   collapses with the body on phone and is always reachable, in both the
   sticky desktop rail and the fixed phone thumb-zone bar.

   Session 06r: the toggle was styled like the rail's own section labels
   (small caps, stone, no affordance) and sat flush against the caveats
   block below it — Michele, testing as a tester, read "LEAVE A NOTE" as a
   heading and the expiry caveat under it as its content. Now a
   hairline-bordered pill in the gold-dark/gold-light pairing
   `.badge-benchmark` already uses, with a leading +/− glyph, a
   bottom border of its own so it never sits flush against whatever
   follows it in .verdict-rail-body, and a sub-hint while collapsed. */
.note-box {
  margin-top: var(--s4);
  padding-top: var(--s4);
  padding-bottom: var(--s4);
  border-top: var(--rule) solid var(--line);
  border-bottom: var(--rule) solid var(--line);
}

.note-box-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  background: transparent;
  border: var(--rule) solid var(--gold-dark);
  border-radius: 999px;
  padding: var(--s2) var(--s4);
  cursor: pointer;
  font-family: var(--font-body);
  font-size: var(--step--1);
  letter-spacing: 1px;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--gold-dark);
  transition: background-color var(--motion), color var(--motion), border-color var(--motion);
}

.note-box-toggle:hover {
  background: var(--gold-light);
}

/* Session 06s, VR-PERSIST-20 — the pill IS the save confirmation now
   (spec/05: never colour alone, so the label text changes too, not only
   the fill). Transient on success (~3s, setNotePillSaved()), persistent
   on failure until the reader retries (setNotePillFailed()) — on the
   phone thumb-zone bar this state change is the ONLY feedback a reader
   sees (spec/07), so it must never be colour-only. */
.note-box-toggle.is-saved {
  background: var(--gold-light);
  border-color: var(--gold-dark);
  color: var(--gold-dark);
}

.note-box-toggle.is-failed {
  background: var(--fail-soft);
  border-color: var(--fail);
  color: var(--fail);
}

.note-box-toggle-icon {
  font-size: var(--step-1);
  line-height: 1;
}

.note-box-subhint {
  margin: var(--s2) 0 0;
  font-size: var(--step--1);
  color: var(--muted);
  font-weight: 300;
}

.note-box-body {
  margin-top: var(--s3);
}

.note-box-input {
  width: 100%;
  box-sizing: border-box;
  border: var(--rule) solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--step--1);
  line-height: 1.6;
  padding: var(--s3);
  resize: vertical;
}

.note-box-input:focus {
  outline: none;
  border-color: var(--gold);
}

.note-box-hint {
  margin: var(--s2) 0 0;
  font-size: var(--step--1);
  color: var(--muted);
  font-weight: 300;
}

.note-box-actions {
  margin-top: var(--s3);
  display: flex;
  justify-content: flex-end;
  gap: var(--s3);
}

/* The margin-against-hurdle bar. 50% = sits exactly at the hurdle; the
   marker is fixed at the midpoint so the fill's position IS the story —
   decorative positioning of the engine's own marginBps/marginMonths, never
   a new figure (spec/05 section 6: never colour alone, always the number
   beside it — the bar supplements verdict-margin's text, it never replaces it). */
.verdict-margin-bar {
  position: relative;
  height: 4px;
  margin-top: var(--s2);
  background: var(--navy-secondary);
  border-radius: 2px;
  overflow: visible;
}

.verdict-margin-bar-fill {
  display: block;
  height: 100%;
  border-radius: 2px;
  background: var(--gold);
  transition: width var(--motion-slow);
}

.verdict-margin-bar[data-clears="false"] .verdict-margin-bar-fill {
  background: var(--fail);
}

.verdict-margin-bar-marker {
  position: absolute;
  left: 50%;
  top: -3px;
  width: 2px;
  height: 10px;
  background: var(--gold-light);
  transform: translateX(-50%);
}

/* Verdict state chooser on Step 3 */
.verdict-choice {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}

.choice {
  display: flex;
  gap: var(--s4);
  align-items: flex-start;
  padding: var(--s4);
  border: var(--rule) solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  cursor: pointer;
  transition: border-color var(--motion), background-color var(--motion);
}

.choice:hover {
  border-color: var(--gold-dark);
}

.choice:has(input:checked) {
  border-color: var(--gold);
  background: var(--surface-muted);
}

.choice-text {
  display: flex;
  flex-direction: column;
  gap: var(--s1);
}

.choice-title {
  font-weight: 600;
  letter-spacing: 0.5px;
}

.choice-note {
  font-size: var(--step--1);
  color: var(--muted);
  font-weight: 300;
}

.rationale-gate {
  margin-top: var(--s4);
  padding-left: var(--s6);
  border-left: 2px solid var(--strategic);
}

/* =========================================================================
   Results panels
   ========================================================================= */

.panel {
  border: var(--rule) solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  padding: var(--s6);
  margin-bottom: var(--s6);
}

.panel-buyer {
  border-left: 3px solid var(--gold);
  background: var(--surface-muted);
}

/* Session 05f — `#gate-preview` had no frame at all: its `.deflist` sat as
   bare text against the fields above and below it, which is what read as
   "not aligned". Same family as `.panel`, distinct name because `.panel`
   and `.ws-panel` already collided once this project on equal-specificity
   base-class rules (05e, the verdict-rail position bug). */
.panel-inset {
  border: var(--rule) solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-muted);
  padding: var(--s5);
  margin: var(--s5) 0;
}

.panel-inset:empty {
  display: none;
}

.panel-title {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  margin: 0 0 var(--s2);
}

.panel-note {
  font-size: var(--step--1);
  color: var(--muted);
  font-weight: 300;
  margin: 0 0 var(--s5);
  max-width: var(--measure);
}

.deflist {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--s5);
  margin: 0;
}

/* VR-DOC-08, Session 05k-f — found at last, from Michele's own screenshot.
   .deflist's auto-fit grid used to place <dt> and <dd> as two SEPARATE
   grid items, so the column count was emergent, not literal: at 2 columns
   every label happened to land above its own value; at 3 columns the
   pairs marched out of step, a label's value wrapping to the row below a
   DIFFERENT label. Each label/value pair is now wrapped in one
   .deflist-pair div — one grid item per pair — so ANY column count the
   grid resolves to keeps every label with its own value. The grid is
   deliberately NOT capped at two columns; that would waste width rather
   than fix the cause. */
.deflist-pair {
  min-width: 0;
}

.deflist dt {
  font-size: var(--step--1);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
  margin-bottom: var(--s1);
}

.deflist dd {
  margin: 0;
  font-size: var(--step-1);
}

/* =========================================================================
   Footer
   ========================================================================= */

.footer {
  border-top: var(--rule) solid var(--line);
  background: var(--surface);
  padding: var(--s5) 0;
  font-size: var(--step--1);
  color: var(--muted);
  font-weight: 300;
}

.footer-inner {
  width: min(1160px, calc(100vw - var(--s6)));
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
}

.footnote {
  font-size: var(--step--1);
  color: var(--muted);
  font-weight: 300;
}

/* Session 06e, Task 1 — this note used to sit inside the landing screen's
   foot .actions-bar (border-top + padding-top gave it separation from the
   table above). Standing alone now, it needs that separation itself; no
   border, since it is a footnote, not a bar of controls. */
.landing-storage-note {
  margin-top: var(--s5);
}

/* VR-EXPORT-08, Session 06z — the shipped samples, offered on Home. One
   row per sample: the scenario's own name as the control (it already
   carries "Sample — not real data", so the row says what it is before it
   is opened) with its currency and unit count beside it.
   Text left, numbers right — CLAUDE.md's standing alignment rule. */
.sample-list {
  list-style: none;
  margin: var(--s3) 0 0;
  padding: 0;
  border-top: var(--rule) solid var(--line);
}

.sample-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
  border-bottom: var(--rule) solid var(--line);
}

.sample-row-open {
  flex: 1 1 auto;
  text-align: left;
  justify-content: flex-start;
  border: 0;
  padding: var(--s3) 0;
  min-width: 0;
}

.sample-row-meta {
  flex: 0 0 auto;
  text-align: right;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
  white-space: nowrap;
}

/* A phone has no room for a long scenario name and its figures on one
   line, and truncating the name is the wrong trade — the name is what a
   reader is choosing between. The meta drops under it instead. */
@media (max-width: 640px) {
  .sample-row {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }

  .sample-row-meta {
    text-align: left;
    padding-bottom: var(--s3);
  }
}

/* VR-EXPORT-09, Session 06z — a copy serving work it did not ship with.
   Deliberately the loudest thing on the Home screen: --fail, not the
   open-item orange, because this is not an item to look into later but a
   reason to stop and ask someone. It sits above the list it is describing,
   full width, with the same left rule the app already uses to mark a block
   as a warning rather than a note. */
.landing-foreign-warning {
  margin: 0 0 var(--s5);
  padding: var(--s4) var(--s5);
  background: var(--fail-soft);
  border: var(--rule) solid var(--fail);
  border-left-width: 4px;
  border-radius: var(--radius-sm, 2px);
}

.landing-foreign-title {
  margin: 0 0 var(--s3);
  font-family: var(--font-display);
  font-size: var(--step-0);
  text-transform: uppercase;
  letter-spacing: 1.5px;
  color: var(--fail);
}

.landing-foreign-body,
.landing-foreign-action {
  margin: 0;
  font-size: var(--step--1);
  color: var(--ink);
}

.landing-foreign-action {
  margin-top: var(--s3);
  font-weight: 600;
}

.landing-foreign-list {
  margin: var(--s2) 0 var(--s3);
  padding-left: var(--s5);
  font-size: var(--step--1);
  color: var(--ink);
  font-weight: 600;
}

/* The folder path and the build date — structured fact a reader can go and
   check, so they are set in the data face and kept on their own lines.
   `pre-wrap` honours the newline the renderer writes between them; a long
   drive path wraps inside this box rather than widening the page
   (spec/05: no page-level horizontal overflow at any width). */
.landing-foreign-where {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  color: var(--ink-soft);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* VR-EXPORT-09, amended in Session 07b — the stale bundle note.
   Deliberately the OPEN-ITEM orange and not --fail: the sibling block above
   is a reason to stop and ask someone, and this is a note beside the data
   that has not caught up with the code. Same box shape so the two read as
   one family, a different register so they are never confused (spec/05
   section 6: a word and a shape, never colour alone — the title carries
   the word). */
.landing-build-note {
  margin: 0 0 var(--s5);
  padding: var(--s4) var(--s5);
  background: var(--strategic-soft);
  border: var(--rule) solid var(--strategic);
  border-left-width: 4px;
  border-radius: var(--radius-sm, 2px);
}

.landing-build-note-title {
  margin: 0 0 var(--s2);
  font-family: var(--font-display);
  font-size: var(--step-0);
  text-transform: uppercase;
  letter-spacing: 1.5px;
  color: var(--strategic);
}

/* The two dates are structured fact a reader can go and check against the
   files themselves, so they are set in the data face. `pre-wrap` honours
   the newline the renderer writes; long content wraps inside this box
   rather than widening the page (spec/05: no page-level horizontal
   overflow at any width). */
.landing-build-note-body {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  color: var(--ink-soft);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Placeholder marker — a figure a later session must still replace. */
.todo-flag {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.5px;
  color: var(--strategic);
  text-transform: uppercase;
}

.footer-right-group {
  display: inline-flex;
  align-items: center;
  gap: var(--s4);
}

.footer-right-group .btn-quiet {
  padding: 0;
  font-size: var(--step--1);
  letter-spacing: 1px;
  text-transform: uppercase;
}

/* The activity log — a discreet, session-only trail. Not a new screen. */
.activity-log {
  border-top: var(--rule) solid var(--line);
  background: var(--surface-muted);
  max-height: 40vh;
  overflow-y: auto;
}

.activity-log-inner {
  width: min(1160px, calc(100vw - var(--s6)));
  margin: 0 auto;
  padding: var(--s5) 0;
}

.activity-log-title {
  font-size: var(--step--1);
  letter-spacing: 1.4px;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--muted);
  margin: 0 0 var(--s3);
}

.activity-log ol {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column-reverse;
  gap: var(--s2);
  font-family: var(--font-mono);
  font-size: var(--step--1);
}

.activity-log li {
  display: flex;
  gap: var(--s3);
  color: var(--ink-soft);
}

.activity-log time {
  color: var(--muted);
  flex: 0 0 auto;
}

/* =========================================================================
   Responsive — verified at 1440 / 1024 / 768 / 390
   ========================================================================= */

@media (max-width: 768px) {
  main {
    padding: var(--s6) 0 var(--s8);
  }

  .topbar-inner,
  .shell,
  .steprail-inner,
  .footer-inner {
    width: calc(100vw - var(--s5));
  }

  .unit-body,
  .panel,
  .verdict {
    padding: var(--s5) var(--s4);
  }

  .w-sm,
  .w-md,
  .w-lg {
    width: 100%;
  }

  .actions-bar {
    flex-direction: column;
    align-items: stretch;
  }

  .actions-bar .btn-row {
    justify-content: stretch;
  }

  .actions-bar .btn {
    flex: 1 1 auto;
  }

  /* Session 06e, Task 2 — .btn-with-status is a plain wrapper div, not a
     .btn, so it did not pick up the flex:1 1 auto rule above and stayed
     button-width, packed wherever the wrapping row put it. #pdf-path-status
     then anchored its FIXED 24rem width to that wrapper's right edge
     (right:0), and at this width the wrapper can sit far enough left that
     a box that wide runs off the left of the viewport. Stretching the
     wrapper to the row's full width fixes that. */
  .actions-bar .btn-with-status {
    flex: 1 1 auto;
  }

  /* At this width .actions-bar's buttons each end up one per row (wrap +
     stretch), stacked vertically with Save Scenario directly below Export
     PDF. #pdf-path-status's own desktop fix — position:absolute, so a
     revealed path never pushes the row's OTHER buttons sideways — has a
     side effect once the wrapped text is several lines tall: out of flow,
     it does not push the Save Scenario wrapper below it either, so a long
     path overlaps the button that follows it in the stack. There is no
     sideways neighbour to protect at this width (each button already owns
     its own row), so back in normal flow is the fix here: it now pushes
     Save Scenario down instead of overlapping it. */
  #pdf-path-status {
    position: static;
    width: auto;
    margin-top: var(--s2);
    text-align: left;
  }
}

@media (max-width: 480px) {
  .verdict-word {
    letter-spacing: 2px;
  }

  .unit-head {
    align-items: flex-start;
  }
}

/* =========================================================================
   THE EXPORT DOCUMENT — Session 05

   The print stylesheet IS the report generator (spec/05 section 9). There is
   no PDF library and there is not going to be one.

   The document is built into #export-root and has nothing in common with the
   screen layout — that separation is what makes controlled A4 pagination
   possible at all. On screen the container is hidden; [data-printing] shows
   it, which doubles as a preview state.
   ========================================================================= */

.export-root {
  display: none;
}

:root[data-printing="true"] .export-root {
  display: block;
  background: var(--surface-sunken);
  padding: var(--s6) 0;
}

/* Session 05l, Task 1 — the build-time paginator needs real layout
   (getBoundingClientRect) to measure each page before the caller ever sets
   [data-printing]. This attribute is set by export.js's own paginate(),
   synchronously, for the duration of one measurement pass, and cleared in a
   finally before build() returns — nothing is ever shown to the user under
   it, unlike [data-printing] which doubles as the on-screen preview. */
:root[data-export-measuring="true"] .export-root {
  display: block;
}

/* --- Document tokens. Ink values live here, per the no-hex-outside-:root
       rule; the document is deliberately black-on-white, not the screen
       palette, because it is printed and photocopied. ------------------- */
:root {
  --doc-ink: #14161f;
  --doc-ink-soft: #444a5a;
  --doc-muted: #6a7080;
  --doc-rule: #c9ccd6;
  --doc-rule-soft: #e4e6ec;
  --doc-paper: #ffffff;
  --doc-navy: #2c2826; /* charcoal — AssetMANDATUM, Session 06j */
  --doc-gold: #9a7d5a; /* bronze */
  --doc-gold-pale: #f4efe6; /* linen */
  /* Page 4's whole point: an unverified claim must not be mistakable for a
     sourced one, so it gets its own ground, not a footnote marker. */
  --doc-unverified-ink: #8a4b10;
  --doc-unverified-bg: #fdf3e5;
  --doc-unverified-rule: #d98b2b;
  --doc-sourced-ink: #1f5c43;
  --doc-sourced-rule: #2f7d5c;
  /* A benchmark-sourced figure is the exception on these pages — most are
     analyst entries — so it carries a subtle tint to be findable at a
     glance without shouting like the page-4 unverified treatment. */
  --doc-sourced-bg: #edf5f1;
  /* Verdict word colours, legible against the navy band (now charcoal,
     Session 06j) — re-checked, all comfortably >6.5:1, unchanged. */
  --doc-verdict-pass: #7fd1a8;
  --doc-verdict-fail: #f0958a;
  --doc-verdict-strategic: #e8a75a;
  --doc-verdict-insufficient: #c9ccd6;
  --doc-on-navy-rule: rgba(255, 255, 255, 0.28);
  --doc-on-navy-wash: rgba(255, 255, 255, 0.07);
  --doc-pad-x: 15mm;
  --doc-pad-y: 12mm;
}

/* --- Page geometry ---------------------------------------------------- */

.doc-page {
  position: relative;
  box-sizing: border-box;
  width: 210mm;
  /* 296mm, not the full 297: at an exact fit any sub-pixel — an embedded
     logo settling, a font metric — spills a near-empty sheet after the last
     page. The millimetre of slack is invisible and makes pagination stable.

     Session 05l, Task 1 (VR-EXPORT-05) — a FIXED height, not min-height.
     Before this session a page could grow past one sheet with nothing to
     stop it, so an overrunning section spilled onto a second, mostly-empty
     physical sheet that export.js's build() never created and that
     therefore carried none of its furniture. export.js's own paginate()
     now measures each page's content against this fixed capacity and opens
     a real continuation `.doc-page` (through makePage(), inheriting every
     piece of furniture by construction) before content is ever allowed to
     overrun it. `overflow` stays the default `visible` deliberately —
     clipping is the defect class this session removes, not a substitute
     for it; a page that still overruns after pagination is a bug the
     verification harness (tools/verify-document.js) will surface as a
     measured number, not hide behind a clipped box. */
  height: 296mm;
  margin: 0 auto var(--s6);
  padding: var(--doc-pad-y) var(--doc-pad-x);
  background: var(--doc-paper);
  color: var(--doc-ink);
  font-family: var(--font-body);
  font-size: 9.2pt;
  line-height: 1.5;
  display: flex;
  flex-direction: column;
}

.doc-page-body {
  flex: 1 1 auto;
  padding: 4mm 0 3mm;
}

/* --- Running head and foot. VR-EXPORT-01: generated per page, so a page
       cannot exist without its confidentiality mark, date and version. -- */

.doc-running-head,
.doc-running-foot {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s4);
  font-size: 6.6pt;
  letter-spacing: 0.9pt;
  text-transform: uppercase;
  color: var(--doc-muted);
}

.doc-running-head {
  border-bottom: 0.5pt solid var(--doc-rule);
  padding-bottom: 2.5mm;
}

.doc-running-foot {
  border-top: 0.5pt solid var(--doc-rule);
  padding-top: 2.5mm;
  margin-top: auto;
}

.doc-confidential {
  color: var(--doc-unverified-ink);
  font-weight: 600;
}

.doc-foot-meta {
  font-family: var(--font-mono);
  letter-spacing: 0.4pt;
  /* Session 06k, item 3 — .doc-foot-left grew a second line (the Beta
     sentence, 06j) and both footer halves are default flex:0 1 auto items
     competing for the same row, so the meta block (date · version · page
     N / M, with " · N / M" appended by paginate()) was shrinking below its
     own max-content width and wrapping the page count onto a second line.
     The meta block's content never wants to wrap — pin it. */
  white-space: nowrap;
  flex-shrink: 0;
}

/* VR-EXPORT-01 — the denomination, stated where the figures are. Sits in the
   centre of the running head: legible at a glance, quieter than the
   confidentiality mark it shares the rule with. */
.doc-running-currency {
  font-family: var(--font-mono);
  letter-spacing: 0.4pt;
  font-weight: 500;
  color: var(--doc-ink);
  white-space: nowrap;
}

/* --- Type scale ------------------------------------------------------- */

.doc-h2 {
  font-family: var(--font-display);
  font-size: 15pt;
  font-weight: 600;
  letter-spacing: 1.4pt;
  text-transform: uppercase;
  margin: 0 0 5mm;
  padding-bottom: 2.5mm;
  border-bottom: 0.5pt solid var(--doc-rule);
  position: relative;
}

.doc-h2::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -0.5pt;
  width: 22mm;
  height: 1pt;
  background: var(--doc-gold);
}

.doc-h3 {
  font-family: var(--font-display);
  font-size: 10.5pt;
  font-weight: 600;
  letter-spacing: 1.1pt;
  text-transform: uppercase;
  margin: 3mm 0 1.5mm;
  color: var(--doc-ink);
}

/* First heading on a page sits tight under the rule. */
.doc-page-body > .doc-h3:first-child {
  margin-top: 0;
}

.doc-h4 {
  font-size: 9pt;
  font-weight: 600;
  letter-spacing: 0.5pt;
  margin: 0;
}

.doc-note {
  font-size: 7.6pt;
  color: var(--doc-muted);
  margin: 0 0 3mm;
  max-width: 150mm;
}

/* VR-FX-01 in the document: a line entered in another currency states what
   was typed and the rate that converted it, under its own label. The PDF
   travels without its context, so a rate visible only on screen is not
   visible at all. */
.doc-fx-entered {
  font-family: var(--font-mono);
  font-size: 6.6pt;
  color: var(--doc-muted);
  margin: 1mm 0 0;
  letter-spacing: -0.1pt;
}

.doc-eyebrow {
  font-size: 7.4pt;
  letter-spacing: 2pt;
  text-transform: uppercase;
  color: var(--doc-gold);
  font-weight: 600;
  margin: 0 0 3mm;
}

/* --- Cover ------------------------------------------------------------ */

.doc-page-cover .doc-page-body {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.doc-cover-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s5);
  min-height: 26mm;
}

.doc-logo-img {
  /* Large enough that a wordmark inside the artwork is actually legible in
     print — 22mm rendered the firm mark's own type too small to read. */
  max-height: 28mm;
  max-width: 72mm;
  object-fit: contain;
}

/* Session 06j: the AssetMANDATUM wordmark injected as inline SVG markup
   (export.js logoOrWordmark()). Coloured --doc-navy so it reads as ink on
   the white cover, matching the old .doc-logo-img's sizing intent. */
.doc-inline-wordmark {
  color: var(--doc-navy);
  max-height: 28mm;
  width: 72mm;
}

.doc-inline-wordmark svg {
  display: block;
  width: 100%;
  height: auto;
}

.doc-cover-project .doc-logo-img {
  /* Bounded on both axes only — never a fixed width AND height, which is
     what would squash a mark. object-fit keeps the aspect regardless. */
  max-height: 30mm;
  max-width: 38mm;
  object-fit: contain;
}

/* A reversed (white) mark on the ground it was drawn for. The asset is
   placed as supplied — never recoloured or resized on one axis — it is the
   panel behind it that makes it visible on white paper.

   Padding is equal on all four sides: uneven padding made a square mark sit
   in a rectangular tile, which reads as a squashed logo even though the
   artwork is untouched. */
.doc-cover-project-on-dark {
  background: var(--doc-navy);
  padding: 4mm;
  display: flex;
  align-items: center;
  justify-content: center;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

/* The legal entity, printed under the mark. A logo alone does not identify
   who is legally behind an investor-facing document. */
.doc-cover-legal {
  font-size: 7.4pt;
  letter-spacing: 0.9pt;
  color: var(--doc-ink-soft);
  margin: 2mm 0 0;
}

.doc-wordmark {
  font-family: var(--font-display);
  font-size: 11pt;
  font-weight: 600;
  letter-spacing: 2.4pt;
  text-transform: uppercase;
  margin: 0;
  color: var(--doc-navy);
}

.doc-cover-mid {
  padding: 12mm 0;
}

.doc-cover-title {
  font-family: var(--font-display);
  font-size: 30pt;
  font-weight: 700;
  letter-spacing: 1.6pt;
  text-transform: uppercase;
  line-height: 1.08;
  margin: 0 0 4mm;
  color: var(--doc-navy);
  max-width: 150mm;
}

.doc-cover-mix {
  font-size: 10pt;
  letter-spacing: 0.6pt;
  color: var(--doc-ink-soft);
  margin: 0 0 10mm;
  padding-bottom: 6mm;
  border-bottom: 1pt solid var(--doc-gold);
  max-width: 150mm;
}

/* Session 05e — who the document is FOR. Independent of branding mode,
   optional, never invented: printed only when at least one field is set. */
.doc-cover-recipient {
  font-size: 9pt;
  letter-spacing: 0.4pt;
  color: var(--doc-ink-soft);
  margin: -6mm 0 8mm;
  max-width: 150mm;
}

.doc-cover-facts {
  display: grid;
  grid-template-columns: 26mm 1fr;
  gap: 2mm var(--s4);
  margin: 0;
  max-width: 110mm;
}

.doc-cover-facts dt {
  font-size: 7pt;
  letter-spacing: 1.1pt;
  text-transform: uppercase;
  color: var(--doc-muted);
  font-weight: 600;
}

.doc-cover-facts dd {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 8.6pt;
  font-variant-numeric: tabular-nums;
  color: var(--doc-ink);
}

.doc-cover-foot {
  border-top: 0.5pt solid var(--doc-rule);
  padding-top: 4mm;
}

.doc-cover-confidential {
  font-size: 8pt;
  letter-spacing: 1.6pt;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--doc-unverified-ink);
  margin: 0 0 2mm;
}

.doc-cover-note {
  font-size: 7.2pt;
  color: var(--doc-muted);
  margin: 0;
  max-width: 150mm;
}

/* Session 06j: the full Beta paragraph — DRAFT wording, Michele's to
   correct. Directly under .doc-cover-note, same size family, but its own
   paragraph so it reads as a distinct statement, not a continuation of the
   screening-figures note above it. */
.doc-cover-beta {
  font-size: 7.2pt;
  color: var(--doc-muted);
  margin: 2mm 0 0;
  max-width: 150mm;
}

.doc-attribution {
  font-size: 7.2pt;
  color: var(--doc-muted);
  margin: 2mm 0 0;
  font-style: italic;
}

/* --- Tables. No vertical rules, figures right-aligned and tabular, and a
       row never splits across a page (spec/05 section 7). -------------- */

.doc-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 8pt;
  margin: 0 0 3mm;
}

.doc-table-compact {
  font-size: 7.4pt;
}

/* Session 06j-b: the research-table Source column now carries a clickable
   label (export.js renderLinkedCell()), never the raw URL — this is the
   fix behind the over-wide-document defect. Every research table cell
   wraps normally so a long positioning/class description or a multi-link
   Source cell never forces the table wider than the page again. */
.doc-table-compact td,
.doc-table-compact th {
  overflow-wrap: break-word;
}

.doc-source-link {
  color: var(--doc-gold);
  text-decoration: underline;
}

/* VR-DOC-09, Session 05l Task 2 — the two table variants that carry a
   Provenance column (Development Cost, 4 columns; the per-unit compact
   table, 3 columns) each get an explicit <colgroup> (export.js
   addColgroup()) sized so Provenance lands 142mm from the body's left edge
   in both. `table-layout: fixed` makes the browser honour those widths
   exactly rather than auto-sizing around cell content — without it, a
   <colgroup> is only ever a hint. Scoped to this class, not `.doc-table`
   generally, because the research-extract and Assumptions & Sources
   tables have neither a Provenance column nor a fixed column count. */
.doc-table-provcols {
  table-layout: fixed;
}

.doc-table-provcols td,
.doc-table-provcols th {
  overflow-wrap: break-word;
}

/* =========================================================================
   The Competitive Set, on paper — Session 06p (export.js
   renderCompSetDocTable()).

   Ten metrics per property will not fit one readable portrait row, and the
   auto-layout table this replaces was the document's single widest element
   — the box Chrome's print engine scaled the WHOLE job down to fit (the
   tester round's two-thirds-size PDF, 20 Sep). Each property is two lines
   over ten fixed base columns (identity over metrics, one <tbody> per
   property); `table-layout: fixed` + the export's own <colgroup> contract
   (COMP_TABLE_COLS_MM, summing to the exact 180mm body) make the table's
   width deterministic and identical on screen and in print.
   `overflow-wrap: anywhere` is the floor-reducer `break-word` is not:
   under `table-layout: fixed` content wraps inside its declared column
   rather than pushing the table past the page. */
.doc-comp-table {
  table-layout: fixed;
  width: 180mm;
}

.doc-comp-table td,
.doc-comp-table th,
.doc-table.doc-comp-table td.num,
.doc-table.doc-comp-table th.num {
  overflow-wrap: anywhere;
  /* .num's white-space: nowrap (`.doc-table td.num`, below) is right for
     a short money figure in a sized column; here a "num" cell can carry
     the model's own longer text ("27 villas / 38 keys", an annotated rate
     range) and nowrap paints it across its neighbours — under the fixed
     layout a cell that cannot wrap simply overflows in place. Wrapping
     wins; the .num selectors are repeated at higher specificity because
     the nowrap rule is `.doc-table td.num` and would otherwise outrank
     the bare-class override. */
  white-space: normal;
}

/* A property's block reads as one unit: no rule between its own two lines
   (the identity line's default bottom border is removed), and the block
   closes with a firmer rule than the soft per-row one the base .doc-table
   td carries. */
.doc-comp-table tbody.doc-comp-property tr.doc-comp-identity td {
  border-bottom: 0;
  padding-bottom: 0.4mm;
}

.doc-comp-table tbody.doc-comp-property tr.doc-comp-metrics td {
  border-bottom: 0.6pt solid var(--doc-rule);
  padding-top: 0.4mm;
}

.doc-comp-table td.doc-comp-name {
  font-weight: 600;
}

/* The metrics header line is the quieter of the two — same family as the
   th treatment above, one step smaller. */
.doc-comp-table thead tr.doc-comp-metrics th {
  font-size: 6.2pt;
  font-weight: 500;
  border-bottom: 0.75pt solid var(--doc-rule);
  padding-top: 0;
}

.doc-comp-table thead tr.doc-comp-identity th {
  border-bottom: 0;
}

.doc-table th {
  /* VR-DOC-15, Session 06k — the screen-only sticky-header rule above
     (`thead th { position: sticky; ... }`) is an element selector with no
     document-side reset, so every exported table header inherited it:
     harmless while scrolling the live app, but Chrome's print/PDF pagination
     computes a sticky element's position per physical page rather than per
     scroll offset, so on any page after the first the header could paint
     over its own table's rows instead of sitting above them. A printed
     document never scrolls — position:sticky has no legitimate purpose here
     — so this resets it outright rather than trying to bound its travel. */
  position: static;
  top: auto;
  background: transparent;
  z-index: auto;
  text-align: left;
  font-size: 6.8pt;
  letter-spacing: 0.9pt;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--doc-muted);
  border-bottom: 0.75pt solid var(--doc-rule);
  padding: 1.2mm 2mm;
}

.doc-table th.num,
.doc-table td.num {
  text-align: right;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Session 05g, defect 7 — text left, numbers right, percentages centred
   (CLAUDE.md, every document this firm produces), applied on top of .num's
   money/mono styling rather than replacing it. */
.doc-table th.num.pct,
.doc-table td.num.pct {
  text-align: center;
}

.doc-table td {
  padding: 0.8mm 2mm;
  border-bottom: 0.4pt solid var(--doc-rule-soft);
  vertical-align: top;
}

/* VR-DOC-15, Session 06k, item 6 — a select label displayed in the Value
   column (export.js) is text, not a figure: explicit here (rather than
   relying on the absence of `.num`) so it can never silently inherit
   `.num`'s `white-space: nowrap` from a later, more specific rule. */
.doc-table td.doc-cell-text {
  white-space: normal;
  text-align: left;
}

.doc-table tr {
  break-inside: avoid;
  page-break-inside: avoid;
}

.doc-row-total td {
  border-top: 0.75pt solid var(--doc-rule);
  border-bottom: none;
  font-weight: 600;
}

.doc-row-group td {
  background: var(--doc-gold-pale);
  font-size: 6.8pt;
  letter-spacing: 1.1pt;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--doc-gold);
}

.doc-cell-sub {
  color: var(--doc-muted);
  font-size: 7pt;
}

/* VR-DOC-14 — an open item inside a table cell. Orange, so it reads as
   something still to be settled rather than a failure (CLAUDE.md), but
   without the solid badge treatment `.doc-badge-unverified` carries, which
   is sized for a research block and would shout inside a figure cell. */
.doc-cell-tbv {
  color: var(--doc-unverified-ink);
  font-size: 7pt;
}

/* Session 06p — the note variant sits on its own line under the figure
   and wraps normally: its parent td is `.num` (nowrap), and inline it
   painted across the row into the Provenance chip — a real overlap the
   harness only began SEEING once buildReport() refused hidden-root
   (vacuous) measurements. */
.doc-cell-tbv-note {
  display: block;
  white-space: normal;
  overflow-wrap: break-word;
}

/* --- Provenance chips — VR-EXPORT-03 ---------------------------------- */

.doc-prov {
  display: block;
  font-size: 6.6pt;
  line-height: 1.35;
}

.doc-prov-label {
  display: inline-block;
  letter-spacing: 0.7pt;
  text-transform: uppercase;
  font-weight: 600;
  padding: 0.3mm 1.2mm;
  border: 0.4pt solid var(--doc-rule);
  border-radius: 0.6mm;
  color: var(--doc-muted);
}

.doc-prov-benchmark .doc-prov-label {
  color: var(--doc-sourced-ink);
  border-color: var(--doc-sourced-rule);
  background: var(--doc-sourced-bg);
  font-weight: 700;
}

/* The whole row is tinted, not just the chip: a reader scanning the value
   column should see which figures carry a source without tracking across to
   the provenance column. */
.doc-table tr.doc-row-sourced td {
  background: var(--doc-sourced-bg);
}

.doc-table tr.doc-row-sourced td.num {
  color: var(--doc-sourced-ink);
  font-weight: 500;
}

.doc-prov-entered .doc-prov-label {
  color: var(--doc-ink-soft);
}

/* Session 06p — a figure the app derived (total building area × an entered
   share) is neither "Not entered" nor an analyst's own number; the chip
   names the derivation in its detail line. Same quiet register as
   Analyst entry — the loud treatment stays reserved for sourced figures. */
.doc-prov-derived .doc-prov-label {
  color: var(--doc-ink-soft);
}

.doc-prov-detail {
  display: block;
  color: var(--doc-muted);
  margin-top: 0.6mm;
}

/* --- Business unit blocks --------------------------------------------- */

/* An SPV and its business units read as one block: the entity's own numbers
   at the top, then the assumptions behind them. */
.doc-spv-group {
  break-inside: avoid;
  page-break-inside: avoid;
  margin-top: 2mm;
  background: var(--doc-gold-pale);
  border-left: 1mm solid var(--doc-gold);
  padding: 1.8mm 3mm;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

/* Title and figures share one row. Stacked, each SPV block cost 22mm and two
   of them pushed the page over; inline they cost half that and read better —
   the entity and its numbers on one line. */
.doc-spv-group {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
}

.doc-spv-head {
  display: flex;
  align-items: baseline;
  gap: 3mm;
  margin-bottom: 0;
}

.doc-spv-title {
  font-family: var(--font-display);
  font-size: 9.5pt;
  font-weight: 600;
  letter-spacing: 1.2pt;
  text-transform: uppercase;
  margin: 0;
  color: var(--doc-navy);
}

.doc-spv-count {
  font-size: 6.6pt;
  letter-spacing: 0.8pt;
  text-transform: uppercase;
  color: var(--doc-muted);
}

/* Label and figure sit side by side on one baseline, so the whole block is a
   single line rather than a two-row grid. */
.doc-spv-figures {
  display: flex;
  align-items: baseline;
  gap: 1.2mm 4mm;
  flex-wrap: wrap;
  margin: 0;
}

.doc-spv-figures dt {
  font-size: 6.2pt;
  letter-spacing: 0.7pt;
  text-transform: uppercase;
  color: var(--doc-muted);
  font-weight: 600;
}

.doc-spv-figures dd {
  margin: 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 8.2pt;
  color: var(--doc-ink);
}

.doc-spv-gate {
  font-family: var(--font-body) !important;
  font-size: 6.8pt !important;
  color: var(--doc-gold) !important;
  font-weight: 600;
}

.doc-unit {
  break-inside: avoid;
  page-break-inside: avoid;
  margin-bottom: 2mm;
  border-top: 1pt solid var(--doc-gold);
  padding-top: 2.5mm;
}

.doc-unit-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s4);
  margin-bottom: 1.2mm;
}

.doc-unit-persp {
  font-size: 6.8pt;
  letter-spacing: 0.9pt;
  text-transform: uppercase;
  color: var(--doc-muted);
}

/* --- Verdict ----------------------------------------------------------- */

.doc-verdict {
  background: var(--doc-navy);
  color: var(--doc-paper);
  padding: 6mm;
  margin-bottom: 6mm;
  border-left: 2mm solid var(--doc-gold);
  break-inside: avoid;
  page-break-inside: avoid;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

.doc-verdict-eyebrow {
  font-size: 6.8pt;
  letter-spacing: 1.4pt;
  text-transform: uppercase;
  margin: 0 0 2mm;
  opacity: 0.75;
}

.doc-verdict-word {
  font-family: var(--font-display);
  font-size: 26pt;
  font-weight: 700;
  letter-spacing: 2.4pt;
  text-transform: uppercase;
  line-height: 1;
  margin: 0 0 2.5mm;
}

.doc-verdict-pass .doc-verdict-word { color: var(--doc-verdict-pass); }
.doc-verdict-fail .doc-verdict-word { color: var(--doc-verdict-fail); }
.doc-verdict-strategic .doc-verdict-word { color: var(--doc-verdict-strategic); }
.doc-verdict-insufficient .doc-verdict-word { color: var(--doc-verdict-insufficient); }

.doc-verdict-roll {
  font-family: var(--font-mono);
  font-size: 7.6pt;
  margin: 0;
  opacity: 0.9;
}

/* Session 05g, defect 1 — an implausible margin must be visible in red
   where the verdict is given, not only in the methodology notes at the
   back. --doc-verdict-fail is already tuned for legibility on this navy
   banner (see .doc-verdict-fail .doc-verdict-word above). */
/* VR-BENCH-09, Session 06x — surface 3 of three, and the one a reader is
   most likely to meet alone: an exported document travels without the
   screen beside it. Same words as the rail's (app.js olderDataStatement()),
   in the document's own open-item ink — the "To be verified and confirmed"
   register, not the fail register beneath it. */
.doc-verdict-older-data {
  font-family: var(--font-body);
  font-size: 7.6pt;
  line-height: 1.45;
  font-weight: 600;
  color: var(--doc-unverified-ink);
  margin: 1.5mm 0 0;
  max-width: 150mm;
}

.doc-verdict-caveat {
  font-family: var(--font-mono);
  font-size: 7.6pt;
  font-weight: 700;
  color: var(--doc-verdict-fail);
  margin: 1.5mm 0 0;
}

.doc-verdict-tests {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s4);
  margin-top: 4mm;
  padding-top: 4mm;
  border-top: 0.5pt solid var(--doc-on-navy-rule);
}

.doc-verdict-test-label {
  font-size: 6.6pt;
  letter-spacing: 1.1pt;
  text-transform: uppercase;
  margin: 0 0 1mm;
  opacity: 0.75;
}

.doc-verdict-figure {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 9.5pt;
  margin: 0 0 0.8mm;
  /* Session 05g, defect 7 — right by default (this is the payback test's
     month count); .pct (the margin test's percentage) centres instead. */
  text-align: right;
}

.doc-verdict-margin {
  font-size: 7pt;
  margin: 0;
  opacity: 0.8;
  text-align: right;
}

.doc-verdict-figure.pct,
.doc-verdict-margin.pct {
  text-align: center;
}

.doc-verdict-breakeven {
  margin: 4mm 0 0;
  padding-top: 3mm;
  border-top: 0.5pt solid var(--doc-on-navy-rule);
  font-size: 7.6pt;
  opacity: 0.92;
}

.doc-rationale {
  margin-top: 3mm;
  padding: 2.5mm 3mm;
  border-left: 0.8mm solid var(--doc-verdict-strategic);
  background: var(--doc-on-navy-wash);
}

.doc-rationale-label {
  font-size: 6.4pt;
  letter-spacing: 1.1pt;
  text-transform: uppercase;
  margin: 0 0 1mm;
  opacity: 0.75;
}

.doc-rationale-text {
  font-size: 7.8pt;
  margin: 0;
  font-style: italic;
}

/* --- KPI strip --------------------------------------------------------- */

/* Three bands — label, figure, caption — each one consistent height across
   every cell, so the row of figures shares a single baseline whatever the
   label above it does. "Total Development Cost" wraps to two lines while the
   other four labels do not; without this the most-read line in the document
   staggers. Fixed structurally, not by shortening the label to dodge it.
   Session 05b, defect 3. */
.doc-kpis {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  grid-template-rows: auto auto auto;
  border: 0.5pt solid var(--doc-rule);
  margin-bottom: 5mm;
  break-inside: avoid;
}

.doc-kpi {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
  padding: 3mm 2mm;
  border-right: 0.4pt solid var(--doc-rule-soft);
}

/* Session 06k, item 7 — `container-type` used to sit on `.doc-kpi` itself
   (VR-DOC-07, Session 05k-d), but size containment makes an element an
   independent layout root, which disqualifies it from participating in
   its PARENT's subgrid — so `.doc-kpi`'s own `grid-template-rows: subgrid`
   silently stopped working the moment VR-DOC-07 added the container query
   beside it, and every card whose label wrapped to two lines carried its
   value at a different height from the rest, live-verified on Michele's
   own Venice record. Moving the query onto this inner wrapper — one of
   THREE ordinary children `.doc-kpi` places into the subgrid's three row
   tracks, not the subgrid item itself — keeps `.doc-kpi` a plain subgrid
   participant while `.doc-kpi-value`'s `cqi` clamp still resolves against
   a real inline-size context. */
.doc-kpi-valuebox {
  container-type: inline-size;
  /* Moved from .doc-kpi-value below — the wrapper is now the actual
     subgrid item (row 2 of 3), so IT anchors to the row's own bottom edge,
     not the <p> inside it. */
  align-self: end;
}

.doc-kpi:last-child {
  border-right: none;
}

.doc-kpi-label {
  font-size: 6.2pt;
  letter-spacing: 0.9pt;
  text-transform: uppercase;
  color: var(--doc-muted);
  font-weight: 600;
  margin: 0 0 1.4mm;
  align-self: start;
}

.doc-kpi-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  /* Sized to the longest figure the strip realistically carries. The ISO
     prefix (VR-EXPORT-01) adds four characters to every money KPI, and at
     10.5pt "USD 24,200,000" overflowed a 30.6mm cell by half a millimetre
     and wrapped — so two tiles showed a two-line figure and three showed a
     one-line one. Measured: DM Mono here is 2.08mm/char, the cell is
     31.6mm, so this fits 15 characters on one line.
     VR-DOC-07, Session 05k-d: 15 characters is not a hard ceiling — Venice's
     "EUR 109,895,262" is 16, and .doc-kpi's own comment above already
     documents that the fixed 10pt size overflows past that length rather
     than truncating (no overflow:hidden is set here), which bleeds text
     across the cell's border rather than clipping it — visually broken
     either way. A container-query clamp replaces the fixed size so a
     longer figure shrinks to the cell instead. */
  font-size: clamp(7pt, 6.4cqi, 10pt);
  letter-spacing: -0.1pt;
  margin: 0 0 1.2mm;
  line-height: 1.1;
  /* formatMoneyDocument() joins the ISO code and figure with a non-breaking
     space (Session 05c, defect 2) — this is a hard guarantee against that
     regressing, matching .kpi-value's screen-side reinforcement. */
  white-space: nowrap;
  /* Session 05g, defect 7 — numbers right by default; .pct (Developer
     Margin) overrides to centred below. */
  text-align: right;
}

.doc-kpi-value.pct {
  text-align: center;
}

.doc-kpi-compare {
  font-size: 6.2pt;
  color: var(--doc-muted);
  margin: 0;
  align-self: start;
}

.doc-buyer {
  border-left: 1mm solid var(--doc-gold);
  padding-left: 4mm;
  break-inside: avoid;
}

.doc-deflist {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 1.5mm var(--s4);
  margin: 0;
  max-width: 120mm;
}

.doc-deflist dt {
  font-size: 7.4pt;
  color: var(--doc-ink-soft);
}

.doc-deflist dd {
  margin: 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 8.4pt;
  text-align: right;
}

/* Session 05g, defect 7 — percentages centred, not right-aligned. */
.doc-deflist dd.pct {
  text-align: center;
}

/* Session 06p — the suffix slot. A suffix ("total", "/ yr") printed inside
   a right-aligned figure pushes that row's digits left of its unsuffixed
   neighbours (Michele's tester-round PDF, Key Figures by Perspective).
   Every dd in these lists is value + a fixed-width suffix slot — empty on
   suffixless rows — so the digits share one right edge down the column. */
.doc-deflist dd.doc-figure-dd {
  display: flex;
  justify-content: flex-end;
  align-items: baseline;
}

.doc-figure-suffix {
  flex: 0 0 8mm;
  width: 8mm;
  text-align: left;
  padding-left: 1.2mm;
  font-size: 7pt;
  color: var(--doc-muted);
  font-family: var(--font-body);
}

/* Session 06k, item 4 — the Development Cost / Costs of Sale tables above
   this block use a fixed 62mm+42mm colgroup (COST_TABLE_COLS_MM,
   export.js) so their Amount column's right edge lands 104mm from the
   page body's left edge (VR-DOC-09). `.doc-deflist`'s own `1fr auto`
   columns size to content instead, landing its numbers on a different,
   further-right axis — three blocks on the same page, two columns.
   Fixing both tracks to the SAME 62mm/42mm widths puts this block's
   numbers on that same 104mm line; `max-width` is dropped since the
   tracks are now explicit rather than content-driven. A label too long
   for 62mm wraps in its own column, the same behaviour the tables above
   already accept for an over-long line-item name. */
.doc-deflist-costaxis {
  grid-template-columns: 62mm 42mm;
  max-width: none;
}

/* Session 05l, Task 5 — Net Profit, beneath the costs-of-sale block, reads
   as unmistakably a total: a rule above it and bold weight, the same
   visual language as .doc-row-total on the tables it sits beneath. */
.doc-net-profit-label,
.doc-net-profit-value {
  border-top: 0.75pt solid var(--doc-rule);
  padding-top: 1.2mm;
  font-weight: 600;
}

.doc-net-profit-value {
  font-size: 9.5pt;
}

/* Session 05l, Task 6 — the three-perspective mirror of the on-screen Blue
   Box. Each section's own heading sits close to its own figures, with
   breathing room before the next perspective's block. */
.doc-perspective-block {
  margin: 0 0 5mm;
  break-inside: avoid;
}

.doc-perspective-block .doc-h4 {
  margin-bottom: 2mm;
}

/* =========================================================================
   PAGE 4 — sourced vs unverified.

   The whole condition on which research was allowed into a client-facing
   document: a reader must never mistake an unsourced claim for a sourced
   one, without hovering, clicking or reading small print. So the two get
   different grounds, different rules and an explicit badge — not a footnote
   marker and not a tooltip.
   ========================================================================= */

.doc-research-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s4);
  margin-bottom: 3mm;
}

.doc-research-meta {
  font-family: var(--font-mono);
  font-size: 7pt;
  color: var(--doc-muted);
  margin: 0;
}

.doc-stale {
  font-size: 7pt;
  font-weight: 600;
  color: var(--doc-unverified-ink);
  margin: 0;
}

.doc-legend {
  display: flex;
  gap: var(--s5);
  padding: 2.5mm 3mm;
  border: 0.5pt solid var(--doc-rule);
  margin-bottom: 5mm;
  font-size: 7pt;
}

.doc-legend-item {
  display: flex;
  align-items: center;
  gap: 2mm;
}

.doc-legend-swatch {
  width: 4mm;
  height: 4mm;
  flex: 0 0 auto;
  border: 0.5pt solid var(--doc-rule);
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

.doc-legend-sourced .doc-legend-swatch {
  background: var(--doc-paper);
  border-left: 1mm solid var(--doc-sourced-rule);
}

.doc-legend-unverified .doc-legend-swatch {
  background: var(--doc-unverified-bg);
  border-left: 1mm solid var(--doc-unverified-rule);
}

.doc-research-section {
  margin-bottom: 2.5mm;
}

/* Session 05f — .doc-research-block already carries break-inside: avoid,
   but the section's own heading did not, so a page break could land
   between the heading and its first block: the heading ends one page, the
   content starts the next with no title and no running header. Glue the
   heading to what follows it, lighter-weight than avoiding the whole
   section (which risks pushing a large section entirely to the next page). */
.doc-research-section .doc-h3 {
  break-after: avoid;
  page-break-after: avoid;
}

.doc-research-block {
  break-inside: avoid;
  page-break-inside: avoid;
  margin-bottom: 2mm;
  padding: 2.5mm 3mm;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

.doc-research-sourced {
  border-left: 1mm solid var(--doc-sourced-rule);
  background: var(--doc-paper);
}

/* Loud on purpose. This is the treatment that has to read across a room. */
.doc-research-unverified,
.doc-research-gap {
  border-left: 1mm solid var(--doc-unverified-rule);
  background: var(--doc-unverified-bg);
}

.doc-research-text {
  margin: 0;
  font-size: 7.8pt;
  line-height: 1.5;
  white-space: pre-wrap;
}

.doc-badge-unverified,
.doc-badge-gap,
.doc-badge-sample {
  display: inline-block;
  font-size: 6.4pt;
  letter-spacing: 1.2pt;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--doc-paper);
  background: var(--doc-unverified-rule);
  padding: 0.6mm 2mm;
  margin: 0 0 1.8mm;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

/* VR-DOC-19, Session 07d: a sample's badge takes the same neutral ink as
   the no-figure badge and never the open-item orange — there is nothing on
   invented data for a reader to go and verify. The BLOCK keeps its tint
   (Michele's ruling, 26 Sep): only the wording changes. */
.doc-badge-gap,
.doc-badge-sample {
  background: var(--doc-muted);
}

.doc-badge-sourced {
  font-size: 6.4pt;
  letter-spacing: 1.2pt;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--doc-sourced-ink);
  margin: 1.8mm 0 0;
}

.doc-truncated {
  font-size: 6.6pt;
  font-style: italic;
  color: var(--doc-muted);
  margin: 1.5mm 0 0;
}

/* A cell whose value the research did not establish. The quiet mark that
   lets the table around it be judged on what it does say (Session 05b,
   defect 4) — the rule that an unsourced figure is visibly unsourced,
   applied to the cell rather than to the whole table. */
.doc-cell-unknown {
  color: var(--doc-muted);
  text-align: center;
}

.doc-table-note {
  font-size: 6.6pt;
  color: var(--doc-muted);
  margin: 1.2mm 0 0;
}

.doc-research-footnote {
  font-size: 7pt;
  color: var(--doc-muted);
  border-top: 0.5pt solid var(--doc-rule);
  padding-top: 2.5mm;
  margin-top: 4mm;
}

/* --- Back page --------------------------------------------------------- */

.doc-sources {
  margin: 0 0 2.5mm;
  padding-left: 4mm;
  font-size: 7.4pt;
  color: var(--doc-ink-soft);
}

.doc-sources li {
  margin-bottom: 0.7mm;
  break-inside: avoid;
}

.doc-disclaimer {
  font-size: 6.8pt;
  color: var(--doc-muted);
  line-height: 1.45;
  margin: 0 0 2mm;
  text-align: justify;
}

.doc-contact {
  border-top: 1pt solid var(--doc-gold);
  padding-top: 2mm;
}

.doc-contact-name {
  font-family: var(--font-display);
  font-size: 9.5pt;
  font-weight: 600;
  letter-spacing: 1.4pt;
  text-transform: uppercase;
  margin: 0 0 1mm;
  color: var(--doc-navy);
}

.doc-contact-line {
  font-size: 7.2pt;
  color: var(--doc-muted);
  margin: 0;
}

/* The operating nodes, given real presence. This is the one place the
   document advertises the firm's reach. */
.doc-contact-nodes {
  margin: 1.5mm 0;
  display: flex;
  align-items: baseline;
  gap: 2.5mm;
  flex-wrap: wrap;
}

.doc-node {
  font-family: var(--font-display);
  font-size: 8.4pt;
  font-weight: 600;
  letter-spacing: 1.5pt;
  text-transform: uppercase;
  color: var(--doc-navy);
}

.doc-node-sep {
  color: var(--doc-gold);
  font-size: 8.4pt;
}

/* Session 06j: groups .doc-foot-firm and the new .doc-foot-beta so the
   running foot's own space-between layout (this block vs. the date/version
   meta) still reads as two items, not three spread across the width. */
.doc-foot-left {
  display: flex;
  flex-direction: column;
  gap: 0.5mm;
}

/* The footer repeats on every page, so it stays quiet — but it must still
   name the firm, its base and where to find it. */
.doc-foot-firm {
  letter-spacing: 0.7pt;
  text-transform: none;
}

/* Session 06j: the Beta disclaimer — DRAFT wording, Michele's to correct.
   Quieter than the firm line it sits under, never competing with the
   confidentiality mark in the running head above. */
.doc-foot-beta {
  font-size: 5.8pt;
  letter-spacing: 0.5pt;
  text-transform: none;
  color: var(--doc-muted);
}

/* .doc-refusal-lede is GONE — Session 06x. The export refusal it styled
   (an out-of-window benchmark) was withdrawn by Michele on 23 Sep 2026;
   see export.js, where buildRefusal() used to live. */

/* =========================================================================
   Print rules
   ========================================================================= */

@page {
  size: A4;
  margin: 0;
}

@media print {
  /* The screen body is a flex column with min-height:100vh. Left in place,
     that flex context stretches the page boxes and Chrome emits a blank
     sheet after every page. Reset it to plain block flow before anything
     else. */
  html,
  body {
    display: block !important;
    min-height: 0 !important;
    height: auto !important;
    margin: 0 !important;
    padding: 0 !important;
    background: none !important;
  }

  /* Only the document prints. The screen app is not a report and never
     was — hiding it wholesale is what makes the pagination predictable. */
  body > *:not(.export-root) {
    display: none !important;
  }

  /* The on-screen preview rule `:root[data-printing] .export-root` is more
     specific than a bare `.export-root`, and a media query adds no
     specificity of its own — so without matching that selector here, the
     preview's 32px padding survives into the print and spills a trailing
     blank sheet. `data-printing` is set during the real print, so this is
     not hypothetical. */
  :root[data-printing="true"] .export-root,
  .export-root {
    display: block !important;
    background: none !important;
    padding: 0 !important;
    margin: 0 !important;
  }

  /* Break BEFORE each page rather than after. `break-after: always` on every
     page emits an empty final sheet — the break after the last page has to
     land somewhere — and a `:last-child` exception did not reliably suppress
     it in Chrome. Breaking before every page but the first has no such
     trailing break to place. */
  .doc-page {
    margin: 0;
    page-break-before: always;
    break-before: page;
    page-break-after: auto;
    break-after: auto;
    box-shadow: none;
  }

  .doc-page:first-child {
    page-break-before: auto;
    break-before: auto;
  }

  /* Backgrounds carry meaning here — the verdict band and, above all, the
     unverified-research ground. Losing them to a printer's default would
     erase the one distinction page 4 exists to make. */
  * {
    -webkit-print-color-adjust: exact !important;
    print-color-adjust: exact !important;
  }
}

/* Logo pickers on Step 1 — a named-asset select, plus a path field when the
   user chooses "Other file…". */
.logo-picker {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}

/* =========================================================================
   VR-PERSIST-22, Session 06u — the server-stopped modal.

   Michele's finding 18: the app said nothing when its own server died and
   only contradicted itself at save time. His ruling was a PROMINENT POPUP,
   not a text notice — so this is an in-app overlay, never alert()/confirm()
   (those block the page, cannot be styled, and read as the browser rather
   than as this app; the same ban confirmInline() already works under).

   Deliberately quiet in tone despite being prominent: nothing has been
   lost, and the panel's own first sentence says so. The strategic amber
   rather than the fail red for exactly that reason — this is an open item
   to resolve, not a failure, the same register CLAUDE.md sets for
   "To be verified and confirmed".
   ========================================================================= */

.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s5);
  background: rgba(44, 40, 38, 0.55);
}

.modal-overlay[hidden] {
  display: none;
}

.modal-panel {
  width: min(520px, 100%);
  max-height: 100%;
  overflow-y: auto;
  background: var(--surface);
  border: var(--rule) solid var(--line-strong);
  border-top: 3px solid var(--strategic);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--s6);
}

.modal-eyebrow {
  margin: 0 0 var(--s2);
  font-family: var(--font-body);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--strategic);
}

.modal-title {
  margin: 0 0 var(--s4);
  font-family: var(--font-display);
  font-size: var(--step-2);
  letter-spacing: 0.02em;
  color: var(--ink);
}

.modal-body {
  margin: 0 0 var(--s4);
  font-size: var(--step-0);
  line-height: 1.6;
  color: var(--ink-soft);
  max-width: var(--measure);
}

.modal-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-top: var(--s5);
}

.modal-note {
  margin: var(--s4) 0 0;
  font-size: var(--step--1);
  line-height: 1.5;
  color: var(--strategic);
}

/* The phone layout (spec/07's card stack): the panel fills the width and
   its actions stack full-width into the thumb zone rather than sitting as
   two small targets side by side. */
@media (max-width: 640px) {
  .modal-overlay {
    padding: var(--s4);
    align-items: flex-end;
  }

  .modal-panel {
    padding: var(--s5);
  }

  .modal-actions {
    flex-direction: column;
  }

  .modal-actions .btn {
    width: 100%;
  }
}

/* Session 06u, Michele's finding 13 — "What this line typically includes",
   under Construction Cost and Soft Costs. Reuses .disclosure-panel's own
   chrome (the +/− summary) rather than inventing a second collapsible
   look. Text left, per the standing alignment rule: this is prose, not
   figures. It informs and never gates — no validation touches it. */
.field-typical-items {
  margin-top: var(--s3);
  max-width: var(--measure);
}

.field-typical-list {
  margin: var(--s3) 0 0;
  padding-left: var(--s5);
  display: flex;
  flex-direction: column;
  gap: var(--s1);
  font-size: var(--step--1);
  line-height: 1.5;
  color: var(--ink-soft);
  text-align: left;
}

.field-typical-note {
  margin-top: var(--s3);
}

/* =========================================================================
   VR-UNIT-15, Session 06x — the Asset sale's operating-unit picker.

   Michele's ruling: an Asset sale may cover several operating units, so the
   single <select> became a checkbox list. One row per unit, each captioned
   with that unit's own exit value, so the reader sees what ticking it adds
   before they tick it. Single column (spec/05 section 7), and the exit
   figure wraps under the name rather than pushing the row at 390px.
   ========================================================================= */

.asset-sale-units {
  margin-top: var(--s4);
}

.asset-sale-unit-list {
  list-style: none;
  margin: var(--s3) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}

.asset-sale-unit-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s2);
  padding: var(--s2) var(--s3);
  border: var(--rule) solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}

.asset-sale-unit-check {
  flex: 0 0 auto;
}

.asset-sale-unit-label {
  flex: 1 1 auto;
  font-family: var(--font-body);
  font-size: var(--step--1);
  cursor: pointer;
}

/* Numbers right (CLAUDE.md, standing instruction), and on its own full
   line below the name once the row runs out of width. */
.asset-sale-unit-exit {
  flex: 0 0 auto;
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--muted);
}

@media (max-width: 560px) {
  .asset-sale-unit-exit {
    flex-basis: 100%;
    margin-left: 0;
    text-align: left;
  }
}

/* VR-PERSIST-07, Session 06x — the months/years chip in a duration field's
   suffix slot. Sized and weighted exactly like .currency-chip in the prefix
   slot, which is the control it mirrors: one compact select, inside the
   field frame, naming the unit the number beside it is written in. */
.field-affix-duration {
  display: flex;
  align-items: center;
}

.duration-chip {
  font-family: var(--font-body);
  font-size: var(--step--2);
  letter-spacing: 0.5px;
  color: var(--muted);
  background: transparent;
  border: 0;
  padding: 0 var(--s1);
  cursor: pointer;
}

.duration-chip:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* =========================================================================
   VR-INPUT-07, Session 06x — typical entries offered inside the field.

   The same visual language as .location-suggest-list (VR-INPUT-05, 06t),
   which is the mechanism this reuses. It differs in one way only: this list
   anchors to its own input wrapper rather than to a whole field row, so it
   needs no 12rem label-column offset at desktop widths — the inclusion row
   has no label column to clear.
   ========================================================================= */

.inclusion-label-wrap {
  position: relative;
  display: block;
  flex: 1 1 14rem;
  min-width: 0;
}

.inclusion-label-wrap .inclusion-input-label {
  width: 100%;
}

.suggest-list {
  position: absolute;
  z-index: 5;
  top: 100%;
  left: 0;
  right: 0;
  margin: 2px 0 0;
  padding: 0;
  list-style: none;
  background: var(--field-bg);
  border: var(--rule) solid var(--field-border);
  border-radius: var(--radius);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
  max-height: 14rem;
  overflow-y: auto;
}

.suggest-item {
  padding: var(--s2) var(--s3);
  font-family: var(--font-body);
  font-size: var(--step--1);
  cursor: pointer;
}

.suggest-item:hover,
.suggest-item:focus {
  background: var(--surface-muted);
}

/* =========================================================================
   Session 06x, Michele's finding 5 — the figure row per perspective, on the
   Business Units group header.

   Revenue, Net / GOP, EBITDA and Exit value, each NAMED — four bare numbers
   under a heading would be unreadable. The label is small and quiet, the
   figure is mono and tabular, and the block pushes to the right of the
   heading until the header runs out of width, at which point it wraps to
   its own full-width line rather than squeezing the heading.
   ========================================================================= */

.unit-group-figures {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s4);
  margin-left: auto;
  align-items: baseline;
}

.unit-group-figure {
  display: flex;
  flex-direction: column;
  align-items: flex-end; /* numbers right — CLAUDE.md, standing instruction */
  gap: 1px;
}

.unit-group-figure-label {
  font-family: var(--font-body);
  font-size: var(--step--2);
  letter-spacing: 0.8px;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--muted);
}

.unit-group-figure-value {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
}

@media (max-width: 700px) {
  .unit-group-figures {
    flex-basis: 100%;
    margin-left: 0;
    justify-content: space-between;
  }
}

/* ===========================================================================
   Session 08 — the hosted edition's own two pages, and the account block
   in the app's topbar.

   Direction B (spec/07) on one column: the same charcoal topbar, the same
   champagne rule, the same Cinzel title as every screen in the app. A
   visitor should not be able to tell that the page they sign in on and the
   page they land on were written a month apart.

   These rules are additive. Nothing above is touched, so a mode that never
   shows any of this — the USB bundle, 127.0.0.1:4400 — renders exactly as
   it rendered before this session.
   =========================================================================== */

.auth-body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--page);
}

.auth-main {
  flex: 1 1 auto;
  width: min(var(--measure), calc(100vw - var(--s6)));
  margin: 0 auto;
  padding: var(--s8) 0 var(--s7);
}

/* The admin list needs a table's worth of width; the sign-in form does not
   and must not have it — a single column of two fields set across 1100px
   reads as a form somebody forgot to lay out. */
.auth-main-wide {
  width: min(1160px, calc(100vw - var(--s6)));
}

.auth-card {
  background: var(--surface);
  border: var(--rule) solid var(--line);
  box-shadow: var(--shadow-soft);
  padding: var(--s7) var(--s6) var(--s6);
}

.auth-title {
  font-family: var(--font-display);
  font-size: var(--step-3);
  font-weight: 600;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--ink);
  margin: var(--s2) 0 0;
}

.auth-title::after {
  content: "";
  display: block;
  width: 64px;
  height: 2px;
  background: var(--gold);
  margin-top: var(--s3);
}

.auth-lede {
  margin: var(--s4) 0 0;
  color: var(--ink-soft);
  font-size: var(--step-0);
  line-height: 1.6;
}

.auth-form {
  margin-top: var(--s6);
  display: flex;
  flex-direction: column;
  gap: var(--s5);
}

.auth-field {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}

/* spec/05 section 4: an input must LOOK like an input. Its own border and
   white ground, not the app's composed .field-control frame — this page has
   no affixes, no steppers and no chips to compose around. */
.auth-input {
  background: var(--field-bg);
  border: var(--rule) solid var(--field-border);
  border-radius: var(--radius);
  padding: var(--s3);
  font-family: var(--font-body);
  font-size: var(--step-1);
  color: var(--field-ink);
  width: 100%;
}

.auth-input:focus {
  outline: 2px solid var(--focus);
  outline-offset: 1px;
}

.auth-hint {
  margin: 0;
  font-size: var(--step--1);
  color: var(--muted);
  line-height: 1.5;
}

/* Two registers, and the difference between them matters. A warning is a
   thing that went wrong; an open item is a thing not yet settled, and
   Michele's standing instruction (9 Sep 2026) is that an unknown is stated
   forward in orange, never condemned. "Your account is awaiting approval"
   is an open item, so that is the register it is set in. */
.auth-message {
  margin: 0;
  padding: var(--s3) var(--s4);
  border-left: 3px solid var(--fail);
  background: var(--fail-soft);
  color: var(--ink);
  font-size: var(--step-0);
  line-height: 1.55;
}

.auth-message[data-tone="open"] {
  border-left-color: var(--strategic);
  background: var(--strategic-soft);
}

.auth-actions {
  display: flex;
  gap: var(--s3);
}

.auth-actions .btn {
  min-width: 11rem;
}

.auth-switch {
  margin: var(--s6) 0 0;
  padding-top: var(--s4);
  border-top: var(--rule) solid var(--line);
  font-size: var(--step--1);
  color: var(--muted);
}

.btn-link {
  appearance: none;
  background: none;
  border: 0;
  padding: 0 0 1px;
  margin-left: var(--s2);
  font-family: var(--font-body);
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--gold-dark);
  border-bottom: var(--rule) solid var(--gold);
  cursor: pointer;
}

.btn-link:hover {
  color: var(--ink);
}

.auth-footer {
  /* Side padding, not just top and bottom: on a 390px phone this line wraps
     and without it the second line runs into both edges of the screen. */
  padding: var(--s5) var(--s4) var(--s6);
  text-align: center;
}

.auth-footer p {
  margin: 0;
  font-size: var(--step--1);
  color: var(--muted);
}

.auth-empty {
  margin: var(--s6) 0 0;
  color: var(--muted);
  font-size: var(--step-0);
}

.admin-table {
  width: 100%;
  margin-top: var(--s6);
  border-collapse: collapse;
}

.admin-table th {
  text-align: left;
  font-size: var(--step--1);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--ink-soft);
  border-bottom: 2px solid var(--gold);
  padding: 0 var(--s4) var(--s3) 0;
}

.admin-table td {
  text-align: left;
  padding: var(--s4) var(--s4) var(--s4) 0;
  border-bottom: var(--rule) solid var(--line);
  font-size: var(--step-0);
  color: var(--ink);
  vertical-align: middle;
}

.admin-col-actions {
  width: 1%;
  white-space: nowrap;
  padding-right: 0;
}

.admin-col-actions .btn + .btn {
  margin-left: var(--s3);
}

.topbar-link {
  font-size: var(--step--1);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--gold-light);
  text-decoration: none;
  border-bottom: var(--rule) solid transparent;
}

.topbar-link:hover {
  border-bottom-color: var(--gold);
}

/* The app's own topbar, hosted mode only. Hidden in every other mode, and
   hidden by the `hidden` attribute rather than a class, so a mode that
   never fills it never shows an empty frame. */
.topbar-account {
  display: flex;
  align-items: center;
  gap: var(--s3);
  font-size: var(--step--1);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--gold-light);
}

.topbar-account-name {
  max-width: 16rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.topbar-account .btn-quiet {
  color: var(--gold-light);
  border-color: transparent;
}

.topbar-account .btn-quiet:hover {
  color: var(--text-light);
  border-color: var(--gold);
}

/* The phone (spec/07, Direction C's screens): the card loses its frame and
   becomes the page, the way every other card in this app does. */
@media (max-width: 560px) {
  .auth-main {
    padding: var(--s6) 0 var(--s5);
  }

  .auth-card {
    padding: var(--s5) var(--s4);
  }

  .auth-actions .btn {
    width: 100%;
  }
}

/* The pending list stacks EARLIER than the card above does, and for a
   reason worth writing down: four columns whose last one is two buttons
   that must not wrap has an intrinsic width of roughly 700px, so at 768 it
   pushed out through the right edge of its own card — the horizontal
   overflow `spec/05` forbids outright. A tablet gets one card per person
   instead, which is also simply the better way to read a decision list. */
@media (max-width: 860px) {
  .admin-table,
  .admin-table thead,
  .admin-table tbody,
  .admin-table tr,
  .admin-table td {
    display: block;
    width: 100%;
  }

  .admin-table thead {
    display: none;
  }

  .admin-table tr {
    border-bottom: var(--rule) solid var(--line-strong);
    padding: var(--s4) 0;
  }

  .admin-table td {
    border: 0;
    padding: var(--s1) 0;
  }

  .admin-col-actions {
    white-space: normal;
    padding-top: var(--s3);
  }
}
