/* Hallmark · genre: editorial · macrostructure: 05 Workbench (four-stage product grid, 2×2 at ≥68rem) · theme: custom (vibe: "clinical letterhead, warm paper, terracotta" · paper oklch(95.8% 0.007 97.4) · accent oklch(57.3% 0.154 37.8) warm · Newsreader + Segoe UI) · enrichment: cropped screenshots + Tier-B SVG · nav: N10 masthead → scroll-morph · footer: Ft4 colophon (mono · log-style, labelled rows) · axes: light+deep-chapter / roman-serif / warm
 * contrast: pass (40-41, site/tools/check-contrast.py) · slop: pass (42-45) · honest: pass (46) · chrome: pass (47) · tokens: pass (48) · responsive: pass (49) · icons: pass (30) · mobile: pass (34, 49, 50-57)
 * note: --color-sheet stays pure #ffffff by brand parity with the app's --card token (gates 7/22 exception, bounded panels + figure mats only). --surface-sheet is the BAND surface and is deliberately not pure white.
 * note: theme rotation suspended — see tokens.css and .hallmark/log.json. */
/* Hallmark · pre-emit critique: P5 H5 E5 S5 R5 V5 */
/* Third pass · 2026-08-10 art-directed redesign: three chapter surfaces (paper / cool
 * sheet / deep teal) via .band + .wrap, full-bleed with no viewport math; hero diptych;
 * the four-stage product story as a 2×2 grid; the cell-rule motif as the page's one divider language;
 * cropped screenshots replacing full frames. Preserved intact from the second pass: the
 * base reset, :focus-visible (instant, never animated), the skip link, interpolate-size,
 * the reduced-motion block, pointer:coarse, forced-colors, and both @media print blocks —
 * extended, not replaced. */

/* ---------- base ---------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  overflow-x: clip;
}

/* lets ::details-content transition to height: auto where supported */
:root {
  interpolate-size: allow-keywords;
}

@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

body {
  background: var(--color-paper);
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  -webkit-font-smoothing: antialiased;
  overflow-wrap: break-word;
}

/* The nav is sticky and opaque, so anything scrolled to by the browser — an anchor
   target, or a control the browser reveals during reverse tab traversal — has to clear
   it. Sections alone were not enough: Shift+Tab was parking focused FAQ summaries up to
   71% behind the bar, and the skip link's #top target put the h1's cap-height under it. */
section[id],
main[id],
:is(a[href], button, summary, [tabindex]) {
  scroll-margin-top: calc(var(--nav-h) + var(--space-xs));
}

h1,
h2,
h3 {
  font-family: var(--font-display);
  font-style: normal;
  font-weight: 600;
  line-height: var(--leading-display);
  letter-spacing: var(--track-display);
  margin: 0 0 var(--space-sm);
  overflow-wrap: anywhere;
  min-width: 0;
}

h2 {
  font-size: var(--text-section);
  max-width: 20ch;
  text-wrap: balance;
}

h3,
.sub-h {
  font-size: var(--text-md);
}

/* `.sub-h` (0,1,0) outranks the bare `h2` element selector, so h2.sub-h and h3.sub-h
   both rendered at --text-md and the prose pages lost a level: on /how-it-works/ a
   chapter head and a step inside it were indistinguishable. .sub-h is used only on the
   subpages - index.html cannot be affected. The prose ladder becomes 32 / 25 / 20 / 16. */
h2.sub-h {
  font-size: var(--text-lg);
}

p {
  margin: 0 0 var(--space-sm);
  max-width: var(--measure);
}

/* File paths and dotted identifiers are single unbreakable tokens. Left alone they set
   a min-content floor that pushes a grid track wider than a 320px viewport, so they get
   `anywhere` (which does affect intrinsic sizing) rather than `break-word` (which does
   not). Prose gets break-word as a general safety net. */
code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  overflow-wrap: anywhere;
}

img {
  max-width: 100%;
  height: auto;
  display: block;
}

::selection {
  background: var(--color-tint);
}

/* screen-reader-only: carries the stage numeral into the heading's accessible name,
   so "1.0 · Add" is announced exactly as it was before the numeral was set large */
.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- focus (instant, never animated) ---------- */
/* --focus-ring is re-pointed per chapter surface below, so the ring is always ≥3:1 */
:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  transition: none;
}

/* ---------- skip link ---------- */
.skip-link {
  position: absolute;
  left: var(--space-sm);
  top: var(--space-3xs);
  z-index: 30;
  padding: var(--space-2xs) var(--space-sm);
  background: var(--color-ink);
  color: var(--color-paper);
  text-decoration: none;
  border-radius: var(--radius-sm);
  transform: translateY(-200%);
}

.skip-link:focus-visible {
  transform: none;
}

/* ---------- the surface contract ----------
   Three chapter surfaces, one component set. Every component below draws with these
   indirections instead of naming a colour, so the same markup is correct on warm paper,
   on the cool sheet, and on deep teal. Paper is the default, declared on :root so that
   anything outside a band (nav, masthead, skip link) is already correct. */
:root {
  --hair: var(--color-hairline);
  --muted: var(--color-muted);
  --accent-fg: var(--color-accent-text);   /* body-size accent text */
  --accent-graphic: var(--color-accent);   /* rules, ticks, fills, large numerals */
  --focus-ring: var(--color-focus);
  --plate: var(--color-sheet);             /* the mat a screenshot sits on */
  --plate-hair: var(--color-hairline);
  --shadow: var(--shadow-plate);
  --ghost-line: var(--ghost-line-paper);
  --reg: color-mix(in oklch, var(--muted) 55%, transparent);
}

.band {
  position: relative;
  padding-block: var(--space-2xl);
  /* the distance from the viewport edge to the page's text column — the one measurement
     that makes full-bleed possible without any 100vw math (which breaks when a scrollbar
     is visible). 100% here is the band's own width, i.e. the viewport minus its scrollbar. */
  --edge: max(var(--page-gutter), calc((100% - var(--wrap-max)) / 2 + var(--page-gutter)));
}

.band-paper {
  background: var(--color-paper);
  color: var(--color-ink);
}

.band-sheet {
  background: var(--surface-sheet);
  color: var(--color-ink);
  --hair: var(--hairline-sheet);
  --plate-hair: var(--hairline-sheet);
}

.band-deep {
  background: var(--surface-deep);
  color: var(--on-deep);
  --hair: var(--hairline-deep);
  --muted: var(--on-deep-muted);
  --accent-fg: var(--accent-on-deep);
  --accent-graphic: var(--accent-on-deep);
  /* --focus-ring is deliberately NOT re-pointed here: --color-focus measures 4.83:1 on
     this surface, and outline-offset seats the ring on the band rather than on the
     button fill, so the paper ring is already correct on deep. */
  --plate: var(--surface-sheet);
  --plate-hair: var(--hairline-sheet);
  --shadow: var(--shadow-plate-deep);
  --ghost-line: var(--ghost-line-deep);
}

/* chapters that continue across a section boundary close the seam themselves */
.band-sheet-open,
.band-deep-open {
  padding-bottom: var(--space-lg);
}

.band-sheet-close,
.band-deep-close {
  padding-top: 0;
}

.wrap {
  max-width: var(--wrap-max);
  margin-inline: auto;
  padding-inline: var(--page-gutter);
}

/* a wrap that keeps one edge on the page grid and lets the other run to the viewport */
.wrap-bleed-right {
  max-width: var(--wrap-max);
  margin-inline: auto;
  padding-inline: var(--page-gutter);
}

.section-lede {
  color: var(--muted);
  margin-top: calc(-1 * var(--space-2xs));
  max-width: var(--measure-tight);
}

/* .table-scroll carries a top margin and no bottom one, so in #specifications the
   closing link sat flush against the last row and read as an eighth table row. 2.5rem
   is exactly the gap #coverage already shows; margins collapse, so that one is unmoved. */
.section-close {
  color: var(--muted);
  margin-block-start: var(--space-lg);
}

/* ---------- the motif ----------
   Derived from the mark itself (a circle enclosing a two-column table): a bounded rule
   broken by a dividing tick. One divider language for the whole page. */
.cell-rule {
  border: 0;
  height: var(--tick-h);
  margin: var(--space-md) 0;
  position: relative;
  overflow: visible;
}

.cell-rule::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: var(--rule-hair);
  background: var(--hair);
  transform-origin: left center;
}

.cell-rule::after {
  content: "";
  position: absolute;
  left: 38.2%;
  bottom: 0;
  width: var(--rule-hair);
  height: var(--tick-h);
  background: var(--hair);
}

/* the seam rule between two movements of one chapter: tighter, no draw */
.cell-rule-seam {
  margin-block: 0 var(--space-xl);
}

/* M5 · chapter rule draw — the one entrance per chapter, replacing the second pass's
   per-element fade-up on every step. Four on the page. */
html.io .cell-rule[data-draw]::before {
  transform: scaleX(0);
  transition: transform var(--dur-long) var(--ease-out);
}

html.io .cell-rule[data-draw]::after {
  opacity: 0;
  transition: opacity var(--dur-short) var(--ease-out) var(--dur-long);
}

html.io .cell-rule[data-draw].is-in::before {
  transform: scaleX(1);
}

html.io .cell-rule[data-draw].is-in::after {
  opacity: 1;
}

/* The column ghost — literally a score-table column grid, at the threshold of visibility.
   Inset to the text column rather than the full band: run edge-to-edge, its 72px lines
   landed 8px inside the content edge on both sides, and a near-miss reads as an error
   where a clean landing reads as structure. */
.ghost {
  position: absolute;
  inset: 0 var(--edge);
  pointer-events: none;
  background-image: repeating-linear-gradient(
    90deg,
    var(--ghost-line) 0 var(--rule-hair),
    transparent var(--rule-hair) var(--ghost-step)
  );
}

/* ---------- screenshots ----------
   Crops only. Nothing is retouched, recomposed, or re-drawn: the window head visible in
   these images is the application's own chrome (gate 47 — no fake title bars, traffic
   lights, or URL pills anywhere on this page). */
.shot {
  margin: 0;
  position: relative;
}

.shot-mat {
  position: relative;
  background: var(--plate);
  border: var(--rule-hair) solid var(--plate-hair);
  border-radius: var(--radius-sm);
  padding: var(--space-2xs);
  box-shadow: var(--shadow);
}

.shot-mat img {
  border-radius: 2px;
}

/* Registration corners — the mark's rectangle corners as crop marks. This is how a
   screenshot (and the price panel, which is a specimen of the same kind) gets a frame
   without anyone drawing browser furniture around it.
   Anchored to .shot-mat, NOT .shot: the mat is the box the crop marks have to register,
   and .shot can be taller than it. Hung off .shot the marks floated ~114px above and
   ~165px below the thing they are supposed to register. */
.shot-mat::before,
.panel::before {
  content: "";
  position: absolute;
  inset: calc(-1 * var(--space-xs));
  pointer-events: none;
  background-repeat: no-repeat;
  background-image:
    linear-gradient(var(--reg) 0 0), linear-gradient(var(--reg) 0 0),
    linear-gradient(var(--reg) 0 0), linear-gradient(var(--reg) 0 0),
    linear-gradient(var(--reg) 0 0), linear-gradient(var(--reg) 0 0),
    linear-gradient(var(--reg) 0 0), linear-gradient(var(--reg) 0 0);
  background-size:
    var(--space-xs) var(--rule-hair), var(--rule-hair) var(--space-xs),
    var(--space-xs) var(--rule-hair), var(--rule-hair) var(--space-xs),
    var(--space-xs) var(--rule-hair), var(--rule-hair) var(--space-xs),
    var(--space-xs) var(--rule-hair), var(--rule-hair) var(--space-xs);
  background-position:
    left top, left top,
    right top, right top,
    left bottom, left bottom,
    right bottom, right bottom;
}

.shot figcaption {
  color: var(--muted);
  font-size: var(--text-sm);
  /* The mat's corner marks are inset -12px (--space-xs), so an 8px gap put the bottom
     pair 4px INTO the caption's first line on every captioned figure. 24px clears the
     marks plus 12px of air. */
  margin-top: var(--space-md);
  padding-inline: var(--space-3xs);
  max-width: var(--measure-tight);
}

/* ---------- masthead (N6 → N10) ---------- */
.masthead {
  text-align: center;
  background: var(--color-paper);
}

.mast-line {
  font-family: var(--font-display);
  font-variant-caps: all-small-caps;
  letter-spacing: var(--track-caps);
  font-size: var(--text-sm);
  color: var(--color-muted);
  max-width: none;
  margin: 0;
  padding: var(--space-sm) var(--page-gutter) 0;
}

.mast-name {
  margin: var(--space-2xs) 0 var(--space-sm);
  max-width: none;
  padding-inline: var(--page-gutter);
}

.mast-name a {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-2xl);
  letter-spacing: var(--track-display);
  color: inherit;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
}

.mark {
  width: 1em;
  height: 1em;
  flex: none;
}

.mast-sentinel {
  height: 1px;
}

.mast-nav {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--color-paper);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  min-height: var(--nav-h);
  padding-inline: var(--page-gutter);
  /* M2 · fixed geometry: only this color changes when the bar sticks */
  border-bottom: var(--rule-hair) solid transparent;
  transition: border-bottom-color var(--dur-short) var(--ease-out);
}

.mast-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  column-gap: var(--space-md);
  row-gap: var(--space-3xs);
}

.mast-nav ul a {
  display: inline-block;
  color: var(--color-ink);
  font-size: var(--text-sm);
  text-decoration: none;
  padding: var(--space-2xs) 0;
  border-bottom: 2px solid transparent;
  transition: border-bottom-color var(--dur-micro) var(--ease-out);
}

.mast-nav ul a:hover {
  border-bottom-color: var(--color-hairline);
}

/* "location" is the homepage's JS section tracking; "page" is the subpages' own
   static marker. :is() takes its most specific argument, so specificity is unchanged. */
.mast-nav ul a:is([aria-current="location"], [aria-current="page"]) {
  border-bottom-color: var(--color-accent);
}

/* `.mast-nav ul a` (0,1,2) outranked a bare `.nav-cta a` (0,1,1), so the pill's own
   padding and border never applied: it computed `padding: 8px 0` and a transparent
   2px bottom border against a 1px accent border on the other three sides - an
   open-bottomed box whose sides touched the glyphs. Raised to (0,2,3), and only the
   inline padding is set: the block axis belongs to the (pointer: coarse) rule at the
   foot of this file, and the 40rem open-menu rule still outranks both. */
.mast-nav ul li.nav-cta a {
  color: var(--color-accent-text);
  border: var(--rule-hair) solid var(--color-accent);
  border-radius: var(--radius-md);
  padding-inline: var(--space-xs);
}

.mast-nav ul li.nav-cta a:hover {
  background: var(--color-tint);
  border-bottom-color: var(--color-accent);
}

.mast-mini {
  display: none;
  align-items: center;
  gap: var(--space-2xs);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-md);
  color: inherit;
  text-decoration: none;
  margin-right: auto;
  white-space: nowrap;
}

.mast-nav.is-stuck {
  border-bottom-color: var(--color-hairline);
}

/* M4 · nav progress rail — where you are in the page, read straight off the scroll
   position. Ornamental, so it is the one place a scroll-timeline is used; behind
   @supports, and the bar keeps its plain hairline everywhere else. */
@supports (animation-timeline: scroll()) {
  .mast-nav::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: calc(-1 * var(--rule-hair));
    width: 100%;
    height: var(--rule-stage);
    background: var(--color-accent);
    transform: scaleX(0);
    transform-origin: left center;
    animation: nav-rail linear both;
    animation-timeline: scroll(root block);
  }

  @keyframes nav-rail {
    to {
      transform: scaleX(1);
    }
  }
}

/* On wide viewports the condensed wordmark overlays the left gutter and fades in —
   the link row itself never moves. Below 68rem the stuck bar just gains its rule.
   (68rem keeps the overlay clear of the centered link row; visibility keeps the
   hidden link out of the tab order and the accessibility tree.) */
@media (min-width: 68rem) {
  .mast-mini {
    display: inline-flex;
    position: absolute;
    left: var(--page-gutter);
    top: 50%;
    transform: translateY(-50%) translateX(-4px);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    margin-right: 0;
    transition:
      opacity var(--dur-short) var(--ease-out),
      transform var(--dur-short) var(--ease-out),
      visibility var(--dur-short);
  }

  .mast-nav.is-stuck .mast-mini {
    opacity: 1;
    visibility: visible;
    transform: translateY(-50%);
    pointer-events: auto;
  }
}

.mast-menu {
  display: none;
  font: 600 var(--text-sm) var(--font-body);
  color: var(--color-ink);
  background: none;
  border: var(--rule-hair) solid var(--color-hairline);
  border-radius: var(--radius-sm);
  padding: var(--space-2xs) var(--space-sm);
  min-height: 44px;
  transition: background-color var(--dur-micro) var(--ease-out);
}

.mast-menu:hover {
  background: var(--color-tint);
}

.mast-menu:active {
  transform: translateY(1px);
}

.mast-rule {
  border: 0;
  border-block: var(--rule-hair) solid var(--color-hairline);
  height: 4px;
  margin: 0 var(--page-gutter);
}

/* ---------- buttons & quiet links ---------- */
.btn-primary {
  display: inline-block;
  background: var(--color-accent);
  color: var(--color-on-accent);
  font: 600 var(--text-base) var(--font-body);
  text-decoration: none;
  padding: var(--space-xs) var(--space-md);
  border-radius: var(--radius-md);
  min-height: 44px;
  transition:
    background-color var(--dur-micro) var(--ease-out),
    transform var(--dur-micro) var(--ease-out);
}

.btn-primary:hover {
  background: var(--color-accent-text);
}

.btn-primary:active {
  transform: translateY(1px);
}

/* on deep the fill lifts and the label goes dark — a light-on-dark button would
   disappear into the band, and a coloured halo is a named tell */
.band-deep .btn-primary {
  background: var(--accent-on-deep);
  color: var(--color-ink);
}

.band-deep .btn-primary:hover {
  background: color-mix(in oklch, var(--accent-on-deep), white 14%);
}

.link-quiet {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--accent-graphic);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.25em;
  padding: var(--space-2xs) 0;
  transition: text-decoration-thickness var(--dur-micro) var(--ease-out);
}

.link-quiet:hover {
  text-decoration-thickness: 2px;
}

.label-caps {
  font: 600 var(--text-xs) var(--font-body);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--muted);
  margin: 0 0 var(--space-2xs);
}

/* ---------- hero · the bleed-right diptych ---------- */
/* fold budget: the primary CTA must clear a 1280×800 viewport (audit hero-1) */
.hero {
  padding-top: var(--space-lg);
  padding-bottom: var(--space-lg);
  z-index: 1;
}

.hero-grid {
  display: grid;
  gap: var(--space-lg);
}

.hero h1 {
  font-size: var(--text-display);
  max-width: 20ch;
  text-wrap: balance;
  margin-bottom: var(--space-md);
}

.hero-sub {
  font-size: var(--text-md);
  max-width: 44ch;
}

.hero-ctas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md);
  margin: var(--space-lg) 0;
}

.hero-trust {
  color: var(--muted);
  font-size: var(--text-sm);
  max-width: none;
}

.hero-plate {
  position: relative;
  z-index: 1;
}

/* M1 · hero entrance — one orchestrated arrival on load, then the page settles */
@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}

@keyframes plate-arrive {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
}

html.js .hero-copy > * {
  animation: rise var(--dur-long) var(--ease-out) backwards;
}

html.js .hero-copy .hero-sub {
  animation-delay: 80ms;
}

html.js .hero-copy .hero-ctas {
  animation-delay: 160ms;
}

html.js .hero-copy .hero-trust {
  animation-delay: 240ms;
}

html.js .hero-plate {
  animation: plate-arrive var(--dur-long) var(--ease-out) 200ms backwards;
}

/* ---------- workflow · the four stages ---------- */
.band-stage {
  z-index: 0;
}

/* .stages is itself a wrap — it must not reset the inline padding that gives it a
   gutter on phones, so only the block axis is zeroed here */
.stages {
  list-style: none;
  margin-block: var(--space-md) var(--space-lg);
  padding-block: 0;
}

.stage-num {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-numeral);
  line-height: var(--leading-flat);
  letter-spacing: var(--track-display);
  font-variant-numeric: tabular-nums;
  color: var(--accent-graphic);
  margin: 0 0 var(--space-2xs);
  max-width: none;
}

.stage-title {
  font-size: var(--text-xl);
  margin-bottom: var(--space-sm);
}

.stage-body {
  max-width: var(--measure-tight);
}

/* The mat is a full-width block, so an image narrower than the mat would sit left with
   a bare strip beside it. Each authentic crop keeps its own content-led aspect; width
   fills the mat and height remains intrinsic, with desktop alignment handled by the
   stage grid rather than by forcing every screenshot into one ratio. */
.stage-fig img {
  width: 100%;
  height: auto;
}

/* ---------- coverage · the ledger ---------- */
.tiers {
  margin: var(--space-md) 0 var(--space-lg);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-lg);
}

.tiers dt {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-md);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  border-top: var(--rule-stage) solid currentColor;
  padding-top: var(--space-xs);
  position: relative;
}

.tier-primary dt {
  font-size: var(--text-lg);
}

.tier-count {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--text-md);
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  flex: none;
}

.tiers dd {
  margin: var(--space-2xs) 0 0;
  max-width: none;
}

.tier-note {
  display: block;
  color: var(--muted);
  font-size: var(--text-sm);
  margin-bottom: var(--space-2xs);
}

.tier-list {
  list-style: none;
  margin: var(--space-2xs) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  column-gap: var(--space-lg);
}

.tier-list li {
  border-top: var(--rule-hair) solid var(--hair);
  padding-block: var(--space-2xs);
}

/* the writes-today tier carries the accent tick — the motif's divider, turned marker */
.tier-primary .tier-list li {
  position: relative;
  padding-left: var(--space-sm);
}

.tier-primary .tier-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: var(--space-2xs);
  width: var(--rule-stage);
  height: 0.85em;
  background: var(--accent-graphic);
}

.tier-form {
  display: block;
  color: var(--muted);
  font-size: var(--text-sm);
}

.shot-strip {
  margin-top: var(--space-lg);
}

/* ---------- security · the vault ---------- */
.vault {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-xl);
}

/* Two near-identical discs 24px apart under one caption read as a double render. Each
   mark now says which it is, and the shared caption below keeps carrying the claim.
   A two-row grid rather than two flex columns: the marks then register on their own
   bottom edges (the captured chip's mat is 10px taller than the drawn seal) no matter
   how many lines the labels beneath them wrap to. */
.seal-pair {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: auto auto;
  justify-content: start;
  justify-items: start;
  align-items: end;
  column-gap: var(--space-md);
  row-gap: var(--space-2xs);
}

.seal-label {
  margin: 0;
  max-width: 6.5rem;
  align-self: start;
}

.seal {
  width: 88px;
  height: auto;
  display: block;
  color: currentColor;
  flex: none;
}

.seal-lg {
  width: 120px;
  margin: 0 auto var(--space-lg);
}

.seal .seal-check {
  stroke: var(--accent-graphic);
}

.shot-chip {
  flex: none;
}

.shot-chip .shot-mat {
  padding: var(--space-3xs);
}

.shot-chip img {
  width: 88px;
}

.seal-caption {
  color: var(--muted);
  font-size: var(--text-sm);
  max-width: 38ch;
  margin: var(--space-sm) 0 0;
}

/* M6 · the seal's check draws once its panel is in view. Generalised from the second
   pass's #security-only rule so the closing seal draws too. */
html.io [data-seal] .seal-check {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset var(--dur-long) var(--ease-out) 150ms;
}

html.io [data-seal].is-in .seal-check {
  stroke-dashoffset: 0;
}

.allowlist {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-lg);
}

.allowlist-col {
  position: relative;
  border-top: var(--rule-hair) solid var(--hair);
  padding-top: var(--space-sm);
}

.allowlist-col::before {
  content: "";
  position: absolute;
  top: 0;
  left: 38.2%;
  width: var(--rule-hair);
  height: var(--tick-h);
  background: var(--hair);
}

.allowlist-items {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  letter-spacing: var(--track-mono);
  margin: 0;
  max-width: none;
}

/* the automated-test proof is the page's strongest credibility artifact — bracketed,
   never fine print */
.pin-note {
  position: relative;
  font-size: var(--text-base);
  color: inherit;
  margin: 0;
  padding-left: var(--space-md);
  max-width: var(--measure);
}

.pin-note::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.15em;
  bottom: 0.15em;
  width: var(--space-2xs);
  border: var(--rule-stage) solid var(--accent-graphic);
  border-right: 0;
}

.vault-prose {
  max-width: var(--measure);
}

.vault-prose p:last-child {
  margin-bottom: 0;
}

/* ---------- specifications · the sheet, continuous ---------- */
/* deliberately the quietest head on the page: a Workbench reference sheet does not shout */
.spec-h {
  font-size: var(--text-xl);
  color: var(--muted);
}

.table-scroll {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  margin-top: var(--space-lg);
}

.spec-sheet {
  width: 100%;
  min-width: 44rem;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
  /* rows are drawn with border-top only, so without this the table just stops */
  border-bottom: var(--rule-hair) solid var(--hair);
}

.spec-sheet th,
.spec-sheet td {
  text-align: left;
  vertical-align: top;
  border-top: var(--rule-hair) solid var(--hair);
  padding: var(--space-sm) var(--space-md) var(--space-sm) 0;
}

/* three registers per row: the label in small caps, the value in body, the
   qualifier in mono — editorial voice beside machine truth */
.spec-sheet th {
  font: 600 var(--text-xs) var(--font-body);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--muted);
  white-space: nowrap;
  width: 9rem;
  padding-top: calc(var(--space-sm) + 0.25em);
}

.spec-sheet td:last-child {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  letter-spacing: var(--track-mono);
  color: var(--muted);
  padding-right: 0;
}

/* ---- the same sheet, a different job ----
   On index.html the <th> IS the label - PLATFORM / INPUT / OUTPUT - and --text-xs is
   correctly the panel-label token. On /assessments/ the same <th scope="row"> carries
   the assessment name, the column a reader scans, so it takes body register instead;
   nowrap + 9rem was also inflating that column to 340px to hold one long name. */
.spec-sheet-index th[scope="row"] {
  font: 600 var(--text-sm) var(--font-body);
  text-transform: none;
  letter-spacing: normal;
  color: inherit;
  white-space: normal;
}

/* `width` on a cell is only a hint to the auto table algorithm, which read the long
   assessment names as a reason to hand column 1 the spare space - 384px of a 1024px
   table, still squeezing the two columns that carry the content. Fixed layout takes
   the declared width literally and splits the rest evenly between the value columns.
   Widths are declared on the head row, because that is the row fixed layout reads. */
.spec-sheet-index {
  table-layout: fixed;
}

.spec-sheet-index thead th {
  width: auto;
}

.spec-sheet-index thead th:first-child {
  width: 11rem;
}

/* A per-cell label for the phone restack only, where the column heads are off-screen.
   Real DOM rather than generated content, and aria-hidden: the table's own header
   associations survive the restack intact, so a ::before label would double-announce. */
.cell-label {
  display: none;
}

/* ---------- pricing · the card, off-centre ---------- */
.pricing-field {
  display: grid;
  gap: var(--space-lg);
}

.panel {
  position: relative;
  background: var(--plate);
  border: var(--rule-hair) solid var(--plate-hair);
  border-radius: var(--radius-md);
  padding: var(--space-md);
  max-width: 28rem;
  box-shadow: var(--shadow);
}

.panel-label {
  margin-bottom: var(--space-xs);
}

.price {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-4xl);
  line-height: var(--leading-display);
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.price-unit {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--text-base);
  font-variant-caps: all-small-caps;
  letter-spacing: var(--track-caps);
  color: var(--muted);
}

.price-note {
  color: var(--muted);
  font-size: var(--text-sm);
  margin-top: var(--space-3xs);
}

.facts {
  list-style: none;
  margin: var(--space-md) 0 var(--space-lg);
  padding: 0;
}

.facts li {
  position: relative;
  border-top: var(--rule-hair) solid var(--hair);
  padding-block: var(--space-2xs);
  font-size: var(--text-sm);
}

.facts li::before {
  content: "";
  position: absolute;
  top: 0;
  left: 38.2%;
  width: var(--rule-hair);
  height: var(--tick-h);
  background: var(--hair);
}

/* ---------- FAQ · the interview ---------- */
/* Narrow measure, but still hung on the page's left spine. Capping the wrap itself
   would re-centre the whole block and break the alignment every other chapter keeps. */
.faq-wrap > * {
  max-width: 52rem;
}

/* The one child of a .faq-wrap that is not prose. /assessments/ hangs its three-column
   coverage table in this wrap, and a 52rem reading measure capped that table at 832px
   inside a 1152px page - the reason the page read as half empty. Scoped to the wrapper
   rather than raising the wrap's own cap, for the reason given directly above. */
.faq-wrap > .table-scroll-wide {
  max-width: none;
}

#faq details {
  border-top: var(--rule-hair) solid var(--hair);
}

/* the chapter's cell rule already sits 40px above; a second identical hairline there
   was the page's one divider language repeating itself for no reason */
#faq details:first-of-type {
  border-top: 0;
}

#faq summary {
  position: relative;
  list-style: none;
  cursor: pointer;
  padding: var(--space-md) var(--space-xl) var(--space-md) 0;
  min-height: 44px;
}

#faq summary::-webkit-details-marker {
  display: none;
}

#faq summary:hover .faq-q {
  color: var(--accent-fg);
}

#faq .faq-q {
  display: inline;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-md);
  letter-spacing: var(--track-display);
  transition: color var(--dur-micro) var(--ease-out);
}

/* the disclosure glyph is the cell rule itself: a hairline with a dividing tick.
   Opening lays the tick flat onto the rule. */
#faq summary::before {
  content: "";
  position: absolute;
  right: var(--space-3xs);
  top: 50%;
  width: 16px;
  height: var(--rule-hair);
  margin-top: calc(-0.5 * var(--rule-hair));
  /* --hair is tuned for rules between rows and is far too faint at 16px; the glyph has
     to read as a rule crossed by a tick, or the motif is lost and only the tick shows */
  background: var(--reg);
}

#faq summary::after {
  content: "";
  position: absolute;
  right: calc(var(--space-3xs) + 7px);
  top: 50%;
  width: var(--rule-stage);
  height: 12px;
  margin-top: -6px;
  background: var(--accent-graphic);
  transition: transform var(--dur-short) var(--ease-out);
}

#faq details[open] summary::after {
  transform: rotate(90deg);
}

/* answer eases open where ::details-content is supported; a clean cut elsewhere */
#faq details::details-content {
  height: 0;
  overflow: clip;
  transition:
    height var(--dur-short) var(--ease-out),
    content-visibility var(--dur-short) allow-discrete;
}

#faq details[open]::details-content {
  height: auto;
}

.faq-body {
  padding-bottom: var(--space-md);
}

.faq-body p {
  margin: 0 0 var(--space-sm);
  max-width: var(--measure-tight);
}

.faq-body p:last-child {
  margin-bottom: 0;
}

/* ---------- demo · the close ---------- */
.band-deep-open {
  padding-top: var(--space-3xl);
}

.demo-wrap {
  text-align: center;
}

.demo-wrap h2 {
  max-width: 18ch;
  margin-inline: auto;
}

.demo-wrap p {
  margin-inline: auto;
}

.demo-lede {
  max-width: var(--measure-tight);
}

.demo-cta {
  margin-block: var(--space-lg) var(--space-2xs);
}

.demo-note {
  color: var(--muted);
  font-size: var(--text-sm);
}

.demo-close {
  color: var(--muted);
  font-size: var(--text-sm);
  max-width: none;
}

/* M7 · the app's signature bloom — once, when #demo first enters the viewport */
@keyframes bloom {
  0% {
    box-shadow: 0 0 0 0 transparent;
  }

  50% {
    box-shadow: 0 0 0 5px var(--glow-deep);
  }

  100% {
    box-shadow: 0 0 0 0 transparent;
  }
}

.bloom {
  animation: bloom var(--dur-bloom) var(--ease-out) 1;
}

/* ---------- colophon (Ft4) · the same sentences, split into a log ---------- */
.colophon {
  padding-bottom: var(--space-xl);
}

.colophon-rows {
  margin: 0;
}

.colophon-row {
  position: relative;
  display: grid;
  grid-template-columns: 7rem minmax(0, 1fr);
  gap: var(--space-md);
  border-top: var(--rule-hair) solid var(--hair);
  padding-block: var(--space-xs);
}

.colophon-row::before {
  content: "";
  position: absolute;
  top: 0;
  left: 38.2%;
  width: var(--rule-hair);
  height: var(--tick-h);
  background: var(--hair);
}

.colophon dt {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: var(--track-caps);
  color: var(--muted);
}

.colophon dd {
  margin: 0;
  overflow-wrap: anywhere;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  letter-spacing: var(--track-mono);
  color: var(--muted);
}

.colophon a {
  color: inherit;
}

/* ---------- privacy page ---------- */
.prose-page p,
.prose-page li {
  max-width: var(--measure);
}

.prose-page h1 {
  font-size: var(--text-section);
}

.prose-page .sub-h {
  margin-top: var(--space-lg);
}

/* ---------- wide layout · ≥68rem ----------
   Below this width every grid here collapses to one column. Nothing in this
   block is gated on scripting. */
@media (min-width: 68rem) {
  /* the hero keeps its left edge on the page grid and runs off the right */
  .wrap-bleed-right {
    max-width: none;
    margin-inline: 0;
    padding-left: var(--edge);
    padding-right: 0;
  }

  .hero-grid {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: var(--space-2xl);
    align-items: center;
  }

  .hero h1 {
    max-width: 16ch;
  }

  /* the window is oversized and cut by the root's overflow clip — no 100vw anywhere,
     so a visible scrollbar cannot open a horizontal one */
  .hero-plate .shot-mat {
    width: 114%;
    max-width: none;
  }

  /* Tightens the gap to the deep band so the plate sits low against the chapter break.
     It does NOT overlap the band: the copy column is the taller of the two, so with
     align-items:center the plate never reaches the hero's bottom edge. The z-index on
     .hero / .band-stage stays anyway, so the plate wins if a future crop ever does. */
  .hero-plate {
    margin-bottom: calc(-1 * var(--space-xl));
  }

  /* ---- workflow: four stages as a 2x2 grid. The scroll-driven sticky rig this
     replaces cost ~270vh for four short steps, and hid three of the four
     screenshots until an observer ran. Plain CSS, no JS precondition. ---- */
  #workflow .stages {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--space-xl);
    row-gap: var(--space-md);
    margin-bottom: var(--space-md);
  }

  #workflow .stage {
    display: grid;
    grid-template-rows: auto minmax(0, 1fr);
    row-gap: var(--space-lg);
  }

  #workflow .stage-fig {
    align-self: end;
    width: 100%;
    margin: 0;
  }

  #workflow .stage-fig--generate {
    align-self: start;
  }

  /* A paired rule makes the second row a deliberate new beat. Its padding also
     balances the two content-driven row tracks without declaring either height. */
  #workflow .stage:nth-child(n + 3) {
    border-top: var(--rule-hair) solid var(--hair);
    padding-top: var(--space-lg);
  }

  #workflow .section-close {
    max-width: none;
    margin-top: 0;
    border-top: var(--rule-hair) solid var(--hair);
    padding-top: var(--space-md);
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--space-xl);
  }

  #workflow .section-close-links {
    grid-column: 2;
  }
  /* ---- coverage: three asymmetric columns, the widest carrying the live tier ---- */
  .tiers {
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-xl);
  }

  .tier-list {
    grid-template-columns: minmax(0, 1fr);
  }

  /* ---- security: the allowlist is the centrepiece; the prose steps back ---- */
  .vault {
    grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
    grid-template-areas:
      "seal  allow"
      "pin   pin"
      "prose prose";
    column-gap: var(--space-2xl);
    row-gap: var(--space-xl);
  }

  .vault-seal {
    grid-area: seal;
  }

  .allowlist {
    grid-area: allow;
  }

  .pin-note {
    grid-area: pin;
  }

  /* The prose spans both columns, so a single reading measure left ~700px of the band
     empty beside 718px of text — a column stranded in a field. Two columns echo the
     allowlist's rhythm directly above and halve the height; break-inside keeps each
     paragraph whole, so the four split 2/2 instead of tearing mid-sentence. */
  .vault-prose {
    grid-area: prose;
    max-width: none;
    columns: 2;
    column-gap: var(--space-2xl);
  }

  .vault-prose p {
    break-inside: avoid;
    max-width: var(--measure-tight);
  }

  /* ---- pricing: the panel sits off-centre in a wide field, registration-marked ---- */
  .pricing-field {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    column-gap: var(--space-2xl);
    align-items: start;
  }

  .pricing-head {
    grid-column: 1;
    grid-row: 1;
  }

  /* off-centre in a wide empty field, registered by crop marks rather than by a
     free-floating bracket — a shape hanging in the void is decoration, and the
     registration corners already are the page's language for "this is a specimen" */
  .panel {
    grid-column: 2;
    grid-row: 1;
    justify-self: start;
    margin-left: var(--space-2xl);
  }
}

/* ---------- responsive ---------- */
@media (max-width: 67.999rem) {
  /* .hero-plate .shot-mat used to be capped at 44rem here. That left 203px of the hero
     column unused at ~1009px and, more to the point, held the app's own 14px UI text
     down to 8.3px. Unlike the stage cap below it carried no rationale, so it is gone
     and the hero plate takes its column; the stage cap is a different argument. */
  .stage-copy {
    margin-bottom: var(--space-md);
  }

  .stage {
    border-top: var(--rule-stage) solid currentColor;
    padding-block: var(--space-lg);
  }

  /* Uncapped, the plate takes the whole column: at ~1000px that is a 900px-wide crop
     ~500px tall standing beside copy that wraps at 52ch, and four of them ran the
     section past 4000px. The cap keeps the plate in relation to its own text column.
     The hero plate is deliberately NOT capped here: it is one plate, not four, and it
     is the page's first proof of the product. */
  .stage-fig .shot-mat {
    max-width: 34rem;
  }

  .stage-num {
    font-size: var(--text-3xl);
  }

  /* The off-centre panel is a >=68rem composition - it needs the 7fr field beside the
     5fr headline to read as placement. Below that the panel sits under a full-width
     heading, where a 28rem cap just stranded it: 448px inside a 676px column at 768. */
  .panel {
    max-width: none;
  }
}

@media (max-width: 60rem) {
  .allowlist {
    gap: var(--space-md);
  }
}

@media (max-width: 40rem) {
  .mast-line {
    display: none;
  }

  .mast-name {
    margin-block: var(--space-sm);
  }

  .mast-name a {
    font-size: var(--text-xl);
  }

  /* with JS the nav row carries the wordmark; the big name row would duplicate it */
  html.js .mast-name {
    display: none;
  }

  /* this shorthand sits after the chapter-seam rules and would outrank them by source
     order, so the one that has to survive on phones — a chapter continued across two
     <section>s shows no padding at the join — is re-asserted here. .band-deep-open
     deliberately keeps the phone value instead of its --space-3xl opening. */
  .band {
    padding-block: var(--space-lg);
  }

  .band-sheet-close,
  .band-deep-close {
    padding-top: 0;
  }

  .stage-title {
    font-size: var(--text-lg);
  }

  .allowlist {
    grid-template-columns: minmax(0, 1fr);
  }

  /* column-gap, not the `gap` shorthand: the shorthand set one axis when .seal-pair
     was a flex row, but it resets both on a grid and would take the 8px between each
     mark and its label out to 16px. */
  .seal-pair {
    column-gap: var(--space-sm);
  }

  .hero-ctas .btn-primary {
    width: 100%;
    text-align: center;
  }

  .panel {
    padding: var(--space-md);
  }

  /* "per seat / month" was breaking into two fragments, leaving MONTH alone at the
     left margin under the price. Its own line keeps it one phrase. Not `nowrap`:
     the unit is 226px and the panel's inner width falls below that under ~300px. */
  .price-unit {
    display: block;
    line-height: 1.2;
  }

  .colophon-row {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3xs);
  }

  /* spec sheet restacks — no horizontal scroll on phones */
  .spec-sheet {
    min-width: 0;
  }

  .spec-sheet tr {
    display: block;
    border-top: var(--rule-hair) solid var(--hair);
    padding-block: var(--space-sm);
  }

  .spec-sheet th,
  .spec-sheet td {
    display: block;
    border: 0;
    padding: 0;
    width: auto;
    white-space: normal;
  }

  .spec-sheet td {
    margin-top: var(--space-3xs);
  }

  /* Restacked, an /assessments/ row is three unlabelled lines and "what writes today"
     reads exactly like "not covered" - a claims problem, not a layout one. The <thead>
     is clipped rather than display:none so the column-header association survives, and
     the visible labels are aria-hidden so nothing is announced twice. */
  .spec-sheet-index thead {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  .spec-sheet-index .cell-label {
    display: block;
    font: 600 var(--text-xs) var(--font-body);
    text-transform: uppercase;
    letter-spacing: var(--track-caps);
    color: var(--muted);
    margin-bottom: var(--space-3xs);
  }

  .spec-sheet-index td {
    margin-top: var(--space-2xs);
  }

  /* nav collapses behind the Menu disclosure only when JS is present */
  html.js .mast-menu {
    display: inline-block;
    margin-left: auto;
  }

  html.js .mast-nav ul {
    display: none;
  }

  html.js .mast-nav.is-open ul {
    display: block;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--color-paper);
    border-bottom: var(--rule-hair) solid var(--color-hairline);
    padding: var(--space-2xs) var(--page-gutter) var(--space-sm);
    max-height: calc(100vh - var(--nav-h));
    max-height: calc(100dvh - var(--nav-h));
    overflow-y: auto;
  }

  html.js .mast-nav.is-open ul a {
    display: block;
    padding: var(--space-xs) 0;
    min-height: 44px;
  }

  /* the demo CTA stays a distinct pill inside the open menu, never a plain row */
  html.js .mast-nav.is-open .nav-cta a {
    display: inline-block;
    border: var(--rule-hair) solid var(--color-accent);
    border-radius: var(--radius-md);
    padding: var(--space-2xs) var(--space-sm);
    margin-block: var(--space-2xs);
  }

  html.js .mast-nav {
    justify-content: flex-start;
  }

  html.js .mast-nav .mast-mini {
    display: inline-flex;
    margin-right: 0;
  }
}

/* ---------- reduced motion: cuts, not glides ---------- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
  }

  html.io .cell-rule[data-draw]::before {
    transform: none;
  }

  html.io .cell-rule[data-draw]::after {
    opacity: 1;
  }

  html.io [data-seal] .seal-check {
    stroke-dashoffset: 0;
  }

  /* the universal squash above cannot reach a scroll() timeline — cut it explicitly */
  @supports (animation-timeline: scroll()) {
    .mast-nav::after {
      animation: none;
      display: none;
    }
  }

  /* nor this pseudo-element */
  #faq details::details-content {
    transition: none;
  }

  .bloom {
    animation: none;
  }
}

/* ---------- touch: taller targets where fingers do the pointing ---------- */
@media (pointer: coarse) {
  .mast-nav ul a {
    padding-block: var(--space-xs);
  }

  /* SC 2.5.8 - the Pages row is a navigation list set inline in 14px mono, giving its
     links 16px targets. It has to be inline-block, not padding on the inline box and
     not an absolutely positioned ::after: both of those leave the line box at 21.7px,
     so on the two-line wrap this row takes at 320px the enlarged targets OVERLAP and a
     tap near the edge of one link activates its neighbour - worse than the 16px it
     replaced. An inline-block's padding grows the line box with it, so the targets
     come out exactly adjacent. Fine pointers keep the colophon's tight log rhythm. */
  .colophon a {
    display: inline-block;
    padding-block: var(--space-2xs);
  }
}

/* ---------- Windows High Contrast ----------
   Forced colors replaces every background, so nothing on this page may depend on a dark
   or tinted band to carry meaning — the bands are atmosphere, the rules and text are the
   information. Decorative geometry is withdrawn rather than left to render as noise. */
@media (forced-colors: active) {
  .btn-primary,
  .mast-menu {
    border: 1px solid ButtonText;
  }

  .mast-nav ul a:is([aria-current="location"], [aria-current="page"]) {
    text-decoration: underline;
  }

  #faq summary::before,
  #faq summary::after {
    background: CanvasText;
  }

  .ghost,
  .shot-mat::before,
  .panel::before {
    display: none;
  }

  .cell-rule::before,
  .cell-rule::after,
  .allowlist-col::before,
  .facts li::before,
  .colophon-row::before,
  .tier-primary .tier-list li::before {
    background: CanvasText;
  }

  .pin-note::before {
    border-color: CanvasText;
  }
}

/* ---------- print ----------
   The bands flatten to paper: a page that printed as three blocks of ink would be
   unusable. */
@media print {
  .band-paper,
  .band-sheet,
  .band-deep {
    background: var(--color-sheet) !important;
    color: var(--color-ink) !important;
    --hair: var(--color-hairline);
    --muted: var(--color-muted);
    --accent-fg: var(--color-accent-text);
    --accent-graphic: var(--color-accent);
    --plate: var(--color-sheet);
    --plate-hair: var(--color-hairline);
    --shadow: none;
  }

  .ghost,
  .mast-nav,
  .skip-link {
    display: none !important;
  }

  .stages {
    display: block !important;
  }

  .stage-fig {
    page-break-inside: avoid;
  }

  html.io .cell-rule[data-draw]::before {
    transform: none !important;
  }

  html.io .cell-rule[data-draw]::after {
    opacity: 1 !important;
  }

  html.io [data-seal] .seal-check {
    stroke-dashoffset: 0 !important;
  }
}
