/* ==========================================================================
   Ledgr — public website stylesheet. The look is "Clear Day".
   --------------------------------------------------------------------------
   White is the page and blue does the work. Nothing is separated by a line:
   space, soft tints of blue, depth and type do that. The only drawn edges left
   are a form field's and the focus ring, because a control has to be findable.
   Blue is a ground in one place only, the closing .cta card, inside white
   margins. Light-only by the owner's decision: the site has no dark mode.

   Typefaces are self-hosted (assets/fonts). They are NOT loaded from a CDN on
   purpose: the server sets Cross-Origin-Embedder-Policy:require-corp globally
   for the WASM app, which blocks cross-origin font fetches.
   ========================================================================== */

/* ── Fonts ─────────────────────────────────────────────────────────────── */
@font-face { font-family: 'DM Sans'; src: url('/assets/fonts/dmsans_regular.ttf') format('truetype');  font-weight: 400; font-display: swap; }
@font-face { font-family: 'DM Sans'; src: url('/assets/fonts/dmsans_medium.ttf') format('truetype');   font-weight: 500; font-display: swap; }
@font-face { font-family: 'DM Sans'; src: url('/assets/fonts/dmsans_semibold.ttf') format('truetype'); font-weight: 600; font-display: swap; }
@font-face { font-family: 'DM Sans'; src: url('/assets/fonts/dmsans_bold.ttf') format('truetype');     font-weight: 700; font-display: swap; }
@font-face { font-family: 'Outfit';  src: url('/assets/fonts/outfit_semibold.ttf') format('truetype'); font-weight: 600; font-display: swap; }
@font-face { font-family: 'Outfit';  src: url('/assets/fonts/outfit_bold.ttf') format('truetype');     font-weight: 700; font-display: swap; }
@font-face { font-family: 'JetBrains Mono'; src: url('/assets/fonts/jetbrainsmono_regular.ttf') format('truetype');  font-weight: 400; font-display: swap; }
@font-face { font-family: 'JetBrains Mono'; src: url('/assets/fonts/jetbrainsmono_semibold.ttf') format('truetype'); font-weight: 600; font-display: swap; }

/* ── Tokens ────────────────────────────────────────────────────────────── */
:root {
  /* The blue scale, pale tint to deep. --b700 is the brand primary. */
  --b50: #F4F8FE; --b100: #E8F1FC; --b200: #D2E3F8; --b300: #A9CBF2; --b400: #6BA4E8;
  --b500: #2A72D4; --b600: #1A5BC0; --b700: #0D47A1; --b800: #0B3A86; --b900: #0A2B63;

  /* Brand. --sec is the teal ink: #00838F itself drops below AA on a teal tint. */
  --pri: #0D47A1;
  --sec: #007580;
  --cyan: #00BCD4;
  --chart-blue: #1A5BC0;

  /* Surfaces. Ink tokens below are held at AA on every one of these by SiteContrastTest. */
  --page: #F4F8FE;
  --card: #FFFFFF;
  --surface: #F4F8FE;
  --feature-bg: #F4F8FE;
  --divider: #E8F1FC;
  /* A form field's edge, at 3:1 or better on white, Blue 50 and Blue 100. The one line left. */
  --edge: #6C87B5;

  /* Text. Ink is blue too, so a page reads as blue and white rather than black on white. */
  --text: #0A2B63;
  --text-2: #31466A;
  --muted: #46597C;
  --neutral: #4E6287;
  --faint: #5B7296;

  /* Semantic: status only, never decoration */
  --success: #0B6E4A;
  --success-bg: #DDF3EA;
  --warn: #8F4405;
  --warn-bg: #FBEBD9;
  --error: #B3261E;
  --error-bg: #FBE4E2;
  --info-bg: #DFF4F5;

  /* Code: a pale blue page, never a dark terminal */
  --code-bg: #F4F8FE;
  --code-fg: #0A2B63;

  /* The atmosphere behind a page's first screen */
  --air: linear-gradient(180deg, rgba(255, 255, 255, 0) 62%, #FFFFFF 100%),
         radial-gradient(58% 78% at 88% -6%, #D2E3F8 0, rgba(210, 227, 248, 0) 68%),
         radial-gradient(46% 62% at 2% 92%, #D9F4F8 0, rgba(217, 244, 248, 0) 66%), #FFFFFF;
  --well: radial-gradient(70% 90% at 96% 0%, #D2E3F8 0, rgba(210, 227, 248, 0) 70%),
          linear-gradient(150deg, #E8F1FC, #F4F8FE 62%, #E4F7FA);

  /* Shape + rhythm */
  --r-sm: 10px;
  --r: 18px;
  --r-lg: 28px;
  --r-well: 40px;
  --maxw: 1180px;
  --nav-h: 68px;

  /* Depth: blue-tinted, wide and soft, in place of every border */
  --shadow-sm: 0 1px 2px rgba(13, 71, 161, .05), 0 10px 28px -10px rgba(13, 71, 161, .14);
  --shadow: 0 2px 6px rgba(13, 71, 161, .05), 0 28px 64px -18px rgba(13, 71, 161, .24);
  --shadow-lg: 0 4px 10px rgba(13, 71, 161, .06), 0 48px 96px -28px rgba(13, 71, 161, .36);
  --glow: 0 14px 30px -10px rgba(13, 71, 161, .5);
}

/* ── Reset ─────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--card);
  color: var(--text);
  font-family: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}
img, svg { max-width: 100%; }
h1, h2, h3, h4 { font-family: 'Outfit', 'DM Sans', sans-serif; font-weight: 700; line-height: 1.2; margin: 0; letter-spacing: -.015em; }
p { margin: 0; }
a { color: var(--pri); text-decoration: none; }
a:hover { text-decoration: underline; }
code, kbd, pre, .mono, .num { font-family: 'JetBrains Mono', ui-monospace, 'SF Mono', monospace; }
ul, ol { margin: 0; padding-left: 1.25rem; }
table { border-collapse: collapse; width: 100%; }
:focus-visible { outline: 3px solid var(--sec); outline-offset: 2px; border-radius: 4px; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

/* Skip link — keyboard users land on content, not 12 nav items */
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--pri); color: #fff; padding: 10px 16px; border-radius: 0 0 var(--r-sm) 0; font-weight: 600;
}
.skip:focus { left: 0; }

/* ── Layout helpers ────────────────────────────────────────────────────── */
/* --wrap-pad is the gutter, published as a variable because a strip that
   full-bleeds out to the viewport edge (see .jump) has to cancel exactly
   this padding, and a retyped 24px goes stale the day the gutter moves. */
/* A box narrowed for reading (90 of them set max-width inline) starts where the full page does, under the
   logo, rather than centring: centred, a page's words began 200px right of its own nav. Full-width boxes
   come out exactly as centred did. */
.wrap { --wrap-pad: 24px; width: 100%; max-width: var(--maxw); margin-inline: max(0px, (100% - var(--maxw)) / 2) auto; padding: 0 var(--wrap-pad); }
.section { padding: 112px 0; }
.section--tight { padding: 64px 0; }
.section--alt { background: var(--well); border-radius: var(--r-well); width: calc(100% - 48px); max-width: 1400px; margin: 24px auto; }
.section--soft { background: linear-gradient(160deg, var(--b50), var(--b100)); border-radius: var(--r-well); width: calc(100% - 48px); max-width: 1400px; margin: 24px auto; }
/* The desktop rhythm reads as dead space on a phone: 84px top + 84px bottom
   puts ~168px of nothing between consecutive sections on a 390px screen, which
   is nearly half a viewport of scrolling past nothing. */
@media (max-width: 640px) {
  .section { padding: 56px 0; }
  .section--tight { padding: 40px 0; }
  .section--alt, .section--soft { width: calc(100% - 16px); margin: 8px auto; border-radius: var(--r-lg); }
  .section-head { margin-bottom: 30px; }
}
.center { text-align: center; }
.center .eyebrow, .eyebrow.center { margin-left: auto; margin-right: auto; }
.stack-sm > * + * { margin-top: 8px; }

.eyebrow { display: block; width: fit-content; max-width: 100%; padding: 5px 14px; border-radius: 999px; background: var(--b100);
  font-size: 14px; font-weight: 600; line-height: 1.3; color: var(--pri); margin-bottom: 16px; }


h2.h-section { font-size: clamp(32px, 4.2vw, 56px); line-height: 1.08; letter-spacing: -.028em; font-weight: 600; text-wrap: balance; }
.lede { color: var(--text-2); font-size: 18px; max-width: 62ch; text-wrap: pretty; }

.section-head { max-width: 820px; margin: 0 0 56px; }
.section-head .lede { margin: 16px 0 0; }

.grid { display: grid; gap: 20px; }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 960px) { .grid-3, .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px) { .grid-2, .grid-3, .grid-4 { grid-template-columns: minmax(0, 1fr); } }
/* A set of exactly three (/accountants/'s three roles) is one row until the phone stacks it: dropping to two columns
   left the third card alone on its own row at 800px (QA-35 F-75). After the 960 rule, so it wins in that band. */
@media (min-width: 641px) and (max-width: 960px) { .grid--hold-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* ── Buttons ───────────────────────────────────────────────────────────── */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: 'DM Sans', sans-serif; font-size: 15.5px; font-weight: 600; line-height: 1.2;
  min-height: 46px; padding: 12px 24px; border-radius: 999px; border: 0;
  cursor: pointer; transition: transform .18s ease, box-shadow .18s ease, background-color .18s ease, color .18s ease;
  text-decoration: none; white-space: nowrap; }
.btn:hover { text-decoration: none; transform: translateY(-1px); }
.btn-primary { background: var(--pri); color: #fff; box-shadow: var(--glow); }
.btn-primary:hover { background: var(--b800); }
.btn-ghost { background: var(--b100); color: var(--pri); }
.btn-ghost:hover { background: var(--b200); color: var(--b800); }
.btn-onDark { background: var(--b100); color: var(--pri); }
.btn-onDark:hover { background: var(--b200); }
.btn-cyan { background: var(--pri); color: #fff; }
.btn-sm { min-height: 44px; padding: 9px 18px; font-size: 14.5px; }
.btn-lg { min-height: 56px; padding: 15px 32px; font-size: 16.5px; }

/* ── Top navigation ────────────────────────────────────────────────────── */
.nav { position: sticky; top: 0; z-index: 100; height: var(--nav-h);
  background: rgba(255, 255, 255, .86); backdrop-filter: saturate(160%) blur(14px); }
.nav-inner { display: flex; align-items: center; gap: 28px; height: 100%; }
.brand { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.brand:hover { text-decoration: none; }
.brand img { width: 32px; height: 32px; border-radius: 8px; display: block; }
.brand span {
  font-family: 'Outfit', sans-serif; font-weight: 700; font-size: 20px; letter-spacing: -.02em; color: var(--text);
}
.brand small {
  font-size: 10px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--sec); background: var(--info-bg); padding: 2px 7px; border-radius: 5px; align-self: center;
}
.nav-links { display: flex; align-items: center; gap: 4px; margin-right: auto; }
.nav-links a { color: var(--text-2); font-size: 15px; font-weight: 500; padding: 8px 13px; border-radius: 999px;
  transition: background .13s ease, color .13s ease; }
.nav-links a:hover { background: var(--page); color: var(--pri); text-decoration: none; }
.nav-links a[aria-current="page"] { color: var(--pri); background: var(--b100); font-weight: 600; }
/* Sign in in the menu: the header's own is hidden between 431 and 1024px (below), so the menu offers it
   exactly there. Hidden here (desktop shows the header's) and again at 430px and below (the header swaps
   Get started free for Sign in). */
.nav-links .nav-signin { display: none; }
.nav-cta { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.nav-toggle { display: none; background: var(--b100); border: 0; border-radius: 50%;
  width: 44px; height: 44px; cursor: pointer; align-items: center; justify-content: center; }
.nav-toggle span { display: block; width: 18px; height: 2px; background: var(--text); position: relative; }
.nav-toggle span::before, .nav-toggle span::after {
  content: ''; position: absolute; left: 0; width: 18px; height: 2px; background: var(--text);
}
.nav-toggle span::before { top: -6px; } .nav-toggle span::after { top: 6px; }

/* Just above the menu breakpoint the roomy row (28px gaps, 13px beside each link) needs ~1100px: at 1025px
   the CTA ran 50px past the viewport and the page scrolled sideways (QA-43 F-68). Tightened to fit the
   narrowest width the full row is shown at; the collapse stays 1024px because the hero, the grids and the
   menu's Sign in key off it. Measured: overflow 50 at 1025 -> 0 here, 0 from 1100 up as before. */
@media (min-width: 1025px) and (max-width: 1140px) {
  .nav-inner { gap: 14px; }
  .nav-links a { padding: 8px 8px; }
}

@media (max-width: 1024px) {
  .nav-toggle { display: flex; }
  .nav-links {
    position: absolute; top: var(--nav-h); left: 0; right: 0; display: none;
    flex-direction: column; align-items: stretch; gap: 2px; margin: 0; padding: 12px 16px 18px;
    background: var(--card); box-shadow: var(--shadow);
  }
  .nav[data-open="true"] .nav-links { display: flex; }
  .nav-links a { padding: 12px 14px; font-size: 16px; }
  .nav-cta .btn-ghost { display: none; }
  .nav-links .nav-signin { display: flex; align-items: center; min-height: 44px; margin-top: 6px; color: var(--pri); font-weight: 600; }
  /* On desktop it is `.nav-links { margin-right: auto }` that pushes the CTA to
     the right edge. That link row goes `position: absolute` here, so it is out
     of flow and its auto margin pushes nothing — the sign-in button and the
     hamburger then sat immediately beside the wordmark. Measured at 390px:
     brand ended at x=105, the CTA group ran 117→245, leaving 144px of empty
     header to its right, with the menu button two thirds of the way from the
     edge a thumb reaches for. The auto margin has to move to the element that
     is still in flow. */
  .nav-cta { margin-left: auto; }
}

/* Must follow the 1024px block — it re-shows the ghost button hidden above. */
@media (max-width: 430px) {
  /* On a small phone the brand, a wide CTA and the toggle will not share a
     line. Swap the wide "Get started free" for the short "Sign in" so the
     header still offers a way in without pushing the page sideways. */
  .wrap { --wrap-pad: 16px; }
  .nav-inner { gap: 12px; }
  .brand span { font-size: 18px; }
  .nav-cta .btn-primary { display: none; }
  .nav-cta .btn-ghost { display: inline-flex; }
  .nav-links .nav-signin { display: none; }
  /* Long CTA labels ("Start free — 5 invoices a month") wrap instead of
     overflowing; nowrap is only worth keeping while there is room for it. */
  .btn { white-space: normal; text-align: center; }
  .btn-lg { padding: 13px 18px; font-size: 15px; }
}

/* ── Hero (dark app chrome) ────────────────────────────────────────────── */
.hero { position: relative; overflow: hidden; color: var(--text); background: var(--air); }
.hero-inner {
  position: relative; z-index: 1; display: grid;
  /* minmax(0,…) everywhere a grid holds mono numerals: the default `auto`
     minimum refuses to shrink below min-content and the page then scrolls
     sideways on a phone. */
  grid-template-columns: minmax(0, 1.02fr) minmax(0, .98fr);
  gap: 56px; align-items: center;
  /* padding-block, never the `padding` shorthand: this element is also .wrap,
     and .wrap's horizontal padding IS the page gutter. A shorthand of
     `84px 0 96px` zeroed it, so on a phone — where nothing else constrains the
     width — the hero heading, lede and buttons sat flush against the screen
     edge while every section below them was inset. */
  padding-block: 84px 96px;
}
/* `minmax(0, 1fr)` and NOT `1fr`, for exactly the reason the rule above states.
   `1fr` is `minmax(auto, 1fr)`, and `auto` refuses to shrink below the widest
   item's min-content — so the single-column override silently undid the desktop
   rule's fix. Measured on /developers/, whose hero holds a code block with long
   unbreakable lines: the column resolved to 460px inside a 390px viewport and
   `.hero { overflow: hidden }` CLIPPED the h1, the lede, both call-to-action
   buttons, the feature list and the code mock. 156px lost at 320px, 116 at 360,
   86 at 390, 48 at 428 — content gone, not merely scrolled past. The home page
   lost 3px at 320px the same way, through its .shot mock.
   `min-width: 0` on the items is the second half: a grid item's automatic
   minimum size is its min-content unless it is a scroll container, so the track
   being allowed to shrink is not on its own enough. */
@media (max-width: 1024px) {
  .hero-inner { grid-template-columns: minmax(0, 1fr); gap: 44px; padding-block: 60px 68px; }
  .hero-inner > * { min-width: 0; }
}
/* A hero with no visual beside it — pricing, products, support, the developer
   playground. The padding is a class rather than an inline style because an
   inline style beats a media query: every one of these heroes was keeping its
   full desktop spacing on a 390px screen, which is most of a viewport of empty
   dark blue before the heading. Pages still set their own max-width inline,
   because the measure is a per-page decision about the copy. */
.hero-band { position: relative; z-index: 1; padding-block: 60px 64px; }
@media (max-width: 640px) { .hero-band { padding-block: 40px 44px; } }
.hero h1 { font-size: clamp(38px, 5.6vw, 72px); letter-spacing: -.034em; line-height: 1.02; font-weight: 600; color: var(--text); text-wrap: balance; }
.hero h1 .grad { color: var(--b600);}
.hero p.lede { color: var(--text-2); font-size: 19px; margin-top: 20px; max-width: 56ch; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 32px; }
.hero-meta { display: flex; flex-wrap: wrap; gap: 10px 22px; margin-top: 30px; padding: 0; }
.hero-meta li { list-style: none; display: flex; align-items: center; gap: 8px; font-size: 14.5px; color: var(--text-2); }
.hero-meta li::before {
  content: ''; width: 8px; height: 8px; border-radius: 50%; background: var(--b500); flex-shrink: 0;
}

/* Fake app window used as the hero screenshot stand-in */
.shot { background: var(--card); border-radius: var(--r-lg); box-shadow: var(--shadow-lg); overflow: hidden; }
.shot-bar { display: flex; align-items: center; gap: 8px; padding: 11px 14px; background: var(--card); }
.shot-bar i { width: 9px; height: 9px; border-radius: 50%; background: var(--b200); display: block; }
.shot-bar .url {
  margin-left: 8px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--muted);
  background: var(--b50); padding: 4px 12px; border-radius: 999px;
}
.shot-body { padding: 18px; background: var(--page); }
.kpi-row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.shot-split {
  display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr); gap: 10px; margin-top: 10px;
}
.shot-runway { padding: 14px; display: flex; flex-direction: column; align-items: center; justify-content: center; }
/* SVG text is painted by `fill`, not by `color`, so these two labels were typed as
   hex and were invisible both to the token that owns them and to a browser-side
   contrast sweep reading `getComputedStyle().color`. The "weeks" label was a copy of
   the old --muted at 3.48:1. */
.runway-figure { fill: var(--text); }
.runway-unit { fill: var(--muted); }
.shot-note { padding: 12px 14px; margin-top: 10px; display: flex; align-items: center; gap: 10px; }
.kpi { background: var(--card); border-radius: var(--r-sm); padding: 12px; box-shadow: var(--shadow-sm); }
.kpi .k-label { font-size: 11.5px; font-weight: 600; color: var(--muted); }
.kpi .k-value { font-family: 'JetBrains Mono', monospace; font-size: 17px; font-weight: 600; color: var(--text); margin-top: 6px; }
.kpi .k-delta { font-size: 11px; font-weight: 600; margin-top: 2px; }
@media (max-width: 560px) {
  /* The mock is decorative — let it shrink rather than push the page sideways. */
  .shot-body { padding: 12px; }
  .kpi { padding: 9px; }
  .kpi .k-label { font-size: 10px; }
  .kpi .k-value { font-size: 12px; }
  .kpi .k-delta { font-size: 9.5px; }
  .shot-note { flex-wrap: wrap; }
}
.k-delta.up { color: var(--success); } .k-delta.down { color: var(--error); }

/* ── Cards ─────────────────────────────────────────────────────────────── */
.card { background: var(--card); border-radius: var(--r-lg); padding: 28px; box-shadow: var(--shadow-sm); }
.card--link { display: block; color: inherit; transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease; }
.card--link:hover { text-decoration: none; transform: translateY(-3px); box-shadow: var(--shadow); }
.card h3 { font-size: 18px; margin-bottom: 8px; }
.card p { color: var(--text-2); font-size: 15px; }
.card .more {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 14px;
  font-size: 14px; font-weight: 600; color: var(--pri);
}
.card--link:hover .more { gap: 10px; }

.icon-tile { width: 48px; height: 48px; border-radius: 16px; display: flex; align-items: center; justify-content: center;
  margin-bottom: 18px; background: var(--b100); flex-shrink: 0; }
.icon-tile svg { width: 23px; height: 23px; stroke: var(--pri); fill: none; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.icon-tile--teal { background: var(--info-bg); }
.icon-tile--cyan { background: var(--b200); }
.icon-tile--pri { background: var(--b200); }
.icon-tile--soft { background: var(--info-bg); }
.icon-tile--soft svg { stroke: var(--sec); }

/* Plan badge. The shape lives on the class itself, not on `.mod .plan-tag`, so
   it works wherever a page needs to say which tier something arrives on — the
   product page puts one beside a heading rather than in a card corner, and for
   as long as the padding and type were scoped to `.mod` those badges rendered
   as bare coloured text. Only the corner POSITIONING is scoped. */
.plan-tag { display: inline-block; vertical-align: middle;
  font-size: 12px; font-weight: 600;
  padding: 3px 10px; border-radius: 999px; background: var(--b100); color: var(--pri); }
/* Module card gets its badge in the corner */
.mod { position: relative; }
.mod .plan-tag { position: absolute; top: 16px; right: 16px; }
.plan-tag.free { background: var(--info-bg); color: #00636C; }
.plan-tag.starter { background: var(--b100); color: var(--pri); }
.plan-tag.business { background: var(--b200); color: var(--pri); }
.plan-tag.professional { background: var(--b300); color: var(--text); }
.plan-tag.enterprise { background: var(--b600); color: #fff; }
/* The tier above Enterprise. Deliberately the darkest badge on the page — it is
   the only one that is a second product rather than more of the first. */
.plan-tag.manufacturing { background: var(--b900); color: #fff; }
/* Practice is off the ladder rather than on top of it, so it does not continue
   the free→enterprise colour progression — it steps sideways into teal. */
.plan-tag.practice { background: var(--info-bg); color: #00636C; }
/* Sold on its own rather than included in a tier. The neutral outline says
   "buy this" where every other badge says "you already have this". */
.plan-tag.addon { background: var(--card); color: var(--text-2); box-shadow: var(--shadow-sm); }

/* ── Feature rows (alternating) ────────────────────────────────────────── */
.feature {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 56px; align-items: center;
}
.feature + .feature { margin-top: 88px; }
.feature--flip .feature-text { order: 2; }
@media (max-width: 900px) {
  .feature { grid-template-columns: minmax(0, 1fr); gap: 32px; }
  .feature + .feature { margin-top: 56px; }
  .feature--flip .feature-text { order: 0; }
}
.feature h3 { font-size: clamp(26px, 2.8vw, 36px); font-weight: 600; letter-spacing: -.02em; margin-bottom: 14px; }
.check-list { list-style: none; padding: 0; margin-top: 22px; }
.check-list li { position: relative; padding-left: 30px; margin-bottom: 11px; font-size: 15px; color: var(--text-2); }
.check-list li::before {
  content: ''; position: absolute; left: 0; top: 4px; width: 20px; height: 20px; border-radius: 50%; background: var(--b100);
}
.check-list li::after {
  content: ''; position: absolute; left: 6px; top: 10px; width: 8px; height: 4px;
  border-left: 2px solid var(--pri); border-bottom: 2px solid var(--pri);
  transform: rotate(-45deg);
}

/* ── Stat strip ────────────────────────────────────────────────────────── */
.stat-strip {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px;
}
@media (max-width: 760px) { .stat-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 420px) { .stat-strip { grid-template-columns: minmax(0, 1fr); } }
.stat { background: var(--b50); border-radius: var(--r-lg); padding: 24px; text-align: left; }
.stat b {
  display: block; font-family: 'Outfit', sans-serif; font-size: 28px; font-weight: 600;
  color: var(--text); letter-spacing: -.02em;
}
.stat:nth-child(odd) { background: var(--b100); }
.stat span { display: block; font-size: 14px; color: var(--text-2); margin-top: 6px; }

/* ── Pricing ───────────────────────────────────────────────────────────── */
/* Three across, not five. There are six tiers on the SME ladder, so a five-wide
   grid left the sixth card alone on a second row with four empty columns beside
   it — the "not aligned" pricing table. Three columns divide six exactly at
   every width the grid is used (6 cards on /pricing/, 6 on the home preview),
   and the cards are wide enough to read a feature list in. `stretch` rather than
   `start` so every card in a row is the same height and the buttons line up. */
.price-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; align-items: stretch; }
/* Six steps, each a shade deeper than the last. */
.price-grid > .plan:nth-child(3), .price-grid > .plan:nth-child(4) { background: var(--b100); }
.price-grid > .plan:nth-child(5), .price-grid > .plan:nth-child(6) { background: var(--b200); }
@media (max-width: 980px) { .price-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px) { .price-grid { grid-template-columns: minmax(0, 1fr); } }
.plan {
  background: var(--b50); border-radius: var(--r-lg); padding: 26px 24px;
  display: flex; flex-direction: column; height: 100%;
}
.plan--featured { position: relative; }
.price-grid > .plan.plan--featured:nth-child(n), .plan--featured { background: var(--pri); color: #fff; box-shadow: var(--shadow-lg); }
.plan--featured::before {
  content: 'Most popular'; position: absolute; top: -13px; left: 24px;
  background: var(--card); color: var(--pri); box-shadow: var(--shadow-sm);
  font-size: 12.5px; font-weight: 600; padding: 4px 14px; border-radius: 999px; white-space: nowrap;
}
.plan h3 { font-size: 19px; font-weight: 600; }
.plan .amount {
  font-family: 'Outfit', sans-serif; font-size: 34px; font-weight: 600; color: var(--text);
  margin: 12px 0 2px; letter-spacing: -.03em;
}
.plan .amount small { font-family: 'DM Sans', sans-serif; font-size: 13px; font-weight: 500; color: var(--muted); letter-spacing: 0; }
/* One divider under the header block, never three. Each of these used to be a
   `.seats` div carrying its own bottom border, so a card quoting an ex-VAT
   price, an incl-VAT price and a seat count drew three hairlines across itself.
   Shared with .plan-wide below — the off-ladder card quotes the same three
   things in the same order. */
.plan .incl, .plan-wide .incl { font-size: 12.5px; color: var(--muted); }
.plan .seats { font-size: 13px; color: var(--neutral); padding-bottom: 16px; }
.plan ul { list-style: none; padding: 16px 0 0; margin: 0 0 18px; flex: 1; }
.plan li { font-size: 13.5px; color: var(--text-2); padding: 5px 0 5px 22px; position: relative; }
.plan li::before {
  content: '✓'; position: absolute; left: 0; color: var(--pri); font-weight: 700; font-size: 12px;
}
.plan li.off { color: var(--faint); }
.plan li.off::before { content: '—'; color: var(--faint); }
/* A plan note is caption ink wherever it is used: outside .plan it rendered as body text, flush under the ladder on
   /accountants/ (QA-35 F-73). Inside a plan the rule below adds its own spacing. */
.plan-note { font-size: 12.5px; color: var(--muted); }
.plan .plan-note, .plan-wide .plan-note { font-size: 12.5px; color: var(--muted); margin: 0 0 14px; }
.plan .btn { width: 100%; }

/* The off-ladder tier (Practice). It is not a rung above Manufacturing — it is
   priced by a client book rather than by modules — so it gets a full-width card
   of its own instead of a seventh column that would never divide evenly. */
.plan-wide {
  display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); gap: 32px; align-items: center;
  background: var(--card); border-radius: var(--r-lg);
  padding: 32px 36px; box-shadow: var(--shadow-sm);
}
.plan-wide h3 { font-size: 21px; }
.plan-wide .amount {
  font-family: 'Outfit', sans-serif; font-size: 36px; font-weight: 600; color: var(--text);
  letter-spacing: -.03em; margin: 10px 0 2px;
}
.plan-wide .amount small { font-family: 'DM Sans', sans-serif; font-size: 13px; font-weight: 500; color: var(--muted); letter-spacing: 0; }
.plan-wide ul { list-style: none; padding: 0; margin: 0; }
.plan-wide li { font-size: 14px; color: var(--text-2); padding: 5px 0 5px 22px; position: relative; }
.plan-wide li::before { content: '✓'; position: absolute; left: 0; color: var(--sec); font-weight: 700; font-size: 12px; }
@media (max-width: 860px) { .plan-wide { grid-template-columns: minmax(0, 1fr); gap: 22px; padding: 24px 22px; } }

/* Comparison matrix */
.matrix-scroll {
  overflow-x: auto; overscroll-behavior-x: contain;
  border-radius: var(--r-lg); background: var(--card); box-shadow: var(--shadow-sm); padding: 6px;
  /* A paint context of its own, so the table's own corners cannot draw over the
     rounded border of the box that clips it. */
  position: relative;
}
/* border-collapse: separate (not the global collapse) is deliberate: collapsed
   borders drop cell borders on a positioned cell and let the header columns
   size independently of the body, which is exactly the "headers not lined up
   with the content" bug. Separate borders + zero spacing keeps the auto layout
   (so the label column stays wide enough not to wrap) while making the header
   columns track the body columns instead of drifting out of line. The bottom
   border is re-applied per cell since collapse is off. */
.matrix { min-width: 760px; width: 100%; font-size: 14px; border-collapse: separate; border-spacing: 0; }
/* 760px is right for the nine-column plan matrix it was written for and wasteful
   for a three-column one. The documentation layout's content column is 672px at
   1440px — narrower than the page because of the guide sidebar and the on-this-page
   list — so a flat 760px made every matrix in the guides scroll sideways at EVERY
   width, desktop included, which reads as a rendering fault rather than as a
   scroller. Measured: 90px over on seven guides at 1440px.
   Scaled to the column count instead. `:has()` is what lets a stylesheet ask "how
   many columns does this table have" without a second class on ten pages, and the
   direction is deliberately safe: a browser without `:has()` drops these two rules
   and keeps the 760px above, i.e. exactly today's behaviour. `:has(thead)` guards
   the case of a matrix with no header row, where the count would be unknowable. */
.matrix:has(thead):not(:has(thead th:nth-child(5))) { min-width: 520px; }
.matrix:has(thead):not(:has(thead th:nth-child(4))) { min-width: 380px; }
.matrix th, .matrix td { padding: 13px 14px; text-align: left; vertical-align: middle; }
/* Every second row is tinted: rows are told apart without a rule between them. */
.matrix tbody tr:nth-child(even) > * { background: var(--b50); }
.matrix tbody tr > :first-child { border-radius: var(--r-sm) 0 0 var(--r-sm); }
.matrix tbody tr > :last-child { border-radius: 0 var(--r-sm) var(--r-sm) 0; }
/* The column headings must sit over the cells they describe. They were left
   aligned while every cell under them was centred, so on a seven-tier table the
   word "Enterprise" sat above the Professional ticks — the misalignment that
   made the plan tables look broken. Only the first heading (the row-label
   column) stays ragged-left, because its cells are sentences. */
.matrix thead th {
  font-size: 13px; font-weight: 600; color: var(--muted); text-align: center;
}
.matrix thead th:first-child { text-align: left; }
.matrix tbody th { font-weight: 600; color: var(--text-2); }
.matrix td { text-align: center; color: var(--neutral); }
.matrix td.yes { color: var(--success); font-weight: 700; }
.matrix td.no { color: var(--faint); }
.matrix tbody tr:hover > * { background: var(--b100); }
/* A table's note is prose and prose must not scroll sideways, so it sits under the
   scroller rather than in a caption inside it. It was a `position: sticky` caption,
   which rode over the last row on a phone. */
.matrix-note { margin: 10px 0 0; padding: 0 6px; font-size: 13px; color: var(--muted); }
/* The plan matrix centres its cells because they hold a tick or a dash. A table
   whose cells are sentences needs them ragged-right, or every row reads as a
   caption. */
.matrix--prose td { text-align: left; line-height: 1.55; }
/* A prose column is ONE alignment, headings included, and it had three. The base
   rule above centres every heading so that "Enterprise" sits over the Enterprise
   ticks; a prose table then left-aligned its cells and nobody moved the headings
   back, so on /privacy/ the heading "What we rely on it for" floated over the
   middle of a column of paragraphs. Worse on /pricing/'s "What you are not
   charged for", where `.yes` kept the centring and a plain cell did not:
   measured at 1000px CSS, the heading sat at x=694, each "Included" at x=688 and
   R249/mo, R700/mo and the R2 499/mo total at **x=555** — the three figures a
   reader opens that table for, 133px left of everything else in their own column
   and under no heading. So the `.yes`/`.no` exception goes with it: a column
   holding both a word and a price cannot be centred for one and ragged for the
   other. The colour still marks what is included; the position no longer
   contradicts the rows around it. */
.matrix--prose thead th { text-align: left; }

/* The label column is NOT pinned, and that was measured rather than preferred.
   It used to be `position: sticky; left: 0`, on the argument that a scrolled row
   of ticks with no visible label is unreadable. True — and on a phone the cure
   was worse than the disease, because the label column is the WIDEST one in
   every one of these tables and it is subtracted from a viewport that has
   nothing to spare. Measured on /pricing/ at 390px CSS: the eight-column usage
   table gave 356px of scroller, of which the pinned label took 182px, leaving
   **174px** — one and a half of the seven plan columns — however far you
   scrolled. At 320px it left 104px. The reader could always reach the label they
   already knew and never the figures they came for.
   So the whole table scrolls. The row heading scrolls back into view in one
   gesture, which is the trade the other way round, and `.scroll-hint` — revealed
   by markScrollers() in ledgr.js only when the table really does overflow — is
   what tells the reader it scrolls at all. If this is ever reinstated, measure
   the REMAINING width at 320px before believing it helps. */

@media (max-width: 640px) {
  .matrix { font-size: 13px; }
  .matrix th, .matrix td { padding: 10px 11px; }
  .matrix thead th { font-size: 12px; }
  /* `small` is a relative size, so a 13px phone table rendered the ex-VAT figure
     under each total at 10.8px. A price is the one thing on this page nobody
     should have to squint at. */
  .matrix small { font-size: 12px; }
  /* 760px of table in a 286px window is 2.6 screens of sideways travel, and 760
     was chosen for a desktop column rather than for this. Every value here is a
     FLOOR, not a cap — a table is never narrower than its min-content width — so
     lowering one cannot clip or squeeze a column; it only stops forcing width
     the content did not ask for. A two-column table then simply fits the phone.
     Each selector MIRRORS one of the three base rules above exactly, at the same
     specificity, so source order is what decides. A plain `.matrix { min-width }`
     here would silently lose to `.matrix:has(thead):not(…)`, which is two
     specificity classes higher, and the phone would keep the desktop floor with
     nothing on screen to say why. */
  .matrix:has(thead):has(thead th:nth-child(8)) { min-width: 620px; }
  .matrix:has(thead):not(:has(thead th:nth-child(5))) { min-width: 340px; }
  .matrix:has(thead):not(:has(thead th:nth-child(4))) { min-width: 0; }
}

/* An eight-tier table is eight lots of cell padding, and at 14px each that is
   112px of the table's own minimum width spent on whitespace. Measured: the
   WhatsApp/AI-scan table's min-content was 820px inside a 790px prose column, so
   it scrolled sideways by 30px at EVERY desktop width — which reads as a
   rendering fault rather than as a scroller (see the min-width note above, which
   is the same mistake one step along). Tightening the gutter on the widest
   tables alone takes it to 756px and it fits; the narrower tables keep the
   roomier padding, because they have the width to spend. */
.matrix:has(thead th:nth-child(8)) th,
.matrix:has(thead th:nth-child(8)) td { padding-left: 10px; padding-right: 10px; }

/* ── Dark band (SA features) ───────────────────────────────────────────── */
.band {
  background: radial-gradient(60% 90% at 4% 100%, #C9F0F5 0, rgba(201, 240, 245, 0) 70%), linear-gradient(150deg, var(--b200), var(--b100) 70%);
  color: var(--text); padding: 96px 0; position: relative; overflow: hidden;
  border-radius: var(--r-well); width: calc(100% - 48px); max-width: 1400px; margin: 24px auto;
}
.band .wrap { position: relative; z-index: 1; }
.band h2 { color: var(--text); }
.band-card {
  background: var(--card); box-shadow: var(--shadow-sm);
  border-radius: var(--r-lg); padding: 24px;
}
.band-card h3 { font-size: 18px; font-weight: 600; color: var(--text); margin-bottom: 8px; }
.band-card p { color: var(--text-2); font-size: 14.5px; }
.band-card .tag {
  display: inline-block; font-size: 12.5px; font-weight: 600;
  color: var(--pri); background: var(--b100);
  padding: 3px 11px; border-radius: 999px; margin-bottom: 12px;
}

/* ── CTA ───────────────────────────────────────────────────────────────── */
.cta {
  background: radial-gradient(50% 120% at 100% 0%, rgba(0, 188, 212, .34) 0, rgba(0, 188, 212, 0) 70%), linear-gradient(120deg, #0A3A8A, var(--pri) 55%, #1454B4);
  border-radius: var(--r-well); padding: 72px 56px; color: #fff; box-shadow: var(--shadow-lg);
}
.cta h2 { color: #fff; font-size: clamp(30px, 3.8vw, 48px); font-weight: 600; letter-spacing: -.026em; }
.cta p { color: #EEF4FD; margin: 14px 0 0; max-width: 60ch; }
.cta .btn-primary, .cta .btn-cyan { background: #fff; color: var(--pri); box-shadow: none; }
.cta .btn-primary:hover, .cta .btn-cyan:hover { background: var(--b100); color: var(--b900); }
.cta .btn-onDark, .cta .btn-ghost { background: rgba(255, 255, 255, .16); color: #fff; }
.cta .btn-onDark:hover, .cta .btn-ghost:hover { background: rgba(255, 255, 255, .26); }
.cta :focus-visible { outline-color: #fff; }
.cta-actions { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 28px; }
@media (max-width: 620px) { .cta { padding: 40px 22px; border-radius: var(--r-lg); } }

/* ── Footer ────────────────────────────────────────────────────────────── */
.footer { background: linear-gradient(180deg, #fff, var(--b100)); color: var(--text-2); padding: 72px 0 28px; }
.footer-grid { display: grid; grid-template-columns: minmax(0, 1.5fr) repeat(5, minmax(0, 1fr)); gap: 32px; }
/* Six columns stop fitting well before the mobile breakpoint — step down to
   three first, otherwise the section headings wrap mid-word around 1000px. */
@media (max-width: 1100px) { .footer-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 900px) { .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.footer h4 {
  font-family: 'DM Sans', sans-serif; font-size: 14px; font-weight: 600; color: #0A2B63; margin-bottom: 14px;
}
.footer ul { list-style: none; padding: 0; }
.footer li { margin-bottom: 9px; }
.footer a { color: var(--text-2); font-size: 14.5px; }
.footer a:hover { color: var(--pri); text-decoration: none; }
/* A link a thumb has to hit is 44px high, as the buttons are (.btn-sm). The list rows were 24px
   of text with a 9px gap; the row is now the target, so the gap moves inside it. */
@media (pointer: coarse) {
  .footer li { margin-bottom: 0; }
  .footer li a, .footer-base a { display: inline-flex; align-items: center; min-height: 44px; }
}
.footer .brand span { color: var(--text); }
.footer .about { font-size: 14.5px; color: var(--text-2); margin-top: 14px; max-width: 34ch; }
/* The app-store badges. Another organisation's mark is not ours to restyle, so these follow nothing. */
.footer-apps { margin-top: 40px; }
.footer-apps h4 { margin-bottom: 0; }
.footer-apps p { font-size: 14.5px; color: var(--text-2); margin-top: 8px; max-width: 52ch; }
.app-badges { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 14px; }
/* `width: auto` against the height, so the intrinsic aspect ratio decides — the two
   badges genuinely differ (3.36 against 2.99 on their visible buttons) and forcing a
   common width would squash one of them, which both vendors forbid outright. The 8px
   gap is small on purpose: Google's file already carries ~10px of clear space at this
   scale, so the optical gap is the ~18px it should be, and shrinking it with a negative
   margin would eat the clear space Google's guidelines require. */
.app-badges img { display: block; width: auto; }
.app-badges .badge-apple { height: 40px; }
.app-badges .badge-google { height: 60px; }
.footer-base {
  margin-top: 44px; padding-top: 22px;
  display: flex; flex-wrap: wrap; gap: 12px; justify-content: space-between; font-size: 13.5px; color: var(--muted);
}

/* ══ Docs layout ═══════════════════════════════════════════════════════ */
.docs { display: grid; grid-template-columns: 264px minmax(0, 1fr) 216px; gap: 40px; align-items: start; }
@media (max-width: 1200px) { .docs { grid-template-columns: 250px minmax(0, 1fr); } .toc { display: none; } }
@media (max-width: 900px) { .docs { grid-template-columns: minmax(0, 1fr); gap: 0; } }

.doc-nav {
  position: sticky; top: calc(var(--nav-h) + 24px); max-height: calc(100vh - var(--nav-h) - 48px);
  overflow-y: auto; padding: 32px 0 40px;
}
.doc-nav h4 {
  font-family: 'DM Sans', sans-serif; font-size: 13.5px; font-weight: 600; color: var(--muted); margin: 22px 0 8px;
}
.doc-nav h4:first-child { margin-top: 0; }
.doc-nav a {
  display: block; padding: 7px 12px; border-radius: var(--r-sm); font-size: 14.5px;
  color: var(--text-2);
}
.doc-nav a:hover { background: var(--b50); text-decoration: none; color: var(--pri); }
.doc-nav a[aria-current="page"] {
  background: var(--b100); color: var(--pri); font-weight: 600;
}
@media (max-width: 900px) {
  .doc-nav {
    position: static; max-height: none; padding: 16px 0;
    margin-bottom: 8px;
  }
  .doc-nav details { background: var(--b50); border-radius: var(--r); padding: 10px 14px; }
  /* The disclosure that opens the whole guide list on a phone. 25px tall before,
     and it is the only way to reach the other twenty-five guides from here. */
  .doc-nav summary { font-weight: 600; cursor: pointer; font-size: 15px;
                     min-height: 30px; display: flex; align-items: center; gap: 8px; }
  /* ledgr.js closes the list on a phone, and a closed summary with its marker
     hidden (`list-style: none`) read as a heading rather than as a menu. The
     chevron says it opens; the guide's name says where the reader already is. */
  .doc-nav summary::-webkit-details-marker { display: none; }
  .doc-nav summary::after {
    content: ''; flex-shrink: 0; margin-left: auto; width: 8px; height: 8px;
    border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
    transform: translateY(-2px) rotate(45deg); transition: transform .15s;
  }
  .doc-nav details[open] > summary::after { transform: translateY(2px) rotate(-135deg); }
  .doc-nav-here {
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-weight: 500; color: var(--muted);
  }
  .doc-nav-here::before { content: '·'; margin-right: 8px; }
}

/* ── Guide search ──────────────────────────────────────────────────────
   Ships with the `hidden` attribute and is revealed by ledgr.js, so a
   reader with no JavaScript never sees a box that would not search. The
   results panel is absolutely positioned so opening it never reflows the
   guide list underneath — a list that jumps as you type is unusable. */
.doc-search { position: relative; margin: 0 0 20px; }
.doc-search input {
  width: 100%; min-height: 44px; padding: 9px 12px; font: inherit; font-size: 14px;
  color: var(--text); background: var(--card);
  border-radius: var(--r-sm);
}
.doc-search input::placeholder { color: var(--faint); }
.doc-search input:focus {
  outline: none; border-color: var(--sec); box-shadow: 0 0 0 3px rgba(0, 131, 143, .14);
}
.doc-search-results {
  position: absolute; z-index: 20; top: calc(100% + 6px); left: 0; right: 0;
  background: var(--card); border-radius: var(--r-sm);
  box-shadow: var(--shadow);
  max-height: 340px; overflow-y: auto; padding: 6px;
}
.doc-search-results a {
  display: block; padding: 8px 10px; border-radius: 6px; border-left: 0;
  font-size: 14px; color: var(--text-2);
}
.doc-search-results a strong { display: block; font-weight: 600; }
.doc-search-results a small { display: block; font-size: 12px; color: var(--muted); margin-top: 1px; }
.doc-search-results a:hover,
.doc-search-results a.active { background: var(--b100); color: var(--pri); text-decoration: none; }
.doc-search-results a:hover small,
.doc-search-results a.active small { color: var(--pri); }
.doc-search-empty { margin: 0; padding: 10px; font-size: 13.5px; color: var(--muted); }

.doc-main { padding: 34px 0 88px; min-width: 0; }
.crumbs { font-size: 13px; color: var(--muted); margin-bottom: 14px; }
.crumbs a { color: var(--muted); }
.crumbs a:hover { color: var(--pri); }
.crumbs span { margin: 0 6px; color: var(--faint); }

/* One page-title scale for the guides and the accountant directory (a bare <h1> there was the
   browser's 32px with the lede touching it, against 34-48px and a 14px gap everywhere else). */
.doc-main h1, .directory h1 { font-size: clamp(34px, 4vw, 48px); font-weight: 600; letter-spacing: -.028em; }
.doc-main > p.lede, .directory > p.lede { margin-top: 14px; font-size: 17px; }
.doc-main h2 {
  font-size: 28px; font-weight: 600; letter-spacing: -.02em; margin: 56px 0 14px; padding-top: 14px; scroll-margin-top: calc(var(--nav-h) + 20px);
}
.doc-main h3 { font-size: 19px; font-weight: 600; margin: 30px 0 10px; scroll-margin-top: calc(var(--nav-h) + 20px); }
.doc-main p { margin-top: 12px; color: var(--text-2); }
.doc-main ul, .doc-main ol { margin-top: 12px; color: var(--text-2); }
.doc-main li { margin-bottom: 7px; }
.doc-main li > ul, .doc-main li > ol { margin-top: 6px; }
.doc-main hr { border: 0; margin: 44px 0; }
.doc-main table { margin-top: 18px; font-size: 14.5px; }
/* Reference tables can be wider than the content column — the developer page's
   endpoint catalogue is three columns of paths. Let the table itself scroll
   rather than the page — no per-page wrapper markup needed. A `.matrix` has its own
   `.matrix-scroll`, and a second scroller inside it fights the first. */
@media (max-width: 1100px) {
  .doc-main table:not(.matrix) { display: block; max-width: 100%; overflow-x: auto; overscroll-behavior-x: contain; }
}
.doc-main th, .doc-main td { padding: 11px 14px; text-align: left; vertical-align: top; }
.doc-main tbody tr:nth-child(even) > * { background: var(--b50); }
.doc-main td:first-child, .doc-main tbody th:first-child { border-radius: var(--r-sm) 0 0 var(--r-sm); }
.doc-main td:last-child { border-radius: 0 var(--r-sm) var(--r-sm) 0; }
.doc-main thead th { font-weight: 600; font-size: 13.5px; color: var(--muted); }
.doc-main :not(pre) > code {
  background: var(--b100); border-radius: 6px;
  padding: 1.5px 6px; font-size: .87em; color: var(--text);
  overflow-wrap: anywhere;
}
.doc-main img { border-radius: var(--r); box-shadow: var(--shadow-sm); }

/* Numbered step list */
.steps { list-style: none; padding: 0; margin-top: 22px; counter-reset: step; }
.steps > li { position: relative; padding: 0 0 22px 46px; margin: 0; counter-increment: step; }
.steps > li:last-child { padding-bottom: 0; }
.steps > li::before {
  content: counter(step); position: absolute; left: 0; top: -2px;
  width: 30px; height: 30px; border-radius: 50%;
  background: var(--pri); color: #fff;
  font-size: 13px; font-weight: 600;
  display: flex; align-items: center; justify-content: center;
}
.steps > li > strong { display: block; font-size: 16px; color: var(--text); margin-bottom: 4px; }
.steps p { margin-top: 6px; }

/* Callouts */
.note { border-radius: var(--r); padding: 16px 20px; margin-top: 22px; font-size: 15px; background: var(--info-bg); color: #14555C; }
.note strong { color: #00636C; }
.note.warn { background: var(--warn-bg); color: #6E3403; }
.note.warn strong { color: var(--warn); }
.note.tip { background: var(--success-bg); color: #0A4D35; }
.note.tip strong { color: var(--success); }
.note.plan { background: var(--b100); color: var(--text); }
.note.plan strong { color: var(--pri); }
.note p { color: inherit; margin-top: 6px; }
.note p:first-child { margin-top: 0; }
.note a { color: inherit; font-weight: 600; text-decoration: underline; }

/* In-page table of contents */
.toc { position: sticky; top: calc(var(--nav-h) + 34px); padding: 34px 0; font-size: 13.5px; }
.toc h4 {
  font-family: 'DM Sans', sans-serif; font-size: 13.5px; font-weight: 600; color: var(--muted); margin-bottom: 10px;
}
.toc a { display: block; padding: 5px 12px; border-radius: 999px; color: var(--text-2); }
.toc a:hover { color: var(--pri); text-decoration: none; }
.toc a.active { color: var(--pri); background: var(--b100); font-weight: 600; }

/* The closing call to action.

   `.cta` declares no margin of its own, and the whole doc body is laid out on TOP
   margins — `.doc-main p` 12px, `h2` 52px, `.note` 22px, `.doc-pager` 56px — so a
   `.cta` following any of them sat flush against it, with 56px of air underneath and
   none above. Most visible on the pages that close on a `.note`, where a tinted
   callout and the gradient panel met on a shared edge and read as one broken block.

   Scoped to `.doc-main` deliberately. On the marketing pages `.cta` sits inside a
   padded `<section>` or carries its own inline `margin-top`, so a global rule would
   add a second gap to spacing somebody has already chosen. 52px matches the `h2`
   rhythm this block is a peer of. */
.doc-main .cta { margin-top: 52px; }

/* Prev/next */
.doc-pager { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; margin-top: 56px; }
/* Stacked, "next" is no longer the right-hand card, so right-aligning it just
   makes two cards that disagree about where their text starts. */
@media (max-width: 620px) { .doc-pager { grid-template-columns: minmax(0, 1fr); } .doc-pager .next { text-align: left; } }
.doc-pager a {
  border-radius: var(--r); padding: 16px 20px; color: inherit; background: var(--b50);
  transition: background-color .14s ease;
}
.doc-pager a:hover {  text-decoration: none; box-shadow: var(--shadow-sm); }
.doc-pager small { display: block; font-size: 13px; font-weight: 600; color: var(--muted); }
.doc-pager b { display: block; margin-top: 4px; font-size: 15.5px; color: var(--pri); font-weight: 600; }
.doc-pager .next { text-align: right; }

/* ── Code blocks ───────────────────────────────────────────────────────── */
.code { position: relative; margin-top: 20px; border-radius: var(--r); overflow: hidden; background: var(--code-bg); }
.code-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 8px 12px 8px 16px; background: var(--b100); }
.code-head span { font-size: 13px; font-weight: 600; color: var(--muted); }
.code-copy { background: var(--card); color: var(--pri); border: 0; border-radius: 999px; font-size: 12.5px; font-weight: 600; padding: 6px 14px; cursor: pointer; font-family: 'DM Sans', sans-serif; min-height: 30px; }
.code-copy:hover { background: var(--b200); }
.code-copy[data-done="true"] { color: var(--success); }
.code pre {
  margin: 0; padding: 15px 16px; overflow-x: auto; color: var(--code-fg);
  font-size: 13px; line-height: 1.62; tab-size: 2;
}
.code pre code { background: none; border: 0; padding: 0; color: inherit; font-size: inherit; }
/* A sample written without its `.code` wrapper still scrolls inside itself: the
   webhook envelope on /developers/ was a bare <pre> and widened the whole page to
   755px on a 390px phone. */
.doc-main pre { overflow-x: auto; }

/* Method pills reused from the API sandbox */
.m {
  display: inline-block; min-width: 52px; text-align: center; color: #fff;
  font-family: 'JetBrains Mono', monospace; font-size: 10.5px; font-weight: 600;
  padding: 3px 7px; border-radius: 5px; letter-spacing: .03em;
}
.m.get { background: var(--success); } .m.post { background: #1A5BC0; }
.m.put { background: var(--warn); } .m.delete { background: var(--error); }

.ep-table { margin-top: 20px; font-size: 14px; }
.ep-table td:first-child { width: 76px; }
.ep-table td code { white-space: nowrap; }
@media (max-width: 640px) {
  .doc-main .ep-table { display: block; overflow: visible; }
  .ep-table tbody { display: block; }
  .ep-table tr { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 4px 10px; padding: 10px 12px; border-radius: var(--r-sm); }
  .ep-table tr:nth-child(even) { background: var(--b50); }
  .doc-main .ep-table td { display: block; width: auto; padding: 0; background: none; }
  .ep-table td:nth-child(3) { grid-column: 1 / -1; }
  .ep-table td code { white-space: normal; overflow-wrap: anywhere; }
}

/* ── Misc ──────────────────────────────────────────────────────────────── */
.pill-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 20px; }
.pill { font-size: 13.5px; font-weight: 500; padding: 6px 14px; border-radius: 999px;
  background: var(--b100); color: var(--text-2); }
.pill--onDark { background: var(--b100); color: var(--text-2); }

/* Small semantic variants of .pill. Every status pill on the site was written
   as `class="pill" style="font-size:11px; background:var(--success-bg); …"` —
   the same four declarations retyped at each call site, which is how two of
   them came to disagree about the green. Defined after .pill, not before: both
   are single-class selectors, so the later rule is the one that wins. */
.pill--sm { font-size: 11px; font-weight: 600; padding: 3px 9px; white-space: nowrap; }
.pill--ok { background: var(--success-bg); color: var(--success); }
.pill--warn { background: var(--warn-bg); color: var(--warn); }
.pill--bad { background: var(--error-bg); color: var(--error); }
.pill--info { background: var(--info-bg); color: var(--sec); }

/* ── Illustrative stills ───────────────────────────────────────────────────
   The product stills used by the logistics and manufacturing showcases. Same
   idea as `.shot` in the hero — a mock, never a screenshot — but these sit in
   a light card beside body copy rather than in the dark hero band.

   They are classes rather than inline style because the markup lives in
   `partials/still-pod.html` and `partials/still-bom.html` and is drawn by two
   pages each. A still copied onto the second page is a still that stops
   matching the first the next time one of them is edited.
   ------------------------------------------------------------------------ */
.still { padding: 0; overflow: hidden; }
.still-head { padding: 15px 20px; color: #fff; display: flex; align-items: center; justify-content: space-between; gap: 14px; }
.still-head--dark { background: var(--b100); color: var(--text); }
.still-head--brand { background: var(--pri); }
.still-head .lbl { font-size: 12.5px; font-weight: 600; }
.still-head--brand .lbl { color: #EEF4FD; }
.still-head--dark .lbl { color: var(--muted); }
.still-head .val { font-size: 18px; font-weight: 600; margin-top: 2px; }
.still-body { padding: 16px 20px; }
.still-foot { background: var(--surface); padding: 14px 20px; }
.still-label { font-size: 12.5px; font-weight: 600; color: var(--muted); }
.still-sign { display: block; margin-top: 4px; }
.still figcaption { font-size: 12.5px; color: var(--muted); margin-top: 10px; }

/* A bordered list of rows — the trip's stops, and MRP's suggestions. */
.rowset { border-radius: var(--r-sm); overflow: hidden; background: var(--b50); }
.rowline { display: flex; align-items: center; gap: 10px; padding: 11px 13px; }
.rowline + .rowline { }
.rowline .t { flex: 1; min-width: 0; }
.rowline .t b { display: block; font-size: 13.5px; font-weight: 600; }
.rowline .t span { display: block; font-size: 11.5px; color: var(--muted); }
.rowline .n {
  width: 22px; height: 22px; flex: none; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-family: 'JetBrains Mono', monospace; font-size: 11px; font-weight: 600;
  background: var(--info-bg); color: var(--sec); }

/* One line of a bill of materials. The indent is set per row by the page,
   because the depth is a fact about that line rather than about the class. */
.bom-row { display: flex; align-items: baseline; gap: 10px; padding: 7px 0; font-size: 13.5px; }
.bom-row + .bom-row { }
.bom-row .lead { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 8px; }
.bom-row .lvl { font-family: 'JetBrains Mono', monospace; font-size: 11.5px; color: var(--faint); flex: none; }
.bom-row .qty { font-family: 'JetBrains Mono', monospace; font-size: 12px; color: var(--muted); white-space: nowrap; }
.bom-row .amt { font-family: 'JetBrains Mono', monospace; font-size: 13px; color: var(--text-2); white-space: nowrap; min-width: 74px; text-align: right; }
.bom-total {
  display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
  margin-top: 10px; padding-top: 11px; font-weight: 700; font-size: 15px;
}

@media (max-width: 440px) {
  .still-head { flex-direction: column; align-items: flex-start; gap: 8px; }
  .still-body, .still-foot { padding: 13px 14px; }
  /* The quantity is the first thing that can go: the description and the money
     are what the still is showing. */
  .bom-row .qty { display: none; }
}

.kbd {
  font-family: 'JetBrains Mono', monospace; font-size: 12px; background: var(--card);
  border-radius: 5px; padding: 2px 6px; color: var(--text-2);
}

/* ── Product page: one block per module ────────────────────────────────── */
.mod-block { scroll-margin-top: calc(var(--nav-h) + 16px); }
.mod-block + .mod-block { margin-top: 88px; }
.mod-head { display: flex; align-items: flex-start; gap: 18px; margin-bottom: 26px; }
.mod-head .icon-tile { margin-bottom: 0; width: 52px; height: 52px; }
.mod-head .icon-tile svg { width: 25px; height: 25px; }
.mod-head h2 { font-size: clamp(23px, 2.6vw, 29px); }
.mod-head .meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.mod-head .lede { margin-top: 10px; font-size: 16px; }
@media (max-width: 560px) { .mod-head { flex-direction: column; gap: 12px; } }

.feat { border-radius: var(--r); padding: 20px 22px; background: var(--b50); }
.feat h4 { font-family: 'DM Sans', sans-serif; font-size: 15px; font-weight: 700; margin: 0 0 6px; }
.feat p { font-size: 14px; color: var(--neutral); }

/* Jump bar on the product page */
.jump { display: flex; flex-wrap: wrap; gap: 8px; }
.jump a {
  font-size: 14px; font-weight: 500; padding: 7px 15px; border-radius: 999px;
  background: var(--b100); color: var(--text-2);
}
.jump a:hover {  color: var(--pri); text-decoration: none; }
/* On a phone this is ONE ROW that scrolls sideways, and the history is worth
   reading before changing it back — it has been both ways.

   It wrapped until now, and wrapping is honest about what is there: every chip
   is reachable with no gesture. What it costs is height, and these bars are
   `position: sticky` under the nav on all seven pages that carry one, so the
   height is spent twice — once on arrival and again over the section the reader
   just jumped to. Measured at 390×844 before: /products/ 169px, /terms/ 149px,
   the other five 110px — 13–20% of the viewport, pinned there.

   A single-row scroller was tried before this and reverted, for a reason that
   was not the row: it carried `scrollbar-width: none` and no fade, so four
   fifths of /products/'s module index sat off-screen with NOTHING on screen to
   say so. Undiscoverable navigation is worse than tall navigation. So the row
   comes back only with the affordance it lacked:

     • it full-bleeds to the viewport edge, so the next chip is cut by the
       screen rather than stopping short of a gutter — a half-chip is the
       plainest possible statement that the row continues;
     • the two edge gradients below are the classic cover/shadow pair. The
       `local` layers scroll with the content and the `scroll` layers are fixed
       to the box, so a shadow shows only at an edge that HAS more content
       behind it and vanishes when the row is scrolled to its end. Pure CSS,
       no script, and it works on iOS Safari where the scrollbar is invisible
       anyway — which is exactly the case the first attempt got wrong.

   The chips also stop shrinking. Shrinking existed to save rows; with one row
   there are no rows to save, and 13.5px type in a 32px-tall pill is a better
   tap target than the 12px/26px the `:has(a:nth-child(14))` rule had cut
   /products/ down to.

   1024px, not the 720px the shrink rules used, because that is the width the
   NAV already switches to compact chrome at — one boundary for "this is a
   phone or a tablet", not two. It is also the width that catches iPad
   portrait: measured at 768, /products/ still stacked to 131px of sticky bar
   and /terms/, /privacy/ and /accountants/ to 85px. */
@media (max-width: 1024px) {
  .jump {
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* /support/'s bar is `display: flex` on the .wrap, and a flex item's
       min-width defaults to its content — so without this the strip refuses to
       shrink and the PAGE scrolls sideways instead of the row. */
    min-width: 0;
    /* Cancel the gutter so the row runs edge to edge, then put it back inside
       as padding so the first and last chip still line up with the text. */
    margin-inline: calc(-1 * var(--wrap-pad, 24px));
    padding-inline: var(--wrap-pad, 24px);
    /* `overflow-x` makes the OTHER axis a clip too, and a chip fills the row
       exactly — measured 38.3px tall in a 38.3px strip, 0px of clearance. The
       site's one focus style is `3px solid + 2px offset`, so a keyboard user
       tabbing along these chips would have seen no ring at all. The padding
       gives the ring 6px to paint in and the equal negative margin gives it
       back, so nothing above or below this bar moves. */
    padding-block: 6px;
    margin-block: -6px;
    /* Cover, cover, shadow, shadow — order matters, the covers paint over the
       shadows and are the layers that scroll away. --page is the background
       every one of these bars sits on. */
    background-image:
      linear-gradient(to right, var(--page) 40%, rgba(244, 248, 254, 0)),
      linear-gradient(to left,  var(--page) 40%, rgba(244, 248, 254, 0)),
      linear-gradient(to right, rgba(13, 71, 161, .14), rgba(13, 71, 161, 0)),
      linear-gradient(to left,  rgba(13, 71, 161, .14), rgba(13, 71, 161, 0));
    background-position: left center, right center, left center, right center;
    background-repeat: no-repeat;
    background-size: 26px 100%, 26px 100%, 16px 100%, 16px 100%;
    background-attachment: local, local, scroll, scroll;
  }
  .jump::-webkit-scrollbar { display: none; }
  /* Without these a long label wraps to two lines inside its own pill and the
     flex row shrinks every chip to fit rather than overflowing — which is the
     wrapped bar again, one level down. */
  .jump a { flex: 0 0 auto; white-space: nowrap; }
}

/* ==========================================================================
   API playground (/developers/playground/)
   --------------------------------------------------------------------------
   The console used to ship its own document — its own :root, its own type,
   its own chrome — so it looked like a different product. Everything below is
   scoped to .pg* and draws entirely on the tokens above, so it inherits the
   site's palette, radii and shadows and cannot drift away from them again.
   ========================================================================== */
/* The middle column is where the work happens: the shapes, the editable request and
   the response it replaces. The sandbox column is a sign-in form and nothing else
   since the runner moved, so it is narrower than the 340px it needed when it also
   had to hold a JSON body. */
.pg { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 20px; align-items: start; }
/* Three panels, then two, then one. The middle step drops the sandbox to a full
   width row beneath the index and the detail; the last drops everything into a
   single column.
   The single-column step is at 900px rather than 760 because a portrait tablet
   is the width this console actually falls apart at: 230px of monospace paths
   beside a detail pane holding request and response bodies leaves neither one
   readable, and both were scrolling sideways inside a page that was not. */
@media (max-width: 1180px) { .pg { grid-template-columns: 230px minmax(0, 1fr); } .pg-side { grid-column: 1 / -1; } }
@media (max-width: 900px)  { .pg { grid-template-columns: minmax(0, 1fr); gap: 16px; } .pg-side { grid-column: auto; } }

.pg-panel {
  background: var(--card); border-radius: var(--r);
  padding: 16px; box-shadow: var(--shadow-sm);
}
/* Sticky only where there is room beside the content; on a narrow screen the
   panels are stacked and pinning one would hide the thing below it.

   The index is a flex column pinned to the viewport: its search header does not
   shrink and does not scroll, and only the list beneath it scrolls. The earlier
   build made the whole panel scroll with the search box sticky INSIDE it — two
   nested scroll/sticky contexts, whose height maths disagreed (it pinned at
   nav-h + 16 but sized itself against nav-h + 48), so the last rows fell past
   the panel's bottom edge and the sticky header could jump over them. One scroll
   region, one height, and the list can never hide a row again.

   The flex column applies at EVERY width; only the pinning is desktop-only. The
   stacked layout kept the old shape — the whole panel scrolling, the search box
   sticky inside it — and its `top: 0` resolved inside the panel's 16px padding,
   so on a phone the rows slid up behind the search box and showed through again
   in the strip above it. */
.pg-index { display: flex; flex-direction: column; overflow: hidden; }
@media (min-width: 901px) {
  .pg-index {
    position: sticky; top: calc(var(--nav-h) + 16px);
    max-height: calc(100vh - var(--nav-h) - 32px);
  }
}
@media (min-width: 1181px) {
  .pg-side { position: sticky; top: calc(var(--nav-h) + 16px); max-height: calc(100vh - var(--nav-h) - 32px); overflow-y: auto; }
}

.pg-h {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .09em;
  color: var(--faint); margin: 0 0 10px;
}
.pg-cat {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .09em;
  color: var(--faint); margin: 16px 0 6px;
}
.pg-index .pg-cat:first-child { margin-top: 0; }

.pg-ep {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  padding: 6px 8px; margin-bottom: 2px; border: 0; border-radius: var(--r-sm);
  background: transparent; cursor: pointer; font: inherit;
}
.pg-ep:hover { background: var(--surface); }
.pg-ep.active { background: var(--b100); }
.pg-ep .pg-path {
  font-family: 'JetBrains Mono', monospace; font-size: 11.5px; color: var(--text-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.pg-method {
  font-size: 9.5px; font-weight: 700; letter-spacing: .04em; color: #fff;
  padding: 3px 6px; border-radius: 5px; min-width: 42px; text-align: center; flex-shrink: 0;
}
.pg-method.GET { background: var(--success); }
.pg-method.POST { background: var(--chart-blue); }
.pg-method.PUT, .pg-method.PATCH { background: var(--warn); }
.pg-method.DELETE { background: var(--error); }

.pg-auth-badge {
  font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: 3px 8px; border-radius: 5px; background: var(--surface); color: var(--neutral);
  }
.pg-auth-badge.none { background: var(--success-bg); color: var(--success); }
.pg-auth-badge.jwt { background: var(--info-bg); color: var(--sec); }
.pg-auth-badge.api-key { background: var(--pri-bg); color: var(--pri); border-color: #DDD6FE; }
.pg-auth-badge.admin { background: var(--warn-bg); color: var(--warn); }
/* The five credential kinds nobody signing in here holds. They share one neutral
   treatment on purpose: the distinction that matters to a reader is "this is
   not my session", not which of the five it is. `signed-url` is the odd one —
   there is no token at all, the HMAC in the query string is the credential —
   but it lands in the same place for the reader: not callable from this console. */
.pg-auth-badge.driver-token,
.pg-auth-badge.supplier-token,
.pg-auth-badge.customer-token,
.pg-auth-badge.portal-token,
.pg-auth-badge.signed-url,
.pg-auth-badge.oidc { background: var(--surface); color: var(--neutral);  }

.pg-card { background: var(--card); border-radius: var(--r); padding: 24px; box-shadow: var(--shadow-sm); }
.pg-sig { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 6px 0 0; }
/* An endpoint path is one unbreakable token, and some of them are long
   (`/api/consolidation-groups/{id}/intercompany-transactions`). Without this it
   sets the card's min-content width and the console scrolls sideways on a
   phone. */
.pg-sig code { font-size: 14px; font-weight: 600; color: var(--text); background: none; padding: 0; overflow-wrap: anywhere; min-width: 0; }
.pg-summary { color: var(--neutral); font-size: 14.5px; margin-top: 12px; }
.pg-card .pg-h { margin-top: 22px; }

.pg-pre {
  background: var(--code-bg); color: var(--code-fg); border-radius: var(--r-sm);
  padding: 14px 16px; font-family: 'JetBrains Mono', monospace; font-size: 12px; line-height: 1.55;
  overflow-x: auto; -webkit-overflow-scrolling: touch; white-space: pre;
}
.pg-pre--resp { white-space: pre-wrap; max-height: 340px; overflow: auto; margin-top: 8px; }

.pg-empty {
  border-radius: var(--r); padding: 28px;
  text-align: center; color: var(--muted); font-size: 14px;
}
.pg-side .pg-empty { padding: 18px; font-size: 13px; }

.pg-auth, .pg-runner { background: var(--surface); border-radius: var(--r-sm); padding: 14px; }
.pg-status { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--text-2); margin: 0 0 10px; }
.pg-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--faint); flex-shrink: 0; }
.pg-dot.on { background: var(--success); }

.pg-auth label, .pg-runner label {
  display: block; font-size: 11.5px; font-weight: 600; color: var(--neutral); margin: 10px 0 4px;
}
.pg-auth input, .pg-runner input, .pg-runner textarea {
  width: 100%; padding: 8px 10px; border-radius: 6px;
  background: var(--card); color: var(--text); font-family: inherit; font-size: 12.5px;
}
.pg-runner textarea { font-family: 'JetBrains Mono', monospace; font-size: 11.5px; line-height: 1.5; resize: vertical; }
.pg-auth input:focus, .pg-runner input:focus, .pg-runner textarea:focus { outline: 3px solid var(--sec); outline-offset: 1px;  }
/* A password field with a reveal button inside it. The input keeps its own right
   padding so a long password never runs under the button, which is the failure the
   obvious absolute-positioned version has and only shows up with a real password
   in the box. */
.pg-reveal { position: relative; }
.pg-reveal input { padding-right: 58px; }
.pg-reveal-btn {
  position: absolute; top: 50%; right: 5px; transform: translateY(-50%);
  border-radius: 5px; background: var(--surface);
  color: var(--neutral); font-family: inherit; font-size: 10.5px; font-weight: 600;
  padding: 4px 8px; cursor: pointer; line-height: 1;
}
/* The visible chip is 45x21, which is the right size beside a 35px input and the wrong
   size under a thumb. The pseudo-element extends the TAP target — measured at 390dp by
   hit-testing outward from the chip's centre, 45x21 becomes 55x38 — without moving a
   pixel of the chip; growing the chip itself would either overflow the 35px input or push
   the password text under it. It stays inside the input's 58px right padding, so it never
   covers text the reader is trying to select. */
.pg-reveal-btn::after { content: ''; position: absolute; inset: -10px -6px; }
.pg-reveal-btn:hover { color: var(--text); border-color: var(--faint); }
.pg-reveal-btn:focus-visible { outline: 3px solid var(--sec); outline-offset: 1px; }

.pg-row { display: flex; gap: 8px; margin-top: 10px; }
.pg-row .btn { flex: 1; justify-content: center; }
.pg-note { font-size: 11.5px; line-height: 1.6; color: var(--muted); margin-top: 12px; }

/* --------------------------------------------------------------------------
   Try it — the request form, inside the detail card
   --------------------------------------------------------------------------
   It used to live in the 340px sandbox column beside the sign-in form, which
   made a forty-line JSON body something you edited by scrolling, and put what
   you sent and what came back in two different columns. It sits in the detail
   pane now, between the endpoint's own shapes and the response it produces, so
   the whole round trip reads down one column.
   -------------------------------------------------------------------------- */
.pg-card .pg-runner { padding: 16px 18px; }
/* Two short fields on one row above the body, which takes the full width. They
   collapse to one column below 640px, where a half-width path input is narrower
   than the paths it holds. */
.pg-try-grid { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 14px; }
@media (max-width: 640px) { .pg-try-grid { grid-template-columns: minmax(0, 1fr); gap: 0; } }
.pg-try-field label { margin-top: 0; }
.pg-card .pg-runner input, .pg-card .pg-runner textarea { font-size: 13px; }
.pg-card .pg-runner #pg-run-path { font-family: 'JetBrains Mono', monospace; font-size: 12.5px; }
.pg-card .pg-runner textarea { min-height: 160px; }
/* The sentence at the top of the disclosure — what the example is, or what the
   endpoint answers where there is no example. Prose rather than the small print
   the other .pg-note instances are, because it is the first thing read there. */
.pg-note--lead { margin-top: 0; margin-bottom: 10px; font-size: 12.5px; color: var(--neutral); }
/* The published shape, as a disclosure under the live result. It opens by default —
   a reader who is reading rather than running must see it without a click — and the
   script shuts it once a real body has landed, so the documentation folds away
   instead of pushing the real answer down the page. */
.pg-doc { border-radius: var(--r-sm); margin-top: 12px; }
.pg-doc > summary {
  cursor: pointer; list-style: none; padding: 9px 12px;
  font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em;
  color: var(--neutral); border-radius: var(--r-sm);
}
.pg-doc > summary::-webkit-details-marker { display: none; }
/* The caret is drawn rather than left to the browser's own marker, which is
   unstyleable in Safari and sits outside the padding everywhere else. */
.pg-doc > summary::before {
  content: '\203A'; display: inline-block; width: 13px; color: var(--faint);
  font-size: 14px; line-height: 1; transition: transform .12s ease;
}
.pg-doc[open] > summary::before { transform: rotate(90deg); }
.pg-doc[open] > summary { color: var(--text-2); border-radius: var(--r-sm) var(--r-sm) 0 0; }
.pg-doc > summary:hover { background: var(--surface); }
.pg-doc > summary:focus-visible { outline: 3px solid var(--sec); outline-offset: -2px; }
.pg-doc-body { padding: 12px; }
/* The live result sits directly under the Response heading, and only exists once
   something has been sent. */
#pg-resp:empty { display: none; }

.pg-status-line { font-family: 'JetBrains Mono', monospace; font-size: 11.5px; color: var(--neutral); margin: 10px 0 0; }
.pg-status-line.ok { color: var(--success); }
.pg-status-line.err { color: var(--error); }

/* --------------------------------------------------------------------------
   Finding one endpoint among ~800
   --------------------------------------------------------------------------
   The catalogue became the whole registered surface, so the index stopped
   being a list somebody scans and became one they search. Everything below
   exists to make that possible: a search field, method chips, and categories
   that stay shut until asked. Nothing here is decoration — an always-expanded
   index at this size is 800 rows of monospace and no way through it.
   -------------------------------------------------------------------------- */
.pg-search {
  width: 100%; padding: 8px 10px; margin-bottom: 8px;
  border-radius: 6px;
  background: var(--card); color: var(--text); font-family: inherit; font-size: 12.5px;
}
.pg-search:focus { outline: 3px solid var(--sec); outline-offset: 1px;  }

/* The search header is a fixed row at the top of the flex column: it keeps its
   height, never scrolls away, and the list below it owns the only scrollbar.
   Bled to the panel edges so the divider spans the full width. */
.pg-searchbar {
  flex: 0 0 auto; background: var(--card);
  margin: -16px -16px 0; padding: 16px 16px 12px;
  }

/* The one scroll region. Given room to breathe at top and bottom so the first
   category clears the divider and the last row never sits flush against the
   panel edge — the two places rows used to disappear. */
#pg-index {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  margin: 0 -16px -16px; padding: 12px 16px 16px;
  overscroll-behavior: contain;
}
@media (max-width: 900px) {
  /* Stacked: the panel is not pinned, but an unpinned list of ~1 300 rows is a
     wall between the search box and the detail pane below it. Categories stay
     collapsed until asked (the script's default), and the panel is capped so
     there is always a way past it — the list below the search box is the one
     thing in it that scrolls, exactly as on a desktop. */
  .pg-index { max-height: 62vh; }
}

/* The way back up from the detail on a stacked layout, where the list is a
   screen or more above the endpoint the reader just opened. Beside the list
   there is nothing to go back to, so it is not drawn there. */
.pg-back { display: none; }
@media (max-width: 900px) {
  .pg-back {
    display: inline-flex; align-items: center; min-height: 32px; margin: -6px 0 6px;
    padding: 0; border: 0; background: none; cursor: pointer;
    font: inherit; font-size: 13.5px; font-weight: 600; color: var(--pri);
  }
}

.pg-chips { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 8px; }
/* `min-height: 24px` is WCAG 2.5.8 (AA) — measured at 18px before, which is a
   miss on the one control a developer taps repeatedly on a phone. The type stays
   9.5px: these are five method labels in a row inside a 358px panel, so the pill
   grows by padding rather than by text. */
.pg-chip {
  font-size: 9.5px; font-weight: 700; letter-spacing: .04em; color: #fff;
  padding: 3px 9px; border: 0; border-radius: 5px; cursor: pointer; font-family: inherit;
  min-height: 24px; min-width: 34px;
}
.pg-chip.GET { background: var(--success); }
.pg-chip.POST { background: var(--chart-blue); }
.pg-chip.PUT, .pg-chip.PATCH { background: var(--warn); }
.pg-chip.DELETE { background: var(--error); }
/* Switched off reads as drained rather than hidden, so the row keeps its
   shape and the chip stays where the eye last left it. */
.pg-chip.off { background: var(--surface); color: var(--faint); box-shadow: inset 0 0 0 1px var(--border); }

.pg-result-count {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  font-size: 11px; color: var(--faint); margin: 0 0 10px;
}
/* Padding rather than a bigger font, for the reason above: it sits on one line
   beside the result count. 18px tall before. */
.pg-linkbtn {
  border: 0; background: none; padding: 4px 2px; cursor: pointer; font: inherit;
  font-size: 11px; color: var(--sec); text-decoration: underline; flex-shrink: 0;
  min-height: 24px;
}
.pg-linkbtn:hover { color: var(--pri); }

.pg-group { margin-bottom: 2px; }
.pg-cat-toggle {
  display: flex; align-items: center; gap: 6px; width: 100%; text-align: left;
  padding: 6px 4px; border: 0; border-radius: var(--r-sm);
  background: transparent; cursor: pointer; font: inherit;
}
.pg-cat-toggle:hover { background: var(--surface); }
.pg-cat-name {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .09em;
  color: var(--faint); flex: 1; min-width: 0;
}
.pg-cat-toggle.open .pg-cat-name { color: var(--text-2); }
.pg-caret { color: var(--faint); transition: transform .12s ease; flex-shrink: 0; }
.pg-cat-toggle.open .pg-caret { transform: rotate(90deg); }
.pg-count {
  font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--faint);
  background: var(--surface); border-radius: 9px; padding: 1px 6px; flex-shrink: 0;
}
.pg-eps { padding-left: 4px; }
.pg-eps[hidden] { display: none; }
.pg-blurb { font-size: 11px; line-height: 1.55; color: var(--muted); margin: 2px 4px 6px; }

/* Derived on the server from Plans.kt and ApiScopes, so these are the figures
   the licence guard and the key check will actually use. */
.pg-facts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.pg-fact {
  display: inline-flex; align-items: baseline; gap: 6px; font-size: 11.5px; color: var(--text-2);
  background: var(--surface); border-radius: var(--r-sm); padding: 5px 10px;
}
.pg-fact b {
  font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em;
  color: var(--faint);
}
.pg-fact code { font-size: 11.5px; background: none; padding: 0; color: var(--text); }

.pg-warn {
  font-size: 12.5px; line-height: 1.6; color: #7C4A02; margin-top: 14px;
  background: var(--warn-bg); border-radius: var(--r-sm); padding: 10px 12px;
}
.pg-row--left { justify-content: flex-start; }
.pg-row--left .btn { flex: 0 0 auto; }

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

/* ==========================================================================
   Support page enhancements (/support/)
   --------------------------------------------------------------------------
   Scoped to .sup* so they draw on the tokens above and cannot drift from the
   rest of the site. Three small touches: at-a-glance hero stats under the
   lede, a left accent rail on the four channel cards that colour-codes them
   to their icon tile, and a sticky jump bar that reads as a bar rather than a
   loose row of pills.
   ========================================================================== */

/* Hero stats — the answer to "is this worth reading" before the reading. Sits
   on the dark hero, so the numbers are light and the labels are muted. */
.sup-stats {
  display: flex; flex-wrap: wrap; gap: 14px 40px; margin-top: 32px;
  padding: 0; list-style: none;
}
.sup-stats li { display: flex; flex-direction: column; gap: 2px; }
.sup-stats b {
  font-family: 'JetBrains Mono', monospace; font-size: clamp(22px, 3vw, 28px);
  font-weight: 600; color: var(--text); letter-spacing: -.02em; line-height: 1.1;
}
.sup-stats span { font-size: 13.5px; color: var(--text-2); }

/* Accent channel cards — a coloured rail down the left edge that matches the
   card's icon tile, so the eye can pair "the teal one" with "email" without
   reading. Rides on top of .card, so it keeps the card's hover and shadow. */
.channel-card { position: relative; overflow: hidden; }
.sup-jumpbar {
  position: sticky; top: var(--nav-h); z-index: 40;
  background: var(--page);
  padding: 12px 0;
}
.sup-jumpbar .wrap { display: flex; align-items: center; }

/* ==========================================================================
   Responsive pass
   --------------------------------------------------------------------------
   Everything above already carries its own breakpoints; this block is the
   whole-page sweep — the three widths the site is actually read at (phone,
   tablet, large desktop) rather than the width one component stops fitting.
   It is LAST in the file on purpose: these rules refine what the component
   blocks set, so they have to come after them.
   ========================================================================== */

/* A table that scrolls sideways gives no sign that it does until you try. The
   pages that hold one say so, and only where it is true.
   `@media (max-width: 940px)` was the wrong instrument and hid the hint where it
   was needed most: whether a scroller overflows depends on ITS OWN column, not on
   the viewport, and every `.matrix` in the documentation layout overflows at every
   width including 1440px (a 760px min-width table in a 672px column). So the hint
   is displayed and `markScrollers()` in ledgr.js sets `hidden` from a measurement
   — which also means a hint whose sentence has stopped being true disappears. It
   starts hidden so the sentence is never shown before it has been checked, and
   with no JavaScript the reader still gets the native scrollbar. */
.scroll-hint { font-size: 12.5px; color: var(--muted); margin: 0 0 10px; }
.scroll-hint[hidden] { display: none; }

/* The cue that more lies to the side: a narrow fade on the edge that has more,
   gone from that edge at its end of the travel. Both from `data-more`, which
   markScrollers() keeps from the real scroll position, so the last column is never
   left faded: this used to end through a scroll-driven animation, which Firefox does
   not have, and the fade stayed over the last 34px for good. No script, no attribute,
   no fade; the scrollbar stays. A mask drops a box's shadow, so one that overflows
   goes without. */
[data-more] { --fade-l: #000; --fade-r: #000;
  -webkit-mask-image: linear-gradient(to right, var(--fade-l), #000 34px, #000 calc(100% - 34px), var(--fade-r));
  mask-image: linear-gradient(to right, var(--fade-l), #000 34px, #000 calc(100% - 34px), var(--fade-r)); }
[data-more~="left"] { --fade-l: transparent; }
[data-more~="right"] { --fade-r: transparent; }
[data-more=""] { -webkit-mask-image: none; mask-image: none; }
.matrix-scroll[data-overflowing] { box-shadow: none; }
[data-overflowing]:focus-visible { outline: 3px solid var(--sec); outline-offset: 2px; }
/* A scrollbar that stays visible, where the platform lets a page ask. */
.matrix-scroll::-webkit-scrollbar, .doc-main table:not(.matrix)::-webkit-scrollbar { height: 8px; }
.matrix-scroll::-webkit-scrollbar-thumb, .doc-main table:not(.matrix)::-webkit-scrollbar-thumb { background: var(--b200); border-radius: 4px; }
@supports not selector(::-webkit-scrollbar) {
  .matrix-scroll, .doc-main table:not(.matrix) { scrollbar-width: thin; scrollbar-color: var(--b200) transparent; }
}

/* On a phone a table of up to five columns reads as stacked rows, each cell under its
   own column's name from `data-label` (stackTables() in ledgr.js). A wider plan
   comparison stays a table: seven figures a row, stacked, is seven screens. */
@media (max-width: 640px) {
  table.stack, .matrix.stack, .matrix.stack:has(thead) { display: block; min-width: 0 !important; width: 100%; overflow: visible; }
  table.stack thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  table.stack tbody, table.stack tr, table.stack th, table.stack td { display: block; }
  table.stack tbody tr { padding: 10px 12px; border-radius: var(--r-sm); }
  table.stack tbody tr:nth-child(even) > * { background: none; }
  table.stack tbody tr:nth-child(even) { background: var(--b50); }
  table.stack th, table.stack td { padding: 3px 0; text-align: left; overflow-wrap: anywhere; }
  table.stack td[data-label]::before { content: attr(data-label); display: block; font-size: 12px; font-weight: 600; color: var(--muted); }
}

/* ── Large desktop ─────────────────────────────────────────────────────────
   A 1180px column on a 1920px screen leaves a third of the width empty on each
   side and squeezes six pricing cards into a strip. One token widens every
   .wrap on the site at once. */
@media (min-width: 1440px) {
  :root { --maxw: 1280px; }
  .lede { font-size: 17.5px; }
}

/* ── Tablet (portrait) ─────────────────────────────────────────────────────
   The awkward width: too narrow for three columns of prose, too wide to be
   treated as a phone. */
@media (max-width: 900px) {
  .section-head { margin-bottom: 36px; }
  .feature h3, .mod-head h2 { hyphens: auto; }
  .mod-block + .mod-block { margin-top: 56px; }
  .cta { padding: 44px 30px; }
}

/* ── Phone ─────────────────────────────────────────────────────────────────
   Actions become full-width blocks rather than a wrapped row of pills that all
   end at different points, and the dense components lose a few pixels of
   padding they cannot afford. */
@media (max-width: 560px) {
  .hero-actions, .cta-actions { flex-direction: column; align-items: stretch; }
  .hero-actions .btn, .cta-actions .btn { width: 100%; }
  .hero p.lede { font-size: 16.5px; }
  .card { padding: 20px; }
  .plan { padding: 20px 18px; }
  .plan .amount { font-size: 27px; }
  .plan-wide .amount { font-size: 28px; }
  .feat { padding: 16px 17px; }
  .band { padding: 52px 0; }
  .band-card { padding: 18px; }
  .stat { padding: 20px 16px; }
  .doc-main { padding: 24px 0 60px; }
  /* Two footer columns at 360px put one word on each line. */
  .footer { padding: 44px 0 24px; }
  .footer-base { margin-top: 32px; }

  /* ── Code and the developer pages ───────────────────────────────────────
     A sample is read on a phone as often as anywhere else — usually because
     somebody is checking a payload shape away from their desk. The block still
     scrolls sideways; this just stops it wasting width on padding. */
  .code pre { font-size: 12px; padding: 13px 13px; }
  .code-head { flex-wrap: wrap; padding: 8px 11px; }
  .code-head span { overflow-wrap: anywhere; }

  /* The API console. Its panels are already single-column by here; these are
     the paddings and type sizes that were still built for a 340px sidebar. */
  .pg-panel, .pg-card { padding: 16px; }
  .pg-searchbar { margin: -16px -16px 0; padding: 14px 16px 11px; }
  .pg-sig code { font-size: 13px; }
  .pg-pre { font-size: 11.5px; padding: 12px 13px; }
  .pg-index { max-height: 55vh; }
  .pg-facts { gap: 6px; }
}

/* Below this the two-column footer stops being a grid and starts being a
   ransom note. */
@media (max-width: 400px) {
  .footer-grid { grid-template-columns: minmax(0, 1fr); gap: 24px; }
}



/* ==========================================================================
   Licence-purchase dialog (/pricing/)
   --------------------------------------------------------------------------
   Moved out of an inline <style> in pricing.html together with that page's
   inline <script>, so the site can be served under a real CSP — see
   ContentSecurityPolicy.siteCsp. Attribute styles still need
   `style-src 'unsafe-inline'` (a nonce cannot cover an attribute), but a
   <style> ELEMENT is exactly what a tightened style-src would block.
   ========================================================================== */
#buy-overlay {
  position:fixed; inset:0; background:rgba(10,43,99,.42); z-index:100;
  display:flex; align-items:center; justify-content:center; padding:20px;
}
/* Higher specificity than the rule above, so the `hidden` attribute still
   wins. Setting `display:flex` inline instead beat [hidden]'s UA rule and
   the dialog was open on page load. */
#buy-overlay[hidden] { display:none }
.buy-dialog {
  background:#fff; border-radius:16px; max-width:440px; width:100%; padding:28px;
  box-shadow:var(--shadow-lg);
  /* On a short phone in landscape the dialog is taller than the screen. */
  max-height:calc(100vh - 40px); overflow-y:auto;
}
.term-btn { padding:9px 14px; border:0; border-radius:999px; background:var(--b100);
            font-size:13.5px; cursor:pointer; font-family:inherit; color:var(--text-2) }
.term-btn[aria-pressed="true"] { background:var(--pri); font-weight:600; color:#fff }
@media (max-width: 430px) {
  #buy-overlay { padding:12px }
  .buy-dialog { padding:20px }
  /* `.term-btn { flex:1; min-width:0; text-align:center }` was here and is gone with the
     fourth term: #buy-terms is a two-column grid at every width now, so the buttons already
     stretch to their column and a `flex` property on a grid item does nothing. A dead rule
     naming the wrong layout model is worse than no rule — it is what the next reader will
     reach for when this needs changing. A <button> centres its own label. */
}

/* ── Site forms: the partner application (V325) and the callback request ────
   Scoped to .site-form on purpose — opt in by class. These rules were once
   scoped to #apply-form alone, and the support page's callback form rendered
   with bare browser controls because of it. The marketing site had no general form
   styling at all before this — the buy dialog carries its own — and a set of
   bare `input`/`select`/`textarea` rules here would reach into the playground's
   runner and the buy dialog, both of which style their own fields already.
   Two touch rules are load-bearing rather than decorative: the 44px minimum
   height, because this form is filled in on a phone as often as not; and the
   16px font size on the control itself, below which iOS Safari zooms the page
   on focus and leaves the rest of the form off-screen. */
.site-form .field { margin-bottom: 16px; }
.site-form .field-row { display: flex; gap: 0 16px; flex-wrap: wrap; }
.site-form .field-row > .field { flex: 1 1 240px; min-width: 0; }
.site-form label {
  display: block; font-size: 13px; font-weight: 600; color: var(--text-2);
  margin-bottom: 6px;
}
.site-form .req-mark, .site-form .opt-mark {
  font-weight: 500; font-size: 11.5px; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em; margin-left: 6px;
}
.site-form input,
.site-form select,
.site-form textarea {
  width: 100%; box-sizing: border-box; min-height: 44px;
  padding: 11px 13px; font-family: inherit; font-size: 16px; line-height: 1.4;
  color: var(--text); background: var(--card);
  border-radius: 10px;
}
.site-form textarea { min-height: 108px; resize: vertical; }
.site-form input::placeholder, .site-form textarea::placeholder { color: var(--muted); opacity: 1; }
/* The native arrow sits hard against the border; this one has the field's own padding. */
.site-form select {
  appearance: none; -webkit-appearance: none; padding-right: 40px; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%2364748B' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 15px center;
}
.site-form input:focus, .site-form select:focus, .site-form textarea:focus {
  outline: 3px solid var(--sec); outline-offset: 1px; 
}
/* Named as well as coloured: colour must never be the only carrier. */
.site-form .form-error {
  margin: 16px 0 0; padding: 11px 14px; font-size: 14px;
  color: var(--error); background: var(--error-bg);
  border-radius: 10px;
}
.site-form .form-error[hidden] { display: none; }

/* ── Common problems (G-06) ────────────────────────────────────────────────────
   One card per refusal. The quote is the app's own sentence, so it reads as a
   quotation: set apart, never restyled into something the app does not show. */
.kb-entry {
  border-radius: var(--r-sm); background: var(--card);
  padding: 4px 18px 14px; margin: 14px 0;
}
.kb-entry h3 { margin-top: 14px; }
.kb-says {
  margin: 10px 0 12px; padding: 10px 14px;
  background: var(--surface); border-radius: 0 var(--r-sm) var(--r-sm) 0;
  color: var(--text); font-size: 15px; overflow-wrap: anywhere;
}
.kb-says p { margin: 0; }
.kb-says p + p { margin-top: 6px; }
.kb-says var, .lede var { font-style: italic; color: var(--muted); }
.kb-guide { font-size: 14px; color: var(--muted); }

/* ── What's new (G-02) ───────────────────────────────────────────────────────── */
.changelog { max-width: 820px; }
.cl-month { margin-top: 34px; }
.cl-month h2 { font-size: 24px; scroll-margin-top: calc(var(--nav-h) + 20px); }
.cl-entry { padding: 16px 0 6px; }
.cl-entry time { display: block; font-size: 13px; color: var(--muted); font-weight: 600; }
.cl-entry h3 { font-size: 18px; margin: 4px 0 6px; }
.cl-entry p { color: var(--neutral); }
.cl-guide { font-size: 14px; margin-top: 6px; }

/* ── Find an accountant (G-03) ───────────────────────────────────────────────── */
.directory { max-width: 980px; }
.dir-search { margin: 18px 0 8px; max-width: 460px; }
.dir-search[hidden] { display: none; }
.dir-search label { display: block; font-size: 14px; font-weight: 600; margin-bottom: 6px; }
.dir-search input {
  width: 100%; padding: 11px 13px; font: inherit; font-size: 16px; color: var(--text);
  background: var(--card); border-radius: 10px;
}
.dir-search input:focus { outline: 3px solid var(--sec); outline-offset: 1px;  }
.dir-count { font-size: 14px; color: var(--muted); margin: 10px 0; }
.dir-province h2 { font-size: 20px; margin: 26px 0 10px; }
.dir-province[hidden], .dir-card[hidden], .dir-none[hidden] { display: none; }
.dir-list {
  list-style: none; margin: 0; padding: 0; display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
}
.dir-card {
  border-radius: var(--r-sm); background: var(--card);
  padding: 14px 16px; min-width: 0; overflow-wrap: anywhere;
}
.dir-card h3 { font-size: 17px; margin: 0 0 4px; }
.dir-place, .dir-verified, .dir-contact { font-size: 14px; color: var(--neutral); margin: 4px 0 0; }
.dir-verified { color: var(--success); font-weight: 600; }
.dir-services { list-style: none; padding: 0; margin: 10px 0 0; }
.dir-small { font-size: 13px; color: var(--muted); margin-top: 18px; }

/* ── Ledgr Academy (/training/) ─────────────────────────────────────────────── */
.doc-watch { font-size: 15px; margin: -4px 0 14px; color: var(--neutral); }
.doc-watch a { font-weight: 600; }
.train-count { color: var(--neutral); font-size: 15px; margin: 0 0 14px; }
.train-jump { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 40px; }
.train-jump a { padding: 6px 14px; border-radius: 999px; background: var(--b100); font-size: 14px; font-weight: 600; color: var(--text); text-decoration: none; }
.train-jump a:hover {  color: var(--pri); }
.train-course { margin: 0 0 56px; scroll-margin-top: 90px; }
.train-course h2 { margin: 0 0 6px; }
.train-intro { color: var(--neutral); margin: 0 0 20px; max-width: 70ch; }
.train-grid { list-style: none; padding: 0; margin: 0; display: grid; gap: 18px; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.train-card { display: flex; flex-direction: column; height: 100%; border-radius: var(--r-sm); background: var(--card); overflow: hidden; color: var(--text); text-decoration: none; box-shadow: var(--shadow-sm); transition: transform .18s ease, box-shadow .18s ease; }
.train-card:hover { transform: translateY(-3px); box-shadow: var(--shadow); }
.train-card img { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; background: var(--b100); }
.train-card .train-meta { padding: 12px 16px 0; font-size: 13px; color: var(--muted); }
.train-card b { padding: 4px 16px 0; font-size: 17px; line-height: 1.3; }
.train-card .train-goal { padding: 6px 16px 16px; font-size: 14px; color: var(--neutral); }
.train-lesson { padding: 40px 0 84px; }
.train-crumb { font-size: 14px; color: var(--muted); margin: 0 0 10px; }
.train-lesson h1 { font-size: clamp(28px, 4vw, 42px); margin: 0 0 8px; }
.train-player { margin: 26px 0 36px; }
.train-player video { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; border-radius: var(--r-sm); background: var(--b100); box-shadow: var(--shadow-lg); }
.train-player figcaption { font-size: 14px; color: var(--muted); margin-top: 10px; }
.train-body { display: grid; gap: 40px; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
@media (max-width: 860px) { .train-body { grid-template-columns: minmax(0, 1fr); } }
.train-steps li { margin: 0 0 10px; }
.train-body h2 { margin: 0 0 12px; }
.train-chapters { margin: -14px 0 36px; padding: 18px 22px; border-radius: var(--r-sm); background: var(--card); }
.train-chapters h2 { font-size: 15px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); margin: 0 0 10px; }
.train-chapters ol { margin: 0; padding-left: 20px; display: grid; gap: 6px; }
.train-chapters a { font-weight: 600; }
.train-chapters .train-at { display: inline-block; min-width: 3.2em; font-family: 'JetBrains Mono', ui-monospace, monospace; font-weight: 500; color: var(--neutral); }
.train-chapters .train-n { font-size: 13px; color: var(--muted); white-space: nowrap; }
.train-part { font-size: 18px; margin: 22px 0 8px; scroll-margin-top: 90px; }
.train-part:first-of-type { margin-top: 0; }
.train-part-find::before { content: "Where to find it · "; color: var(--muted); font-weight: 500; }
.train-steps kbd { font-size: .92em; padding: 1px 6px; border-radius: 4px; background: var(--page); color: var(--text); overflow-wrap: anywhere; }
.train-steps b, .train-quiz b { color: var(--pri); }
.train-quiz > li { margin: 0 0 18px; }
.train-quiz > li > p { font-weight: 600; margin: 0 0 6px; }
.train-quiz ul { margin: 0 0 6px; padding-left: 20px; color: var(--neutral); }
.train-quiz details summary { cursor: pointer; font-size: 14px; color: var(--pri); font-weight: 600; }
.train-quiz details p { margin: 6px 0 0; }
.train-pager { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-top: 40px; padding-top: 22px; }
.train-pager a { font-weight: 600; }
.train-pager a[rel="next"] { margin-left: auto; text-align: right; }

/* ==========================================================================
   Motion and graphics (website redesign, 2026-10)
   --------------------------------------------------------------------------
   One layer over the pages as they were: every sentence and every
   {{fact:…}} stays where it is, and each graphic below is drawn by ledgr.js
   FROM the table or list already on the page, so no figure exists twice and a
   reader with no script still has the table. Motion is CSS, scroll-driven where
   the browser supports it, and none of it runs for a reader who asked for less.
   ========================================================================== */

/* ── Scroll reveal ─────────────────────────────────────────────────────── */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .reveal {
      animation: lg-rise linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 20%;
    }
  }
}
@keyframes lg-rise { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
@keyframes lg-slide { from { opacity: 0; transform: translateX(-16px); } to { opacity: 1; transform: none; } }
@keyframes lg-growy { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes lg-growx { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes lg-pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(0, 188, 212, .5); } 70% { box-shadow: 0 0 0 10px rgba(0, 188, 212, 0); } }
@keyframes lg-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }
@keyframes lg-travel { 0% { left: 0; opacity: 0; } 15%, 85% { opacity: 1; } 100% { left: calc(100% - 12px); opacity: 0; } }
@keyframes lg-ring { from { stroke-dasharray: 0 239; } }

/* ── Hero: a dot grid, a still that comes alive, and the day's events ──── */

/* The day's events sit in a row BENEATH the still. Hung over it they hid the
   still's own VAT201 note on a tablet, and any overlap hides something at some
   width; beneath it they cover nothing and still slide in and float. */
.card--link, .plan { transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease; }
.card--link:hover, .plan:hover { transform: translateY(-3px); box-shadow: var(--shadow); }
.plan--featured h3, .plan--featured .amount { color: #fff; }
.plan--featured .amount small, .plan--featured .seats, .plan--featured .incl, .plan--featured .plan-note { color: #EEF4FD; }
.plan--featured li { color: #fff; }
.plan--featured li::before { color: #fff; }
.plan--featured .btn-primary, .plan--featured .btn-ghost { background: #fff; color: var(--pri); box-shadow: none; }
@media (prefers-reduced-motion: reduce) { .card--link, .plan { transition: none; } .card--link:hover, .plan:hover { transform: none; } }

/* ── Module filter ──────────────────────────────────────────────────────── */
.mod-filter { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin: 0 0 28px; }
.mod-chip {
  display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 16px; border-radius: 999px;
  background: var(--card); color: var(--text-2);
  font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
}
.mod-chip span { font-family: 'JetBrains Mono', monospace; font-size: 12px; color: var(--muted); }
.mod-chip[aria-pressed="true"] { background: var(--pri);  color: #fff; }
.mod-chip[aria-pressed="true"] span { color: #EEF4FD; }
.mod-chip:focus-visible { outline: 3px solid var(--sec); outline-offset: 2px; }

/* ── One ledger underneath ─────────────────────────────────────────────── */
.flow { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2.1fr); gap: 28px; align-items: start; margin-top: 8px; }
.flow-steps { display: flex; flex-direction: column; gap: 8px; }
.flow-step {
  display: flex; gap: 14px; align-items: flex-start; width: 100%; padding: 16px 18px; text-align: left;
  border: 1px solid transparent; border-radius: var(--r); background: transparent; font: inherit; color: inherit; cursor: pointer;
}
.flow-step b { display: block; font-size: 16.5px; color: var(--text); }
.flow-step b + span { display: block; margin-top: 4px; font-size: 14.5px; line-height: 1.5; color: var(--muted); }
.flow-step[aria-pressed="true"] { background: var(--card); box-shadow: var(--shadow); }
.flow-step:focus-visible { outline: 3px solid var(--sec); outline-offset: 2px; }
.flow-n {
  flex-shrink: 0; width: 34px; height: 34px; border-radius: 10px; display: flex; align-items: center; justify-content: center;
  font-family: 'JetBrains Mono', monospace; font-weight: 600; background: var(--b100); color: var(--pri);
}
.flow-step[aria-pressed="true"] .flow-n { background: var(--pri); color: #fff; }
.flow-stage { display: grid; gap: 14px; }
.flow--js .flow-stage { gap: 0; }
.flow--js .flow-panel[hidden] { display: none; }
.flow-panel {
  display: grid; grid-template-columns: minmax(0, 1fr) 90px minmax(0, 1fr); gap: 16px; align-items: center;
  padding: 24px; border-radius: var(--r-lg); background: var(--well);
}
.flow-doc { background: var(--card); border-radius: var(--r); padding: 18px; box-shadow: var(--shadow-sm); }
.flow-row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; font-size: 14px; margin-top: 8px; }
.flow-row:first-child { margin-top: 0; }
.flow-muted { color: var(--muted); }
.flow-total { margin-top: 12px; padding: 8px 10px; border-radius: var(--r-sm); background: var(--b100); font-weight: 700; }
.flow-sub { margin: 6px 0 10px; font-size: 13px; color: var(--muted); }
.flow-match { margin: 12px 0 0; font-size: 13.5px; font-weight: 600; color: var(--success); }
.flow-path { position: relative; height: 10px; border-radius: 5px; background: var(--b100);}
.flow-path span { position: absolute; top: -1px; width: 12px; height: 12px; border-radius: 50%; background: var(--b500); }
@media (prefers-reduced-motion: no-preference) { .flow-path span { animation: lg-travel 1.8s ease-in-out infinite; } }
.flow-ledger { background: var(--card); color: var(--text); border-radius: var(--r); padding: 18px; box-shadow: var(--shadow-sm); }
.flow-head { font-size: 13px; font-weight: 600; color: var(--muted); }
.flow-line { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 10px; margin-top: 10px; font-size: 13.5px; }
.flow-line .num { min-width: 92px; text-align: right; }
.flow-line .dr { color: var(--pri); }
.flow-line .cr { color: var(--text-2); }
.flow-tb { margin-top: 14px; padding: 8px 10px; border-radius: var(--r-sm); background: var(--b100); color: var(--text); }
.flow-tb .num { color: var(--pri); font-weight: 600; }
@media (prefers-reduced-motion: no-preference) {
  .flow--js .flow-panel:not([hidden]) .flow-line { animation: lg-slide .45s cubic-bezier(.2, .7, .2, 1) both; }
  .flow--js .flow-panel:not([hidden]) .flow-line:nth-of-type(3) { animation-delay: .12s; }
  .flow--js .flow-panel:not([hidden]) .flow-line:nth-of-type(4) { animation-delay: .24s; }
  .flow--js .flow-panel:not([hidden]) .flow-doc { animation: lg-rise .45s cubic-bezier(.2, .7, .2, 1) both; }
}
@media (max-width: 980px) { .flow { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 640px) {
  .flow-panel { grid-template-columns: minmax(0, 1fr); padding: 16px; }
  .flow-path { width: 10px; height: 36px; margin: 0 auto; }
  .flow-path span { display: none; }
  .flow-line .num { min-width: 0; }
}

/* ── Charts drawn from the pricing tables ──────────────────────────────── */
.chart-card { margin: 18px 0; padding: 24px; border-radius: var(--r-lg); background: var(--card); box-shadow: var(--shadow-sm); }
.chart-card label { display: block; font-weight: 700; }
.chart-card input[type=range] { width: 100%; height: 32px; accent-color: var(--pri); }
.bars { display: grid; gap: 6px; align-items: end; height: 180px; margin-top: 14px; }
.bars > div { display: flex; flex-direction: column; justify-content: flex-end; height: 100%; min-width: 0; }
.bars i { display: block; border-radius: 8px 8px 3px 3px; background: var(--b200); transition: background .25s ease; transform-origin: bottom; }
.bars i.on { background: var(--b500); }
.bars small { margin-top: 6px; font-family: 'JetBrains Mono', monospace; font-size: 11px; text-align: center; color: var(--muted); overflow-wrap: anywhere; }
.bars b { font-family: 'JetBrains Mono', monospace; font-size: 13px; text-align: center; color: var(--pri); margin-bottom: 4px; }
.band-result {
  display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-top: 16px; padding: 14px 18px; border-radius: var(--r); background: var(--b100); color: var(--text);
}
.band-result span { color: var(--text-2); font-size: 14.5px; }
.band-result b { font-family: 'Outfit', sans-serif; font-size: 24px; font-weight: 600; }
.hbars { display: flex; flex-direction: column; gap: 8px; }
.hbars > div { display: grid; grid-template-columns: 120px minmax(0, 1fr) 64px; gap: 10px; align-items: center; font-size: 14px; }
.hbars em { font-style: normal; font-weight: 600; }
.hbars span { height: 12px; border-radius: 6px; background: var(--divider); overflow: hidden; }
.hbars span i { display: block; height: 100%; border-radius: 6px; background: var(--b400); transform-origin: left; }
.hbars span i.pro { background: var(--pri); }
.hbars b { font-family: 'JetBrains Mono', monospace; font-weight: 600; text-align: right; }
/* Eight bands on a phone: a label that wrapped lifted its bar off the baseline, so labels hold one line and
   may run a little past a narrow column, centred. The table below gives each band one line. */
@media (max-width: 640px) {
  .bars { gap: 4px; }
  .bars small { align-self: center; font-family: 'DM Sans', sans-serif; font-size: clamp(8.5px, 2.7vw, 11px); white-space: nowrap; overflow-wrap: normal; }
  .matrix-scroll[data-band-chart] table.stack thead { position: static; width: auto; height: auto; overflow: visible; clip-path: none; }
  .matrix-scroll[data-band-chart] table.stack tr { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 10px 12px; }
  .matrix-scroll[data-band-chart] table.stack td { text-align: right; }
  .matrix-scroll[data-band-chart] table.stack td[data-label]::before { display: none; }
}
/* The partner ladder: label and share on one line, the bar under them, so it fits a phone. The chart holds
   every row of the table, so a phone is not made to read them twice; with no script the table stands. */
.ladder-bars { gap: 14px; }
.ladder-bars > div { grid-template-columns: minmax(0, 1fr) auto; gap: 6px 12px; }
.ladder-bars span { grid-row: 2; grid-column: 1 / -1; }
@media (max-width: 640px) {
  .ladder-bars { padding: 18px; }
  .ladder-bars + .matrix-scroll { display: none; }
}
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .bars i { animation: lg-growy linear both; animation-timeline: view(); animation-range: entry 10% cover 40%; }
    .hbars span i { animation: lg-growx linear both; animation-timeline: view(); animation-range: entry 10% cover 40%; }
  }
}
.allow { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; margin: 18px 0; }
.allow > div { padding: 14px 16px; border-radius: var(--r); background: var(--b50); }
.allow > div.pro { background: var(--pri); color: #fff; }
.allow h4 { margin: 0; font-size: 14.5px; }
.allow p { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0 8px; margin: 8px 0 0; font-size: 13px; color: var(--muted); }
/* The label takes what is left and the figure drops under it when a word like "Not included"
   cannot sit beside it; before, the figure kept its own width and ran out of the card. */
.allow p span { flex: 1 1 0; }
.allow p b { margin-left: auto; text-align: right; font-family: 'JetBrains Mono', monospace; font-size: 15px; color: var(--text); }
.allow .pro p { color: #EEF4FD; }
.allow .pro p b { color: #fff; }
.matrix-scroll.has-cards table { display: none; }
.matrix .col-pro { background: var(--b100); }
.matrix thead .col-pro { color: var(--pri); }

/* ── Accordions (the FAQs) ─────────────────────────────────────────────── */
.faq .card { padding: 0 22px; }
.faq .card > h3 { margin: 0; }
.faq-btn {
  display: flex; justify-content: space-between; align-items: center; gap: 16px; width: 100%; min-height: 60px;
  padding: 16px 0; border: 0; background: none; font: inherit; font-weight: 700; color: var(--text); text-align: left; cursor: pointer;
}
.faq-btn:focus-visible { outline: 3px solid var(--sec); outline-offset: 2px; }
.faq-btn::after { content: '+'; flex-shrink: 0; font-size: 24px; font-weight: 400; color: var(--pri); transition: transform .2s ease; }
.faq-btn[aria-expanded="true"]::after { transform: rotate(45deg); }
.faq-body { padding-bottom: 18px; }
.faq-body > :first-child { margin-top: 0; }

/* ── Switching: the cutover, tabs, a checklist that ticks ──────────────── */
.cutover { display: grid; grid-template-columns: minmax(0, 1fr) 128px minmax(0, 1fr); gap: 6px; margin-top: 36px; text-align: left; }
.cutover-side { padding: 22px 24px; background: var(--b100); border-radius: var(--r); }
.cutover-side--new { background: var(--pri); }
.cutover small { display: block; font-size: 13px; font-weight: 600; color: var(--muted); }
.cutover-side--new small { color: #EEF4FD; }
.cutover b { display: block; margin-top: 8px; font-size: 18px; color: var(--text); }
.cutover-side--new b { color: #fff; }
.cutover-side span { display: block; margin-top: 6px; font-size: 14.5px; line-height: 1.5; color: var(--text-2); }
.cutover-side--new span { color: #EEF4FD; }
.cutover-date { display: flex; flex-direction: column; align-items: center; justify-content: center; background: var(--card); box-shadow: var(--shadow-sm); border-radius: var(--r); padding: 12px; text-align: center; }
.cutover-date small { color: var(--muted); }
.cutover-date b { margin-top: 4px; font-size: 20px; color: var(--pri); }
@media (max-width: 640px) {
  .cutover { grid-template-columns: minmax(0, 1fr); }
  .cutover-date { flex-direction: row; gap: 12px; min-height: 52px; }
  .cutover-date b { margin-top: 0; }
}
.tabs-bar { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 6px; margin-bottom: 16px; -webkit-overflow-scrolling: touch; }
.tab-btn {
  flex-shrink: 0; min-height: 44px; padding: 0 16px; border-radius: 999px; background: var(--card); color: var(--text-2); font: inherit; font-size: 14px; font-weight: 600; cursor: pointer; white-space: nowrap;
}
.tab-btn[aria-selected="true"] { background: var(--pri);  color: #fff; }
.tab-btn:focus-visible { outline: 3px solid var(--sec); outline-offset: 2px; }
.tabs--js { grid-template-columns: minmax(0, 1fr) !important; }
.tabs--js > [hidden] { display: none; }
.check-progress { display: flex; align-items: center; gap: 14px; margin: 0 0 18px; }
.check-progress svg circle + circle { transition: stroke-dashoffset .35s ease; }
.check-progress b { font-family: 'JetBrains Mono', monospace; font-size: 22px; }
.check-progress span { color: var(--muted); }
.check-list.tickable li { padding-left: 0; }
.check-list.tickable li::before, .check-list.tickable li::after { content: none; }
.tick-btn {
  display: flex; gap: 12px; align-items: flex-start; width: 100%; min-height: 44px; padding: 8px 10px; margin-left: -10px;
  border: 0; border-radius: 10px; background: none; font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.tick-btn:hover { background: var(--b100); }
.tick-btn:focus-visible { outline: 3px solid var(--sec); outline-offset: 1px; }
.tick-btn i {
  flex-shrink: 0; width: 24px; height: 24px; margin-top: 1px; border-radius: 8px; border: 1.5px solid var(--edge);
  display: flex; align-items: center; justify-content: center; transition: background .2s ease, border-color .2s ease;
}
.tick-btn[aria-checked="true"] i { background: var(--success); border-color: var(--success); }
.tick-btn[aria-checked="true"] i::after { content: ''; width: 6px; height: 11px; border: solid #fff; border-width: 0 2.5px 2.5px 0; transform: translateY(-1px) rotate(45deg); }
.tick-btn[aria-checked="true"] > span { color: var(--muted); text-decoration: line-through; }
.effort { display: inline-block; font-size: 12.5px; font-weight: 600; padding: 4px 11px; border-radius: 999px; white-space: nowrap; background: var(--divider); color: var(--text-2); }
.effort--automatic { background: var(--success-bg); color: var(--success); }
.effort--minutes { background: var(--b100); color: var(--pri); }
.effort--csv { background: var(--info-bg); color: #00636C; }
.effort--typing { background: var(--warn-bg); color: var(--warn); }
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .steps > li::before { animation: lg-rise linear both; animation-timeline: view(); animation-range: entry 0% cover 25%; }
  }
}
/* `hidden` loses to any author `display` on a card, so the three things the
   script hides by attribute say so outright. */
[data-mod-grid] > [hidden], .faq-body[hidden], .tabs--js > [hidden] { display: none !important; }
.scroll-hint.is-moot { display: none !important; }
/* The still's VAT note on a phone: "Prepare it" broke onto two lines because the
   row could not wrap and the button could. The button keeps its one line and
   the row wraps around it instead. */
.shot-note { flex-wrap: wrap; row-gap: 8px; }
.shot-note .btn { white-space: nowrap; flex-shrink: 0; }

/* Guides and the developer hub: the same lift on cards and a calmer reveal. */
.doc-main .card { transition: transform .2s ease, box-shadow .2s ease; }
.doc-main a.card:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
@media (prefers-reduced-motion: reduce) { .doc-main .card { transition: none; } .doc-main a.card:hover { transform: none; } }

/* ── Form fields ───────────────────────────────────────────────────────────
   The one drawn edge left on the site: WCAG 1.4.11 wants a control to be findable at
   3:1, and a tint alone does not reach it. --edge measures 3.64:1 on white. */
.site-form input, .site-form select, .site-form textarea, .doc-search input, .dir-search input,
.pg-auth input, .pg-runner input, .pg-runner textarea, .pg-search { border: 1.5px solid var(--edge); }

/* ── Home hero: The Exploded Company ───────────────────────────────────────
   Every length in the stack is --u, one design pixel, so the whole object scales with the
   viewport with no script. --g is the gap between plates, in the same units: 99 is fully open,
   and that is the default, so the page is right before motion.js has run, and if it never does.
   The scroll track only gets its height once the script is live (.is-live). */
/* The ground sits on the pin, not on the tall scroll track: Firefox repainted a track-sized
   gradient on every frame of the scroll, so it has a layer of its own. */
.hero[data-xp] { overflow: clip; background: none; }
.hero[data-xp] .xp-pin { isolation: isolate; }
.hero[data-xp] .xp-pin::before { content: ''; position: absolute; inset: 0; z-index: -1; background: var(--air); will-change: transform; }
.xp-pin { --u: min(1px, calc((100vh - var(--nav-h)) / 800)); position: relative; min-height: min(calc(100vh - var(--nav-h)), 900px); display: flex; align-items: center; }
.xp-copy { position: relative; z-index: 2; pointer-events: none; }
.xp-text { max-width: 560px; padding-block: 40px; pointer-events: auto; }
.xp { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
.xp-stack, .xp-over { --g: 99; position: absolute; left: 63%; top: 50%; width: 0; height: 0; }
.xp-stack { transform-style: preserve-3d; }
.plate {
  position: absolute; left: calc(var(--u) * -225); top: calc(var(--u) * -135); width: calc(var(--u) * 450); height: calc(var(--u) * 270);
  padding: calc(var(--u) * 20) calc(var(--u) * 24); border-radius: calc(var(--u) * 28); background: #fff; color: var(--text);
  font-size: calc(var(--u) * 15); line-height: 1.5; box-shadow: 0 2px 4px rgba(13, 71, 161, .06), 0 36px 70px -24px rgba(13, 71, 161, .38);
  transform: rotateX(60deg) rotateZ(-40deg) translateZ(calc(var(--u) * (2.5 - var(--i)) * var(--g)));
  transition: background-color .5s ease, color .5s ease;
}
.plate-k { font-size: calc(var(--u) * 13); font-weight: 600; color: var(--neutral); }
.plate-t { font-family: 'Outfit', sans-serif; font-weight: 600; font-size: calc(var(--u) * 27); letter-spacing: -.02em; line-height: 1.25; margin-bottom: calc(var(--u) * 8); }
.plate-r { display: flex; justify-content: space-between; gap: 12px; padding: calc(var(--u) * 4) calc(var(--u) * 10); margin-inline: calc(var(--u) * -10); border-radius: calc(var(--u) * 10); }
.plate-r:nth-of-type(odd) { background: rgba(13, 71, 161, .05); }
.plate-r span:last-child { font-family: 'JetBrains Mono', monospace; }
.plate.lit0 { background: var(--b100); } .plate.lit1 { background: var(--b200); } .plate.lit2 { background: var(--b300); } .plate.lit3 { background: var(--b400); }
.plate.lit0 .plate-k, .plate.lit1 .plate-k, .plate.lit2 .plate-k, .plate.lit3 .plate-k { color: var(--text); }
.plate.lit4 { background: var(--b600); color: #fff; } .plate.lit5 { background: var(--pri); color: #fff; }
.plate.lit4 .plate-k, .plate.lit5 .plate-k { color: #EEF4FD; }
.plate.lit4 .plate-r:nth-of-type(odd), .plate.lit5 .plate-r:nth-of-type(odd) { background: rgba(255, 255, 255, .1); }
.xp-bead { position: absolute; left: calc(var(--u) * -46); top: calc(var(--u) * -46); width: calc(var(--u) * 92); height: calc(var(--u) * 92); border-radius: 50%; background: radial-gradient(circle, #fff 0 12%, rgba(0, 188, 212, .7) 26%, rgba(0, 188, 212, 0) 70%); opacity: 0; }
.xp-label { position: absolute; left: calc((100vw + min(100vw, var(--maxw))) / 2 - 24px - 63vw - 170px); top: calc(var(--u) * ((var(--i) - 2.5) * var(--g) * .866 - 26)); width: 170px; padding: 8px 14px; border-radius: 14px; background: #fff; box-shadow: var(--shadow-sm); font-size: 14px; line-height: 1.3; color: var(--text); }
.xp-label b { display: block; font-size: 12.5px; font-weight: 600; color: var(--muted); }
.xp-cap { display: none; }
@media (max-width: 1380px) { .xp-label { display: none; } .xp-stack, .xp-over { left: 70%; } }
@media (min-width: 1700px) { .xp-stack, .xp-over { left: 60%; } .xp-label { left: calc((100vw + min(100vw, var(--maxw))) / 2 - 24px - 60vw - 170px); } }
/* On a phone there is no room to pin both, and the headline and button must be on the first
   screen, so the words come first and the stack opens as it scrolls through the viewport. */
@media (max-width: 1100px) {
  .xp-pin { --u: min(.74px, calc(100vw / 760)); display: flex; flex-direction: column; align-items: stretch; min-height: 0; }
  .xp-copy { order: -1; }
  .xp { position: relative; inset: auto; height: calc(var(--u) * 640 + 30px); }
  .xp-stack, .xp-over { left: 50%; top: calc(50% - 15px); }
  .xp-cap { display: block; position: absolute; left: 16px; right: 16px; bottom: 0; font-size: 14px; font-weight: 600; text-align: center; color: var(--text); }
  .xp-text { padding-block: 44px 8px; max-width: 640px; }
}
@media (min-width: 1101px) {
  .is-live .xp-track { height: 210vh; }
  .is-live .xp-pin { position: sticky; top: var(--nav-h); height: calc(100vh - var(--nav-h)); min-height: 0; overflow: hidden; }
}

/* ── Section animations. Each is a static figure first; see motion.js ─────── */
.zd { position: relative; margin: 0 0 56px; height: clamp(230px, 30vw, 400px); display: flex; flex-direction: column; align-items: center; justify-content: center; border-radius: var(--r-well); background: var(--air); overflow: hidden; }
.zd canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.zd-num { font-family: 'Outfit', sans-serif; font-weight: 700; font-size: clamp(96px, 19vw, 260px); line-height: 1; letter-spacing: -.03em; color: var(--pri); transition: opacity .6s ease; }
.zd-cap { font-size: 15px; font-weight: 600; color: var(--muted); margin-top: 4px; }
.zd-chip { position: absolute; top: 18px; z-index: 2; padding: 8px 16px; border-radius: var(--r); background: #fff; box-shadow: var(--shadow-sm); font-weight: 600; font-size: 16px; color: var(--text); transition: opacity .5s ease; }
.zd-chip small { display: block; font-size: 12.5px; color: var(--muted); }
.zd-chip--l { left: 18px; } .zd-chip--r { right: 18px; text-align: right; }
.zd.is-running .zd-num { opacity: 0; }
.ty { margin: 0; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 56px; align-items: center; }
.dial { position: relative; width: min(100%, 520px); aspect-ratio: 1; justify-self: center; border-radius: 50%; container-type: inline-size; }
.dial-face { position: absolute; inset: 0; border-radius: 50%; background: radial-gradient(circle at 38% 30%, #fff 0, #fff 40%, var(--b50) 100%); box-shadow: 0 3cqw 8cqw -2cqw rgba(13, 71, 161, .34), 0 .4cqw 1cqw rgba(13, 71, 161, .08); }
.dial-arc { --a: 360deg; position: absolute; inset: 3cqw; border-radius: 50%; background: conic-gradient(from 0deg, var(--b300) 0, var(--b500) calc(var(--a) * .6), var(--pri) var(--a), var(--b100) var(--a) 360deg); -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 6cqw), #000 calc(100% - 5.9cqw)); mask: radial-gradient(farthest-side, transparent calc(100% - 6cqw), #000 calc(100% - 5.9cqw)); }
.dk { position: absolute; left: 50%; top: 50%; width: 6cqw; height: 6cqw; margin: -3cqw 0 0 -3cqw; border-radius: 50%; background: #fff; box-shadow: 0 .5cqw 1.6cqw rgba(13, 71, 161, .45); transform: rotate(0deg) translateY(-44cqw); }
.dm { position: absolute; left: 50%; top: 50%; width: 9cqw; height: 4.6cqw; margin: -2.3cqw 0 0 -4.5cqw; display: flex; align-items: center; justify-content: center; border-radius: 3cqw; font-size: 2.6cqw; font-weight: 600; color: var(--muted); transition: background-color .3s ease, color .3s ease; }
.dm.on { background: var(--b100); color: var(--pri); }
.dp { position: absolute; left: 50%; top: 50%; width: 2.2cqw; height: 2.2cqw; margin: -1.1cqw 0 0 -1.1cqw; border-radius: 50%; background: var(--b100); transition: background-color .3s ease; }
.dp.on { background: currentColor; }
.dw { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); display: flex; flex-direction: column; align-items: center; }
.dw small { font-size: 2.6cqw; font-weight: 600; color: var(--muted); }
.dw b { font-family: 'Outfit', sans-serif; font-size: 11cqw; font-weight: 600; letter-spacing: -.03em; line-height: 1.05; color: var(--pri); }
.ty h3 { font-size: clamp(26px, 2.8vw, 36px); font-weight: 600; letter-spacing: -.02em; margin-bottom: 14px; }
.dl { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 8px; font-size: 15px; color: var(--text-2); }
.dl li { position: relative; padding-left: 21px; }
.dl i { position: absolute; left: 0; top: .42em; width: 11px; height: 11px; border-radius: 50%; }
.dl b { color: var(--text); font-weight: 600; margin-right: 4px; }
.ty-note { margin-top: 12px; font-size: 14.5px; color: var(--muted); }
@media (max-width: 860px) { .ty { grid-template-columns: minmax(0, 1fr); gap: 32px; } }
.lv { height: clamp(150px, 20vw, 260px); margin-top: 40px; border-radius: var(--r-well) var(--r-well) 0 0; position: relative; overflow: hidden; background: linear-gradient(180deg, #fff 0, var(--b200) 22%, var(--b400) 62%, var(--b500) 100%); }
.lv canvas { position: absolute; inset: 0; width: 100%; height: 100%; transition: opacity .8s ease; }
.lv-cta { margin-top: 0; border-radius: 0 0 var(--r-well) var(--r-well); }
@media (max-width: 620px) { .lv { border-radius: var(--r-lg) var(--r-lg) 0 0; } .lv-cta { border-radius: 0 0 var(--r-lg) var(--r-lg); } }

/* ── Pricing estimator ───────────────────────────────────────────────────── */
.est { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: 40px; align-items: center; }
.est[hidden] { display: none; }
.est h2 { font-size: 24px; font-weight: 600; margin-bottom: 12px; }
.est-row { display: grid; grid-template-columns: 170px minmax(0, 1fr) 36px; gap: 12px; align-items: center; min-height: 44px; font-size: 15px; font-weight: 600; color: var(--text-2); }
.est-row input { width: 100%; height: 44px; margin: 0; accent-color: var(--pri); }
.est-row output { font-family: 'JetBrains Mono', monospace; text-align: right; }
.est-mods { border: 0; margin: 10px 0 0; padding: 0; }
.est-mods legend { padding: 0; font-size: 15px; font-weight: 600; color: var(--text-2); margin-bottom: 8px; }
.est-mods > div { display: flex; flex-wrap: wrap; gap: 8px; }
.est .tab-btn[aria-pressed="true"] { background: var(--pri); color: #fff; }
.est-out { background: var(--b50); border-radius: var(--r); padding: 22px 24px; }
.est-plan { font-weight: 600; color: var(--text-2); }
.est-price { font-family: 'Outfit', sans-serif; font-size: 32px; font-weight: 600; letter-spacing: -.02em; color: var(--text); line-height: 1.2; margin: 2px 0 10px; }
@media (max-width: 860px) { .est { grid-template-columns: minmax(0, 1fr); gap: 20px; } .est-row { grid-template-columns: minmax(0, 1fr) 36px; } .est-row input { grid-column: 1 / -1; grid-row: 2; } }
/* Stacked, the module buttons push the answer a screen below the controls, so it stays pinned to the
   bottom of the screen while the form is in view. Block, because a sticky grid item cannot leave its row. */
@media (max-width: 860px) {
  .est { display: block; }
  .est-out { display: contents; }
  .est-answer {
    position: sticky; bottom: calc(12px + env(safe-area-inset-bottom)); z-index: 5;
    display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
    margin-top: 20px; padding: 14px 18px; background: var(--b50); border-radius: var(--r);
    box-shadow: 0 12px 32px -12px rgba(13, 71, 161, .38);
  }
  .est-answer .est-price { font-size: 22px; margin: 0; }
  .est-out .plan-note { margin-top: 14px; }
}
/* ── Academy ─────────────────────────────────────────────────────────────── */
.train-feature { position: relative; display: block; border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--shadow-lg); background: var(--b100); }
.train-feature img { display: block; width: 100%; height: auto; }
.train-play { position: absolute; left: 50%; top: 50%; width: 76px; height: 76px; margin: -38px 0 0 -38px; border-radius: 50%; background: #fff; box-shadow: var(--shadow); }
.train-play::after { content: ''; position: absolute; left: 30px; top: 23px; border-left: 22px solid var(--pri); border-top: 15px solid transparent; border-bottom: 15px solid transparent; }
.track { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; min-height: 52px; padding: 4px 10px; margin-bottom: 20px; border-radius: 999px; background: var(--b100); width: fit-content; max-width: 100%; }
.stn { width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; }
.stn::before { content: ''; width: 18px; height: 18px; border-radius: 50%; background: #fff; box-shadow: var(--shadow-sm); transition: transform .18s ease, background-color .18s ease; }
.stn:hover::before { transform: scale(1.3); }
.stn.done::before { background: var(--b500); }
.train-progress { font-weight: 600; color: var(--pri); margin: 0 0 14px; }
.train-card.done .train-meta::after { content: ' · Watched'; color: var(--pri); font-weight: 600; }
.train-chapters { background: var(--b50); }
.tab-btn, .mod-chip { border: 0; background: var(--b100); }
.stat b::first-letter { text-transform: uppercase; }
.doc-main .ty { grid-template-columns: minmax(0, 1fr); gap: 28px; margin: 24px 0; }
.doc-main .ty .dial { max-width: 360px; margin-inline: auto; width: 100%; }
.mod-block .zd { margin: 8px 0 28px; }

/* ── Community pages (routes/community/CommunityRoutes.kt) ───────────────────────────────────
   The bar's links were bare 24px lines with the sign-up button hanging off their baseline, and a
   question longer than a phone's field was cut mid-word in the placeholder. */
.cm-top nav { display: flex; flex-wrap: wrap; align-items: center; }
.cm-top nav a:not(.btn) { display: inline-flex; align-items: center; min-height: 44px; }
/* F-84: on the field, not on ::placeholder, where neither Chromium nor WebKit applies text-overflow. */
.cm-form input { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }
