@import url("/styles/portal_tokens.css?v=9805477672");

/* NF-2057 Part 2 — self-hosted display face (Anton, OFL; licence at
   /fonts/OFL.txt). Single master serves the whole 100-900 weight range so
   the browser never synthesizes a faux bold. swap keeps text visible on the
   system condensed stack until the ~20 KB latin subset arrives. */
@font-face {
  font-family: "Anton";
  src: url("/fonts/anton-latin.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

* { box-sizing: border-box; }

/* ── A CLOSED <details> MUST NOT LAY OUT ITS CONTENT ──────────────────────────
 *
 * 🔴 THE COLLAPSED MOBILE NAV RENDERED ON EVERY PORTAL PAGE. A user agent hides
 *    a closed <details>'s content at USER-AGENT origin, and an author `display`
 *    on a direct child outranks it. `.nav-list { display: flex }` is exactly
 *    that, so at <=1120px the closed drawer's five links were laid out — 56x268
 *    starting at the Menu button and running down over the hero. Measured on
 *    / at 375x812 before this rule: `ul.nav-list` at [17,129] 56x268, its links
 *    overlapping `h1#page-title` by up to 48x44, invisible only because later
 *    content painted on top, and STILL IN THE TAB ORDER — the first of them
 *    sat at index 8 of the page's 37 focusables.
 *
 *    This is a CLASS of bug, not one selector: any element given a `display`
 *    and later placed inside a <details>. Restoring the user agent's behaviour
 *    at author origin closes all of them at once, and nothing legitimately
 *    renders inside a collapsed disclosure. Every rule that opens the drawer is
 *    `[open]`-scoped, so none of them is touched.
 */
details:not([open]) > *:not(summary) { display: none; }
/* ── ELEMENT BASELINE ────────────────────────────────────────────────────────
 *
 * This surface styles most of its headings through element selectors already
 * (`main > section > h2`, `.tour-copy h2`), which is why its sweep came back
 * clean — but "clean because every page happens to use a styled container" is
 * not a base layer. The tenant app had the same shape and shipped seven bare
 * <h3>s at the user agent's 1.17em. Declared here so an unclassed heading on a
 * new portal page lands on the scale instead of on Chrome's defaults.
 *
 * Specificity 0-0-1: every rule already in this file outranks all of it.
 */
h1, h2, h3, h4, h5, h6 {
  margin-block: 0 var(--nf-space-2);
  color: inherit;
}
h1 { font-size: var(--nf-display-2); font-weight: var(--nf-weight-black); line-height: var(--nf-leading-none); }
h2 { font-size: var(--nf-display-3); font-weight: var(--nf-weight-bold); line-height: var(--nf-leading-tight); }
h3 { font-size: var(--nf-display-5); font-weight: var(--nf-weight-bold); line-height: var(--nf-leading-snug); }
h4 { font-size: var(--nf-text-xl); font-weight: var(--nf-weight-semibold); line-height: var(--nf-leading-snug); }
h5 { font-size: var(--nf-text-lg); font-weight: var(--nf-weight-semibold); line-height: var(--nf-leading-snug); }
h6 { font-size: var(--nf-text-md); font-weight: var(--nf-weight-semibold); line-height: var(--nf-leading-snug); }

p { margin-block: 0 var(--nf-space-3); line-height: var(--nf-leading-relaxed); }
ul, ol, dl { margin-block: 0 var(--nf-space-3); line-height: var(--nf-leading-relaxed); }
ul, ol { padding-inline-start: var(--nf-space-4); }
dd { margin-inline-start: var(--nf-space-3); }
hr { margin-block: var(--nf-space-4); border: 0; border-block-start: 1px solid var(--nf-border); }
small { font-size: var(--nf-text-sm); }
code, kbd, samp, pre { font-family: var(--nf-font-mono); font-size: var(--nf-text-sm); }
figure { margin: 0 0 var(--nf-space-3); }
blockquote { margin: 0 0 var(--nf-space-3); padding-inline-start: var(--nf-space-3); border-inline-start: 2px solid var(--nf-border); }
h1:last-child, h2:last-child, h3:last-child, h4:last-child, h5:last-child,
h6:last-child, p:last-child, ul:last-child, ol:last-child, dl:last-child,
figure:last-child, blockquote:last-child { margin-block-end: 0; }

body {
  margin: 0;
  color: var(--nf-fg);
  background: var(--nf-bg);
  font-family: var(--nf-font-body);
  font-size: var(--nf-text-md);
  line-height: var(--nf-leading-normal);
}
a { color: var(--nf-accent); }
/* The two-tone focus indicator, portal-wide. The outline occupies 3-5px out
   from the border box and the 8px shadow spread occupies 0-8px, so the halo
   flanks the ring on both sides. One band always clears 3:1 whatever the control
   sits on; --nf-focus-halo in portal_tokens.css has the measurements.

   🔴 box-shadow replaces rather than accumulates. Nothing focusable on this
   surface currently carries a resting shadow -- checked across portal.css with
   comments stripped -- so nothing is flattened by this. A future focusable that
   does carry one must restate it here alongside the halo. */
a:focus-visible,
button:focus-visible,
summary:focus-visible {
  scroll-margin-block: var(--nf-space-2);
  outline: var(--nf-focus-ring-width) solid var(--nf-focus);
  outline-offset: var(--nf-focus-ring-offset);
  box-shadow: 0 0 0 8px var(--nf-focus-halo);
}
/* NF-782 — 44px is the floor for anything a thumb has to hit.
   Every non-inline interactive control below declares `min-height: 44px`
   explicitly rather than relying on padding plus a 15px/1.55 line box, which
   lands at 39–43px and drifts the moment the type scale changes. */
.skip-link {
  position: absolute;
  left: var(--nf-space-3);
  top: -5rem;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0.75rem 1rem;
  background: var(--nf-surface);
  border: 1px solid var(--nf-control-border);
  border-radius: var(--nf-radius-ctl);
  z-index: var(--nf-layer-skip);
  color: var(--nf-fg);
  text-decoration: none;
  font-weight: var(--nf-weight-semibold);
}
.skip-link:focus { top: var(--nf-space-3); }
.hero,
.hero-inner,
.band-inner,
main {
  width: min(var(--nf-measure), calc(100% - 2rem));
  margin-inline: auto;
}
.site-header {
  width: 100%;
  margin-inline: 0;
  background-color: var(--nf-surface);
  border-block-end: 1px solid var(--nf-border);
}
.site-header__inner {
  width: min(var(--nf-measure), calc(100% - 2rem));
  min-width: 0;
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  /* Compact portal chrome is intentional: controls retain the universal 44px
     floor while the secondary row no longer competes with the global strip. */
  gap: var(--nf-space-2);
  padding-block: var(--nf-space-2);
}
.wordmark {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--nf-font-display);
  /* OWNER 2026-08-02: bigger wordmark, capped at the nav-button height (44px).
     2.1rem renders ~34px of glyph in the display face - clearly larger,
     safely under the cap. */
  /* A compact secondary row needs the mark to read as an identity, not a
     display headline. This is one scale step down while preserving its 44px
     target and the stronger display face. */
  font-size: var(--nf-text-4xl);
  line-height: var(--nf-leading-none);
  font-weight: var(--nf-weight-bold);
  color: var(--nf-fg);
  text-decoration: none;
  letter-spacing: -0.02em;
}
.wordmark span {
  /* The second half of the mark is the house red on every theme (owner,
     2026-09-25: gray is unacceptable; 2026-09-26: red on every theme).
     --nf-brand-mark-text is the brand red, not the accent-text role, so a
     seasonal revision that recolors accent text (Halloween Dark paints
     --nf-red-text orange) cannot move the mark off red. */
  color: var(--nf-brand-mark-text);
}
.nav-primary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--nf-space-2);
}
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border: 1px solid var(--nf-control-border);
  border-radius: var(--nf-radius-ctl);
  background: var(--nf-surface);
  color: var(--nf-fg);
  font: inherit;
  font-size: var(--nf-text-2xl);
  cursor: pointer;
}
/* OWNER 2026-08-02: "the red border around the view toggle looks bad." A
   control that only switches themes should not light up in the brand's loudest
   ink. Quiet feedback: the surface tints, the border stays put. */
.theme-toggle:hover { background: var(--nf-band); }
.nav-list {
  display: flex;
  flex-wrap: wrap;
  /* The primary row keeps its visual hierarchy through weight, not excess
     gaps; a smaller consistent gap prevents an otherwise roomy desktop bar
     from breaking into two tall rows. */
  gap: var(--nf-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}
/* The global strip stays subordinate through its lighter 13px/500 treatment;
   the portal row earns primary status with weight and its full 44px targets,
   not a larger type step that forces the secondary header to wrap. */
.nav-list a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--nf-fg);
  text-decoration: none;
  font-size: var(--nf-text-md);
  font-weight: var(--nf-weight-semibold);
  letter-spacing: -0.01em;
}
.nav-list a:hover { color: var(--nf-accent); }
.nav-list a.nav-demo {
  padding-inline: var(--nf-space-2);
  border: 1px solid var(--nf-control-border);
  border-radius: var(--nf-radius-ctl);
  font-weight: var(--nf-weight-bold);
}
.nav-list a[aria-current="page"] {
  font-weight: var(--nf-weight-bold);
  color: var(--nf-accent);
  text-decoration: underline;
  text-decoration-thickness: 0.15em;
  text-underline-offset: 0.2em;
}
.nav-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0.5rem 0.9rem;
  border-radius: var(--nf-radius-ctl);
  /* --nf-cta-bg, not --nf-accent: the night accent is too light to hold
     white type at AA; the CTA fill token stays dark enough in both themes. */
  background: var(--nf-cta-bg);
  color: var(--nf-cta-fg);
  text-decoration: none;
  font-weight: var(--nf-weight-semibold);
  border: 1px solid var(--nf-cta-edge);
  box-shadow: var(--nf-shadow-press);
}
.nav-cta:hover { filter: brightness(1.05); color: var(--nf-cta-fg); }
.nav-toggle {
  display: none;
  border: 1px solid var(--nf-control-border);
  border-radius: var(--nf-radius-ctl);
  background: var(--nf-surface);
  color: var(--nf-fg);
  /* The padding lives on the SUMMARY, not here. <details> paints the box but
     only <summary> toggles it, so 0.75rem of padding on this element was 12px
     of painted-but-inert button on each side - 40% of a 64px target did
     nothing when tapped. Audited 2026-08-03 on all 11 pages. */
  font: inherit;
}
.nav-toggle summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  min-width: 44px;
  padding: 0 0.75rem;
}
.nav-toggle summary::-webkit-details-marker { display: none; }

/* Keep the public header on the same narrow-control contract as APP, TALK and
   Control Plane: a full 44px pointer/touch target with a quieter 40px visible
   plate. The details element owns the plate so Menu does not retain a second
   full-size border behind its summary. */
@media (max-width: 820px) {
  .site-header :is(.nav-toggle, .theme-toggle) {
    position: relative;
    isolation: isolate;
    inline-size: var(--nf-control-min);
    block-size: var(--nf-control-min);
    background: transparent;
    border: 0;
    box-shadow: none;
  }
  .site-header .nav-toggle > summary {
    inline-size: var(--nf-control-min);
    min-inline-size: var(--nf-control-min);
    block-size: var(--nf-control-min);
    min-block-size: var(--nf-control-min);
    padding: 0;
    border: 0;
    background: transparent;
    box-shadow: none;
  }
  .site-header :is(.nav-toggle, .theme-toggle)::after {
    content: "";
    position: absolute;
    inset: 2px;
    z-index: -1;
    border: 1px solid var(--nf-control-border);
    border-radius: var(--nf-radius-ctl);
    background: var(--nf-surface);
    pointer-events: none;
  }
  .site-header :is(.nav-toggle, .theme-toggle):hover::after {
    background: var(--nf-band);
  }
  .site-header :is(.nav-toggle, .theme-toggle):active::after {
    background: var(--nf-band);
  }
}

/* The global strip stays above this header on every surface. Seasonal art is
   applied to `.site-header`, which deliberately spans the viewport; its
   navigation remains in the measured inner row. That keeps the artwork from
   looking like a cropped rectangle while preserving the established 44px
   control geometry. */
.site-header .nav-toggle-icon {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
}
.site-header .nav-toggle-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.site-header .nav-subscription svg { display: none; }
.site-header .nav-primary > a[data-portal-account-state="signed-in"] {
  display: none;
}
@media (max-width: 600px) {
  .site-header .nav-primary > .nav-subscription {
    width: 44px;
    height: 44px;
    padding: 0;
    position: relative;
    isolation: isolate;
    background: transparent;
    border: 0;
    box-shadow: none;
  }
  .site-header .nav-primary > .nav-subscription::after {
    content: "";
    position: absolute;
    inset: 2px;
    z-index: -1;
    border: 1px solid var(--nf-fg);
    border-radius: var(--nf-radius-ctl);
    background: transparent;
    pointer-events: none;
  }
  .site-header .nav-primary > .nav-subscription:hover::after {
    background: var(--nf-band);
  }
  .site-header .nav-subscription span { display: none; }
  .site-header .nav-subscription svg {
    display: block;
    width: 20px;
    height: 20px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
  }
}

/* Hero — the press sheet (NF-2057 Part 2, Variant B "Four-Colour").
   Home is the only route with .hero-band, so the loud treatment scopes
   itself: halftone field behind the masthead, crop marks on the trim. */
.hero-band {
  position: relative;
  /* The home hero is a permanent dark artwork plate. Its ink must remain
     readable when the surrounding page switches to a light palette. */
  --nf-hero-ink: var(--nf-graphic-ink);
  --nf-hero-ink-soft: var(--nf-graphic-ink-muted);
  --nf-hero-plate: var(--nf-graphic-surface);
  background: var(--nf-hero-band-bg);
  border-bottom: 1px solid var(--nf-border);
  padding-block: clamp(2.5rem, 7vw, 5rem);
}
/* Halftone field behind the hero: the four-colour tell. */
.hero-band::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    radial-gradient(var(--nf-hero-dot-1) 1px, transparent 1.5px),
    radial-gradient(var(--nf-hero-dot-2) 1px, transparent 1.5px);
  background-size: 9px 9px, 9px 9px;
  background-position: 0 0, 4px 5px;
  -webkit-mask-image: radial-gradient(80% 90% at 78% 20%, #000 0%, transparent 68%);
  mask-image: radial-gradient(80% 90% at 78% 20%, #000 0%, transparent 68%);
}
/* Crop marks on the hero corners: trimmed like a printed sheet. */
.hero-band::after {
  content: "";
  position: absolute;
  inset: 6px;
  pointer-events: none;
  background:
    linear-gradient(var(--nf-hero-ink-soft), var(--nf-hero-ink-soft)) top left / 16px 1px no-repeat,
    linear-gradient(var(--nf-hero-ink-soft), var(--nf-hero-ink-soft)) top left / 1px 16px no-repeat,
    linear-gradient(var(--nf-hero-ink-soft), var(--nf-hero-ink-soft)) top right / 16px 1px no-repeat,
    linear-gradient(var(--nf-hero-ink-soft), var(--nf-hero-ink-soft)) top right / 1px 16px no-repeat,
    linear-gradient(var(--nf-hero-ink-soft), var(--nf-hero-ink-soft)) bottom left / 16px 1px no-repeat,
    linear-gradient(var(--nf-hero-ink-soft), var(--nf-hero-ink-soft)) bottom left / 1px 16px no-repeat,
    linear-gradient(var(--nf-hero-ink-soft), var(--nf-hero-ink-soft)) bottom right / 16px 1px no-repeat,
    linear-gradient(var(--nf-hero-ink-soft), var(--nf-hero-ink-soft)) bottom right / 1px 16px no-repeat;
  opacity: 0.7;
}
.hero-band > .hero { position: relative; z-index: var(--nf-layer-raised); }
.hero {
  display: grid;
  gap: var(--nf-space-5);
  align-items: center;
  padding-block: 0;
}
@media (min-width: 900px) {
  .hero { grid-template-columns: 1.05fr 0.95fr; }
}
.hero-copy h1 {
  font-family: var(--nf-font-display);
  /* OWNER 2026-08-02: "SLIGHTLY reduce" - one step down, ~10%. That step is
     now display-1 (48->64px), which also absorbs the two phone overrides
     this rule used to need. Measured before: 46.4px at 375, 66.4px at 1440. */
  font-size: var(--nf-display-1);
  font-weight: var(--nf-weight-black);
  letter-spacing: 0.005em;
  line-height: var(--nf-leading-none);
  text-transform: none;
  text-wrap: balance;
  margin: 0 0 var(--nf-space-3);
}
/* Display headings: plain solid ink. The in-glyph halftone (candidate 6) and
   the plate-offset text-shadow before it were both withdrawn by the owner on
   2026-08-02: "for the heading texts just make them all plain text." */
.nf-heading-halftone,
.hero-copy h1,
.pain-inner h2,
.story-band h2,
.tour-copy h2,
.product-preview h2,
.page-intro h1,
.module-card h3,
.workflow h2,
.module-grid h2,
.cta-heading,
.prose h2 {
  /* OWNER VERDICT 2026-08-02: "for the heading texts just make them all
     plain text. The dots everywhere are too fucking much." Candidate 6 is
     withdrawn. Solid ink, no clip, no texture, no offset.
     OWNER 2026-09-11: the display face carries its own ink token so night
     headlines are the logo ivory while the body stays the ladder text. */
  color: var(--nf-display-ink);
  -webkit-text-fill-color: currentColor;
  background-image: none;
  text-shadow: none;
}
/* Forced-colours users get the system colour back; a clipped background has no
   meaning there and would render the heading invisible. */
@media (forced-colors: active) {
  .nf-heading-halftone,
  .hero-copy h1,
  .pain-inner h2,
  .story-band h2,
  .tour-copy h2,
  .product-preview h2,
  .page-intro h1 {
    background-image: none;
    color: CanvasText;
    -webkit-text-fill-color: CanvasText;
  }
}
.hero-copy .lede {
  margin: 0 0 var(--nf-space-4);
  color: var(--nf-muted);
  font-size: var(--nf-text-xl);
  max-width: 36rem;
}
/* OWNER 2026-09-11 "navy plate": in day the hero band is a navy plate, so its
   copy takes the plate inks (ivory display, cool-grey lede, red eyebrow). At
   night these resolve to the ordinary ladder values, so one rule set serves
   both themes. */
.hero-band .hero-copy h1 { color: var(--nf-hero-ink); }
.hero-band .hero-copy .lede { color: var(--nf-hero-ink-soft); }
.hero-band .eyebrow { color: var(--nf-plate-red-text); }
.hero-band .eyebrow::before { background: var(--nf-plate-red-text); }
/* Outranks `.hero-actions .nav-cta-secondary` below: on the navy hero plate
   the outline CTA takes the plate ink, not the page ink. */
.hero-band .hero-actions .nav-cta-secondary {
  border-color: var(--nf-hero-ink);
  color: var(--nf-hero-ink);
}
.hero-band .hero-actions .nav-cta-secondary:hover {
  background: color-mix(in srgb, var(--nf-hero-ink) 12%, transparent);
  color: var(--nf-hero-ink);
}
.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--nf-space-3);
}
.hero-actions .nav-cta-secondary,
.page-cta .nav-cta-secondary,
.nav-cta-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0.55rem 1.1rem;
  border-radius: var(--nf-radius-ctl);
  /* Press-craft secondary: inked outline on the stock, no filled pill. */
  border: 1px solid var(--nf-fg);
  color: var(--nf-fg);
  text-decoration: none;
  font-weight: var(--nf-weight-semibold);
  background: transparent;
}
.hero-actions .nav-cta-secondary:hover,
.page-cta .nav-cta-secondary:hover,
.nav-cta-secondary:hover {
  background: var(--nf-surface);
}
.contact-form .nav-cta-secondary[hidden] {
  display: none;
}
/* NF-778: the box must be the shape of the art it holds. `object-fit: contain`
   silently letterboxes any mismatch, so a box ratio that does not track the
   shipped viewBox shrinks the composition instead of showing it. The width cap
   pairs with max-height (42.66rem / (1280/900) ≈ 30rem) so the clamp can never
   re-square the box on mid-width single-column layouts. */
/* ---------------------------------------------------------------------------
   THE PLATE-OFFSET CONTRACT (NF-2057)

   `box-shadow` traces the element's OWN `border-radius`. An element that takes
   the offset without declaring a radius therefore renders SQUARE shadow corners
   — even when the artwork inside it is rounded. That is not a hypothetical:
   `.page-index .tour-visual` had its radius deliberately removed (a CSS border
   was tracing straight edges around already-rounded SVG art) but kept the
   shadow, so every product-tour figure stamped square corners while the hero
   above it stayed round. The two properties are ONE decision, so they are
   declared in ONE place and can no longer drift apart.

   Take the offset by adding `.nf-plate` — never by writing the literal again.
   A caller may retune the corner via `--nf-plate-radius`, but cannot omit it. */
.nf-plate {
  border-radius: var(--nf-plate-radius, var(--nf-radius-panel));
  box-shadow: var(--nf-shadow-plate);
}
/* Existing consumers, adopted without touching their markup. */
.hero-visual,
.proof-list li,
.page-index .tour-visual {
  border-radius: var(--nf-plate-radius, var(--nf-radius-panel));
  box-shadow: var(--nf-shadow-plate);
}
.hero-visual {
  width: min(100%, 42.66rem);
  margin-inline: auto;
  height: auto;
  aspect-ratio: 1280 / 900;
  max-height: 30rem;
  object-fit: contain;
  object-position: center;
  background: var(--nf-surface);
  border: 1px solid var(--nf-stroke);
  display: block;
}

/* Proof + story + product tour */
.proof-strip {
  padding-block: var(--nf-space-4);
}
.pain-band {
  padding-block: var(--nf-space-5);
}
.pain-inner {
  width: min(var(--nf-measure), calc(100% - 2rem));
  margin-inline: auto;
  max-width: 44rem;
}
.pain-inner h2 {
  font-family: var(--nf-font-display);
  font-weight: var(--nf-weight-bold);
  letter-spacing: -0.02em;
  line-height: var(--nf-leading-tight);
  margin: 0 0 var(--nf-space-3);
  font-size: var(--nf-display-3);
}
.pain-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem;
  margin: var(--nf-space-4) 0;
  padding: 0;
  list-style: none;
}
.pain-chips li {
  padding: 0.45rem 0.75rem;
  border: 2px solid var(--nf-stroke);
  border-radius: var(--nf-radius-ctl);
  background: transparent;
  font-family: var(--nf-font-mono);
  font-size: var(--nf-text-sm);
  font-weight: var(--nf-weight-semibold);
  letter-spacing: 0.06em;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
/* OWNER 2026-08-03: the little red arrow floated on its own in the left column,
   pointing at nothing in particular, and the resolve box was the one container
   on the page wearing a 6px accent edge and a control-sized corner. Both were
   special cases. The arrow is gone and the box takes the same card contract as
   every other container here, so it reads as part of the band. */
.pain-resolve {
  margin: var(--nf-space-4) 0 0;
  padding: var(--nf-space-4);
  border: var(--nf-card-stroke) solid var(--nf-stroke);
  border-radius: var(--nf-radius-card);
  background: var(--nf-card);
  box-shadow: var(--nf-shadow-panel);
  color: var(--nf-fg);
  font-weight: var(--nf-weight-medium);
}
.hero-stack { position: relative; }
/* Halftone imagery treatment: dots over the plate, faded on a diagonal. */
.hero-stack::after,
.tour-figure::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: var(--nf-radius-panel);
  background-image: radial-gradient(var(--nf-halftone-dot) 1px, transparent 1.4px);
  background-size: 7px 7px;
  -webkit-mask-image: linear-gradient(160deg, #000 0%, transparent 55%);
  mask-image: linear-gradient(160deg, #000 0%, transparent 55%);
}
/* NF-782: <picture> is inline by default, so a grid item or figure child would
   shrink-wrap and leave the art-directed source at its intrinsic width. Every
   art-directed image on the portal is a block that fills its column. */
.hero-stack picture,
.tour-row picture,
.commerce-story picture,
.diagram picture { display: block; width: 100%; }
/* NF-833: art-direction lives in <picture media=…>, not display:none siblings. */
.proof-list {
  display: grid;
  gap: var(--nf-space-3);
  margin: 0 auto;
  padding: 0;
  list-style: none;
  width: min(var(--nf-measure), calc(100% - 2rem));
}
@media (min-width: 900px) {
  .proof-list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.proof-list li {
  margin: 0;
  padding: var(--nf-space-3);
  background: var(--nf-card);
  border: 1px solid var(--nf-stroke);
  /* Cards keep the card corner; the offset itself comes from the contract
     block above, so the shadow follows this radius instead of squaring off. */
  --nf-plate-radius: var(--nf-radius-card);
  color: var(--nf-ink-soft);
  font-size: var(--nf-text-lg);
}
.proof-list strong {
  display: block;
  margin-bottom: 0.35rem;
  font-family: var(--nf-font-display);
  font-weight: var(--nf-weight-black);
  font-size: var(--nf-text-lg);
  letter-spacing: 0.01em;
  color: var(--nf-display-ink);
}
.promo-preview {
  width: min(var(--nf-measure), calc(100% - 2rem));
  margin-inline: auto;
  padding-block: var(--nf-space-5);
  display: grid;
  gap: var(--nf-space-4);
  align-items: center;
}
.promo-preview h2 {
  font-family: var(--nf-font-display);
  font-size: var(--nf-display-3);
  line-height: var(--nf-leading-tight);
}
.promo-preview__copy { max-width: 37rem; }
.promo-preview__media { min-width: 0; }
.promo-preview video {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #11141c;
  border: 2px solid var(--nf-stroke);
  border-radius: var(--nf-radius-card);
  object-fit: contain;
}
.promo-preview__summary {
  margin: var(--nf-space-2) 0 0;
  color: var(--nf-ink-soft);
  font-size: var(--nf-text-sm);
}
@media (min-width: 900px) {
  .promo-preview { grid-template-columns: minmax(0, 0.78fr) minmax(0, 1.22fr); }
}
@media (max-width: 680px) {
  .promo-preview video {
    aspect-ratio: 9 / 16;
    max-height: 72vh;
  }
}
.story-band {
  width: min(var(--nf-measure), calc(100% - 2rem));
  margin-inline: auto;
  padding-block: var(--nf-space-5);
  max-width: 42rem;
}
.story-band h2 {
  font-family: var(--nf-font-display);
  font-weight: var(--nf-weight-bold);
  letter-spacing: -0.02em;
  line-height: var(--nf-leading-tight);
  margin: 0 0 var(--nf-space-3);
  font-size: var(--nf-display-3);
}
.tour-row {
  display: grid;
  gap: var(--nf-space-4);
  align-items: center;
  width: min(var(--nf-measure), calc(100% - 2rem));
  margin-inline: auto;
  padding-block: var(--nf-space-5);
}
/* NF-779: the flip/order rules must name the direct grid child. The visual now
   sits inside <picture class="tour-figure">, so ordering .tour-visual would be a
   no-op and every band would silently collapse to copy-left/art-right. */
@media (min-width: 900px) {
  .tour-row { grid-template-columns: 0.95fr 1.05fr; }
  /* Varied scale — the lead band gives the product more room than the rest so
     the tour does not read as three identical rectangles. */
  .tour-row-lead { grid-template-columns: 0.8fr 1.2fr; }
  .tour-row-flip .tour-copy { order: 2; }
  .tour-row-flip .tour-figure { order: 1; }
}
.tour-figure {
  position: relative;
  display: block;
  width: 100%;
}
.tour-copy h2 {
  font-family: var(--nf-font-display);
  font-weight: var(--nf-weight-bold);
  letter-spacing: -0.02em;
  line-height: var(--nf-leading-tight);
  margin: 0.35rem 0 var(--nf-space-3);
  font-size: var(--nf-display-4);
}
.tour-copy p { color: var(--nf-muted); margin: 0 0 var(--nf-space-3); }
.text-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-weight: var(--nf-weight-semibold);
  color: var(--nf-accent);
  text-decoration: none;
}
.text-link:hover { text-decoration: underline; }
.tour-visual {
  width: 100%;
  height: auto;
  /* The product SVGs draw their own rounded card, border and shadow. Adding a
     CSS border here traced a straight-edged rectangle around already-rounded
     art — the art's corners tucked inside the frame's square ones. The artwork
     is self-framing, so no CSS border/radius/fill. The plate-offset shadow
     below is kept: it reads as ink on the page, not as a frame. (NF-2057.) */
  display: block;
}
/* Home carries the loud plate offsets; other pages keep the quiet press shadow.
   The radius comes from the contract block and is what rounds the offset to
   match the self-framing SVG art. It draws nothing on its own — there is still
   no border, background or fill here — so the square-frame problem the comment
   above describes does not come back. */
.page-cta {
  flex-direction: column;
  align-items: flex-start;
}
.cta-heading {
  font-family: var(--nf-font-display);
  font-weight: var(--nf-weight-bold);
  letter-spacing: -0.02em;
  margin: 0 0 var(--nf-space-2);
  font-size: var(--nf-display-5);
}
.cta-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--nf-space-3);
}

/* Alternating bands */
.band {
  padding-block: var(--nf-space-5);
}
.band-alt {
  background: var(--nf-band);
  border-block: 1px solid var(--nf-border);
}
.band-accent {
  /* OWNER 2026-08-02, second pass: the 1px pink frame read as washed-out next
     to the ink-bordered cards above it. The CTA band is a CARD like everything
     else on the page - the one container contract: 2px ink border, card
     corner, panel shadow. The red tint stays as its identity; the chrome
     stops being a special case. */
  background: var(--nf-tint-red-soft);
  border: 2px solid var(--nf-stroke);
  border-radius: var(--nf-radius-card);
  box-shadow: var(--nf-shadow-panel);
}
/* The conversion band gets a magenta halftone wash rising on the diagonal. */
.page-cta.band-accent {
  position: relative;
}
.page-cta.band-accent::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: radial-gradient(var(--nf-halftone-m) 1.2px, transparent 1.7px);
  background-size: 8px 8px;
  -webkit-mask-image: linear-gradient(115deg, transparent 45%, #000 100%);
  mask-image: linear-gradient(115deg, transparent 45%, #000 100%);
}
.page-cta.band-accent > * { position: relative; }

.product-preview h2,
.page-intro h1,
.workflow h2,
.module-grid h2,
.prose h2 {
  font-family: var(--nf-font-display);
  font-weight: var(--nf-weight-bold);
  letter-spacing: -0.02em;
  line-height: var(--nf-leading-tight);
  margin: 0 0 var(--nf-space-3);
}
.shot-frame {
  margin: var(--nf-space-3) 0 0;
  padding: 0;
}
.shot-frame figcaption {
  margin-top: var(--nf-space-2);
  color: var(--nf-muted);
  font-size: var(--nf-text-lg);
}
.product-shot {
  width: 100%;
  height: auto;
  max-height: 32rem;
  object-fit: contain;
  object-position: top center;
  /* The product SVGs draw their own rounded card + shadow. A CSS border here
     traces a straight-edged rectangle around that already-rounded art (the
     art's corners tuck inside the frame's square ones), and object-fit:contain
     leaves letterbox gaps the border rings in a way the fill does not touch.
     The artwork is self-framing — no CSS frame. (NF-2057 follow-up.) */
  display: block;
}
main { min-height: 40vh; padding-block: 0; }
.launch-notice {
  width: 100%;
  border-bottom: 1px solid var(--nf-border);
  /* This temporary beta rail sits directly under the header on the same
     ground, so the two rails read as one. Tint this rail only (the header is
     unchanged): a sunken tone blended from the theme's own surface and ink,
     so every theme, including Halloween Light/Dark, keeps the contrast. */
  background: var(--nf-gutter);
  background: color-mix(in srgb, var(--nf-surface) 88%, var(--nf-fg));
  color: var(--nf-fg);
}
.launch-notice p {
  width: min(var(--nf-measure), calc(100% - 2rem));
  margin: 0 auto;
  padding: var(--nf-space-2) var(--nf-space-4);
  color: inherit;
  font-size: var(--nf-text-sm);
  line-height: var(--nf-leading-normal);
  text-align: center;
}
.launch-notice strong { color: var(--nf-red-text); }
.launch-notice time { font-weight: var(--nf-weight-semibold); }
.hero-availability {
  max-width: 27rem;
  margin-bottom: var(--nf-space-3);
  color: var(--nf-hero-ink);
}
/* #2977 LML S3a — launch-list signup. Both homes are dark plates (the hero
   navy plate and the CTA ink plate), so the form reads one local ink that each
   plate sets. It ships `hidden`; launch_signup.js reveals it only while capture
   is open, and `[hidden]` must beat the grid display below. */
.launch-signup {
  --launch-ink: var(--nf-hero-ink);
  --launch-ink-soft: var(--nf-hero-ink-soft);
  display: grid;
  gap: var(--nf-space-2);
  max-width: 30rem;
  margin: 0 0 var(--nf-space-4);
}
.launch-signup[hidden],
[data-launch-entry][hidden] { display: none; }
.page-cta.band-accent .launch-signup {
  --launch-ink: var(--nf-plate-ink);
  --launch-ink-soft: var(--nf-plate-ink-soft);
  margin: var(--nf-space-4) 0 0;
}
.launch-signup-title {
  margin: 0;
  font-size: var(--nf-text-xl);
  line-height: var(--nf-leading-tight);
  color: var(--launch-ink);
}
.launch-signup-label {
  font-size: var(--nf-text-sm);
  font-weight: var(--nf-weight-semibold);
  color: var(--launch-ink);
}
.launch-signup-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--nf-space-2);
}
.launch-signup-input {
  flex: 1 1 14rem;
  min-width: 0;
  min-height: 44px;
  box-sizing: border-box;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--launch-ink);
  border-radius: var(--nf-radius-ctl);
  background: var(--nf-surface);
  color: var(--nf-fg);
  font: inherit;
}
.launch-signup-input:focus-visible {
  outline: var(--nf-focus-ring-width) solid var(--nf-focus);
  outline-offset: var(--nf-focus-ring-offset);
  box-shadow: 0 0 0 5px var(--nf-focus-halo);
}
.launch-signup-input[aria-invalid="true"] {
  border-color: var(--nf-plate-red-text);
  box-shadow: inset 0 0 0 1px var(--nf-plate-red-text);
}
.launch-signup .launch-signup-submit {
  flex: 0 0 auto;
  border-color: var(--launch-ink);
  cursor: pointer;
  font: inherit;
  font-weight: var(--nf-weight-semibold);
}
.launch-signup .launch-signup-submit[disabled] { cursor: progress; opacity: 0.8; }
.launch-signup-note,
.launch-signup-status {
  margin: 0;
  font-size: var(--nf-text-sm);
  color: var(--launch-ink-soft);
}
.launch-signup-status { min-block-size: 2.8em; }
.launch-signup-status:empty { visibility: hidden; }
.launch-signup-status:focus {
  outline: var(--nf-focus-ring-width) solid var(--nf-focus);
  outline-offset: var(--nf-focus-ring-offset);
}
.launch-signup-status[data-tone="ok"] {
  color: var(--launch-ink);
  font-weight: var(--nf-weight-semibold);
}
.launch-signup-status[data-tone="error"] {
  color: var(--nf-plate-red-text);
  font-weight: var(--nf-weight-semibold);
}
/* Honeypot: off-screen for people, still in the form for bots. */
.launch-signup-trap {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  margin: 0;
}
main section.page-intro,
main section.module-grid,
main section.workflow,
main section.prose,
main section.page-cta,
main section.product-preview,
main section.story-band,
main section.tour-row {
  width: min(var(--nf-measure), calc(100% - 2rem));
  margin-inline: auto;
  max-width: min(var(--nf-measure), 100%);
  padding-block: var(--nf-space-5);
}
/* Full-bleed alternating surfaces (keep inner content on the measure) */
main section.band-alt,
main section.band-accent,
main section.proof-strip,
main section.pain-band {
  width: 100%;
  max-width: none;
  margin-inline: 0;
  padding-inline: max(var(--nf-band-inset), calc((100% - min(var(--nf-measure), 100% - 2rem)) / 2));
  box-sizing: border-box;
}
main section.tour-row.band-alt {
  width: 100%;
  max-width: none;
  margin-inline: 0;
  padding-inline: max(var(--nf-band-inset), calc((100% - min(var(--nf-measure), 100% - 2rem)) / 2));
  display: grid;
}
/* OWNER/audit 2026-08-03: this was `margin-inline: auto`, so the intro block
   centred inside the content column while every H2, grid and button below it
   started at the column edge - a 192px indent between a page title and its own
   page, measured on 10 of 11 routes. Cap the measure, keep the left edge. */
main section.page-intro { max-width: 46rem; margin-inline: 0; }
.page-intro .lede,
.section-lede {
  color: var(--nf-muted);
  max-width: 40rem;
}
.module-cards {
  display: grid;
  gap: var(--nf-space-3);
  margin: var(--nf-space-4) 0 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 720px) {
  .module-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1100px) {
  .module-cards.module-cards-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.module-card {
  padding: var(--nf-space-4);
  border: 2.5px solid var(--nf-stroke);
  border-radius: var(--nf-radius-card);
  background: var(--nf-card);
  box-shadow: var(--nf-shadow-panel);
  transition: transform var(--nf-motion) ease, box-shadow var(--nf-motion) ease;
}
.module-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--nf-shadow-lg);
}
.module-card.tint-red { background: var(--nf-tint-red); }
.module-card.tint-cream { background: var(--nf-surface-2); }
.module-icon {
  width: 3rem;
  height: 3rem;
  display: block;
  margin: 0 0 var(--nf-space-2);
}
.module-card h3 {
  font-family: var(--nf-font-display);
  font-size: var(--nf-text-xl);
  font-weight: var(--nf-weight-bold);
  margin: 0.35rem 0 var(--nf-space-2);
}
.module-card p:last-child { margin-bottom: 0; }
/* The card is an <li> with no display of its own, so its slack fell under the
   trailing link: a card whose copy ran one line shorter left that link 23-24px
   above its row-mates. Measured on all four pages that use this component --
   features 23, integrations 23, pricing 24, community 23 -- and every one goes
   to 0 with the trailing element pushed to the end of a column flex.

   Flex does not collapse adjacent margins, and the icon's 8px bottom margin was
   collapsing with the heading's 0.35rem top margin to 8px. Left alone that gap
   would become 13.6px and every icon card would grow 5.6px, so the heading's
   top margin is dropped exactly where an icon precedes it. Card heights are
   then identical to the pixel on all four pages; only the link moves. */
.module-card {
  display: flex;
  flex-direction: column;
}

.module-card > :last-child { margin-block-start: auto; }

.module-card > .module-icon + h3 { margin-block-start: 0; }
/* Readiness badges (Variant B): set like print job tickets — mono, square,
   one ink each. Red plate = proven on the floor, cyan outline = on press,
   grey outline = not yet scheduled. */
.badge {
  display: inline-block;
  margin: 0;
  padding: 0.2rem 0.55rem;
  border-radius: var(--nf-radius-ctl);
  border: 1px solid transparent;
  font-family: var(--nf-font-mono);
  font-size: var(--nf-text-2xs);
  font-weight: var(--nf-weight-semibold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
/* Doubled class (.badge.badge-*): container rules like `.tour-copy p`
   carry (0,1,1) specificity and silently took the badge's ink otherwise. */
.badge.badge-beta {
  /* Tenant-reachable now: the key plate, full ink. */
  background: var(--nf-ink);
  color: var(--nf-cream);
}
.badge.badge-owner-live {
  /* Proven on the Legacy Nexus floor: the magenta plate (per Variant B). */
  background: var(--nf-cta-bg);
  color: var(--nf-cta-fg);
}
.badge.badge-in-progress {
  background: transparent;
  color: var(--nf-plate-c-text);
  border-color: var(--nf-plate-c-text);
}
.badge.badge-planned {
  background: transparent;
  color: var(--nf-muted);
  border-color: var(--nf-border);
}

/* #780 — comic-operations lifecycle rail.
   Long-box rhythm: a spine down the left edge of every step, a halftone wash,
   an issue marker, and floor-language chips (grade, quantity, box location,
   listing state) instead of a flat equal-card grid. */
/* NF-2057 Part 2 (Variant B): the rail is a press sheet — panels sit flat in
   a visible gutter grid instead of floating as six identical cards. The lead
   panel and the List panel take wider columns so the run has rhythm. */
.flow-rail {
  display: grid;
  gap: var(--nf-space-1);
  margin: var(--nf-space-4) 0 0;
  padding: var(--nf-space-1);
  list-style: none;
  background: var(--nf-gutter);
  border: 1px solid var(--nf-border);
}
@media (min-width: 640px) {
  .flow-rail { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1000px) {
  .flow-rail { grid-template-columns: 1.35fr 1fr 1fr; }
  .flow-step:nth-child(4) { grid-column: 1 / span 2; }
}
.flow-step {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--nf-space-2);
  margin: 0;
  padding: var(--nf-space-3);
  background: var(--nf-step-bg);
}
.flow-step .badge { align-self: flex-start; }
.issue-marker {
  margin: 0;
  font-family: var(--nf-font-mono);
  font-size: var(--nf-text-2xs);
  font-weight: var(--nf-weight-medium);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--nf-muted);
}
.issue-marker span {
  display: inline-block;
  margin-left: 0.35rem;
  color: var(--nf-accent);
  font-size: var(--nf-text-md);
  font-weight: var(--nf-weight-bold);
  letter-spacing: 0.06em;
}
.flow-step h3 {
  margin: 0;
  font-family: var(--nf-font-display);
  font-size: var(--nf-text-xl);
  font-weight: var(--nf-weight-bold);
  letter-spacing: 0.02em;
  text-transform: none;
}
.flow-copy {
  margin: 0;
  color: var(--nf-ink-soft);
  font-size: var(--nf-text-lg);
}
.flow-chips {
  display: flex;
  flex-wrap: wrap;
  /* Rounded chips never touch: one 8px step between them. */
  gap: var(--nf-space-2);
  margin: auto 0 0;
  padding: var(--nf-space-2) 0 0;
  list-style: none;
}
.flow-chips li {
  margin: 0;
  /* #6016 — was 0.15rem (2.4px) vertical: on no scale, and the tightest
     vertical padding anywhere on the portal. space-1 is the floor. */
  padding: var(--nf-space-1) var(--nf-space-2);
  border: 2px solid var(--nf-stroke);
  border-radius: var(--nf-radius-ctl);
  background: transparent;
  color: var(--nf-fg);
  font-family: var(--nf-font-mono);
  font-size: var(--nf-text-xs);
  font-weight: var(--nf-weight-semibold);
  letter-spacing: 0.06em;
  max-width: 100%;
}
.readiness-legend {
  display: grid;
  gap: var(--nf-space-2);
  margin: var(--nf-space-3) 0 0;
  padding: 0;
  list-style: none;
  color: var(--nf-muted);
  font-size: var(--nf-text-md);
}
@media (min-width: 900px) {
  .readiness-legend { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.readiness-legend li { margin: 0; }
.readiness-legend .badge { margin-right: 0.4rem; vertical-align: baseline; }
.proof-list .badge { margin-bottom: var(--nf-space-2); }
.diagram {
  margin: var(--nf-space-4) 0 0;
  padding: 0;
}
.diagram img {
  width: 100%;
  height: auto;
  border: 1px solid var(--nf-stroke);
  border-radius: var(--nf-radius-panel);
  box-shadow: var(--nf-shadow-panel);
  background: var(--nf-card);
  display: block;
}
.diagram figcaption {
  margin-top: var(--nf-space-2);
  color: var(--nf-muted);
  font-size: var(--nf-text-lg);
}
/* #827 — compact product-led commerce stories (shared tokens only; no forked theme) */
.commerce-story {
  margin: var(--nf-space-4) 0 0;
  padding: 0;
}
.commerce-story img {
  width: 100%;
  height: auto;
  max-height: 16rem;
  object-fit: contain;
  object-position: left center;
  border: 1px solid var(--nf-stroke);
  border-radius: var(--nf-radius-panel);
  box-shadow: var(--nf-shadow-panel);
  background: var(--nf-card);
  display: block;
}
.commerce-story figcaption {
  margin-top: var(--nf-space-2);
  color: var(--nf-muted);
  font-size: var(--nf-text-lg);
}
.commerce-outcomes {
  margin: var(--nf-space-4) 0 0;
  padding-left: 1.25rem;
  color: var(--nf-fg);
}
.commerce-outcomes li {
  margin-bottom: var(--nf-space-2);
}
.commerce-outcomes strong {
  color: var(--nf-accent);
}
.commerce-mini-grid {
  display: grid;
  gap: var(--nf-space-3);
  margin-top: var(--nf-space-4);
}
@media (min-width: 768px) {
  .commerce-mini-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.commerce-mini {
  margin: 0;
  padding: var(--nf-space-3);
  background: var(--nf-card);
  border: 2.5px solid var(--nf-stroke);
  border-radius: var(--nf-radius-card);
  box-shadow: var(--nf-shadow-panel);
}
.commerce-mini h3 {
  margin: 0 0 var(--nf-space-2);
  font-size: var(--nf-text-lg);
  font-family: var(--nf-font-display);
}
.commerce-mini p {
  margin: 0 0 var(--nf-space-2);
  color: var(--nf-muted);
  font-size: var(--nf-text-md);
}
.commerce-mini-outcome {
  margin: 0 !important;
  color: var(--nf-fg) !important;
  font-weight: var(--nf-weight-semibold);
  font-size: var(--nf-text-md) !important;
}
.page-cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--nf-space-3);
  padding-block: var(--nf-space-5);
  align-items: flex-start;
}
.notice {
  max-width: 40rem;
  margin: var(--nf-space-4) 0 0;
  padding: var(--nf-space-3) var(--nf-space-4);
  border: 2.5px solid var(--nf-stroke);
  border-left: 6px solid var(--nf-accent);
  border-radius: var(--nf-radius-ctl);
  background: var(--nf-surface);
  box-shadow: var(--nf-shadow-panel);
}
.eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-family: var(--nf-font-mono);
  font-size: var(--nf-text-xs);
  font-weight: var(--nf-weight-semibold);
  color: var(--nf-red-text);
  margin: 0 0 var(--nf-space-2);
}
/* Registration dash: the red plate leads every eyebrow (owner 2026-09-11). */
.eyebrow::before {
  content: "";
  display: inline-block;
  width: 22px;
  height: 2px;
  background: var(--nf-red-text);
  vertical-align: middle;
  margin-right: 0.5rem;
}
/* Press-sheet seams: a registration rule between stacked sections on the
   measure. Bands carry their own border-block, so both neighbours must be
   band-free or the seam doubles into a 2px line. */
main > section:not(.band-alt):not(.band-accent) + section:not(.band-alt):not(.band-accent) {
  border-top: 1px solid var(--nf-border);
}
/* Preserve deliberately authored Title Case while retaining the display face. */
.page-index main h2,
.page-index .cta-heading {
  text-transform: none;
}
/* #6016 — the home-only heading ramp is gone: display-3 is the section-head
   step everywhere, so this rule said nothing the base rule did not. It was
   the third of three different curves for one semantic level. */
/* Anton is condensed by design; the old negative tracking (tuned for the
   system stack) makes its glyphs collide. One late override wins them all. */
.wordmark,
.hero-copy h1,
.pain-inner h2,
.story-band h2,
.tour-copy h2,
.cta-heading,
.product-preview h2,
.page-intro h1,
.workflow h2,
.module-grid h2,
.prose h2 {
  letter-spacing: 0.01em;
}

/* The footer owns the full viewport edge; only its navigational content keeps
   the page measure. It cannot read as a second floating panel below main. */
.site-footer {
  width: 100%;
  border-top: 1px solid var(--nf-border);
  padding-block: var(--nf-space-5);
  margin: var(--nf-space-4) 0 0;
  background: var(--nf-footer-bg);
  color: var(--nf-footer-ink);
  border-radius: 0;
}
.site-footer nav {
  width: min(var(--nf-measure), calc(100% - 2rem));
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  gap: var(--nf-space-3);
  align-items: center;
}
#portal-shortcut-help-slot {
  display: flex;
  margin-inline-start: auto;
}
.site-footer a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Height was 44px but width was never constrained, so short labels ("FAQ"
     28.5px, "Terms" 42.2px) failed WCAG 2.5.5 on the narrow axis. */
  min-height: 44px;
  min-width: 44px;
  color: var(--nf-footer-muted);
  text-decoration: none;
}
.site-footer a:hover { color: var(--nf-footer-link); }
.site-footer a[aria-current="page"] {
  font-weight: var(--nf-weight-bold);
  color: var(--nf-footer-link);
  text-decoration: underline;
  text-decoration-thickness: 0.15em;
  text-underline-offset: 0.2em;
}

@media (max-width: 720px) {
  #portal-shortcut-help-slot {
    flex-basis: 100%;
    margin-inline-start: 0;
  }
  .nav-toggle { display: block; }
  .nav-primary-desktop { display: none; }
  .nav-toggle[open] .nav-list {
    display: flex;
    flex-direction: column;
    margin-top: var(--nf-space-2);
  }
}

/* OP03: seasonal scenery belongs in the gutters while every Home section uses
   one outer measure and one readable panel silhouette. The page itself remains
   transparent, so this does not recreate the discarded giant wrapper. */
:root[data-nf-theme-family="midnight-harvest"] .page-index main > .hero-band,
:root[data-nf-theme-family="midnight-harvest"] .page-index main > section {
  width: min(var(--nf-measure), calc(100% - 2rem));
  max-width: min(var(--nf-measure), calc(100% - 2rem));
  margin-inline: auto;
  box-sizing: border-box;
}
:root[data-nf-theme-family="midnight-harvest"] .page-index main > section {
  padding-inline: var(--nf-band-inset);
  border-radius: var(--nf-radius-panel);
  background: var(--nf-surface);
}
/* Rounded panels never touch (owner, 2026-09-25): every panel after the hero
   keeps one step of scenery between itself and the panel above, the same gap
   the hero band already leaves below itself. */
:root[data-nf-theme-family="midnight-harvest"] .page-index main > section:not(:first-child) {
  margin-block-start: var(--nf-space-4);
}
:root[data-nf-theme-family="midnight-harvest"] .page-index main > .hero-band > section.hero {
  width: 100%;
}
/* #11969: on Halloween Dark the hero plate, its readability scrim, the
   signup note plate and the secondary CTA tints follow the purple surface
   ladder instead of the site-chrome navy, and the panel carries a rim so it
   separates from the purple canvas. Halloween Light keeps the dark plate. */
:root[data-nf-theme-family="midnight-harvest"][data-theme="dark"] .page-index main > .hero-band {
  --nf-hero-plate: var(--nf-surface);
}
:root[data-nf-theme-family="midnight-harvest"][data-theme="dark"] .page-index main > .hero-band > section.hero {
  box-shadow:
    0 0 0 1px var(--nf-border),
    0 0 0 3px color-mix(in srgb, var(--nf-cta-bg) 18%, transparent),
    0 28px 56px -28px color-mix(in srgb, var(--nf-cta-bg) 45%, transparent),
    var(--nf-shadow-panel);
}
@media (max-width: 720px) {
  :root[data-nf-theme-family="midnight-harvest"] .page-index main > .hero-band,
  :root[data-nf-theme-family="midnight-harvest"] .page-index main > section {
    width: calc(100% - 2rem);
    max-width: calc(100% - 2rem);
  }
}
@media (min-width: 721px) {
  .nav-toggle { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    scroll-behavior: auto !important;
    transition: none !important;
    /* Beta, cp and talk have always neutralised keyframe animation here; the
     * portal declared only transitions, so this block would have let the first
     * @keyframes anyone added run at full speed for someone who asked the OS
     * for none. There is no such animation in this file today -- that is why
     * nobody noticed, and why it is worth closing before there is. */
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* #778 — mobile sticky Request Beta Access (44px tap) */
.mobile-sticky-cta {
  display: none;
}
/* #6016. Everything inside this block is written for a phone - safe-area insets,
   the iOS toolbar-animation strip, a `hover: hover` guard on the blur, thumb
   reach - but it ran to 1199px, which is every tablet and every small laptop
   window. At 1000px the bar sat over the page duplicating an "Explore the Demo"
   button that was plainly visible in the hero at the same moment, and
   `body:has(.mobile-sticky-cta)` reserved 88px of viewport for it on machines
   that never needed it. 720px is the portal's own mobile boundary: six rules
   already use `max-width: 720px` and four its `min-width: 721px` counterpart.
   Neither `min-width: 1200px` block in this file pairs with this one - both are
   tour/hero column layout - so there is no gap to leave behind. */
@media (max-width: 720px) {
  .mobile-sticky-cta {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: var(--nf-layer-chrome);
    justify-content: center;
    padding: 0.75rem 1rem calc(0.75rem + env(safe-area-inset-bottom, 0));
    background: var(--nf-bg);
    border-top: 2.5px solid var(--nf-stroke);
    /* own compositor layer: momentum scroll is driven off the main thread, and a
       non-promoted fixed element visibly trails it on iOS */
    transform: translateZ(0);
    backface-visibility: hidden;
    will-change: transform;
  }
  /* iOS positions fixed elements against the layout viewport while the browser
     toolbar animates, which opens a strip under the bar. This paints the bar's
     own background into that strip, off-screen, so the transient gap shows the
     bar's colour instead of the page scrolling past underneath it. */
  .mobile-sticky-cta::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    height: 100vh;
    /* ink, not page colour: this strip is only ever seen for the moment the iOS
       toolbar animates, and black continues the bar's own top border instead of
       reading as a second white band under the button (owner) */
    background: var(--nf-fg);
    pointer-events: none;
  }
  /* the blur re-samples the page behind it every frame, which is what made the
     lag visible in the first place; keep it only where a pointer implies a
     device that can afford it */
  @media (hover: hover) and (pointer: fine) {
    .mobile-sticky-cta {
      background: color-mix(in srgb, var(--nf-bg) 92%, transparent);
      backdrop-filter: blur(8px);
    }
  }
  /* WCAG 2.2 SC 2.4.11 Focus Not Obscured: Tab scrolls a target flush to the
     viewport bottom, which is where the fixed bar is. Stop the scroller short of
     it, or focused footer links land behind the bar. */
  html {
    scroll-padding-bottom: 6.5rem;
  }
  .mobile-sticky-cta .nav-cta {
    width: min(100%, 24rem);
    min-height: 44px;
    justify-content: center;
  }
  body:has(.mobile-sticky-cta) {
    padding-bottom: 5.5rem;
  }
  /* #6016 CONTINUED - THE PHONE HALF OF THE OBJECTION ABOVE.
     Narrowing the query to 720px fixed the tablet and small-laptop case. Below
     720px the bar still duplicates a demo button that is on screen at the same
     moment, and now it clips the row that button sits in. Measured on the live
     apex and against the built artifact, 2026-09-06, scrollY 0, signed OUT, as
     the viewport-clipped intersection of each control with the bar (a bar parked
     off-screen occludes nothing; a control below the fold is not covered):

       390x844   bar 774..844   Explore the Demo and Sign In 765..809, 35px of a
                                44px control behind the bar, both of them
       360x800   bar 730..800   Request Beta Access 704..748, 18px of 44 behind
                                the bar - the PRIMARY call to action
       414x896   bar 826..896   row 580..684 - clear
       430x932   bar 862..932   row 581..685 - clear
       375x667   bar 597..667   row 704..808 - below the fold, never in
                                contention
       320x568   bar 498..568   row 805..969 - below the fold

     Signed IN the numbers move: portal_identity.js rewrites the third button
     from Sign In to Account, and at 360 that re-wraps the row from two lines to
     three, which put Request Beta Access at 756..800 - the whole control behind
     the bar. Worse, not better, and the same defect.

     THE OVERLAP TRACKS TEXT WRAPPING, NOT VIEWPORT HEIGHT. The h1 and the lede
     wrap to fewer lines from 414px up and carry the row ~125px higher; the tall
     420px-class phones are the ones that clear. So there is no padding, margin
     or reserved strip that can hold the row off the bar - the number it would
     have to be changes with how the copy happens to break. The bar has to know
     where the row is, which is /scripts/sticky_cta.js: an IntersectionObserver
     that sets data-sticky-cta on the body, dormant while the row is on screen
     and revealed once it has gone. That is what a sticky CTA is FOR - re-offering
     an action after the original scrolls away - so the bar stops being a
     duplicate as well as stopping being an occluder.

     Gated on `body:has(.hero-actions)` rather than on the route, so the eleven
     routes with no hero CTA row keep today's always-on bar and a route that
     grows one is covered without editing this block.

     `visibility`, not `opacity`: a bar that is merely transparent still takes
     the tap and still takes Tab, which is the WCAG 2.2 SC 2.4.11 failure the
     scroll-padding rule above exists to prevent. `translate3d` and not
     `translateY` because the base rule promotes this element with
     `translateZ(0)`, and a 2D transform here would demote it and hand back the
     iOS momentum-scroll trailing that promotion was added to cure. Neither the
     5.5rem body padding nor the 6.5rem scroll padding is conditional: both
     concern the foot of the document, where the bar is always revealed. */
  body:has(.hero-actions) .mobile-sticky-cta {
    visibility: hidden;
    transform: translate3d(0, 100%, 0);
    transition: transform var(--nf-motion-slow) ease,
                visibility var(--nf-motion-slow) ease;
  }
  body[data-sticky-cta="revealed"]:has(.hero-actions) .mobile-sticky-cta {
    visibility: visible;
    transform: translate3d(0, 0, 0);
  }
  /* NF-782 — the 16rem cap is a desktop density control. Against the portrait
     mobile source it becomes the binding constraint and scales the art down to
     ~205px wide, which is exactly the illegibility the art direction exists to
     avoid. Let the phone composition use the full column. */
  .commerce-story img {
    max-height: none;
    object-position: center;
  }
  /* NF-778 — art-direct the hero BOX to match the art-directed hero SOURCE.
     index.html swaps in product-hero-mobile.svg at this same breakpoint, and
     that asset is a 720x960 portrait phone composition. Left in the desktop
     16/10 box it rendered at 47% of the available width, so the purpose-built
     mobile art came out smaller than the desktop art it replaced. 24rem wide
     at 3/4 is exactly 32rem tall, so max-height never clamps the shape.

     2026-08-03: the compact art now serves up to 1199px (below that the desktop
     mock's labels fall under 9px), so THIS BOX MOVES WITH IT. The box and the
     source must share one breakpoint or the portrait composition gets
     letterboxed into a landscape frame - which is exactly what NF-778 caught. */
  .hero-visual {
    aspect-ratio: 3 / 4;
    width: min(100%, 24rem);
    max-height: 32rem;
  }
}

/* High browser zoom and landscape phones leave too little height for a fixed
   duplicate CTA. Keep the normal navigation/hero links and return that space
   to the document, including the footer's scroll and padding reservation. */
@media (max-width: 720px) and (max-height: 480px) {
  .mobile-sticky-cta { display: none; }
  html { scroll-padding-bottom: var(--nf-space-2); }
  body:has(.mobile-sticky-cta) { padding-bottom: 0; }
}

/* SF-037 contact form */
.contact-form {
  max-width: 32rem;
  margin-top: var(--nf-space-4);
}
.contact-form .field {
  margin: 0 0 var(--nf-space-3);
}
.contact-form label {
  display: block;
  font-size: var(--nf-text-md);
  margin-bottom: var(--nf-space-2);
  font-weight: var(--nf-weight-semibold);
}
.contact-form .optional {
  color: var(--nf-muted);
  font-weight: var(--nf-weight-regular);
}
.contact-form input,
.contact-form select,
.contact-form textarea {
  width: 100%;
  /* Native validation scrolls the first invalid field into view. Reserve its
     preceding label and focus halo instead of aligning the input to the edge. */
  scroll-margin-block: var(--nf-space-5);
  /* #6016 — a form control is inline-block by default, so it sits on the text
     baseline and the line box keeps descender room UNDER it. That reserved
     strip measured 3px here: the gap below a field was 3px larger than the
     8px above it, on no scale and for no reason a reader could see. Making it
     a block box removes the baseline entirely. */
  display: block;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--nf-control-border);
  border-radius: var(--nf-radius-ctl);
  background: var(--nf-surface);
  color: var(--nf-fg);
  font: inherit;
  height: auto;
  /* NF-782: 2.25rem = 36px missed the 44px tap floor on every field. */
  min-height: 44px;
  box-sizing: border-box;
}
.contact-form input:focus-visible,
.contact-form select:focus-visible,
.contact-form textarea:focus-visible {
  outline: var(--nf-focus-ring-width) solid var(--nf-focus);
  outline-offset: var(--nf-focus-ring-offset);
  /* 5px, not 8px: this ring sits at 1-3px, so the halo only needs to reach 5px
     to flank it. Fields sit shoulder to shoulder in this form and an 8px band
     would bridge the gap to the next one. */
  box-shadow: 0 0 0 5px var(--nf-focus-halo);
}
/* #7893: a field left invalid (after a blocked send, or after the reader
   edits it and moves on) gets a 2px danger edge and the message contact.js
   writes under it. The inset shadow doubles the border without moving the
   layout, and the thicker edge keeps the state from resting on colour alone.
   contact.js sets aria-invalid, so what the eye sees and what a screen reader
   hears come from the same state. */
.contact-form input[aria-invalid="true"],
.contact-form textarea[aria-invalid="true"] {
  border-color: var(--nf-t-danger);
  box-shadow: inset 0 0 0 1px var(--nf-t-danger);
}
.contact-form input[aria-invalid="true"]:focus-visible,
.contact-form textarea[aria-invalid="true"]:focus-visible {
  box-shadow: inset 0 0 0 1px var(--nf-t-danger), 0 0 0 5px var(--nf-focus-halo);
}
.contact-form .field-error {
  display: block;
  margin-top: var(--nf-space-2);
  color: var(--nf-t-danger);
  font-size: var(--nf-text-sm);
  font-weight: var(--nf-weight-semibold);
}
.contact-form .field-error[hidden] {
  display: none;
}
.contact-form button[type="submit"],
.contact-form .submit-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  background: var(--nf-cta-bg);
  color: var(--nf-cta-fg);
  border: 1px solid var(--nf-cta-edge);
  border-radius: var(--nf-radius-ctl);
  padding: 0.55rem 1.1rem;
  font: inherit;
  font-weight: var(--nf-weight-semibold);
  cursor: pointer;
}

/* ═══════════ OWNER RULING 2026-08-02: PRESS-SHEET DIES AS A VISUAL SYSTEM
   "P0-2: kill as visuals, keep as copy voice."

   The comic press sheet stays in the WORDS - "issue", "long box", "on the
   floor", "owner-proven". It leaves the PIXELS entirely. Every ornament
   rejected across this session came out of this vocabulary: registration
   crosshairs, Ben-Day clusters, misregistered heading plates, the two-block
   gutter dividers, the corner bursts. Removing them one at a time treated
   symptoms; this removes the source.

   Killed here: the hero halftone field, the crop marks on the trim, the
   halftone screens over imagery, the conversion-band wash, and the misregistered
   two-plate offset shadow. What is left is the app's own language: surface,
   border, corner, one shadow, colour that means something.

   Overrides rather than deletions, so the removed system stays legible in one
   place and any single piece is a one-line restore. */

/* Hero: no halftone field.

   The CROP MARKS STAY, restored at the owner's request: "where did the 4 corners
   around the top area go? I liked those, it made the very top/most important
   section more prominent."

   That is the right call and it survives the press-sheet cull on its own merits.
   The design skill bans hairline marks "as decoration" and then qualifies it:
   "use them only when they organize real content." Four corner brackets around
   the hero delimit the single most important region on the page. They are doing
   layout work, not evoking a pressroom, and they would earn their place on a
   page that had never heard of a press sheet.

   The halftone field beneath them does not clear that bar: it was texture for
   texture's sake, and it stays dead. */
.hero-band::before { content: none; }

/* Imagery: no halftone screen over the art. */
.hero-stack::after,
.tour-figure::after { content: none; }

/* Conversion band: no magenta wash. */
.page-cta.band-accent::before { content: none; }

/* The misregistered plate offset becomes one ordinary shadow. The offset WAS
   the press metaphor: two plates deliberately out of register. Without the
   metaphor it is just a coloured smear behind a card. */
.nf-plate,
.hero-visual,
.proof-list li,
.page-index .tour-visual {
  box-shadow: var(--nf-shadow-panel);
}


/* ═══════ v13: IMAGERY CONTAINERS — ONE STATIC TREATMENT, OUTLINED, NO HOVER
   OWNER AUDIT 2026-08-02: "The stock image containers are inconsistent static
   designs. Some have mouseover effects, some don't. No mouseover unless it is
   actually a clickable link. They definitely need to be outlined instead of
   blending into the container."

   Audit findings, and the rule each one broke:
   - .tour-figure:hover translated and revealed a shadow; .hero-visual had no
     hover at all. Neither is a link (verified: no <a>/<button> ancestor), so
     BOTH lose hover. The one hover that survives is .variant-card on the
     review index, because it IS an <a> - which is exactly the owner's rule.
   - The v6 contract said the artwork "draws its own card" and removed borders.
     Overruled: the art's soft self-frame blends into the band behind it. Every
     product visual now takes the panel tier explicitly: 2px ink outline, panel
     corner, one shadow. Same object, same treatment, every instance. */
.hero-visual,
.tour-visual,
.page-index .tour-visual {
  border: 2px solid var(--nf-stroke);
  border-radius: var(--nf-radius-panel);
  box-shadow: var(--nf-shadow-panel);
  background: var(--nf-surface);
}

/* Module cards are <li> text blocks, not links: the lift implied clickability
   the cards do not have. Static now; depth comes from the resting shadow. */
.module-card:hover {
  transform: none;
  box-shadow: var(--nf-shadow-panel);
}

/* ═══════ v14: THE FULL-PAGE BLUE BORDER, AND THE REAL IMAGE FRAME
   OWNER 2026-08-02: "The blue border around the entire mf page is not what I
   meant. What I need is a standard black border with some shadowing cast
   behind it."

   The blue was never a border I wrote: <main id="main-content" tabindex="-1">
   is the skip-link target, and when it takes focus the BROWSER draws its
   default focus ring around the element - which is the entire page, in
   Chrome's blue. tabindex="-1" means it can only be focused programmatically
   (skip link), never by Tab, so suppressing the ring here costs keyboard
   users nothing - the skip link itself keeps its visible focus style. */
main[tabindex="-1"]:focus,
main[tabindex="-1"]:focus-visible {
  outline: none;
}

/* The frame, per the owner's reference: standard ink border with a shadow CAST
   BEHIND the container - a real drop shadow, not the near-flat press shadow
   the v13 pass used. */
.hero-visual,
.tour-visual,
.page-index .tour-visual {
  border: 2px solid var(--nf-stroke);
  box-shadow: var(--nf-shadow-md);
}

/* v14b — OWNER 2026-08-02 (dark view): "weird bordering around the stock
   images, like there's 2 conflicting sets."

   Diagnosis: exactly two sets. The artwork bakes its own rounded cream panel
   into the SVG (stock #f8f6f0, its own corner radius), and v13 drew a second
   rounded frame around the <img> box. Between the two corners sits the img's
   CSS background - var(--nf-surface), which is #1c1926 at night. In day the
   gap is white-on-cream and invisible; at night it renders as a dark halo
   wedged between the frames. Same rule, one theme fine, the other broken.

   Fix: the img's backing is pinned to the artwork's own baked stock. One frame
   (ours), art floating on matching stock, both themes.

   v14b-b - 2026-09-04. The line above used to end "as a LITERAL, because the
   art does not theme and its backing therefore cannot either". That premise
   expired: every one of the 16 plate images on the portal now ships a
   -dark.svg variant, and <picture> selects it on prefers-color-scheme during
   preload. The literal was the one value in this rule that could not follow,
   so at night the backing stayed cream and v14b's own symptom came back in
   the other theme:

     - product-listing-loop-dark, product-catalog-b-dark and
       product-pricing-dark draw their panel inset 16px with NO full-canvas
       backdrop. Sampled off the live page, that band is rgba(0,0,0,0), so the
       cream backing painted a halo around a dark panel on a dark page.
       product-import-dark, which does carry a backdrop rect, sampled
       32,29,43 and had none - two sections of /features/ disagreeing, which
       is exactly the "2 conflicting sets" report.
     - 15 of the 16 plates are loading="lazy" and several are above the fold,
       so until they paint the cream backing IS the box: a full-width cream
       slab on a dark page.

   --nf-plate-stock carries the same #f8f6f0 in light (so light is byte-for-
   byte what v14b shipped) and #201d2b in dark - the backdrop the dark SVGs
   bake in themselves. The invariant is unchanged; it just holds in both
   themes now instead of one. */
.hero-visual,
.tour-visual,
.page-index .tour-visual {
  background: var(--nf-plate-stock);
}

/* v14c: same logic as the backing, applied to the frame. --nf-fg flips to
   cream at night, which painted a cream border around a cream artwork - an
   invisible frame, again only in one theme. The artwork's own ink is #1c1b19
   baked into the SVG; the frame matches it, literally, in both themes.
   "Standard black border" - owner's words - in day and night alike.
   2026-09-11: the artwork ink is now navy (#0f1a37 day / the night border),
   so the frame follows --nf-frame-line, declared per scheme in the tokens. */
.hero-visual,
.tour-visual,
.page-index .tour-visual {
  border-color: var(--nf-frame-line);
}

/* Page artworks (owner rule: every major page carries at least one image so no
   route is a wall of text). The img reuses the .tour-visual frame contract. */
.page-art { margin: var(--nf-space-4) auto; max-width: 56rem; }
.page-art img { width: 100%; height: auto; display: block; }

/* ═══════ v15: WHITE DESK, CREAM SHEET (owner, day view)
   The content column becomes a cream sheet on the white canvas: nav and page
   margins white, content areas cream, cards white-on-cream inside the sheet.
   Night keeps its own values (--nf-band is already the darker plane there). */
main {
  background: var(--nf-band);
  border-radius: var(--nf-sheet-radius);
  padding: var(--nf-space-5) var(--nf-space-4);
  /* Give the content sheet a visible gap below navigation and above the footer. */
  margin-block: var(--nf-space-4);
}
/* Home's hero band is content too: solid stock, not a fade into the canvas. */
:root:not([data-theme="dark"]) .hero-band {
  background: var(--nf-hero-band-bg);
}
/* Chips/wells that assumed the page behind them was cream now sit on the sheet;
   the sheet IS that cream, so surfaces inside it stay white for contrast. */
:root:not([data-theme="dark"]) main .pain-chips li,
:root:not([data-theme="dark"]) main .badge {
  background: var(--nf-surface);
}

/* ═══════ v16: THE SHEET IS A DAY-ONLY DEVICE (owner + design skill)
   OWNER: "In dark view, the black container around main content just looks
   weird." The skill's dark-mode rule explains why: dark hierarchy comes from
   LIGHTER elevated surfaces; the sheet rendered DARKER than the canvas
   (#100e15 on #14121a), which reads as a hole, not a surface. And "sheet of
   paper on a desk" has no night-time equivalent - at night the cards
   (--nf-surface, lighter than the page) already carry the elevation.
   Day keeps the white desk + cream sheet; night is one seamless canvas. */
:root[data-theme="dark"] main {
  background: transparent;
  border-radius: 0;
}
:root[data-theme="dark"] .hero-band {
  border-radius: 0;
}

/* Keep the sheet's top inset equal to its 16px side insets. The crop marks sit
   6px inside the first section, with a matching 16px gap below the band. */
main { padding-top: var(--nf-space-4); }
.hero-band { margin-bottom: var(--nf-space-4); }

/* ═══════ v17: TOP-SECTION SPACING, STANDARDIZED (owner audit)
   Every subpage opens with ~60px above its heading (sheet 16 + section 40)
   and 40px below before the divider. Closed to match the 22px rhythm the
   crop marks already set: first section starts 8px into the sheet (16+8=24)
   and intros end at 24px. Applies to every page by class, not per-page. */
main > section:first-child { padding-block-start: 8px; }
main section.page-intro { padding-block-end: var(--nf-space-4); }

/* ═══════ v18: HOME BAND FIXES (owner)
   1. Double divider: the hero band drew its own border-bottom AND the next
      band draws the [data-pg] hairline the press gauge counts by. One divider
      system only - the gauge's hairline stays, the band's own line goes.
   2. The crop marks sat ~80px from the hero content (padding clamp topped at
      5rem). Closed to ~36px so the corners frame the content, not dead air. */
.hero-band {
  border-bottom: 0;
  padding-block: clamp(1.5rem, 3vw, 2.25rem);
}

/* ═══════ v19: CORNER MARKS ON EVERY PAGE'S TOP SECTION (owner)
   The four registration corners that frame Home's hero now frame the opening
   section of every page. One shared rule; Home's hero keeps its own identical
   marks (same pseudo, same drawing), so nothing doubles. */
main > section:first-child { position: relative; }
main > section:first-child::after {
  content: "";
  position: absolute;
  inset: 6px;
  pointer-events: none;
  background:
    linear-gradient(var(--nf-muted), var(--nf-muted)) top left / 16px 1px no-repeat,
    linear-gradient(var(--nf-muted), var(--nf-muted)) top left / 1px 16px no-repeat,
    linear-gradient(var(--nf-muted), var(--nf-muted)) top right / 16px 1px no-repeat,
    linear-gradient(var(--nf-muted), var(--nf-muted)) top right / 1px 16px no-repeat,
    linear-gradient(var(--nf-muted), var(--nf-muted)) bottom left / 16px 1px no-repeat,
    linear-gradient(var(--nf-muted), var(--nf-muted)) bottom left / 1px 16px no-repeat,
    linear-gradient(var(--nf-muted), var(--nf-muted)) bottom right / 16px 1px no-repeat,
    linear-gradient(var(--nf-muted), var(--nf-muted)) bottom right / 1px 16px no-repeat;
  opacity: 0.7;
}

/* ═══════ v19b: CORNERS FRAME THE SHEET, NOT THE TEXT COLUMN (owner)
   v19 hung the corner marks on the intro section, whose box is the narrow
   46rem text measure - so the corners condensed around the copy and the
   top-left mark collided with the eyebrow. Wrong anchor.

   The first section now spans the sheet's full width (the marks land at the
   sheet edges, exactly like Home's hero) while its CONTENT keeps the 46rem
   measure via its children. Bottom corners sit at the section's end, which is
   directly above the first divider. */
main > section.page-intro:first-child {
  width: 100%;
  max-width: none;
}
main > section.page-intro:first-child > * {
  max-width: 46rem;
  /* Was `auto`. This is the rule that actually produced the 192px indent: it
     centres the title block inside a full-width first section while every H2 and
     grid below starts at the column edge. Left edge, same as the content. */
  margin-inline: 0;
}

/* ═══════ v19c: ONE CORNER-TO-DIVIDER DISTANCE, EVERYWHERE (owner audit)
   Home's bottom corners sat 22px above the divider (6px mark inset + the
   hero's 16px bottom margin); subpage intros had no bottom margin, so theirs
   sat at 6px. Same mark, two distances. Every first section now carries the
   same 16px bottom margin the hero does: 22px corner-to-divider, all pages. */
main > section:first-child { margin-bottom: var(--nf-space-4); }

/* Home bottom-section artworks (owner: the first page must not be a wall of
   text). The long-box art fills the flow grid's last cell - which also cures
   the bento empty-cell violation. Art cells carry no card chrome. */
/* v20b (owner: "the entire section looks weird"): the art cell is a CARD like
   its six siblings - white, ink border, card corner, same shadow - not a bare
   slot showing the band through it. The invented long-box object is gone; the
   covers sit flat and orderly the way the grid sits flat and orderly. */
.flow-art {
  display: flex; align-items: center; justify-content: center;
  background: var(--nf-card);
  border: 2px solid var(--nf-stroke);
  border-radius: var(--nf-radius-card);
  box-shadow: var(--nf-shadow-panel);
  padding: var(--nf-space-3);
}
.flow-art img { width: 100%; height: auto; display: block; }
.proof-art { max-width: 62rem; }

/* ═══════ v20: PAIN BAND USES THE FULL SHEET (owner)
   It was the only section clamped to 44rem while everything else runs full
   width. Two columns now: the complaint on the left, the new scattered-to-one
   artwork on the right; the consolidation callout spans below both. */
.pain-inner {
  max-width: none;
  display: grid;
  gap: var(--nf-space-4);
  align-items: start;
}
@media (min-width: 900px) {
  .pain-inner { grid-template-columns: 1.1fr 0.9fr; }
  .pain-inner > .pain-bridge, .pain-inner > .pain-resolve, .pain-inner > .pain-arrow { grid-column: 1 / -1; }
}
.pain-art { margin: 0; }
.pain-art img { width: 100%; max-width: 34rem; height: auto; display: block; margin-inline: auto; }

/* The art card fills the rest of its row: 6 steps + art = no empty cell. */
@media (min-width: 900px) { .flow-art { grid-column: span 2; } }

/* ═══════ v20c: THE FLOW GRID IS SIX EQUAL CARDS (owner: two art-in-grid
   attempts rejected; the proven fix all day has been subtraction).
   Source / Catalog / Grade & Sign / List / Sell / Report - 3x2, uniform,
   nothing wide, nothing bolted on. Perfect bento: 6 items, 6 cells. The
   page's imagery lives in the hero, the pain artwork and the floor strip. */
@media (min-width: 900px) {
  .flow-rail { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .flow-rail > li { grid-column: auto !important; }
}

/* v21b (owner: "it stretches out farther than the inner containers above it").
   The boxed CTA's border now sits exactly where sibling bands put their cards:
   inset by the same --nf-band-inset token the band padding uses. Same source,
   so they cannot drift apart again. */
.page-cta.band-accent {
  margin-inline: var(--nf-band-inset);
  width: auto;
}

/* ═══════ v21c: ONE CARD STROKE, ONE INSET SYSTEM, NO PINK (owner)
   1. Card borders were split 2px / 2.5px across one page. Every card-tier
      container now draws --nf-card-stroke. 2.5px, one source.
   2. Home still overhung because .proof-list re-applied its own measure inset
      INSIDE an already-inset band - a second accidental source. Inner lists
      fill their section; the section's --nf-band-inset is the only inset.
   3. The tint is gone: the CTA is a white house card like its siblings. */
.module-card,
.proof-list li,
.flow-step,
.flow-rail > li,
.page-cta.band-accent,
.pain-resolve {
  border-width: var(--nf-card-stroke);
}
.band-alt .proof-list,
.band-accent .proof-list,
main .proof-list {
  width: 100%;
}
/* ═══════ v21d: THE CTA IS THE INK PLATE (owner: "make it distinct from all
   the white containers above it"). Not a new color - the house's own dark
   surface, the same ink every artwork titlebar wears. White cards present the
   product; the ink card asks for the sale. Red primary pops hardest here. */
.page-cta.band-accent {
  background: var(--nf-cta-plate);
  color: var(--nf-plate-ink);
}
/* #6016 - the primary's own fill cannot delimit it here. Crimson on the day
   plate is 2.81:1 and on the night plate 2.31:1; WCAG 1.4.11 wants 3:1 for a
   control's boundary, so the most important button on the site was relying on
   a boundary that is not there. Its ring no longer depends on the fill: the
   plate's cream is 10.88:1 at night and higher in day, and it is the same ring
   the secondaries beside it have always worn, so the row reads as one set. */
.page-cta.band-accent .nav-cta {
  border-color: var(--nf-plate-ink);
}
.page-cta.band-accent .cta-heading { color: var(--nf-plate-ink); }
.page-cta.band-accent .section-lede { color: var(--nf-plate-ink-soft); }
.page-cta.band-accent .nav-cta-secondary {
  border-color: var(--nf-plate-ink);
  color: var(--nf-plate-ink);
  background: transparent;
}
.page-cta.band-accent .nav-cta-secondary:hover {
  background: color-mix(in srgb, var(--nf-plate-ink) 12%, transparent);
  color: var(--nf-plate-ink);
}

/* .flow-step never had a base border on the live page (it was only styled
   inside the motion-review scope) - width without style draws nothing. The
   card tier is explicit now: stroke AND style AND ink, from the tokens. */
.flow-step,
.flow-rail > li {
  border: var(--nf-card-stroke) solid var(--nf-stroke);
  border-radius: var(--nf-radius-card);
  background: var(--nf-card);
  box-shadow: var(--nf-shadow-panel);
}

/* ═══════ v22: NIGHT GETS ITS SHEET BACK (owner)
   Day's cream sheet reads right; night should match it - same rounded
   container, same width - in "the same dark blue as the top section":
   that is --nf-hero-plane (#201d2b at night), already a token. Crucially it
   is LIGHTER than the canvas (#14121a), so unlike the v16-era attempt this
   is correct dark elevation - a surface, not a hole. */
:root[data-theme="dark"] main {
  background: var(--nf-hero-plane);
  border-radius: var(--nf-sheet-radius);
}

/* ═══════ v23: BOTTOM LINK ROWS, HORIZONTAL AND CENTERED (owner)
   The plain "Next steps" sections (about/pricing/faq/...) hold bare links as
   direct children and inherited column + flex-start: a left-hugging vertical
   stack. They are a centered row now. The band-accent CTA keeps its own
   copy/actions layout and is untouched. */
/* OWNER 2026-08-03: left-aligned, not centred. Centring made a wrapped row read
   as broken - "Request Beta Access" and "Privacy" on line one, "Security"
   floating alone under the middle of them. Left alignment keeps the wrap tidy
   and matches every other block on the page. They fit one line where they fit;
   where they do not, the second line starts at the same left edge. */
.page-cta:not(.band-accent) {
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: flex-start;
  align-items: center;
  gap: var(--nf-space-3);
}

/* v23b (owner: match the proof grid's treatment): the cards were already
   identical - the measured delta was the grid gap alone, 3px vs 16px.
   Same token as the proof grid; the two grids can no longer disagree. */
.flow-rail { gap: var(--nf-space-3); }

/* v23c (owner: "get rid of the container containing the inner 6 blocks").
   The flow grid's old long-box gutter chrome - gutter fill, 3px padding, its
   own border - is gone. Six cards on the open sheet, like the proof grid.
   (That gutter padding was also the source of the mystery 3px gap.) */
.flow-rail {
  background: none;
  border: 0;
  padding: 0;
  box-shadow: none;
  border-radius: 0;
}

/* ═══════ v24: THE FLOW GRID BECOMES A FLOW CHART (owner: "each step leads
   to the next"). House vocabulary only: the mono step numerals and ink
   arrows the CLZ import artwork already speaks. Row-major reading order:
   01→02→03, drop to 04, →05→06. No markup changes - counters and pseudo
   connectors. */
.flow-rail { counter-reset: flowstep; }
.flow-rail > li { position: relative; counter-increment: flowstep; }
.flow-rail > li h3::before {
  content: "0" counter(flowstep);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.9em;
  height: 1.9em;
  margin-right: 0.55em;
  border: 2px solid var(--nf-stroke);
  border-radius: 50%;
  font-family: ui-monospace, Menlo, monospace;
  font-size: 0.62em;
  font-weight: var(--nf-weight-semibold);
  color: var(--nf-fg);
  vertical-align: 0.22em;
}

/* ═══════ v25: CHIPS GET A QUIET TINT (owner: "white container inside of
   white container"). Chips inside white cards sit on the warm band stock by
   day; at night they step LIGHTER than the card (surface-2), per the dark
   elevation rule. One source per theme, both already tokens. */
.flow-chips li,
.pain-chips li,
main .badge {
  background: var(--nf-chip);
  color: var(--nf-chip-ink);
}

/* ═══════ v26: AUDIT FIXES (design-consultant findings B-1, B-2, B-3, C-2)

   B-1: at narrow widths the 46rem intro column reaches the sheet edge and the
   eyebrow row sat inside the corner-mark band (measured -22px overlap at 375).
   The mark band is 22px deep (6 inset + 16 arm); mobile first sections start
   below it with clearance. */
@media (max-width: 899px) {
  main > section:first-child { padding-block-start: 34px; }
}

/* B-2: the 26px corner-to-divider invariant held only where section met
   section; the 7 page-art figures broke the seam chain, so those pages had NO
   first divider. The figure now carries the same 1px seam at the same 26px
   (6 mark inset + 20 section margin), and the section after it reseams. */
main > section:first-child + .page-art {
  border-top: 1px solid var(--nf-border);
  margin-top: 0;
  padding-top: var(--nf-space-4);
}
main .page-art + section {
  border-top: 1px solid var(--nf-border);
}

/* B-3: the closing CTA rendered at two stroke weights (2.5px accent variant,
   1px plain). One component, one rule: the plain variant is a link row, not a
   card - it draws no box at all. The accent variant keeps the card stroke. */
.page-cta:not(.band-accent) {
  border: 0;
}

/* C-2: at tablet widths "Sign In" orphaned onto a third header row. The nav
   cluster wraps as one centered unit instead. */
@media (max-width: 1023px) {
  .nav-primary { flex-wrap: wrap; justify-content: center; row-gap: var(--nf-space-2); }
}

/* C-4a (audit): below 720px the sticky bottom bar already pins Request Beta
   Access, so the nav copy of the same CTA hides - two instances on the first
   screen, not three. Same 720px gate as the sticky bar itself. */
@media (max-width: 720px) {
  .site-header .nav-cta { display: none; }
}

/* C-2b: the same orphan C-2 fixed at tablet widths came back on the phone. At
   375 the header box is 343px, and the four controls left after C-4a hides the
   CTA measure 303.99px (Menu 61.98, theme 44, Community 114.8, Sign In 83.21).
   The inherited 3x16px column gaps take the row to 351.99px -- 8.99px over --
   so "Sign In" wrapped alone onto a centred third row. That spent 206px of an
   812px viewport on a header, and worse: the Menu dropdown opens at the
   summary's bottom + 8px, which is y=145, directly on top of Sign In at
   y=138..182, so the site's own menu covered one of its own header controls.
   The overflow is entirely gap. At --nf-space-2 the cluster needs 327.99px and
   fits with 15px to spare, so the header is two deliberate rows -- wordmark,
   then the control cluster -- instead of three with an orphan. Row-gap stays
   exactly as C-2 set it; this narrows only the inline axis, only below 720. */
@media (max-width: 720px) {
  .nav-primary { column-gap: var(--nf-space-2); }
}
@media (max-width: 380px) {
  .nav-primary > .nav-cta-secondary { padding-inline: var(--nf-space-2); }
  .nav-toggle summary { padding-inline: var(--nf-space-2); }
}

/* ═══════ v27: NAV COMPRESSION (owner: "better solution for auto compressing
   the nav links" at squeezed desktop widths). The skill's rule: condense,
   drop, or hamburger - never wrap. The header has shipped a dormant
   <details class="nav-toggle"> menu all along; below the fit threshold it
   takes over from the inline list. The demo entry increases the fit threshold;
   narrower layouts retain the existing two-row brand/control arrangement. */
@media (max-width: 1279px) {
  .nav-primary > div > .nav-list { display: none; }
  .nav-toggle { display: block; position: relative; }
  /* At phone width nav-toggle isolates its compact control plate.  Elevate the
     open details element itself so that isolation cannot trap the menu beneath
     the raised hero. */
  .nav-toggle[open] { z-index: var(--nf-layer-overlay); }
  .nav-toggle[open] > .nav-list {
    position: absolute;
    top: calc(100% + 8px);
    /* C-2d: this was `right: 0`, which pins the sheet's RIGHT edge to the
       button's. The sheet has min-width 14rem (224px) and the button is 60px, so
       it extends 164px to the LEFT of the button -- fine once the nav has been
       pushed right by the rest of the header, and off the screen before that. At
       721, the narrowest width where the toggle exists and the nav row still
       starts at the left margin, the button's right edge is x=93 and the sheet
       opened at x=-131: every one of the five links was off-screen and only an
       empty sliver of the panel showed. Anchoring the left edge instead opens
       the sheet rightward from the button, which fits at every width the toggle
       is used (the widest case, 1120, puts it at 558..782 inside a 1104 box)
       and keeps it attached to the control that opened it. */
    left: 0;
    z-index: var(--nf-layer-overlay);
    display: flex;
    flex-direction: column;
    gap: 0;
    min-width: 14rem;
    padding: var(--nf-space-2);
    background: var(--nf-surface);
    border: 1px solid var(--nf-border);
    border-radius: var(--nf-radius-ctl);
    box-shadow: var(--nf-shadow-md);
  }
  .nav-toggle[open] > .nav-list a {
    width: 100%;
    padding-inline: var(--nf-space-3);
    border-radius: var(--nf-radius-ctl);
  }
  .nav-toggle[open] > .nav-list a:hover { background: var(--nf-band); }
}

/* C-2c: #6923 made the four controls fit one row from 360 up, but it cannot fit
   them at 320 -- the 1.4.10 reflow floor -- where they need 327.99px in a 288px
   box. There the cluster still wraps, and the wrap exposes a separate fault: the
   Menu sheet is anchored to the Menu BUTTON, so `top: 100%` means "below the row
   that button is on", not "below the nav". Measured at 320: the sheet opened at
   y=145 across Sign In at y=138..182 -- the site's own menu covering its own
   header control. Anchoring the sheet to the cluster instead makes `top: 100%`
   clear every wrapped row. Scoped to 720 because that is where the cluster can
   wrap; above it the header is one row and re-anchoring would only detach the
   sheet from its button (at 1120 it moved 486px away from Menu). The sheet stays
   left-aligned to the button either way -- the button is the cluster's first
   visible child, so the two share a left edge. */
@media (max-width: 720px) {
  /* Anchor the open sheet to the full header, not the signed-in control
     cluster. The compact 820px control rule has two class selectors, so match
     its specificity when restoring the toggle to static positioning. */
  .site-header {
    position: relative;
    z-index: var(--nf-layer-overlay);
  }
  .site-header .nav-primary { position: static; }
  .site-header .nav-toggle { position: static; }
  /* A static toggle cannot bound its plate: the ::after then fills the whole
     header and hides the wordmark. The summary carries the plate instead. */
  .site-header .nav-toggle::after { content: none; }
  .site-header .nav-toggle > summary {
    position: relative;
    isolation: isolate;
  }
  .site-header .nav-toggle > summary::after {
    content: "";
    position: absolute;
    inset: 2px;
    z-index: -1;
    border: 1px solid var(--nf-control-border);
    border-radius: var(--nf-radius-ctl);
    background: var(--nf-surface);
    pointer-events: none;
  }
  .site-header .nav-toggle > summary:is(:hover, :active)::after {
    background: var(--nf-band);
  }
  /* The signed-in cluster can be narrower than the menu. Anchor the menu
     to the cluster's right edge so removing Sign In cannot push it offscreen. */
  .site-header .nav-toggle[open] > .nav-list {
    left: auto;
    right: 0;
    max-width: calc(100vw - var(--nf-space-4) - var(--nf-space-4));
  }
}

/* ═══════ v29: THE SHEET RUNS EDGE-TO-EDGE ON PHONES
   NF-782's browser-calibrated guard caught this: the content sheet's own
   margin (16+16) and padding (24+24) stacked on top of each section's inset
   (16+16), costing 112px of a 360px viewport and shrinking art from 296px to
   248px. On a phone the sheet is the page, so it carries no inset of its own;
   the sections keep theirs and the art returns to full column width. */
/* OWNER 2026-08-03: on a phone the sheet IS the page. It runs the full width,
   with square corners.

   The v29 rule below zeroed the margins but left `main`'s own
   `width: min(--nf-measure, calc(100% - 2rem))` from the base rule standing. So
   the sheet stayed 2rem narrower than the screen AND lost the `margin-inline:
   auto` that had been centering it - it collapsed flush against the left edge
   and dumped the whole 32px on the right. Measured on the live site at 390px:
   main was 358px wide at left:0, gapRight:32. Overriding the width is the fix;
   zeroing the margin alone was what broke the centering. */
@media (max-width: 720px) {
  :root {
    --nf-sheet-radius: 0px;
  }
  main {
    width: 100%;
    max-width: none;
    margin-inline: 0;
    /* The sheet's BACKGROUND runs edge to edge; its CONTENT never does.
       OWNER 2026-08-03: the corner marks sit at inset 6px with a 16px arm, so
       they occupy x 6..22 of the section. A 1rem gutter put the h1 at x=16 -
       straddling the arm, so the text read as spilling past the very marks that
       are supposed to frame it. 2rem clears the arm with 10px to spare and the
       copy sits evenly inside all four corners. See the section rule below:
       the clearance belongs to the element that DRAWS the corners, not to main. */
    padding-inline: var(--nf-band-inset);
  }
}

/* The pain-band artwork sat inside the two-column grid's own gap on phones,
   landing 32px narrower than every other image. On a phone the band is one
   column, so the figure takes the full content width like its siblings. */
@media (max-width: 720px) {
  /* .pain-inner applied its own 2rem inset on top of the band's, so this one
     band ran 32px narrower than every other section. The band's inset is the
     only one on phones. */
  .pain-inner { width: 100%; }
  .pain-art { margin-inline: 0; }
  .pain-art img { max-width: none; }
}


/* ═══════ AUDIT 2026-08-03: READABLE MEASURE
   Body copy on /about/, /security/, /faq/, /privacy/, /terms/ and /solutions/
   was rendering 1120px wide at 15px - about 161 characters per line, more than
   double the readable ceiling, while the .lede directly above it was capped at
   640px. The measure changed mid-page. One cap, everywhere prose lives. */
main :is(p, li, dd, blockquote):not(.lede):not(.section-lede):not(.eyebrow) {
  max-width: min(68ch, 100%);
}

/* ═══════ AUDIT 2026-08-03: THE INTERIOR PAGES GET A TYPE RAMP
   Interior H1 was a flat 30px - SMALLER than the home page's H2 (40px) and only
   7.5px from its own H2, so ten routes had no heading hierarchy at all. */
main > section.page-intro > h1,
main > section:first-child > h1 {
  font-size: var(--nf-display-2);
  line-height: var(--nf-leading-none);
}
main > section > h2 {
  font-size: var(--nf-display-3);
  line-height: var(--nf-leading-tight);
}

/* ═══════ AUDIT 2026-08-03: BOTTOM CTA GROUPS ARE A ROW, NOT A RAGGED STACK
   Interior pages stacked their CTAs vertically at shrink-to-fit widths
   (188/86/127px on /pricing/, 144/97/67px on /contact/) while the home page
   laid the same component out as a row. */
main > section:last-child > .nav-cta,
main > section:last-child > .nav-cta-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
}


/* ═══════ AUDIT 2026-08-03: THE PHONE MENU OPENED OFF-SCREEN
   `.nav-toggle[open] > .nav-list` is `right: 0`, anchored to the Menu button.
   On a phone that button sits just after the wordmark, so right-aligning a
   14rem panel to it resolved to `left: -145px`: the entire primary nav rendered
   past the left edge as a blank box, and elementFromPoint returned null at every
   item's centre. Navigation was dead on both phone widths, on all 11 pages. It
   only looked fine at 768 because the button sits further right there.
   Anchor to the button's LEFT edge on phones and clamp to the viewport. */
@media (max-width: 720px) {
  /* Match the earlier `.site-header` selector so this later phone-specific
     anchor actually overrides its right-aligned desktop rule. */
  .site-header .nav-toggle[open] > .nav-list {
    left: var(--nf-space-4);
    right: auto;
    /* The full-header anchor keeps the 14rem sheet inside the viewport even
       when a signed-in avatar pushes Menu into the right-side control cluster. */
    min-width: 14rem;
  }
}

/* ═══════ AUDIT 2026-08-03: ARTWORK MUST NOT GLOW IN DARK MODE
   /pricing/'s illustration carried a hard #f8f6f0 fill in BOTH themes - a
   358x367 white slab at 15.28:1 against the night page, while the same
   component on /solutions/ and /integrations/ flipped correctly. The art is a
   drawing on the page, not a lightbox: let the sheet show through. */
/* Superseded: making the art transparent in dark mode stripped the paper its
   ink is drawn on, so every dark label vanished. Each artwork now ships a dark
   variant with its own stock, wired through the <picture> below. */


/* ═══════ AUDIT 2026-08-03: CROSS-PAGE CONSISTENCY
   Measured across all 11 routes, 4 viewports, both themes. */

/* The home hero h1 was SMALLER than every interior h1 on a phone (33.6 vs
   41.6px) after the interior pages got their type ramp - the landing headline
   was weaker than the legal pages'. The hero leads at every width. */
/* #6016 — resolved by display-1 rather than by a third hero clamp: the step
   floors at 48px, which is above every interior h1 at every width. The
   line-height here only restated the base rule's --nf-leading-none. */

/* Card corner radius was fragmented three ways at an identical 2px border -
   16 on .module-card/.flow-step/.page-cta, 14 on the art frames, 8 on .notice -
   with 16 and 14 colliding on the same page. One card corner. */
.tour-visual,
.hero-visual,
.notice {
  border-radius: var(--nf-radius-card);
}

/* The page-art frame had three treatments: 896px/2px on seven routes,
   1072px/1px on solutions and integrations, unframed on home. One frame. */
figure.commerce-story > img,
figure.diagram > img {
  border-width: var(--nf-card-stroke);
  border-color: var(--nf-stroke);
}

/* Footer links sat 24px left of the content column at 768 and 1440 on every
   page: they aligned to main's BORDER box while the h1 and body copy align to
   its PADDING box. */
@media (min-width: 721px) {
  .site-footer nav {
    /* main's own padding (--nf-space-4) PLUS the section inset that every h1 and
       paragraph sits behind, so the footer row starts on the same vertical as
       the content above it rather than 24px to its left. */
    padding-inline: calc(var(--nf-space-4) + var(--nf-band-inset));
  }
}

/* Remaining card-tier containers still on the panel corner (14px), which
   collided with 16px on the same page. */
.page-art img,
figure.commerce-story img,
figure.diagram img,
.commerce-mini,
.story-band img {
  border-radius: var(--nf-radius-card);
}


/* ═══════ OWNER 2026-08-03: THE COPY SITS INSIDE THE FOUR CORNER MARKS
   `main > section:first-child::after` draws the marks at `inset: 6px` with a
   16px arm, so they occupy 6..22px of the section's own box. The heading and
   lede sat at the section's content edge - x=0 relative to it - so the type ran
   straight past the marks that are meant to frame it, on every page.
   The section that draws the corners now reserves room for them. */
main > section:first-child {
  padding-inline: 2rem;
}
@media (min-width: 721px) {
  main > section:first-child { padding-inline: 2.5rem; }
}

/* Home draws its corner marks on .hero-band, and the hero copy lives two levels
   in (section.hero > .hero-copy), so the section rule above does not reach it.
   Same clearance, same reason: the type sits inside the marks. */
.hero-band > section.hero {
  padding-inline: 1rem;
}
@media (max-width: 720px) {
  /* The sheet and hero padding already provide gutters. A second constrained
     measure cost the mobile copy another 32px and pushed discovery below it. */
  .hero-band > section.hero { width: auto; margin-inline: 0; }
  .hero-copy h1 { font-size: var(--nf-display-2); }
}
@media (min-width: 721px) {
  .hero-band > section.hero { padding-inline: 1.5rem; }
}


/* ═══════ COMPACT ARTWORK MUST NOT BE ENLARGED
   #6016. The <=1199px art swap is deliberate and stays: below 1200px the desktop
   mocks are 1280-unit drawings whose 18-unit labels fall under 9px, so a compact
   composition takes over. That composition sets its OWN type larger to survive
   being small - contact-reply-mobile.svg is 460x300 with 19px and 26px labels,
   against the desktop asset's 900x180 with 14px and 18px.

   `.tour-visual { width: 100% }` then stretched it to the column anyway. Measured
   on /contact/ at a 1000px viewport: the 460px-wide compact source rendered
   896px wide, a 1.95x enlargement, so type drawn at 19/26px landed at roughly
   37/50px - two to three times the 14px the rest of that page sets - and the
   card read as broken rather than as art.

   `width: auto` makes a replaced element take its intrinsic width, and
   `max-width: 100%` still lets a narrow phone column shrink it. So the compact
   art is drawn at the size its type was tuned for and never larger. */
@media (max-width: 1199px) {
  /* `.page-art img { width: 100% }` is (0,1,1) and a bare `.tour-visual` is
     (0,1,0), so the plain class lost and the art kept stretching. Matched at
     (0,2,1) instead of raised with !important. */
  img.tour-visual,
  .page-art img.tour-visual {
    width: auto;
    max-width: 100%;
    margin-inline: auto;
  }
}

/* ═══════ ARTWORK LEGIBILITY AT DESKTOP WIDTHS
   The product mocks are 1280-unit drawings that were rendering at 467-629px, so
   an 18-unit label landed at 6-9px. Raising the type instead collides inside a
   fixed mock (verified: at 26 units the hero's "Convention floor" chip overruns
   "+ Add Issue"). The drawing is what should get bigger, not the type inside it.
   At >=672px an 18-unit label clears 9.4px. */
@media (min-width: 1200px) {
  /* .hero-visual is deliberately excluded: it caps its own width at 42.66rem,
     tuned so 1280/900 art is exactly 30rem tall and max-height never clamps.
     Replacing that with width:100% unbounds it and re-letterboxes the art. */
  .tour-visual {
    width: 100%;
    max-width: 42rem;
  }
}

/* .tour-row-flip reorders its children, so the art lands in the FIRST grid
   column while the sizes above assume it is second - that is why the flipped
   rows rendered their mock at 376px while the others got 672px. Size the
   columns the way the flip actually lays them out. */
@media (min-width: 1200px) {
  /* Explicit, not :has(). `:has(> picture:last-child)` resolves to (0,2,1) -
     one element token more specific than `.tour-row.tour-row-flip` (0,2,0) - so
     the flip rule silently lost and flipped rows kept the narrow column. */
  .tour-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 42rem);
  }
  .tour-row.tour-row-flip {
    grid-template-columns: minmax(0, 42rem) minmax(0, 1fr);
  }
  /* the pain band's figure is not a tour-row; its COLUMN was the constraint at
     457px, so widening the image alone did nothing. */
  .pain-inner {
    grid-template-columns: minmax(0, 1fr) minmax(0, 34rem);
  }
  .pain-art img {
    width: 100%;
    max-width: 34rem;
  }
}

/* ═══════ OWNER REVIEW 2026-08-07: void space around the closing furniture
   Measured at 390px: the CTA band left an 18px inset beside its last button and
   42px of dead cream below it, and the footer spent 40px above AND below a
   single row of five short links. On a phone that is most of a screen of
   nothing. The space under the last button now matches the space beside it, and
   the footer's block padding halves on phones only - desktop keeps its air. */
main > section.page-cta,
main > section.page-cta.band-accent {
  /* the band's own shorthand set 40px all round; only the bottom needed to come
     in, so the gap under the last button matches the 18px inset beside it */
  padding-bottom: var(--nf-space-3);
}
@media (max-width: 720px) {
  .site-footer {
    padding-block: var(--nf-space-4);
    margin-top: var(--nf-space-3);
  }
}

/* ── LINK TARGETS ─────────────────────────────────────────────────────────────
 *
 * #6016. WCAG 2.5.8 exempts a link sitting inline in a sentence and asks for a
 * real target for every other one; --nf-control-min is this surface's own
 * answer to that. Measured across the twelve shipping portal pages: 21 links
 * under it — the FAQ's six jump links at 17px tall, and the trailing "Security
 * / Privacy / Terms / Contact" links on About, Pricing, Security and Contact.
 *
 * Structural, not a list of the pages that were wrong: a link that is an item
 * in a nav, or the whole of a list item, is a destination the reader aims at.
 * A link inside a sentence keeps its line — the standard exempts it and a 44px
 * box would break the paragraph it belongs to.
 */
.linkrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--nf-space-2);
}
nav a[href],
li > a[href]:only-child {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--nf-control-min);
}

/* The rule above sets the BLOCK floor and stops, and on a short label it is the
   inline axis that fails: measured across all twelve pages, "Terms" in the
   /about/ linkrow renders 39.7x44 at 375 -- right height, 4.3px short on width.
   It is the only one today, which is the argument for stating the floor rather
   than leaving it to how long a word happens to be.

   The forum hit this in its own copy of this rule and repaired it (nf_v6.scss
   NF-V6-17); the app's copy was repaired in #6330. Same two floors as both:
   the WCAG 2.2 AA 2.5.8 minimum where a pointer is likely, the full control
   size where a fingertip is. Desktop is a no-op here -- every portal nav link
   at 1440 already clears 24px. */
nav a[href],
li > a[href]:only-child {
  min-inline-size: 24px;
}

@media (max-width: 820px) {
  nav a[href],
  li > a[href]:only-child {
    min-inline-size: var(--nf-control-min);
  }
}

/* UI02: first-visit comic art carries the headline and actions in one panel. */
main > .hero-band,
:root:not([data-theme="dark"]) main > .hero-band {
  margin-bottom: var(--nf-space-3);
  padding-block: var(--nf-space-2) var(--nf-space-3);
  border: 0;
  background: transparent;
}
main > .hero-band::before,
main > .hero-band::after { content: none; }
main > .hero-band > section.hero {
  --nf-first-visit-fallback: var(--nf-hero-plate);
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: center;
  min-height: clamp(26rem, 42vw, 30rem);
  width: min(var(--nf-measure), calc(100% - 2rem));
  max-width: none;
  margin-inline: auto;
  padding: clamp(1.5rem, 4vw, 3.5rem);
  overflow: hidden;
  border-radius: var(--nf-radius-panel);
  background: var(--nf-first-visit-fallback);
  box-shadow: var(--nf-shadow-panel);
}
main > .hero-band .hero-stack,
main > .hero-band .hero-stack picture {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}
main > .hero-band .hero-stack { z-index: 0; }
main > .hero-band .hero-stack picture { background: var(--nf-first-visit-fallback); }
main > .hero-band .hero-stack::after {
  /* Restore the readability scrim after the earlier halftone removal. */
  content: "";
  border-radius: 0;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--nf-hero-plate) 97%, transparent),
    color-mix(in srgb, var(--nf-hero-plate) 90%, transparent) 42%,
    color-mix(in srgb, var(--nf-hero-plate) 38%, transparent) 68%,
    transparent 82%);
  -webkit-mask-image: none;
  mask-image: none;
}
main > .hero-band .hero-visual {
  display: block;
  width: 100%;
  height: 100%;
  max-height: none;
  aspect-ratio: auto;
  object-fit: cover;
  object-position: center;
  background: var(--nf-first-visit-fallback);
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
main > .hero-band .hero-copy {
  position: relative;
  z-index: 1;
  max-width: 30rem;
}
main > .hero-band .hero-copy h1 {
  margin-bottom: var(--nf-space-2);
  color: var(--nf-hero-ink);
  font-size: var(--nf-display-3);
  line-height: var(--nf-leading-tight);
}
main > .hero-band .hero-copy .lede {
  max-width: 27rem;
  margin-bottom: var(--nf-space-3);
  color: var(--nf-hero-ink-soft);
  font-size: var(--nf-text-lg);
}
/* #2977 LML: on phones the taller signup copy runs past the scrim onto the
   artwork, so the note and status carry their own plate. */
main > .hero-band .launch-signup-note,
main > .hero-band .launch-signup-status {
  justify-self: start;
  padding: 0.125rem 0.5rem;
  margin-inline: -0.5rem;
  border-radius: var(--nf-radius-ctl);
  background: color-mix(in srgb, var(--nf-hero-plate) 92%, transparent);
}
/* The plate stays dark in every theme but the red follows the theme (2.96:1
   in light), so the error reads in plate ink with a red rule. */
main > .hero-band .launch-signup-status[data-tone="error"] {
  color: var(--launch-ink);
  box-shadow: inset 3px 0 0 var(--nf-plate-red-text);
}
main > .hero-band .eyebrow { color: var(--nf-hero-ink-soft); }
main > .hero-band .eyebrow::before { background: currentColor; }
main > .hero-band .hero-actions { gap: var(--nf-space-2); }
main > .hero-band .hero-actions .nav-cta-secondary {
  border-color: color-mix(in srgb, var(--nf-hero-ink) 80%, transparent);
  color: var(--nf-hero-ink);
  background: color-mix(in srgb, var(--nf-hero-plate) 35%, transparent);
}
main > .hero-band .hero-actions .nav-cta-secondary:hover {
  background: var(--nf-hero-ink);
  color: var(--nf-hero-plate);
}
@media (max-width: 899px) {
  main > .hero-band > section.hero {
    min-height: 40rem;
    align-items: flex-start;
    width: 100%;
    padding: 1.5rem;
  }
  main > .hero-band .hero-stack::after {
    background: linear-gradient(180deg,
      color-mix(in srgb, var(--nf-hero-plate) 98%, transparent),
      color-mix(in srgb, var(--nf-hero-plate) 93%, transparent) 55%,
      color-mix(in srgb, var(--nf-hero-plate) 35%, transparent) 76%,
      transparent 92%);
  }
  main > .hero-band .hero-copy .lede {
    font-size: var(--nf-text-md);
  }
}

/* #959 tip: optional detail behind a keyboard/touch "?" disclosure. The
   shared theme script closes [data-nf-popover] on Escape or an outside press. */
.nf-tip-row {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--nf-space-2);
  container-type: inline-size;
}
/* #9495: a tip-row checkbox and its label meet the 24px target size. */
.nf-tip-row > input[type="checkbox"] {
  width: 20px;
  height: 20px;
  margin: 2px;
  accent-color: var(--nf-accent);
}
.nf-tip-row > input[type="checkbox"] + label {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
}
.nf-tip-row .nf-tip > summary {
  position: relative;
  display: inline-grid;
  place-items: center;
  inline-size: var(--nf-control-min);
  block-size: var(--nf-control-min);
  color: var(--nf-muted);
  font-size: var(--nf-text-sm);
  font-weight: var(--nf-weight-semibold);
  line-height: var(--nf-leading-none);
  list-style: none;
  cursor: pointer;
  touch-action: manipulation;
}
.nf-tip-row .nf-tip > summary::-webkit-details-marker {
  display: none;
}
.nf-tip-row .nf-tip > summary::marker {
  content: "";
}
.nf-tip-row .nf-tip > summary::after {
  content: "";
  position: absolute;
  inset: 0;
  inline-size: 1.75em;
  block-size: 1.75em;
  margin: auto;
  border: 1px solid var(--nf-border);
  border-radius: 50%;
}
.nf-tip-row .nf-tip[open] > summary {
  color: var(--nf-fg);
}
.nf-tip-row .nf-tip[open] > summary::after {
  border-color: var(--nf-focus);
}
.nf-tip-row .nf-tip > p {
  position: absolute;
  inset-inline: 0 auto;
  inset-block-start: calc(100% + var(--nf-space-1));
  z-index: 1;
  box-sizing: border-box;
  inline-size: min(44ch, 100cqi);
  margin: 0;
  padding: var(--nf-space-2) var(--nf-space-3);
  border: 1px solid var(--nf-border);
  border-radius: var(--nf-radius-ctl);
  background: var(--nf-surface);
  color: var(--nf-fg);
  font-size: var(--nf-text-sm);
  line-height: var(--nf-leading-snug);
  box-shadow: var(--nf-shadow-panel);
}
@media (forced-colors: active) {
  .nf-tip-row .nf-tip > summary::after,
  .nf-tip-row .nf-tip > p {
    border-color: CanvasText;
  }
}
