/*
 * Numerint design system
 * Canonical CSS contract. Tokens live here; components are promoted from
 * repeated patterns. Companion human-readable doc: docs/design-system.md.
 *
 * Naming convention: --nx-<category>-<variant> for tokens,
 * .nx-<component>[-<part>][.is-<state>] for components.
 * All tokens and components authored during the 2026-04 UI overhaul use
 * the nx- prefix to avoid collision with the vendored Shock theme.
 */

:root {
  /* Backgrounds */
  --nx-bg-primary: #fff;
  --nx-bg-soft: #f9f9f9;
  --nx-bg-elevated: #f2f2f2;
  /* Sticky-nav surface, one shade darker than --nx-bg-elevated so the bar
   * is visibly distinct from the public-page body bg. Sits between
   * --nx-bg-elevated and --nx-bg-inset in the depth scale. */
  --nx-bg-nav: #e6e6e6;
  --nx-bg-inset: #0f1423;

  /* Text */
  --nx-text-primary: #0f1423;
  --nx-text-secondary: #333;
  --nx-text-muted: #6f727b;

  /* Borders */
  --nx-border-subtle: #d1d5db;

  /* Accent */
  --nx-accent: #1fff6f;
  --nx-accent-15: #1fff6f26;
  --nx-accent-soft: #1fff6f40;

  /* Ink overlays — alpha steps of #0f1423 for borders, subtle tints,
   * muted text on light surfaces. */
  --nx-ink-05: rgba(15, 20, 35, 0.05);
  --nx-ink-10: rgba(15, 20, 35, 0.10);
  --nx-ink-25: rgba(15, 20, 35, 0.25);
  --nx-ink-65: rgba(15, 20, 35, 0.65);
  --nx-ink-85: rgba(15, 20, 35, 0.85);

  /* White overlays — alpha steps of #fff for muted text on --nx-bg-inset
   * (dark surfaces like the featured pricing card and footer). */
  --nx-paper-60: rgba(255, 255, 255, 0.60);
  --nx-paper-78: rgba(255, 255, 255, 0.78);
  --nx-paper-88: rgba(255, 255, 255, 0.88);

  /* Semantic */
  --nx-success: #007a4d;
  --nx-warning: #ff9800;
  --nx-danger: #e63946;
  --nx-info: #023e8a;
  --nx-focus: #0ea5e9;

  /* Semantic overlays — alpha steps of the semantic hues for badge/tag
   * backgrounds. Keep in lockstep with the base hue above. */
  --nx-info-10: rgba(2, 62, 138, 0.10);
  --nx-warning-12: rgba(255, 152, 0, 0.12);
  --nx-danger-10: rgba(230, 57, 70, 0.10);

  /* Warning text colour — darker than --nx-warning so it reaches WCAG AA
   * (~5.0:1) on the --nx-warning-12 overlay used for .is-pending badges. */
  --nx-warning-text: #7a4400;
  --nx-danger-text: #a42831;

  /* Font families — display font fronts the brand Typekit face; fallback
   * cascade matches theme.css so dev machines without Typekit still render
   * legibly. */
  --nx-font-display: "darkmode-on", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --nx-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;

  /* Spacing scale (8px cadence) */
  --nx-space-1: 4px;
  --nx-space-2: 8px;
  --nx-space-3: 12px;
  --nx-space-4: 16px;
  --nx-space-5: 24px;
  --nx-space-6: 32px;
  --nx-space-7: 48px;
  --nx-space-8: 64px;

  /* Type scale (rem-first; codebase uses rem as the primary unit) */
  --nx-text-xs: 0.75rem;
  --nx-text-sm: 0.85rem;
  --nx-text-md: 1rem;
  --nx-text-lg: 1.2rem;
  --nx-text-xl: 1.5rem;
  --nx-text-2xl: 2.2rem;

  /* Radius scale */
  --nx-radius-sm: 4px;
  --nx-radius-md: 8px;
  --nx-radius-lg: 16px;
  --nx-radius-xl: 22px;
  --nx-radius-full: 999px;

  /* Shadow scale */
  --nx-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.08);
  --nx-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.15);
  --nx-shadow-card: 0 6px 20px rgba(15, 20, 35, 0.08);
  --nx-shadow-card-hover: 0 18px 40px rgba(15, 20, 35, 0.14);
}

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

.nx-wrap {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 var(--nx-space-5);
}

.nx-main {
  display: block;
}

.nx-section {
  padding: var(--nx-space-8) 0;
}

.nx-section--flush-top { padding-top: 0; }
.nx-section--flush-bot { padding-bottom: 0; }
.nx-section--bg-soft { background: var(--nx-bg-soft); }

/* ============================================================================
 * Shared N-backdrop — six consumer sites share the same cover-sized,
 * bottom-anchored N.svg atmosphere. Consolidated at C.4.2 /dev:review after
 * the 8th page-scoped copy was flagged. The three remaining copies at
 * .nx-hero::before, .nx-pricing-hero::before and .nx-faq-hero::before are
 * intentionally distinct — fixed-size pseudo-element backdrops with
 * explicit opacity overrides — so they are NOT in this list.
 * ========================================================================= */
.nx-trio,
.nx-status-page,
.nx-about-page .nx-section,
.nx-blog-page .nx-section,
.nx-blog-post-page .nx-section,
.nx-filters-page,
.nx-housekeeping-page,
.nx-invoice-page,
.nx-cookie-policy-page,
.nx-message-page .nx-section {
  background-image: url("/static/assets/images/N.svg");
  background-position: bottom;
  background-size: cover;
  background-repeat: no-repeat;
}

/* Shared backdrop wrapper. One N anchored at the bottom (from the shared
 * rule above); child sections become transparent so the single image
 * spans the whole group. */
.nx-trio {
  background-color: var(--nx-bg-elevated);
  overflow: hidden;
}
.nx-trio > .nx-section { background: transparent; }

/* ===========================================================================
 * Navigation — visual skin on top of the Bootstrap / Shock navbar.
 *
 * The Bootstrap structure (navbar + navbar-brand + navbar-toggler +
 * navbar-collapse + navbar-nav + nav-item + nav-link + #navbar_links) is
 * preserved intact so (a) menu-engine.min.js still toggles navbar-sticky /
 * scrolled-down / scrolled-up for the scroll-reactive state, (b) Bootstrap
 * collapse keeps handling the mobile hamburger, and (c) app.js can still
 * inject a dynamic Logout <li class="nav-item"><a class="nav-link"> into
 * #navbar_links on authenticated pages. We add .nx-nav to the outer <nav>
 * and style by Bootstrap's existing selectors — no parallel .nx-nav-link
 * class, so the dynamic logout item picks up the styling automatically.
 *
 * .nx-nav.navbar-sticky is the hook for the dark "scrolled" state.
 * =======================================================================*/

/* Light grey navbar (--nx-bg-nav, fixed, ~68px tall) that flips to dark
 * --nx-bg-inset on scroll. Shock's rules live in menu-engine.min.css; for
 * the container, .shock-header .navbar (0,2,0) outranks a bare .nx-nav, so
 * we match that with .shock-header .navbar.nx-nav (0,3,0) to win. Nav-link
 * overrides further up at (0,4,0). The .navbar-sticky class is toggled by
 * menu-engine.min.js when the page scrolls past the nav. */
.shock-header .navbar.nx-nav {
  background: var(--nx-bg-nav);
  transition: background 0.3s, box-shadow 0.3s;
}
.shock-header .navbar.nx-nav.navbar-sticky {
  background: var(--nx-bg-inset);
  box-shadow: 0 6px 24px rgba(15, 20, 35, 0.18);
}
/* .nx-nav-inner is layered on Bootstrap's .container (see base.html) so the
 * nav's max-width + horizontal padding match the original Shock navbar.
 * Only override flex layout here; horizontal padding stays owned by
 * .container so the logo/toggler gap matches the original. */
.nx-nav .nx-nav-inner {
  display: flex;
  flex-wrap: inherit;
  align-items: center;
  justify-content: space-between;
}
.nx-nav .navbar-brand {
  display: inline-flex;
  align-items: center;
  padding: 0;
  margin: 0;
}
.nx-nav .navbar-brand img {
  height: 32px;
  width: auto;
  display: block;
}
.nx-nav .navbar-brand .nx-logo--light { display: none; }
.nx-nav.navbar-sticky .navbar-brand .nx-logo--dark { display: none; }
.nx-nav.navbar-sticky .navbar-brand .nx-logo--light { display: block; }

/* Override Shock's 30px nav-link padding (menu-engine.min.css) — that made
 * the navbar 84px tall. Specificity 0,4,0 beats Shock's 0,3,0. The :hover
 * overrides keep the link colour stable (dark on light, light on dark) so
 * only the accent underline animates in — Shock's default hover would
 * tint the text green, which fights the underline. */
.shock-header .navbar.nx-nav .navbar-nav .nav-link {
  padding: 22px 12px;
  font-family: var(--nx-font-display);
  font-size: 15px;
  font-weight: 500;
  margin: 0 2px;
}
.shock-header .navbar.nx-nav .navbar-nav .nav-link:hover,
.shock-header .navbar.nx-nav .navbar-nav .nav-link:focus {
  color: var(--nx-text-primary);
}
.shock-header .navbar.nx-nav.navbar-sticky .navbar-nav .nav-link:hover,
.shock-header .navbar.nx-nav.navbar-sticky .navbar-nav .nav-link:focus {
  color: var(--nx-bg-primary);
}
/* Accent underline on hover — pure pseudo, no competing ::after in Shock. */
.nx-nav .nav-link::after {
  content: "";
  position: absolute;
  left: 50%;
  right: 50%;
  bottom: 18px;
  height: 2px;
  background: var(--nx-accent);
  transition: left 0.25s, right 0.25s;
}
.nx-nav .nav-link:hover::after { left: 14px; right: 14px; }

/* Navbar toggler — Bootstrap handles visibility via .navbar-expand-lg.
 * We only swap the icon colour in the scrolled state. */
.nx-nav .navbar-toggler {
  border: 0;
  padding: var(--nx-space-2);
  color: var(--nx-text-primary);
}
.nx-nav.navbar-sticky .navbar-toggler { color: var(--nx-bg-primary); }
.nx-nav .navbar-toggler:focus-visible {
  outline: 2px solid var(--nx-focus);
  outline-offset: 2px;
  box-shadow: none;
}

/* Offset the page so the fixed nav does not overlap content. Applied via
 * <body class="... nx-body"> in base.html. */
/* Offset the page below the fixed nav. Match nav-link height (22+22+line-
 * height ~= 68px). On mobile the nav shrinks (no nav-links visible, only the
 * brand sets its height), so the body bg must match the nav bg — otherwise
 * the extra padding-top gap shows as a white band above the hero. */
body.nx-body {
  padding-top: 68px;
  background: var(--nx-bg-elevated);
  /* Sticky-footer layout — main flex-grows so nav + main + footer fit the
   * viewport on short pages (checkout confirmations), without altering
   * appearance on content-heavy pages where main already exceeds the fold. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
/* On mobile (below Bootstrap's lg breakpoint, when navbar-expand-lg
 * collapses) the navbar shrinks to ~52px (brand only). The 68px desktop
 * padding leaves a 16px slice of body-bg above the hero — visible as a
 * stripe because --nx-bg-nav is one shade darker than --nx-bg-elevated.
 * Match the padding to the collapsed navbar height so the gap closes. */
@media (max-width: 991.98px) {
  body.nx-body { padding-top: 52px; }
}
body.nx-body > main { flex: 1 0 auto; }

/* ===========================================================================
 * Buttons — pill-shaped, brand-accent primary + dark + outline variants
 * =======================================================================*/

.nx-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 13px 26px;
  border-radius: var(--nx-radius-full);
  font-family: var(--nx-font-display);
  font-weight: 400;
  font-size: 15px;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  border: 1.5px solid transparent;
  transition: background 0.2s, color 0.2s, border-color 0.2s, transform 0.2s;
}
.nx-btn:focus-visible {
  outline: 2px solid var(--nx-focus);
  outline-offset: 2px;
}
.nx-btn .nx-btn-arrow {
  font-size: 18px;
  line-height: 1;
  position: relative;
  top: -1px;
  transition: transform 0.2s;
}
.nx-btn:hover .nx-btn-arrow { transform: translateX(4px); }

.nx-btn-primary {
  background: var(--nx-accent);
  color: var(--nx-text-primary);
  border-color: var(--nx-accent);
}
.nx-btn-primary:hover {
  background: var(--nx-bg-inset);
  color: var(--nx-accent);
  border-color: var(--nx-bg-inset);
}

.nx-btn-dark {
  background: var(--nx-bg-inset);
  color: var(--nx-bg-primary);
  border-color: var(--nx-bg-inset);
}
.nx-btn-dark:hover {
  background: var(--nx-accent);
  color: var(--nx-text-primary);
  border-color: var(--nx-accent);
}

.nx-btn-outline {
  background: transparent;
  color: var(--nx-text-primary);
  border-color: var(--nx-text-primary);
}
.nx-btn-outline:hover {
  background: var(--nx-text-primary);
  color: var(--nx-accent);
}

/* ===========================================================================
 * Section titles — centred kicker + headline + optional subcopy
 * =======================================================================*/

.nx-sec-title {
  text-align: center;
  margin-bottom: var(--nx-space-7);
}
.nx-kicker {
  display: block;
  font-family: var(--nx-font-display);
  font-size: var(--nx-text-xs);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--nx-success);
  font-weight: 500;
  margin-bottom: 10px;
}
.nx-sec-title :is(h1, h2) {
  font-family: var(--nx-font-display);
  font-size: clamp(30px, 3.4vw, 44px);
  font-weight: 500;
  color: var(--nx-text-primary);
  letter-spacing: -0.01em;
  line-height: 1.15;
  margin: 0;
}
.nx-sec-title .nx-sec-sub {
  font-size: var(--nx-text-md);
  color: var(--nx-ink-65);
  margin: 10px auto 0;
  max-width: 620px;
}

/* Page subtitle — generic class for the subtitle paragraph that sits
 * under a page-hero h1 (pricing, faq, about, and future consumers like
 * instructions). Promoted from identical duplicates `.nx-pricing-sub`
 * and `.nx-faq-sub` on 2026-04-21 — three consumers meets the
 * defer-until-second-consumer promotion threshold, and the rule bodies
 * were byte-identical. Parallel to `.nx-sec-sub` (section-scoped
 * subtitle, `.nx-sec-title > .nx-sec-sub`) but sized for page heroes
 * rather than section titles. Distinct from `.nx-hero .nx-hero-sub`
 * (line ~445), which is the homepage-hero-specific subtitle with
 * different typography (17px/1.55/28px-bottom) — the namespaces are
 * intentionally separate. */
.nx-page-sub {
  font-size: 18px;
  color: var(--nx-ink-65);
  line-height: 1.5;
  margin: 22px auto 0;
  max-width: 580px;
}

/* ===========================================================================
 * Xero App Store link — underline uses --nx-accent as the decoration colour
 * =======================================================================*/

.nx-xas {
  color: var(--nx-text-primary);
  text-decoration: underline;
  text-decoration-color: var(--nx-accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
  transition: text-decoration-thickness 0.15s;
}
.nx-xas:hover {
  color: var(--nx-text-primary);
  text-decoration-thickness: 3px;
}

/* ===========================================================================
 * Hero — centred eyebrow + headline + sub + CTA row.
 * Uses a subtle N backdrop as atmosphere. Cards detach below — no overlap.
 * =======================================================================*/

.nx-hero {
  padding: 128px 0 96px;
  background: var(--nx-bg-soft);
  text-align: center;
  position: relative;
  overflow: hidden;
}
.nx-hero::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 1100px;
  height: 1100px;
  background: url("/static/assets/images/N.svg") center / contain no-repeat;
  opacity: 0.03;
  pointer-events: none;
}
.nx-hero .nx-hero-inner {
  max-width: 720px;
  margin: 0 auto;
  padding: 0 var(--nx-space-5);
  position: relative;
  z-index: 1;
}
.nx-hero .nx-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 8px 18px;
  background: var(--nx-bg-primary);
  border: 1px solid var(--nx-ink-10);
  border-radius: var(--nx-radius-full);
  font-size: 13px;
  color: var(--nx-text-primary);
  margin-bottom: var(--nx-space-6);
  letter-spacing: 0.02em;
  box-shadow: 0 2px 6px rgba(15, 20, 35, 0.04);
}
/* Pure-CSS dot — avoids the font-glyph baseline offset that makes `●`
 * sit slightly below the text's optical centre. */
.nx-hero .nx-eyebrow::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--nx-accent);
  flex-shrink: 0;
}
.nx-hero h1 {
  font-family: var(--nx-font-display);
  font-size: clamp(38px, 5.4vw, 64px);
  line-height: 1.04;
  font-weight: 500;
  letter-spacing: -0.02em;
  max-width: 14ch;
  margin: 0 auto var(--nx-space-5);
  text-wrap: balance;
  color: var(--nx-text-primary);
}
.nx-hero .nx-hero-sub {
  font-size: 1.125rem;
  line-height: 1.6;
  color: var(--nx-ink-65);
  margin: 0 auto var(--nx-space-7);
  max-width: 56ch;
}
.nx-hero .nx-hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--nx-space-3);
  align-items: center;
  justify-content: center;
}

/* ===========================================================================
 * Feature cards — 4-up grid of icon + heading + body
 * =======================================================================*/

.nx-features { background: var(--nx-bg-elevated); }
.nx-features-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--nx-space-4);
  position: relative;
  z-index: 1;
}
.nx-feature-card {
  background: var(--nx-bg-primary);
  border-radius: 18px;
  /* Top edge is the brand accent; remaining sides keep the soft hairline.
   * padding-top is reduced by the extra 4px of border so visual top
   * spacing matches the other sides. */
  border: 1px solid var(--nx-ink-05);
  border-top: 5px solid var(--nx-accent);
  padding: var(--nx-space-5);
  padding-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  box-shadow: 0 2px 8px rgba(15, 20, 35, 0.04);
  height: 100%;
  transition: transform 0.2s, box-shadow 0.2s;
}
.nx-feature-card:hover {
  transform: translateY(-3px);
  box-shadow: 0 12px 28px rgba(15, 20, 35, 0.10);
}
.nx-feature-card .nx-feature-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}
.nx-feature-card .nx-feature-icon {
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  color: var(--nx-text-primary);
  opacity: 0.85;
}
.nx-feature-card .nx-feature-icon svg {
  width: 100%;
  height: 100%;
  display: block;
}
.nx-feature-card h3 {
  font-family: var(--nx-font-display);
  font-size: 20px;
  font-weight: 500;
  line-height: 1.2;
  color: var(--nx-text-primary);
  margin: 0;
}
.nx-feature-card p {
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--nx-ink-85);
  flex: 1;
  margin: 0;
}
.nx-feature-card p a {
  color: var(--nx-success);
  text-decoration: underline;
}

/* ===========================================================================
 * Trust pair — rate card + CTA card, side by side
 * =======================================================================*/

.nx-trust {
  background: var(--nx-bg-elevated);
  padding: 32px 0 48px;
}
.nx-trust-pair {
  display: grid;
  grid-template-columns: 1.1fr 1.5fr;
  gap: 20px;
  max-width: 1040px;
  margin: 0 auto;
}
.nx-trust-card {
  border-radius: 20px;
  padding: 28px 32px;
  display: flex;
  flex-direction: column;
  gap: var(--nx-space-2);
  justify-content: center;
}
.nx-trust-card.is-rate {
  background: var(--nx-bg-primary);
  border: 1px solid var(--nx-ink-05);
  box-shadow: 0 4px 14px rgba(15, 20, 35, 0.06);
}
.nx-trust-card.is-rate .nx-trust-big {
  font-family: var(--nx-font-display);
  font-size: 52px;
  font-weight: 500;
  color: var(--nx-text-primary);
  letter-spacing: -0.02em;
  line-height: 1;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
}
.nx-trust-card.is-rate .nx-trust-stars {
  color: var(--nx-text-primary);
  font-size: 22px;
  letter-spacing: 0.06em;
  white-space: nowrap;
}
.nx-trust-card.is-rate .nx-trust-lbl {
  font-size: 14.5px;
  color: var(--nx-ink-65);
}
.nx-trust-card.is-cta {
  background: var(--nx-bg-inset);
  color: var(--nx-bg-primary);
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  position: relative;
  overflow: hidden;
}
.nx-trust-card.is-cta::before {
  content: "";
  position: absolute;
  top: -40px;
  right: -40px;
  width: 180px;
  height: 180px;
  border-radius: 50%;
  background: var(--nx-accent-soft);
}
.nx-trust-card.is-cta h3 {
  font-family: var(--nx-font-display);
  font-size: 22px;
  font-weight: 500;
  line-height: 1.2;
  margin: 0;
  position: relative;
  z-index: 1;
}
.nx-trust-card.is-cta .nx-btn {
  position: relative;
  z-index: 1;
}

/* ===========================================================================
 * Coverage table — "What's included" table card with chips
 * =======================================================================*/

.nx-table-card {
  background: var(--nx-bg-primary);
  border-radius: 20px;
  overflow: hidden;
  max-width: 900px;
  margin: 0 auto;
  box-shadow: 0 10px 30px rgba(15, 20, 35, 0.08);
  border: 1px solid var(--nx-ink-10);
}
.nx-table-head {
  background: var(--nx-bg-inset);
  color: var(--nx-bg-primary);
  padding: 18px 28px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.nx-table-head h3 {
  font-family: var(--nx-font-display);
  font-size: 20px;
  font-weight: 500;
  margin: 0;
}
.nx-table-legend {
  display: flex;
  gap: 16px;
  align-items: center;
  font-size: 12.5px;
  color: var(--nx-paper-88);
}
.nx-table-legend span {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.nx-table-legend .nx-table-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
}
.nx-table-legend .nx-table-dot.is-yes { background: var(--nx-accent); }
.nx-table-legend .nx-table-dot.is-no { background: var(--nx-ink-25); }

.nx-table-scroll {
  max-height: 520px;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
  scrollbar-color: var(--nx-ink-10) transparent;
}
.nx-table-scroll::-webkit-scrollbar { width: 3px; }
.nx-table-scroll::-webkit-scrollbar-track { background: transparent; }
.nx-table-scroll::-webkit-scrollbar-thumb {
  background-color: var(--nx-ink-10);
  border-radius: 0;
}
.nx-table-scroll::-webkit-scrollbar-thumb:hover { background-color: var(--nx-ink-65); }

.nx-table {
  width: 100%;
  border-collapse: collapse;
}
.nx-table th,
.nx-table td {
  padding: 12px 20px;
  text-align: left;
  font-size: 14.5px;
  font-weight: 400;
}
.nx-table thead th {
  background: var(--nx-bg-soft);
  color: var(--nx-ink-65);
  font-family: var(--nx-font-display);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-weight: 500;
  border-bottom: 1px solid var(--nx-ink-10);
  position: sticky;
  top: 0;
  z-index: 1;
}
.nx-table thead th:not(:first-child) { text-align: center; }
.nx-table tbody td { border-bottom: 1px solid var(--nx-ink-05); }
.nx-table tbody tr:last-child td { border-bottom: none; }
.nx-table tbody tr:hover td { background: var(--nx-accent-15); }
.nx-table tbody td:not(:first-child) { text-align: center; }
.nx-table tbody td:first-child {
  font-weight: 500;
  color: var(--nx-text-primary);
}

.nx-chip {
  display: inline-grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  font-size: 13px;
  font-weight: 500;
  line-height: 1;
}
.nx-chip.is-yes { background: var(--nx-accent); color: var(--nx-text-primary); }
.nx-chip.is-no { background: var(--nx-ink-05); color: var(--nx-ink-65); }

.nx-table-note {
  margin: 22px auto 0;
  text-align: center;
  font-size: 13.5px;
  color: var(--nx-ink-65);
  max-width: 840px;
}

/* ===========================================================================
 * Quote carousel — static illustration + cycling quote card
 * Stack uses CSS Grid so the container takes the tallest quote height
 * and doesn't jump as slides rotate.
 * =======================================================================*/

.nx-quote-wrap {
  display: grid;
  grid-template-columns: 2fr 3fr;
  gap: 48px;
  max-width: 1100px;
  margin: 0 auto;
  align-items: center;
}
.nx-quote-wrap img {
  max-width: 400px;
  width: 100%;
  margin: 0 auto;
}
.nx-quote-card {
  background: var(--nx-bg-primary);
  border-radius: var(--nx-radius-xl);
  padding: 40px 44px;
  box-shadow: 0 10px 40px rgba(15, 20, 35, 0.10);
  border: 1px solid var(--nx-ink-05);
  position: relative;
}
.nx-quote-card::before {
  content: "\201C";
  position: absolute;
  top: 10px;
  left: 22px;
  font-size: 100px;
  color: var(--nx-accent);
  line-height: 1;
  opacity: 0.35;
  font-weight: 500;
  z-index: 0;
}
.nx-quote-slot {
  display: grid;
  position: relative;
  z-index: 1;
}
.nx-quote-slot .nx-quote {
  grid-area: 1 / 1;
  opacity: 0;
  transition: opacity 0.35s ease;
  pointer-events: none;
}
.nx-quote-slot .nx-quote.is-active {
  opacity: 1;
  pointer-events: auto;
}
.nx-quote-slot blockquote {
  font-family: var(--nx-font-display);
  font-size: clamp(19px, 2.1vw, 24px);
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: -0.005em;
  color: var(--nx-text-primary);
  margin: 0 0 20px;
}
.nx-quote-slot cite {
  font-style: normal;
  font-size: 14px;
  color: var(--nx-ink-65);
  display: block;
  line-height: 1.6;
}
.nx-quote-slot cite strong {
  color: var(--nx-text-primary);
  font-weight: 500;
}
.nx-quote-slot cite .nx-quote-stars {
  color: var(--nx-text-primary);
  margin-right: 10px;
  letter-spacing: 0.06em;
}
.nx-quote-nav {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid var(--nx-ink-05);
}
.nx-quote-nav .nx-quote-dots {
  display: flex;
  gap: 8px;
  flex: 1;
}
.nx-quote-nav .nx-quote-dot {
  width: 9px;
  height: 9px;
  border-radius: 10px;
  background: var(--nx-ink-10);
  border: none;
  padding: 0;
  cursor: pointer;
  flex: 0 0 auto;
  transition: background-color 0.2s, width 0.2s;
}
.nx-quote-nav .nx-quote-dot.is-active {
  background: var(--nx-accent);
  width: 24px;
}
.nx-quote-nav .nx-quote-arrow {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--nx-bg-elevated);
  border: none;
  cursor: pointer;
  display: grid;
  place-items: center;
  font-size: 16px;
  transition: background 0.2s;
  color: var(--nx-text-primary);
}
.nx-quote-nav .nx-quote-arrow:hover { background: var(--nx-accent); }

/* ===========================================================================
 * About cards — two-up side-by-side
 * =======================================================================*/

.nx-about { background: var(--nx-bg-elevated); }
.nx-about-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 28px;
  max-width: 1040px;
  margin: 0 auto;
}
.nx-about-card {
  background: var(--nx-bg-primary);
  border-radius: var(--nx-radius-lg);
  padding: 28px;
  border: 1px solid var(--nx-ink-05);
  /* Explicit foreground so h3/p don't inherit Shock's #f2f2f2 body text. */
  color: var(--nx-text-primary);
}
.nx-about-card h3 {
  font-family: var(--nx-font-display);
  font-size: 22px;
  font-weight: 500;
  margin: 0 0 14px;
}
.nx-about-card p {
  font-size: 15px;
  color: var(--nx-ink-85);
  line-height: 1.6;
  margin: 0 0 18px;
}

/* ===========================================================================
 * Footer — dark band with 5-column layout (contact + 3 nav cols + brand)
 * =======================================================================*/

.nx-footer {
  background: var(--nx-bg-inset);
  color: var(--nx-paper-78);
  padding: 56px 0 40px;
}
.nx-footer-top {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1fr 1.2fr;
  gap: 40px;
  align-items: flex-start;
}
.nx-footer h4 {
  font-family: var(--nx-font-display);
  font-size: 11px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.55);
  font-weight: 500;
  margin: 0 0 18px;
}
.nx-footer p {
  font-size: 14px;
  line-height: 1.6;
  color: var(--nx-paper-78);
  margin: 0 0 14px;
}
.nx-footer a {
  color: var(--nx-paper-88);
  text-decoration: none;
  transition: color 0.2s;
}
.nx-footer a:hover { color: var(--nx-accent); }
.nx-footer .nx-footer-em {
  color: var(--nx-accent);
  text-decoration: underline;
  text-decoration-color: rgba(31, 255, 111, 0.5);
  text-underline-offset: 3px;
}
.nx-footer .nx-footer-em:hover { text-decoration-color: var(--nx-accent); }
.nx-footer .nx-footer-badge { margin-top: 14px; }
.nx-footer .nx-footer-badge img { max-width: 180px; }

.nx-footer-col ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.nx-footer-col a {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  font-size: 15px;
  color: var(--nx-paper-88);
  line-height: 1.2;
  letter-spacing: 0.005em;
}
.nx-footer-col a::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--nx-accent);
  opacity: 0;
  transform: translateX(-8px);
  transition: opacity 0.2s, transform 0.2s;
}
.nx-footer-col a:hover::before {
  opacity: 1;
  transform: translateX(0);
}

.nx-footer-brand img {
  height: 30px;
  margin-bottom: 14px;
}
.nx-footer-brand .nx-footer-tag {
  font-size: 13.5px;
  color: var(--nx-paper-60);
  line-height: 1.55;
  margin-bottom: 18px;
}
.nx-footer-brand .nx-footer-copy {
  font-size: 12.5px;
  color: rgba(255, 255, 255, 0.4);
  letter-spacing: 0.01em;
}

/* ===========================================================================
 * Status page — authenticated app-page anchor (Phase C.1.2).
 * Full list of components documented in docs/design-system.md.
 *
 * JavaScript contract: status.js targets per-tenant IDs
 *   status_td_<id>, data_td_<id>, eta_td_<id>, ia_td_<id>,
 *   backup_options_<id>, disconnect_<id>
 * and writes innerHTML containing .nx-btn / .nx-btn-primary pills plus
 * Bootstrap spinners. The size rule near the bottom of this block scopes
 * those buttons down to a row-appropriate pill without touching global
 * .nx-btn defaults.
 * =======================================================================*/

.nx-status-page {
  background-color: var(--nx-bg-elevated);
  padding: var(--nx-space-7) 0 var(--nx-space-8);
  min-height: calc(100vh - 68px);
  overflow: hidden;
}
.nx-status-head {
  text-align: center;
  margin-bottom: 80px;
}
.nx-status-head .nx-kicker { color: var(--nx-success); }
.nx-status-head h1 {
  font-family: var(--nx-font-display);
  font-size: clamp(32px, 4vw, 48px);
  font-weight: 500;
  color: var(--nx-text-primary);
  letter-spacing: -0.01em;
  line-height: 1.1;
  margin: 0 0 var(--nx-space-3);
}
.nx-status-head h1 mark {
  background: transparent;
  color: var(--nx-text-primary);
  padding-bottom: 3px;
  border-bottom: 3px solid var(--nx-accent);
}
.nx-status-head p {
  font-size: var(--nx-text-md);
  color: var(--nx-ink-65);
  max-width: 560px;
  margin: 22px auto 0;
}

/* Status badge — semantic state pill with leading dot */
.nx-status-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px;
  border-radius: var(--nx-radius-full);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.03em;
  line-height: 1.2;
}
.nx-status-badge::before {
  content: "";
  width: 7px; height: 7px; border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}
.nx-status-badge.is-idle     { background: var(--nx-ink-05); color: var(--nx-ink-65); }
.nx-status-badge.is-active   { background: var(--nx-info-10); color: var(--nx-info); }
.nx-status-badge.is-active::before { animation: nx-status-pulse 1.4s ease-in-out infinite; }
.nx-status-badge.is-complete { background: var(--nx-accent-15); color: var(--nx-success); }
.nx-status-badge.is-pending  { background: var(--nx-warning-12); color: var(--nx-warning-text); }
.nx-status-badge.is-danger   { background: var(--nx-danger-10); color: var(--nx-danger); }
@keyframes nx-status-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.4; transform: scale(0.85); }
}

/* Progress — indeterminate CSS-only track + fill */
.nx-status-progress-line {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  color: var(--nx-ink-65);
}
.nx-status-progress-bar {
  flex: 1;
  height: 4px;
  max-width: 200px;
  background: var(--nx-ink-05);
  border-radius: 999px;
  overflow: hidden;
}
.nx-status-progress-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--nx-accent), var(--nx-success));
  border-radius: 999px;
  /* Sized via transform so the animation stays on the compositor
   * (scaleX + translateX) instead of triggering layout each frame. */
  width: 100%;
  transform-origin: left center;
  animation: nx-status-indet 2.6s ease-in-out infinite;
}
@keyframes nx-status-indet {
  0%   { transform: translateX(0%)   scaleX(0.20); }
  50%  { transform: translateX(41.67%) scaleX(0.60); }
  100% { transform: translateX(400%) scaleX(0.20); }
}

/* Card shell — one <li> per tenant row */
.nx-status-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--nx-space-4);
}
.nx-status-card {
  --stripe: var(--nx-ink-10);
  position: relative;
  background: var(--nx-bg-primary);
  border-radius: var(--nx-radius-lg);
  border: 1px solid var(--nx-ink-05);
  box-shadow: var(--nx-shadow-card);
  overflow: hidden;
  transition: transform 0.2s, box-shadow 0.2s;
}
.nx-status-card::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 4px;
  background: var(--stripe);
}
.nx-status-card:hover {
  transform: translateY(-1px);
  box-shadow: var(--nx-shadow-card-hover);
}
.nx-status-card.is-active   { --stripe: var(--nx-info); }
.nx-status-card.is-complete { --stripe: var(--nx-accent); }
.nx-status-card.is-pending  { --stripe: var(--nx-warning); }
.nx-status-card.is-danger   { --stripe: var(--nx-danger); }

.nx-status-card-inner {
  /* Left padding = 26px gutter + 4px stripe width (::before on .nx-status-card).
   * If stripe width changes, update both this calc and the ::before rule. */
  padding: 22px 26px 22px calc(26px + 4px);
}
.nx-status-card-top {
  display: flex;
  align-items: center;
  gap: var(--nx-space-4);
  flex-wrap: wrap;
}
.nx-status-tenant-block {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.nx-status-state-label {
  font-size: var(--nx-text-xs);
  color: var(--nx-ink-65);
  line-height: 1.2;
}
.nx-status-tenant-name {
  font-family: var(--nx-font-display);
  font-size: 20px;
  font-weight: 500;
  color: var(--nx-text-primary);
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.nx-status-card-primary {
  display: flex;
  gap: var(--nx-space-3);
  align-items: center;
  flex-wrap: wrap;
}
.nx-status-card-divider {
  height: 1px;
  background: var(--nx-ink-05);
  margin: 16px 0;
}
.nx-status-card-bottom {
  display: flex;
  align-items: center;
  gap: var(--nx-space-5);
  flex-wrap: wrap;
  /* Reserve the natural height of an expanded metric block (label 10.5px +
   * gap 2px + value min-height 1.5em on 14.5px ≈ 40px) so the row keeps
   * the same height across all states. Without this, states that hide
   * every metric collapse to button height (~30px) and the Filters /
   * Disconnect buttons jump up between transitions. */
  min-height: 2.5rem;
}

/* Metrics — label over value */
.nx-status-metrics {
  display: flex;
  gap: var(--nx-space-6);
  flex: 1;
  min-width: 0;
}
.nx-status-metric {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.nx-status-metric-label {
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--nx-ink-65);
}
.nx-status-metric-value {
  font-size: 14.5px;
  color: var(--nx-text-primary);
  font-weight: 500;
}
.nx-status-metric-value.is-muted {
  color: var(--nx-ink-25);
  font-weight: 400;
}
/* status.js writes innerHTML directly into the metric <div> slots without
 * re-applying the .nx-status-metric-value class — inherit the value styling
 * for any direct child so "150 / 42", "Apr 18…", "COMPLETE" stay legible.
 * min-height reserves one text line even when the value is empty so card
 * height doesn't bounce when transitioning between states with/without
 * populated metrics. */
.nx-status-metric > div,
.nx-status-metric > div .status-p {
  font-size: 14.5px;
  color: var(--nx-text-primary);
  font-weight: 500;
  margin: 0;
  min-height: 1.5em;
}
/* Hide metric cells whose value <div> is empty for the current state. Half the
 * states leave one or more of (Data / ETA / Items·Files) blank and the
 * dangling labels read as broken UI. status.js sets innerHTML="" on cells it
 * doesn't populate, so :empty matches reliably. */
.nx-status-metric:has(> div:empty) { display: none; }

/* Secondary actions (Filters / Disconnect) */
.nx-status-card-secondary {
  display: flex;
  gap: var(--nx-space-2);
  flex-wrap: wrap;
  align-items: center;
}
.nx-status-card-secondary .nx-btn {
  padding: 7px 14px;
  font-size: 12.5px;
}
/* Filters lives in a per-tenant slot rendered alongside Disconnect. The slot
 * is empty in states without a Filters CTA (scan / rescan / scanning /
 * running / pending) — collapse it so the flex `gap` doesn't push Disconnect
 * away from the row's right edge. Status JS sets innerHTML="" reliably. */
.nx-status-card-secondary > div:empty { display: none; }
.nx-status-card-secondary .nx-btn-outline {
  color: var(--nx-ink-65);
  border-color: var(--nx-ink-10);
}
.nx-status-card-secondary .nx-btn-outline:hover {
  background: var(--nx-bg-inset);
  color: var(--nx-bg-primary);
  border-color: var(--nx-bg-inset);
}

/* Inline button sizing — smaller pills inside status cards so they don't
 * dominate the row. Renderers emit .nx-btn.nx-btn-primary directly. */
.nx-status-card-primary .nx-btn,
.nx-status-metric .nx-btn {
  padding: 9px 20px;
  font-size: 13.5px;
}

/* Shimmer sweep modifier — opt-in via .nx-btn-shimmer alongside any
 * .nx-btn variant. Used by the green Download CTA and the dark scan-state
 * CTA (the only action the user can take from a fresh tenant) so the eye
 * is drawn to the lone clickable thing on the row. Scoped under
 * .nx-status-card-primary because the animation is infinite and we don't
 * want it leaking to other buttons elsewhere on the site.
 * Renderers: downloadCell() and scanActionButton({shimmer:true}) in status.js. */
.nx-status-card-primary .nx-btn-shimmer {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.nx-status-card-primary .nx-btn-shimmer > span {
  position: relative;
  z-index: 2;
}
.nx-status-card-primary .nx-btn-shimmer::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    110deg,
    transparent 0%,
    transparent 35%,
    rgba(255, 255, 255, 0.55) 50%,
    transparent 65%,
    transparent 100%
  );
  transform: translateX(-120%);
  animation: nx-btn-shimmer-sweep 3.6s ease-in-out infinite;
  pointer-events: none;
  z-index: 1;
}
/* Featured Download CTA — dark-circle download glyph. Lifts Download above
 * the adjacent green Filters pill in the status action row. Renderer:
 * downloadCell() in status.js. */
.nx-status-card-primary .nx-btn-download .nx-btn-icon {
  display: inline-flex;
  /* Sized to the label's line-height so the icon doesn't push the pill
   * taller than the adjacent Filters / Backup-again buttons in the same row. */
  width: 14px;
  height: 14px;
  align-items: center;
  justify-content: center;
  border-radius: var(--nx-radius-full);
  background: var(--nx-text-primary);
  color: var(--nx-accent);
  flex-shrink: 0;
  /* Resting nudge: the visual mass of the down-arrow sits high inside the
   * circle, so a 1px shift lines it up with the label's x-height. */
  transform: translateY(1px);
  transition: transform 0.2s;
}
.nx-status-card-primary .nx-btn-download:hover .nx-btn-icon {
  transform: translateY(2px);
}
.nx-status-card-primary .nx-btn-download .nx-btn-icon svg {
  /* Width parity matters: (icon - svg) must be even so the centering
   * offset is an integer pixel. Otherwise sub-pixel layout (the row's
   * content width can land on a half-pixel because of font-size 13.5px)
   * makes the icon's antialiased edges and the crisp SVG disagree on
   * where the visual centre is, reading as off-centre. 14 - 10 = 4. */
  display: block;
  width: 10px;
  height: 10px;
}
@keyframes nx-btn-shimmer-sweep {
  0%   { transform: translateX(-120%); }
  55%  { transform: translateX(120%); }
  100% { transform: translateX(120%); }
}
@media (prefers-reduced-motion: reduce) {
  .nx-status-card-primary .nx-btn-shimmer::after {
    animation: none;
  }
  .nx-status-card-primary .nx-btn-download .nx-btn-icon {
    transition: none;
  }
}

/* Inline progress spinner emitted by renderScanning/renderRunning */
.nx-status-card-primary .running-spinner {
  margin-left: 0;
  width: 14px;
  height: 14px;
}
.nx-status-card-primary .status-text {
  font-size: 13px;
  font-weight: 500;
  color: var(--nx-info);
  letter-spacing: 0.03em;
}

/* Notes grid — 4 horizontal info cards (replaces legacy bullet list) */
.nx-status-notes-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--nx-space-3);
  max-width: 1000px;
  margin: var(--nx-space-6) auto 0;
}
.nx-status-note-card {
  background: var(--nx-bg-primary);
  border: 1px solid var(--nx-ink-05);
  border-radius: var(--nx-radius-lg);
  padding: 18px 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
}
.nx-status-note-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--nx-shadow-card);
  border-color: var(--nx-ink-10);
}
.nx-status-note-icon {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--nx-accent-15);
  color: var(--nx-success);
  display: grid;
  place-items: center;
  font-size: 18px;
  font-weight: 500;
  line-height: 1;
}
.nx-status-note-card h5 {
  font-family: var(--nx-font-display);
  font-size: 14px;
  font-weight: 500;
  color: var(--nx-text-primary);
  margin: 0;
}
.nx-status-note-card p {
  font-size: 13px;
  color: var(--nx-ink-65);
  margin: 0;
  line-height: 1.5;
}
.nx-status-note-card a {
  color: var(--nx-success);
  text-decoration: underline;
  white-space: nowrap;
}

/* Sticky action dock — replaces the legacy top toolbar (count + Connect),
 * the bottom dashed "Connect another business" ghost card, and the inline
 * terms-row. The same controls render twice: once in the in-flow anchor
 * (.nx-status-dock-anchor) at the bottom of the page, and once in the
 * floating duplicate (.nx-status-dock) that fades in via status.js when
 * the anchor leaves the viewport. Pattern adapted from statement-processor's
 * sticky-action-dock; status.js mirrors checkbox state across both copies
 * so the terms cookie always agrees with both UIs. */

/* In-flow anchor — quiet horizontal action row beneath a hairline rule.
 * Always visible; the floating dock hides whenever this is on screen. */
.nx-status-dock-anchor {
  margin: var(--nx-space-6) 0 var(--nx-space-4);
  padding: var(--nx-space-3) 0;
  border-top: 1px solid var(--nx-ink-10);
  display: flex;
  align-items: center;
  gap: var(--nx-space-4);
  flex-wrap: wrap;
  justify-content: space-between;
  color: var(--nx-text-primary);
}

/* Floating dock — dark inset pill centred at the bottom of the viewport.
 * Default state is hidden (opacity 0, pointer-events none); status.js adds
 * .is-visible to reveal. Fixed positioning sits outside .nx-wrap on purpose
 * so it sizes against the viewport, not the page column. */
.nx-status-dock {
  position: fixed;
  left: 50%;
  bottom: calc(1rem + env(safe-area-inset-bottom));
  transform: translateX(-50%) translateY(0.75rem);
  width: min(72rem, calc(100% - 1.5rem));
  padding: 12px 18px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--nx-space-4);
  flex-wrap: wrap;
  border-radius: var(--nx-radius-lg);
  background: var(--nx-bg-inset);
  color: var(--nx-bg-primary);
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.06) inset,
    0 24px 48px -20px rgba(15, 20, 35, 0.55),
    0 6px 16px -8px rgba(15, 20, 35, 0.4);
  z-index: 1040;
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms cubic-bezier(0.22, 1, 0.36, 1),
              transform 200ms cubic-bezier(0.22, 1, 0.36, 1);
}
.nx-status-dock.is-visible {
  opacity: 1;
  pointer-events: auto;
  transform: translateX(-50%) translateY(0);
}

/* Inner cluster — the count + terms label group; CTA sits as a sibling. */
.nx-status-dock-cluster {
  display: flex;
  align-items: center;
  gap: var(--nx-space-4);
  flex-wrap: wrap;
}

/* Connected-businesses count chip with a leading accent dot. Inherits the
 * surrounding text colour so the same primitive reads correctly in both
 * the dark dock and the light anchor. */
.nx-status-dock-count {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--nx-font-display);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: inherit;
}
.nx-status-dock-count::before {
  content: "";
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--nx-accent);
  flex-shrink: 0;
}

/* Terms checkbox label — colour and link tone differ by container so the
 * text stays legible against both surfaces. */
.nx-status-dock-terms {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  line-height: 1.5;
  color: inherit;
  margin: 0;
}
.nx-status-dock-terms input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  border: 1.5px solid currentColor;
  border-radius: 3px;
  background: transparent;
  cursor: pointer;
  position: relative;
  margin: 0;
  transition: background-color 0.15s, border-color 0.15s;
}
.nx-status-dock-terms input[type="checkbox"]:checked {
  background: var(--nx-accent);
  border-color: var(--nx-accent);
}
.nx-status-dock-terms input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  inset: 2px 4px;
  border: solid var(--nx-text-primary);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.nx-status-dock-terms input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--nx-focus);
  outline-offset: 2px;
}

/* Anchor inherits the light page palette: muted text, dark link, accent dot
 * stays accent. Dock keeps the dark surface with light text + a lime link. */
.nx-status-dock-anchor .nx-status-dock-count { color: var(--nx-ink-65); }
.nx-status-dock-anchor .nx-status-dock-terms { color: var(--nx-ink-65); }
.nx-status-dock-anchor .nx-status-dock-terms a {
  color: var(--nx-text-primary);
  text-decoration: underline;
}
.nx-status-dock .nx-status-dock-count { color: rgba(255, 255, 255, 0.75); }
.nx-status-dock .nx-status-dock-terms { color: rgba(255, 255, 255, 0.78); }
.nx-status-dock .nx-status-dock-terms a {
  color: var(--nx-accent);
  text-decoration: underline;
}
.nx-status-dock .nx-status-dock-terms input[type="checkbox"] {
  border-color: rgba(255, 255, 255, 0.55);
}

/* CTA size override — slightly tighter than the global .nx-btn so the pill
 * fits the dock's height without dwarfing the count + checkbox row. */
.nx-status-dock-cta.nx-btn {
  padding: 9px 18px;
  font-size: 13.5px;
}

/* Reduced-motion: halt the pulse + indeterminate bar, suppress the small
 * hover lift on status cards, and drop the dock's slide-up — the opacity
 * fade still fires so the show/hide remains visible. */
@media (prefers-reduced-motion: reduce) {
  .nx-status-badge.is-active::before { animation: none; }
  .nx-status-progress-fill { animation: none; transform: translateX(30%) scaleX(0.40); }
  .nx-status-card,
  .nx-status-note-card {
    transition: none;
  }
  .nx-status-card:hover,
  .nx-status-note-card:hover {
    transform: none;
  }
  .nx-status-dock { transform: translateX(-50%); transition: opacity 200ms ease; }
  .nx-status-dock.is-visible { transform: translateX(-50%); }
}

/* ===========================================================================
 * Form controls — checkbox + select + price-summary + radio primitives.
 * First promoted from Phase C.1.3 (filters.html anchor); .nx-radio added
 * at Phase C.4.1 (housekeeping.html — first real radio consumer). Layout-
 * level concerns (.nx-form-row, .nx-input) deferred to C.4.2 (invoice.html
 * — the first real text-input consumer in the restyle family).
 * =======================================================================*/

/* .nx-checkbox — custom square checkbox. Apply directly to <input type="checkbox">.
 * Replaces the browser default with a 17px square that fills with --nx-accent
 * and shows an inline SVG tick when checked. */
.nx-checkbox {
  appearance: none;
  -webkit-appearance: none;
  width: 17px;
  height: 17px;
  margin: 0;
  border: 1.5px solid var(--nx-ink-25);
  border-radius: 4px;
  background-color: var(--nx-bg-primary);
  background-repeat: no-repeat;
  background-position: center;
  flex-shrink: 0;
  cursor: pointer;
  transition: border-color 0.15s, background-color 0.15s;
}
.nx-checkbox:hover { border-color: var(--nx-ink-65); }
.nx-checkbox:focus-visible {
  outline: 2px solid var(--nx-focus);
  outline-offset: 2px;
}
.nx-checkbox:checked {
  background-color: var(--nx-accent);
  border-color: var(--nx-accent);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none'><path d='M2.5 6.2L5 8.7L9.5 3.5' stroke='%230f1423' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-size: 11px;
}
.nx-checkbox:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* .nx-radio — custom radio, filled-dot-when-checked. Apply directly to
 * <input type="radio">. Promoted from the housekeeping.html anchor (C.4.1)
 * as the first real radio consumer in the codebase. Mirror-shape of
 * .nx-checkbox above — same 20x20 footprint, same 1.5px border, same hover
 * + focus-visible ring + disabled treatment; the only morphological
 * difference is border-radius: 50% and the checked-state inner dot. */
.nx-radio {
  appearance: none;
  -webkit-appearance: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1.5px solid var(--nx-ink-25);
  background: var(--nx-bg-primary);
  cursor: pointer;
  flex-shrink: 0;
  margin: 0;
  transition: border-color 0.15s, background 0.15s;
}
.nx-radio:hover { border-color: var(--nx-ink-65); }
.nx-radio:focus-visible {
  outline: 2px solid var(--nx-focus);
  outline-offset: 2px;
}
.nx-radio:checked {
  border-color: var(--nx-text-primary);
  background: var(--nx-text-primary);
  box-shadow: inset 0 0 0 3px var(--nx-bg-primary);
}
.nx-radio:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* .nx-select — custom native <select> with chevron. Apply directly to <select>.
 * Uses appearance:none to strip browser chrome and paints its own --nx-ink-65
 * chevron via background-image. Keeps keyboard + a11y semantics of native select. */
.nx-select {
  width: 100%;
  padding: 10px 14px;
  padding-right: 32px;  /* extra right space for the custom chevron */
  border: 1px solid var(--nx-ink-10);
  border-radius: var(--nx-radius-sm);
  background-color: var(--nx-bg-primary);
  color: var(--nx-text-primary);
  font-family: var(--nx-font-display);
  font-size: 14px;
  font-weight: 400;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' stroke='%236f727b' stroke-width='1.5' fill='none' stroke-linecap='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 10px;
  cursor: pointer;
  transition: border-color 0.15s;
}
.nx-select:hover { border-color: var(--nx-ink-65); }
.nx-select:focus-visible {
  outline: 2px solid var(--nx-focus);
  outline-offset: 2px;
  border-color: var(--nx-focus);
}
.nx-select:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* .nx-input — text-input primitive. Apply directly to
 * <input type="text|email|tel|url|password|number|search">. Promoted from
 * the invoice.html anchor (C.4.2) as the first real text-input consumer in
 * the codebase. Mirror-shape of .nx-select above — same 1px --nx-ink-10
 * border, same --nx-radius-sm corners, same --nx-font-display 14px/400
 * typography, same --nx-focus ring, same hover + disabled treatment. The
 * only morphological differences are no chevron background image and
 * padding-driven height (vs .nx-select's same 10/14 padding but with the
 * extra 32px right-padding reservation for the chevron).
 *
 * The raw `padding: 10px 14px` and `font-size: 14px` here are intentionally
 * identical to .nx-select above (not tokenised yet). Update both primitives
 * together if the input control scale changes. */
.nx-input {
  appearance: none;
  -webkit-appearance: none;
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 10px 14px;
  border: 1px solid var(--nx-ink-10);
  border-radius: var(--nx-radius-sm);
  background-color: var(--nx-bg-primary);
  color: var(--nx-text-primary);
  font-family: var(--nx-font-display);
  font-size: 14px;
  font-weight: 400;
  line-height: 1.4;
  transition: border-color 0.15s;
}
.nx-input:hover { border-color: var(--nx-ink-65); }
.nx-input:focus-visible {
  outline: 2px solid var(--nx-focus);
  outline-offset: 2px;
  border-color: var(--nx-focus);
}
.nx-input::placeholder { color: var(--nx-text-muted); opacity: 1; }
.nx-input:disabled { opacity: 0.5; cursor: not-allowed; }
.nx-input:invalid:not(:placeholder-shown),
.nx-input[aria-invalid="true"] {
  border-color: var(--nx-danger);
}

/* ============================================================================
 * Forms layout primitives — .nx-form-row / .nx-form-label / .nx-form-group /
 * .nx-form-group-legend / .nx-req-mark / .nx-field-error /
 * .nx-form-required-note. Promoted at C.4.2 (invoice.html) as the first
 * real text-input consumer. Pair with .nx-input above; the .nx-field-error
 * reveal selectors are currently .nx-input-only — extend the sibling rules
 * to cover .nx-select / .nx-checkbox / .nx-radio when those controls gain
 * their own field-error slots.
 * ========================================================================= */

/* .nx-form-row — single label+input pair. Stacked label-above-input shape
 * (chosen at the C.4.2 brainstorm 2026-04-22 over inline and floating-label
 * variants). Gap below for help or error text. First-consumer primitive;
 * future text-input pages inherit this exact shape (add .nx-form-row--<x>
 * modifiers only when a second shape is truly needed, per defer-until-
 * second-consumer discipline). */
.nx-form-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: var(--nx-space-4);
}
.nx-form-row:last-child { margin-bottom: 0; }

.nx-form-label {
  font-size: var(--nx-text-sm);
  font-weight: 500;
  color: var(--nx-text-primary);
  line-height: 1.4;
}

/* .nx-req-mark — tiny asterisk marker next to required-field labels.
 * aria-hidden="true" on each occurrence — required semantics come from
 * aria-required="true" on the input, not the glyph. */
.nx-req-mark {
  color: var(--nx-danger);
  margin-left: 3px;
  font-weight: 600;
}

/* .nx-field-error — error message slot beneath .nx-input. Hidden by default;
 * revealed by sibling-combinator selectors that detect invalid state. The
 * :not(:placeholder-shown) guard suppresses the error on empty fields that
 * haven't been interacted with (otherwise required-but-empty fields would
 * flash red on first paint).
 *
 * Known limitation: the hook fires for any :invalid state, so per-field
 * error copy should be written generically enough to cover all failure
 * modes of the input it's paired with. Invoice's email message is
 * deliberately umlaut-specific (the only Xero-originated constraint
 * worth explaining); "not-an-email"-style failures get the same copy,
 * which is a known tradeoff — revisit when server-side field errors land. */
.nx-field-error {
  display: none;
  margin: 0;
  color: var(--nx-danger);
  font-size: var(--nx-text-xs);
  line-height: 1.4;
}
.nx-input:invalid:not(:placeholder-shown) ~ .nx-field-error,
.nx-input[aria-invalid="true"] ~ .nx-field-error {
  display: block;
}

/* .nx-form-group — shared <fieldset> primitive. Removes UA border +
 * padding + default min-width so the fieldset can act as a pure grouping
 * container inside any card/shell without visual leakage. The min-width: 0
 * undoes <fieldset>'s auto min-width behaviour that otherwise breaks
 * flex/grid children. */
.nx-form-group {
  border: none;
  padding: 0;
  margin: 0;
  min-width: 0;
}

/* .nx-form-group-legend — visible <legend> styled as a group heading.
 * Uppercase micro-label with a divider rule underneath. Apply directly to
 * <legend> inside a <fieldset class="nx-form-group">. */
.nx-form-group-legend {
  display: block;
  width: 100%;
  font-size: var(--nx-text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--nx-text-muted);
  padding-bottom: var(--nx-space-3);
  margin-bottom: var(--nx-space-4);
  border-bottom: 1px solid var(--nx-ink-10);
}

/* .nx-form-required-note — small muted caption under a page hero explaining
 * the asterisk convention. Accepts an embedded .nx-req-mark span. */
.nx-form-required-note {
  margin: var(--nx-space-3) 0 0;
  color: var(--nx-text-muted);
  font-size: var(--nx-text-xs);
}
.nx-form-required-note .nx-req-mark { font-size: inherit; }

/* .nx-price-summary — semantic marker for a card/panel that shows price info on
 * the left and action buttons on the right. The inner DOM varies by page (filters
 * uses a Bootstrap .form-area/.form-col chain; invoice will have its own wrapping),
 * so page-scoped CSS establishes the flex layout at the appropriate nested level.
 * The token itself is intentionally a hook with minimal styling — consumers are
 * expected to extend it. */
.nx-price-summary {
  /* Intentional hook — no layout-level rules. Page CSS owns the flex layout. */
}

/* ===========================================================================
 * Filters page — authenticated form-patterns anchor (Phase C.1.3).
 * Full list of per-section design decisions documented in
 * docs/design-system.md > "Filters page components" chapter.
 *
 * All rules are scoped under .nx-filters-page so nothing leaks to other
 * templates. Uses only the promoted --nx-* tokens; no new tokens added.
 *
 * Vue bindings preserved verbatim: all v-model, v-if, v-for, @click, :class,
 * :aria-expanded on inputs, selects, and the date-range button continue to
 * fire against the unchanged filters.js logic.
 * =======================================================================*/

.nx-filters-page {
  background-color: var(--nx-bg-elevated);
  min-height: calc(100vh - 68px);
}

/* Strip the inner section's Shock `.n-background` N.svg pattern — its
 * viewBox has substantial low-opacity fill that reads as a blank "gap"
 * between the last card and the footer on short-content pages. The
 * page's --nx-bg-elevated fills the full viewport cleanly instead
 * (matching index.html + status.html which never opted into .n-background). */
.nx-filters-page .shock-section.n-background {
  background-image: none;
}

/* Kill Shock's .pt-8 / .pb-7 section padding — filters owns its own vertical
 * rhythm via the design-system spacing scale. */
.nx-filters-page .shock-section {
  padding: var(--nx-space-8) 0 var(--nx-space-7);
}
.nx-filters-page .container { max-width: 860px; padding-top: 0; }
.nx-filters-page .basic-intro { margin-bottom: var(--nx-space-6); }

/* Override Shock's .invoice-column (main.css: 50% width + 20px padding + outer
 * box-shadow). Filters has exactly one invoice-column — let it fill the
 * container so the price card has room for info + Cancel + Back inline. */
.nx-filters-page .invoice-column {
  width: 100%;
  padding: 0;
  box-shadow: none;
}

/* Heading — replace the .text-style-6 / .text-color / .black / .animated-underline
 * Shock visual language with design-system typography. */
.nx-filters-page h1 {
  font-family: var(--nx-font-display);
  font-size: clamp(30px, 3.4vw, 44px);
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1.1;
  color: var(--nx-text-primary);
  margin: 0 0 var(--nx-space-6);
  text-align: center;
}
.nx-filters-page h1 mark {
  background: transparent;
  color: var(--nx-text-primary);
  padding-bottom: 3px;
  border-bottom: 3px solid var(--nx-accent);
}
/* Defensively suppress Shock's .animated-underline::before on the <mark>. No
 * current JS toggles .active on #filtersPageIndicator, so the pseudo stays at
 * width:0 today; this rule guarantees our static border-bottom remains the
 * sole underline even if a future feature adds .active. */
.nx-filters-page h1 mark.animated-underline::before { display: none; }

/* Card shell — reuse .nx-card shape from C.1.2 but target the existing
 * .card.card-body wrappers directly so the template doesn't need an extra
 * class on every card. */
.nx-filters-page .card.card-body {
  background: var(--nx-bg-primary);
  border: 1px solid var(--nx-ink-05);
  border-radius: var(--nx-radius-lg);
  padding: 22px 26px;
  margin-bottom: var(--nx-space-4);
  box-shadow: var(--nx-shadow-card);
}
.nx-filters-page .card.card-body.is-compact { padding: 16px 22px; }
.nx-filters-page .card.card-body .form-area { background: transparent; padding: 0; }

/* ─── Previous filters card ─────────────────────────────────────────── */
/* The <h3 class="description black"> becomes a clickable collapse trigger.
 * Scope-addition 1 adds @click, :class, :aria-expanded, aria-controls,
 * role="button", tabindex="0", and keydown handlers on this element.
 *
 * Selector uses `.invoice-div h3.description` (not `:first-of-type`) because
 * the previous-filters card is NOT the first-of-type div in its parent
 * (the .basic-intro div above holds the page heading). The template has
 * exactly one h3.description (the collapse trigger) so a class-scoped match
 * is unambiguous without a new wrapper class. */
.nx-filters-page .invoice-div h3.description {
  font-family: var(--nx-font-display);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--nx-ink-65);
  margin: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  user-select: none;
  padding: 0;
  transition: margin 0.15s;
}
.nx-filters-page .invoice-div h3.description:not(.is-collapsed) {
  margin-bottom: var(--nx-space-4);
}
/* Chevron uses an SVG triangle with its centroid at viewBox centre (points:
 * (2.5,4) (7.5,4) (5,7) → centroid (5,5)) so rotating to the collapsed state
 * keeps the triangle visually centred in the circle. The previous Unicode
 * "▾" glyph had asymmetric font metrics that made the point-right rotation
 * look noticeably off-centre. `background-color` is used instead of the
 * shorthand `background` so the hover rule doesn't wipe out the SVG. */
.nx-filters-page .invoice-div h3.description::before {
  content: "";
  display: inline-block;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background-color: var(--nx-ink-05);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'><path d='M2.5 4 L7.5 4 L5 7 Z' fill='%230f1423'/></svg>");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 10px 10px;
  transition: transform 0.2s, background-color 0.15s;
}
.nx-filters-page .invoice-div h3.description:hover::before { background-color: var(--nx-ink-10); }
.nx-filters-page .invoice-div h3.description.is-collapsed::before { transform: rotate(-90deg); }
.nx-filters-page .invoice-div h3.description:focus-visible {
  outline: 2px solid var(--nx-focus);
  outline-offset: 2px;
  border-radius: var(--nx-radius-sm);
}

/* Previous filters list — override Bootstrap .row + .col-md-6 to vertical stack.
 * Selector specificity (0,3,0) beats Bootstrap's .col-md-6 (0,1,0). */
.nx-filters-page .row.previous-filters-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
}
.nx-filters-page .pf-col.col-md-6 {
  width: 100%;
  max-width: 100%;
  flex: 1 1 100%;
  padding: 0;
}

/* Each <button class="previous-filters-button"> — full-width row with radio +
 * date. The <i class="pi pi-circle(-fill)?"> icon inside is bound by Vue via
 * :class to the matchingFilterId computed prop; we restyle the icon colour
 * but don't touch its class. */
.nx-filters-page .previous-filters-button {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 10px 12px;
  border: 1px solid transparent;
  border-radius: var(--nx-radius-sm);
  background: transparent;
  color: var(--nx-text-primary);
  font-family: var(--nx-font-display);
  font-size: 14px;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.nx-filters-page .previous-filters-button:hover {
  background: var(--nx-bg-soft);
  border-color: var(--nx-ink-10);
  box-shadow: var(--nx-shadow-sm);
  color: var(--nx-text-primary);
}
.nx-filters-page .previous-filters-button:has(i.pi-circle-fill) {
  background: var(--nx-accent-15);
  border-color: var(--nx-accent);
}
/* Render both pi-circle and pi-circle-fill as a CSS-drawn outline ring —
 * `font-size: 0` suppresses the PrimeIcons glyph (which would otherwise
 * paint a solid filled circle on the `-fill` variant). The selected state
 * gets a smaller green dot inside via ::after; ::before is avoided because
 * PrimeIcons uses it to render the font glyph. */
.nx-filters-page .previous-filters-button i.pi {
  width: 16px;
  height: 16px;
  border: 1.5px solid var(--nx-ink-25);
  border-radius: 50%;
  flex-shrink: 0;
  position: relative;
  top: -2px;
  box-sizing: border-box;
  font-size: 0;
}
.nx-filters-page .previous-filters-button i.pi-circle-fill::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--nx-accent);
}

/* ─── Checkbox grid — column-major 2-col / 7-row flat list ──────────── */
/* Grid fills top-to-bottom in column 1 first, then column 2. This places
 * Invoice PDFs (DOM position 2) directly under Invoices (position 1), and
 * Quote PDFs (position 13) directly under Quotes (position 12). Both
 * parent/sub pairs end up visually adjacent — which is what lets the sub-row
 * "L" connector line point cleanly at its parent above. */
.nx-filters-page .form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: repeat(7, auto);
  grid-auto-flow: column;
  column-gap: 36px;
  row-gap: 0;
  /* !important to override Bootstrap's `.row { margin: 0 calc(-.5 * var(--bs-gutter-x)) }`
   * (`-12px`) which would pull the grid past the card's horizontal padding. */
  margin: 0 !important;
}

.nx-filters-page .form-check {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 4px 12px 12px;
  /* !important to override Shock/Bootstrap `.me-2` utility (margin-right: 0.5rem)
   * which otherwise leaves an asymmetric gap on the right of each cell. */
  margin: 0 !important;
  border-top: 1px solid var(--nx-ink-05);
  cursor: pointer;
  transition: background 0.15s;
}
/* Neutralise Bootstrap's .form-check-input { margin-left: -1.5em } which would
 * hang the checkbox into a gutter that doesn't exist in our flex row layout. */
.nx-filters-page .form-check input[type="checkbox"] {
  margin: 0 !important;
}

/* Restore .nx-checkbox's green-fill + dark-tick treatment inside .form-area —
 * Shock's `.form-area .form-check-input` rule (main.css:856) wins on
 * specificity over .nx-checkbox alone, so the inputs were rendering with
 * Shock's brand colours. The selector below adds .form-area to the chain
 * to beat Shock's rule. Tick SVG is the same path as the base .nx-checkbox
 * rule above. */
.nx-filters-page .form-area .nx-checkbox {
  border: 1.5px solid var(--nx-ink-25);
  border-radius: 4px;
  background-color: var(--nx-bg-primary);
}
.nx-filters-page .form-area .nx-checkbox:checked {
  background-color: var(--nx-accent);
  border-color: var(--nx-accent);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none'><path d='M2.5 6.2L5 8.7L9.5 3.5' stroke='%230f1423' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-size: 11px;
}
.nx-filters-page .form-check:hover { background: var(--nx-bg-soft); }
/* 14 items, 2 columns, column-major flow: position 8 is the top of column 2,
 * so it must drop its top-border to match position 1 at the top of column 1. */
.nx-filters-page .form-check:first-child,
.nx-filters-page .form-check:nth-child(8) { border-top: none; }

/* Label styling — beats Shock's .form-label .form-check-label .description.black */
.nx-filters-page .form-check label.form-label,
.nx-filters-page .form-check .tooltip-wrapper label.form-label {
  font-family: var(--nx-font-display);
  font-size: 14px;
  font-weight: 500;
  line-height: 1.3;
  color: var(--nx-text-primary);
  margin: 0;
  padding: 0;
  cursor: pointer;
  flex: 1;
}
.nx-filters-page .form-check .tooltip-wrapper {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
}

/* PDF-sub rows (.gen-pdf) — indented + muted label + grey pill background.
 * No "L" connector line — the indentation + grey pill already read as
 * "child of the row above" without a graphical connector (which was
 * stealing visual weight and clashing with the checkbox). */
.nx-filters-page .form-check.gen-pdf {
  padding-left: 44px;
  background: var(--nx-bg-soft);
  border-radius: var(--nx-radius-sm);
}
.nx-filters-page .form-check.gen-pdf label.form-label,
.nx-filters-page .form-check.gen-pdf .tooltip-wrapper label.form-label {
  font-weight: 400;
  font-size: 13.5px;
  color: var(--nx-ink-85);
}

/* Tooltip badges — the .tooltip-icon becomes a subtle "i" badge. The inner
 * <a><i class="fa-solid fa-circle-info"/></a> keeps its FAQ link + icon glyph. */
.nx-filters-page .tooltip-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  margin: 0;
  flex-shrink: 0;
  opacity: 1;
}
.nx-filters-page .tooltip-icon a {
  display: inline-flex;
  width: 100%;
  height: 100%;
  align-items: center;
  justify-content: center;
  color: var(--nx-ink-65);
  text-decoration: none;
}
.nx-filters-page .tooltip-icon a:hover { color: var(--nx-text-primary); }
.nx-filters-page .tooltip-icon i.fa-solid { font-size: 14px; line-height: 1; }

/* Tooltip popover — page-scoped override that preserves the base main.css:2897
 * visibility+opacity mechanism. We only flip the position above the icon and
 * swap colour/shadow to tokenised values. Base rule stays the sole show/hide
 * mechanism, keeping other templates' tooltips unchanged. */
.nx-filters-page .tooltip-text {
  top: auto;
  bottom: calc(100% + 8px);
  left: auto;
  right: 0;
  background: var(--nx-bg-inset);
  color: var(--nx-bg-primary);
  border-radius: var(--nx-radius-sm);
  box-shadow: var(--nx-shadow-lg);
  font-size: 12px;
  line-height: 1.45;
  padding: 10px 12px;
  max-width: 280px;
  min-width: 200px;
}
.nx-filters-page .tooltip-icon:hover + .tooltip-text,
.nx-filters-page .tooltip-icon:focus-within + .tooltip-text {
  opacity: 1;
}

/* ─── Date range card ───────────────────────────────────────────────── */
/* Break the column-major grid INSIDE the date-range card (it inherits .form-row
 * which is now a 2-col grid; for the date-range card we want the label + button
 * stacked vertically, so we reset .form-row to block inside .date-range-card). */
.nx-filters-page .date-range-card .form-area .form-row {
  display: block;
  margin: 0;
}
.nx-filters-page .date-range-card .form-col { padding: 0; }
.nx-filters-page .date-range-card .mx-2 { margin: 0; padding: 0; }

/* Card title — restyle the existing <label class="description black"> as the
 * card-title rather than a form label. */
.nx-filters-page .date-range-card label.description[for="dateRangeDisplay"] {
  display: block;
  font-family: var(--nx-font-display);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--nx-ink-65);
  margin: 0 0 var(--nx-space-3);
  padding: 0;
}
.nx-filters-page .date-range-wrapper { position: relative; }

/* Date button — outlined pill with chevron pseudo-element. */
.nx-filters-page button.date-range-display {
  width: 100%;
  padding: 14px 18px;
  border: 1px solid var(--nx-ink-10);
  border-radius: var(--nx-radius-md);
  background: var(--nx-bg-primary);
  color: var(--nx-text-primary);
  font-family: var(--nx-font-display);
  font-size: 15px;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  transition: border-color 0.15s;
}
.nx-filters-page button.date-range-display:hover { border-color: var(--nx-ink-65); }
.nx-filters-page button.date-range-display[aria-expanded="true"] { border-color: var(--nx-text-primary); }
.nx-filters-page button.date-range-display::after {
  content: "▾";
  color: var(--nx-ink-65);
  font-size: 12px;
  transition: transform 0.2s;
}
.nx-filters-page button.date-range-display[aria-expanded="true"]::after { transform: rotate(180deg); }
.nx-filters-page button.date-range-display:focus-visible {
  outline: 2px solid var(--nx-focus);
  outline-offset: 2px;
  border-color: var(--nx-focus);
}
.nx-filters-page .date-range-label {
  flex: 1;
  color: inherit;
}

/* Popover — 2-col Start | End grid with coloured section dots.
 * `min-width: 440px` so each Month select has room for the longest option
 * ("September") after chevron padding; desktop default anchors to the
 * button's left edge. Mobile falls back to single-column in the @media
 * block below, where min-width no longer applies (viewport < 640px). */
.nx-filters-page .date-range-dropdown {
  margin-top: 10px;
  padding: 22px;
  min-width: 440px;
  background: var(--nx-bg-primary);
  border: 1px solid var(--nx-ink-10);
  border-radius: var(--nx-radius-md);
  box-shadow: var(--nx-shadow-card-hover);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 26px;
}
/* Out-specify the rule above so the global [v-cloak] display:none wins during
 * the pre-Vue-hydration FOUC window. Without this, the calendar dropdown is
 * briefly visible on first load because .nx-filters-page .date-range-dropdown
 * (specificity 0,2,0) beats [v-cloak] (0,1,0) in main.css. */
.nx-filters-page .date-range-dropdown[v-cloak] {
  display: none;
}
.nx-filters-page .date-range-section h6.description {
  font-family: var(--nx-font-display);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--nx-ink-65);
  margin: 0 0 10px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.nx-filters-page .date-range-section h6.description::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--nx-accent);
}
/* Second .date-range-section in DOM order is End — darker dot. */
.nx-filters-page .date-range-section:nth-of-type(2) h6.description::before {
  background: var(--nx-bg-inset);
}
.nx-filters-page .date-range-selects {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
/* Tighter chevron padding in the popover so the longest month ("September"
 * or "February") fits in the 1fr grid column without clipping. */
.nx-filters-page .date-range-selects .nx-select {
  padding: 10px 10px;
  padding-right: 24px;
  background-position: right 8px center;
  background-size: 8px;
  font-size: 13px;
}

/* .date-range-actions spans both grid columns */
.nx-filters-page .date-range-actions {
  grid-column: 1 / -1;
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 4px;
  padding-top: 16px;
  border-top: 1px solid var(--nx-ink-05);
}

/* Popover Apply / Cancel — compact size override on the global .nx-btn
 * vocabulary. Cancel uses .nx-btn-outline tinted softer (ink-65 / ink-25)
 * so it sits below Apply in emphasis. */
.nx-filters-page .date-range-actions .nx-btn {
  padding: 7px 14px;
  font-size: 12.5px;
}
.nx-filters-page .date-range-actions .nx-btn-outline {
  color: var(--nx-ink-65);
  border-color: var(--nx-ink-25);
}
.nx-filters-page .date-range-actions .nx-btn-outline:hover {
  background: var(--nx-bg-soft);
  color: var(--nx-text-primary);
  border-color: var(--nx-text-primary);
}
.nx-filters-page .date-range-actions .nx-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ─── Price summary card ────────────────────────────────────────────── */
/* The price card inherits .form-row grid from the column-major rule above;
 * reset it to block here so the inner flex layout can run. */
.nx-filters-page .nx-price-summary .form-area { margin: 0; padding: 0; }
.nx-filters-page .nx-price-summary .form-row {
  display: block;
  margin: 0;
}
.nx-filters-page .nx-price-summary .form-col.col-lg-12 {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 20px;
  padding: 0;
}
/* Price info wrapper — the first .mx-2 contains h5#price + tooltip-wrapper(h5#attachments) */
.nx-filters-page .nx-price-summary .form-col > .mx-2:first-child {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 200px;
  flex: 1 1 auto;
  padding: 0;
  margin: 0;
}
/* Action button wrappers — subsequent .mx-2 children (Cancel then Back after the
 * scope-addition 2 relocation). Natural DOM order = Cancel on left, Back on right. */
.nx-filters-page .nx-price-summary .form-col > .mx-2 + .mx-2 {
  margin: 0;
  padding: 0;
  flex-shrink: 0;
}

/* Price typography */
.nx-filters-page .nx-price-summary h5#price,
.nx-filters-page .nx-price-summary .tooltip-wrapper h5#attachments {
  font-family: var(--nx-font-display);
  margin: 0;
  padding: 0;
}
.nx-filters-page .nx-price-summary h5#price {
  font-size: 28px;
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1;
  color: var(--nx-text-primary);
}
.nx-filters-page .nx-price-summary h5#attachments {
  font-size: 13px;
  font-weight: 400;
  color: var(--nx-ink-65);
}
.nx-filters-page .nx-price-summary .tooltip-wrapper {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* Price-card Apply / Cancel — slightly larger pill than the popover.
 * Apply uses .nx-btn-primary; Cancel uses .nx-btn-dark (dark fill →
 * accent-on-hover, mirroring the home page "Download Example" button). */
.nx-filters-page .nx-price-summary .nx-btn {
  padding: 9px 20px;
  font-size: 13.5px;
  font-weight: 500;
}

/* ===========================================================================
 * About page (Phase C.2.1 — 2026-04-20)
 *
 * Page-scoped restyle of templates/about.html. Applied under .nx-about-page
 * on the <main> element so nothing leaks into other templates. Paired with
 * the one-line .nx-sec-title :is(h1,h2) extension above — no other globally-
 * promoted components ship in this commit.
 * =======================================================================*/

/* Title mark — green underline matching the filters/status precedent
 * (.nx-filters-page h1 mark, .nx-status-head h1 mark). Transparent <mark>
 * background, 3px --nx-accent border-bottom sitting 3px below the text. */
.nx-about-page h1 mark {
  background: transparent;
  color: var(--nx-text-primary);
  padding-bottom: 3px;
  border-bottom: 3px solid var(--nx-accent);
}

/* H1 type matches the .nx-pricing-title / .nx-faq-hero hero pattern
 * (36-56px, -0.015em tracking, 1.25 line-height) rather than the
 * smaller default shared by .nx-sec-title h2s on the homepage. The
 * Instructions and Blog index pages share the same content-page hero
 * scale; selector list consolidated in D.7.3. */
.nx-about-page .nx-sec-title h1,
.nx-instructions-page .nx-sec-title h1,
.nx-blog-page .nx-sec-title h1 {
  font-size: clamp(36px, 4.6vw, 56px);
  letter-spacing: -0.015em;
  line-height: 1.25;
}

/* Swap .nx-sec-title's margin-bottom for padding-bottom so the space
 * it owns below itself does NOT margin-collapse with the following
 * <hr>'s margin-top. Without this, the 48px title margin eats the
 * 32px hr margin (adjacent siblings collapse to max), leaving only
 * 48px above the hr instead of the intended 48 + 32 = 80 that matches
 * the FAQ page's hero-to-hr gap. Padding is inside the box and does
 * not collapse. Visually equivalent for a standalone title block. */
.nx-about-page .nx-sec-title {
  margin-bottom: 0;
  padding-bottom: var(--nx-space-7);
}

/* Prose max-width override — .nx-prose ships with max-width: 70ch (~600px)
 * for blog/legal reading. About reads as marketing prose; 1000px sits
 * between the reading column and the full --nx-wrap fill (~1152px) so the
 * content column feels close to live numerint.com's .container-hosted
 * width. margin:auto centres the column inside .nx-wrap. Global .nx-prose
 * rule is untouched so blog + legal templates keep their reading column. */
.nx-about-page .nx-prose {
  max-width: 1000px;
  margin: 0 auto;
}

/* Hr — token-native rewrite of Shock's .blog-divider (main.css:2658).
 * Hairline via --nx-ink-10 reads lighter on the N.svg watermark than
 * --nx-border-subtle; matches max-width of prose above so edges align.
 *
 * Selector list grows one consumer per page-family as the restyle
 * progresses (2026-04-21: .nx-faq-page; 2026-04-23: .nx-blog-page —
 * 4th consumer). Kept as a multi-page selector list rather than
 * renamed to a generic .nx-section-divider because the consumers are
 * all hero-separator HRs; a promotion pass belongs with Phase D's
 * broader dead-CSS cleanup when the full consumer set is known.
 *
 * Margins asymmetric 16/32: the subtitle-to-HR gap is tighter than the
 * HR-to-content gap below. Above-HR gap = subtitle padding/hero
 * padding-bottom (48) + HR mt (16) = 64, below-HR gap = HR mb (32) +
 * 0 = 32. */
.nx-about-page hr,
.nx-blog-page hr,
.nx-blog-post-page hr,
.nx-faq-page hr,
.nx-filters-page hr,
.nx-instructions-page hr,
.nx-invoice-page hr,
.nx-legal-page hr,
.nx-pricing-page hr {
  border: 0;
  border-top: 1px solid var(--nx-ink-10);
  margin: var(--nx-space-4) auto var(--nx-space-6);
  max-width: 1000px;
}

/* N.svg watermark + 70vh floor — translation of Shock's .n-background
 * (theme.css:195). Image properties come from the shared N-backdrop
 * rule near the top of this file; the 70vh floor is shared across the
 * three content-page families (About, Blog index, Blog post) so the
 * watermark always reads as a section-tall atmosphere even on short
 * post stubs. Selector list earned a primitive promotion in D.7.3 after
 * the third byte-identical copy surfaced. */
.nx-about-page .nx-section,
.nx-blog-page .nx-section,
.nx-blog-post-page .nx-section {
  min-height: 70vh;
}

/* Accent bar — 28x3 --nx-accent strip above each <h2> inside .nx-prose.
 * Pure decoration; gives the five use-case sections a scannable rhythm
 * without changing copy or markup. */
.nx-about-page .nx-prose h2::before {
  content: "";
  display: block;
  width: 28px;
  height: 3px;
  background: var(--nx-accent);
  margin-bottom: 10px;
}

/* ===========================================================================
 * Prose — long-form content wrapper. Used by blog + legal templates.
 * Wrap markdown-rendered HTML in <div class="nx-prose">...</div>.
 * =======================================================================*/

.nx-prose {
  color: var(--nx-text-secondary);
  font-size: var(--nx-text-md);
  line-height: 1.6;
  max-width: 70ch;
}
.nx-prose h1 {
  font-size: var(--nx-text-2xl);
  color: var(--nx-text-primary);
  margin: var(--nx-space-6) 0 var(--nx-space-4);
}
.nx-prose h2 {
  font-size: var(--nx-text-xl);
  color: var(--nx-text-primary);
  margin: var(--nx-space-5) 0 var(--nx-space-3);
}
.nx-prose h3 {
  font-size: var(--nx-text-lg);
  color: var(--nx-text-primary);
  margin: var(--nx-space-4) 0 var(--nx-space-2);
}
.nx-prose h4 {
  font-size: var(--nx-text-md);
  color: var(--nx-text-primary);
  margin: var(--nx-space-4) 0 var(--nx-space-2);
}
.nx-prose h5 {
  font-size: var(--nx-text-lg);
  font-weight: 700;
  color: var(--nx-text-primary);
  letter-spacing: -0.005em;
  line-height: 1.3;
  margin: var(--nx-space-6) 0 var(--nx-space-3);
}
.nx-prose h6 {
  font-size: var(--nx-text-sm);
  font-weight: 700;
  color: var(--nx-text-primary);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin: var(--nx-space-4) 0 var(--nx-space-2);
}
.nx-prose p {
  margin: 0 0 var(--nx-space-4);
}
.nx-prose ul,
.nx-prose ol {
  margin: 0 0 var(--nx-space-4) var(--nx-space-5);
}
.nx-prose li {
  margin-bottom: var(--nx-space-2);
}
.nx-prose a {
  color: var(--nx-success);
  text-decoration: underline;
}
.nx-prose blockquote {
  border-left: 3px solid var(--nx-accent);
  padding-left: var(--nx-space-4);
  color: var(--nx-text-muted);
  margin: var(--nx-space-5) 0;
}
.nx-prose code {
  font-family: var(--nx-font-mono);
  background: var(--nx-bg-elevated);
  padding: 2px 6px;
  border-radius: var(--nx-radius-sm);
}
.nx-prose pre {
  background: var(--nx-bg-inset);
  color: var(--nx-bg-primary);
  padding: var(--nx-space-4);
  border-radius: var(--nx-radius-md);
  overflow-x: auto;
}
.nx-prose pre code {
  background: transparent;
  padding: 0;
  color: inherit;
}
.nx-prose img {
  max-width: 100%;
  height: auto;
  display: block;
  margin: var(--nx-space-5) auto 0;
  border-radius: var(--nx-radius-md);
}

/* ===========================================================================
 * Pricing page (Phase C.2.2 — 2026-04-20)
 *
 * Calculator-first pricing page. Three class-name families:
 *
 *   .nx-pricing-page       — page scope on <main>, backdrop + section rhythm
 *   .nx-pricing-*          — hero title block, pillars row, FAQ grid,
 *                            disclaimer (page-scoped selectors below)
 *   .nx-calc-*             — reusable calculator component: wrap, readout,
 *                            price, slider, number input, badges. Extracted
 *                            as a generic family so future surfaces can
 *                            reuse it — currently only the pricing page
 *                            consumes it.
 *
 * Companion JS: static/assets/js/pricing-calculator.js (mirrors
 * dexero_utils.BasePricing._calculate_price — any curve-parameter change
 * must be reflected in both files).
 * =======================================================================*/

.nx-pricing-page {
  background: var(--nx-bg-primary);
}

/* Stack wrapper — holds the calculator hero + the pillars on a single
 * shared atmosphere. Mirrors the homepage `.nx-trio` pattern: one
 * cover-sized N anchored at the bottom spans the whole wrapper, and
 * inner sections go transparent so the watermark reads as one
 * continuous backdrop behind the calculator and pillars. */
.nx-pricing-stack {
  position: relative;
  overflow: hidden;
  background-color: var(--nx-bg-elevated);
  background-image: url("/static/assets/images/N.svg");
  background-position: bottom;
  background-size: cover;
  background-repeat: no-repeat;
}

/* Hero — transparent so the .nx-pricing-stack watermark shows through. */
.nx-pricing-hero {
  background: transparent;
  /* Bottom is space-4 (was space-7) since the pillars now live inside
   * the hero — replacing the deleted .nx-pricing-pillars section whose
   * own bottom padding was space-4 to the FAQ. */
  padding: var(--nx-space-8) 0 var(--nx-space-4);
  position: relative;
}
.nx-pricing-hero-inner { position: relative; }

/* Title block — mirrors About page .nx-sec-title pattern. Bottom space
 * is padding (not margin) so the following <hr>'s margin-top does not
 * collapse with it — same reason as .nx-about-page .nx-sec-title above. */
.nx-pricing-title {
  text-align: center;
  margin: 0 auto;
  padding-bottom: var(--nx-space-7);
  max-width: 820px;
}
.nx-pricing-title h1 {
  font-family: var(--nx-font-display);
  font-size: clamp(36px, 4.6vw, 56px);
  font-weight: 500;
  color: var(--nx-text-primary);
  letter-spacing: -0.015em;
  /* 1.25 (up from 1.1) so when the headline wraps on mobile, the <mark>'s
   * border-bottom on line 1 sits clear of the letter tops on line 2 (the
   * original 1.1 had the underline cutting through the top of "k" in "back"). */
  line-height: 1.25;
  /* Gap to the subtitle paragraph comes from .nx-page-sub margin-top
   * (22px) — matches .nx-status-head p pattern at design-system.css:1207. */
  margin: 0 0 var(--nx-space-3);
}
.nx-pricing-title h1 mark {
  background: transparent;
  color: var(--nx-text-primary);
  padding-bottom: 3px;
  border-bottom: 3px solid var(--nx-accent);
}

/* Calc + pillars wrapper — plain block flow on mobile (calc and pillars-grid
 * each keep their own max-width + margin:auto centring, matching the previous
 * separate-section rendering exactly), flex row at ≥1024px (see @media block
 * below). Page-scoped so the homepage's standalone .nx-pillars-grid is
 * unaffected. */
.nx-pricing-page .nx-calc-pillars-row {
  max-width: 980px;
  margin: 0 auto;
}
/* Replicates the previous calc→pillars vertical gap (hero bottom space-7 +
 * pillars section top space-7 = 96px). Reset to 0 in the desktop media query. */
.nx-pricing-page .nx-calc-pillars-row .nx-pillars-grid {
  margin-top: calc(var(--nx-space-7) * 2);
}

/* Calculator component — dark centred card. Grid with pinned column so wide
 * price content cannot propagate width to the count row or slider (the
 * mobile-shift glitch observed in the mockup review). */
.nx-calc {
  background: var(--nx-bg-inset);
  color: var(--nx-bg-primary);
  border-radius: var(--nx-radius-xl);
  padding: var(--nx-space-7);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--nx-space-5);
  box-shadow: var(--nx-shadow-lg);
  position: relative;
  overflow: hidden;
  max-width: 820px;
  margin: 0 auto;
}
.nx-calc::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 4px;
  background: var(--nx-accent);
}
.nx-calc-label {
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--nx-paper-60);
  font-weight: 500;
}

.nx-calc-readout {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--nx-space-4);
  flex-wrap: wrap;
}
.nx-calc-price {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-family: var(--nx-font-display);
  line-height: 1;
}
.nx-calc-cur {
  font-size: 38px;
  color: var(--nx-paper-78);
  font-weight: 500;
}
.nx-calc-num {
  /* Fluid: at narrow viewports 96px pushes "£300.00" past the 264px-wide
   * interior on a 375px viewport. clamp glides between 52px (small mobile)
   * and 96px (≥686px). tabular-nums stabilises digit widths so the 5→6→7
   * character jumps don't visually jitter. */
  font-size: clamp(52px, 14vw, 96px);
  font-variant-numeric: tabular-nums;
  color: var(--nx-accent);
  font-weight: 500;
  letter-spacing: -0.04em;
}
.nx-calc-per {
  color: var(--nx-paper-60);
  font-size: 14px;
  letter-spacing: 0.04em;
  padding-bottom: var(--nx-space-3);
}

.nx-calc-count {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--nx-space-3) 0;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}
.nx-calc-count label {
  color: var(--nx-paper-78);
  font-size: 14px;
}
.nx-calc-count-field {
  display: flex;
  align-items: center;
  gap: var(--nx-space-2);
}
.nx-calc-count-field input[type="number"] {
  font-family: var(--nx-font-display);
  font-size: 20px;
  width: 110px;
  padding: 8px 12px;
  border: 1px solid rgba(255, 255, 255, 0.20);
  border-radius: var(--nx-radius-md);
  background: rgba(255, 255, 255, 0.06);
  color: var(--nx-bg-primary);
  text-align: center;
  /* Strip the native number-input spin buttons — replaced by .nx-calc-step
   * +/− siblings so the controls match the dark calc card aesthetic and
   * render consistently across Webkit and Firefox (the native widget is
   * stylable on Webkit only and looks ugly in white on this dark surface). */
  -moz-appearance: textfield;
  appearance: textfield;
}
.nx-calc-count-field input[type="number"]::-webkit-outer-spin-button,
.nx-calc-count-field input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.nx-calc-count-field input[type="number"]:focus-visible {
  outline: 2px solid var(--nx-accent);
  outline-offset: 1px;
}

/* Custom +/− stepper that replaces the native spinner. Click steps the
 * count by 1 (handled in pricing-calculator.js). Native arrow keys still
 * work on the input itself for keyboard step. */
.nx-calc-step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.20);
  border-radius: var(--nx-radius-md);
  background: rgba(255, 255, 255, 0.06);
  color: var(--nx-bg-primary);
  font-family: var(--nx-font-display);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
  -webkit-appearance: none;
  appearance: none;
}
.nx-calc-step:hover {
  background: rgba(255, 255, 255, 0.12);
  border-color: var(--nx-accent);
}
.nx-calc-step:active {
  background: rgba(255, 255, 255, 0.18);
}
.nx-calc-step:focus-visible {
  outline: 2px solid var(--nx-accent);
  outline-offset: 1px;
}

input[type="range"][data-calc-slider] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 6px;
  background: linear-gradient(to right, var(--nx-accent), color-mix(in srgb, var(--nx-accent) 30%, transparent) 80%, rgba(255, 255, 255, 0.1));
  border-radius: var(--nx-radius-full);
  outline: none;
}
input[type="range"][data-calc-slider]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--nx-accent);
  border: 4px solid var(--nx-bg-inset);
  cursor: pointer;
  box-shadow: 0 0 0 1px var(--nx-accent);
}
input[type="range"][data-calc-slider]::-moz-range-thumb {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--nx-accent);
  border: 4px solid var(--nx-bg-inset);
  cursor: pointer;
  box-shadow: 0 0 0 1px var(--nx-accent);
}
input[type="range"][data-calc-slider]:focus-visible::-webkit-slider-thumb {
  outline: 3px solid var(--nx-focus);
  outline-offset: 2px;
}
input[type="range"][data-calc-slider]:focus-visible::-moz-range-thumb {
  outline: 3px solid var(--nx-focus);
  outline-offset: 2px;
}
.nx-calc-hint {
  /* 3 equal columns so the middle "5,000" sits at exactly 50% of the
   * track width — flex + space-between centres each span's box, but
   * because "0" and "10,000" have very different widths the middle
   * span ended up left of the slider's actual midpoint. */
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  font-size: 11px;
  color: var(--nx-paper-60);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-top: var(--nx-space-1);
}
.nx-calc-hint > span:first-child { text-align: left; }
.nx-calc-hint > span:nth-child(2) { text-align: center; }
.nx-calc-hint > span:last-child { text-align: right; }

/* Lead line above the in-card CTA row — frames the calculator's output
 * as an estimate and signals that the next step (the green button) takes
 * the user into the Xero sign-in flow rather than calculating in place. */
.nx-calc-cta-lead {
  margin: var(--nx-space-4) 0 var(--nx-space-3);
  padding-top: var(--nx-space-4);
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  font-size: 14px;
  line-height: 1.5;
  color: var(--nx-paper-78);
  text-align: center;
}
.nx-calc-cta-lead strong {
  /* Calc card is dark, so --nx-text-primary (near-black) is invisible.
   * --nx-paper-88 lifts the strong text above the muted paper-78 lead
   * without going to pure white. */
  color: var(--nx-paper-88);
  font-weight: 600;
}

.nx-calc-badges {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--nx-space-3);
  /* Reserve exact badge height so hiding the floor badge (when price exceeds
   * the floor) does not shrink the card. */
  min-height: 36px;
}
.nx-calc-cta {
  /* In-card primary CTA. Sits on the right of .nx-calc-badges opposite
   * the floor badge so the conversion path reads "estimate (left) → take
   * action (right)". Kept on the same row so the calc card has a single
   * footer rather than a stacked "extras" pile. */
  margin-left: auto;
}
.nx-calc-badge {
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 4px 10px;
  border-radius: var(--nx-radius-full);
  font-weight: 600;
  color: var(--nx-text-primary);
  /* Keep the pill on a single line; below 440px viewport the
   * .nx-calc-badges parent switches to column flex (see media block) so
   * badge and CTA stack centred rather than the badge text wrapping
   * multi-line inside the pill. */
  white-space: nowrap;
}
.nx-calc-badge.is-floor {
  background: var(--nx-accent);
}

/* Pillars — 3-column "icon + heading + body" explanatory pattern.
 * Consumers: /pricing (soft default — pale-green rounded square),
 *            / homepage (with .is-strong modifier — full-green circle).
 * Default icon styling matches the page's .nx-kicker treatment so it
 * sits quietly next to the "PRICING" kicker. The .is-strong modifier
 * (added in Task 2 of this plan) overrides shape/colour/weight for the
 * homepage. Promotion logged in docs/decisions/log.md (2026-04-28). */
.nx-pillars-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--nx-space-5);
  list-style: none;
  padding: 0;
  margin: 0 auto;
  max-width: 980px;
}
.nx-pillar {
  text-align: center;
  padding: var(--nx-space-4) var(--nx-space-3);
  /* Override core.css's `ul > li { margin: 0.5rem 0 }` rule (which is
   * zeroed on :first-child / :last-child only). In a 3-item grid that
   * pushes the middle pillar down by 8px relative to its neighbours. */
  margin: 0;
}
.nx-pillar-icon {
  width: 56px;
  height: 56px;
  border-radius: var(--nx-radius-md);
  background: var(--nx-accent-15);
  color: var(--nx-success);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--nx-font-display);
  font-size: 22px;
  font-weight: 500;
  margin: 0 auto var(--nx-space-3);
}
/* Strong variant of .nx-pillar-icon — used on the homepage pricing
 * section. Full-green circle, dark glyph/SVG icon, weight 700. The
 * keyhole cutout in the no-subscription padlock SVG is drawn in
 * var(--nx-accent), matching this background; if this background
 * colour ever changes, update the keyhole shapes in
 * templates/index.html. */
.nx-pillar-icon.is-strong {
  border-radius: 50%;
  background: var(--nx-accent);
  color: var(--nx-text-primary);
  font-weight: 700;
}
/* Centred CTA wrapper used below the homepage pricing pillar grid.
 * Page-scoped to the homepage; the pricing page now keeps its CTA
 * inside the calculator card footer instead. */
.nx-pricing-cta-row {
  text-align: center;
  margin-top: var(--nx-space-5);
}
.nx-pillar h3 {
  font-family: var(--nx-font-display);
  font-size: 18px;
  font-weight: 500;
  color: var(--nx-text-primary);
  margin: 0 0 var(--nx-space-2);
}
.nx-pillar p {
  font-size: 14px;
  color: var(--nx-ink-65);
  line-height: 1.55;
  margin: 0 auto;
  max-width: 280px;
}

/* FAQ grid — "Pricing plainly" */
.nx-pricing-faq {
  /* Transparent so the .nx-pricing-stack watermark spans the FAQ section
   * too. The previous bg-soft + hairline divider read as a separate
   * surface; the unified stack atmosphere makes them redundant. */
  background: transparent;
  padding: var(--nx-space-8) 0 var(--nx-space-6);
}
.nx-pricing-faq h2 {
  text-align: center;
  font-family: var(--nx-font-display);
  font-size: 32px;
  font-weight: 500;
  color: var(--nx-text-primary);
  margin: 0 0 var(--nx-space-6);
  letter-spacing: -0.01em;
}
.nx-pricing-faq h2 mark {
  background: transparent;
  color: var(--nx-text-primary);
  padding-bottom: 3px;
  border-bottom: 3px solid var(--nx-accent);
}
.nx-pricing-faq-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--nx-space-4);
  max-width: 960px;
  margin: 0 auto;
}
.nx-pricing-faq-item {
  background: var(--nx-bg-primary);
  border: 1px solid var(--nx-ink-05);
  border-radius: var(--nx-radius-lg);
  padding: var(--nx-space-5);
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--nx-space-3);
}
.nx-pricing-faq-mark {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--nx-accent-15);
  color: var(--nx-success);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--nx-font-display);
  font-size: 17px;
  font-weight: 500;
}
.nx-pricing-faq-item h3 {
  font-family: var(--nx-font-display);
  font-size: 16px;
  font-weight: 500;
  color: var(--nx-text-primary);
  margin: 0 0 var(--nx-space-1);
}
.nx-pricing-faq-item p {
  font-size: 14px;
  color: var(--nx-ink-65);
  line-height: 1.55;
  margin: 0;
}
.nx-pricing-faq-item p a {
  color: var(--nx-success);
  text-decoration: underline;
}

/* Disclaimer */
.nx-pricing-disclaimer {
  padding: var(--nx-space-5) 0 var(--nx-space-7);
  background: transparent;
}
.nx-pricing-disclaimer ul {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--nx-space-2);
  max-width: 780px;
  margin: 0 auto;
  text-align: center;
  color: var(--nx-ink-65);
  font-size: 14px;
  line-height: 1.6;
}
.nx-pricing-disclaimer a {
  color: var(--nx-success);
  text-decoration: underline;
}

/* Responsive — at ≥1024px the calc + pillars sit side-by-side (calc on
 * the left at 620px, pillars stacked vertically on the right). Below
 * 1024px they stack (calc above, pillars below — the pre-restructure
 * visual). Pillars + FAQ collapse to single column under 960px. The
 * calculator card responds via the clamp() on .nx-calc-num + the existing
 * grid column pin. Under 520px, the readout stacks vertically. */
@media (min-width: 1024px) {
  .nx-pricing-page .nx-calc-pillars-row {
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    gap: var(--nx-space-6);
  }
  .nx-pricing-page .nx-calc-pillars-row .nx-calc {
    max-width: 620px;
    margin: 0;
    flex: 0 1 620px;
  }
  /* Stack the 3 pillars vertically inside the row, scoped via the new
   * wrapper so the homepage's standalone .nx-pillars-grid stays 3-col.
   * margin: 0 also resets the mobile margin-top that gives the calc→pillars
   * vertical gap. */
  .nx-pricing-page .nx-calc-pillars-row .nx-pillars-grid {
    grid-template-columns: 1fr;
    flex: 1 1 auto;
    margin: 0;
  }
}
@media (max-width: 960px) {
  .nx-pillars-grid { grid-template-columns: 1fr; gap: var(--nx-space-4); }
  .nx-pricing-faq-grid { grid-template-columns: 1fr; }
}
@media (max-width: 520px) {
  .nx-calc { padding: var(--nx-space-5); }
  .nx-calc-cur { font-size: 30px; }
  .nx-calc-readout {
    flex-direction: column;
    align-items: flex-start;
  }
}
/* Calc card footer (floor badge + CTA): the row layout starts overflowing
 * the calc inner width below ~438px viewport — stack and centre instead so
 * each gets its own line cleanly rather than offsetting left/right. */
@media (max-width: 440px) {
  .nx-calc-badges {
    flex-direction: column;
    align-items: center;
  }
  /* Override the row-mode margin-left: auto so the CTA centres in column
   * mode rather than sticking to the right edge of the card. */
  .nx-calc-cta {
    margin: 0;
  }
}

/* ===========================================================================
 * Responsive breakpoints — desktop-first collapses
 * =======================================================================*/

@media (max-width: 1040px) {
  .nx-footer-top {
    grid-template-columns: 1.2fr 1fr 1fr 1fr;
    gap: 36px;
  }
  .nx-footer-brand {
    grid-column: 1 / -1;
    margin-top: 20px;
    padding-top: 30px;
    border-top: 1px solid rgba(255, 255, 255, 0.08);
    display: flex;
    gap: 24px;
    align-items: center;
    flex-wrap: wrap;
  }
  .nx-footer-brand img { margin-bottom: 0; }
  .nx-footer-brand .nx-footer-tag { margin-bottom: 0; }
  .nx-footer-brand .nx-footer-copy { margin-left: auto; }
}

@media (max-width: 960px) {
  .nx-about-grid,
  .nx-quote-wrap { grid-template-columns: 1fr; }
  .nx-features-grid { grid-template-columns: 1fr 1fr; }
  .nx-trust-pair { grid-template-columns: 1fr; }
  .nx-footer-top { grid-template-columns: 1fr 1fr; }
  .nx-footer-brand { grid-column: 1 / -1; }
  .nx-quote-wrap img { max-width: 260px; }
  .nx-table-head { flex-direction: column; align-items: flex-start; }
}

/* Nav overrides are keyed to Bootstrap's navbar-expand-lg collapse breakpoint
 * (991.98px), not the design-system 960px breakpoint — otherwise between
 * 961–991px the nav would collapse via Bootstrap but keep its desktop styles.
 * Match the original Shock mobile dropdown: no padding on the list, 8px 0 on
 * each link, items sit at the container edge (~16px from the viewport). */
@media (max-width: 991.98px) {
  .nx-nav .navbar-nav {
    margin-left: 0;
    align-items: flex-start;
    padding: 0;
    gap: 0;
  }
  .shock-header .navbar.nx-nav .navbar-nav .nav-link {
    padding: 8px 0;
    margin: 0;
  }
  .nx-nav .nav-link::after { display: none; }
}

@media (max-width: 820px) {
  .nx-status-notes-grid { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 640px) {
  .nx-status-card-inner { padding: 18px 20px 18px 24px; }
  .nx-status-card-top { gap: var(--nx-space-3); }
  .nx-status-tenant-block { min-width: 100%; }
  .nx-status-tenant-name { font-size: 18px; }
  .nx-status-metrics { gap: var(--nx-space-4); flex-wrap: wrap; }
  .nx-status-card-bottom { align-items: flex-start; }
  .nx-status-card-secondary { width: 100%; justify-content: flex-end; margin-top: 8px; }
  .nx-status-notes-grid { grid-template-columns: 1fr; }

  /* Sticky dock — fill the viewport edge-to-edge with a small inset, tighten
   * padding, and shrink the cluster gap so the count + terms + CTA still
   * fit on a phone screen without forcing the CTA to its own line. */
  .nx-status-dock {
    width: calc(100% - 1rem);
    bottom: calc(0.5rem + env(safe-area-inset-bottom));
    padding: 10px 14px;
    gap: var(--nx-space-3);
  }
  .nx-status-dock-cluster { gap: var(--nx-space-3); }
  .nx-status-dock-count { font-size: 12px; }
  .nx-status-dock-terms { font-size: 12px; }
  .nx-status-dock-cta.nx-btn { padding: 8px 14px; font-size: 13px; }

  /* Filters page — single-column checkbox list on mobile, restore border on
   * position 8 (which is no longer top-of-column-2 in row-major flow). */
  .nx-filters-page .form-row {
    grid-template-columns: 1fr;
    grid-template-rows: auto;
    grid-auto-flow: row;
    column-gap: 0;
  }
  .nx-filters-page .form-check:nth-child(8) { border-top: 1px solid var(--nx-ink-05); }

  /* Date range popover collapses to single column on mobile and drops the
   * 440px desktop floor so it cannot overflow narrow viewports — width
   * tracks the date button's wrapper instead. */
  .nx-filters-page .date-range-dropdown {
    grid-template-columns: 1fr;
    gap: 18px;
    min-width: 0;
    width: 100%;
  }

  /* Price summary on mobile — price info on its own row, Cancel + Back share
   * the row below side-by-side. align-items: center on the price column keeps
   * the price text horizontally centred (matches the desktop "centred" feel
   * the user wants preserved on mobile). */
  .nx-filters-page .nx-price-summary .form-col.col-lg-12 {
    flex-wrap: wrap;
    justify-content: center;
  }
  .nx-filters-page .nx-price-summary .form-col > .mx-2:first-child {
    flex: 1 1 100%;
    align-items: center;
  }
}

@media (max-width: 560px) {
  .nx-features-grid,
  .nx-footer-top { grid-template-columns: 1fr; }
  .nx-table th,
  .nx-table td { padding: 12px 10px; }
  .nx-table thead th {
    letter-spacing: 0.08em;
    font-size: 10.5px;
  }
  .nx-table-head { padding: 16px 20px; }
  .nx-table-head h3 { font-size: 17px; }
}

/* ===========================================================================
 * FAQ page — Phase C.2.3
 *
 * PROMOTED 2026-04 (Phase C.6): .nx-toc, .nx-toc-label,
 * .nx-toc-link, .nx-toc-grid, .nx-toc-section, .nx-toc-section-head,
 * .nx-toc-top-link renamed to .nx-toc, .nx-toc-label, .nx-toc-link,
 * .nx-toc-grid, .nx-toc-section, .nx-toc-section-head, .nx-toc-top-link.
 * Second consumer is templates/legal_base.html (see
 * docs/decisions/log.md 2026-04-26). FAQ disclosure cluster
 * (.nx-faq-disclosure, .nx-faq-toggle, .nx-faq-panel*) stays FAQ-scoped.
 *
 * Page-scoped restyle of templates/faq.html. Page-shell rules prefixed
 * with .nx-faq-page so nothing leaks into other templates. Defer-until-
 * second-consumer rule applied for the disclosure cluster (see
 * docs/decisions/log.md 2026-04-21).
 *
 * Companion JS: static/assets/js/faq.js (disclosure toggle + scroll-spy +
 * hash-based auto-open). Deep-link IDs preserved verbatim from the
 * pre-refactor template: collapse-trigger-{section}-{N} on the <button>,
 * collapse-{section}-{N} on the panel <div>.
 * =======================================================================*/

.nx-faq-page {
  padding-bottom: var(--nx-space-8);
}

/* Hero — mirrors the .nx-pricing-hero pattern (kicker + <mark>-underlined
 * h1 + subtitle) with an N.svg watermark at 70% opacity-0.06. */
.nx-faq-hero {
  padding: var(--nx-space-8) 0 var(--nx-space-7);
  position: relative;
  overflow: hidden;
  text-align: center;
}
.nx-faq-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("/static/assets/images/N.svg");
  background-position: center bottom;
  background-size: 70%;
  background-repeat: no-repeat;
  opacity: 0.06;
  pointer-events: none;
}
.nx-faq-hero-inner {
  position: relative;
  max-width: 820px;
  margin: 0 auto;
  padding: 0 var(--nx-space-5);
}
.nx-faq-hero h1 {
  font-family: var(--nx-font-display);
  font-size: clamp(36px, 4.6vw, 56px);
  font-weight: 500;
  color: var(--nx-text-primary);
  letter-spacing: -0.015em;
  /* 1.25 so wrapped headlines don't collide with the line-1 <mark>
   * border-bottom on line 2 letter tops. Matches the .nx-pricing-title h1
   * rationale at line ~2503. */
  line-height: 1.25;
  margin: 0 0 var(--nx-space-3);
}
.nx-faq-hero h1 mark {
  background: transparent;
  color: var(--nx-text-primary);
  padding-bottom: 3px;
  border-bottom: 3px solid var(--nx-accent);
}

/* Page-scoped wrap: slightly narrower than the global 1200px so the FAQ
 * body reads with more horizontal breathing on wide viewports. The hero
 * stays full-bleed; only the body grid tightens. */
.nx-faq-page .nx-wrap { max-width: 1120px; }

/* Body grid + sticky sidebar ToC.
 * Sticky offsets: 100px = 68px fixed-nav + 32px breathing. Used for the
 * sticky ToC `top` and for `.nx-toc-section scroll-margin-top` (native
 * anchor scrolling from the ToC). The JS hash-scroll for #collapse-*
 * links in faq.js uses 120px instead — arriving from an external icon
 * deserves more visual breathing above the targeted question than a
 * within-page section jump, where the user already has scroll context. */
.nx-faq-body { padding: 0 var(--nx-space-5) var(--nx-space-7); }
.nx-toc-grid {
  display: grid;
  grid-template-columns: 240px 1fr;
  gap: var(--nx-space-7);
  align-items: start;
}
.nx-toc { position: sticky; top: 100px; }
.nx-toc-label {
  display: block;
  font-family: var(--nx-font-display);
  font-size: var(--nx-text-xs);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--nx-success);
  font-weight: 500;
  margin-bottom: var(--nx-space-3);
}
.nx-toc ul { list-style: none; padding: 0; margin: 0; }
.nx-toc li { margin-bottom: var(--nx-space-2); }
.nx-toc-link {
  display: block;
  padding: 8px 12px;
  border-radius: var(--nx-radius-md);
  color: var(--nx-ink-65);
  font-size: var(--nx-text-sm);
  font-weight: 500;
  text-decoration: none;
  border-left: 2px solid transparent;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.nx-toc-link:hover {
  color: var(--nx-text-primary);
  background: var(--nx-bg-elevated);
}
.nx-toc-link.is-active {
  color: var(--nx-text-primary);
  background: var(--nx-accent-15);
  border-left-color: var(--nx-accent);
}

/* Section header + per-section back-to-top. */
.nx-toc-section { scroll-margin-top: 100px; }
.nx-toc-section + .nx-toc-section { margin-top: var(--nx-space-7); }
.nx-toc-section-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding-bottom: var(--nx-space-3);
  border-bottom: 1px solid var(--nx-ink-10);
  margin-bottom: var(--nx-space-4);
}
/* Generic — margin: 0 across any TOC consumer. FAQ-specific font sizing
 * lives in the page-scoped rule below so legal pages (h5 inside the
 * section head) get .nx-prose typography rather than FAQ's xl h2. */
.nx-toc-section-head :is(h2, h3, h4, h5, h6) { margin: 0; }
.nx-faq-page .nx-toc-section-head h2 {
  font-family: var(--nx-font-display);
  font-size: var(--nx-text-xl);
  font-weight: 500;
  color: var(--nx-text-primary);
  letter-spacing: -0.01em;
}
/* Section-head scope bumps specificity to 0,2,0 so the rule beats the
 * generic .nx-prose a (0,1,1). Legal wraps section-heads in .nx-prose;
 * FAQ doesn't, but the section-head ancestor is always present, so a
 * single rule covers both consumers without a page-scoped override.
 * flex-shrink + white-space match the mockup spec — keep "Back to top ↑"
 * on one line at narrow widths; the heading wraps instead of the link. */
.nx-toc-section-head .nx-toc-top-link {
  font-size: var(--nx-text-sm);
  color: var(--nx-ink-65);
  text-decoration: none;
  font-weight: 500;
  flex-shrink: 0;
  white-space: nowrap;
}
.nx-toc-section-head .nx-toc-top-link:hover { color: var(--nx-text-primary); }

/* Disclosure — vanilla <button aria-expanded> + grid-rows panel.
 * No Bootstrap dependency. APG Disclosure pattern (not Accordion): the
 * panel carries aria-labelledby pointing to the trigger button but NO
 * role="region" (33 disclosures would otherwise flood the landmarks
 * list — see critique amendment in spec commit 3d18e23). */
.nx-faq-disclosure {
  border-bottom: 1px solid var(--nx-ink-10);
}
.nx-faq-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--nx-space-4);
  padding: var(--nx-space-4) 0;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 0;
  cursor: pointer;
  font: inherit;
  color: inherit;
  /* 120px is the authoritative deep-link landing offset for this page.
   * The browser's native anchor-scroll on /faq#collapse-trigger-... uses
   * this; the JS in faq.js pairs it with scrollIntoView so in-page
   * hashchange lands identically. Larger than the 100px sticky-nav
   * offset on purpose — see the `.nx-faq-body` comment above. */
  scroll-margin-top: 120px;
}
.nx-faq-toggle:focus-visible {
  outline: 2px solid var(--nx-focus);
  outline-offset: 2px;
  border-radius: var(--nx-radius-sm);
}
.nx-faq-question {
  font-family: var(--nx-font-display);
  font-size: var(--nx-text-md);
  font-weight: 500;
  color: var(--nx-text-primary);
  margin: 0;
  line-height: 1.4;
}
/* `.nx-faq-question` wraps the toggle (h3 > button) for spec-conformant
 * heading-in-phrasing-content. Expanded-state colour lives on the button
 * and cascades into the text span via `color: inherit`. */
.nx-faq-toggle[aria-expanded="true"] { color: var(--nx-success); }

/* Plus-to-minus icon: ::before is the horizontal bar (always visible);
 * ::after is the vertical bar that fades out when expanded, leaving
 * just the horizontal as a minus sign. Matches the pre-refactor
 * faq.html UX and the APG convention for expand/collapse indicators. */
.nx-faq-icon {
  flex-shrink: 0;
  width: 22px; height: 22px;
  position: relative;
  color: var(--nx-accent);
}
.nx-faq-icon::before,
.nx-faq-icon::after {
  content: "";
  position: absolute;
  background: currentColor;
  border-radius: 1px;
}
.nx-faq-icon::before { top: 50%; left: 0; width: 100%; height: 2px; transform: translateY(-50%); }
.nx-faq-icon::after  {
  left: 50%; top: 0; height: 100%; width: 2px;
  transform: translateX(-50%);
  transition: transform 0.25s ease, opacity 0.25s ease;
}
.nx-faq-toggle[aria-expanded="true"] .nx-faq-icon::after {
  transform: translateX(-50%) scaleY(0.3);
  opacity: 0;
}

/* Grid-rows trick: animates 0fr → 1fr between closed and open. Inner
 * wrapper with overflow:hidden clips content during collapse. Browsers
 * older than Chrome 117 / Safari 17.2 / Firefox 119 get an instant snap
 * instead of animation — no broken behaviour, just less polish.
 *
 * `visibility: hidden` on the inner wrapper is the a11y contract: clipped
 * content (answer links, mailtos, hash anchors) must stay out of the tab
 * order and the accessibility tree while the disclosure is closed.
 * Transitioning `visibility` with a delay equal to the grid-rows transition
 * keeps the content visible during the closing animation, then hides it
 * at the end; on open it flips to visible immediately so focus can reach
 * it as the panel expands. Matches the pre-refactor Bootstrap `.collapse`
 * behaviour, which used `display: none` when closed. */
.nx-faq-panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.28s ease;
}
.nx-faq-panel-inner {
  overflow: hidden;
  visibility: hidden;
  transition: visibility 0s linear 0.28s;
}
.nx-faq-disclosure.is-open .nx-faq-panel { grid-template-rows: 1fr; }
.nx-faq-disclosure.is-open .nx-faq-panel-inner {
  visibility: visible;
  transition: visibility 0s linear 0s;
}

.nx-faq-answer {
  padding: 0 0 var(--nx-space-5);
  color: var(--nx-ink-65);
  font-size: var(--nx-text-md);
  line-height: 1.6;
  max-width: 70ch;
}
.nx-faq-answer p { margin: 0 0 var(--nx-space-3); }
.nx-faq-answer p:last-child { margin-bottom: 0; }
.nx-faq-answer a { color: var(--nx-success); text-decoration: underline; }
.nx-faq-answer strong { color: var(--nx-text-primary); font-weight: 600; }

/* Responsive — sidebar collapses below content on narrow viewports. */
@media (max-width: 767px) {
  .nx-toc-grid { grid-template-columns: 1fr; }
  .nx-toc { position: static; margin-bottom: var(--nx-space-5); }
}


/* ===========================================================================
 * Instructions page — Phase C.2.4
 *
 * Page-scoped restyle of templates/instructions.html. All rules prefixed
 * with .nx-instructions-page so nothing leaks into other templates.
 * Layout: About-style .nx-sec-title hero → hr → condensed .nx-prose intro
 * → sticky-rail + scrolly-viewport body grid (220px rail + 11 step cards).
 *
 * Class names are promotion-ready
 * (.nx-instructions-rail, .nx-instructions-step, .nx-step-num — the latter
 * page-scoped under .nx-instructions-page so it cannot collide with any
 * future homepage/pricing consumer). Defer-until-second-consumer rule
 * applied: no promotions in this commit. See docs/decisions/log.md
 * 2026-04-21 entries ("Defer .nx-page-mark promotion" +
 * "Defer .nx-sidebar-toc promotion").
 *
 * Reused primitives (unchanged): .nx-sec-title, .nx-kicker, .nx-page-sub,
 * .nx-wrap, .nx-section, .nx-prose. The multi-page `hr` selector above
 * extends to .nx-instructions-page (third consumer — still below the
 * promotion threshold to .nx-section-divider).
 *
 * A11y contract (paired with static/assets/js/instructions.js):
 *   — <nav aria-label="Steps"> wraps the rail.
 *   — Active rail link carries .is-active AND aria-current="step".
 *   — prefers-reduced-motion gates the smooth-scroll declaration below.
 *   — <article> cards carry scroll-margin-top: 100px so hash navigation
 *     lands them clear of the fixed 68px nav + 32px breathing.
 * =======================================================================*/

.nx-instructions-page {
  padding-bottom: var(--nx-space-8);
}

/* Hero — About pattern. <mark> gets the same 3px accent border-bottom
 * About uses. No N.svg watermark (dropped during Phase 1 review). */
.nx-instructions-page h1 mark {
  background: transparent;
  color: var(--nx-text-primary);
  padding-bottom: 3px;
  border-bottom: 3px solid var(--nx-accent);
}
.nx-instructions-page .nx-sec-title {
  margin-bottom: 0;
  padding-bottom: var(--nx-space-7);
}

/* Intro prose — a single column, wider than .nx-prose's default 70ch so
 * the marketing prose fills the About-ish column. 900px sits between
 * .nx-prose's 70ch (~600px) default and the full nx-wrap (1200px). */
.nx-instructions-intro {
  max-width: 900px;
  margin: 0 auto;
}

/* Body grid — 220px sticky rail + 1fr viewport. Gap smaller than the
 * .nx-toc-grid 48px so the rail feels tethered to the viewport. */
.nx-instructions-page .nx-section-body {
  padding-top: 0;
  padding-bottom: var(--nx-space-7);
}
.nx-instructions-grid {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: var(--nx-space-5);
  align-items: start;
}

/* Rail — sticky sidebar ToC with numbered dots on a vertical line.
 * Visually distinct from .nx-toc (flat list with left-border accent)
 * — page-scope both until a third consumer earns a shared primitive.
 * Sticky offset 100px matches .nx-toc (68px fixed-nav + 32px
 * breathing). */
.nx-instructions-rail {
  position: sticky;
  top: 100px;
  align-self: start;
  max-height: calc(100vh - 120px);
  overflow: auto;
  padding-right: var(--nx-space-3);
}
.nx-instructions-rail-label {
  display: block;
  font-family: var(--nx-font-display);
  font-size: var(--nx-text-xs);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--nx-success);
  font-weight: 500;
  margin-bottom: var(--nx-space-3);
}
.nx-instructions-rail ol {
  list-style: none;
  padding: 0;
  margin: 0;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
/* Vertical line running under the dots. Trimmed top/bottom by 12px so
 * the line doesn't protrude past the first/last dot's centre. */
.nx-instructions-rail ol::before {
  content: "";
  position: absolute;
  left: 18px;
  top: 12px;
  bottom: 12px;
  width: 2px;
  background: var(--nx-ink-10);
}
.nx-instructions-rail a {
  display: flex;
  gap: 10px;
  align-items: center;
  position: relative;
  padding: 10px 12px 10px 40px;
  border-radius: var(--nx-radius-md);
  font-family: var(--nx-font-display);
  font-size: var(--nx-text-sm);
  color: var(--nx-ink-65);
  font-weight: 500;
  text-decoration: none;
  transition: background 0.15s ease, color 0.15s ease;
}
.nx-instructions-rail a::before {
  content: "";
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--nx-bg-primary);
  border: 2px solid var(--nx-border-subtle);
  box-sizing: border-box;
  z-index: 1;
  transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.nx-instructions-rail a:hover {
  background: var(--nx-bg-elevated);
  color: var(--nx-text-primary);
}
.nx-instructions-rail a.is-active {
  color: var(--nx-text-primary);
  background: var(--nx-accent-15);
  font-weight: 500;
}
.nx-instructions-rail a.is-active::before {
  background: var(--nx-accent);
  border-color: var(--nx-accent);
  box-shadow: 0 0 0 3px var(--nx-accent-soft);
}
.nx-instructions-rail a:focus-visible {
  outline: 2px solid var(--nx-focus);
  outline-offset: 2px;
}
.nx-instructions-rail .num {
  font-family: var(--nx-font-mono);
  font-weight: 500;
  color: inherit;
}

/* Viewport — vertical stack of 11 step articles. Each is scroll-margined
 * so hash navigation + scroll-spy land cards clear of the nav. */
.nx-instructions-viewport {
  display: flex;
  flex-direction: column;
  gap: var(--nx-space-7);
}
.nx-instructions-step {
  background: var(--nx-bg-primary);
  border: 1px solid var(--nx-ink-10);
  border-radius: var(--nx-radius-lg);
  padding: var(--nx-space-7);
  box-shadow: var(--nx-shadow-card);
  scroll-margin-top: 100px;
}
.nx-instructions-page .nx-step-num {
  display: inline-block;
  font-family: var(--nx-font-display);
  font-size: var(--nx-text-xs);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--nx-success);
  font-weight: 500;
  margin-bottom: var(--nx-space-3);
}
.nx-instructions-step h2 {
  font-family: var(--nx-font-display);
  font-size: 28px;
  font-weight: 500;
  color: var(--nx-text-primary);
  letter-spacing: -0.01em;
  line-height: 1.25;
  margin: 0 0 var(--nx-space-3);
}
.nx-instructions-step p {
  font-size: 16px;
  line-height: 1.6;
  color: var(--nx-text-secondary);
  margin: 0 0 var(--nx-space-5);
  max-width: 72ch;
}
.nx-instructions-step p a {
  color: var(--nx-success);
  text-decoration: underline;
}
.nx-instructions-step p a:hover {
  text-decoration-thickness: 2px;
}
.nx-instructions-step figure {
  margin: 0;
  background: var(--nx-bg-elevated);
  border: 1px solid var(--nx-border-subtle);
  border-radius: var(--nx-radius-md);
  overflow: hidden;
  box-shadow: var(--nx-shadow-sm);
}
.nx-instructions-step figure img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  object-position: top;
}

/* Smooth-scroll — CSS-only, guarded by reduced-motion. Native anchor
 * scroll-behavior is instant by default; this opts in to smooth for
 * users who have not explicitly disabled motion. JS does NOT call
 * scrollIntoView anywhere (instructions.js is scroll-spy only). */
@media (prefers-reduced-motion: no-preference) {
  .nx-instructions-page {
    scroll-behavior: smooth;
  }
}

/* Responsive — below 960px the rail hides entirely (Phase 2 critique 1).
 * Steps scroll sequentially; no jump-nav on mobile. */
@media (max-width: 960px) {
  .nx-instructions-grid {
    grid-template-columns: 1fr;
    gap: 0;
  }
  .nx-instructions-rail {
    display: none;
  }
  .nx-instructions-step {
    padding: var(--nx-space-5);
  }
  .nx-instructions-step h2 {
    font-size: 22px;
  }
  .nx-instructions-step p {
    font-size: 15px;
  }
}

/* ===========================================================================
 * Message pages (D.3b/c — unified hero-only state-keyed shape) —
 * page-scoped restyle for templates/checkout_success.html,
 * templates/checkout_failed.html, templates/checkout_cancel.html, and
 * templates/error.html. All four share the same hero shape; only the
 * kicker colour, <mark> underline colour, and optional support-reference
 * tail vary by state.
 *
 * State modifiers: --{success,failed,cancel,notfound,server,login}.
 * Merged from .nx-checkout-page (Phase C.3) + .nx-error-page (Phase C.7).
 * =======================================================================*/

.nx-message-page {
  display: flex;
  flex-direction: column;
}

.nx-message-page .nx-section {
  /* Image properties come from the shared N-backdrop rule near the top
   * of this file; only the flex stretch-and-centre behaviour is
   * page-specific here. */
  flex: 1 0 auto;
  display: flex;
  align-items: center;
}

.nx-message-wrap {
  max-width: 640px;
  margin-inline: auto;
  text-align: center;
  padding: var(--nx-space-7) var(--nx-space-4);
}

.nx-message-page h1 {
  margin: var(--nx-space-3) 0;
  color: var(--nx-text-primary); /* Shock's body color is --gray-color (#f2f2f2) = same as --nx-bg-elevated, so h1 inherits invisible. */
}

.nx-message-page h1 mark {
  background: transparent;
  color: inherit;
  border-bottom: 3px solid;
  padding-bottom: 2px;
}

.nx-message-page .nx-page-sub { margin-bottom: var(--nx-space-5); }
.nx-message-page .nx-btn      { margin-top: var(--nx-space-2); }

/* State modifiers — kicker text colour + <mark> underline colour. */

.nx-message-page--success .nx-kicker { color: var(--nx-success); }
.nx-message-page--success h1 mark    { border-bottom-color: var(--nx-accent); }

/* failed (checkout) + server (error) share the AA-safe danger tone.
 * --nx-danger-text computes 6.33:1 on --nx-bg-elevated; decorative
 * --nx-danger (4.5:1 fails) keeps the original hue for the 3px underline. */
.nx-message-page--failed  .nx-kicker,
.nx-message-page--server  .nx-kicker { color: var(--nx-danger-text); }
.nx-message-page--failed  h1 mark,
.nx-message-page--server  h1 mark    { border-bottom-color: var(--nx-danger); }

/* cancel (checkout) + notfound + login (error) share info-blue. */
.nx-message-page--cancel   .nx-kicker,
.nx-message-page--notfound .nx-kicker,
.nx-message-page--login    .nx-kicker { color: var(--nx-info); }
.nx-message-page--cancel   h1 mark,
.nx-message-page--notfound h1 mark,
.nx-message-page--login    h1 mark    { border-bottom-color: var(--nx-info); }

/* Optional support-reference paragraph — separate paragraph below the CTA
 * on error variants (renders only when the payload includes a reference
 * UUID; server + login). The failed checkout uses inline <code> within
 * .nx-page-sub instead — both consumers share the .nx-message-page code
 * styling below. */
.nx-message-ref {
  margin-top: var(--nx-space-5);
  color: var(--nx-text-muted);
  font-size: 0.9375rem;
}

/* Inline support-reference <code> pill — used by failed checkout (inline
 * within .nx-page-sub) and by error variants (inside .nx-message-ref).
 * Monospace + elevated background + nowrap so the reference renders as a
 * copy-paste-friendly token. Bootstrap reboot supplies the foreground
 * colour (--bs-code-color). */
.nx-message-page code {
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  background: var(--nx-bg-elevated);
  padding: 2px 6px;
  border-radius: 4px;
  white-space: nowrap;
}

/* ===========================================================================
 * Housekeeping page (C.4.1) — page-scoped restyle of templates/housekeeping.html.
 * Single-column hero + rounded fieldset card wrapping three .nx-radio-row
 * entries + action bar outside the card on the page's N.svg backdrop.
 *
 * Sticky-footer behaviour inherited from body.nx-body (lines 247–257) —
 * do NOT set an explicit min-height on this page or the footer renders
 * below the viewport on short content.
 * =======================================================================*/

.nx-housekeeping-page {
  background-color: var(--nx-bg-elevated);
  /* Image properties come from the shared N-backdrop rule near the top. */
}

/* App-page section frame — top/bottom rhythm with a transparent fill so
 * the page-level N-backdrop on .nx-housekeeping-page / .nx-invoice-page
 * shows through. Selector list consolidated in D.7.3. */
.nx-housekeeping-page .nx-section,
.nx-invoice-page .nx-section {
  padding: var(--nx-space-8) 0 var(--nx-space-7);
  background: transparent;
}

.nx-housekeeping-wrap {
  max-width: 760px;
  margin-inline: auto;
  padding: 0 var(--nx-space-5);
}

/* Hero — sized for a long wrapped h1. Clamp ceiling (38px) below filters'
 * 44px so "How would you like to disconnect?" renders on one line at
 * desktop widths while still wrapping gracefully on narrow viewports.
 * Invoice shares the same app-page hero scale so its long copy
 * ("Who's this invoice for?") wraps comfortably at the same ceiling;
 * selector list consolidated in D.7.3. */
.nx-housekeeping-page .nx-sec-title {
  margin-bottom: var(--nx-space-6);
}
.nx-housekeeping-page .nx-sec-title h1,
.nx-invoice-page .nx-sec-title h1 {
  font-family: var(--nx-font-display);
  font-size: clamp(26px, 3.2vw, 38px);
  font-weight: 500;
  color: var(--nx-text-primary);
  letter-spacing: -0.01em;
  line-height: 1.25;
  margin: 0 0 var(--nx-space-3);
}
.nx-housekeeping-page .nx-sec-title h1 mark {
  background: transparent;
  color: var(--nx-text-primary);
  padding-bottom: 3px;
  border-bottom: 3px solid var(--nx-accent);
}

/* Fieldset card shell — lives on the <fieldset>, not the <form>, so the
 * action bar below sits outside the card on the page's N.svg backdrop
 * (matches brainstorm Variant A's framing). */
.nx-housekeeping-fieldset {
  border: 1px solid var(--nx-ink-10);
  border-radius: var(--nx-radius-lg);
  background: var(--nx-bg-primary);
  overflow: hidden;
  margin: 0;
  padding: 0;
}

/* Radio row — wrapper <div> containing a <label> (radio + title + desc)
 * and a sibling <a class="nx-info-link">. Info-icon click does NOT
 * propagate into the radio because it lives outside the label. */
.nx-housekeeping-page .nx-radio-row {
  position: relative;
  display: flex;
  align-items: flex-start;
  border-bottom: 1px solid var(--nx-ink-10);
  transition: background 0.15s;
}
.nx-housekeeping-page .nx-radio-row:last-of-type { border-bottom: none; }
.nx-housekeeping-page .nx-radio-row:hover { background: var(--nx-bg-soft); }
/* :has() first-use in this stylesheet. Pre-Chrome 105 / Firefox 121 /
 * Safari 15.4 silently drop this rule, so the selected row gets no tint.
 * Radio selection and form submission are unaffected — degradation is
 * cosmetic only. */
.nx-housekeeping-page .nx-radio-row:has(.nx-radio:checked) {
  background: var(--nx-accent-15);
}

.nx-housekeeping-page .nx-radio-row__label {
  flex-grow: 1;
  display: flex;
  align-items: flex-start;
  gap: var(--nx-space-4);
  padding: var(--nx-space-5);
  cursor: pointer;
  min-width: 0;
}
.nx-housekeeping-page .nx-radio-row__label .nx-radio { margin-top: 2px; }

.nx-housekeeping-page .nx-radio-row__body {
  display: flex;
  flex-direction: column;
  gap: var(--nx-space-2);
  min-width: 0;
}
.nx-housekeeping-page .nx-radio-row__title {
  font-weight: 500;
  font-size: var(--nx-text-md);
  color: var(--nx-text-primary);
}
.nx-housekeeping-page .nx-radio-row__desc {
  margin: 0;
  color: var(--nx-text-muted);
  font-size: var(--nx-text-sm);
  line-height: 1.55;
}

/* Info-icon link — shape + states only. Positional margin is page-scoped
 * below so the primitive stays reusable. Click selects only the FAQ
 * deep-link; does not flip the radio selection. */
.nx-info-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  color: var(--nx-text-muted);
  text-decoration: none;
  font-size: var(--nx-text-sm);
  border-radius: var(--nx-radius-full);
  transition: color 0.15s, background 0.15s;
}
.nx-info-link:hover {
  color: var(--nx-text-primary);
  background: var(--nx-ink-05);
}
.nx-info-link:focus-visible {
  outline: 2px solid var(--nx-accent);
  outline-offset: 2px;
}

/* Page-scoped positioning: tuned to sit flush at the top-right of a
 * .nx-radio-row__label with padding var(--nx-space-5). Other pages that
 * adopt .nx-info-link will set their own margins in their own scope. */
.nx-housekeeping-page .nx-info-link {
  margin: var(--nx-space-4) var(--nx-space-4) 0 0;
}

/* NOTE: .nx-kicker on this page inherits the design-system default colour
 * (var(--nx-success), green) — intentionally not overridden. Housekeeping
 * is a user-choice page, not a state-outcome page; there is no state to
 * signal. Contrast with .nx-message-page--{success,failed,cancel,notfound,server,login}
 * which override per-state because their kicker text ("PAYMENT · SUCCESS /
 * FAILED / CANCELLED" etc.) reflects the outcome. Reviewed 2026-04-22. */

/* Actions bar — sits outside the fieldset card on the page backdrop.
 * Cancel (outline) left, Confirm disconnect (primary) right. */
.nx-housekeeping-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--nx-space-4);
  margin-top: var(--nx-space-6);
  flex-wrap: wrap;
}

/* Footer note — muted reminder beneath the actions. */
.nx-housekeeping-foot {
  color: var(--nx-text-muted);
  font-size: var(--nx-text-sm);
  text-align: center;
  margin-top: var(--nx-space-5);
}

/* ============================================================================
 * Invoice page (C.4.2) — page-scoped restyle of templates/invoice.html.
 * Hero + one card shell containing two <fieldset> groups (Contact / Billing
 * address) + action bar outside the card on the N.svg backdrop.
 *
 * Sticky-footer behaviour inherited from body.nx-body (lines 247-257) — do
 * NOT set an explicit min-height on this page scope.
 * ========================================================================= */

.nx-invoice-page {
  background-color: var(--nx-bg-elevated);
  /* Image properties come from the shared N-backdrop rule near the top. */
}

.nx-invoice-wrap {
  max-width: 760px;
  margin-inline: auto;
  padding: 0 var(--nx-space-5);
}

/* Hero — clamp ceiling (38px) matches housekeeping; rule body lives on
 * the consolidated multi-selector in the Housekeeping chapter. */
.nx-invoice-page .nx-sec-title { margin-bottom: var(--nx-space-6); }
/* 9th page-scoped copy of the h1 mark pattern — .nx-page-mark promotion
 * deferred to Phase D (see 2026-04-21 decisions-log "Defer .nx-page-mark
 * promotion" entry; 2026-04-22 C.4.2 re-eval declined promotion due to
 * 3px-vs-4px thickness divergence that belongs to a consolidation commit). */
.nx-invoice-page .nx-sec-title h1 mark {
  background: transparent;
  color: var(--nx-text-primary);
  padding-bottom: 3px;
  border-bottom: 3px solid var(--nx-accent);
}

/* Card shell — one outer container wrapping two inner <fieldset> groups.
 * The card is NOT a <fieldset> itself (differs from housekeeping) because
 * the semantic grouping lives on the inner <fieldset>s, not the outer. */
.nx-invoice-card {
  border: 1px solid var(--nx-ink-10);
  border-radius: var(--nx-radius-lg);
  background: var(--nx-bg-primary);
  padding: var(--nx-space-6);
  display: flex;
  flex-direction: column;
  gap: var(--nx-space-6);
}

/* .nx-form-group reset + legend styling come from the shared forms-layout
 * sub-chapter above — no page-scoped override needed here. */

/* Actions bar — outside the card on the N.svg backdrop. Cancel left,
 * Continue to payment right. Wraps to stacked on narrow viewports. */
.nx-invoice-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--nx-space-4);
  margin-top: var(--nx-space-6);
  flex-wrap: wrap;
}

.nx-invoice-foot {
  color: var(--nx-text-muted);
  font-size: var(--nx-text-sm);
  text-align: center;
  margin-top: var(--nx-space-5);
}

/* Mobile: card drops to softer padding so the 760px wrap doesn't dominate
 * the viewport on ~375px devices. */
@media (max-width: 640px) {
  .nx-invoice-card { padding: var(--nx-space-5); }
}

/* ===========================================================================
 * Blog index page (Phase C.5.1 — 2026-04-23)
 *
 * Page-scoped restyle of templates/blog.html (the /blog index). Applied under
 * .nx-blog-page on the <main> element so nothing leaks into other templates.
 * Shared rules (N-backdrop, hr) are joined by selector-list extensions above
 * rather than re-declared here. No new globally-promoted primitives ship in
 * this commit — the .nx-post-card family stays page-scoped until a second
 * consumer earns promotion (see docs/decisions/log.md 2026-04-23 entry).
 *
 * Companion blog_post.html (C.5.2) will extend this chapter with a .nx-prose
 * wrapper for the long-form markdown body; no pre-emptive shared classes.
 * =======================================================================*/

/* Title mark — transparent <mark> background, 3px --nx-accent border-bottom
 * sitting 3px below the text. Same pattern as .nx-about-page h1 mark,
 * .nx-faq-hero h1 mark, .nx-filters-page h1 mark. */
.nx-blog-page h1 mark {
  background: transparent;
  color: var(--nx-text-primary);
  padding-bottom: 3px;
  border-bottom: 3px solid var(--nx-accent);
}

/* Hero h1 size — blog index uses the shared content-page hero scale
 * (clamp 36-56px, -0.015em tracking, 1.25 line-height); rule body lives
 * on the consolidated multi-selector in the About chapter. */

/* Post-card list — 1-column stacked flex container centred at 800px.
 * 800px sits between .nx-prose's 70ch reading column (~600px) and the
 * full .nx-wrap fill (~1152px). Wider than pure prose because each card
 * is already a bordered container that needs elbow room, narrower than
 * the full wrap because a stretched 1200px one-column list looks sparse
 * with 4 entries. */
.nx-blog-page .nx-blog-list {
  display: flex;
  flex-direction: column;
  gap: var(--nx-space-5);
  max-width: 800px;
  margin: 0 auto;
}

/* Post card — stretched-link <a>, hairline resting border (--nx-ink-05),
 * deepens to --nx-ink-25 on hover. Subtle 1px lift on hover, no shadow
 * change (minimal-archive flavour, chosen over the shadow-lift variant
 * during the 2026-04-23 brainstorm — the blog is a quiet archive, not
 * a product-card grid). Padding uses the 24/32 asymmetric pair so the
 * card reads as a wide horizontal block on desktop. */
.nx-blog-page .nx-post-card {
  display: block;
  background: var(--nx-bg-primary);
  border: 1px solid var(--nx-ink-05);
  border-radius: var(--nx-radius-md);
  padding: var(--nx-space-5) var(--nx-space-6);
  text-decoration: none;
  color: var(--nx-text-primary);
  transition: border-color 0.2s ease, transform 0.2s ease;
}

/* Hover affordances are gated behind (hover: hover) so touch devices don't
 * fire sticky :hover state during taps and scroll-near interactions. Keyboard
 * users still get the :focus-visible outline rule below. */
@media (hover: hover) {
  .nx-blog-page .nx-post-card:hover {
    border-color: var(--nx-ink-25);
    transform: translateY(-1px);
  }
}

/* Reduced-motion: suppress the 1px lift + transition for users who opt
 * out of motion. Matches the pattern at line 1639 covering .nx-status-*
 * cards. */
@media (prefers-reduced-motion: reduce) {
  .nx-blog-page .nx-post-card { transition: none; }
  .nx-blog-page .nx-post-card:hover { transform: none; }
}

/* Focus-visible ring — standard --nx-focus 2px outline at 2px offset,
 * mirroring other interactive surfaces in the design system. */
.nx-blog-page .nx-post-card:focus-visible {
  outline: 2px solid var(--nx-focus);
  outline-offset: 2px;
}

/* Post-card title — <h2> (correcting the pre-existing <h3> which skipped
 * a level; /blog has no section h2 above the cards). Typography forced
 * to 20px / 600-weight so the browser-default <h2> size doesn't ship.
 * Margin zeroed on the top edge so the card padding controls the start;
 * bottom-margin uses the spacing scale. */
.nx-blog-page .nx-post-card-title {
  display: block;
  font-family: var(--nx-font-display);
  font-size: 20px;
  line-height: 1.3;
  font-weight: 600;
  margin: 0 0 var(--nx-space-3) 0;
}

/* Hover/focus underline on the title — accent-green, continuous through
 * letter descenders (text-decoration-skip-ink: none) so the line doesn't
 * break under p / y / g / j / q. Default browser behaviour is auto-skip;
 * we explicitly opt out because the skip-over looks ragged against a
 * 2px coloured underline. Safari 15.4+, Firefox 70+, Chrome 64+.
 *
 * Hover variant gated behind (hover: hover) so the underline doesn't flash
 * on mobile taps/scroll. Keyboard users keep the focus-visible underline. */
@media (hover: hover) {
  .nx-blog-page .nx-post-card:hover .nx-post-card-title {
    text-decoration: underline;
    text-decoration-color: var(--nx-accent);
    text-decoration-thickness: 2px;
    text-underline-offset: 3px;
    text-decoration-skip-ink: none;
  }
}
.nx-blog-page .nx-post-card:focus-visible .nx-post-card-title {
  text-decoration: underline;
  text-decoration-color: var(--nx-accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
  text-decoration-skip-ink: none;
}

/* Post-card description — 15px body in the ink-65 muted tone to keep
 * visual weight on the title. */
.nx-blog-page .nx-post-card-desc {
  margin: 0;
  font-size: 15px;
  line-height: 1.55;
  color: var(--nx-ink-65);
}

/* ============================================================================
 * Blog post page (Phase C.5.2 — 2026-04-23)
 *
 * Page-scoped restyle of templates/blog_post.html (the /blog/<slug>
 * single-post reader). Applied under .nx-blog-post-page on the <main>
 * element so nothing leaks into other templates. Shared rules (N-backdrop,
 * hr, .nx-prose h4, .nx-prose img) are joined by selector-list extensions
 * above or added to the shared .nx-prose chapter rather than re-declared
 * here. No new globally-promoted primitives ship in this commit — the
 * .nx-blog-post-back wrapper stays page-scoped until a second consumer
 * earns promotion (see docs/decisions/log.md 2026-04-23 entry).
 *
 * Differs from .nx-blog-page (C.5.1, the index listing): the index is a
 * card-feed archive at 800px max-width with stretched-link post cards;
 * the post page is long-form prose at 880px max-width with a smaller
 * editorial h1 and a back-button. Sibling scopes by design.
 * ========================================================================== */

/* Hero h1 — editorial scale, smaller than both the .nx-sec-title default
 * (clamp 30–44px) and the marketing-hero override used by the index,
 * About, FAQ, Instructions (clamp 36–56px). Post titles are long
 * sentences (e.g. post1 is 62 chars); scaled-down font + 680px max-width
 * makes them wrap to 2–3 lines at the hero column rather than spilling
 * across the full 1200px wrap. Centred within .nx-sec-title. */
.nx-blog-post-page .nx-sec-title h1 {
  font-size: clamp(26px, 2.6vw, 34px);
  letter-spacing: -0.01em;
  line-height: 1.2;
  max-width: 680px;
  margin: 0 auto;
}

/* Prose max-width override — .nx-prose ships with max-width: 70ch
 * (~600px) for tight reading columns. Blog posts read better at a
 * slightly wider 880px, halfway between the 70ch default and the
 * full .nx-wrap fill (~1152px). Mirrors the .nx-about-page .nx-prose
 * 1000px override pattern. margin:auto centres inside .nx-wrap. Global
 * .nx-prose default is untouched so legal templates keep their 70ch
 * reading column. */
.nx-blog-post-page .nx-prose {
  max-width: 880px;
  margin: 0 auto;
  /* `flow-root` establishes a new block formatting context so the floated
   * lead image (rule below) cannot escape the prose wrapper and overlap the
   * back-button band. Modern clearfix without overflow:hidden side-effects. */
  display: flow-root;
}

/* Lead-image float — when a post opens with a paragraph that contains only
 * an image (the markdown `![alt](url)` lead-image pattern in posts 1–3), float
 * it right so the surrounding paragraphs wrap around it. Mirrors the editorial
 * layout on numerint.com. Disabled below 720px where the float would squeeze
 * the text column unreadably; the image falls back to the centred default. */
@media (min-width: 720px) {
  .nx-blog-post-page .nx-prose > p:first-child:has(> img:only-child) {
    float: right;
    width: 42%;
    max-width: 380px;
    margin: 0 0 var(--nx-space-5) var(--nx-space-6);
  }
  .nx-blog-post-page .nx-prose > p:first-child:has(> img:only-child) > img {
    margin: 0;
    width: 100%;
  }
}

/* Back-button wrapper — positions the shared .nx-btn.nx-btn-outline
 * pill at the bottom of the prose column, left-aligned, with a generous
 * top gap so it reads as a "finished reading" affordance rather than
 * an in-flow control. */
.nx-blog-post-page .nx-blog-post-back {
  margin: var(--nx-space-7) auto 0;
  max-width: 880px;
}

/* Optical alignment for the U+2190 (←) glyph — most fonts render it sitting
 * on the math baseline, which leaves it visually low against capital letters
 * in "Back". A 1px upward translate aligns the arrow with the cap-height
 * midline of the label. */
.nx-blog-post-page .nx-blog-post-back .nx-back-arrow {
  display: inline-block;
  transform: translateY(-1px);
}

/* ===========================================================================
 * Legal pages (Phase C.6 — 2026-04-26)
 *
 * Page scope: .nx-legal-page on <main>. Reuses .nx-sec-title hero,
 * .nx-toc + .nx-toc-section* (renamed from FAQ in same release),
 * .nx-prose body. Adds: .nx-notice (token-driven inline alert),
 * .nx-prose h5/h6 extensions (in the .nx-prose chapter above), the
 * .nx-version-list footer block, plus a few page-scoped niceties
 * (heading scroll-margin, hr inheritance, mobile TOC hiding at 960px).
 *
 * Companion JS: static/assets/js/legal.js (scroll-spy only, no
 * hash-priority — see comment in faq.js for the wider FAQ contract).
 * =======================================================================*/

.nx-legal-page {
  padding-bottom: var(--nx-space-8);
}

.nx-legal-page .nx-sec-title {
  margin-bottom: var(--nx-space-6);
  padding-top: var(--nx-space-7);
  padding-bottom: 0;
}
.nx-legal-page .nx-sec-title h1 { line-height: 1.25; }
.nx-legal-page .nx-sec-title h1 mark {
  background: transparent;
  color: var(--nx-text-primary);
  padding-bottom: 3px;
  border-bottom: 3px solid var(--nx-accent);
}
.nx-legal-page .nx-page-sub {
  margin-top: 18px;
  max-width: 720px;
}
.nx-legal-page .nx-legal-meta {
  font-size: var(--nx-text-sm);
  color: var(--nx-ink-65);
  margin: 14px auto 0;
}

/* Anchor offset for id-bearing prose headings — URL fragments target
 * the heading itself, not the wrapping section-head. 80px clears the
 * 68px fixed nav with 12px breathing. */
.nx-legal-page .nx-prose :is(h2, h3, h4, h5, h6)[id] {
  scroll-margin-top: 80px;
}

/* Legal section spacing — legal templates use bare <header
 * class="nx-toc-section-head"> without a wrapping .nx-toc-section, so the
 * shared .nx-toc-section + .nx-toc-section sibling-margin rule never fires.
 * Restore inter-section breathing room from the mockup spec
 * (static/assets/dev/legal-mockup/current.html). :first-of-type avoids
 * pushing the first section away from the archived banner / hero hr. */
.nx-legal-page .nx-toc-section-head { margin-top: var(--nx-space-7); }
.nx-legal-page .nx-toc-section-head:first-of-type { margin-top: 0; }

/* Sub-clause indent — Terms uses 11.1.1, 13.2.1 nested numbering.
 * Indents a level so the visual nesting matches the numbering. */
.nx-legal-page .nx-prose p.sub-clause {
  margin-left: var(--nx-space-5);
}

/* Buttons inside .nx-prose lose both their text-decoration: none and
 * their explicit color under the .nx-prose a rule (same specificity,
 * later in cascade — sets color: --nx-success, text-decoration: underline).
 * Page-scoped overrides — keep .nx-btn looking like a button rather
 * than an underlined link. Cookie policy CTA is the current consumer;
 * primary is the only variant in legal templates. */
.nx-legal-page .nx-prose .nx-btn { text-decoration: none; }
.nx-legal-page .nx-prose .nx-btn-primary { color: var(--nx-text-primary); }
.nx-legal-page .nx-prose .nx-btn-primary:hover { color: var(--nx-accent); }

/* Body grid + sticky TOC sidebar overflow guard. */
.nx-toc-body { padding: 0 0 var(--nx-space-7); }
.nx-toc {
  /* Existing sticky positioning lives in the FAQ TOC chapter — this
   * only adds the overflow guard for tall TOCs (privacy has 16
   * entries, terms has 19). */
  max-height: calc(100vh - 120px);
  overflow-y: auto;
}

/* DPA Sub-processors table — page-scoped, single consumer. Replaces
 * the Shock .scheme-1 styling that was stripped during the DPA restyle. */
.nx-legal-page .nx-prose table {
  width: 100%;
  border-collapse: collapse;
  margin: var(--nx-space-4) 0 var(--nx-space-5);
  font-size: var(--nx-text-sm);
}
.nx-legal-page .nx-prose thead th {
  text-align: left;
  font-family: var(--nx-font-display);
  font-size: var(--nx-text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--nx-ink-65);
  font-weight: 600;
  padding: 10px 12px;
  border-bottom: 1px solid var(--nx-ink-10);
  background: var(--nx-bg-elevated);
}
.nx-legal-page .nx-prose tbody td {
  padding: 12px;
  vertical-align: top;
  border-bottom: 1px solid var(--nx-ink-10);
  color: var(--nx-text-secondary);
}
.nx-legal-page .nx-prose tbody tr:last-child td { border-bottom: 0; }

/* Notice — token-driven inline alert. .is-warning is the first
 * variant; future error/empty surfaces become .is-info / .is-danger. */
.nx-notice {
  display: block;
  margin: 0 0 var(--nx-space-6);
  padding: var(--nx-space-4) var(--nx-space-5);
  border-radius: var(--nx-radius-md);
  border-left: 3px solid currentColor;
}
.nx-notice .nx-notice-eyebrow {
  display: block;
  font-family: var(--nx-font-display);
  font-size: var(--nx-text-xs);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-weight: 700;
  margin-bottom: 6px;
}
.nx-notice p { margin: 0; font-size: var(--nx-text-sm); line-height: 1.55; }
.nx-notice p + p { margin-top: var(--nx-space-2); }
.nx-notice.is-warning {
  background: var(--nx-warning-12);
  color: var(--nx-warning-text);
}
.nx-notice.is-warning a {
  color: var(--nx-success);
  text-decoration: underline;
  font-weight: 600;
}

/* Other Versions footer block — small page-scoped pattern. Promotion
 * deferred until a second consumer arrives. */
.nx-legal-page .nx-version-list {
  margin-top: var(--nx-space-8);
  padding-top: var(--nx-space-5);
  border-top: 1px solid var(--nx-ink-10);
}
.nx-legal-page .nx-version-list-label {
  display: block;
  font-family: var(--nx-font-display);
  font-size: var(--nx-text-xs);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--nx-success);
  font-weight: 500;
  margin-bottom: 10px;
}
.nx-legal-page .nx-version-list-intro {
  font-size: var(--nx-text-sm);
  color: var(--nx-ink-65);
  margin: 0 0 var(--nx-space-3);
}
.nx-legal-page .nx-version-list-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--nx-space-3) var(--nx-space-4);
}
.nx-legal-page .nx-version-list-row a {
  font-size: var(--nx-text-sm);
  color: var(--nx-success);
  text-decoration: underline;
}
.nx-legal-page .nx-version-list-row a.is-current { font-weight: 700; }

/* Mobile: hide TOC entirely below 960px. Matches .nx-instructions-rail
 * mobile rule. Body content stacks single-column. Privacy / terms / DPA
 * carry 16 / 19 / ~19 TOC entries — stacking them on mobile would push
 * body content ~700–840px off-screen. Cmd+F is the mobile fallback. */
@media (max-width: 960px) {
  .nx-toc-grid { grid-template-columns: 1fr; }
  .nx-toc { display: none; }
}
