/* ============================================================================
   Aptura demo — component layer.  Build 6, Phase 2 (Skeleton).  Owner: R2.
   Requires styles/tokens.css (which imports the canonical design tokens).

   These are not a demo skin. They are the member deliverables staged: the
   readout chrome here is the same chrome the real S4 readout and the souvenir
   PDF use. Build accordingly.

   HOW THIS FILE IS ORGANISED
     0  Base and surface contexts
     1  Type
     2  Buttons and links
     3  Progress dots and the step counter
     4  The stages-by-lanes grid (decision 0003A)
     5  Readout page chrome (cover, inside page, logo plate)
     6  The fit-linework signature
     7  The reserved seat ("your analysis here")
     8  Motion utilities
    10  The shell: bar, foot, nav
    11  S0, the intake
    12  S2, the instrument
    13  S3, the read - THE REVEAL MOTION LAYER
    14  S4 and S5 - lists, roadmap, legend, deck
    15  S6, the math
    16  S6 and S7, fields and forms
    17  S8, the founding offer
    18  The print cover, and print (decision 0003B)

   THE SURFACE CONTRACT
     Every component reads its colors from context variables, so one component
     works on both grounds. Wrap any subtree in .d-on-dark or .d-on-light.
     Nesting is allowed; the nearest ancestor wins.

   CONTRAST
     Every pair introduced is computed and stated inline. Requirements are
     4.5:1 for body text and 3:1 for non-text and UI marks (WCAG 2.2 AA).
     --text-soft never appears on a dark surface. Signal is never a large fill
     on Bone. Text on dark is Bone, never white.
   ========================================================================= */

/* ==========================================================================
   0. BASE AND SURFACE CONTEXTS
   ========================================================================== */

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

.d-app {
  margin: 0;      /* .d-app is the <body>; the UA's 8px margin would frame the
                     whole ink walkthrough in a stripe of page background. */
  /* The canvas behind the frame. Without it an overscroll bounce shows the
     user agent's white (or black) through, which is the one colour in this
     app that belongs to nobody. */
  background: var(--ink-abyss);
  font-family: var(--font-body);
  font-weight: var(--fw-body);
  font-size: var(--fs-lead);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

.d-app img,
.d-app svg { max-width: 100%; }

/* --- Dark ground (Ink Abyss). S1, S3, the S4 cover, S7. ------------------ */
.d-on-dark {
  --surface: var(--ink-abyss);
  --surface-raised: var(--ink-deep);
  --fg: var(--bone);            /* Bone on Ink Abyss ........... 13.60:1 */
  --fg-soft: var(--text-soft-on-dark); /* Bone 0.55 ............  4.96:1 */
  --fg-accent: var(--signal);   /* Signal on Ink Abyss ......... 10.21:1 */
  --rule: var(--rule-on-dark);  /* Bone 0.40, non-text .........  3.24:1 */
  --rule-decor: var(--hairline-on-dark); /* decorative only ....  1.75:1 */
  --focus: var(--focus-on-dark);
  --btn-primary-bg: var(--signal);
  --btn-primary-fg: var(--ink-abyss); /* Ink Abyss on Signal ... 10.21:1 */
  background: var(--surface);
  color: var(--fg);
}

/* --- Light ground (Bone). S0, S2, the inside pages, S5, S6, S8. ---------- */
.d-on-light {
  --surface: var(--bone);
  --surface-raised: var(--bone-card);
  --fg: var(--text);            /* Text on Bone ................ 13.40:1 */
  --fg-soft: var(--text-soft);  /* Text Soft on Bone ...........  6.33:1 */
  --fg-accent: var(--signal-on-light); /* derived, on Bone .....  4.53:1 */
  --rule: var(--rule-strong);   /* non-text on Bone ............  4.18:1 */
  --rule-decor: var(--line);    /* decorative only .............  1.35:1 */
  --focus: var(--focus-on-light);
  --btn-primary-bg: var(--ink);
  --btn-primary-fg: var(--bone); /* Bone on Ink ................ 10.37:1 */
  background: var(--surface);
  color: var(--fg);
}

/* Raised panels inherit the ground and only change the fill. On Bone Card
   every ratio above improves: Text 15.06:1, Text Soft 7.11:1,
   --signal-on-light 5.10:1, --rule-strong 4.70:1. */
.d-panel {
  background: var(--surface-raised);
  border: 1px solid var(--rule-decor);
  padding: var(--space-md);
}

/* A full walkthrough screen. Each beat lands without scrolling gymnastics.
   It fills whatever the ink frame leaves rather than a whole viewport (see
   section 10), so the bar and the footer are never pushed off the bottom. */
.d-screen {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: var(--pad-page);
  overflow: hidden;
}

/* Native widgets (the colour picker, the range track, select popups,
   scrollbars) follow the surface they sit on instead of the OS default. */
.d-on-dark  { color-scheme: dark; }
.d-on-light { color-scheme: light; }

.d-measure { max-width: var(--measure); }

/* Vertical rhythm inside a screen. Every screen's content sits in one
   [data-role="reveal-host"] block; making that block a flex column gives the
   whole walkthrough a single default spacing step without any component
   having to know what sits above it. Components that need more space still
   set their own margins, which add to the gap rather than fight it. */
.d-screen > [data-role="reveal-host"] {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

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

/* One focus treatment for the whole app. The walkthrough must be completable
   with a keyboard, so the ring is always visible and never removed. */
.d-app :focus-visible {
  outline: var(--focus-width) solid var(--focus);
  outline-offset: var(--focus-offset);
  border-radius: var(--radius-ring);
}

/* ==========================================================================
   1. TYPE
   Fraunces display, Inter body, JetBrains Mono for eyebrows, labels, counters
   and step markers only. Hierarchy is weight and size, never family.
   Never body copy in mono or serif.
   ========================================================================== */

.d-cover-line,
.d-display-1,
.d-display-2,
.d-display-3,
.d-display-4 {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-weight: var(--fw-display);
  color: var(--fg);
  margin: 0;
  text-wrap: balance;
}

.d-cover-line { font-size: var(--fs-cover);     line-height: var(--lh-cover);   letter-spacing: var(--track-xl); }
.d-display-1  { font-size: var(--fs-display-1); line-height: var(--lh-cover);   letter-spacing: var(--track-xl); }
.d-display-2  { font-size: var(--fs-display-2); line-height: var(--lh-display); letter-spacing: var(--track-lg); }
.d-display-3  { font-size: var(--fs-display-3); line-height: var(--lh-display); letter-spacing: var(--track-lg); font-weight: var(--fw-display-mid); }
.d-display-4  { font-size: var(--fs-display-4); line-height: 1.3;               letter-spacing: var(--track-none); }

/* Emphasis inside display type is Fraunces italic, per the brand system. */
.d-display-1 em,
.d-display-2 em,
.d-cover-line em { font-style: italic; }

.d-lead,
.d-body,
.d-caption {
  font-family: var(--font-body);
  line-height: var(--lh-body);
  margin: 0;
  text-wrap: pretty;
}
.d-lead    { font-size: var(--fs-lead);    color: var(--fg); }
.d-body    { font-size: var(--fs-body);    color: var(--fg); }
.d-caption { font-size: var(--fs-caption); color: var(--fg-soft); line-height: 1.6; }
.d-body--soft,
.d-lead--soft { color: var(--fg-soft); }
.d-strong { font-weight: var(--fw-strong); }

/* Mono. Eyebrows, field labels, step markers, counters, folios. Nothing else. */
.d-eyebrow,
.d-label,
.d-mono {
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-variant-ligatures: none;
  line-height: var(--lh-mono);
  margin: 0;
}
.d-eyebrow {
  font-size: var(--fs-mono);
  letter-spacing: var(--track-mono-wide);
  text-transform: uppercase;
  /* On dark an eyebrow may be Signal (10.21:1). On light it is --text-soft
     (6.33:1 on Bone, 7.11:1 on Bone Card). The prospect's brand color is
     NEVER used here, at any size. */
  color: var(--fg-accent);
}
.d-on-light .d-eyebrow { color: var(--fg-soft); }
.d-label {
  font-size: var(--fs-mono);
  letter-spacing: var(--track-mono);
  text-transform: uppercase;
  color: var(--fg-soft);
}
.d-label--sm { font-size: var(--fs-mono-sm); }
.d-mono { font-size: var(--fs-mono); color: var(--fg-soft); letter-spacing: 0.02em; }

/* The sample / honesty label. Acceptance criterion 9: it is visible on every
   result-bearing surface and is never the faintest element on its screen. */
.d-sample-label {
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--track-mono);
  text-transform: uppercase;
  line-height: 1.6;
  margin: 0;
  color: var(--label-sample-on-dark); /* Bone 0.72 on Ink Abyss ... 7.59:1 */
}
.d-on-light .d-sample-label {
  display: inline-block;
  /* The chip is the width of its words. Inside a flex or grid column it would
     otherwise stretch edge to edge and stop reading as a stamp. */
  align-self: flex-start;
  justify-self: start;
  padding: var(--space-2xs) var(--space-xs);
  background: var(--label-sample-chip-on-light);
  color: var(--label-sample-ink-on-light); /* Bone on Ink ........ 10.37:1 */
}
/* Do not add an opacity rule to .d-sample-label. 0.72 is the floor. */

/* ==========================================================================
   2. BUTTONS AND LINKS
   One primary, one secondary, one text link. Keyboard-completable: every one
   has a visible focus-visible ring from the base rule above.
   ========================================================================== */

.d-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: var(--fw-strong);
  line-height: 1.2;
  text-decoration: none;
  padding: 0.8125rem 1.5rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: none;
  color: inherit;
  cursor: pointer;
  transition:
    background-color var(--dur) var(--ease-out),
    border-color var(--dur) var(--ease-out),
    color var(--dur) var(--ease-out);
}

/* Primary. Signal fill on dark, Ink fill on light. Signal is never a large
   fill on Bone, which is why the light primary is Ink. */
.d-btn--primary {
  background: var(--btn-primary-bg);
  border-color: var(--btn-primary-bg);
  color: var(--btn-primary-fg);
}
.d-on-dark  .d-btn--primary:hover { background: color-mix(in srgb, var(--signal) 88%, var(--bone)); border-color: transparent; }
.d-on-light .d-btn--primary:hover { background: var(--ink-deep); border-color: var(--ink-deep); } /* Bone on Ink Deep .. 12.07:1 */

/* Secondary. Outline only. Border is a UI mark and clears 3:1 on both grounds:
   dark = Bone 0.40 (3.24:1), light = --rule-strong (4.18:1 on Bone). */
.d-btn--secondary {
  border-color: var(--rule);
  color: var(--fg);
}
.d-on-dark  .d-btn--secondary:hover { border-color: var(--bone); }
.d-on-light .d-btn--secondary:hover { border-color: var(--ink); }

/* Text link. Underline carries the affordance, so it is a UI mark:
   dark = Signal (10.21:1), light = --signal-on-light (4.53:1 on Bone). */
.d-link {
  font-family: inherit;
  font-size: inherit;
  font-weight: var(--fw-strong);
  color: var(--fg);
  text-decoration: none;
  padding-bottom: 2px;
  border-bottom: 1px solid var(--fg-accent);
  background: none;
  border-left: 0; border-right: 0; border-top: 0;
  cursor: pointer;
  transition: border-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
}
.d-link:hover { border-bottom-color: var(--fg); }

.d-btn[disabled],
.d-link[disabled] { opacity: 0.5; cursor: not-allowed; }

.d-actions { display: flex; flex-wrap: wrap; gap: var(--space-sm); align-items: center; }

/* ==========================================================================
   3. PROGRESS DOTS AND THE STEP COUNTER
   Eight steps. The counter is mono and always present, so the arc has a
   visible end even when the dots are small (PRD §4, persistent elements).
   ========================================================================== */

.d-progress {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.d-progress__dots {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.d-progress__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  border: 1px solid var(--rule);   /* upcoming, non-text ....... 3.24:1 dark / 4.18:1 light */
  background: none;
  padding: 0;
  transition: width var(--dur) var(--ease-out), background-color var(--dur) var(--ease-out);
}
/* Steps already taken: solid, in the foreground color. */
.d-progress__dot.is-done {
  background: var(--fg);
  border-color: var(--fg);
}
/* The current step: a bar, not a dot. Shape carries the state, not color
   alone, so it survives a projector and a monochrome print. */
.d-progress__dot.is-current {
  width: 22px;
  border-radius: 4px;
  background: var(--fg-accent);
  border-color: var(--fg-accent);
}

.d-progress__count {
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-size: var(--fs-mono-sm);
  letter-spacing: 0.12em;
  color: var(--fg-soft);          /* 4.96:1 dark / 6.33:1 light */
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .d-progress__dot { transition: none; }
}

/* ==========================================================================
   4. THE STAGES-BY-LANES GRID  (decision 0003A)
   Five lanes (rows) x four stages (columns), the client's current stage
   marked per lane. Overall stage is stated separately, above it.

   POSITION CARRIES THE VALUE. There is no alpha ramp and there is no color
   ramp. A marked cell differs from an unmarked cell by three independent
   channels at once: a solid fill, a border, and the stage word printed inside
   it. It is therefore identifiable without color (WCAG 1.4.1), legible on a
   boardroom projector, and printable in a single ink.

   MARKUP CONTRACT (F1): see styles/README.md.
   ========================================================================== */

.d-stagegrid {
  --lane-col: minmax(7rem, 13rem);
  display: grid;
  grid-template-columns: var(--lane-col) repeat(4, minmax(0, 1fr));
  gap: 6px;
  align-items: stretch;
  margin: 0;
}

.d-stagegrid__corner { grid-column: 1; }

.d-stagegrid__col {
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-size: var(--fs-mono-sm);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-align: center;
  color: var(--fg-soft);          /* 4.96:1 dark / 6.33:1 light */
  padding-bottom: var(--space-2xs);
}

.d-stagegrid__lane {
  display: flex;
  align-items: center;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: var(--fw-body);
  color: var(--fg);               /* 13.60:1 dark / 13.40:1 light */
  padding-right: var(--space-sm);
}

.d-stagegrid__cell {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
  padding: var(--space-2xs);
  border: 1px solid var(--rule-decor);
  background: none;
}

/* The marked cell. Fill + border + word. */
.d-stagegrid__cell.is-marked {
  background: var(--fg-accent);
  border-color: var(--fg-accent);
}
.d-stagegrid__mark {
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-size: var(--fs-mono-sm);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: center;
  /* Dark ground: the fill is Signal and the word is Ink Abyss . 10.21:1 */
  color: var(--ink-abyss);
}
/* Light ground: an Ink fill, not a Signal one. Signal is never a large fill
   on Bone, and Ink keeps the grid printable in one ink. */
.d-on-light .d-stagegrid__cell.is-marked {
  background: var(--ink);
  border-color: var(--ink);
}
.d-on-light .d-stagegrid__mark { color: var(--bone); } /* Bone on Ink .. 10.37:1 */

/* The unmarked cell still needs to read as a position, not as damage. A
   hairline centre tick does that without competing with the marks. */
.d-stagegrid__cell:not(.is-marked)::after {
  content: "";
  width: 10px;
  height: 1px;
  background: var(--rule);        /* non-text .. 3.24:1 dark / 4.18:1 light */
}

/* Narrow screens: the stage word is replaced by its ordinal (a 1-4 mark is
   the alternative §9.3 explicitly allows). The word stays in the accessible
   name via the markup contract, so nothing is lost to a screen reader. */
.d-stagegrid__mark-short { display: none; }
@media (max-width: 46rem) {
  .d-stagegrid { --lane-col: minmax(5.5rem, 9rem); gap: 4px; counter-reset: d-col; }
  .d-stagegrid__mark-full { display: none; }
  .d-stagegrid__mark-short { display: inline; }
  /* The column headers go to ordinals too. "Compounding" is about 73px at
     11px mono and four of them plus the lane column need 380px, so on a
     327px page the words do not shorten - they overlap, which is worse than
     unreadable, it is wrong. The word stays in the DOM for assistive tech,
     every cell's accessible name already names its stage in full, and the
     legend below prints the 01-04 mapping in words. This is the "or a 1-4
     mark" alternative design brief 9.3 explicitly allows. */
  .d-stagegrid__col {
    counter-increment: d-col;
    font-size: 0;
    letter-spacing: 0;
  }
  .d-stagegrid__col::before {
    content: counter(d-col, decimal-leading-zero);
    font-size: var(--fs-mono-sm);
    letter-spacing: 0.08em;
  }
  /* ...and the legend carries the key, so the ordinals are never a code the
     reader has to guess at. */
  .d-stagegrid-legend { counter-reset: d-step; }
  .d-stagegrid-legend__item { counter-increment: d-step; }
  .d-stagegrid-legend__word::before {
    content: counter(d-step, decimal-leading-zero) " · ";
  }
}

/* The legend shows all four steps, never two endpoints. */
.d-stagegrid-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  align-items: center;
  margin-top: var(--space-md);
  padding-top: var(--space-sm);
  border-top: 1px solid var(--rule-decor);
}
.d-stagegrid-legend__item {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-size: var(--fs-mono-sm);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-soft);
}
.d-stagegrid-legend__swatch {
  width: 14px; height: 14px;
  border: 1px solid var(--rule);
  flex: none;
}
.d-stagegrid-legend__item.is-current .d-stagegrid-legend__swatch {
  background: var(--fg-accent);
  border-color: var(--fg-accent);
}
.d-on-light .d-stagegrid-legend__item.is-current .d-stagegrid-legend__swatch {
  background: var(--ink);
  border-color: var(--ink);
}

/* ==========================================================================
   5. READOUT PAGE CHROME
   The S4 cover frame and the inside-page frame, carrying the prospect's
   brand. Direction 1c, "Verdict On The Cover" (decision 0001): the cover is
   the finding, the firm sits above it as the byline.

   Their color appears here as a plate, a rule and a card top. It carries no
   type. Every brand-colored mark is decorative and is never the only thing
   telling the reader something, so any hex is safe (AC10).
   ========================================================================== */

.d-readout {
  container-type: inline-size;
  position: relative;
  width: 100%;
  /* The page object scales as a page, not as a viewport, so a readout in a
     card and a readout full-bleed hold the same proportions. */
  --fs-cover-page:  clamp(1.75rem, 6.1cqi, 4.875rem);  /* 78px at 1280 */
  --fs-stage-page:  clamp(1.5rem,  4.1cqi, 3.25rem);   /* 52px at 1280 */
  --fs-firm-page:   clamp(1.05rem, 1.72cqi, 1.375rem); /* 22px at 1280 */
  /* The mono labels scale as a page too. Leaving them at a fixed rem was the
     one thing on the cover that did not, and it is what pushed the byline out
     of the negative space and under the linework at every container narrower
     than about 1100px - which breaks the binding rule, not just the layout. */
  --fs-mono-page:   clamp(0.625rem, 1.02cqi, 0.8125rem); /* 13px at 1280 */
}
.d-readout__page {
  aspect-ratio: 8 / 5;   /* the 1280x800 frame the direction was drawn on */
}

/* --- The cover ---------------------------------------------------------- */
.d-cover {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--space-lg);
  padding: var(--pad-page);
  overflow: hidden;
}

/* Every mono label on a readout page is set at the page's own scale, so the
   byline, the eyebrow, the folio and the labels shrink with the artifact and
   stay inside the left-hand column the linework leaves for them. */
.d-cover .d-eyebrow,
.d-cover .d-label,
.d-cover .d-sample-label,
.d-cover__preparedby,
.d-cover__gapcount,
.d-page__header,
.d-page__folio,
.d-page .d-label,
.d-page .d-sample-label { font-size: var(--fs-mono-page); }

/* The byline is capped short of the linework's leading edge (x=640 of the
   1280 viewBox, i.e. half the page). Linework never sits behind type. */
.d-cover__byline {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  max-width: 48%;
}
.d-cover__firm {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-weight: var(--fw-display);
  font-size: var(--fs-firm-page);
  line-height: 1.2;
  letter-spacing: var(--track-none);
  color: var(--fg);               /* Bone on Ink Abyss ........ 13.60:1 */
  margin: 0;
}
.d-cover__preparedby {
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--track-mono);
  text-transform: uppercase;
  color: var(--fg-soft);          /* Bone 0.55 ................  4.96:1 */
  margin: var(--space-2xs) 0 0;
}

/* Logo-less path. A 3px brand rule at the left of the byline: their color,
   no type on it. If their hex happens to sit close to Ink Abyss the rule
   fades, and nothing is lost, because the firm name is doing the work. */
.d-cover__byline--wordmark {
  border-left: 3px solid var(--brand-accent);
  padding-left: var(--space-md);
}

.d-cover__statement { max-width: var(--measure-display); }
.d-cover__verdict {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-weight: var(--fw-display);
  font-size: var(--fs-cover-page);
  line-height: var(--lh-cover);
  letter-spacing: var(--track-xl);
  color: var(--fg);
  margin: 0;
  text-wrap: balance;
}
/* Spacing modifier, used together with .d-eyebrow. The cover eyebrow is
   therefore Signal on dark. Never the brand color: at 13px it is type, and
   #D2452A on Ink Abyss measures 3.74:1 against a 4.5:1 floor. */
.d-cover__eyebrow { margin-bottom: var(--space-md); }

/* The cover rule. 120x3, their color, decorative. */
.d-cover__rule {
  width: 120px;
  height: 3px;
  background: var(--brand-accent);
  margin-bottom: var(--space-md);
}

.d-cover__foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  align-items: flex-end;
  justify-content: space-between;
  border-top: 1px solid var(--rule-decor);
  padding-top: var(--space-md);
}
.d-cover__stage {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-weight: var(--fw-display);
  font-size: var(--fs-stage-page);
  line-height: 1;
  letter-spacing: var(--track-xl);
  color: var(--fg);
  margin: 0;
}
/* The one number on the cover is the gap count the prospect generated in S2
   (decision 0002). Descriptive, never comparative. No peer or percentile
   language anywhere near it. */
.d-cover__gapcount {
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--track-mono);
  text-transform: uppercase;
  color: var(--fg-accent);        /* Signal on Ink Abyss ...... 10.21:1 */
  margin: 0;
}

/* --- The logo plate ------------------------------------------------------
   Their color as a fill, with one large Fraunces initial. The initial flips
   between Bone and Ink Abyss by the plate's luminance; the mechanism and the
   0.178 threshold are documented in tokens.css §4. Worst case at the
   crossover is 3.69:1, so THE INITIAL IS LARGE TEXT (>= 24px) where 3:1 is
   the AA requirement. Do not shrink it below --plate-size 3rem.
   The 1px ring guarantees the plate's own edge reads at >= 3:1 whatever the
   hex, including a hex identical to the surface behind it.               */
.d-plate {
  --plate-size: clamp(3rem, 4.1cqi, 3.25rem);  /* 48 -> 52px */
  width: var(--plate-size);
  height: var(--plate-size);
  flex: none;
  display: grid;
  place-items: center;
  background: var(--brand-accent);
  box-shadow: inset 0 0 0 1px var(--rule);     /* 3.24:1 dark / 4.18:1 light */
  overflow: hidden;
}
.d-plate__initial {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-weight: var(--fw-display);
  /* 24px is the WCAG large-text threshold at normal weight, and large text is
     what makes the 3.69:1 worst case an AA pass. The max() is a hard floor:
     never let this compute below 1.5rem. */
  font-size: max(1.5rem, calc(var(--plate-size) * 0.5));
  line-height: 1;
  letter-spacing: var(--track-none);
  color: var(--bone);                          /* fallback if color-mix fails */
  color: var(--brand-plate-ink);               /* Bone or Ink Abyss by luminance */
}
/* An uploaded logo replaces the initial. It is rendered in-browser and never
   stored (PRD §7). Contain, never cover, so nobody's mark gets cropped. */
.d-plate__logo {
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: 12%;
}
.d-plate--logo { background: none; box-shadow: none; }

/* --- An inside page ------------------------------------------------------ */
.d-page {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
/* The running header. Firm, client, and the sample word, on every page. */
.d-page__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-md) var(--pad-page);
  border-bottom: 1px solid var(--rule-decor);
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--track-mono);
  text-transform: uppercase;
  color: var(--fg-soft);
}
.d-page__body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding: var(--space-lg) var(--pad-page);
}
/* Page numbering. Mono, "04 / 06", right of the running header or in the foot. */
.d-page__folio {
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-size: var(--fs-mono);
  letter-spacing: 0.12em;
  color: var(--fg-soft);
  white-space: nowrap;
}
.d-page__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  margin-top: auto;
  padding: var(--space-sm) var(--pad-page) var(--space-md);
  border-top: 1px solid var(--rule-decor);
}

/* A move card. Their color is the 3px top edge only. */
.d-movecard {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  background: var(--surface-raised);
  border: 1px solid var(--rule-decor);
  border-top: 3px solid var(--brand-accent);
  padding: var(--space-md);
}
.d-movecard__n {
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--track-mono);
  color: var(--fg-soft);   /* NOT the brand color: at 13px this is type. */
}
.d-movecard__meta { margin-top: auto; }

/* The roadmap ramp. Their color stepped down in four. Decorative only: the
   quarter labels above it carry the sequence, so the ramp is never the sole
   carrier of meaning and an unreadable hex costs nothing. */
.d-roadmap-ramp { display: flex; gap: 2px; }
.d-roadmap-ramp > * { height: 6px; flex: 1; background: var(--brand-accent); }
.d-roadmap-ramp > :nth-child(2) { opacity: 0.66; }
.d-roadmap-ramp > :nth-child(3) { opacity: 0.42; }
.d-roadmap-ramp > :nth-child(4) { opacity: 0.22; }

/* --- The readout on a phone ----------------------------------------------
   A 327px-wide "page" is not a page. Held at 8:5 it is 204px tall, and
   .d-cover / .d-page clip to it, so the verdict, the grid and the moves are
   simply cut off - the artifact the whole walkthrough exists to show. Below
   46rem the readout therefore stops being a scale model of a sheet and
   becomes a stack of sections at full reading size. The souvenir PDF is
   unaffected: it is generated at page width, and .d-cover--print keeps its
   clip so the foot band can still bleed.                                  */
@media (max-width: 46rem) {
  .d-readout { --fs-mono-page: var(--fs-mono); }
  .d-readout__page { aspect-ratio: auto; }
  .d-cover:not(.d-cover--print),
  .d-page { overflow: visible; }
  .d-cover { min-height: 60svh; }
  /* The byline can use the full width again, because... */
  .d-cover__byline { max-width: 100%; }
  /* ...there is no linework left for it to run into. At this width a cover
     has no negative space to give, and the binding rule is that the
     signature occupies negative space or it does not appear. */
  .d-linework--cover { display: none; }
}

/* ==========================================================================
   6. THE FIT-LINEWORK SIGNATURE
   One component, one instance per screen. Thin 1px lines, 4-6 bands,
   Signal on Ink Abyss. Negative space only, NEVER behind type. Covers cap at
   0.5. Stroke stays 1px at every scale: vector-effect keeps it there when the
   viewBox is stretched, so no thickening and no glow, ever.

   MARKUP CONTRACT (F1): see styles/README.md for the canonical SVG.
   ========================================================================== */

.d-linework {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
}
/* Anything that must paint above the linework carries .d-above. Type always
   does. This is how "never behind type" is enforced structurally. */
.d-above { position: relative; z-index: 1; }

/* ...and this is how it is enforced GEOMETRICALLY, which is the half that
   .d-above cannot do. The template's curves begin at x=640 of a 1280 viewBox,
   i.e. at the horizontal middle of whatever box holds them. On a full-bleed
   screen that middle lands inside the 44rem reading column at every viewport
   under about 1500px, so a headline runs under the signature even though it
   paints on top of it. Starting the container where the reading column ends
   moves the curves permanently clear of the type.
   Below 60rem a screen has no negative space to give, so the signature is
   dropped rather than parked underneath a paragraph. */
.d-screen > .d-linework {
  left: min(64%, calc(var(--measure) + var(--pad-page) * 2));
}
@media (max-width: 60rem) {
  .d-screen > .d-linework { display: none; }
}

.d-linework svg { width: 100%; height: 100%; display: block; }
.d-linework path {
  fill: none;
  stroke: var(--linework-color);
  stroke-width: var(--linework-stroke);
  vector-effect: non-scaling-stroke;
}
.d-linework__band { opacity: var(--band, var(--linework-band-3)); }
.d-linework__band:nth-of-type(1) { --band: var(--linework-band-1); }
.d-linework__band:nth-of-type(2) { --band: var(--linework-band-2); }
.d-linework__band:nth-of-type(3) { --band: var(--linework-band-3); }
.d-linework__band:nth-of-type(4) { --band: var(--linework-band-4); }
.d-linework__band:nth-of-type(5) { --band: var(--linework-band-5); }
.d-linework__band:nth-of-type(6) { --band: var(--linework-band-6); }

/* On a cover, no band exceeds --linework-opacity-cover-max (0.5). */
.d-linework--cover .d-linework__band {
  opacity: min(var(--band), var(--linework-opacity-cover-max));
}
/* The S3 resolve fades the whole signature back so nothing competes with the
   finding. Applied as a class, not a keyframe, so it is a transition. */
.d-linework { transition: opacity var(--dur-slow) var(--ease-out); }
/* 0.16 is the exploration's own resolve value. It multiplies the per-band
   opacity, so a receded band is 0.03-0.10 - decorative to the point of being
   a texture, which is the intent: at the resolve, nothing competes with the
   finding. The 0.2-0.7 band range still governs the drawn state. */
.d-linework.is-receded { opacity: var(--linework-receded); }

/* Draw-on: 900ms with a band stagger. Paths need pathLength="1" in the
   markup so one dash length works for every curve. */
@keyframes d-linework-draw {
  from { stroke-dashoffset: 1; }
  to   { stroke-dashoffset: 0; }
}
@media (prefers-reduced-motion: no-preference) {
  .d-linework--draw path {
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
    animation: d-linework-draw var(--dur-linework) var(--ease-out) forwards;
  }
  .d-linework--draw .d-linework__band:nth-of-type(2) path { animation-delay: 80ms; }
  .d-linework--draw .d-linework__band:nth-of-type(3) path { animation-delay: 160ms; }
  .d-linework--draw .d-linework__band:nth-of-type(4) path { animation-delay: 240ms; }
  .d-linework--draw .d-linework__band:nth-of-type(5) path { animation-delay: 320ms; }
  .d-linework--draw .d-linework__band:nth-of-type(6) path { animation-delay: 400ms; }
}
/* Reduced motion: the lines render already drawn. */
@media (prefers-reduced-motion: reduce) {
  .d-linework--draw path { stroke-dasharray: none; stroke-dashoffset: 0; animation: none; }
  .d-linework { transition: none; }
}

/* ==========================================================================
   7. THE RESERVED SEAT  ("your analysis here")
   Decision 0001 finding 7. On the explored spread this was a dashed box at
   the bottom of the page, the least prominent element on it. If the pitch is
   that the machine brings the data and the consultant brings the judgment,
   the judgment slot cannot be a footnote.

   So: same width as the roadmap, same panel weight as the move cards, a
   Fraunces heading at the same size as "The top three moves", a solid 3px
   top edge in their color, and ruled writing lines that read as a page
   waiting for an author. No dashes anywhere.
   ========================================================================== */

.d-seat {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  background: var(--surface-raised);
  border: 1px solid var(--rule);      /* solid, 4.18:1 on light. Not dashed. */
  border-top: 3px solid var(--brand-accent);
  padding: var(--space-md) var(--space-md) var(--space-lg);
}
.d-seat__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-md);
  align-items: baseline;
  justify-content: space-between;
}
.d-seat__title {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-weight: var(--fw-display);
  font-size: var(--fs-display-2);     /* same step as the roadmap heading */
  line-height: var(--lh-display);
  letter-spacing: var(--track-lg);
  color: var(--fg);
  margin: 0;
}
.d-seat__byline {           /* "Prepared by [Name]" sits here, mono. */
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--track-mono);
  text-transform: uppercase;
  color: var(--fg-soft);
}
.d-seat__body { max-width: var(--measure); color: var(--fg); font-size: var(--fs-body); }
/* Ruled lines. Decorative, so --rule-decor is correct here; the heading and
   the body carry the meaning. */
.d-seat__lines {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin-top: var(--space-xs);
}
.d-seat__lines > * { height: 1px; background: var(--rule-decor); }

/* ==========================================================================
   8. MOTION UTILITIES
   Standard transitions 150-300ms ease-out. Step reveals 240ms with a 60ms
   stagger. Every animated property has a reduced-motion fallback, and the
   fallback is the finished state, never a missing one.
   ========================================================================== */

@keyframes d-rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
@keyframes d-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@media (prefers-reduced-motion: no-preference) {
  .d-reveal {
    animation: d-rise var(--dur-reveal) var(--ease-out) both;
    animation-delay: calc(var(--i, 0) * var(--stagger));
  }
  .d-reveal--fade {
    animation-name: d-fade;
  }
  /* The S3 grid assembling cell by cell, 30ms apart. */
  .d-stagegrid--assembling .d-stagegrid__cell {
    animation: d-fade var(--dur-reveal) var(--ease-emphasis) both;
    animation-delay: calc(var(--i, 0) * var(--stagger-cell));
  }
}
@media (prefers-reduced-motion: reduce) {
  .d-reveal,
  .d-stagegrid--assembling .d-stagegrid__cell {
    animation: none;
    opacity: 1;
    transform: none;
  }
  .d-app *,
  .d-app *::before,
  .d-app *::after {
    transition-duration: 1ms !important;
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}

/* ==========================================================================
   10. THE SHELL: BAR, FOOT, NAV
   The walkthrough is framed in ink at the top and the bottom, and the screen
   is shown inside that frame. On S4, S5 and S6 the frame is what makes the
   Bone artifact read as an object being held up to you rather than as a web
   page: Ink Abyss against Bone Card measures 15.28:1, so the edge of the
   frame is unmistakable and the paper inside it looks like paper.
   ========================================================================== */

.d-shell {
  display: flex;
  flex-direction: column;
  min-height: 100svh;
}
.d-shell > main,
.d-shell > [data-role="main"] {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.d-bar,
.d-foot {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-sm) var(--pad-page);
}
.d-bar {
  justify-content: flex-end;
  min-height: 3.5rem;
  border-bottom: 1px solid var(--rule-decor);
}
/* The progress nav is hidden on S0, and an empty ink strip above the first
   question is dead space in the one place the screen has to feel like a
   conversation. The bar collapses when it has nothing to show. */
.d-bar:not(:has(.d-progress:not([hidden]))) {
  min-height: 0;
  padding-block: 0;
  border-bottom: 0;
}
.d-foot {
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-lg);
  padding-block: var(--space-md);
  border-top: 1px solid var(--rule-decor);
}
.d-foot .d-caption { color: var(--fg-soft); }  /* Bone 0.55 on Ink Abyss .. 4.96:1 */

.d-nav { gap: var(--space-sm); }
/* Back is quieter than Next without dropping below the non-text floor: both
   are outline buttons at --rule (3.24:1 on dark), and Back gives up label
   weight, not contrast. Nothing in this walkthrough is disabled by fading. */
.d-nav__back { font-weight: var(--fw-body); }
.d-nav__next { font-weight: var(--fw-strong); }

/* Only ever visible when content.json itself failed to load, so it sits on
   the user agent's own ground with no surface context to inherit. */
.d-boot-error {
  margin: 0;
  padding: var(--space-xl) var(--pad-page);
  font-family: var(--font-body);
  font-size: var(--fs-lead);
  text-align: center;
  color: var(--text);            /* Text on the UA white ground ..... 16.73:1 */
}

/* ==========================================================================
   11. S0 — THE INTAKE   (beat E1, "Seen")
   Concierge, not form. One question at a time on the ink, set in Fraunces at
   the size of a sentence somebody said out loud, with the answer written on a
   single ruled line. No card, no box, no asterisk, no "step 2 of 5": the only
   thing on the screen is the question being asked. A boxed input is a form; a
   ruled line is a card somebody is writing on while you talk.
   ========================================================================== */

/* Centred on both axes. S1, S3, S7 and S8 are left-aligned against the
   linework in the right-hand negative space; S0 and S2 have no linework, and
   a lone column pinned to the left gutter of a wide ink field reads as an
   unfinished page rather than as somebody's undivided attention. */
.d-screen--s0 { justify-content: center; align-items: center; }
.d-screen--s0 > [data-role="reveal-host"] { width: min(38rem, 100%); }

.d-intake__greeting {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-weight: var(--fw-display);
  font-size: var(--fs-display-3);
  line-height: var(--lh-display);
  letter-spacing: var(--track-lg);
  color: var(--fg);                    /* Bone on Ink Abyss ........ 13.60:1 */
  margin: var(--space-sm) 0 0;
}

/* The stage. Every field lands in the same place, so swapping one for the
   next never moves the question, the buttons, or the ground beneath them. */
.d-intake {
  position: relative;
  min-height: 15rem;
  margin: var(--space-xl) 0 var(--space-lg);
}
.d-intake__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}
/* Both fields occupy the same box during the 180ms swap. Absolute placement
   is layout, not animation; only opacity and transform are animated. */
.d-intake__field.is-leaving {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.d-intake__prompt {
  display: block;
  font-size: var(--fs-display-3);      /* 22 -> 30px. A question, not a label. */
  line-height: var(--lh-display);
  letter-spacing: var(--track-lg);
  color: var(--fg);
  margin-bottom: var(--space-xs);
}
.d-intake__control {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  align-items: flex-start;
}
.d-intake .d-actions { margin-top: var(--space-md); }

/* The ruled line. The rule is the affordance, so it is held at the non-text
   floor and goes to the accent, at 2px, on focus. The focus ring from .d-app
   is deliberately left in place on top of it.
   F1 puts .d-field__input on these too, so every rule here is written at
   .d-field__input.d-intake__input to win on specificity rather than on where
   it happens to sit in the file. */
.d-field__input.d-intake__input {
  width: 100%;
  font-family: var(--font-body);
  font-size: var(--fs-display-4);      /* 18 -> 21px */
  line-height: 1.4;
  color: var(--fg);                    /* Bone on Ink Abyss ........ 13.60:1 */
  background: none;
  border: 0;
  border-bottom: 1px solid var(--rule); /* Bone 0.40, non-text ......  3.24:1 */
  border-radius: 0;
  padding: var(--space-xs) 0;
  margin-top: 0;
  transition: border-color var(--dur) var(--ease-out);
}
.d-intake__input::placeholder { color: var(--fg-soft); opacity: 1; }  /* 4.96:1 */
.d-field__input.d-intake__input:focus {
  border-bottom-color: var(--fg-accent); /* Signal on Ink Abyss ..... 10.21:1 */
  border-bottom-width: 2px;
  padding-bottom: calc(var(--space-xs) - 1px);
}

/* The brand-colour field. The native swatch, sized as a plate, so the
   prospect sees their colour at the size it will appear on the cover.
   The ring is outside the chip and therefore reads against the ground at
   3.24:1 whatever hex is chosen, including a hex identical to the ground. */
.d-field__input.d-intake__input--color {
  width: 3.25rem;
  height: 3.25rem;
  padding: 0;
  margin: 0;
  border: 0;
  border-radius: 0;
  background: none;
  cursor: pointer;
  box-shadow: 0 0 0 1px var(--rule);
}
.d-intake__input--color::-webkit-color-swatch-wrapper { padding: 0; }
.d-intake__input--color::-webkit-color-swatch { border: 0; border-radius: 0; }
.d-intake__input--color::-moz-color-swatch { border: 0; border-radius: 0; }

.d-swatches { display: flex; flex-wrap: wrap; gap: var(--space-sm); }
.d-swatch {
  width: 2rem;
  height: 2rem;
  flex: none;
  padding: 0;
  border: 0;
  border-radius: 0;
  cursor: pointer;
  background: var(--swatch, var(--brand-accent));
  /* The hairline ring is what makes a preset visible at all when its hex sits
     close to the ground: Bone 0.40 on Ink Abyss = 3.24:1. */
  box-shadow: 0 0 0 1px var(--rule);
  transition: box-shadow var(--dur) var(--ease-out), transform var(--dur) var(--ease-out);
}
/* Selection is a ring drawn OUTSIDE the chip, on the ground, never a change
   to the chip's own colour. So it measures 10.21:1 on dark whatever the hex,
   and it is a geometry change: the state is not carried by colour alone. */
.d-swatch.is-selected {
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--fg-accent);
}
.d-swatch:hover { transform: translateY(-2px); }

.d-logopick {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md);
}
.d-logopick__input {
  font-family: var(--font-body);
  font-size: var(--fs-caption);
  color: var(--fg-soft);               /* 4.96:1 dark / 6.33:1 light */
  max-width: 100%;
}
/* The file button is the only control here, so it is dressed as a real
   secondary button instead of being left as the browser's grey chrome. */
.d-logopick__input::file-selector-button {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: var(--fw-strong);
  color: var(--fg);
  background: none;
  border: 1px solid var(--rule);       /* 3.24:1 dark / 4.18:1 light */
  border-radius: var(--radius);
  padding: 0.625rem 1.125rem;
  margin-right: var(--space-sm);
  cursor: pointer;
}
.d-logopick__preview {
  width: 3.25rem;
  height: 3.25rem;
  flex: none;
  object-fit: contain;                 /* contain, never cover: nobody's mark
                                          gets cropped in their own demo. */
  background: var(--surface-raised);
  box-shadow: 0 0 0 1px var(--rule);
}

.d-bandpick { display: flex; flex-wrap: wrap; gap: var(--space-xs); }
.d-bandpick__option {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: var(--fw-strong);
  line-height: 1.2;
  color: var(--fg);                    /* 13.60:1 dark / 13.40:1 light */
  background: none;
  border: 1px solid var(--rule);       /* 3.24:1 dark / 4.18:1 light */
  border-radius: var(--radius);
  padding: 0.75rem 1.125rem;
  cursor: pointer;
  transition:
    background-color var(--dur) var(--ease-out),
    border-color var(--dur) var(--ease-out),
    color var(--dur) var(--ease-out);
}
/* Selected is a fill AND a border change, so the choice survives greyscale. */
.d-bandpick__option.is-selected {
  background: var(--fg-accent);
  border-color: var(--fg-accent);
  color: var(--surface);               /* Ink Abyss on Signal ...... 10.21:1 */
}
.d-on-light .d-bandpick__option.is-selected {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--bone);                  /* Bone on Ink .............. 10.37:1 */
}

/* The field swap: 180ms, matching app.js's TRANSITION_MS. Opacity and
   transform only. */
@media (prefers-reduced-motion: no-preference) {
  .d-intake__field {
    transition:
      opacity var(--dur) var(--ease-out),
      transform var(--dur) var(--ease-out);
  }
  .d-intake__field.is-entering { opacity: 0; transform: translateY(10px); }
  .d-intake__field.is-leaving  { opacity: 0; transform: translateY(-10px); }
}
@media (prefers-reduced-motion: reduce) {
  .d-intake__field,
  .d-intake__field.is-entering,
  .d-intake__field.is-leaving { transition: none; opacity: 1; transform: none; }
  .d-swatch { transition: none; }
  .d-swatch:hover { transform: none; }
}

/* ==========================================================================
   12. S2 — THE INSTRUMENT
   Four binary questions, one at a time. One question at the size of something
   said out loud, two equal answers, and a running gap count underneath. The
   count is the only total on the screen because it is the one number that
   reaches the S4 cover (decision 0002), so it is given a result's weight, not
   a caption's.
   ========================================================================== */

.d-screen--s2 { justify-content: center; align-items: center; }
.d-screen--s2 > [data-role="reveal-host"] { width: min(46rem, 100%); }

.d-question {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  min-height: 13rem;
  margin: var(--space-xl) 0 var(--space-lg);
}
.d-question__count { color: var(--fg-soft); margin: 0; }   /* 4.96:1 */
.d-question__text {
  font-size: var(--fs-display-2);      /* 26 -> 38px */
  line-height: var(--lh-display);
  letter-spacing: var(--track-lg);
  color: var(--fg);                    /* 13.60:1 */
  max-width: 34ch;
  margin: 0;
}
.d-question__choices { gap: var(--space-sm); margin-top: var(--space-xs); }
.d-question__choices .d-btn { min-width: 8rem; }

.d-gapcount {
  font-size: var(--fs-mono-lg);
  letter-spacing: var(--track-mono);
  text-transform: uppercase;
  color: var(--fg-accent);             /* Signal on Ink Abyss ...... 10.21:1 */
  border-top: 1px solid var(--rule);   /* non-text .................  3.24:1 */
  padding-top: var(--space-sm);
  margin: var(--space-lg) 0 var(--space-sm);
}
.d-gapcount:empty { display: none; }

/* ==========================================================================
   13. S3 — THE READ.  THE REVEAL MOTION LAYER.
   Direction 1c, "One Thing At A Time" (decision 0001). A single centred stage
   on the linework. Each beat REPLACES the last; exactly one element group is
   moving at any moment.

   TRANSFORMS AND OPACITY ONLY. Nothing here animates width, height, top or
   left; there is no blur and no animated shadow. The lane leaders sweep on
   scaleX, the transcript lifts on translateY, and the typing is done by JS
   writing characters, because every CSS typewriter animates width or clip.

   HOW BEATS SWITCH.  app.js shows one beat at a time with the `hidden`
   attribute, which this layer turns into display:none. That means a beat
   cannot cross-fade out - so the beat is carried by its ENTRANCE: revealing
   an element restarts any animation on it, so each beat rises into place on
   its own the moment it is unhidden, with no class coordination at all
   between the two files. Nothing here needs JS to add a state class.

   MARKUP CONTRACT (F1), as built:

     .d-read                       the screen's reveal host
       .d-read__head               step label left, sample label right
       .d-read__stage              fixed-height stage; beats never resize it
         .d-read__beat--transcript .d-read__title + .d-read__transcript
           .d-read__line           .is-typing while JS writes it,
                                   .is-past once the next line starts
         .d-read__beat--sweep      .d-read__lanes + .d-read__count
           .d-read__lane           .d-read__lane-name + .d-read__lane-stage
         .d-read__beat--resolved   .d-read__verdict + .d-stagegrid--read
           .d-read__stagename      the overall stage, set left
       .d-read__log                the reduced-motion record
         .d-read__log-item         .d-read__log-n + .d-read__log-text
       .d-read__controls           .d-read__skip and the replay control

   The stage carries a min-height, so a beat arriving or leaving never moves
   the step label above it or the controls below it. That stillness is the
   direction: one thing at a time, and nothing else moves.
   ========================================================================== */

.d-screen--s3 { justify-content: center; }

/* The read screen is wider than the body measure. The transcript still reads
   at 44rem, but the resolved frame has to hold a five-lane grid whose cells
   print a word, and squeezing that into a reading measure is what turns the
   grid back into the unreadable thing review finding 3 was about. So the host
   opens up and the reading blocks are capped individually. */
.d-screen--s3 > .d-read { max-width: min(74rem, 100%); }
.d-read__title,
.d-read__transcript,
.d-read__log-list,
.d-screen--s3 > .d-read > .d-caption { max-width: var(--measure); }

/* The step label and the sample label stack at the top left as one slug.
   They are NOT set at opposite ends of the frame: the fit linework occupies
   the right-hand negative space of this screen, and the binding rule is that
   linework never sits behind type. A right-aligned sample label crosses the
   bands at every viewport under about 1400px, and that label in particular
   (acceptance criterion 9) is the last string that may be made hard to read.
   Capped so the pair can never grow into the signature. */
.d-read__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  max-width: min(30rem, 46%);
}

.d-read__stage {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: clamp(19rem, 46vh, 32rem);
  margin: var(--space-md) 0;
}
.d-read__beat {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}
/* The entrance. One group rises 14px and fades in; nothing else on the screen
   moves while it does. Runs on reveal, so no state class is required. */
@keyframes d-beat-in {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: no-preference) {
  .d-read__beat { animation: d-beat-in var(--dur-beat) var(--ease-emphasis) both; }
}

.d-read__title { margin-bottom: var(--space-xs); }

/* --- Beat 1: the transcript ---------------------------------------------- */
.d-read__transcript {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}
.d-read__line {
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-size: clamp(1rem, 0.72rem + 1.2vw, 1.625rem);  /* 16 -> 26px, the 1c frame */
  line-height: 1.5;
  color: var(--fg);                    /* Bone on Ink Abyss ........ 13.60:1 */
  margin: 0;
  min-height: 1.5em;                   /* the line holds its own height while
                                          JS types into it, so nothing below
                                          it moves one character at a time. */
}
/* An older line lifts 12px and settles at exactly the secondary-text floor.
   Bone at 0.55 over Ink Abyss composites to #838D89 = 4.96:1, so a line you
   have already read is still readable, and the sample label at 0.72 (7.59:1)
   still outranks it - which is the requirement, not a nicety.
   DO NOT TAKE THIS BELOW 0.55. */
.d-read__line.is-past { opacity: 0.55; transform: translateY(-12px); }
@media (prefers-reduced-motion: no-preference) {
  .d-read__line {
    transition:
      opacity var(--dur-beat) var(--ease-out),
      transform var(--dur-beat) var(--ease-out);
  }
  /* The caret, for a typewriter that wants one. JS writes the characters;
     CSS supplies only the mark, because every CSS typewriter animates width
     or clip-path and both are banned. */
  .d-read__line.is-typing::after {
    content: "";
    display: inline-block;
    width: 0.5em;
    height: 1em;
    margin-left: 0.12em;
    vertical-align: -0.12em;
    background: var(--fg-accent);      /* Signal on Ink Abyss ...... 10.21:1 */
    animation: d-caret 1s steps(1, end) infinite;
  }
}
@keyframes d-caret { 0%, 55% { opacity: 1; } 56%, 100% { opacity: 0; } }

/* --- Beat 2: the lane sweep ---------------------------------------------- */
/* Five lanes, each one landing 90ms after the last, with a leader rule that
   sweeps out from the name to the stage word. The leader carries no value -
   the stage word does, printed, on every lane - so it is a gesture, and it
   sweeps on scaleX rather than on width. The stagger is nth-child, so the
   beat needs nothing set on it from JS. */
.d-read__lanes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}
.d-read__lane {
  display: grid;
  grid-template-columns: minmax(6rem, 13rem) minmax(1.5rem, 1fr) auto;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-xs) 0;
}
.d-read__lane-name  { grid-column: 1; font-size: var(--fs-body); color: var(--fg); }  /* 13.60:1 */
.d-read__lane::after {
  content: "";
  grid-column: 2;
  height: 2px;
  background: var(--rule);             /* non-text .................  3.24:1 */
  transform-origin: left center;
}
.d-read__lane-stage {
  grid-column: 3;
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--track-mono);
  text-transform: uppercase;
  color: var(--fg);                    /* 13.60:1 - the value, printed */
  text-align: right;
}
@keyframes d-lane-sweep { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@media (prefers-reduced-motion: no-preference) {
  /* app.js appends the lanes one at a time, so the lane-to-lane stagger is
     already there and CSS must not add a second one. Each leader sweeps as
     its own lane arrives; --i is honoured if a future build sets it, and
     defaults to no extra delay if it does not. */
  .d-read__lane::after {
    animation: d-lane-sweep var(--dur-lane) var(--ease-emphasis) both;
    animation-delay: calc(var(--i, 0) * var(--stagger-lane));
  }
}
/* The stage word is NOT animated, on purpose. It is the value the lane
   carries, and an animation with a fill mode is one stalled frame away from
   hiding it. Decoration animates; the finding does not. */
.d-read__count { color: var(--fg-soft); margin: 0; }   /* 4.96:1 */

/* --- Beat 3: the resolve -------------------------------------------------- */
/* The overall stage sets left, the grid assembles beside it cell by cell
   30ms apart, and the linework recedes to 0.16. This is also the frame the
   reduced-motion path lands on, so it is a complete, legible statement with
   nothing animating in it. */
/* Single column by default; the stage only sets LEFT once there is genuinely
   room for the grid to print its words beside it. Below that the stage sits
   above the grid and the grid takes the full width, which is the same
   statement in a different shape - never a grid with its words clipped. */
.d-read__beat--resolved {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-lg);
  align-items: center;
  /* This is the one block on S3 that is wider than the reading column, so it
     is the one block the geometric guard in section 6 cannot keep clear of
     the signature. It carries the ground with it instead. Invisible - it is
     the same ink as the screen - and it makes "linework never behind type"
     structurally true here rather than true by timing. */
  background: var(--surface);
  padding: var(--space-md) 0;
}
@media (min-width: 72rem) {
  .d-read__beat--resolved {
    grid-template-columns: minmax(0, 17rem) minmax(0, 1fr);
    gap: var(--space-xl);
  }
}
.d-read__verdict { display: flex; flex-direction: column; gap: var(--space-sm); }
.d-read__stagename {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-weight: var(--fw-display);
  font-size: clamp(2.25rem, 1.1rem + 3.6vw, 3.875rem);  /* 36 -> 62px, per 1c */
  line-height: 1.02;
  letter-spacing: var(--track-xl);
  color: var(--fg);                    /* 13.60:1 */
  margin: 0;
}
/* The grid on the read screen is a supporting element, not the artifact, so
   the cells are shorter here than on the S4 page. The word still prints. */
.d-stagegrid--read { --lane-col: minmax(6rem, 11rem); }
.d-stagegrid--read .d-stagegrid__cell { min-height: 2.25rem; }

@media (max-width: 60rem) {
  .d-read__lane { grid-template-columns: minmax(5rem, 9rem) minmax(1rem, 1fr) auto; gap: var(--space-sm); }
}

.d-read__controls { margin-top: var(--space-sm); }

/* --- The reduced-motion record -------------------------------------------- */
/* Not a fallback: this is the whole beat for anyone who asked the OS not to
   animate. So it is designed - a numbered record of what the engine did, mono
   markers on a ruled list, every line at full contrast - and the sequence
   resolves into it with one 200ms opacity fade and then holds. Decision 0001
   turns on this path still delivering the reasoning (beat E4), and note
   finding 5: the words here never say "scored". */
.d-read__log { display: flex; flex-direction: column; gap: var(--space-xs); }
.d-read__log-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  max-width: var(--measure);
}
.d-read__log-item {
  display: grid;
  grid-template-columns: 3.5rem minmax(0, 1fr);
  gap: var(--space-sm);
  align-items: baseline;
  padding: var(--space-sm) 0;
  border-top: 1px solid var(--rule-decor);
}
.d-read__log-item:last-child { border-bottom: 1px solid var(--rule-decor); }
.d-read__log-n {
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--track-mono);
  color: var(--fg-accent);             /* Signal on Ink Abyss ...... 10.21:1 */
}
.d-read__log-text { font-size: var(--fs-body); color: var(--fg); }   /* 13.60:1 */

@media (prefers-reduced-motion: reduce) {
  .d-read__stage { min-height: 0; }
  .d-read__beat { animation: none; opacity: 1; transform: none; }
  .d-read__line { transition: none; opacity: 1; transform: none; }
  .d-read__line.is-past { opacity: 0.55; transform: none; }
  .d-read__line.is-typing::after { content: none; }
  .d-read__lane::after { animation: none; transform: none; }
  /* The one motion this path keeps: a 200ms fade to the resolved frame. It
     has to beat the blanket collapse in section 8, hence the extra
     specificity and the !important. Opacity only - there is no movement in
     it, which is what makes it a legitimate reduced-motion accommodation
     rather than the thing the preference was set to avoid. */
  .d-app .d-read__stage {
    animation: d-fade var(--dur-static) linear both !important;
    animation-duration: var(--dur-static) !important;
  }
}

/* ==========================================================================
   14. S4 AND S5 — LISTS, ROADMAP, LEGEND, DECK
   ========================================================================== */

.d-moves {
  list-style: none;
  margin: var(--space-md) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-md);
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
}

/* The roadmap. Four quarters read left to right under the brand ramp; the
   quarter label carries the sequence, so the ramp above it never has to. */
.d-roadmap {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0 var(--space-md);
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
}
.d-roadmap__item {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  padding: var(--space-sm) 0 var(--space-md);
}
.d-roadmap__item .d-label { color: var(--fg); }   /* 13.40:1 on Bone */

.d-stagegrid-legend { list-style: none; padding: 0; }
/* The word, not a tint. The legend prints all four steps by name; that is
   what makes it a legend rather than a colour key (review finding 3). */
.d-stagegrid-legend__word { white-space: nowrap; }

/* The deck is a spread being turned, so it is held at a page's width rather
   than stretched to the window. A spread that fills a 27-inch monitor stops
   reading as a document. */
.d-deck {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  max-width: 58rem;
  margin: var(--space-lg) 0;
}
.d-deck__stage { position: relative; }
.d-deck__spread {
  background: var(--surface-raised);
  border: 1px solid var(--rule-decor);
}
.d-deck__nav { justify-content: space-between; align-items: center; }

/* ==========================================================================
   15. S6 — THE MATH
   One slider, one price, one arc. The panel is the only raised surface on the
   screen, so the numbers read as a worked example rather than as a pricing
   table, which is what the illustrative label under it promises.
   ========================================================================== */

.d-math { display: flex; flex-direction: column; gap: var(--space-md); }
.d-math .d-display-2 { letter-spacing: var(--track-lg); }

.d-math__slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 2.5rem;
  margin: 0;
  background: none;
  cursor: pointer;
}
.d-math__slider::-webkit-slider-runnable-track {
  height: 4px;
  background: var(--rule);             /* non-text .. 4.70:1 on Bone Card */
  border-radius: 0;
}
.d-math__slider::-moz-range-track {
  height: 4px;
  background: var(--rule);
  border-radius: 0;
}
.d-math__slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 1.25rem;
  height: 1.25rem;
  margin-top: -0.5rem;
  border: 0;
  border-radius: 0;
  background: var(--btn-primary-bg);   /* Ink on Bone Card ......... 11.65:1 */
}
.d-math__slider::-moz-range-thumb {
  width: 1.25rem;
  height: 1.25rem;
  border: 0;
  border-radius: 0;
  background: var(--btn-primary-bg);
}

.d-arc { list-style: none; margin: 0; padding: 0; }
.d-arc__row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm) var(--space-md);
  padding: var(--space-sm) 0;
  border-top: 1px solid var(--rule-decor);
}
.d-arc__row .d-strong { font-size: var(--fs-lead); color: var(--fg); }

/* ==========================================================================
   16. S6, S7 — FIELDS AND FORMS
   The Build Desk form sits on the ink; the S6 field sits on Bone Card. One
   component, both grounds, through the surface contract. The field boundary
   IS the affordance, so it uses --rule (3.14:1 on Ink Deep, 4.70:1 on Bone
   Card) and never the decorative hairline.
   ========================================================================== */

.d-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  max-width: var(--measure);
  margin: var(--space-lg) 0;
}
.d-field { display: flex; flex-direction: column; gap: var(--space-2xs); }
.d-field__input {
  width: 100%;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.5;
  color: var(--fg);                    /* 12.07:1 on Ink Deep / 15.06:1 on Bone Card */
  background: var(--surface-raised);
  border: 1px solid var(--rule);       /* 3.14:1 on Ink Deep / 4.70:1 on Bone Card */
  border-radius: var(--radius);
  padding: 0.75rem;
  margin-top: var(--space-2xs);
  transition: border-color var(--dur) var(--ease-out);
}
.d-field__input::placeholder { color: var(--fg-soft); opacity: 1; }  /* 4.66:1 on Ink Deep */
.d-field__input:focus { border-color: var(--fg-accent); }  /* Signal on Ink Deep .. 9.06:1 */
textarea.d-field__input { resize: vertical; min-height: 5.5rem; }
select.d-field__input { cursor: pointer; }

/* The status line only exists once it has something to say, and when it does
   it is a statement, not a caption: full foreground, with an accent bar. */
.d-form__status { margin: 0; }
.d-form__status:not(:empty) {
  color: var(--fg);
  border-left: 2px solid var(--fg-accent);
  padding-left: var(--space-sm);
}

/* ==========================================================================
   17. S8 — THE FOUNDING OFFER
   The terms are set as a ruled schedule, label left in mono, value right in
   Fraunces. It should read like the terms page of a letter, because that is
   the object the offer is imitating.
   ========================================================================== */

.d-offer__terms {
  display: grid;
  grid-template-columns: minmax(8rem, 15rem) minmax(0, 1fr);
  gap: 0 var(--space-lg);
  max-width: var(--measure);
  margin: var(--space-lg) 0;
}
.d-offer__term-label,
.d-offer__term-value {
  margin: 0;
  padding: var(--space-sm) 0;
  border-top: 1px solid var(--rule-decor);
}
.d-offer__term-label {
  grid-column: 1;
  color: var(--fg-soft);               /* Bone 0.55 on Ink Abyss ...  4.96:1 */
}
.d-offer__term-value {
  grid-column: 2;
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-weight: var(--fw-display);
  font-size: var(--fs-display-4);
  line-height: 1.3;
  color: var(--fg);                    /* Bone on Ink Abyss ........ 13.60:1 */
}
@media (max-width: 34rem) {
  .d-offer__terms { grid-template-columns: minmax(0, 1fr); }
  .d-offer__term-label { grid-column: 1; padding-bottom: 0; }
  .d-offer__term-value { grid-column: 1; border-top: 0; padding-top: 0; }
}

/* ==========================================================================
   18. THE PRINT COVER, AND PRINT   (decision 0003B)

   The souvenir is the artifact that gets printed and slid across a table. A
   full-bleed #08201F cover with 1px Signal hairlines is the right object on a
   screen and the wrong one on an office printer: heavy toner coverage,
   visible banding, and hairlines that drop out entirely below about 1pt.

   So the cover exists in two treatments sharing ONE layout, ONE set of
   tokens, and ONE set of markup. It is a surface swap plus a relocation of
   the linework, not a second design. Verdict, byline, overall stage and the
   sample label are identical in both, in content and in position.

     screen  Ink Abyss ground, Bone verdict, linework in the right-hand
             negative space of the cover.
     print   Bone Card ground, Ink Deep verdict, the linework confined to the
             dark foot band - which is S4 direction 1b's construction, which
             is exactly what decision 0003B specifies.

   TWO DRIVERS, ONE TREATMENT. The switch is a 0/1 flag, the same mechanism
   tokens.css uses for --brand-plate-flip, so the class and the media query
   cannot drift apart and the swap is written down once:

       .d-cover--print   forces it   (the Phase 4 PDF generator)
       @media print      applies it to every cover (somebody hits Cmd-P)

   color-mix at 0% returns the second colour exactly and at 100% the first, so
   each pair below is an exact swap and never a blend at any intermediate
   value. Do not set --print-cover to anything but 0 or 1.
   ========================================================================== */

.d-cover { --print-cover: 0; }
.d-cover--print { --print-cover: 1; }
@media print { .d-cover { --print-cover: 1; } }

/* This block sits after .d-on-dark / .d-on-light in the cascade at equal
   specificity, which is what lets it own the cover's surface on both. */
.d-cover {
  background: var(--ink-abyss);   /* fallback if color-mix is unavailable */
  background: color-mix(in srgb, var(--bone-card)      calc(var(--print-cover) * 100%), var(--ink-abyss));
  --fg:         color-mix(in srgb, var(--ink-deep)      calc(var(--print-cover) * 100%), var(--bone));
  --fg-soft:    color-mix(in srgb, var(--text-soft)     calc(var(--print-cover) * 100%), var(--text-soft-on-dark));
  --fg-accent:  color-mix(in srgb, var(--signal-on-light) calc(var(--print-cover) * 100%), var(--signal));
  --rule:       color-mix(in srgb, var(--rule-strong)   calc(var(--print-cover) * 100%), var(--rule-on-dark));
  --rule-decor: color-mix(in srgb, var(--line)          calc(var(--print-cover) * 100%), var(--hairline-on-dark));
  --surface:    color-mix(in srgb, var(--bone-card)     calc(var(--print-cover) * 100%), var(--ink-abyss));
  --surface-raised: var(--surface);
  color: var(--fg);
  /* 1 while this subtree is on ink, 0 while it is on paper. The sample label
     and anything else that has to reverse reads this rather than guessing. */
  --on-ink: calc(1 - var(--print-cover));
}
/*  screen  Bone on Ink Abyss ............... 13.60:1   verdict, firm, stage
    print   Ink Deep on Bone Card ........... 13.57:1   verdict, firm
    screen  Bone 0.55 on Ink Abyss ..........  4.96:1   prepared-by
    print   Text Soft on Bone Card ..........  7.11:1   prepared-by
    screen  --rule-on-dark on Ink Abyss ......  3.24:1  non-text
    print   --rule-strong on Bone Card .......  4.70:1  non-text            */

/* The eyebrow follows the binding rule on both grounds: Signal on dark,
   --text-soft on light. Never --fg-accent on light (that would be
   --signal-on-light at 5.10:1, which passes, but the rule is the rule) and
   never the prospect's colour, at any size. */
.d-cover .d-eyebrow {
  color: color-mix(in srgb, var(--text-soft) calc(var(--print-cover) * 100%), var(--signal));
}
/* screen  Signal on Ink Abyss 10.21:1  ·  print  Text Soft on Bone Card 7.11:1 */

/* --- The foot band -------------------------------------------------------
   .d-cover__foot IS the band. On the screen cover it is a region of the ink
   with a hairline above it; in print it is the one piece of heavy ink on the
   page, full bleed to three edges, carrying the overall stage, the gap count
   and the sample label - and the only place a 1px Signal line survives a
   laser printer.

   That is why nothing here is conditional except geometry: in BOTH treatments
   the foot sits on ink and carries Bone type, so its colours are stated once
   and the flag only moves its edges. No new markup is required.

   If the PDF generator wants the strip separated from the foot it adds
   .d-cover__band and the foot returns to sitting on the paper.             */
.d-cover__foot,
.d-cover__band {
  position: relative;
  overflow: hidden;
  --fg: var(--bone);                   /* Bone on Ink Abyss ........ 13.60:1 */
  --fg-soft: var(--text-soft-on-dark); /* Bone 0.55 ................  4.96:1 */
  --fg-accent: var(--signal);          /* Signal on Ink Abyss ...... 10.21:1 */
  --rule: var(--rule-on-dark);         /* non-text .................  3.24:1 */
  --rule-decor: var(--hairline-on-dark);
  --on-ink: 1;
  color: var(--fg);
}
.d-cover__foot {
  /* Full bleed in print, flush in the padding box on screen. --print-cover is
     0 or 1, so each of these is one value or the other, never in between. */
  background: color-mix(in srgb, var(--ink-abyss) calc(var(--print-cover) * 100%), transparent);
  margin-inline: calc(var(--pad-page) * -1 * var(--print-cover));
  margin-bottom: calc(var(--pad-page) * -1 * var(--print-cover));
  padding-inline: calc(var(--pad-page) * var(--print-cover));
  padding-top: calc(var(--space-md) + var(--space-lg) * var(--print-cover));
  padding-bottom: calc(var(--space-lg) * var(--print-cover));
  /* The band is ~20-26% of the page, matching 1b. Zero on the screen cover. */
  min-height: calc(var(--print-cover) * 9rem);
  align-items: flex-end;
  /* The hairline above the foot belongs to the screen cover only; in print
     the edge of the band is the rule. */
  border-top-color: color-mix(in srgb, transparent calc(var(--print-cover) * 100%), var(--hairline-on-dark));
}
/* If a real band element is present, it takes the ink and the foot goes back
   onto the paper. Both treatments still show the same four things. */
.d-cover:has(.d-cover__band) .d-cover__foot {
  background: none;
  margin: 0;
  padding-inline: 0;
  min-height: 0;
  --fg: color-mix(in srgb, var(--ink-deep) calc(var(--print-cover) * 100%), var(--bone));
  --fg-soft: color-mix(in srgb, var(--text-soft) calc(var(--print-cover) * 100%), var(--text-soft-on-dark));
  --fg-accent: color-mix(in srgb, var(--signal-on-light) calc(var(--print-cover) * 100%), var(--signal));
  --on-ink: calc(1 - var(--print-cover));
}
.d-cover__band {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-md);
  margin: var(--space-lg) calc(var(--pad-page) * -1) calc(var(--pad-page) * -1);
  padding: 0 var(--pad-page) var(--space-lg);
  min-height: clamp(6rem, 16cqi, 13rem);
  background: var(--ink-abyss);
}

/* The sample label reverses to an Ink chip wherever it sits on paper, and
   stays Bone at 0.72 wherever it sits on ink - which is the same rule on both
   treatments, driven by --on-ink rather than by which cover this is:
       on ink    Bone 0.72 on Ink Abyss ....................... 7.59:1
       on paper  Bone on an Ink chip ......................... 10.37:1
   Both clear the 0.72 floor from design brief 9.2 by a wide margin. The
   string carrying acceptance criterion 9 is never the faintest thing on
   either treatment, which is the whole reason the floor exists. */
.d-cover .d-sample-label {
  display: inline-block;
  color: color-mix(in srgb, var(--label-sample-on-dark) calc(var(--on-ink) * 100%), var(--label-sample-ink-on-light));
  background: color-mix(in srgb, transparent calc(var(--on-ink) * 100%), var(--label-sample-chip-on-light));
  padding: calc(var(--space-2xs) * (1 - var(--on-ink))) calc(var(--space-xs) * (1 - var(--on-ink)));
}

/* Linework relocation. On the print cover the hairlines must not paint on
   Bone - they would be at 1.50:1 on screen and gone entirely on paper - so
   the cover-level instance is dropped and only an instance inside the band
   renders. Phase 4 moves the [data-role="linework"] host into the band. */
.d-cover--print > .d-linework { display: none; }
.d-cover--print .d-cover__foot > .d-linework,
.d-cover--print .d-cover__band > .d-linework { display: block; }

@media print {
  .d-app { background: var(--bone-card); color: var(--text); }
  .d-shell { min-height: 0; }
  .d-screen { min-height: 0; page-break-inside: avoid; }
  .d-readout__page { page-break-after: always; }
  .d-bar, .d-foot, .d-progress, .d-actions, .d-deck__nav { display: none; }

  /* Marked grid cells: outline and word, no reversed type. A dropped fill on
     an office printer must not be able to erase the finding. */
  .d-stagegrid__cell { border: 1px solid var(--ink); background: none !important; }
  .d-stagegrid__cell.is-marked { border-width: 2px; }
  .d-stagegrid__mark,
  .d-on-light .d-stagegrid__mark { color: var(--ink) !important; }
  .d-stagegrid__mark-full { display: inline !important; }
  .d-stagegrid__mark-short { display: none !important; }

  /* Print-color-adjust only where the fill IS the design and its loss would
     not remove information: the foot band and the logo plate. */
  .d-cover__foot,
  .d-cover__band,
  .d-plate { print-color-adjust: exact; -webkit-print-color-adjust: exact; }

  /* Hairlines drop out on office printers, so the signature only prints where
     it sits on a dark ground: the foot band (decision 0003B). */
  .d-linework { display: none; }
  .d-cover__foot > .d-linework,
  .d-cover__band > .d-linework { display: block; }

  /* A sequence does not print. The resolved frame is the printable S3; the
     record beside it prints too wherever app.js has already shown it. */
  .d-read__beat:not(.d-read__beat--resolved) { display: none; }
  .d-read__stage { min-height: 0; }

  * { animation: none !important; transition: none !important; }
}


/* ---------------------------------------------------------------------------
   [hidden] wins. Every component below sets `display`, which beats the user
   agent's `[hidden] { display: none }` on specificity, so without this every
   screen renders at once. F1 worked around it with an inline style; this rule
   is the real fix and lets that workaround go.
   --------------------------------------------------------------------------- */
[hidden] { display: none !important; }

/* ---------------------------------------------------------------------------
   Skip link. Off-screen until focused, then anchored top-left on the ink
   surface. Without this it renders as visible black text above every screen.
   --------------------------------------------------------------------------- */
.d-skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 0.75rem 1.25rem;
  background: var(--ink-abyss);
  color: var(--signal);
  border: 1px solid var(--signal);
  text-decoration: none;
}
.d-skip-link:focus,
.d-skip-link:focus-visible { left: 0; }
