/* ===========================================================================
   Senti — Marketing & Sales design system (shared across all landing routes)
   Build target: _brief/design/senti-DESIGN-MARKETING.md
   A marketing dialect of the Senti Terminal system: same palette + fonts +
   sharp 2px edges, but larger display scale, 17px readable body, spacious
   rhythm, and one emerald signal-glow per page.
   =========================================================================== */

:root {
  --bg: #0B0D11; --surface: #14171C; --surface-2: #1D2027;
  /* Hairline dropped one step, 2026-08-19: #2A2E36 -> #23272E. Measured against
     --bg it goes 1.43:1 -> 1.30:1, and against --surface 1.32:1 -> 1.20:1. One
     token, 76 uses -- every chapter rule, panel edge, table line and field
     border on all ten routes moves together, which is the point: they are one
     material and they were never meant to read as separate weights.

     This is close to the floor. A hairline is structure, not decoration -- it is
     what tells a reader where a chapter starts when the band has no background.
     Another step down and the [01]-[05] rules stop resolving on a dim laptop
     screen, and the document loses its skeleton rather than its noise. */
  --border: #23272E;
  --fg: #E8EAEE; --fg-muted: #989DA8; --fg-faint: #5F6573;
  --accent: #34D399; --accent-ink: #04231A;
  --accent-soft: color-mix(in srgb, #34D399 10%, transparent);
  --accent-line: color-mix(in srgb, #34D399 42%, transparent);
  --neg: #FF3B30;
  /* Sand is editorial furniture, NOT a second accent. It carries no meaning and
     never marks state — it only warms the page. Every element on a dark UI made
     of grey + emerald is cool, and that uniform temperature is what reads as
     machine-made. Sand is allowed on chrome (chapter indices, counters, micro
     sub-labels, hairlines) and on the shaded face of an illustration. It is
     never allowed on anything actionable: buttons, links, CTAs, status dots,
     focus rings and stat figures stay emerald, because emerald means act/live.
     Contrast on --bg: 11.4:1. */
  --sand: #E7C08B;
  --sand-dim: color-mix(in srgb, #E7C08B 62%, var(--bg));
  --sand-line: color-mix(in srgb, #E7C08B 30%, transparent);
  --mkt-glow: color-mix(in srgb, #34D399 14%, transparent);
  --mkt-grid: color-mix(in srgb, #E8EAEE 4%, transparent);
  /* TWO monos on outward-facing surfaces, one on the product.
     D53 (Senti-Quant DESIGN.md, 2026-06-02, haii.239) retired Geist Mono and
     made JetBrains Mono the sole mono — for the PRODUCT, where the two faces
     were reading as duplicated chrome and the split-fallback was drifting.
     Owner's call (2026-08-06): sales and marketing keep the two-face split,
     because a document is read, not operated — display wants a voice, data
     wants a grid. This narrows D53's scope rather than reversing it, and it
     matches what app/public/sales/senti-sales.css has done since 2026-05-18.

     --ff-mono STAYS JetBrains and STAYS the default, so nothing changes unless
     it opts in. That is deliberate: the product-UI mimicry blocks (.mock__*,
     .terminal__*, .browser__*, .codebody) must render in the real product's
     face, and after D53 the real product is JetBrains only. Painting them
     Geist Mono would make the screenshot lie about the app. */
  --ff-display: "Geist Mono", ui-monospace, SFMono-Regular, monospace;
  --ff-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
  --ff-body: "Geist", ui-sans-serif, system-ui, sans-serif;
  --maxw: 1280px; --gutter: clamp(1rem, 4vw, 2rem);
  /* Square corners, 2026-08-19 (was --r: 2px / --r-lg: 4px). Every box on the
     page reads its corner from one of these two tokens -- .bm-card, .panel,
     .cmp, .tiers, .opts, .sec-block, .btn, form fields, .step__n, :focus-visible
     -- so squaring them here squares the system instead of leaving one card at
     0 and thirty-nine boxes at 4px.

     Deliberately NOT squared: the 9999px pills (.chip, the chapter badge, the
     tier bullets) are shaped, not rounded -- a pill at 0 is a different
     component, not the same one with tighter corners. Nor the hero mockup's own
     chrome (.browser, .terminal, .ticket, .tf), which hardcodes 3-11px because
     it is a picture of the real product's UI; flattening it would make the
     screenshot lie about the app, the same reason those blocks keep their own
     typeface a few lines up. */
  --r: 0px; --r-lg: 0px; --nav-h: 60px;
}
*, *::before, *::after { box-sizing: border-box; }
/* Kills a site-wide 8px horizontal scrollbar. Four separate full-bleed
   constructs -- .sec-full, the band variant, .final's two glow layers -- are
   built on `width: 100vw; margin-inline: calc(50% - 50vw)`, and vw counts the
   vertical scrollbar while the client box does not. On every page tall enough to
   scroll that makes each of them 1440px inside a 1425px viewport and the
   document scrolls 8px sideways over glow nobody can reach.

   Clipping at the root rather than rewriting the four: the 100vw is load-bearing
   there. A band and a plain chapter have different parents, so a percentage
   resolves differently in each and their inner wraps would stop centring on the
   same left edge -- the comment on the band rule says exactly that. `clip`, not
   `hidden`: clip does not create a scroll container, so the sticky hero column
   keeps resolving against the viewport. */
html, body { overflow-x: clip; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0; background: var(--bg); color: var(--fg);
  font-family: var(--ff-body); font-size: 17px; line-height: 1.65;
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
}
a { color: inherit; text-decoration: none; }
::selection { background: var(--accent); color: var(--accent-ink); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r); }
img { max-width: 100%; height: auto; display: block; }
code { font-family: var(--ff-mono); font-size: .92em; color: var(--fg); }
.panel__emph code { display: inline-block; color: var(--accent); background: var(--bg); border: 1px solid var(--border); border-radius: var(--r); padding: .55rem .8rem; font-size: .875rem; line-height: 1.7; max-width: 100%; overflow-x: auto; }
.wrap { max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gutter); }
.narrow { max-width: 60rem; margin-inline: auto; }
.eyebrow { font-family: var(--ff-mono); font-size: .6875rem; font-weight: 500; letter-spacing: .12em; text-transform: uppercase; color: var(--fg-faint); }
.eyebrow .on { color: var(--fg); }
section { padding-block: clamp(64px, 9vw, 120px); }
.section-head { max-width: 42rem; margin: 0 auto clamp(2.5rem, 5vw, 4rem); text-align: center; }
.section-head.left { margin-inline: 0; text-align: left; }
h2.sh { font-family: var(--ff-mono); font-weight: 600; font-size: clamp(1.5rem, 2.5vw, 2.25rem); line-height: 1.15; letter-spacing: -.01em; margin: .8rem 0 0; text-wrap: balance; }
.lead { color: var(--fg-muted); font-size: 1.0625rem; margin: 1rem 0 0; }

/* ---------- nav ---------- */
.nav { position: sticky; top: 0; z-index: 60; height: var(--nav-h); border-bottom: 1px solid transparent; transition: background .2s, border-color .2s; }
.nav.scrolled { background: color-mix(in oklab, var(--bg) 82%, transparent); backdrop-filter: saturate(140%) blur(10px); -webkit-backdrop-filter: saturate(140%) blur(10px); border-bottom-color: var(--border); }
.nav__in { max-width: var(--maxw); margin-inline: auto; height: 100%; display: flex; align-items: center; gap: 1.5rem; padding-inline: var(--gutter); }
.brand { display: inline-flex; align-items: center; gap: .55rem; font-family: var(--ff-mono); font-weight: 600; font-size: 1rem; letter-spacing: -.02em; color: var(--fg); }
.brand img { width: 24px; height: 24px; display: block; }
.nav__links { display: none; align-items: center; gap: .25rem; }
.nav__links a, .nav__drop-btn { font-size: .875rem; color: var(--fg-muted); padding: .5rem .7rem; border-radius: var(--r); background: none; border: 0; font-family: inherit; cursor: pointer; display: inline-flex; align-items: center; gap: .3rem; transition: color .12s; }
.nav__links a:hover, .nav__drop-btn:hover, .nav__drop[open] .nav__drop-btn { color: var(--fg); }
/* The current route (2026-08-20). The markup carried aria-current="page" on
   Home from the start and nothing ever styled it, so every route looked
   identical in the nav -- including the one you were standing on. The other
   nine pages did not even carry the attribute; they do now.

   The label brightens and nothing else -- no plate (owner's call, 2026-08-20).
   It goes past --fg to pure white, which is the ceiling: --fg is 16.53:1 on the
   page and white is 19.91:1, and there is no brighter colour to reach for. That
   also buys the item its own value -- hover stops at --fg, so the current route
   is now the only thing in the bar that is white, and it stays distinguishable
   even while the cursor sits on a sibling. Deliberately not the accent either: DESIGN.md §11 is explicit
   that a navigation link is not a primary action and never wears emerald, which
   is the trap here, because "you are here" feels like it wants the brand
   colour.

   Product and Industries are <summary> elements, not links, and aria-current is
   for links -- so their parent <details> carries .is-current instead and the
   attribute stays honest. */
.nav__links a[aria-current="page"],
.nav__drop.is-current > .nav__drop-btn { color: #FFF; }
.mobile a[aria-current="page"] { color: #FFF; }
.nav__drop { position: relative; }
.nav__drop-btn svg { width: 12px; height: 12px; transition: transform .15s; }
.nav__drop[open] .nav__drop-btn svg { transform: rotate(180deg); }
.nav__menu { position: absolute; top: calc(100% + 8px); left: 0; width: 340px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); padding: .4rem; box-shadow: 0 20px 50px -20px rgba(0,0,0,.7); }
.nav__drop:not([open]) .nav__menu { display: none; }
.nav__menu a { display: block; padding: .6rem .7rem; border-radius: var(--r); }
.nav__menu a:hover { background: var(--surface-2); }
.nav__menu .t { display: block; color: var(--fg); font-size: .875rem; font-weight: 500; }
.nav__menu .d { display: block; color: var(--fg-faint); font-size: .75rem; margin-top: .1rem; }
.nav__spacer { flex: 1; }
/* The right-hand cluster needs its own gap: .nav__in's 1.5rem is the rhythm for
   major regions and would read as three unrelated items rather than one group. */
.nav__end { display: flex; align-items: center; gap: .35rem; }
.nav__cta { display: inline-flex; align-items: center; height: 34px; padding-inline: .85rem; border: 1px solid var(--accent); border-radius: var(--r); color: var(--accent); font-size: .875rem; font-weight: 500; transition: background .12s; margin-left: .25rem; }
.nav__cta:hover { background: var(--accent-soft); }
/* Log in is a text link, not a second button. Existing customers already know
   where they are going; giving it button weight would put two competing
   actions in the header and break the one-primary-CTA rule. */
.nav__login { display: none; font-size: .875rem; font-weight: 500; color: var(--fg-muted);
  padding: .5rem .7rem; border-radius: var(--r); transition: color .12s; white-space: nowrap; }
.nav__login:hover { color: var(--fg); }
/* Scoped to .nav on purpose: the shared `.social` base rule is declared later in
   this file, so a bare `.nav__social` would lose the tie on equal specificity and
   the icons would show at every width. */
.nav .nav__social { display: none; }
/* Divider between "follow us" and "use the product" — two different intents. */
.nav__social::after { content: ""; width: 1px; height: 16px; background: var(--border);
  margin-left: .5rem; }
.burger { display: inline-flex; flex-direction: column; justify-content: center; gap: 4px; width: 40px; height: 40px; margin-left: auto; background: none; border: 0; cursor: pointer; }
.burger span { width: 20px; height: 2px; margin-inline: auto; background: var(--fg-muted); transition: transform .18s, opacity .18s; }
.mobile { position: fixed; inset: var(--nav-h) 0 0 0; z-index: 55; background: var(--bg); padding: 1.25rem var(--gutter) 2rem; display: none; flex-direction: column; overflow-y: auto; }
body.menu-open .mobile { display: flex; }
body.menu-open { overflow: hidden; }
body.menu-open .burger span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
body.menu-open .burger span:nth-child(2) { opacity: 0; }
body.menu-open .burger span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }
.mobile a, .mobile .mlabel { display: block; padding: .75rem .2rem; color: var(--fg-muted); font-size: 1rem; border-bottom: 1px solid var(--border); }
.mobile .mlabel { color: var(--fg-faint); font-family: var(--ff-mono); font-size: .7rem; text-transform: uppercase; letter-spacing: .08em; border-bottom: 0; padding-bottom: .3rem; }
.mobile .sub { padding-left: 1rem; }
.mobile__cta { margin-top: auto; }
.mobile__cta .btn { width: 100%; justify-content: center; margin-top: 1.25rem; }
/* `.mobile a` (0,2,0) outranks `.btn--primary` (0,1,0), so the mobile primary
   CTA was painting --fg-muted on emerald: 1.41:1, effectively unreadable. It is
   the single most important control on the surface most Senti traffic uses. */
.mobile__cta .btn--primary { color: var(--accent-ink); }
.mobile__cta .btn--ghost { color: var(--fg); }
/* The social row is the one place in the mobile sheet that is not a link to a
   page, so it drops the row border and sits below the CTA. */
.mobile__social { display: flex; justify-content: center; gap: .35rem; margin-top: 1rem; }
.mobile__social a { border-bottom: 0; padding: 0; }

/* ---------- social icons (nav, mobile sheet, footer) ---------- */
.social { display: inline-flex; align-items: center; gap: .05rem; }
.social a { display: inline-grid; place-items: center; width: 32px; height: 32px;
  border-radius: var(--r); color: var(--fg-faint);
  transition: color .12s, background .12s; }
.social a:hover { color: var(--fg); background: var(--surface-2); }
.social svg { width: 16px; height: 16px; fill: currentColor; display: block; }
/* display:flex (block-level), not the base rule's inline-flex — otherwise the
   row sits on the same line as the Live beta chip instead of below it.
   -6px is optical: it aligns the first glyph to the text edge above, rather
   than aligning the tap target's edge. */
.footer__social { display: flex; margin-top: 1.15rem; margin-left: -6px; }
.footer__social svg { width: 18px; height: 18px; }
.footer__social a { width: 34px; height: 34px; }

/* ---------- buttons ---------- */
/* 44px, not 46: design/multipage/html-brainstorm.md §4.5 specifies 44px for the
   large (nav + hero) size and the build had drifted 2px above it. 44 is also the
   floor for Apple HIG and WCAG 2.5.5 touch targets, so this is the smallest the
   control may go — the spec's 36px "medium" and 28px "small" sizes are still
   unbuilt, and every .btn on the site shares this one size. */
.btn { display: inline-flex; align-items: center; gap: .5rem; height: 44px; padding-inline: 1.3rem; border-radius: var(--r); font-family: var(--ff-body); font-size: 1rem; font-weight: 500; border: 1px solid var(--accent); transition: background .14s, transform .14s; white-space: nowrap; cursor: pointer; }
.btn--primary { background: var(--accent); color: var(--accent-ink); }
.btn--primary:hover { background: color-mix(in srgb, var(--accent) 88%, white); }
/* --outline carries what used to be called --ghost, unchanged. The rules were
   right; the name was not. A transparent fill with a visible border is shadcn's
   `outline`, and DESIGN.md §8 standardises on shadcn's vocabulary for the whole
   studio, Senti included. The 19 secondary CTAs that pair with .btn--primary in
   the heroes and final sections all want exactly this, so they keep it -- they
   just now call it by its real name. */
/* The secondary CTA: a filled plate, muted label, brightening on hover
   (owner's call, 2026-08-20). This is a deliberate departure from DESIGN.md
   §11, which specifies the secondary tone as transparent with no label change;
   the marketing surface fills it instead, and DESIGN-MARKETING's CTA row is
   where that decision is recorded.

   The label is --fg-muted, not --fg. Not a contrast question -- it measures
   6.0:1 on the plate, past AA -- but a hierarchy one: beside a solid emerald
   "Book a demo" a white label made the secondary read at nearly the primary's
   weight.

   The rest fill is --surface-2 rather than --surface because this variant lives
   over two different grounds: the page (rgb(8,9,12)) in the heroes, and
   form.lead in the close -- and form.lead IS --surface, so a --surface plate
   would vanish exactly where the close's secondary CTA sits. Measured with
   CSS.forcePseudoState on the live page, not guessed. --surface-2 clears both.

   The one ground it does NOT clear is .panel, which is --surface-2 itself. No
   .btn--outline sits in a panel today -- all 20 were audited against their
   painted ground -- but if one ever does, it needs the plate stepped up to
   --border there, and that is the last rung: the ladder is bg -> surface ->
   surface-2 -> border with nothing above the hairline.

   Hover rises the plate to --border and the label to --fg. Filling to exactly
   the hairline value is what keeps the edge from inverting: any lighter and the
   border would read as a dark outline around a lighter button. The border
   itself never changes -- a secondary that grows a brighter edge under the
   cursor competes with the primary next to it. */
.btn--outline { background: var(--surface-2); color: var(--fg-muted); border-color: var(--border); }
.btn--outline:hover { background: var(--border); color: var(--fg); }
/* --ghost is now the ghost DESIGN.md means: no fill, no border, a neutral
   surface on hover. It is the variant for an action that lives INSIDE another
   component and should not compete with the page's own CTA -- which is exactly
   the explorer's Explore link. Note the hover surface is --surface-2, not the
   brand accent: shadcn's `accent` token is a neutral hover ground, not the
   primary colour, and reading it as mint here would make a tertiary action the
   loudest thing in the block. */
.btn--ghost { background: transparent; color: var(--fg); border-color: transparent; }
.btn--ghost:hover { background: var(--surface-2); }
.btn--wire { background: transparent; color: var(--accent); border-color: var(--accent); }
.btn--wire:hover { background: var(--accent-soft); }
/* Arrows are icons, not characters. `&rarr;` inherited the text font, so it
   changed shape with every family it landed in and never matched the Lucide set
   the rest of the page draws from. .ico sizes off its own text (1.1em) so one
   rule serves a 16px button and a 12.5px mono link without a second number. */
.ico { width: 1.1em; height: 1.1em; flex: none; }
.btn .ico { transition: transform .15s; }
.btn:hover .ico { transform: translateX(3px); }

/* ---------- hero ---------- */
.hero { position: relative; overflow: hidden; padding-block: clamp(80px, 12vw, 160px) clamp(48px, 7vw, 88px); text-align: center; }
.hero--sub { padding-block: clamp(64px, 9vw, 116px) clamp(36px, 5vw, 60px); }
.hero__glow { position: absolute; inset: 0; z-index: 0; pointer-events: none; background:
    radial-gradient(60% 50% at 50% 0%, var(--mkt-glow), transparent 70%),
    linear-gradient(var(--mkt-grid) 1px, transparent 1px) 0 0 / 100% 56px,
    linear-gradient(90deg, var(--mkt-grid) 1px, transparent 1px) 0 0 / 56px 100%;
  -webkit-mask-image: radial-gradient(70% 60% at 50% 20%, #000 40%, transparent 78%);
  mask-image: radial-gradient(70% 60% at 50% 20%, #000 40%, transparent 78%);
  animation: gridDrift 16s linear infinite; }
@keyframes gridDrift { to { background-position: 0 0, 56px 56px, 56px 56px; } }
@keyframes bloomPulse { 0%, 100% { opacity: .65; transform: scale(1); } 50% { opacity: 1; transform: scale(1.12); } }
.hero__in { position: relative; z-index: 1; max-width: 60rem; margin-inline: auto; }
.hero h1 { font-family: var(--ff-mono); font-weight: 600; font-size: clamp(2.5rem, 6vw, 5.25rem); line-height: 1.02; letter-spacing: -.03em; margin: 1.5rem 0 0; text-wrap: balance; }
.hero--sub h1 { font-size: clamp(2.1rem, 4.6vw, 3.6rem); line-height: 1.05; }
.hero__hook { font-family: var(--ff-mono); font-weight: 500; font-size: clamp(1.05rem, 2vw, 1.4rem); color: var(--fg); margin: 1.4rem 0 0; letter-spacing: -.01em; }
.hero__desc { color: var(--fg-muted); font-size: 1.1875rem; line-height: 1.6; max-width: 44rem; margin: 1.5rem auto 0; }
.hero--sub .hero__desc { font-size: 1.0625rem; }
.hero__cta { display: flex; flex-wrap: wrap; gap: .85rem; justify-content: center; margin-top: 2.25rem; }
.hero__note { color: var(--fg-faint); font-size: .9rem; max-width: 40rem; margin: 1.75rem auto 0; }

/* ---------- stats strip ---------- */
.stats { border-block: 1px solid var(--border); background: color-mix(in srgb, var(--surface) 45%, var(--bg)); padding-block: clamp(40px, 5vw, 64px); }
.stats__grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.5rem; }
.stat { text-align: center; }
.stat__n { font-family: var(--ff-mono); font-weight: 600; font-size: clamp(1.9rem, 4vw, 3.25rem); line-height: 1; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.stat__n .u { color: var(--fg-muted); }
.stat__l { display: block; font-family: var(--ff-mono); font-size: .7rem; text-transform: uppercase; letter-spacing: .07em; color: var(--fg-faint); margin-top: .7rem; }
.stats__note { text-align: center; color: var(--fg-faint); font-size: .875rem; margin: 1.75rem auto 0; max-width: 46rem; }

/* ---------- solutions grid ---------- */
.sol-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.25rem; }
.sol { display: flex; flex-direction: column; padding: 1.6rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); transition: border-color .15s, transform .15s, background .15s; }
.sol:hover { border-color: var(--accent-line); transform: translateY(-3px); }
.sol__t { font-family: var(--ff-mono); font-weight: 600; font-size: 1.15rem; line-height: 1.25; letter-spacing: -.01em; }
.sol__who { color: var(--fg); font-size: .95rem; margin: .9rem 0 0; }
.sol__who b { color: var(--fg); font-weight: 600; }
.sol__d { color: var(--fg-muted); font-size: .95rem; margin: .55rem 0 0; }
.sol__link { margin-top: auto; padding-top: 1.25rem; color: var(--accent); font-family: var(--ff-mono); font-size: .8rem; letter-spacing: .02em; display: inline-flex; align-items: center; gap: .4rem; }
.sol:hover .sol__link .arr { transform: translateX(3px); }
.sol__link .arr { transition: transform .15s; }

/* ---------- generic feature cards (icon + text) ---------- */
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.25rem; }
.cards.two { grid-template-columns: repeat(2, 1fr); }
.card { padding: 1.6rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); }
/* Numerals are furniture: this box was the largest block of emerald on the page
   that meant nothing at all. */
.card__ix { display: inline-grid; place-items: center; width: 34px; height: 34px; border: 1px solid var(--sand-line); border-radius: var(--r); color: var(--sand); font-family: var(--ff-mono); font-size: .8rem; margin-bottom: 1rem; }
/* display: block on both. They are <span>s in a plain block .card, so they shared
   one line -- "REVIEW One place to review the operation" -- and .card__tag's own
   margin-bottom: .6rem was dead, because vertical margins do not apply to inline
   boxes. That declaration is the proof of intent: the tag was written to sit
   above the title. Measured on /product/ [04] and [06] before the fix; .card is
   only a flex column under .feat-grid, which is why the same markup reads
   correctly there and nowhere else. */
.card__tag, .card__t { display: block; }
.card__t { font-family: var(--ff-mono); font-weight: 600; font-size: 1rem; line-height: 1.25; letter-spacing: -.01em; }
.card__d { color: var(--fg-muted); font-size: .875rem; margin: .55rem 0 0; }

/* ---------- pain / bullet list ---------- */
.checks { list-style: none; margin: 0; padding: 0; max-width: 46rem; }
.checks li { position: relative; padding: .55rem 0 .55rem 1.7rem; color: var(--fg-muted); }
.checks li::before { content: ""; position: absolute; left: 0; top: 1rem; width: 7px; height: 7px; border-radius: 9999px; background: var(--fg-faint); }
.checks.pain li::before { background: var(--fg-faint); border-radius: 1px; }

/* ---------- split (text + panel) ---------- */
.split { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(2rem, 5vw, 4rem); align-items: center; }
.split__body h2 { font-family: var(--ff-mono); font-weight: 600; font-size: clamp(1.4rem, 2.5vw, 2rem); line-height: 1.18; letter-spacing: -.01em; margin: .7rem 0 0; text-wrap: balance; }
.split__body p { color: var(--fg-muted); margin: 1rem 0 0; }
.panel-media { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; }
.panel-media img { width: 100%; }

/* ---------- comparison tables ---------- */
.cmp { position: relative; max-width: 60rem; margin-inline: auto; border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; box-shadow: 0 30px 70px -50px rgba(0,0,0,.6), 0 0 90px -55px color-mix(in srgb, var(--accent) 20%, transparent); }
.cmp__row { display: grid; grid-template-columns: 1fr 1fr; }
.cmp__row + .cmp__row { border-top: 1px solid var(--border); }
.cmp__row > div { padding: 1rem 1.25rem; font-size: 1rem; }
.cmp__row > div:first-child { border-right: 1px solid var(--border); color: var(--fg-muted); }
.cmp__head > div { font-family: var(--ff-mono); font-size: .75rem; text-transform: uppercase; letter-spacing: .07em; color: var(--fg-faint); background: var(--surface-2); }
.cmp__head .accent { color: var(--fg); }
.cmp__row .with { color: var(--fg); }
.cmp__row .with::before { content: ""; display: inline-block; width: 6px; height: 6px; border-radius: 9999px; background: var(--fg-faint); margin-right: .55rem; vertical-align: middle; }

/* .opts — the 3/4-column option matrix marketing writes as a Markdown table.
   .cmp is a fixed two-column "you own / Senti operates" split and cannot carry
   these. Column count comes from --opt-cols so one rule serves both widths.
   Collapses to stacked cards below the table breakpoint, where the row's first
   cell becomes the card title and the rest label themselves from data-l. */
.opts { max-width: var(--measure-wide); margin-inline: auto; border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; }
.opts__row { display: grid; grid-template-columns: repeat(var(--opt-cols, 3), 1fr); }
.opts__row + .opts__row { border-top: 1px solid var(--border); }
.opts__row > div { padding: 1rem 1.25rem; font-size: .95rem; color: var(--fg-muted); line-height: 1.6; }
.opts__row > div + div { border-left: 1px solid var(--border); }
.opts__row > div:first-child { color: var(--fg); }
.opts__row > div:first-child b { font-family: var(--ff-display); font-weight: 500; }
.opts__head > div, .opts__head > div:first-child { font-family: var(--ff-mono); font-size: .72rem; text-transform: uppercase; letter-spacing: .12em; color: var(--fg-faint); background: var(--surface-2); }

/* ---------- panel (highlight / non-custodial) ---------- */
.panel { max-width: 56rem; margin-inline: auto; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-lg); padding: clamp(1.75rem, 4vw, 3rem); text-align: center; }
.panel h2 { font-family: var(--ff-mono); font-weight: 600; font-size: clamp(1.4rem, 2.5vw, 2rem); margin: 0; letter-spacing: -.01em; }
.panel__sub { color: var(--fg-muted); margin: .75rem 0 1.75rem; }
.panel__list { list-style: none; margin: 0 auto; padding: 0; max-width: 40rem; text-align: left; }
.panel__list li { position: relative; padding: .5rem 0 .5rem 1.6rem; color: var(--fg-muted); }
.panel__list li::before { content: ""; position: absolute; left: 0; top: .95rem; width: 7px; height: 7px; border-radius: 9999px; background: var(--fg-faint); }
.panel__emph { color: var(--fg); font-size: 1rem; margin: 1.75rem auto 0; max-width: 44rem; }

/* ===========================================================================
   Body-prose sizes land on whole pixels (2026-08-19)

   Audited every font-size that reaches Home and converted it to px at the 16px
   root. Eleven declarations in the 14.0-15.6px prose band were sitting on
   fractional values -- .97rem = 15.52px, .95rem = 15.2px, .92rem = 14.72px,
   .9rem = 14.4px, .88rem = 14.08px -- so a paragraph, its neighbour and the nav
   above it were each half a pixel apart for no reason anyone chose. They are all
   .875rem (14px) now:

     .step__d  .path__note  .footer__col a  .footer__tag  .bento__who  .bento__d
     .nav__links a / .nav__drop-btn  .nav__menu .t  .nav__mega .nav__feat .t
     .flow__node::after  .terminal__sym

   Follow-up (2026-08-20): form.lead .submit .note was still on .85rem
   (13.6px) -- same prose band, missed by the first sweep. Now .875rem too.

   Deliberately NOT swept into 14px, because 14px would be the wrong size for
   what they are, not merely a rounder one:

     - the 16-17px band (.step__t, .panel__emph, .faq summary, .brand,
       .footer__brand, .bento__t at 16.32-17.28px) -- these are headings and
       lead-ins; dropping them to 14px would flatten a hierarchy, not tidy it.
     - the 8.8-13.6px mono label band (.chip, .stat__l, .tside__hd, .footer__sub
       and ~15 more) -- tracked uppercase furniture. Rounding these
       is worth doing but it is a type-scale decision across all ten routes, not
       a find-and-replace, and it belongs in its own pass.

   The clamp() sizes are left alone by definition: they resolve against the
   viewport, so there is no pixel to round.
   =========================================================================== */
/* ---------------------------------------------------------------------------
   Second rounding pass (2026-08-20). The 19th swept the 14-15.6px prose band
   and deliberately left two others alone: the 16-17px heading band and the
   8.8-13.6px mono label band. The owner has now called three of the values in
   those bands, by size rather than by role:

     13.6px (.85rem) and 13.12px (.82rem)  ->  14px (.875rem)
     12.16px (.76rem) and 12.48px (.78rem) ->  12px (.75rem)
     16.8px (1.05rem)                      ->  16px (1rem)

   Applied to every font-size declaration carrying those values, mono and Geist
   alike. Bare values only: clamp() bounds are untouched, since a clamp resolves
   against the viewport and has no pixel to round.

   What this does NOT do is finish the band. 16.32px (.faq summary, 1.02rem) and
   17.28px (.bento__t, 1.08rem) were not on the list and still sit fractional, so
   the 16-17px band now reads 16 / 16.32 / 17.28. Same for the mono labels: the
   8.8-11.84px values are all still there. Both remain what the first pass said
   they were -- a type-scale decision across all ten routes, waiting on its own
   pass. */
/* ---------- path timeline ---------- */
.path { max-width: 48rem; margin-inline: auto; }
.step { position: relative; display: grid; grid-template-columns: auto 1fr; gap: 1.25rem; padding-bottom: 2rem; }
/* `:last-of-type`, not `:last-child` (2026-08-24).
   `.path`'s last child is a `<p class="path__note">` on nine of the ten `.path`
   blocks on the site, so `:last-child` never matched the last `.step` and the
   connector ran on down out of the final step into the note. Measured at 1512
   with reveals on: eight routes affected — /product/, /trust/, solo-traders,
   quant-prop, earning-platforms, trading-groups-academies, strategies-devs
   (x2) and vps-providers. Home is the only clean one, and only because its note
   moved into the chapter head on 2026-08-19.

   The type selector is what separates them: every step is a `<div>` and every
   note is a `<p>`, checked on all ten `.path` blocks, so `:last-of-type` is the
   last step whether or not a note follows it. Where a `.path` does end on a
   step the two selectors are identical, which is why Home computes unchanged.

   NOT touched: `.step`'s own `padding-bottom: 2rem` still applies to the last
   step, so the gap above the note is what it always was. That is a separate
   decision about spacing, not about a line drawn into empty space. */
.step:not(:last-of-type)::before { content: ""; position: absolute; left: 19px; top: 40px; bottom: 0; width: 1px; background: var(--border); }
.step__n { width: 40px; height: 40px; display: grid; place-items: center; border: 1px solid var(--border); border-radius: var(--r); font-family: var(--ff-mono); font-size: .875rem; color: var(--fg-muted); background: var(--bg); position: relative; z-index: 1; }
.step__t { font-family: var(--ff-mono); font-weight: 600; font-size: 1rem; margin-top: 0; }
.step__d { color: var(--fg-muted); font-size: .875rem; margin-top: .3rem; }
.path__note { color: var(--fg-faint); font-size: .875rem; margin-top: 1.5rem; }

/* ---------- pricing / plan tiers (no returns, structure only) ---------- */
.tiers { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.25rem; align-items: start; }
.tiers--4 { grid-template-columns: repeat(4, 1fr); }
.tier { display: flex; flex-direction: column; padding: 1.75rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); }
.tier.feat { border-color: var(--accent-line); background: color-mix(in srgb, var(--accent) 5%, var(--surface)); }
.tier__tag { font-family: var(--ff-mono); font-size: .66rem; text-transform: uppercase; letter-spacing: .08em; color: var(--accent); }
.tier__t { font-family: var(--ff-mono); font-weight: 600; font-size: 1.3rem; letter-spacing: -.01em; margin: .5rem 0 0; }
.tier__unit { font-size: 1rem; font-weight: 500; color: var(--fg-muted); }
.tier__d { color: var(--fg-muted); font-size: .95rem; margin: .6rem 0 0; }
.tier__list { list-style: none; margin: 1.25rem 0 0; padding: 1.25rem 0 0; border-top: 1px solid var(--border); display: grid; gap: .6rem; }
.tier__list li { position: relative; padding-left: 1.5rem; color: var(--fg-muted); font-size: .93rem; }
.tier__list li::before { content: ""; position: absolute; left: 0; top: .55rem; width: 6px; height: 6px; border-radius: 9999px; background: var(--accent); }
.tier .btn { margin-top: 1.5rem; width: 100%; justify-content: center; }

/* ---------- quote ---------- */
.quote { max-width: 46rem; margin-inline: auto; text-align: center; }
.quote p { font-family: var(--ff-mono); font-weight: 500; font-size: clamp(1.15rem, 2.2vw, 1.6rem); line-height: 1.35; letter-spacing: -.01em; color: var(--fg); text-wrap: balance; }
.quote cite { display: block; font-style: normal; color: var(--fg-faint); font-size: .9rem; margin-top: 1.25rem; }

/* ---------- faq ---------- */
.faq { max-width: 54rem; margin-inline: auto; border-top: 1px solid var(--border); }
.faq details { border-bottom: 1px solid var(--border); }
.faq summary { list-style: none; cursor: pointer; padding: 1.25rem .25rem; display: flex; align-items: center; justify-content: space-between; gap: 1rem; font-family: var(--ff-mono); font-weight: 500; font-size: 1rem; color: var(--fg); }
.faq summary::-webkit-details-marker { display: none; }
.faq summary .pm { color: var(--fg-faint); font-size: 1.4rem; line-height: 1; transition: transform .15s; }
.faq details[open] summary .pm { transform: rotate(45deg); color: var(--accent); }
/* 16px, not the 17px body default. The answer was inheriting `body` while the
   question above it runs at 1.02rem (16.32px), so the subordinate text was the
   LARGER of the two -- the hierarchy read backwards on every route. 1rem puts
   it under the question and keeps the ratio a reader can feel.

   No max-width. The answer used to stop at 46rem while the question above it
   ran the full width of the row, so every open <details> stepped inward from
   its own summary and the block had two right edges. The answer now ends where
   the question ends -- one edge per row, which is what the rest of the numbered
   document does. Owner's call, requested 2026-08-19: on Home the row is the
   full chapter (96rem), so these lines get long. Say the word and a cap comes
   back as a shared token rather than a number written only here.

   Padding is asymmetric on purpose: .25rem left to stay on the summary's text
   edge, 7.75rem (124px) right. With the cap gone the answer ran all the way
   into the column the `+` control occupies, so the last word of a line and the
   toggle above it were sharing one edge with nothing between them. 124px is
   also doing what the removed 46rem cap used to do -- it pulls the line ending
   back off the full-chapter width on Home without reintroducing a second
   measure, because it is clearance from the row's own edge rather than a width
   of its own. Owner's number, raised 40px -> 124px on 2026-08-19. */
.faq__a { padding: 0 7.75rem 1.4rem .25rem; color: var(--fg-muted); font-size: 1rem; }
/* 124px is clearance on a wide row; on a phone it is a third of the row. Below
   700px the answer goes back to the summary's own inset -- there is no spare
   width to give away, and the `+` it was clearing sits on a line of its own by
   then anyway. */
@media (max-width: 700px) { .faq__a { padding-right: .25rem; } }

/* ---------- quick links ---------- */
/* padding-block: 0 (owner's call, 2026-08-21). The 72px was written for a
   .quick that had to separate itself from what came before AND from the section
   edge below. Neither is its job any more: the block above it already hands it
   clamp(1.75rem, 3vw, 2.75rem) of margin-block-start from the chapter-rhythm
   rule, and the section's own padding-block closes the chapter underneath. The
   72px was a third interval stacked on those two, which is why the relevant-
   applications line read as floating in its own empty row rather than as the
   footnote to the chapter it belongs to. */
.quick { text-align: center; font-family: var(--ff-mono); font-size: .875rem; color: var(--fg-faint); letter-spacing: .02em; padding-block: 0; }
.quick a { color: var(--fg-muted); }
.quick a:hover { color: var(--accent); }
.quick .sep { opacity: .5; margin-inline: .6rem; }

/* ---------- final cta + form ---------- */
.final { border-top: 1px solid var(--border); }
.final__card { max-width: 46rem; margin-inline: auto; }
.final h2 { font-family: var(--ff-mono); font-weight: 600; font-size: clamp(1.5rem, 3vw, 2.35rem); line-height: 1.18; letter-spacing: -.01em; margin: 0; text-align: center; text-wrap: balance; }
.final__sub { color: var(--fg-muted); text-align: center; margin: 1rem auto 2.5rem; max-width: 40rem; }
form.lead { position: relative; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); padding: clamp(1.5rem, 4vw, 2.25rem); display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; box-shadow: 0 40px 90px -55px rgba(0,0,0,.7), 0 0 100px -55px color-mix(in srgb, var(--accent) 22%, transparent); }
form.lead .full { grid-column: 1 / -1; }
/* ===========================================================================
   [08] form fields match the LIVE app, not the written spec (2026-08-19)

   Read off app.sentitrade.xyz/login itself -- V2Label and V2Input pulled out of
   index-DtrAHM2C.js, and the tokens out of index-DMuNCd5E.css:

     V2Label  block font-['Geist'] text-[11px] font-semibold tracking-[0.6px]
              uppercase text-[var(--v2-fg-muted)] mb-1.5
     V2Input  h-8 rounded-none border border-[var(--v2-border)] px-3
              bg-[var(--v2-bg)] text-[12px] font-['Geist'] text-[var(--v2-fg)]
              hover:bg-[var(--v2-surface-2)] focus:border-[var(--v2-pos)]

   This is NOT DESIGN.md §20.2.4, and the difference is not drift on our side.
   §20.2.4 specifies 12px sentence-case Geist 400, and the doc says in its own
   words that trader-facing forms have not been migrated onto it yet -- the login
   screen is trader-facing, so it still runs the 11px uppercase tier. Owner chose
   to match what ships over what is written. If the app ever flips V2Label's base
   default, this block has to move with it or the two surfaces split again.

   The dark palettes are the same system, verified value by value: --v2-bg
   #0b0d11 = --bg, --v2-surface-2 #1d2027 = --surface-2, --v2-fg #e8eaee = --fg,
   --v2-fg-muted #989da8 = --fg-muted, --v2-pos #34d399 = --accent. One token is
   deliberately NOT matched: --v2-border is #2a2e36, ours is #23272E, because the
   hairline was taken down a step across this whole site earlier today. Matching
   the app there would put one loud rule inside a page tuned quieter.
   =========================================================================== */
.field { display: flex; flex-direction: column; gap: .375rem; }   /* mb-1.5 = 6px */
/* Label steps off the live app's tier and back onto DESIGN.md §20.2.4 (owner's
   call): 12px, Geist 400, sentence-case, --fg. The input keeps the live app's
   geometry, so the field is now a deliberate hybrid -- product control, product
   spec label.

   letter-spacing goes to normal along with the casing, and that is not a fourth
   change smuggled in: the .6px existed to open up UPPERCASE at 11px. Left on
   sentence-case Geist it is the exact thing that made this label read as a
   different typeface from the body text two turns ago, and §20.2.4 calls
   `tracking-normal` load-bearing for that reason. Say the word if the .6px
   should stay. */
.field label { font-family: var(--ff-body); font-size: .75rem; font-weight: 400;
  letter-spacing: normal; text-transform: none; color: var(--fg); }
.field input, .field select, .field textarea { background: var(--bg);
  border: 1px solid var(--border); border-radius: var(--r); color: var(--fg);
  font-family: var(--ff-body); font-size: .875rem;
  height: 36px; padding: 0 12px; width: 100%;
  transition: background-color .14s, border-color .14s; }

/* There was no ::placeholder rule on this site until now, so the one placeholder
   that already existed (the textarea's) was rendering in the UA's own grey --
   a fixed mid-grey picked for light backgrounds, which sits at roughly 2:1 on
   --bg. --fg-faint is the site's "present but recessive" ink and clears 3:1,
   and it reads as an empty field rather than as a typed value, which is the
   whole job of a placeholder. */
.field input::placeholder, .field textarea::placeholder { color: var(--fg-faint); opacity: 1; }
/* Firefox still ships opacity: .54 on ::placeholder by UA default; without the
   reset above the colour would be applied and then faded again. */

/* The select has no placeholder to style -- "Select one" is a real <option>, so
   it paints in --fg and would read as a chosen value sitting next to three
   inputs that now show faint hints. This dims the control only while the empty
   option is the selected one; .field select option below keeps the open list at
   full contrast. */
.field select:has(option[value=""]:checked) { color: var(--fg-faint); }
/* background-COLOR, not the shorthand: `background: …` resets background-image
   too, and the select's chevron lives there. The shorthand here blanked the
   arrow on hover in the first version of this. */
.field input:hover, .field select:hover, .field textarea:hover { background-color: var(--surface-2); }

/* The select drew the browser's own arrow inside its content box, so the long
   options ("Strategy Developers · Create EA") ran under it and out through the
   12px right padding -- a native control cannot lay itself out around a UA
   glyph it does not tell us the width of. appearance:none takes the glyph away
   and we draw our own, then reserve 34px of right padding for it: 12px of
   inset, a 14px mark, 8px of clearance from the text.

   Chevron as a data-URI background rather than a pseudo-element, because a
   <select> cannot host ::before/::after -- it is a replaced element. The stroke
   is #989DA8 literal (= --fg-muted); a data URI has no access to currentColor
   or to the page's custom properties. */
.field select {
  -webkit-appearance: none; appearance: none;
  padding-right: 34px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23989DA8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 14px 14px;
  text-overflow: ellipsis;
}
/* The popup list is OS-drawn on macOS and ignores this, but Chrome and Firefox
   on Windows/Linux honour it -- without it the open list is white-on-white. */
.field select option { background: var(--surface); color: var(--fg); }
/* A textarea cannot be 36px. It keeps every other part of the skin and takes
   its own height. */
.field textarea { height: auto; min-height: 96px; padding: .5rem 12px; resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus {
  border-color: var(--accent); background-color: var(--bg); outline: none; box-shadow: none; }

form.lead .submit { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; margin-top: .25rem; }
form.lead .submit .note { color: var(--fg-faint); font-size: .875rem; }

/* ---------- footer ---------- */
.footer { border-top: 1px solid var(--border); padding-block: clamp(48px, 6vw, 72px) 2.5rem; }
.footer__grid { display: grid; grid-template-columns: 1.5fr 1.15fr 1fr 1fr 1fr; gap: 2rem 2.5rem; }
/* Capabilities and deployment models are different categories and koni-growth
   requires them visually separated wherever they are listed together — the
   footer included, not just /product and the nav. */
.footer__sub { display: block; font-family: var(--ff-mono); font-size: .6rem;
  text-transform: uppercase; letter-spacing: .09em; color: var(--sand-dim);
  margin: 1rem 0 .35rem; }
.footer__col a + .footer__sub { margin-top: 1.15rem; }
.footer__brand { display: inline-flex; align-items: center; gap: .5rem; font-family: var(--ff-mono); font-weight: 600; font-size: 1rem; color: var(--fg); }
.footer__brand img { width: 22px; height: 22px; }
.footer__tag { color: var(--fg-muted); font-size: .875rem; margin: 1rem 0 0; max-width: 30ch; }
.footer__status { display: inline-flex; align-items: center; gap: .5rem; font-family: var(--ff-mono); font-size: .68rem; text-transform: uppercase; letter-spacing: .07em; color: var(--fg-faint); margin-top: 1.1rem; }
.footer__status .dot { width: 6px; height: 6px; border-radius: 9999px; background: var(--accent); }
.footer__col h4 { font-family: var(--ff-mono); font-size: .68rem; text-transform: uppercase; letter-spacing: .08em; color: var(--fg-faint); margin: 0 0 1rem; }
.footer__col a { display: block; padding: .3rem 0; color: var(--fg-muted); font-size: .875rem; }
.footer__col a:hover { color: var(--fg); }
.footer__bottom { max-width: var(--maxw); margin: 2.5rem auto 0; padding-top: 1.75rem; border-top: 1px solid var(--border); display: flex; flex-wrap: wrap; gap: .75rem 1.5rem; align-items: baseline; justify-content: space-between; }
.footer__legal { color: var(--fg-faint); font-size: .875rem; max-width: 64ch; }
.footer__links { font-family: var(--ff-mono); font-size: .75rem; color: var(--fg-faint); }
.footer__links a { color: var(--fg-muted); }
.footer__links a:hover { color: var(--fg); }

/* ===========================================================================
   2026 VISUAL LANGUAGE — Institutional Terminal
   Asymmetric hero + crafted product mockup, bento, line icons, SVG flow,
   activity ticker, works-with band, count-up. All honesty/PII safe.
   =========================================================================== */

/* ---------- asymmetric home hero ---------- */
.hero--home { text-align: left; padding-block: clamp(72px, 10vw, 132px) clamp(48px, 7vw, 96px); }
.hero--home .hero__in { max-width: none; }
.hero__split { display: grid; grid-template-columns: 1.02fr .98fr; gap: clamp(2rem, 5vw, 4.5rem); align-items: center; }
.hero__copy .eyebrow { display: inline-block; }
.hero--home h1 { font-size: clamp(2.3rem, 4.7vw, 4.1rem); margin-top: 1.1rem; }
.hero--home .hero__hook, .hero--home .hero__desc, .hero--home .hero__note { margin-left: 0; margin-right: 0; }
.hero--home .hero__desc { max-width: 34rem; }
.hero--home .hero__cta { justify-content: flex-start; }
.hero__badges { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1.6rem; }
.chip { display: inline-flex; align-items: center; gap: .45rem; padding: .35rem .7rem; border: 1px solid var(--border); border-radius: 9999px; font-family: var(--ff-mono); font-size: .72rem; font-weight: 500; color: var(--fg-muted); background: color-mix(in srgb, var(--surface) 55%, transparent); transition: background .14s; }
/* 16px icon. stroke-width is set here rather than left to the inline attribute
   so the weight lives with the size it depends on: a presentation attribute on
   the SVG loses to this rule, and the three chips can't drift apart. 1.67 on a
   24 viewBox drawn at 16px renders a 1.11px stroke — the icon grows without the
   line getting heavier than the mono stems beside it. */
.chip svg { width: 16px; height: 16px; stroke-width: 1.67; color: var(--fg-faint); }
/* Hover moves the surface, not the strokes. Text, icon and border all hold their
   value; only the plate behind them steps up one gray, --surface -> --surface-2.
   Brightening the strokes instead made the chip read as a control lighting up,
   which is wrong twice over: these are <span>, not links, and in this system a
   lit-up foreground is what emerald does for things you can act on. No cursor
   change and no lift, for the same reason. Behind @media (hover: hover) so a tap
   on touch cannot leave a chip stuck in the hovered state. */
@media (hover: hover) {
  .chip:hover { background: var(--surface-2); }
}

/* ---------- live product card (Fleet Control mockup) ---------- */
.mock { position: relative; }
.mock__ghost { position: absolute; inset: 18px -14px -18px 22px; border: 1px solid var(--border); border-radius: var(--r-lg); background: color-mix(in srgb, var(--surface) 40%, transparent); z-index: 0; }
.mock__bloom { position: absolute; inset: -26% -16% -26% -6%; z-index: 0; pointer-events: none; background: radial-gradient(55% 55% at 58% 42%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 70%); filter: blur(26px); animation: bloomPulse 7s ease-in-out infinite; }
.mock__card { position: relative; z-index: 1; background: color-mix(in srgb, var(--surface) 66%, transparent); -webkit-backdrop-filter: blur(16px) saturate(135%); backdrop-filter: blur(16px) saturate(135%); border: 1px solid color-mix(in srgb, var(--fg) 9%, transparent); border-radius: var(--r-lg); box-shadow: inset 0 1px 0 color-mix(in srgb, white 10%, transparent), 0 50px 100px -50px rgba(0,0,0,.92), 0 0 90px -34px color-mix(in srgb, var(--accent) 24%, transparent); overflow: hidden; }
.mock__bar { display: flex; align-items: center; gap: .6rem; padding: .8rem 1rem; border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--surface-2) 60%, transparent); }
.mock__dots { display: inline-flex; gap: .32rem; }
.mock__dots i { width: 8px; height: 8px; border-radius: 9999px; background: var(--border); }
.mock__title { font-family: var(--ff-mono); font-size: .8rem; color: var(--fg); letter-spacing: -.01em; }
.live { display: inline-flex; align-items: center; gap: .4rem; margin-left: auto; font-family: var(--ff-mono); font-size: .66rem; text-transform: uppercase; letter-spacing: .08em; color: var(--accent); }
.live .pulse { position: relative; width: 7px; height: 7px; border-radius: 9999px; background: var(--accent); }
.live .pulse::after { content: ""; position: absolute; inset: -4px; border-radius: 9999px; border: 1px solid var(--accent); animation: pulse 2s ease-out infinite; }
@keyframes pulse { 0% { transform: scale(.6); opacity: .9; } 100% { transform: scale(1.6); opacity: 0; } }
.mock__kpis { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; background: var(--border); border-bottom: 1px solid var(--border); }
.mock__kpi { background: var(--surface); padding: .85rem 1rem; }
.mock__kpi .k { display: block; font-family: var(--ff-mono); font-size: .58rem; text-transform: uppercase; letter-spacing: .07em; color: var(--fg-faint); }
.mock__kpi .v { display: block; font-family: var(--ff-mono); font-weight: 600; font-size: 1rem; color: var(--fg); margin-top: .3rem; font-variant-numeric: tabular-nums; }
.mock__kpi .v .u { color: var(--accent); }
.mock__rows { padding: .4rem .5rem; }
.mock__row { display: grid; grid-template-columns: auto 1fr auto auto; align-items: center; gap: .8rem; padding: .6rem .6rem; border-radius: var(--r); }
.mock__row + .mock__row { border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); }
.mock__row .sd { width: 8px; height: 8px; border-radius: 9999px; background: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent); }
.mock__row.paused .sd { background: var(--fg-faint); box-shadow: none; }
.mock__nm { min-width: 0; }
.mock__nm b { display: block; font-family: var(--ff-mono); font-size: .875rem; font-weight: 500; color: var(--fg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mock__nm span { font-family: var(--ff-mono); font-size: .66rem; color: var(--fg-faint); }
.mock__spark { width: 84px; height: 26px; display: block; }
.mock__spark path { fill: none; stroke: var(--accent); stroke-width: 1.5; }
.mock__spark.dim path { stroke: var(--fg-faint); }
.mock__st { font-family: var(--ff-mono); font-size: .62rem; text-transform: uppercase; letter-spacing: .06em; color: var(--accent); }
.mock__row.paused .mock__st { color: var(--fg-faint); }
.mock__foot { display: flex; align-items: center; gap: .6rem; padding: .8rem 1rem; border-top: 1px solid var(--border); background: color-mix(in srgb, var(--surface-2) 45%, transparent); }
.mock__lock { display: inline-flex; align-items: center; gap: .45rem; font-family: var(--ff-mono); font-size: .68rem; color: var(--fg-muted); }
.mock__lock svg { width: 14px; height: 14px; color: var(--accent); }
.mock__foot .up { margin-left: auto; font-family: var(--ff-mono); font-size: .68rem; color: var(--fg-faint); }
.mock__foot .up b { color: var(--fg); }

/* ---------- glass rim-light (BitGo-style edge-lit panels, in emerald) ---------- */
.flow, .panel { position: relative; overflow: hidden; }
.panel { box-shadow: inset 0 1px 0 color-mix(in srgb, white 7%, transparent), 0 0 90px -44px color-mix(in srgb, var(--accent) 20%, transparent); }
.flow { box-shadow: 0 0 100px -54px color-mix(in srgb, var(--accent) 26%, transparent); }
.mock__card::before, .flow::before, .panel::before, .bento__tile.feature::before, .terminal__card::before, .browser::before, .cmp::before, form.lead::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1px;
  background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 44%, transparent), color-mix(in srgb, var(--fg) 7%, transparent) 42%, transparent 66%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude; pointer-events: none; z-index: 4;
}
.hl { color: var(--accent); }

/* ---------- stacked hero + big trading terminal ---------- */
.hero--stack { text-align: center; overflow: clip; }
/* padding-bottom:0 exists for ONE reason: so the stacked terminal mockup sits
   flush against the next section. Only zero it when that mockup is actually the
   last thing in the hero. /product and /proof reuse .hero--stack but end on the
   chip row, and the unconditional zero jammed the chips into the next section's
   top border. If :has() is unsupported the rule drops and every hero keeps its
   padding — the safe way to fail.

   The list is explicit rather than `:has(> .hero__stage)` because the reason
   written above is "ends on the mockup", not "has a stage", and the two are not
   the same claim. /product/ and /proof/ have no stage at all and are excluded
   either way; naming the frames keeps the selector honest about why.

   /trust/ went out of this list on 2026-08-24 and came back the same day
   (owner's call). It left because its `.flow` card had a hard bottom border and
   the zero laid that border flat on the stats section's top border. It came back
   because the border was the wrong thing to fix: the pattern is that a hero
   visual dissolves into the next section, and what the card was missing was the
   foot scrim, not the padding. With `.lit-frame .flow::after` in place the border
   is covered by the scrim's -1px inset, exactly as on the six .device frames, and
   flush is correct again. */
.hero--stack:has(> .hero__stage > :is(.device, .lit-frame)) { padding-bottom: 0; }
.hero--stack .hero__in { max-width: none; }
.hero__copy--center { max-width: 64rem; margin-inline: auto; padding-inline: var(--gutter); position: relative; z-index: 1; }
.hero__copy--center .eyebrow { display: inline-block; }
.hero__copy--center h1 { font-size: clamp(2.6rem, 6vw, 5.4rem); }
.hero__copy--center .hero__hook { font-size: clamp(1.15rem, 2.2vw, 1.55rem); }
.hero__copy--center .hero__desc { max-width: 46rem; font-size: 1.25rem; }
.hero__copy--center .hero__desc { margin-inline: auto; }
.hero__copy--center .hero__cta, .hero__copy--center .hero__badges { justify-content: center; }
/* scroll reveal: pin the hero copy so the terminal rises up and overlaps it */
@media (min-width: 900px) { .hero--stack .hero__copy--center { position: sticky; top: 92px; } }
/* `top: 0` added 2026-08-24. Dropping `position` to relative left `top: 92px`
   standing, and a relative box with a top offset is PAINTED 92px lower while its
   layout box stays put — so the copy slid down over the stage that follows it.
   Measured at 1512 with prefers-reduced-motion on, before the fix:

     /trust/              CTA + chips covered by the stage   92px
     Home, solo, quant, groups, mql5, earning                20px

   /trust/ is the worst because its `.flow` had no top margin, so the stage began
   exactly where the copy's layout box ended; the other five sit 72px lower on
   `.device`'s margin. On /trust/ that meant both buttons and the whole chip row
   were behind the panel — the hero's only call to action, invisible to every
   visitor who has reduced motion switched on.

   `top` is meaningless on a static-flow relative box here in the first place:
   under `sticky` the 92px is the stick threshold, not an offset, and at rest a
   sticky box sits at its normal position. Zeroing it restores what the sticky
   version renders at scroll 0. */
@media (prefers-reduced-motion: reduce) {
  .hero--stack .hero__copy--center { position: relative; top: 0; }
}

/* ---- .lit-frame — the lit treatment for a hero visual that is not a mockup ----
   /trust/ is the one stacked hero whose stage holds a complete card (`.flow`)
   instead of a cut-off screenshot, so it had none of the treatment the other six
   share: no bloom, no travelling edge, and 6px of air under the chip row where
   every other stacked hero has 72px.

   Why a new wrapper and not `.device`. `.device` is two things at once — a
   positioning shell that owns `.mock__bloom` and the stage margin, and a claim
   that the frame is cut off at the fold. The second meaning is now load-bearing:
   `.hero--stack:has(> .hero__stage > .device)` zeroes the hero's bottom padding
   on the strength of it. Wrapping the flow card in `.device` would switch that
   zero back on and put the card's bottom border flat on the stats section again.
   `.lit-frame` takes the first meaning only.

   Width is 60rem because `.mock__bloom` is sized in percentages of its offset
   parent. `.flow` caps itself at 60rem inside a 1512px stage, so a wrapper left
   at stage width would have thrown a 1750px bloom behind a 960px card.

   Margin matches `.device`'s opening term exactly — the two elements do the same
   job in the same slot, and the value is not worth re-deriving. `.device`'s
   trailing `-1px` is not copied: that exists so a mockup merges into the next
   section, which is the opposite of what this card wants. */
.lit-frame { position: relative; max-width: 60rem; margin: clamp(2.5rem, 5vw, 4.5rem) auto 0; }
/* The border has to empty out before the beam can use it, exactly as on
   `.browser`. `background-clip` rather than a restated `linear-gradient(--frame)
   padding-box`: `.browser` restates because it also swaps the fill to `--frame`,
   while `.flow` keeps its own two-layer background and only needs it to stop at
   the padding box. One declaration, and the radial tint stays wherever `.flow`
   decides to put it. */
.lit-frame .flow { border-color: transparent; background-clip: padding-box; }

/* ---- .hero--ladder — one lede, tightened display scale (2026-08-17) ----
   Home merged its two subheads into a single lede, so the display scale that
   was tuned for a short mono hook no longer fits this hero.

   Renamed from .hero--lede on 2026-08-20, when /product/ adopted the same
   composition. The two names were doing two jobs under one selector:

     .hero--lede   = "this is Home" — a page-identity hook. Twenty-odd rules
                     downstream read it through main:has(.hero--lede) and
                     body:has(main > .hero--lede) to reach chapter chrome, the
                     .path grid, the terminal beam, the 16px base scale. None
                     of that belongs to /product/.
     .hero--ladder = "this hero uses the tuned composition" — the eight rules
                     below, and nothing else.

   Adding .hero--lede to /product/'s section would have dragged the whole first
   group along with the second. Splitting the name is what makes the composition
   portable. Specificity and source order are unchanged, so Home computes
   identically — verified property-by-property at 1512/1280/900/600 before and
   after the rename.

   h1: 5rem gives the wide-desktop headline its owner-selected 80px ceiling.
   The 4.8vw middle term keeps the existing responsive scale below 1667px, and
   the 2.3rem floor preserves the mobile treatment. The wider copy measure fits
   the authored two-line break at the 80px ceiling without affecting the lede.

   .hero__hook: opts OUT of --ff-display on this route only. The face split is
   "above ~1rem the type is speaking, below it the type is labelling" — a
   four-noun scope list is read, not declared, and a second large mono block
   sitting directly under a mono h1 reads as a wall rather than a voice. Set in
   the body face the same sentence takes two lines instead of three (59px less),
   which is what puts the terminal mockup back inside the first viewport on a
   1366x660 laptop.

   Scoped to a modifier rather than to .hero--home because all ten routes carry
   .hero--home and .hero__copy--center; Home, /product/ and
   /industries/solo-traders/ carry .hero--ladder, and the other seven still run
   two subheads and keep their own composition until they opt in.

   solo-traders opted in on 2026-08-21 and arrived with /product/'s exact defect,
   measured: gaps of 28.6 / 22.4 / 24 / 36 / 25.6, four of them inside a 13.6px
   band, and the same unreset 11px eyebrow margin putting the label 28.6px from
   its own headline while the lede sat 22.4px away. After: 17.3 / 24 / 32 / 24 at
   every width from 2560 to 390, and the hero 122px shorter. Three routes now,
   one defect, one rule — which is the argument for having split the name. */
.hero--ladder .hero__copy--center { max-width: 76rem; }
.hero--ladder .hero__copy--center h1 { font-size: clamp(2.3rem, 4.8vw, 5rem); }
/* A carve-out for `.hero--sub` was written here on 2026-08-21 and REMOVED the
   same hour, because the premise was false and measuring it said so.

   The premise was that `.hero--sub h1 { clamp(2.1rem, 4.6vw, 3.6rem) }` up at
   the top of this file is a deliberate "quieter sub-page headline" that the
   ladder would override, so a route taking both should keep the smaller one.

   It is not, because it does not render. `.hero--sub h1` is (0,1,1); so is
   `.hero__copy--center h1 { clamp(2.6rem, 6vw, 5.4rem) }` several hundred lines
   below it. Same specificity, later wins. Measured on both routes that carry
   `.hero--sub`, before any change today: `/industries/vps-providers/` and
   `/proof/ea-vs-manual-volume/` both render **86.4px** at 1512 and 2560 — the
   plain stacked-hero size, not 57.6px. The rule has been dead for as long as the
   two have coexisted.

   So there was nothing to hand back. The carve-out would have *imposed* a 57.6px
   headline that the page has never had, a 28.8px cut, on the strength of a rule
   nobody had checked. vps-providers now takes the ladder's h1 like the other
   five: 86.4 → 72.576 at 1512, 80px ceiling at 2560.

   The dead rule itself is left alone — deleting it is a separate decision about
   what `.hero--sub` is supposed to mean, and it touches `/proof/` too. */
/* 12px, not the shared 11px: at .12em tracking an 11px mono label sits right at
   the edge of comfortable reading, and this one is the first line of the page.
   Scoped, because .eyebrow carries 27 labels across the ten routes and most of
   them are section chrome deep in a page, not a page-opening line. */
.hero--ladder .eyebrow { font-size: .75rem; }
/* 16px lede. The measure has to come down with it: 46rem at 20px ran 81.2
   characters a line, and the same width at 16px would run 103. 38rem brings it
   to 85.2.

   CORRECTED 2026-08-21. This comment used to read "~74 ... ~92 ... ~75", and
   claimed 38rem "holds it at ~75" — inside the 50-75 band. Every one of those
   came from counting getClientRects().length as lines; that count is line
   *fragments*, so any inline element inflates it and the divisor was too big.
   Re-measured as sum(rect.width)/text.length -> advance, contentBox/advance ->
   ch. 38rem does not hold the lede in the band and never did: it takes 103 down
   to 85.2, which is better than the alternative and still ten characters over.
   Live on Home (85.2), /product/ and /industries/solo-traders/ (83.8 — the same
   608px measure, one glyph wider on average). Left as-is rather than narrowed,
   because a hero lede centred under a display headline is scanned, not read in
   a column, and 30rem would put it visibly narrower than the CTA row beneath
   it. Recorded so the next reader argues with the real number. */
/* text-wrap: pretty, not balance. Home's lede is two lines and does not need
   either; /product/'s is four and was ending on "outcome." alone — 67px against
   a 608px measure, 11%. balance would even all four lines out and cost the
   first three their full measure for a widow only the last line has; pretty
   only refuses to leave the last line short. Home measures identically with it
   on (2 lines, 562/331), so this is a no-op there and a fix here. Unsupported
   engines ignore the declaration and get today's wrap. */
.hero--ladder .hero__copy--center .hero__hook {
  font-family: var(--ff-body); font-weight: 400;
  font-size: 1rem; line-height: 1.65; letter-spacing: normal;
  color: var(--fg-muted); max-width: 38rem; margin-inline: auto;
  text-wrap: pretty;
}

/* ---- spacing: three groups, not five evenly spaced items ----
   The gaps used to run 29 / 23 / 36 / 25 / 72px — four of them inside a 13px
   band, so nothing marked where the message ended and the action began, and the
   eyebrow (a label belonging to the h1) sat further from it than the lede did.
   These five put every gap on an 8px step and split them in two tiers: 16-24
   inside a group, 48-72 between groups.

   The eyebrow's zero is the load-bearing one. It is a <p> that never reset the
   UA's margin-block: 1em, and .hero__copy--center makes it inline-block, so that
   margin cannot collapse into the h1's — it stacked, and the gap silently
   tracked the eyebrow's font-size. Bumping the label 11px -> 12px widened the
   layout by a pixel nobody declared. Zeroing it hands the spacing back to the
   rules below.

   Net change to the cluster's height: -1px. This is a redistribution, not a
   trim, so the fold measurements are unaffected.

   /product/ came in on 2026-08-20 running 28.6 / 22.4 / 24 / 36 / 25.6 — the
   same five-gaps-in-a-13px-band shape, and the same stacked eyebrow margin,
   only worse: its label is 11px and unreset, so the 11px phantom put the
   eyebrow 28.6px from its own headline while the lede sat 22.4px away. The
   label was further from the thing it labels than the lede was. */
.hero--ladder .eyebrow                          { margin-block: 0; }
.hero--ladder .hero__copy--center h1            { margin-top: 1rem; }    /* 16 — label to headline */
.hero--ladder .hero__copy--center .hero__hook   { margin-top: 1.5rem; }  /* 24 — inside the message */
.hero--ladder .hero__copy--center .hero__cta    { margin-top: 2rem; }    /* 32 — message to action */
.hero--ladder .hero__copy--center .hero__badges { margin-top: 1.5rem; }  /* 24 — inside the action */

/* 60-120 — action to product, the largest break in the hero (2026-08-26).
   The ladder above climbs 16 / 24 / 32 / 24 and then handed the biggest
   semantic jump — copy block to product shot — to `.device`'s own base margin,
   clamp(2.5rem, 5vw, 4.5rem). Two things were wrong with that. It capped at
   72px, so every screen at or above 1440 got the same gap no matter how much
   room it had, and 72px sits under the 86px h1 it has to balance. Owner read
   the result as cramped.

   Scoped to the stage rather than raised on `.device`, because `.device` also
   carries three in-body mockups on /product/ where 72px is an ordinary block
   gap, not a hero break — one of them already fights the base with an inline
   margin-top. `.lit-frame` rides along: it is /trust/'s stage frame and
   declares the identical margin, so widening one without the other would drift
   the two apart. */
.hero__stage > :is(.device, .lit-frame) { margin-top: clamp(3.75rem, 7.5vw, 7.5rem); }

.hero__stage { position: relative; z-index: 2; max-width: 1600px; margin-inline: auto; padding-inline: var(--gutter); }

/* browser-window mockup frame around the terminal screenshot */
.device { position: relative; margin: clamp(2.5rem, 5vw, 4.5rem) 0 -1px; }
.browser { position: relative; z-index: 1; border-radius: 10px 10px 0 0; overflow: hidden; text-align: left;
  transform-origin: 50% 100%;
  background: color-mix(in srgb, var(--surface) 96%, black);
  border: 1px solid color-mix(in srgb, var(--fg) 9%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, white 10%, transparent), 0 60px 120px -50px rgba(0,0,0,.92), 0 0 130px -44px color-mix(in srgb, var(--accent) 20%, transparent); }
.browser__bar { display: flex; align-items: center; gap: .7rem; padding: .6rem .9rem; border-bottom: 1px solid var(--border); background: var(--surface-2); }
.browser__dots { display: inline-flex; gap: .5rem; flex: 0 0 auto; }
.browser__dots i { width: 11px; height: 11px; border-radius: 9999px; background: var(--border); }
.browser__dots i:nth-child(1) { background: #FF5F57; }
.browser__dots i:nth-child(2) { background: #FEBC2E; }
.browser__dots i:nth-child(3) { background: #28C840; }
.browser__url { margin-inline: auto; display: inline-flex; align-items: center; justify-content: center; gap: .45rem; min-width: 220px; max-width: 420px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: .34rem .8rem; font-family: var(--ff-mono); font-size: .75rem; color: var(--fg-muted); }
.browser__url svg { width: 13px; height: 13px; color: var(--accent); }
.browser__spacer { flex: 0 0 auto; width: 53px; }
/* code panel body (API / Create EA hero visuals) — emerald-only, no second hue */
.codebody { padding: clamp(1rem, 3vw, 1.9rem) clamp(1.1rem, 3vw, 2.1rem); font-family: var(--ff-mono); font-size: clamp(.75rem, 1.1vw, .875rem); line-height: 1.85; color: var(--fg-muted); background: color-mix(in srgb, var(--bg) 55%, transparent); overflow-x: auto; }
.codebody .ln { display: block; white-space: pre; }
.codebody .k { color: var(--accent); }
.codebody .v { color: var(--fg); }
.codebody .c { color: var(--fg-faint); }
.codebody .mut { color: var(--fg-faint); }
.device__cap { text-align: center; margin-top: 1.1rem; font-family: var(--ff-mono); font-size: .74rem; color: var(--fg-faint); letter-spacing: .02em; }
.device__cap .lk { display: inline-flex; align-items: center; gap: .35rem; }
.device__cap .lk svg { width: 12px; height: 12px; color: var(--accent); }
.device__cap .sep { opacity: .5; margin-inline: .5rem; }
@media (max-width: 620px) { .device__cap { font-size: .68rem; } .browser__url { min-width: 0; font-size: .625rem; } }
.terminal { position: relative; margin-top: clamp(2.5rem, 5vw, 4.5rem); }
.terminal__scroll { overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; }
.terminal__shot { display: block; width: 100%; height: auto; }
@media (max-width: 760px) {
  .terminal__shot { width: auto; height: 420px; max-width: none; }
  .terminal__scroll::-webkit-scrollbar { height: 4px; }
  .terminal__scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 9999px; }
}
.terminal__card { margin: 0; position: relative; z-index: 1; border-radius: var(--r-lg); overflow: hidden; text-align: left;
  background: color-mix(in srgb, var(--surface) 72%, transparent);
  -webkit-backdrop-filter: blur(16px) saturate(135%); backdrop-filter: blur(16px) saturate(135%);
  border: 1px solid color-mix(in srgb, var(--fg) 9%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, white 10%, transparent), 0 60px 120px -50px rgba(0,0,0,.92), 0 0 130px -44px color-mix(in srgb, var(--accent) 20%, transparent); }
.terminal__top { display: flex; align-items: center; gap: .7rem; padding: .65rem 1rem; border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--surface-2) 55%, transparent); }
.terminal__sym { font-family: var(--ff-mono); font-weight: 600; font-size: .875rem; color: var(--fg); display: inline-flex; align-items: center; gap: .5rem; }
.terminal__sym .g { width: 18px; height: 18px; border-radius: 5px; background: var(--accent); color: var(--accent-ink); display: inline-grid; place-items: center; font-size: .62rem; font-weight: 700; }
.terminal__sym .mtf { color: var(--fg-faint); font-weight: 500; }
.tf { display: inline-flex; gap: .15rem; margin-left: .3rem; }
.tf b { font-family: var(--ff-mono); font-size: .64rem; font-weight: 500; color: var(--fg-faint); padding: .22rem .45rem; border-radius: 3px; }
.tf b.on { color: var(--accent-ink); background: var(--accent); }
.terminal__top .live { margin-left: auto; }
.terminal__body { display: grid; grid-template-columns: 1fr 258px; }
.terminal__chart { position: relative; border-right: 1px solid var(--border); padding: .35rem .5rem .1rem; min-width: 0; }
.terminal__chart svg { width: 100%; height: clamp(240px, 32vw, 372px); display: block; }
.terminal__wm { position: absolute; left: 46%; top: 44%; transform: translate(-50%, -50%); font-family: var(--ff-mono); font-weight: 600; font-size: clamp(2rem, 5vw, 3.4rem); color: color-mix(in srgb, var(--fg) 5%, transparent); letter-spacing: .12em; pointer-events: none; }
.terminal__side { padding: .85rem; display: flex; flex-direction: column; gap: .8rem; }
.tside__hd { font-family: var(--ff-mono); font-size: .58rem; text-transform: uppercase; letter-spacing: .08em; color: var(--fg-faint); }
.tstrat { display: grid; gap: .45rem; margin-top: .1rem; }
.tstrat__row { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: .55rem; padding: .5rem .6rem; border: 1px solid var(--border); border-radius: var(--r); background: color-mix(in srgb, var(--surface) 55%, transparent); }
.tstrat__row .sd { width: 7px; height: 7px; border-radius: 9999px; background: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent); }
.tstrat__row.paused .sd { background: var(--fg-faint); box-shadow: none; }
.tstrat__row b { font-family: var(--ff-mono); font-size: .72rem; font-weight: 500; color: var(--fg); }
.tstrat__row small { display: block; font-family: var(--ff-mono); font-size: .58rem; color: var(--fg-faint); }
.tstrat__row .st { font-family: var(--ff-mono); font-size: .55rem; letter-spacing: .05em; color: var(--accent); text-transform: uppercase; }
.tstrat__row.paused .st { color: var(--fg-faint); }
.ticket { margin-top: auto; border: 1px solid var(--border); border-radius: var(--r); padding: .7rem; background: color-mix(in srgb, var(--surface-2) 45%, transparent); }
.ticket__bs { display: grid; grid-template-columns: 1fr 1fr; gap: .4rem; }
.ticket__bs .b { text-align: center; padding: .45rem; border-radius: 3px; font-family: var(--ff-mono); font-size: .58rem; text-transform: uppercase; letter-spacing: .05em; }
.ticket__bs .b b { display: block; font-size: .84rem; letter-spacing: 0; margin-top: .12rem; }
.ticket__bs .sell { background: color-mix(in srgb, var(--neg) 15%, transparent); color: #ff6b63; border: 1px solid color-mix(in srgb, var(--neg) 32%, transparent); }
.ticket__bs .buy { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); border: 1px solid var(--accent-line); }
.ticket__vol { display: flex; align-items: center; justify-content: space-between; margin-top: .55rem; font-family: var(--ff-mono); font-size: .64rem; color: var(--fg-muted); }
.ticket__vol .pill { border: 1px solid var(--border); border-radius: 3px; padding: .15rem .5rem; color: var(--fg); }
.terminal__foot { display: flex; align-items: center; gap: .6rem; padding: .7rem 1rem; border-top: 1px solid var(--border); background: color-mix(in srgb, var(--surface-2) 45%, transparent); flex-wrap: wrap; }
.terminal__foot .lk { display: inline-flex; align-items: center; gap: .4rem; font-family: var(--ff-mono); font-size: .66rem; color: var(--fg-muted); }
.terminal__foot .lk svg { width: 13px; height: 13px; color: var(--accent); }
.terminal__foot .sep { color: var(--fg-faint); opacity: .5; }
.terminal__foot .up { margin-left: auto; font-family: var(--ff-mono); font-size: .66rem; color: var(--fg-faint); }
.terminal__foot .up b { color: var(--fg); }
@media (max-width: 820px) {
  .terminal__body { grid-template-columns: 1fr; }
  .terminal__chart { border-right: 0; border-bottom: 1px solid var(--border); }
  .terminal__side { flex-direction: row; flex-wrap: wrap; align-items: stretch; }
  .terminal__side .tside__hd { width: 100%; }
  .tstrat { flex: 1; min-width: 200px; }
  .ticket { margin-top: 0; flex: 1; min-width: 200px; }
}
@media (max-width: 520px) { .terminal__side { flex-direction: column; } .terminal__wm { display: none; } }

/* ---------- activity ticker ---------- */
.ticker { border-block: 1px solid var(--border); background: color-mix(in srgb, var(--surface) 30%, var(--bg)); overflow: hidden; }
.ticker__in { display: flex; gap: 3rem; padding: .7rem 0; white-space: nowrap; width: max-content; animation: marquee 34s linear infinite; }
.ticker span { font-family: var(--ff-mono); font-size: .74rem; color: var(--fg-muted); letter-spacing: .02em; display: inline-flex; align-items: center; gap: .5rem; }
.ticker span b { color: var(--fg); }
.ticker span .d { width: 5px; height: 5px; border-radius: 9999px; background: var(--accent); }
@keyframes marquee { to { transform: translateX(-50%); } }
.ticker:hover .ticker__in { animation-play-state: paused; }

/* ---------- works-with band ---------- */
/* Spacing taken from .stats, whole. The two bands already share a grid; sharing
   the rhythm is what makes them read as one chapter instead of two strips that
   happen to look alike. Every number here is .stats' own: padding-block from
   .stats, the heading's bottom margin from .stats__hd, .works__name's top margin
   standing in for .stat__l's. The section was on the generic 120px chapter
   padding, nearly double .stats' 64px, which is why it sat 60px taller with less
   in it.

   Hairline on the bottom only. .stats carries border-block and closes itself top
   and bottom; here the top edge is already drawn by .stats' own bottom border --
   the two sections are flush -- so a second rule there would stack two hairlines
   into a 2px seam, the same doubling the ticker seam had. The background is left
   transparent: .stats lifts its surface because it is a band, this one is not. */
.works { text-align: center; padding-block: clamp(40px, 5vw, 64px); border-bottom: 1px solid var(--border); }
/* 120px from the compatibility hairline to the [01] badge (owner's call,
   2026-08-20). It was 24px until the 19th, then 80px, now 120.

   24px came from `--gap`, the token .sec-bar uses for its own margins, and it
   was the wrong measure for this seam: --gap spaces a chapter bar from its own
   block, while this is the boundary between the hero's closing statement and
   the start of the numbered document. Set on .chapter rather than on --gap so
   the other nine routes keep their bar rhythm untouched.

   120px is the section padding every chapter opens and closes on, so the seam
   is now the same interval the document already uses at full size rather than
   a number of its own. It parted company with .bm-models (still 5rem, still
   separating the two explorers inside [02]) -- that one spaces two things
   inside a chapter, this one separates the front matter from the document.

   Margin collapsing does the arithmetic. .chapter carries no border or padding,
   so .sec-bar's top margin collapses into this one and the pair resolves to
   max(120, 0) = 120 -- they do not add, and since the 20th the bar's own top
   margin is 0 anyway (see the rule in the Home block). Change this number and
   the gap follows it exactly; there is no second value hiding underneath.
   .works is Home-only, so this needs no :has() scope. */
.works + .chapter { margin-top: 7.5rem; }
/* Same size, weight and colour as .stats__hd. The two bands sit 4px apart in
   height and now share a grid, so a 10.9px --fg-faint line over one and a 16px
   --fg line over the other made the pair read as a heading followed by a
   caption. Matching them makes it two chapters of equal rank, which is what
   they are. */
.works__label { margin: 0 0 clamp(1.5rem, 3vw, 2.25rem); font-family: var(--ff-mono); font-size: 1rem; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; color: var(--fg); }
.works__row { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 1.25rem 2.5rem; margin-top: 1.5rem; }
.works__row .mark { font-family: var(--ff-mono); font-weight: 600; font-size: 1rem; letter-spacing: -.01em; color: var(--fg-muted); display: inline-flex; align-items: center; gap: .5rem; opacity: .85; transition: opacity .15s, color .15s; }
.works__row .mark:hover { opacity: 1; color: var(--fg); }
.works__row .mark svg { width: 18px; height: 18px; color: var(--fg-faint); }

/* ---------- rails: icon tile over label (2026-08-19) ----------
   .works__row was an inline mark list -- icon and text on one line, small. That
   is the same form the hero chips use, and form is why the two clusters read as
   a repeat even where the wording differs: change the words and the eye still
   says "seen it". A vertical tile stops looking like a chip at all, so the two
   can share a term without reading as duplication.

   The grid is .stats__grid's, deliberately: 4 columns, 1.5rem gap, same 760px
   breakpoint down to 2. .stats sits directly above at 325px and this band at
   321px, so giving them one internal structure turns two adjacent strips into a
   pair rather than two unrelated bands. Four columns is also why Fleet Control
   left the row -- it is a Senti product, not a rail anyone already trusts, and
   five items against the four above would read as an accident.

   Tile is 56px: above .footer__social's 34px and .btn's 44px, because this one
   is not a control and does not compete with them for the eye, it just needs
   enough face to hold a 24px glyph with air. --r keeps the 2px corner the rest
   of the system uses.

   Hover moves the plate, not the strokes -- the same rule .chip settled on. Icon
   and label hold their value; only the surface steps --surface -> --surface-2.
   Behind @media (hover: hover) so a tap cannot leave a tile stuck lit. */
/* Narrower than .wrap, unlike .stats__grid. That grid can span the full width
   because a 52px figure fills its column; a 56px tile in a 300px column does
   not, and four of them at that spacing read as four isolated objects rather
   than one set -- proximity decides grouping, and 244px of air between items
   beats the 24px gap that is supposed to bind them. 56rem puts the columns at
   200px against a 185px longest label ("MT5-compatible brokers" at 14px mono) --
   the 32px gap takes 8px off each column, so the clearance is 15px, not 21px.
   52rem was the first try and left 5px of clearance -- enough on JetBrains Mono,
   not enough for the ui-monospace fallback if the webfont fails, and a nowrap
   label that overflows its column is a worse failure than a wide grid. */
.works__grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 2rem; margin-top: 0; max-width: 56rem; margin-inline: auto; }
/* Named works__*, not rail*. .rail is already taken further down this file by a
   horizontal carousel track -- `display:flex; overflow-x:auto` plus a full-gutter
   padding-inline -- and being the later rule at equal specificity it won every
   one of these tiles. That is where the horizontal scrollbar came from, and the
   padding is what pushed the labels under the page edge; the premature 2-line
   wrap was the same rule's padding eating the column, not the text. */
.works__item { display: flex; flex-direction: column; align-items: center; text-align: center; min-width: 0; }
.works__icon {
  display: grid; place-items: center; width: 56px; height: 56px;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--surface); transition: background .14s;
}
/* 1.5 on a 24 viewBox drawn at 24px is a true 1.5px stroke. Set here, not on the
   SVG, for the same reason .chip does it: a presentation attribute loses to this
   rule, so the four tiles cannot drift apart when one glyph is swapped. */
.works__icon svg { width: 24px; height: 24px; stroke-width: 1.5; color: var(--fg-muted); }
/* Not uppercase, unlike .stat__l which it otherwise echoes. These are product
   names -- MetaTrader 5 is a MetaQuotes trademark with its own casing, and
   flattening it to METATRADER 5 to win a typographic rhyme gets the rhyme and
   loses the name. */
.works__name { margin-top: .75rem; font-family: var(--ff-mono); font-size: .875rem; font-weight: 500; color: var(--fg); }
@media (hover: hover) {
  .works__item:hover .works__icon { background: var(--surface-2); }
}

/* ---------- line icons ----------
   Scoped to .bento__tile on 2026-08-20. This was a bare `.ico` and it collided
   with the inline arrow declared up at the top of the file: same class name,
   two unrelated components, and this one is 760 lines later, so it won. Every
   `<svg class="ico">` on the page -- the two mega-menu CTAs and, once Home's
   cards came back, nine "Explore ..." links -- was rendering as a 40px bordered
   box instead of a 13px arrow.

   .bento__tile is the only thing that ever wanted the boxed form, and it
   appears in no HTML file today, so the scope costs nothing and the collision
   is gone whether or not the bento ever returns. */
.bento__tile .ico { display: inline-grid; place-items: center; width: 40px; height: 40px; border: 1px solid var(--accent-line); border-radius: var(--r); color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, transparent); }
.bento__tile .ico svg { width: 20px; height: 20px; }

/* ---------- bento grid ---------- */
.bento { display: grid; grid-template-columns: repeat(6, 1fr); gap: 1.1rem; }
.bento__tile { position: relative; display: flex; flex-direction: column; padding: 1.6rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; transition: border-color .16s, transform .16s; }
a.bento__tile:hover { border-color: var(--accent-line); transform: translateY(-3px); }
.bento__tile.col3 { grid-column: span 3; }
.bento__tile.col2 { grid-column: span 2; }
.bento__tile.feature { grid-column: span 3; grid-row: span 2; background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 7%, var(--surface)), var(--surface)); }
.bento__tile .ico { margin-bottom: 1rem; }
/* per-solution atmospheric art band (BitGo-style card image, emerald-only) */
.bento__art { position: relative; margin: -1.6rem -1.6rem 1.2rem; aspect-ratio: 2.3 / 1; overflow: hidden; border-bottom: 1px solid var(--border); background: #090C10; }
.bento__art svg { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.bento__art .al { position: absolute; left: .9rem; bottom: .7rem; font-family: var(--ff-mono); font-size: .6rem; font-weight: 500; letter-spacing: .1em; text-transform: uppercase; color: var(--fg-faint); }
.bento__tile.feature .bento__art { aspect-ratio: auto; flex: 1 1 auto; min-height: 280px; }
a.bento__tile:hover .bento__art svg { transform: scale(1.03); }
.bento__art svg { transition: transform .35s ease-out; transform-origin: 50% 60%; }
.bento__t { font-family: var(--ff-mono); font-weight: 600; font-size: 1.08rem; line-height: 1.25; letter-spacing: -.01em; }
.bento__tile.feature .bento__t { font-size: 1.5rem; margin-top: .3rem; }
.bento__who { color: var(--fg); font-size: .875rem; margin: .8rem 0 0; }
.bento__who b { font-weight: 600; }
.bento__d { color: var(--fg-muted); font-size: .875rem; margin: .5rem 0 0; }
/* Matched to .cx__go, the "Explore" the [01] explorer uses (2026-08-19). Three
   things were wrong here, not one:

   1. Mono at .78rem (12.48px) against Geist at 14px/500 in [01] -- the same
      action wearing two faces two screens apart.
   2. The arrow carried class="ico". This file defines .ico three times at equal
      specificity: an inline 1.1em icon near the top, then a 40px bordered badge
      further down that wins on order. So the arrow was rendering as a 40px box
      with a border round it, on all six cards.
   3. The hover rules pointed at `.arr` and at `a.bento__tile`. The markup has no
      .arr any more and .bento__tile appears in no HTML file at all, so both were
      dead and the rail cards had no arrow nudge whatsoever.

   The card is the link, so hover is read off a.rcard, not off the span -- and
   the underline goes on a span around the label so it cannot strike through the
   arrow beside it. */
.bento__link { margin-top: auto; padding-top: 1.15rem; color: var(--accent);
  font-family: var(--ff-body); font-size: .875rem; font-weight: 500;
  display: inline-flex; align-items: center; gap: .45rem; }
.bento__link svg { width: 1.15em; height: 1.15em; flex: none; transition: transform .15s; }
a.rcard:hover .bento__link svg { transform: translateX(3px); }
a.rcard:hover .bento__link span { text-decoration: underline;
  text-decoration-thickness: 1px; text-underline-offset: .22em; }
.bento__viz { margin-top: 1.25rem; border: 1px solid var(--border); border-radius: var(--r); background: var(--bg); padding: .9rem; }


/* ---------- home: stats heading + product feature cards ---------- */
/* All caps at 16px, so the tracking has to invert. -.01em was right for a 21px
   heading -- optical sizing pulls large type tighter -- but caps have no
   ascender/descender variation to separate them and negative tracking at this
   size closes the counters. .08em is what every other uppercase mono label in
   this file uses (.works__label, .card__tag, .stat__l all sit at .07-.08em), so
   this stops being a heading that happens to be small and joins that set.

   The clamp goes with it: it existed to scale a display size across viewports,
   and at this size there is nothing left to scale -- 16px caps read the same at
   1024 as at 1920.
   Weight stays 600 -- this still labels the band, it is not a field caption. */
.stats__hd { text-align: center; font-family: var(--ff-mono); font-weight: 600; font-size: 1rem; letter-spacing: .08em; text-transform: uppercase; margin: 0 0 clamp(1.5rem, 3vw, 2.25rem); }
.feat-grid .card { display: flex; flex-direction: column; }
a.card { transition: border-color .16s, transform .16s; }
a.card:hover { border-color: var(--accent-line); transform: translateY(-3px); }
.card__tag { font-family: var(--ff-mono); font-size: .625rem; letter-spacing: .08em; text-transform: uppercase; color: var(--fg-faint); margin-bottom: .6rem; }
.card__pain { color: var(--fg-faint); font-size: .9rem; margin: .6rem 0 0; padding-left: .9rem; border-left: 2px solid var(--border); }
.card__link { margin-top: auto; padding-top: 1.1rem; color: var(--accent); font-family: var(--ff-mono); font-size: .75rem; display: inline-flex; align-items: center; gap: .4rem; }
a.card:hover .card__link .arr { transform: translateX(3px); }
.card__link .arr { transition: transform .15s; }
.card--cta { background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 6%, var(--surface)), var(--surface)); }

/* ---------- solutions rail (Phantom-style full-bleed carousel) ---------- */
.rail-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 1.5rem; margin-bottom: clamp(1.75rem, 3.5vw, 2.75rem); }
.rail-head .sh { margin-top: .8rem; }
.rail-nav { display: flex; gap: .6rem; flex: none; padding-bottom: .3rem; }
.rail-btn { width: 42px; height: 42px; display: grid; place-items: center; border: 1px solid var(--border); border-radius: 9999px; background: var(--surface); color: var(--fg-muted); cursor: pointer; transition: color .12s, border-color .12s, background .12s; }
.rail-btn:hover { color: var(--fg); border-color: var(--fg-faint); background: var(--surface-2); }
.rail-btn:disabled { opacity: .35; cursor: default; }
.rail-btn svg { width: 18px; height: 18px; }
/* centered rail: the 5-card strip (5 x 400px + 4 gaps = 2080px) centers when it
   fits; on narrower screens it becomes an edge-to-edge snap carousel. */
.rail-zone { position: relative; }
.rail { display: flex; gap: 1.25rem; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; -ms-overflow-style: none; padding-block: 6px 10px; padding-inline: max(var(--gutter),
    calc((100% - min(100% - 2 * var(--page-gutter), var(--block-max))) / 2 + var(--block-pad))); }
/* That second term is the chapter's own content edge, restated. The rail is the
   one block on the page that did not land on it: it bled to the viewport, which
   is correct and deliberate -- a carousel has to run off the edge to read as
   scrollable -- but it padded itself with --gutter (32px at 1440) while every
   chapter puts its content at --page-gutter + --block-pad (46 + 40 = 86px). So
   the first card sat 54px left of the heading above it, and .chapter--full's own
   comment says the opposite is the intent: "its chrome bar and its heading still
   take the default measure so they land on the same content edge as every other
   chapter." Now the first card lands there too. The right side still bleeds --
   padding-inline is symmetric, but the track scrolls past it.
   The old `calc((100% - 2080px) / 2)` term is gone rather than kept: once
   --block-max caps at --measure the new term resolves to 100%/2 - 640 against
   the old one's 100%/2 - 1040, so it was strictly larger at every width the old
   term could have won. */
.rail-btn--edge { position: absolute; top: 50%; transform: translateY(-50%); z-index: 5; background: color-mix(in srgb, var(--surface) 86%, transparent); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); box-shadow: 0 10px 30px -12px rgba(0,0,0,.8); }
.rail-btn--l { left: 14px; }
.rail-btn--r { right: 14px; }
.rail-zone:not(.is-scrollable) .rail-btn--edge { display: none; }
@media (max-width: 760px) { .rail-btn--edge { display: none; } }
.rail::-webkit-scrollbar { display: none; }
.rcard { position: relative; flex: 0 0 clamp(290px, 30vw, 400px); display: flex; flex-direction: column; padding: 1.5rem 1.5rem 0; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; scroll-snap-align: start; transition: border-color .16s, transform .16s; }
a.rcard:hover { border-color: var(--accent-line); transform: translateY(-3px); }
.rcard .bento__link { margin-top: 1rem; padding-top: 0; margin-bottom: 1.3rem; }
.rcard__art { position: relative; order: 10; margin-top: auto; margin-inline: -1.5rem; border-top: 1px solid var(--border); aspect-ratio: 2.05 / 1; overflow: hidden; background: #090C10; }
.rcard__art svg, .rcard__art img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transition: transform .35s ease-out; transform-origin: 50% 60%; }
a.rcard:hover .rcard__art svg, a.rcard:hover .rcard__art img { transform: scale(1.03); }
.rcard__art .al { position: absolute; left: .9rem; bottom: .7rem; font-family: var(--ff-mono); font-size: .6rem; font-weight: 500; letter-spacing: .1em; text-transform: uppercase; color: var(--fg-faint); }
@media (max-width: 640px) { .rcard { flex-basis: min(84vw, 340px); } .rail-head { align-items: center; } }

/* ---------- SVG flow diagram ---------- */
.flow { max-width: 60rem; margin-inline: auto; border: 1px solid var(--border); border-radius: var(--r-lg); background:
    radial-gradient(80% 120% at 50% -10%, color-mix(in srgb, var(--accent) 7%, transparent), transparent 60%), var(--surface);
  padding: clamp(1.5rem, 4vw, 2.75rem); }
.flow__grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.25rem; align-items: stretch; }
.flow__node { position: relative; text-align: center; padding: 1.1rem .75rem; background: var(--bg); border: 1px solid var(--border); border-radius: var(--r); }
.flow__node.key { border-color: var(--accent-line); background: color-mix(in srgb, var(--accent) 8%, var(--bg)); }
.flow__node .ni { display: inline-grid; place-items: center; width: 34px; height: 34px; border-radius: var(--r); border: 1px solid var(--border); color: var(--fg-muted); margin-bottom: .6rem; }
.flow__node .ni svg { width: 18px; height: 18px; }
/* Whole pixels, like the prose band (owner's call, 2026-08-20). The node's two
   lines were sitting on .8rem (12.8px) and .76rem (12.16px) -- 0.64px apart,
   which is not a hierarchy, it is rounding noise. They are 14px and 12px now,
   a 2px step the eye can actually read as label over caption. */
.flow__node b { display: block; font-family: var(--ff-mono); font-size: .875rem; color: var(--fg); }
.flow__node span { display: block; font-size: .75rem; color: var(--fg-muted); margin-top: .25rem; }
/* The connector between two nodes is a drawn chevron, not a typed arrow
   (owner's call, 2026-08-20).

   Measured before the change: the "\2192" glyph set 12.53px wide in a 12px
   gutter, so it touched the border of the node on each side -- the mark was
   physically wider than the channel it lived in. It was also the only piece of
   line-art in this component that was not line-art: every icon inside a node is
   an SVG stroked at 1.8 with round caps, and a text arrow next to them reads as
   a stray character rather than as part of the diagram.

   So: the gutter goes to 1.25rem (20px) and the mark becomes the same chevron
   the icon set uses, painted as a mask so it takes --fg-muted from CSS and
   stays one colour token rather than a second copy of the palette inside a data
   URI. The pseudo-element spans the whole gutter and centres the 16px chevron
   in it, which is why there is no magic offset here any more -- move the gap
   and the mark follows it. */
.flow__node:not(:last-child)::after {
  content: ""; position: absolute; left: 100%; top: 50%; transform: translateY(-50%);
  width: 1.25rem; height: 16px; z-index: 2; background-color: var(--fg-muted);
  --chev: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E");
  -webkit-mask: var(--chev) center / 16px 16px no-repeat;
          mask: var(--chev) center / 16px 16px no-repeat; }
/* Geist at 14px, not mono at 12.16px (owner's call, 2026-08-20). The mono was
   labelling it as furniture -- a caption under a diagram -- but it is the one
   sentence in [05] that states the boundary the whole chapter is about, and it
   is a full sentence, not a label. Same face and same size as .step__d and
   every other body line in the 14px band, so it now reads as prose that
   happens to sit under the diagram. /trust/ runs the same note under the same
   .flow and gets it too, which is right: it is one component saying one thing
   in two places. */
.flow__note { display: inline-flex; align-items: center; gap: .5rem; margin-top: 1.5rem; font-family: var(--ff-body); font-size: .875rem; color: var(--fg-muted); }
.flow__note svg { width: 15px; height: 15px; color: var(--accent); }

/* ---------- section divider glow accent ---------- */
.rule-glow { height: 1px; border: 0; background: linear-gradient(90deg, transparent, var(--accent-line), transparent); max-width: var(--maxw); margin-inline: auto; }

/* ---------- 2026 responsive ---------- */
@media (max-width: 900px) {
  .bento__tile.feature .bento__art { aspect-ratio: 1.9 / 1; flex: none; min-height: 0; }
  .hero__split { grid-template-columns: 1fr; gap: clamp(2rem, 6vw, 3rem); }
  .hero--home { text-align: center; }
  .hero--home .hero__desc { margin-inline: auto; }
  .hero--home .hero__cta, .hero__badges { justify-content: center; }
  .bento__tile, .bento__tile.col3, .bento__tile.col2, .bento__tile.feature { grid-column: 1 / -1; grid-row: auto; }
  .bento { grid-template-columns: 1fr; }
}
@media (min-width: 901px) and (max-width: 1080px) {
  .bento { grid-template-columns: repeat(4, 1fr); }
  .bento__tile.col3, .bento__tile.feature { grid-column: span 2; }
  .bento__tile.col2 { grid-column: span 2; }
}
@media (max-width: 620px) {
  .flow__grid { grid-template-columns: 1fr; gap: 1.5rem; }
  /* One column: the same chevron turned a quarter, centred in the 1.5rem the
     rows now sit apart. It is one mark rotated, not a second glyph to keep in
     sync with the first. */
  .flow__node:not(:last-child)::after {
    left: 50%; top: 100%; width: 16px; height: 16px;
    transform: translate(-50%, calc(-50% + .75rem)) rotate(90deg); }
  .mock__kpis { grid-template-columns: 1fr 1fr; }
  .mock__kpi:last-child { grid-column: 1 / -1; }
}
@media (prefers-reduced-motion: reduce) {
  .live .pulse::after, .ticker__in, .hero__glow, .mock__bloom { animation: none !important; }
}

/* ---------- motion ---------- */
[data-reveal] { opacity: 0; transform: translateY(14px); transition: opacity .5s ease-out, transform .5s ease-out; }
[data-reveal].in { opacity: 1; transform: none; }
.hero [data-stagger] { opacity: 0; transform: translateY(12px); animation: rise .6s ease-out forwards; }
.hero [data-stagger]:nth-child(1) { animation-delay: .04s; }
.hero [data-stagger]:nth-child(2) { animation-delay: .12s; }
.hero [data-stagger]:nth-child(3) { animation-delay: .2s; }
.hero [data-stagger]:nth-child(4) { animation-delay: .28s; }
.hero [data-stagger]:nth-child(5) { animation-delay: .36s; }
/* The sequence above counts children, and `.hero__stage` is a second parent, so
   the counter restarts: `.device` is child 1 of the stage and takes `.04s`. The
   largest object on the page therefore arrives **tied with the eyebrow** and is
   fully in before the headline has started — measured on all five routes that
   carry a stage, Home included. The intended read is label → headline → lede →
   action → chips → product; what ships is product-and-label first.

   `data-stagger="last"` opts an element out of the positional count and puts it
   one 80ms step after the chips, continuing the same ladder rather than starting
   a second one. An attribute value and not a class because `[data-stagger]` is
   already the hook — every existing selector keeps matching, and a route opts in
   by writing the value, the same way `.hero--ladder` is opted into.

   `.hero .hero__stage > .device[data-stagger="last"]` is (0,4,0) against the
   nth-child rule's (0,3,0), so it wins on specificity and does not depend on
   staying below it in the file.

   Applied to `/industries/solo-traders/` only for now; the other four stages
   still read `.04s`. */
/* `.lit-frame` joins on 2026-08-24 for /trust/, whose stage holds a card and not
   a mockup. `:is()` takes the highest specificity among its arguments and both
   are (0,1,0), so the selector is still (0,4,0) and the six .device routes
   compute exactly what they did before. */
.hero .hero__stage > :is(.device, .lit-frame)[data-stagger="last"] { animation-delay: .44s; }
@keyframes rise { to { opacity: 1; transform: none; } }

/* ---------- responsive ---------- */
@media (min-width: 1080px) { .nav__links { display: flex; } .burger { display: none; } .nav__login { display: inline-flex; align-items: center; } }
/* Social waits for 1240: at 1080 the six nav items, Log in and the CTA already
   fill the bar, and three more glyphs push the whole cluster into the links.
   Below this the icons are still reachable in the mobile sheet and the footer,
   so nothing is lost — they are the least urgent thing in the header. */
@media (min-width: 1240px) { .nav .nav__social { display: inline-flex; } }
@media (max-width: 1024px) { .tiers--4 { grid-template-columns: repeat(2, 1fr); max-width: 40rem; margin-inline: auto; } }
@media (max-width: 960px) {
  .sol-grid, .cards { grid-template-columns: repeat(2, 1fr); }
  .tiers:not(.tiers--4) { grid-template-columns: 1fr; max-width: 30rem; margin-inline: auto; }
  .footer__grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 620px) { .tiers--4 { grid-template-columns: 1fr; max-width: 30rem; } }
@media (max-width: 760px) {
  .stats__grid { grid-template-columns: repeat(2, 1fr); gap: 2rem 1rem; }
  .works__grid { grid-template-columns: repeat(2, 1fr); gap: 2rem 1rem; }
  .sol-grid, .cards, .cards.two { grid-template-columns: 1fr; }
  .split { grid-template-columns: 1fr; }
  .split__media--first { order: -1; }
  form.lead { grid-template-columns: 1fr; }
  .cmp__row { grid-template-columns: 1fr; }
  /* stacked cards: the header row has no meaning once columns are gone */
  .opts__row { grid-template-columns: 1fr; }
  .opts__head { display: none; }
  .opts__row + .opts__row { border-top-width: 8px; }
  .opts__row > div + div { border-left: 0; border-top: 1px solid var(--border); }
  .opts__row > div + div::before { content: attr(data-l); display: block; font-family: var(--ff-mono); font-size: .66rem; text-transform: uppercase; letter-spacing: .12em; color: var(--fg-faint); margin-bottom: .35rem; }
  .cmp__row > div:first-child { border-right: 0; border-bottom: 1px solid var(--border); }
  .cmp__head { display: none; }
  .footer__grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 460px) { .footer__grid { grid-template-columns: 1fr; } }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  [data-reveal], .hero [data-stagger] { opacity: 1 !important; transform: none !important; animation: none !important; transition: none !important; }
}

/* neutral-balance overrides: emerald is reserved for the key node + signals */
.flow__node.key .ni { border-color: var(--accent-line); color: var(--accent); }
.bento__tile .ico { border-color: var(--border); color: var(--fg-muted); background: color-mix(in srgb, var(--fg) 3%, transparent); }


/* ===========================================================================
   Numbered-document layer (homepage first)
   Borrowed from Blockmachine's editorial chrome: every chapter carries an
   index label and a counter, headings run large and left-aligned, and feature
   cards are flat panels with isometric line-art bleeding off the right edge.
   Scoped behind .sec-chrome / --doc / .bm-* so the other six routes keep the
   current treatment until this rolls out to them.
   =========================================================================== */

/* ---------- section chrome ---------- */
.sec-chrome { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
  border-top: 1px solid var(--border); padding-top: .8rem; margin-bottom: clamp(1.75rem, 3.5vw, 2.75rem);
  font-family: var(--ff-mono); font-size: .6875rem; font-weight: 500; letter-spacing: .12em;
  text-transform: uppercase; color: var(--fg-muted); }
.sec-chrome__l { color: var(--fg); }
/* The index and the counter are the two purest pieces of furniture on the page:
   they label the document, they never carry product meaning. Sand here warms
   every chapter without touching a single thing the reader can act on. */
.sec-chrome__ix { color: var(--sand); margin-right: .55rem; }
.sec-chrome__r { flex: none; font-variant-numeric: tabular-nums; color: var(--sand-dim); }
@media (max-width: 560px) { .sec-chrome__r { display: none; } }

/* ---------- oversized, left-aligned section head ---------- */
.section-head--doc { max-width: none; margin: 0 0 clamp(2.25rem, 4.5vw, 3.5rem); text-align: left; }
.section-head--doc .sh { margin-top: 0; font-size: clamp(2.25rem, 5vw, 3.75rem); line-height: 1.04;
  letter-spacing: -.03em; max-width: 22ch; }
.section-head--doc .lead { max-width: 44rem; }

/* ===========================================================================
   Capability explorer -- [01]-[05] on Home (2026-08-19)

   Replaces five .bm-card blocks. The cards asked the reader to work through
   ~2000 characters of prose in five 391px columns before choosing one of five
   things; this asks them to read five titles and open the one that is theirs.
   [06]-[08] stay as cards on purpose: they are a deployment model you choose
   between, not a tour you scan, and a chooser wants its options side by side.

   Zero JavaScript. Five hidden radios drive both columns through the general
   sibling combinator, so the whole thing is one CSS file and one HTML block.
   `<details name>` would have read better semantically but its exclusive-
   accordion behaviour is too new -- where it is unsupported every panel opens
   at once and the stage shows five illustrations stacked. A radio group
   degrades to exactly what it is: choose one of five, arrow keys included.

   All content ships in the HTML and is hidden with CSS, never injected. A
   closed item is still in the DOM, so nothing here costs the page its text.
   =========================================================================== */
.cx { position: relative; }
/* Off-screen, not display:none -- a hidden-by-display radio is unfocusable and
   the arrow keys stop working. */
.cx__r { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

/* 38/62, not 50/50. The chapter block is 1536px at 1920; halved, the text
   column sets ~100 characters a line, so a 50/50 split still has to cap the
   prose and then owns 300px of leftover white. 38% lands the cap and the
   column on the same number. */
.cx__split { display: grid; grid-template-columns: minmax(0, 38fr) minmax(0, 62fr);
  gap: clamp(1.5rem, 4.4vw, 4rem); align-items: stretch; }

/* ---------- left: the list ---------- */
/* One step under --border, scoped to this list rather than taken out of the
   token (2026-08-19). Resolves to #1E2228, 1.22:1 on --bg, where --border sits
   at 1.30:1 -- the same size step the token itself just took.

   Scoped on purpose. A chapter rule is the only thing marking where a chapter
   begins, so it has to survive a dim screen; these are list separators inside a
   block that is already bounded, and the open row is marked a second time by
   --accent-line and by the title going to --fg. A separator can afford to be
   quieter than a boundary. Below this the rows stop reading as a list at all
   and become five unrelated lines of text. */
.cx { --cx-rule: color-mix(in srgb, var(--border) 80%, var(--bg)); }
/* The accent light lives on the open row's bottom rule (owner's call,
   2026-08-19). It used to run around the stage frame, which put the only moving
   thing on the page next to the illustration and away from the thing the reader
   had just acted on. On the rule it does a job instead of decorating: it marks
   which of the five rows is open, and it marks it in the one place the eye is
   already resting.

   A travelling background-position, not a transform, so no wrapper and no
   overflow is needed on .cx__item -- clipping the item would cut the focus ring
   and fight the 0fr->1fr height animation. The percentages are exact rather than
   eyeballed: background-position resolves against (container - image), so with
   a 40% wide image, -66.7% parks it fully off the left edge and 166.7% fully off
   the right. Anything less leaves the head or tail visible at the turn.

   The static --accent-line border underneath stays. It is what carries the open
   state when the sweep is off, which is why reduced motion can simply drop the
   sweep instead of freezing it into a bright smear parked mid-row. */
@keyframes cx-sweep {
  from { background-position: -66.7% 0; }
  to   { background-position: 166.7% 0; }
}
.cx__item { position: relative; }
.cx__item::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
  z-index: 1; pointer-events: none; opacity: 0; transition: opacity .2s ease;
  background-image: linear-gradient(90deg,
    transparent 0,
    color-mix(in srgb, var(--accent) 45%, transparent) 38%,
    var(--accent) 50%,
    color-mix(in srgb, var(--accent) 45%, transparent) 62%,
    transparent 100%);
  background-size: 40% 100%; background-repeat: no-repeat;
  animation: cx-sweep 2.8s linear infinite;
}
/* Mirrored block, mirrored travel -- the same reason the two stages were never
   meant to run the same clock. */
.cx--flip .cx__item::after { animation-direction: reverse; }
.cx__list { border-top: 1px solid var(--cx-rule); }
.cx__item { border-bottom: 1px solid var(--cx-rule); transition: border-color .16s; }
/* No +/x marker (owner's call, 2026-08-19). Open state is carried by the title
   going to --fg and the item's rule going to --accent-line, so the row is not
   left silent -- but it does mean the only "you can open this" cue before the
   first hover is the pointer cursor. Worth watching. */
.cx__hd { display: block; padding: 1rem 0; cursor: pointer; }
.cx__t { font-family: var(--ff-mono); font-size: 1rem; font-weight: 500;
  color: var(--fg-muted); transition: color .16s; }
.cx__hd:hover .cx__t { color: var(--fg); }
/* 0fr -> 1fr is the only way to animate a height the content decides. The inner
   div carries overflow:hidden and min-height:0; without both, the row does not
   actually collapse. */
/* visibility, not just height: a zero-height item is still in the tab order, so
   without this the keyboard walks into the "Explore" link of every closed item
   and focus lands on nothing the reader can see. */
.cx__d { display: grid; grid-template-rows: 0fr; opacity: 0; visibility: hidden;
  transition: grid-template-rows .26s ease, opacity .2s ease, visibility .26s; }
.cx__d > div { overflow: hidden; min-height: 0; }
/* No max-width: the cap sat at 44ch (~352px) inside a 567px column, so the text
   stopped 200px short of the rule under it and the row read as unfinished. At
   14px the full column sets about 81 characters a line -- past the 75 a reading
   measure wants, but this is a two-paragraph disclosure, not an article, and the
   site already runs wider than this in .faq__a. */
.cx__d p { margin: 0 0 .8rem; }
/* The sand rule marks the constraint paragraph. Owner's call, restored
   2026-08-19 after the ink-only version was tried and rejected on sight.

   Noted and overruled, kept here so nobody re-litigates it from scratch: the
   comment above .sec-chrome__ix says sand labels the document and "never
   carries product meaning", so a 2px sand rule beside product copy is that
   token doing a second job. It is now the only sand in the body of Home, since
   the [01]-[09] indices came off. That is the cost; the owner decided the
   marker earns it.

   What did NOT come back is the type scale. The two paragraphs used to sit at
   .9rem and .95rem, 0.8px apart -- under the threshold where a reader sees a
   difference, so it read as inconsistency rather than hierarchy. Both sit at
   .95rem now and the rule does the separating on its own. */
.cx__pain { color: var(--fg-muted); font-size: .875rem;
  padding-left: .9rem; border-left: 2px solid var(--sand-line); }
.cx__sol { color: var(--fg-muted); font-size: .875rem; }
/* The link is a sibling of the label, never inside it: a label that wraps an
   anchor gives one region two jobs, which is the exact confusion this layout
   was chosen to remove. The header opens, this navigates. */
/* A text link, not a .btn: a 44px bordered button inside the opened item read as
   a second, louder component in the middle of a quiet list.

   Geist rather than the mono every other "Explore" uses -- owner's call. The
   arrow is the Lucide arrow-right the rest of the page draws from, not a
   `&rarr;` glyph, so it stops changing shape with whichever family renders it.

   Sized and selected as `.cx__go svg`, deliberately NOT with class="ico": this
   file defines .ico three times at the same specificity -- an inline 1.1em icon
   at the top, then a 40px bordered badge further down that wins by order -- so
   an .ico here would render as a 40px box with a border round it. */
.cx__go { display: inline-flex; align-items: center; gap: .45rem; padding-bottom: 1.15rem;
  font-family: var(--ff-body); font-size: .875rem; font-weight: 500; color: var(--accent); }
.cx__go svg { width: 1.15em; height: 1.15em; flex: none; transition: transform .15s; }
.cx__go:hover svg { transform: translateX(3px); }
/* The underline goes on a span around the label, not on .cx__go itself. Two
   reasons: text-decoration set on a flex container is unreliable across engines
   for its flex items, and an underline that ran the full width would strike
   under the arrow as well, which reads as a struck-through icon rather than a
   link. */
.cx__go:hover span { text-decoration: underline; text-decoration-thickness: 1px;
  text-underline-offset: .22em; }

/* ---------- right: the stage ---------- */
/* min-height rather than aspect-ratio: the split stretches, so a ratio would be
   overridden by the row height anyway. The five items run 350-420 characters,
   about 1.4 lines apart, so the block moves ~32px between items and never
   jumps. */
.cx__stage { position: relative; overflow: hidden; border: 1px solid var(--border);
  min-height: clamp(22rem, 33vw, 30rem);
  background: radial-gradient(120% 90% at 72% 12%, var(--mkt-glow), transparent 62%), var(--surface); }
.cx__stage::before { content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image: linear-gradient(var(--mkt-grid) 1px, transparent 1px),
                    linear-gradient(90deg, var(--mkt-grid) 1px, transparent 1px);
  background-size: 40px 40px; }
.cx__panel { position: absolute; inset: 0; display: grid; place-items: center;
  padding: clamp(1.25rem, 3vw, 2.5rem);
  opacity: 0; visibility: hidden; transform: translateY(10px) scale(.985);
  transition: opacity .3s ease, transform .3s ease, visibility .3s; }
/* The isometric art becomes the ground the mock sits on, which is what the five
   files were already drawn for -- they just used to sit beside the text instead
   of behind it. No new asset was made for this. */
.cx__panel::after { content: ""; position: absolute; inset: 6%; z-index: 0;
  background: var(--cx-art) center / contain no-repeat; opacity: .17; pointer-events: none; }
/* Scoped per explorer. Unscoped, .cx__panel--1 matched BOTH blocks, so the
   deployment models were showing the capabilities' art -- Whitelabel drawn as a
   managed runtime, the API as fleet control. The index is a position in a list,
   not an identity; the art has to be keyed to the list as well as the slot. */
.cx:not(.cx--flip) .cx__panel--1 { --cx-art: url(iso/runtime.svg?v=20260825c); }
.cx:not(.cx--flip) .cx__panel--2 { --cx-art: url(iso/fleet-control.svg); }
.cx:not(.cx--flip) .cx__panel--3 { --cx-art: url(iso/accounts.svg?v=20260825c); }
.cx:not(.cx--flip) .cx__panel--4 { --cx-art: url(iso/create-ea.svg?v=20260825d); }
.cx:not(.cx--flip) .cx__panel--5 { --cx-art: url(iso/records.svg?v=20260825c); }
.cx--flip .cx__panel--1 { --cx-art: url(iso/whitelabel.svg?v=20260825d); }
.cx--flip .cx__panel--2 { --cx-art: url(iso/api.svg?v=20260825c); }
.cx--flip .cx__panel--3 { --cx-art: url(iso/senti-inside.svg?v=20260825d); }
.cx--flip .cx__panel--4 { --cx-art: url(iso/monitoring.svg); }
/* .terminal__card and friends were written for a hero mockup that never shipped
   and had been dead CSS since. The mock panels are built from them rather than
   from a new set, so this adds a component without adding a vocabulary. */
.cx__mock { position: relative; z-index: 1; width: 100%; max-width: 27rem; }
.cx__mock .terminal__side { gap: .55rem; }
.cx__mock .codebody { padding: .9rem 1rem; font-size: .74rem; line-height: 1.75; }
.live i { width: 6px; height: 6px; border-radius: 9999px; background: currentColor;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent); }

/* ---------- the wiring ---------- */
#cx1:checked ~ .cx__split .cx__item--1::after,
#cx2:checked ~ .cx__split .cx__item--2::after,
#cx3:checked ~ .cx__split .cx__item--3::after,
#cx4:checked ~ .cx__split .cx__item--4::after,
#cx5:checked ~ .cx__split .cx__item--5::after { opacity: 1; }
#cy1:checked ~ .cx__split .cx__item--1::after,
#cy2:checked ~ .cx__split .cx__item--2::after,
#cy3:checked ~ .cx__split .cx__item--3::after,
#cy4:checked ~ .cx__split .cx__item--4::after { opacity: 1; }
#cx1:checked ~ .cx__split .cx__item--1,
#cx2:checked ~ .cx__split .cx__item--2,
#cx3:checked ~ .cx__split .cx__item--3,
#cx4:checked ~ .cx__split .cx__item--4,
#cx5:checked ~ .cx__split .cx__item--5 { border-bottom-color: var(--accent-line); }
#cx1:checked ~ .cx__split .cx__item--1 .cx__t,
#cx2:checked ~ .cx__split .cx__item--2 .cx__t,
#cx3:checked ~ .cx__split .cx__item--3 .cx__t,
#cx4:checked ~ .cx__split .cx__item--4 .cx__t,
#cx5:checked ~ .cx__split .cx__item--5 .cx__t { color: var(--fg); }
#cx1:checked ~ .cx__split .cx__item--1 .cx__d,
#cx2:checked ~ .cx__split .cx__item--2 .cx__d,
#cx3:checked ~ .cx__split .cx__item--3 .cx__d,
#cx4:checked ~ .cx__split .cx__item--4 .cx__d,
#cx5:checked ~ .cx__split .cx__item--5 .cx__d { grid-template-rows: 1fr; opacity: 1; visibility: visible; }
#cx1:checked ~ .cx__split .cx__panel--1,
#cx2:checked ~ .cx__split .cx__panel--2,
#cx3:checked ~ .cx__split .cx__panel--3,
#cx4:checked ~ .cx__split .cx__panel--4,
#cx5:checked ~ .cx__split .cx__panel--5 { opacity: 1; visibility: visible; transform: none; }
#cy1:checked ~ .cx__split .cx__item--1,
#cy2:checked ~ .cx__split .cx__item--2,
#cy3:checked ~ .cx__split .cx__item--3,
#cy4:checked ~ .cx__split .cx__item--4 { border-bottom-color: var(--accent-line); }
#cy1:checked ~ .cx__split .cx__item--1 .cx__t,
#cy2:checked ~ .cx__split .cx__item--2 .cx__t,
#cy3:checked ~ .cx__split .cx__item--3 .cx__t,
#cy4:checked ~ .cx__split .cx__item--4 .cx__t { color: var(--fg); }
#cy1:checked ~ .cx__split .cx__item--1 .cx__d,
#cy2:checked ~ .cx__split .cx__item--2 .cx__d,
#cy3:checked ~ .cx__split .cx__item--3 .cx__d,
#cy4:checked ~ .cx__split .cx__item--4 .cx__d { grid-template-rows: 1fr; opacity: 1; visibility: visible; }
#cy1:checked ~ .cx__split .cx__panel--1,
#cy2:checked ~ .cx__split .cx__panel--2,
#cy3:checked ~ .cx__split .cx__panel--3,
#cy4:checked ~ .cx__split .cx__panel--4 { opacity: 1; visibility: visible; transform: none; }
#cy1:focus-visible ~ .cx__split .cx__item--1 .cx__hd,
#cy2:focus-visible ~ .cx__split .cx__item--2 .cx__hd,
#cy3:focus-visible ~ .cx__split .cx__item--3 .cx__hd,
#cy4:focus-visible ~ .cx__split .cx__item--4 .cx__hd {
  outline: 2px solid var(--accent); outline-offset: 4px;
}

/* Keyboard: the radio is off-screen, so its focus ring has to be drawn on the
   header the reader can actually see. */
#cx1:focus-visible ~ .cx__split .cx__item--1 .cx__hd,
#cx2:focus-visible ~ .cx__split .cx__item--2 .cx__hd,
#cx3:focus-visible ~ .cx__split .cx__item--3 .cx__hd,
#cx4:focus-visible ~ .cx__split .cx__item--4 .cx__hd,
#cx5:focus-visible ~ .cx__split .cx__item--5 .cx__hd {
  outline: 2px solid var(--accent); outline-offset: 4px;
}

/* ---------- mirrored variant: [06]-[08] deployment models ---------- */
/* Same component, content axis reversed -- stage left, list right.

   Done with grid-column, not with `direction: rtl` or a `row-reverse`: the DOM
   keeps list-then-stage so the reading order, the tab order and the mobile
   stack all stay list-first, which is the order that makes sense in every one
   of them. Only the painting swaps.

   Why mirror at all rather than run a second identical block: two explorers
   stacked the same way read as one long list that happens to restart its
   numbering, and these are not the same kind of thing. [01]-[05] are
   capabilities you scan; [06]-[08] are three deployment models you choose
   between. Flipping the axis is the cheapest way to say "this is a different
   question" without inventing a second component. */
.cx--flip .cx__split { grid-template-columns: minmax(0, 62fr) minmax(0, 38fr); }
.cx--flip .cx__list  { grid-column: 2; grid-row: 1; }
.cx--flip .cx__stage { grid-column: 1; grid-row: 1; }
/* "From product feature to live operation" is the list's fourth row, not a card
   and not a button (owner's call, 2026-08-19). It opens like the other three and
   its link is the same .cx__go, so nothing in the column is a special case.

   It carries no .cx__pain: the other three name a constraint before naming the
   answer, and this one has no constraint to name -- it is the summary of what
   the three lead to. One paragraph, then the link.

   Worth knowing: it now sits inside the same radio group as the three models,
   so a screen reader counts it as "4 of 4" in a group labelled deployment
   models. That is why the group's label is the neutral one -- it is a list of
   four things to read, and only the first three are models. */

/* ---------- narrow ---------- */
/* The stage stays. On a phone the illustration is the whole payoff for tapping;
   drop it and opening an item buys the reader two paragraphs and nothing else.
   It sits under the list rather than inside the open item, because nesting it
   there without duplicating five panels of markup needs JavaScript, and not
   needing JavaScript is the point of this build. */
@media (max-width: 900px) {
  .cx__split, .cx--flip .cx__split { grid-template-columns: 1fr; }
  .cx--flip .cx__list  { grid-column: 1; grid-row: 1; }
  .cx--flip .cx__stage { grid-column: 1; grid-row: 2; }
  .cx__stage { min-height: 0; aspect-ratio: 16 / 10; }
}
@media (prefers-reduced-motion: reduce) {
  .cx__d, .cx__panel, .cx__go svg, .bento__link svg, .ico { transition: none; }
  /* No frozen smear: the open row keeps its --accent-line border and loses the
     travelling light entirely. */
  .cx__item::after { display: none; }
}

/* ---------- flat feature cards with bleeding isometric art ---------- */
.bm-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.25rem; }
/* --bm-pad exists because .bm-card--wide has to do arithmetic with this padding
   to centre its illustration. Same clamp, same computed value, just readable
   from a calc() instead of hardcoded there. */
.bm-card { --bm-pad: clamp(1.6rem, 2.6vw, 2.25rem);
  position: relative; display: flex; min-height: 330px; padding: var(--bm-pad);
  background: var(--surface); border: 1px solid transparent; border-radius: var(--r-lg);
  overflow: hidden; transition: background .16s, border-color .16s, transform .16s; }
a.bm-card:hover { background: var(--surface-2); border-color: var(--accent-line); transform: translateY(-2px); }
.bm-card__body { position: relative; z-index: 1; display: flex; flex-direction: column; flex: 1; max-width: 57%; }
.bm-card__ix { font-family: var(--ff-mono); font-size: .6875rem; letter-spacing: .09em; color: var(--sand); }
/* .625rem = 10px, not .64rem = 10.24px. Same correction .card__tag took in this
   pass; the rule was missed then because no page was rendering .bm-card__tag at
   the time. Home renders it again as of 2026-08-20, so it is back on the ladder. */
.bm-card__tag { font-family: var(--ff-mono); font-size: .625rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--fg-muted); margin-top: .5rem; }
/* The index and the tag are one label, not two rows. .bm-card__body is a column
   flex, so two sibling spans could only ever be two lines -- `[01]` stranded on
   its own above `Managed Runtime` reads as a loose token rather than as that
   card's number. The index now sits inside the tag, which is the same shape
   .sec-chrome__l already uses for chapter marks: sand index, muted label, one
   line. It also inherits the tag's size and tracking, because two mono sizes on
   one line is a seam the reader can see.

   No white-space: nowrap. The gap is a margin, not a space character, so the
   line has no break opportunity between `[04]` and the first word -- the index
   is already welded to the label -- while a long tag ("Strategy Deployment &
   Create EA") keeps its ordinary wrap inside the card's 57% body column. */
.bm-card__tag:first-child { margin-top: 0; }
.bm-card__tag > .bm-card__ix { font-size: inherit; letter-spacing: inherit; margin-right: .5rem; }
/* 2026-08-19 removed the card indices from Home, on the argument that they were
   the same token as .sec-chrome__ix carrying a different meaning 200px away.
   2026-08-20 put them back with the cards, on the owner's call.

   The nesting is what settles the objection the removal was raising. Welded
   into the tag the index reads as part of that card's label -- `[01] MANAGED
   RUNTIME`, one line, one object -- rather than as a second numbering system
   competing with the chapter mark. It also inherits the tag's .625rem instead
   of standing at .6875rem, so it no longer matches .sec-chrome__ix's size. The
   other three routes still run it as a standalone sibling at its own size. */
/* 18 -> 24, both ends whole. Was clamp(1.15rem, 1.7vw, 1.45rem) = 18.4 -> 23.2,
   the last fractional pair left inside [01] once the cards came back. */
.bm-card__t { font-family: var(--ff-mono); font-weight: 600; font-size: clamp(1.125rem, 1.7vw, 1.5rem);
  line-height: 1.2; letter-spacing: -.02em; margin-top: 1.35rem; }
.bm-card__t:first-child { margin-top: 0; }
/* Same pair, same restore as .cx__pain/.cx__sol above -- [06]-[08] sit one
   screen below the explorer and have to carry the identical idea in the
   identical skin. Sizes stay unified at .95rem here too. */
.bm-card__pain { color: var(--fg-muted); font-size: .875rem; margin: .9rem 0 0;
  padding-left: .9rem; border-left: 2px solid var(--sand-line); }
.bm-card__d { color: var(--fg-muted); font-size: .875rem; margin: .8rem 0 0; }
/* The same link [02]'s cards run (.bento__link, one chapter below). It was mono
   .75rem with a 1.1em arrow and no hover state on the label; [02] is the body
   face at 14/500 with a 1.15em arrow and underlines the label when the card is
   hovered. Two card grids a chapter apart cannot answer "what does a link look
   like here" two different ways, and [02]'s answer is the tuned one.

   `> svg` and `> span`, not descendants: the [09] CTA card puts a .btn inside
   this same wrapper, and its arrow is a .ico governed by the button rules. */
.bm-card__link { margin-top: auto; padding-top: 1.15rem; color: var(--accent);
  font-family: var(--ff-body); font-size: .875rem; font-weight: 500;
  display: inline-flex; align-items: center; gap: .45rem; }
.bm-card__link > svg { width: 1.15em; height: 1.15em; flex: none; transition: transform .15s; }
a.bm-card:hover .bm-card__link > svg { transform: translateX(3px); }
a.bm-card:hover .bm-card__link > span { text-decoration: underline;
  text-decoration-thickness: 1px; text-underline-offset: .22em; }

/* px offsets, not %: a percentage resolves against the card, so the bleed grew
   with card height and clipped the taller cards' art far more than intended. */
.bm-card__art { position: absolute; right: -26px; bottom: -22px; width: 56%; max-width: 330px;
  pointer-events: none; user-select: none; }
.bm-card--cta { background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 6%, var(--surface)), var(--surface)); }
.bm-card--cta .bm-card__body { max-width: none; }

/* ---- .bm-card--wide: the fifth capability, full width ----
   Five cards in a two-up grid leave the last one sitting alone beside an empty
   cell. [05] takes the whole row instead.

   Full width is not "the same card, stretched". The body's 57% cap against
   1263px of content would put ~105 characters on a line -- the band is 50-75,
   and the card's own two-up siblings run 48. So the wide card re-flows: the two
   paragraphs go side by side at ~52 characters each, which is the measure the
   other four already have, and the headline gets the room to land on one line
   instead of three.

   The art earns a bigger cap here too. 330px against a 658px card reads as half
   the card; against 1335px it reads as a stamp in the corner. 420 is the same
   proportion of the wide card that 330 is of a narrow one. */
@media (min-width: 900px) {
  .bm-card--wide { grid-column: 1 / -1; }
  /* The art's width has to become a smaller fraction, not just a larger cap.
     56% is tuned against a 658px card; left at 56% with a 420px ceiling it
     stays pinned at 420 all the way down to a 750px card and starts running
     under the text -- measured at a 1024 viewport it overlapped by 47px. 32%
     of the wide card is the same optical weight 56% has on a narrow one, and
     it shrinks with the card instead of holding its size. */
  /* -43.195% -> -50% (2026-08-21). That number was records.svg's own ink offset
     inside a viewBox it no longer has: the file was cropped to its ink that day,
     so its drawing is now centred in its box and the box centre IS the ink
     centre. Keeping the compensation would have re-introduced the same error it
     was written to cancel, 6.8% of the box height in the other direction.

     The note below still explains where the old number came from and why the
     correction existed; it is kept as the record of why this is now -50% rather
     than deleted as though the problem never existed. */
  .bm-card--wide .bm-card__art { width: 32%; max-width: 420px;
    top: 50%; bottom: auto; transform: translateY(-50%); }
  /* 30rem = 480px, about 68 characters at 14px. 57% is a ratio tuned for a
     658px card; on a full-width one it stops meaning "a column" and starts
     meaning "most of a very wide box" -- 105 characters a line at 1512. */
  .bm-card--wide .bm-card__body { max-width: 30rem; }
}
/* The two paragraphs stay stacked (owner's call, 2026-08-20). They were briefly
   set side by side to use the width; the pain quote and the solution are a
   before/after pair, and reading them as two columns loses the order. One
   column also keeps the wide card reading the same way as the four narrow ones
   -- quote, then answer, then the link.

   That is what the 30rem cap above is for. Stacked at the full 62% the lines
   would run ~105 characters; 30rem holds them at ~62, and it is also what pushes
   the headline onto the two lines it is meant to have. */

/* ---- the illustration is placed by its ink, not by its box ----
   Every offset on .bm-card__art moves the <img> box. On this card the box and
   the drawing are not the same thing, and that is why nudging `right` never
   looked centred.

   HISTORICAL, and the reason `transform` above is now a plain -50%. Until
   2026-08-21 records.svg drew in 360x175 of a 480x360 viewBox -- 36% of the box.
   Measured with getBBox(), not eyeballed:

       inset      left 60   top 68   right 60   bottom 117   (viewBox units)

   The file's viewBox was then cropped to its own ink (now 469.09x229.09, ink
   inset 55/27/55/27, dead centre), which is the general fix for what this note
   describes. The offsets below that read `top`/`transform` no longer need to
   compensate for anything; the `width` and `right` reasoning still stands.

   Left and right are equal, so the drawing IS horizontally centred in its box:
   centre the box in the free space and the drawing centres with it. Top and
   bottom are not -- 68 against 117 -- so the drawing sits high, and `bottom:
   -22px` was bleeding 22px of empty space off the card while the ink stopped
   81px short of the edge.

   The two rules below are records.svg's numbers. A different illustration in
   this slot needs its own; the whole iso set runs from 22% ink (senti-inside)
   to 60% (runtime), so none of them share offsets.

   -- width: 47.2% is the size, ~630px on a 1335px card, giving 473x230 of ink.
      Big enough to hold the right half against a 480px text column without
      outweighing it.
   -- right centres the box between the body's right edge and the card's:
      (100% - width - padding - body) / 2.
   -- top/transform centres the INK vertically. The ink's centre sits at
      (68 + 175/2) / 360 = 43.195% of the box height, so translateY(-43.195%)
      from top:50% puts that point on the card's centre line. Percentage of the
      element's own height, so it holds at any size with no second number.

   No bleed, in either axis. That is a real departure from the card language,
   and it is deliberate: this drawing never actually bled -- 81px of transparent
   padding sat between its ink and the card edge the whole time. */
/* 1100, not 1240. The gate used to be 1240 because a two-column paragraph
   layout lived here; that is gone, so the only thing the threshold has to
   protect is the gap either side of the drawing. That gap is
   (0.646W - padding - 482) / 2 -- it holds at ~48px on a 1100px viewport and
   collapses to 2px by 900, which is where the narrow treatment takes over. */
@media (min-width: 1100px) {
  /* 47.2% was the size approved in the mockup; * .95 is the owner's 5% trim,
     2026-08-20. Written as the multiplication rather than 44.84% so the two
     decisions stay legible and the `right` calc below cannot drift out of sync
     with the width -- it centres the box, so it has to read the same number. */
  .bm-card--wide { --art-w: calc(47.2% * .95); }
  .bm-card--wide .bm-card__art {
    width: var(--art-w); max-width: none;
    right: calc((100% - var(--art-w) - var(--bm-pad) - 30rem) / 2);
  }
}

@media (max-width: 900px) { .bm-grid { grid-template-columns: 1fr; } }
@media (max-width: 680px) {
  .bm-card { min-height: 0; }
  .bm-card__art { display: none; }
  .bm-card__body { max-width: none; }
}

/* Chrome'd sections read as a document: the body blocks share the heading's
   left edge instead of staying centred. Scoped to .sec-block, which is also
   what the chrome bar aligns to, so heading and body share one edge. */
.sec-block .cmp, .sec-block .flow { max-width: none; margin-inline: 0; }
.sec-block .panel, .sec-block .path, .sec-block .faq { margin-inline: 0; }
.sec-block .panel { text-align: left; }

/* ===========================================================================
   Page canvas, chrome bars and section blocks
   Rebuilt after reading blockmachine.io's whole page rather than its hero.
   The structure there is a strict two-part rhythm:

     [chrome bar]   full-bleed, transparent, padding-inline: gutter, 24px margins
     [block]        margin-inline: gutter, opaque surface, sometimes textured
     [chrome bar]
     [block] ...

   The chrome bar sits OUTSIDE and ABOVE the block, on the canvas — so the
   index label, the heading and the cards all land on ONE left edge. The canvas
   is clean: it carries no texture, and shows only in the margins and the 24px
   gaps. Texture belongs to individual blocks, covers them evenly (no fade),
   and only some blocks get it — 3 of 9 on the reference.
   =========================================================================== */
:root {
  --canvas: #08090C;
  --sec-1: #0B0D11;
  --sec-2: #0E1116;
  --page-gutter: clamp(0px, 3.2vw, 52px);
  --block-pad: clamp(1.15rem, 3vw, 2.5rem);
  /* A measure scale, not one rigid width. Blocks need a maximum or they grow
     with the viewport (at 2560px a gutter-only block ran 2452px), but one
     measure for every chapter is equally wrong: a card grid with illustrations
     wants more room than two columns of table text. Each chapter picks one. */
  --measure-narrow: calc(58rem + 2 * var(--block-pad));   /* reading */
  --measure:        calc(var(--maxw) + 2 * var(--block-pad));
  --measure-wide:   calc(96rem + 2 * var(--block-pad));   /* grids, diagrams */
  --block-max: var(--measure);
  --edge: calc(var(--page-gutter) + var(--block-pad));
  --gap: clamp(14px, 1.7vw, 24px);
}
body { background: var(--canvas); }

/* the chrome bar: its own full-bleed row on the canvas */
/* A chapter is one unit: its chrome bar and its block share a measure by
   construction, so a width variant can never put them on different edges. */
.chapter { width: min(100% - 2 * var(--page-gutter), var(--chapter-max, var(--measure)));
  margin-inline: auto; }
.chapter--wide   { --chapter-max: var(--measure-wide); }
.chapter--narrow { --chapter-max: var(--measure-narrow); }
.chapter > .sec-bar, .chapter > .sec-block { width: auto; margin-inline: 0; }

/* A full-bleed chapter: the section itself spans the viewport (the Industries
   rail has to scroll edge to edge), but its chrome bar and its heading still
   take the default measure so they land on the same content edge as every
   other chapter. */
.chapter--full { width: 100%; max-width: none; }
.chapter--full > .sec-bar,
.chapter--full > section > .wrap {
  width: min(100% - 2 * var(--page-gutter), var(--measure));
  max-width: none; margin-inline: auto; padding-inline: var(--block-pad); }

.sec-bar { width: min(100% - 2 * var(--page-gutter), var(--block-max));
  margin-inline: auto; padding-inline: var(--block-pad); margin-block: var(--gap); }
.sec-bar .sec-chrome { margin-bottom: 0; }

.sec-block { position: relative; overflow: hidden; border-radius: var(--r-lg);
  background: var(--sec-1);
  width: min(100% - 2 * var(--page-gutter), var(--block-max)); margin-inline: auto; }
.sec-block > * { position: relative; z-index: 1; }
.sec-block > .wrap { max-width: none; margin-inline: 0; padding-inline: var(--block-pad); }
.sec-block--2 { background: var(--sec-2); }
.sec-block + .sec-block { margin-top: var(--gap); }

/* Block texture was ported (a 45-degree hatch + 32px plus-mark lattice, and a
   4px dot halftone, both measured off the reference's PNGs) and then dropped:
   with the corner cloud carrying the atmosphere, a second texture system made
   the blocks busy and broke this file's own "one atmospheric moment per page"
   rule. The blocks are flat surfaces now. If it is ever wanted back, the recipe
   is in the design doc. */

@media (max-width: 700px) { .sec-block { border-radius: 0; } }

/* A chrome'd section that must stay full-bleed (the Industries rail, whose
   edge-to-edge scroll breaks inside a block) still shares the one left edge. */


/* ===========================================================================
   Atmosphere — viewport-scale, full-bleed, dissolved by gradient masks
   The device this port kept missing. On the reference it is a
   pointer-events-none layer pinned to a viewport CORNER or EDGE, carrying a
   uniform dither, dissolved with a gradient mask. It is independent of the
   content blocks entirely, which is why it reads full-width.

     home        top-0 left-0 · h-[50vh] w-[30%] max-w-[600px]
                 mask: radial-gradient(at 0% 0%, #000 0%, #000 60%, transparent 70%)
     technology  inset-y-0 left-0/right-0 · w-[20%] max-w-[389px] · repeat-y
                 mask: linear-gradient(to right, transparent, #000 40%)
                     + linear-gradient(transparent, #000 20%, #000 80%, transparent)
                 mask-composite: destination-in

   Every one of its three texture PNGs is perfectly uniform (alpha 16 across the
   whole file). The cloud is 100% mask and pinning — none of it is painted.
   =========================================================================== */
main { position: relative; }

/* The dither dot is sand, not neutral white. This is the largest surface on the
   page and the most meaningless — which makes it the highest-leverage place to
   put a warm tone. At 11% over a near-black canvas it never reads as "a colour",
   only as the page no longer being uniformly cold. */
.atmos { position: absolute; pointer-events: none; z-index: 0;
  background-image: radial-gradient(circle at 1px 1px, rgba(231,192,139,.11) 1px, transparent 0);
  background-size: 4px 4px; }

/* the corner cloud, dissolving outward from the page's top-left */
/* The reference's single radial stop (#000 60% -> transparent 70%) leaves only
   ~62px to dissolve along the top edge, which on a flat dark bar reads as a
   visible vertical seam. Shaped by the radial, then guaranteed to zero at the
   box's own right and bottom edges by two linear layers — the same
   mask-composite technique the reference uses on /technology. */
.atmos--tl { top: 0; left: 0; width: 30%; max-width: 600px; height: 50vh;
  -webkit-mask-image: radial-gradient(at 0% 0%, #000 0%, #000 40%, transparent 82%),
                      linear-gradient(to right, #000 45%, transparent 100%),
                      linear-gradient(to bottom, #000 45%, transparent 100%);
          mask-image: radial-gradient(at 0% 0%, #000 0%, #000 40%, transparent 82%),
                      linear-gradient(to right, #000 45%, transparent 100%),
                      linear-gradient(to bottom, #000 45%, transparent 100%);
  -webkit-mask-composite: source-in; mask-composite: intersect; }

/* edge strips running the page height, fading inward and out at top and bottom */
.atmos--edge { top: 0; bottom: 0; width: 20%; max-width: 389px;
  -webkit-mask-composite: source-in; mask-composite: intersect; }
.atmos--l { left: 0;
  -webkit-mask-image: linear-gradient(to right, #000 0%, transparent 100%),
                      linear-gradient(transparent 0%, #000 18%, #000 82%, transparent 100%);
          mask-image: linear-gradient(to right, #000 0%, transparent 100%),
                      linear-gradient(transparent 0%, #000 18%, #000 82%, transparent 100%); }
.atmos--r { right: 0;
  -webkit-mask-image: linear-gradient(to left, #000 0%, transparent 100%),
                      linear-gradient(transparent 0%, #000 18%, #000 82%, transparent 100%);
          mask-image: linear-gradient(to left, #000 0%, transparent 100%),
                      linear-gradient(transparent 0%, #000 18%, #000 82%, transparent 100%); }

/* the blocks are opaque and clip: they cover the atmosphere, so it reads in the
   gutters, the gaps and behind the full-bleed hero — exactly as the reference. */
main > .sec-bar, main > section, main > div { position: relative; z-index: 1; }

/* full-width hairline between movements (.grid-line on the reference) */
.rule-full { height: 1px; border: 0; margin: 0; background: var(--border); }

@media (max-width: 900px) { .atmos--edge { display: none; } }
@media (prefers-reduced-motion: no-preference) { }

/* ONE atmosphere device per page. The reference never stacks them: its home
   page carries only the corner cloud, /technology only the edge strips. They
   are alternate registers, not layers — stacked, they paint the same halftone
   twice at the same phase and the corner visibly doubles in density.
   Belt and braces, since the markup should not contain both either. */
main:has(> .atmos--tl) > .atmos--edge { display: none; }

/* Deployment models form a separate content group after the capability grid. */
/* 80px between the capability grid and the models group (owner's call,
   2026-08-19, was 64px). They are two blocks of the same component, so the gap
   is the only thing telling a reader they are two questions and not one long
   list -- it has to beat the 20px the cards inside either grid sit apart by,
   with room to spare. */
.bm-models { margin-top: 5rem; }
.bm-models__head { margin-bottom: 2.5rem; }

/* Capabilities and deployment models are different categories; koni-growth
   requires /product/ and the homepage grid to keep them visually separated.
   This is the divider inside the card grid, spanning both columns.

   Restored 2026-08-20. It was dropped on 2026-08-19 with Home's card grid, but
   /product/ still runs it at its line 489 -- so between those two dates the
   heading "Deployment models" and its paragraph rendered on /product/ with no
   rule above them, no tracking, and at the body size. Home does not use it
   (.bm-models__head carries a real <h2> there); /product/ is the only caller. */
.bm-split { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: baseline;
  gap: .5rem 1.5rem; padding: clamp(1.5rem, 3vw, 2.25rem) 0 clamp(.5rem, 1vw, .75rem);
  border-top: 1px solid var(--border); }
.bm-split__l { font-family: var(--ff-mono); font-size: .6875rem; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase; color: var(--fg); flex: none; }
.bm-split__d { flex: 1 1 30rem; margin: 0; color: var(--fg-muted); font-size: .875rem; }

/* ===========================================================================
   Mega-menu — multi-column dropdown with illustrations
   The SaaS pattern (columns + a featured panel) rendered in Senti's language:
   no icons-in-circles, which design/DESIGN.md bans. The isometric library is
   the icon set — every menu row already has exactly one drawing that means it.

   The panel anchors to .nav__in, not to the trigger, so both menus drop from
   the same left edge of the content column and neither can overflow the
   viewport. Desktop only; the mobile menu keeps its grouped list.
   =========================================================================== */
.nav__in { position: relative; }
.nav__drop { position: static; }

.nav__mega { position: absolute; top: calc(100% + 6px); left: 0;
  width: min(920px, calc(100vw - 2 * var(--gutter)));
  display: grid; grid-template-columns: 1fr 1fr 15.5rem; gap: 0;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); overflow: hidden;
  box-shadow: 0 28px 70px -28px rgba(0,0,0,.8); }
.nav__drop:not([open]) .nav__mega { display: none; }
.nav__drop[open] .nav__mega { animation: megaIn .13s ease-out; }
@keyframes megaIn { from { opacity: 0; transform: translateY(-2px); } }

.nav__col { padding: 1rem .75rem 1.15rem; }
.nav__col + .nav__col { border-left: 1px solid var(--border); }
.nav__group { display: block; padding: .35rem .65rem .55rem; font-family: var(--ff-mono);
  font-size: .62rem; font-weight: 500; letter-spacing: .1em; text-transform: uppercase;
  color: var(--fg-muted); }

/* a row is thumbnail + label + one line; the thumbnail sits in a flat well so
   the transparent line-art has a surface to read against */
.nav__mega .nav__row { display: grid; grid-template-columns: auto 1fr; gap: .7rem;
  align-items: start; padding: .55rem .65rem; border-radius: var(--r);
  transition: background .12s; }
.nav__mega .nav__row:hover { background: var(--surface-2); }
.nav__mega .nav__row__art { width: 52px; aspect-ratio: 4 / 3; border: 1px solid var(--border);
  border-radius: var(--r); background: #090C10; overflow: hidden; flex: none; }
.nav__mega .nav__row__art img { width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .25s ease-out; }
.nav__mega .nav__row:hover .nav__row__art img { transform: scale(1.06); }
.nav__mega.nav__mega--wide .nav__row__art { width: 64px; aspect-ratio: 2.05 / 1; }
.nav__mega .nav__row .t { display: block; color: var(--fg); font-size: .875rem; font-weight: 500; line-height: 1.3; }
.nav__mega .nav__row .d { display: block; color: var(--fg-muted); font-size: .75rem; margin-top: .18rem; line-height: 1.45; }

/* the featured panel: the one accent moment in the menu */
/* .nav__feat is an <a> inside .nav__links, so it inherits that rule's
   align-items:center and gap. On a column flex container centre makes children
   shrink to content width — which collapsed the art box to its borders once the
   img went out of flow. Reset both explicitly. */
.nav__mega .nav__feat { display: flex; flex-direction: column; align-items: stretch; gap: 0;
  padding: 1rem; background: var(--sec-2); border-left: 1px solid var(--border); }
/* the img is taken out of flow so it cannot feed its intrinsic 480x360 back
   into the column height — otherwise the art grows the panel, which grows the
   art. The box's natural height is the min-height; it fills any room left. */
.nav__mega .nav__feat__art { position: relative; border: 1px solid var(--border); border-radius: var(--r);
  background: #090C10; overflow: hidden; flex: 1 1 auto; min-height: 104px; }
.nav__mega .nav__feat__art img { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block; }
.nav__mega .nav__feat .t { display: block; color: var(--fg); font-size: .875rem; font-weight: 500; margin-top: .8rem; }
.nav__mega .nav__feat .d { display: block; color: var(--fg-muted); font-size: .75rem; margin-top: .3rem; line-height: 1.5; }
.nav__mega .nav__feat__cta { margin-top: auto; padding-top: .9rem; color: var(--accent);
  font-family: var(--ff-mono); font-size: .74rem; display: inline-flex; align-items: center; gap: .4rem; }
.nav__mega .nav__feat:hover .nav__feat__cta .arr { transform: translateX(3px); }
.nav__mega .nav__feat__cta .arr { transition: transform .15s; }

@media (max-width: 1180px) { .nav__mega { grid-template-columns: 1fr 1fr; } .nav__feat { display: none; } }
@media (prefers-reduced-motion: reduce) {
  .nav__drop[open] .nav__mega { animation: none; }
  .nav__row .nav__mega .nav__row__art img { transition: none; }
}

/* [06] Evaluation to production and [07] Common questions used to run as a
   two-up row (.sec-row / .chapter--stretch, removed 2026-08-19). They are two
   chapters, and the numbered document only ever reads in one direction: pairing
   them made [06] and [07] arrive at the reader simultaneously, which is the one
   thing a sequence is not allowed to do. They are plain .chapter blocks now and
   inherit the same measure, padding, heading scale and left edge as [02]-[05].

   Full width for [07]. .faq carries a reading-width cap of its own -- 54rem --
   written for a centred column on the nine other routes. Inside a Home chapter
   the measure is --measure-wide (96rem), so the cap left roughly half the
   chapter empty on the right while the chapter bar, the heading and every other
   block on the page ran to the full edge. Two different content edges in one
   document is the invariant this file keeps having to repair.

   Home-only, same scope as the badges and the heading scale: the other nine
   routes still centre these blocks at their reading width inside a hairline
   chapter, which is a different composition and is not what was reported. */
main:has(.hero--lede) .sec-block > .wrap > .faq {
  max-width: none; margin-inline: 0;
}

/* ===========================================================================
   [06] keeps .path's 48rem column and moves the head in to meet it
   (owner's call, 2026-08-21 -- REVERSES the 2026-08-19/20 treatment above)

   From 2026-08-19 [06] was widened the same way [07] was: cap removed, and
   because six steps stacked at 96rem would have put .step__d on a ~190
   character line, the stack became a three-column grid and the connector was
   switched off (in a grid it is a stub hanging off each cell with nothing to
   join). That solved "two content edges" by widening the component.

   /product/ [11] solved the same tension the other way on 2026-08-21, by
   moving the chapter head IN to the component's edge instead. That option did
   not exist when [06] was widened, and measured against it the wide grid loses:

     .step__d characters a line, at 1512
       three-column grid   31.5 - 48    (below the 50-75 band)
       48rem column        56 - 75      (in it)

   So the argument the earlier comment made -- one column would be unreadable --
   was true of one column at FULL width, which is not this shape. A 48rem column
   is the readable one and the grid is the cramped one.

   What this costs, recorded so it is not rediscovered as a bug:

   1. The chapter roughly doubles, 639px -> ~1100px. Six steps in a column is
      simply taller than six steps in two rows.
   2. [06] is .chapter--band, and /product/ [11] is not. A 768px column inside a
      full-bleed band leaves ~372px of tinted ground on each side, where [11]
      leaves plain canvas. Owner accepted this rather than dropping [06] from
      the [02]/[04]/[06] band alternation.

   The indent is the same formula [11] uses. It resolves against .sec-bar's and
   .wrap's content box -- both are `padding-inline: var(--block-pad)` on a box of
   the same width, so one formula lands both on the same edge as .path. */
main:has(.hero--lede) .sec-block > .wrap > .path {
  max-width: 48rem; margin-inline: auto;
}
main:has(.hero--lede) .chapter:has(.path) > .sec-bar > .sec-chrome,
main:has(.hero--lede) .chapter:has(.path) .sec-block > .wrap > .section-head--doc {
  margin-left: max(0px, (100% - 48rem) / 2);
}
/* The head has to give up its two columns here. At 48rem the 1.15fr / 1fr split
   would hand the 40px heading a 372px track and the lede a 324px one, so the
   heading would break to four lines beside a lede narrower than the reading
   band. Stacked, both get the full 48rem: the heading lands on two lines and
   the lede reads at ~74 characters, the same as the chapter leads on
   /product/. Single column also matches [11], where the head is a heading and
   nothing else. */
main:has(.hero--lede) .chapter:has(.path) .section-head--doc:has(> .lead) {
  display: block; max-width: 48rem;
}
main:has(.hero--lede) .chapter:has(.path) .section-head--doc > .lead {
  max-width: none; margin-top: var(--gap);
}
/* padding-bottom is the space the connector runs down, so it goes back on the
   step now that the stack is back. It was moved to the grid's row-gap on
   2026-08-20 precisely because nothing ran down it in a grid. */
main:has(.hero--lede) .sec-block > .wrap > .path > .step { padding-bottom: 2rem; }
/* Both moved to `:last-of-type` with the base rule above. Home has no
   `.path__note`, so the two selectors select the same element here and Home
   computes identically -- the change is so this block cannot re-introduce the
   defect if Home ever gains a note. */
main:has(.hero--lede) .sec-block > .wrap > .path > .step:last-of-type { padding-bottom: 0; }
main:has(.hero--lede) .sec-block > .wrap > .path > .step:not(:last-of-type)::before {
  display: block;
}
/* Below the width where the indent would go negative the formula already
   collapses to 0 on its own -- `max(0px, ...)` -- so there is no breakpoint to
   maintain here. The column simply becomes the chapter. */

/* ---------- [05] and [08] brought onto the block system ----------
   Two leftovers from the pre-block layout. Inside a .sec-block every body
   element shares ONE width and ONE left edge; nothing re-centres itself. */

/* the panel's list and emphasis each carried their own max-width and
   margin-inline:auto, which put the bullets, the paragraph and the button on
   three different left edges inside one box. */
.sec-block .panel { max-width: none; text-align: left; }
.sec-block .panel__list,
.sec-block .panel__emph { max-width: none; margin-inline: 0; }

/* the final CTA was the last full-bleed, centre-aligned section on a page whose
   other seven chapters are inset blocks with left-aligned headings. It is a
   chapter like any other, so it reads like one. */
.final.sec-block { border-top: 0; }
.final.sec-block .wrap { max-width: none; }
/* 2.5rem, không 2.75rem (2026-08-25). Đóng chương là một chương như bảy
   chương kia, nên tiêu đề nó đọc cùng cỡ: `main:has(.hero--lede)
   .section-head--doc .sh` là clamp(2.25rem, 5vw, 2.5rem) = 40px ở 1440.

   Đây không phải chuyện cho đều mắt. Chú thích của khối lưới ngay bên dưới nói
   thẳng "Measured in Chrome at the 40px size: ... needs 580px", rồi chọn form
   36rem để chừa 605px cho cột trái. Phép đo đó đúng, nhưng rule lại render
   44px, nên cái ngưỡng 580px được tính cho một cỡ chữ không tồn tại. Kết quả đo
   ở 1440 trước khi sửa:

     .final__pitch h2        44px   3 dòng   cột 620px
     .section-head--doc .sh  40px   2 dòng   (cả bảy chương)

   Ba dòng, đúng cái mà 36rem được chọn để tránh. Hạ về 40px là làm cho phép đo
   đã ghi thành đúng, chứ không phải nới thêm chỗ để bù. */
.final.sec-block h2 { text-align: left; max-width: 32ch;
  font-size: clamp(1.75rem, 3.4vw, 2.5rem); line-height: 1.1; letter-spacing: -.02em; }
.final.sec-block .final__sub { text-align: left; margin-inline: 0; max-width: 44rem; }

/* The close reads as pitch-left / form-right. The right half of this block was
   empty; filling it with the form uses the space with content rather than
   decoration — which also avoids reintroducing the block background and the
   accent flood that were both deliberately removed. */
/* `align-items: center`, not `start`. This grid was built when the close
   carried a long pitch — the old heading ran 130+ characters. The koni-growth
   rewrite cut every close to 30-51 characters and 15-27 words, so the pitch is
   now two short lines beside a ~500px form, and top-aligning left it stranded
   at the top of a tall empty column. Centring is what short copy asks for; the
   layout should follow the writing rather than the writing padding the layout.

   The columns also rebalance: 1fr / 34rem gave the pitch more room than the
   form when the pitch was long. Now the form is the taller and more important
   half, so it gets the fixed generous column and the pitch takes what is left,
   capped so a two-line heading does not stretch into a banner. */
.final.sec-block .final__grid { display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 36rem);
  gap: clamp(2rem, 5vw, 5rem); align-items: center; justify-content: space-between; }
/* 28ch and a flexible left track, so the close's heading breaks where the other
   seven do (2026-08-20). At 26rem/18ch the column was 416px and
   "Differentiate on product, not on terminals." set three lines against the two
   every .section-head--doc heading sets -- the cap was never reached, the track
   was. Measured in Chrome at the 40px size: the sentence's balanced split needs
   580px for "Differentiate on product,", so the track has to clear that.
   1fr against a 38rem form leaves 573px at 1440 -- seven short, and 1440 is the
   width this is read at. 36rem leaves 605px and holds from 1440 up; the form
   loses 32px it does not need, being a two-column field grid, not prose.
   Below 1440 the track drops under 580 and the heading takes three lines, which
   is what [01] [03] and [06] do at that width too. */
.final.sec-block .final__pitch h2 { max-width: 28ch; }
.final.sec-block .final__pitch .final__sub { max-width: 34ch; margin-bottom: 0; }
/* `.lead` names two different things in this stylesheet: the lede paragraph
   (margin: 1rem 0 0) and the form. The form inherits that top margin, which
   was invisible when it was stacked and pushes it 16px below the heading in a
   two-column grid. */
.final.sec-block form.lead { max-width: none; margin-top: 0; }
@media (max-width: 980px) { .final.sec-block .final__grid { grid-template-columns: 1fr; } }

/* ===========================================================================
   Chapter layout for the nine non-homepage routes (2026-08-06)

   The 2026-08-04 rollout shipped the chrome bar to all nine routes and stopped
   there: `.sec-block` and `.chapter` never left index.html, so those routes had
   no access to the left-edge rules or the container scale. Measured on
   /industries/vps-providers before this block:

       heading=112  lede=112  table=240  panel=272   -> three left edges

   Scoping to the structure those routes already have (`main > section > :is(.wrap, .narrow)`)
   fixes all nine at once without restructuring nine HTML files. The homepage
   keeps its own `.sec-block` rules; these are written to agree with them.
   =========================================================================== */

/* ---- one left edge per chapter ---- */
main > section > :is(.wrap, .narrow) > .panel,
main > section > :is(.wrap, .narrow) > .cmp,
main > section > :is(.wrap, .narrow) > .flow,
main > section > :is(.wrap, .narrow) > .tiers {
  max-width: none; margin-inline: 0; text-align: left;
}
/* A cap sets a width and keeps the edge; an auto margin MOVES the edge. Only
   the second is forbidden — so these lose the centring, not the measure. */
main > section > :is(.wrap, .narrow) .panel__list,
main > section > :is(.wrap, .narrow) .panel__emph,
main > section > :is(.wrap, .narrow) .panel__sub,
main > section > :is(.wrap, .narrow) .checks { margin-inline: 0; }
main > section > :is(.wrap, .narrow) > .panel .panel__t,
main > section > :is(.wrap, .narrow) > .panel .panel__sub { text-align: left; }

/* ---- the dead right column ----
   A text chapter caps its prose at ~44rem inside a 1280px chapter, leaving 55%
   of the row empty with nothing holding it. `.sec-split` puts the argument left
   and its evidence right, so the measure is a reading decision rather than a
   hole. Collapses to one column below 1000px. */
/* 30rem, not 27: at 27 the display heading (clamp up to 3.75rem) broke
   "One dashboard for supported self-serve operations" into six lines. The
   heading also steps down a size in split mode — a two-column chapter is a
   quieter register than a full-bleed one, and the type should say so. */
/* The step-down below is overridden on /product/ (see main.page-product at the
   end of this file). It was written when a split chapter was a quieter register
   than a full-bleed one, which held while the two sizes were 60px and 38.4px.
   At 40px they would be 1.6px apart -- not a register, just a value that looks
   like a rounding error. And /product/ numbers its chapters and prints
   "/ 08 : 13" next to them, which says outright that all thirteen are peers. */
.sec-split .sh { font-size: clamp(1.6rem, 2.6vw, 2.4rem); max-width: 18ch; }
/* column-gap, not gap. As a shorthand this also set the ROW gap, and the split
   grid has two rows: the chapter bar spans 1/-1, the head and its evidence sit
   under it. So every split chapter paid 72px of row gap on top of the 44px
   margin-bottom .sec-chrome already carries -- 116px from the chapter bar down
   to the heading, against 44px in the eleven chapters that are not split.
   Measured on /product/ 2026-08-20: [08] and [10] at 116px, [01]-[07] and
   [09]-[13] at 44px. The columns want the gap; the rows do not. */
.sec-split > :is(.wrap, .narrow) { display: grid; grid-template-columns: minmax(0, 30rem) minmax(0, 1fr);
  column-gap: clamp(2rem, 5vw, 4.5rem); row-gap: 0; align-items: start; }
.sec-split > :is(.wrap, .narrow) > .sec-chrome { grid-column: 1 / -1; }
.sec-split > :is(.wrap, .narrow) > .section-head { grid-column: 1; margin-bottom: 0; }
.sec-split > :is(.wrap, .narrow) > *:not(.sec-chrome):not(.section-head) { grid-column: 2; }
.sec-split > :is(.wrap, .narrow) > .panel { margin-top: 0 !important; }
@media (max-width: 1000px) {
  .sec-split > :is(.wrap, .narrow) { display: block; }
  .sec-split > :is(.wrap, .narrow) > .panel { margin-top: 1.75rem !important; }
}

/* ---- illustration as a column, not a corner decoration ----
   Two earlier attempts, both wrong for the same underlying reason.

   1. On the SECTION as background art: it belonged to nothing, because a
      chapter is not a claim.
   2. Bleeding off the panel's corner like .bm-card__art: right vocabulary,
      wrong scale. 330px is ~55% of a 600px card and reads as composition; the
      panel runs 1216px at 1440 and 1552px full-bleed, so the same 340px fell to
      ~22% and became a doodle in the corner — present, doing nothing. A wide
      text panel with a small mark in it is worse than one with no mark.

   So the panel is a two-column composition: the claim on the left at a readable
   measure, the drawing on the right as a real column with real space. That is
   what gives it a job — anchoring the right side of a panel that would
   otherwise be an unusually wide block of prose.

   Background is --surface, not the panel's usual --surface-2: the iso top face
   is #1E232B and --surface-2 is #1D2027, the same luminance, so the drawing
   dissolved into its own host and only the line work survived. On a .bm-card
   the faces sit ~1.7x brighter than the card, and that is the figure/ground
   relationship these drawings were built against. */
.panel--art { background: var(--surface); }
/* float, not grid. As a grid the art had to span every row to sit beside the
   text, and its 300px height then forced row 1 to 300px, opening a hole between
   the label and the heading. A float needs no row model: the drawing takes its
   corner and the prose flows around it, which is also what makes the size read
   as composition rather than as a block pushed to one side. */
.panel__art {
  float: right; width: min(38%, 26rem); height: auto; display: block;
  margin: .25rem -1.5rem 1.25rem clamp(1.5rem, 3vw, 2.75rem);
  shape-outside: inset(0 round 8px);
  pointer-events: none; user-select: none;
}
.panel--art::after { content: ""; display: block; clear: both; }
.panel--art > *:not(.panel__art) { max-width: none; margin-inline: 0; }
@media (max-width: 980px) { .panel__art { display: none; } }

/* ---- full-bleed chapter ----
   First attempt only dropped the cap on the inner blocks, so the chapter stayed
   inside `.wrap`'s --maxw and nothing visibly changed. On the homepage
   "full width" means something specific and measurable: at a 1440 viewport a
   default chapter is 1348 @ 46 and a `.chapter--full` section is 1440 @ 0 — the
   SECTION reaches the viewport edge while the bar and heading keep the measure.
   That is what this reproduces for routes with no .chapter wrapper.

   Worth noting: /product's `.wrap` caps at --maxw (1280), which is narrower than
   the homepage's default chapter (1348) even before any full-bleed. So these
   sections were losing on two counts. */
/* The wrap keeps the DEFAULT measure, so the chapter bar and heading sit on the
   same left edge as every other chapter on the page. Only the BLOCK breaks out.
   Getting this backwards was a real defect: widening the wrap moved the bar
   from x=352 to x=184 while its neighbours stayed at 352, so consecutive
   chapters no longer shared a left edge — and the bar ended 464px short of the
   table it labels, which made it read as orphaned rather than as that table's
   label. The homepage never had this problem because `.chapter--full` widens
   the SECTION and leaves the bar at the measure. */
.sec-full { width: 100vw; margin-inline: calc(50% - 50vw); }
.sec-full > :is(.wrap, .narrow) {
  width: min(100% - 2 * var(--page-gutter), var(--maxw));
  max-width: none; margin-inline: auto;
}
/* The block centres on the wrap and is allowed to exceed it — but ONLY when the
   wrap is a plain flow. `:not(.sec-split)` matters: the band pass put .sec-full
   on sections that were already .sec-split, and in a grid the break-out
   (margin-left:50% + translateX) tears the block out of its column and centres
   it on the viewport, where it lands on top of the heading in column 1. A split
   chapter can legitimately be a band; the grid places its blocks, so the
   break-out must stand down. Same reason `.panel--art` floats rather than
   breaking out. */
.sec-full:not(.sec-split) > :is(.wrap, .narrow) > .cmp,
.sec-full:not(.sec-split) > :is(.wrap, .narrow) > .cards,
.sec-full:not(.sec-split) > :is(.wrap, .narrow) > .tiers,
.sec-full:not(.sec-split) > :is(.wrap, .narrow) > .bm-grid,
.sec-full:not(.sec-split) > :is(.wrap, .narrow) > .panel {
  width: min(100vw - 2 * var(--page-gutter), var(--measure-wide));
  max-width: none;
  margin-left: 50%; transform: translateX(-50%);
}

/* ===========================================================================
   Display face — Geist Mono (2026-08-06)
   Opt-in only. Everything not listed here keeps --ff-mono (JetBrains), which
   is what protects the product-UI mimicry blocks from silently changing face.
   The line is drawn at ~1rem: above it the type is speaking, below it the type
   is labelling, and labelling is what JetBrains' grid is for.
   =========================================================================== */
.hero h1,
.hero__hook,
h2.sh,
.brand,
.footer__brand,
.stat__n,
.sol__t,
.card__t,
.step__t,
.tier__t,
.split__body h2,
.panel h2,
.panel__t,
.quote p,
.faq summary,
.final h2,
.bm-card__t,
.rcard__t,
.nav__mega .nav__row .t,
.nav__feat .t { font-family: var(--ff-display); }
/* .faq summary carries the toggle glyph, which is furniture, not voice. */
.faq summary .pm { font-family: var(--ff-mono); }

/* ---- illustration in a split chapter's evidence column ----
   Three routes (quant-prop, solo-traders, strategies-devs) carry no .panel at
   all — they are built from checks, tables and cards — so .panel--art has
   nothing to attach to. In a .sec-split the grid already sends every direct
   child of .wrap to column 2, so an image dropped there lands in the evidence
   column without any extra layout. It sits above the list it illustrates. */
.split__art { display: block; width: min(100%, 30rem); height: auto;
  margin: 0 0 clamp(1.25rem, 3vw, 2rem); }
@media (max-width: 1000px) { .split__art { display: none; } }

/* ===========================================================================
   Closing the hero when no traction band follows (2026-08-06)
   Four routes put a .stats band under the hero and it does the separating.
   Five do not — solo-traders, strategies-devs, trading-groups, /proof and
   vps-providers drop straight from the hero into a chapter, so the hero has
   nothing closing it and the seam reads as empty rather than as a boundary.
   Reuses .stats' own device (a band rule plus a surface lift) rather than
   inventing a second separator, and self-selects with a sibling rule so a
   route that later gains a stats band stops matching on its own.
   =========================================================================== */
.hero + section:not(.stats):not(.works) {
  border-top: 1px solid var(--border);
  background: linear-gradient(to bottom,
    color-mix(in srgb, var(--surface) 42%, transparent) 0,
    transparent clamp(180px, 22vw, 320px));
}
/* The band belongs to the seam, not to the chapter, so it must not travel with
   a full-bleed section's own edge-to-edge background. */
.hero + section.sec-full { background-clip: border-box; }

/* ===========================================================================
   Block rhythm inside a chapter (2026-08-06)
   .panel, .cmp, .cards and friends carry no vertical margin of their own. Where
   an inline style="margin-top:2.5rem" happened to be written the spacing is
   fine; where it was not, blocks weld together. Measured on
   /industries/vps-providers [05]:

       lead -> panel  14px      panel -> cmp  0px      cmp -> lead  2px

   A bordered block touching the paragraph above it, or another bordered block,
   is the "chi tiết sát dí" the owner reported. Spacing this important should be
   a rule, not something remembered per instance — the inline margins are what
   made it look handled while three chapters had none.

   Excluded from .sec-split, where the grid's own gap already does this and a
   margin would double it.
   =========================================================================== */
/* `.sec-block` is included because the HOMEPAGE nests its sections one level
   deeper — `.chapter > section.sec-block > .wrap` — so `main > section` never
   matched it. I fixed nine routes and missed the one that is the reference. */
:is(main > section, .sec-block):not(.sec-split) > :is(.wrap, .narrow)
  > :is(.panel, .cmp, .opts, .cards, .tiers, .flow, .path, .faq, .bm-grid, .checks, .split__art) {
  margin-block-start: clamp(1.75rem, 3vw, 2.75rem);
}
/* Prose that follows a block needs the same air as prose that precedes one.
   `.faq` added 2026-08-21: it was in the block list above (it gets a margin when
   it follows something) but not in this one (nothing got a margin when it
   followed IT). That gap was invisible while every route's `.quick` sat outside
   `<main>` as a sibling of the section, because a sibling never matched this
   rule anyway. Moving the eight `.quick` blocks into their chapters made it
   visible on seven of them at once: `.faq + .quick` measured 0px. */
:is(main > section, .sec-block):not(.sec-split) > :is(.wrap, .narrow)
  > :is(.panel, .cmp, .opts, .cards, .tiers, .flow, .path, .faq, .bm-grid, .checks) + :is(p, .lead, .section-head, .quick, .works__row) {
  margin-block-start: clamp(1.75rem, 3vw, 2.75rem);
}
/* One exception, on Home only: [05]'s .flow (owner's call, 2026-08-20). The
   clamp above is the air between a chapter's heading block and the first thing
   under it, and every other chapter spends it on a panel, a table or a list --
   flat things that start at their first line of text. .flow is a bordered,
   glowing card whose own 1.1rem of node padding and 1px edge already read as
   separation, so the 44px sat on top of a gap the box was drawing for itself.
   Scoped to the homepage because /trust/ runs the same .flow under a plain
   .section-head, where the interval is doing the job it was written for. */
main:has(.hero--lede) .sec-block > .wrap > .flow { margin-block-start: 0; }


/* ===========================================================================
   .narrow is a container variant, not a second left edge (2026-08-06)
   Three chapters on /trust use `<div class="narrow">` instead of `.wrap`. Two
   consequences, both real:
     1. Every chapter rule in this file was written `> .wrap`, so .sec-split and
        .sec-full were applied to those sections in the markup and silently did
        nothing. Fixed above by matching :is(.wrap, .narrow) throughout.
     2. .narrow centres at 60rem, which pushed their chapter bar 128px inboard
        of every other bar on the page. A narrower MEASURE for reading is
        legitimate — that is what the container scale is for — but the bar is
        the chapter's rule line and it defines where the chapter starts. It
        belongs on the shared edge; only the prose should narrow.
   =========================================================================== */
/* Give it .wrap's shape exactly — .narrow has no padding-inline of its own, so
   simply removing the cap ran the bar to x=0, flush against the section edge. */
main > section > .narrow { max-width: var(--maxw); margin-inline: auto;
  padding-inline: var(--gutter); }
main > section > .narrow > .sec-chrome { max-width: none; }
main > section > .narrow > :not(.sec-chrome):not(.panel):not(.cmp):not(.cards):not(.tiers) {
  max-width: 60rem; margin-inline: 0;
}


/* ===========================================================================
   Pending items closed (2026-08-06)
   =========================================================================== */

/* ---- 1. the hero mockup's torn bottom edge ----
   The terminal screenshot is 2549x1326 cropped to ~678px, and the cut lands
   mid-row, so the hero ends on a torn edge. On the homepage that is absorbed by
   the scroll-zoom composition; on a static hero it just reads as cut off.
   A mask dissolves the last ~120px instead of slicing it, which is the same
   device the atmosphere layer already uses. Nothing is re-cropped and no new
   asset is needed. */
.hero--stack .hero__stage .terminal__shot,
.hero--stack .hero__stage img {
  -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 calc(100% - 120px), transparent 100%);
          mask-image: linear-gradient(to bottom, #000 0, #000 calc(100% - 120px), transparent 100%);
}

/* ---- Home: sink the foot of the mockup into the page (2026-08-18) ----
   A scrim, not a mask, and the difference is not stylistic here.

   The rule above masks the <img> alone, so the fade revealed the .browser's own
   plate -- color-mix(--surface 96%, black), about #13161B against the page's
   #0B0D11 -- and the screenshot dissolved into a pale band that read as a seam
   above the ticker. Moving that mask up onto .browser fixed the color and broke
   something worse: a mask makes the frame *translucent*, and what sits behind it
   at that scroll position is the sticky hero copy, so the CTA and the chips bled
   through the Positions table.

   A gradient painted on top has neither problem. It ends on var(--bg), the same
   ground the hero is painted on, so the frame appears to sink into the page; and
   because it is opaque by the end, nothing behind it can show through. --bg is a
   token, so a change of ground carries here without an edit.

   Percentages, not the 120px above: .terminal__shot is height:420px fixed below
   760px, where a fixed 120px took 29% of the image against 20% here. Four stops
   rather than two so the ramp reads as a dissolve, not a wipe with a visible
   start line. color-mix instead of bare `transparent` keeps every stop tinted to
   --bg, so no engine can interpolate the ramp through a grey. Source order beats
   the rule above; no specificity trick.

   Scoped to .hero--lede: the nine other routes end their .browser on a code
   panel that is meant to stay legible to its last line.

   That reason is wrong for three of the nine, checked 2026-08-21. quant-prop,
   solo-traders and trading-groups-academies end their hero on the same
   senti-terminal.png Home does, so they take the 120px mask above and get the
   pale band this scrim exists to remove.

   The obstacle, re-measured 2026-08-21 after getting it wrong once. An earlier
   version of this note said "the scrim ends on --bg" and used that as the reason
   not to port it. That is not what it does: the ramp below ends on
   `var(--canvas, var(--bg))` = #08090C, and the comment inside it argues that
   choice explicitly. The three routes' body is #08090C too, so the scrim's end
   colour is not an obstacle at all.

   What is: what sits *under* the foot. `.device` has margin-bottom -1px, so the
   frame's last pixel is the hero's last pixel. Home makes that land clean with a
   second rule — `.hero--lede + .ticker { background: var(--canvas) }` — so the
   surface below is the colour the scrim dissolved to. On solo-traders the next
   element is `section.sec-split.sec-full` at #0E1116, six levels lighter, and it
   is a full-bleed band rather than a strip that can simply be repainted.
   Measured at 1400: the dissolve would land on #08090C and then step straight to
   #0E1116 across the full width.

   So porting the scrim is two changes, not one, and the second is a decision
   about that band rather than about the frame.

   PORTED 2026-08-21. See `.device--sunk` immediately below. */

/* ===========================================================================
   .device--sunk — the frame treatment, split off .hero--lede (2026-08-21)
   ===========================================================================

   Same move `.hero--ladder` made on 2026-08-20, for the same reason and with
   the same test. Nine rules below used to be scoped to `.hero--lede`, which is
   Home's page identity, so a route could only have the frame by taking the
   chapter chrome, the `.path` grid and the 16px base scale with it. They are not
   about the page. They are about one `<figure class="device">`:

     .hero--lede   = "this is Home".
     .device--sunk = "this frame sinks into the page instead of being cut off" —
                     the nine rules below, and nothing else.

   Everything they touch lives inside the figure — `.browser`, `.mock__bloom`,
   `.terminal__shot`, `.beam`, and `.device`'s own `::after`. The one rule that
   genuinely is Home's stays behind: `.hero--lede + .ticker`, which repaints the
   strip *underneath* the hero and therefore belongs to the page, not the frame.

   Specificity is preserved rule by rule, not approximated. Where the old
   selector leaned on source order against a competing rule, the new one carries
   the same class count so source order still decides — noted at each site.
   Home is on the same computed values afterwards, verified property by property
   at 1512 / 1280 / 900 (see the commit).

   ---------------------------------------------------------------------------
   SPLIT AGAIN, same day: .device--lit and .device--sunk

   The first split assumed one figure wants all nine rules. It does not, and the
   thing that separates them is measurable — how far the content stops from the
   frame's foot:

     route                    frame    content ends    scrim would cover
     solo-traders (image)      637px      1px above          229px
     earning-platforms (code)  341px     29px above          123px
     strategies-devs (code)    317px     29px above          114px

   The screenshot ends 1px from the foot because it was cropped mid-row — there
   is nothing below it, and dissolving it is the whole point. The code panels end
   29px from the foot on a 30.4px padding: they finish where their author meant
   them to. A 123px scrim over a 341px frame eats the last four lines of a `200
   OK` response body. Same class, opposite outcome.

   So the nine rules are two groups, not one:

     .device--lit  = "this frame has a lit edge" — transparent border, the
                     travelling beam, the clip margin the beam needs, and the
                     centred bloom. Content-agnostic: the beam is masked out by
                     24% of the frame height, so it never reaches the content
                     at all, at any frame size.
     .device--sunk = "this frame's content dissolves at its foot" — the foot
                     scrim, the side collar, and the mask reset the scrim
                     replaces. Only correct where the content is cut off.

   --sunk implies --lit in practice (a dissolving foot wants the same edge), so
   the four image routes carry both. The two code routes carry --lit alone.

   Carried by --lit:  Home, solo-traders, quant-prop, trading-groups-academies,
                      earning-platforms, strategies-devs.
   Carried by --sunk: the first four of those. */

/* .terminal__shot must be named: the rule above reaches the image through
   `.hero--stack .hero__stage .terminal__shot`, three classes to this reset's
   two-plus-a-type, so a selector without it loses and the old mask survives
   underneath the scrim. Matching the class count lets source order decide.
   `.device--sunk .browser .terminal__shot` is three classes, same as before. */
.device--sunk .browser .terminal__shot,
.device--sunk .browser img { -webkit-mask-image: none; mask-image: none; }
/* The scrim has to reach past the padding box or the 1px border keeps running
   down through it -- two lit hairlines and a closing line under a frame whose
   contents have already gone. `overflow: hidden` clips a child at the padding
   edge, so the fix is the clip edge itself: `clip` plus a 1px margin moves it out
   to the border box, and the scrim's -1px insets then cover the stroke. Where
   overflow-clip-margin is unsupported the clip stays at the padding box and the
   border simply survives, which is today's rendering -- it degrades to the old
   look, never to a broken one. `clip` over `hidden` also drops the useless
   scroll container; the scroller here is .terminal__scroll, not the frame.

   Both groups need it: the scrim's -1px insets and the beam's `inset: -1px` are
   the same 1px of border box. Listed twice rather than folded into --lit, so
   --sunk still works alone if a frame ever wants a dissolving foot without a
   lit edge. */
.device--lit .browser,
.device--sunk .browser,
.lit-frame .flow { overflow: clip; overflow-clip-margin: 1px; }
/* Border beam (2026-08-18)
   The grey hairline came off this frame for good reasons -- see the history
   below -- but the edge still wants to exist, just not as a drawn line. A single
   emerald arc travelling the perimeter gives the frame a silhouette only while
   it passes, so there is nothing static to sit unevenly.

   No pseudo-element is available: ::before is the glass rim-light shared with
   .panel/.flow, ::after is the foot scrim. So the beam is painted by the border
   itself -- two background layers, the frame fill clipped to the padding box and
   the conic clipped to the border box, which leaves exactly the 1px ring for the
   gradient. Where the conic is transparent the border is transparent too, so the
   frame reads borderless between passes, as it does today.

   --beam is registered as an <angle> because an unregistered custom property is
   a token, not a value, and interpolates in steps. Without @property support the
   angle holds at 0deg: the arc parks in the upper-left and stops moving. That is
   a static highlight, not a broken frame.

   Why the original hairline had to go, kept because it still constrains this:
   1px is the floor for a border, so dimming could only ever take the line from
   luminance 29.6 to 24.4 against an 11.5 ground -- still brighter than both of
   its neighbours (14.5 outside, 12.9 inside), which is exactly why it kept
   reading as a drawn line rather than an edge. `border-color: transparent` would
   not have removed it either: background-clip defaults to border-box, so the
   frame's own #13161B keeps painting under the border. That is the same
   mechanism the beam now uses deliberately -- the fill is pulled back to the
   padding box so the border stops being a window onto it.

   Scoped, so .browser keeps its plain hairline on the routes that use it outside
   a hero. */
@property --beam { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
/* Not a constant-speed sweep. Roughly 140deg of the circle points at the bottom
   of the frame, and the bottom of the frame is under .browser::after -- the beam
   goes into the scrim there and comes out the far side. At a linear 6s that is
   3.5s of blank border out of every 6s, measured, which reads as the animation
   having stopped rather than as a beam passing behind something.

   The breakpoints come from the frame's own geometry, not from the viewport.
   .beam fades out between 9% and 24% of the frame's height -- 58px to 154px down
   a 640px frame. That is deliberately far above the foot scrim, which does not
   start until 64%: the constraint here is the fold, not the frame. At rest the
   frame is 640px tall with only ~198px above a 913px viewport, so a fade tuned
   to the frame's own proportions (the first attempt used 26%-60%) is still 85%
   opaque when the beam reaches the bottom of the screen and the stroke gets
   chopped by the window edge instead of ending. Fading by 154px puts the exit
   ~40px clear of the fold. Scrolling only ever reveals more of the frame, and
   the scroll-zoom scales the box as a whole, so a percentage that clears the
   fold at rest clears it everywhere.

   On the right flank that puts the head solid to 67deg and gone by 75deg, and
   mirrored on the left, so it is live over 285deg -> 0 -> 75deg: 150deg on,
   210deg off. The off arc gets 15% of the cycle, the on arc the other 85%.

   --beam is not the head's angle. The head sits at the 87.5% stop of the conic,
   so head = --beam + 315deg, and the whole schedule has to be written in --beam
   or it lands 315deg out -- which is what the first version of these keyframes
   did: it put the "hidden" arc over the right corner and the right flank and
   whipped the beam through the exact place it was supposed to linger. */
@keyframes beam-run {
  0%   { --beam: 0deg; }     /* head at 315deg -- upper-left, entering the top */
  68%  { --beam: 120deg; }   /* head at  75deg -- faded out down the right flank */
  83%  { --beam: 330deg; }   /* head at 285deg -- back up the left flank */
  100% { --beam: 360deg; }
}

.device--lit .browser {
  --frame: color-mix(in srgb, var(--surface) 96%, black);
  border: 1px solid transparent;
  background: linear-gradient(var(--frame), var(--frame)) padding-box;
}

/* Why the ring is an element and not another background layer on .browser.
   The conic can be clipped to the border box directly -- that was the first
   build -- but then the ring is opaque for its whole circuit and the beam runs
   off the bottom of the screen mid-stroke. Measured on a 1440x913 viewport: the
   frame is 640px tall with only 198px of it above the fold, so of the 55deg the
   conic spends on the right flank just 16deg is on screen. The beam reached the
   corner and stopped, which is not a beam passing behind something, it is a cut.

   A vertical fade is what turns the cut into an exit, and a fade needs a mask.
   Painting --canvas over the lower ring instead would work optically but leaves
   a 1px dark hairline across the bloom between the fade and .browser::after --
   the exact artefact this frame spent so long getting rid of. So: .beam carries
   the dissolve, .beam::before carries the ring, and the ring genuinely goes to
   transparent so the glow behind shows through.

   inset: -1px puts it on the border box, which .browser's transparent 1px border
   reserves for it, and overflow-clip-margin: 1px lets it survive the clip. The
   conic also sweeps unevenly by construction -- the corner radius is 694 against
   320 at the top edge midpoint, so the head moves 2.17x faster through a corner
   than along the top -- and the fade covers that too, since the acceleration
   happens where the ring is already dimming. */
.device--lit .browser > .beam,
.lit-frame .flow > .beam {
  position: absolute; inset: -1px; z-index: 3; pointer-events: none;
  border-radius: 11px 11px 0 0;
  -webkit-mask-image: linear-gradient(to bottom, #000 0 9%, transparent 24%);
  mask-image: linear-gradient(to bottom, #000 0 9%, transparent 24%);
}
/* The fade is shared, the corner is not. /trust/'s card was first given the ring
   unmasked, on the reasoning that the mask answers a frame cut off by the fold
   and this card is fully on screen. That was reasoning about why the rule exists
   instead of about what it looks like, and it produced a different object: a
   closed ring circling all four sides, where every other hero visual on the site
   shows a stroke entering at the top and dissolving down the flanks. The
   signature is the point, so the mask is shared (owner's call, 2026-08-24).

   `border-radius: inherit` stays a difference because it is not a signature, it
   is a fit: `11px 11px 0 0` rounds the two top corners of a `.browser`, whose own
   radius is `10px 10px 0 0`. `.flow` is square (--r-lg is 0), so inheriting gives
   0 on all four and the ring follows the card it sits on. */
.lit-frame .flow > .beam { border-radius: inherit; }
.device--lit .browser > .beam::before,
.lit-frame .flow > .beam::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1px;
  background: conic-gradient(from var(--beam),
    transparent 0 72%,
    color-mix(in srgb, var(--accent) 55%, transparent) 84%,
    var(--accent) 87.5%,
    transparent 89%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask-composite: exclude;
  animation: beam-run 6s linear infinite;
}
/* `beam-loop` lived here and is gone (2026-08-24). It was a constant-speed 4s
   circuit, correct only while the ring was unmasked: with nothing hidden, an
   uneven sweep reads as a stutter. Restoring the fade restores the condition
   `beam-run` was written for — most of the circuit is invisible, so the schedule
   has to hurry the head through the dark arc instead of parking it there — and
   the shared rule above now supplies it. One animation for every lit frame. */
/* Frozen, the comet is a bright smear parked in one corner and reads as a
   rendering fault. Reduced motion gets an even hairline instead -- the same
   colour at the alpha the beam averages to, so the frame keeps an edge without
   anything moving. */
@media (prefers-reduced-motion: reduce) {
  .device--lit .browser > .beam::before,
  .lit-frame .flow > .beam::before {
    animation: none;
    background: color-mix(in srgb, var(--accent) 18%, transparent);
  }
}
/* The bloom, centred. It was off-axis twice over -- gradient centre at 58% and a
   box hanging 16% past the right edge against 6% on the left -- so the glow
   reached further right than left. Measured in the 12px strip beside the frame,
   the right side ran 7.6-9.6 luminance hotter than the left at every height;
   switching the bloom off collapsed that gap to 0.0, while switching off the
   frame's emerald shadow instead left it untouched, which is what pinned it here
   rather than on the shadow or the scrim.

   Horizontal overhang is now 12% each side -- 24% total against the old 22%, so
   the glow keeps roughly its former reach, just shared evenly. Vertical insets
   are left alone: .hero--stack clips at the hero's bottom edge and the collar
   scrim covers the last third, so nothing below the frame depends on them.

   This does flatten the lighting a little. An off-centre source reads as light
   arriving from somewhere, which is presumably why it was written that way; the
   trade is deliberate. */
/* The centring goes with --lit, because the asymmetry it fixes has nothing to do
   with the foot: gradient centre at 58% and a box hanging 16% past the right edge
   against 6% on the left made the glow reach further right than left, measured at
   7.6-9.6 luminance across every height of the 12px strip beside the frame. Any
   frame that is lit wants it. Horizontal overhang is 12% each side -- 24% total
   against the old 22%, so the reach is kept and only shared evenly. */
.device--lit .mock__bloom,
.lit-frame .mock__bloom {
  inset: -26% -12% -26% -12%;
  background: radial-gradient(55% 55% at 50% 42%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 70%);
}
/* The taller ellipse goes with --sunk, because it depends on the collar. 78%
   vertical radius against 55%: only the ellipse's height grows, so the two lit
   strips run further down the frame without getting any wider. That was safe on
   Home because `.device--sunk::after` reaches 90px past each edge and extinguishes
   them. A --lit-only frame has no collar, so it keeps the 55% ellipse and the
   glow stops where the frame does. Source order puts this after the rule above;
   both are (0,2,0) and every --sunk frame is also --lit. */
.device--sunk .mock__bloom {
  background: radial-gradient(55% 78% at 50% 42%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 70%);
}
/* ---- what the scrim cannot reach (2026-08-18) ----
   Measured at the foot of the frame: the scrim lands on #08090C exactly, but the
   ground beside it read #080F10 -- green +6, blue +4 against --canvas -- and the
   strip directly under it was the ticker at #0E1014 behind a #2A2E36 hairline.
   The dissolve was landing correctly and then sitting between two surfaces that
   are not the page.

   The emerald stays. It is the one signal-glow this page is allowed and it reads
   at the top and the sides of the frame; the problem was only ever the last few
   dozen pixels, where a bright collar met a foot that had already dissolved. It
   cannot be masked in place -- box-shadow paints outside the border box, and a
   mask is sized to its element's box with mask-repeat tiling the ramp across
   everything beyond it -- so it is covered instead, by a second scrim that is
   wider than the frame and paints above it.

   .device is the right owner: it is not the element the scroll-zoom transforms,
   so this collar keeps its width while the frame breathes, and it is not clipped,
   so the -90px insets reach the glow spilling past the frame's sides. z-index 2
   puts it over .browser (1) and .mock__bloom (0). */
.device--sunk::after {
  content: ""; position: absolute; inset: auto -90px -1px -90px; height: 11%;
  z-index: 2; pointer-events: none;
  /* Ramp held back so the side glow survives further down. The collar reaches
     90px past each edge, so its opacity curve is also what extinguishes the two
     lit strips beside the frame: they measured flat at ~13.8 luminance down to
     80% of the frame and then fell away. Pushing the climb late -- nothing much
     before the halfway mark, most of it in the last quarter -- keeps the strips
     alive to roughly 92% while the final stop still lands on solid --canvas, so
     the seam with the ticker is unchanged. The frame's own foot does not depend
     on this curve; .browser::after dissolves the content on its own. */
  background: linear-gradient(to bottom,
    color-mix(in srgb, var(--canvas, var(--bg)) 0%, transparent) 0%,
    color-mix(in srgb, var(--canvas, var(--bg)) 14%, transparent) 52%,
    color-mix(in srgb, var(--canvas, var(--bg)) 58%, transparent) 80%,
    var(--canvas, var(--bg)) 97%);
}
/* The ticker is the surface the mockup actually dissolves onto, so on this route
   it drops the band fill for a --canvas ground -- the foot of the frame lands on
   the same colour it started on and the seam reads as page, not as a second
   surface. The top hairline stays: it closes the hero, and without it the
   mockup fades into nothing and the ticker has no upper edge at all. The
   adjacent-sibling selector holds because the only node between the two is a
   comment. */
.hero--lede + .ticker { background: var(--canvas); }

/* The stats band keeps its top rule, but it should be one hairline and not two.
   The ticker carries its own bottom border and the two elements sit flush, so
   left alone the seam renders 2px of --border. Home hands the rule to .stats
   and drops the ticker's, which reads as the single stroke it was always
   meant to be. The seven other routes are untouched: there .stats follows a
   chapter directly and its border-top is already the only line at the seam. */
.hero--lede + .ticker { border-bottom-color: transparent; }

/* The foot scrim, in two sizes. The box is shared; only how far up it reaches
   and how fast it ramps differ, because the two frame kinds have different
   amounts of room below their content:

     --lit   a panel whose content stops inside its own bottom padding. The
             scrim lives ENTIRELY in that padding and cannot reach a glyph.
     --sunk  a screenshot cropped mid-row, content running to the last pixel.
             36% of the frame, because there is nothing to protect.

   Both are listed on the shared box so either works without the other. */
.device--lit .browser::after,
.device--sunk .browser::after,
.lit-frame .flow::after {
  content: ""; position: absolute; inset: auto -1px -1px -1px;
  pointer-events: none;
}
/* 26% of the frame, and a ramp that climbs late. Both numbers came out of
   measuring, so the reasoning is worth keeping.

   The first build was 30.4px — the codebody's own bottom padding — chosen so the
   scrim could not reach a glyph at all. That was provably safe and visually
   almost nothing: a hem, not a dissolve. Pushed taller, the visible fade has to
   overlap the last lines, and the question stops being geometric and becomes a
   legibility budget. Measured as contrast ratio on the last code line at 1400,
   against the same line with the scrim switched off:

     height / ramp            earning-platforms   strategies-devs
     off (baseline)                 15.68:1            3.23:1
     30.4px  0/44/74/93             15.68:1            3.23:1   <- costs nothing
     60px    0/44/74/93              8.84:1            2.42:1
     90px    0/44/74/93              4.30:1            1.72:1
     90px    0/62/82/96              7.19:1            2.11:1   <- chosen
     120px   0/70/88/97              6.60:1            2.01:1
     160px   0/78/91/98              6.46:1            1.95:1

   Two things fall out. Delaying the ramp is worth more than shortening the box:
   90px at 0/62/82/96 keeps 7.19:1 where 90px at 0/44/74/93 keeps 4.30:1, for the
   same visible length of fade. And past ~120px the curve flattens — more height
   buys no more dissolve, it just starts higher up the panel.

   The binding case is strategies-devs, whose last line is a dim `//` comment
   sitting 30px off the foot and reading 3.23:1 before anything touches it —
   already under WCAG AA. This treatment takes it to 2.11:1. That is a real cost
   and it is accepted rather than hidden: `.codebody` is `role="img"` with an
   `aria-label` carrying the content, so it is a depiction of code, not code to
   be read; and the line it dims is a closing comment, not the strategy body.
   earning-platforms, whose last line is bright JSON, keeps 7.19:1.

   26% and not 90px, because a percentage tracks the frame the way --sunk's 36%
   does: 88px on earning-platforms' 341px frame, 82px on strategies-devs' 317px,
   and it shrinks with the panel instead of eating a fixed slab off a short one.

   Target is `--canvas`, same as --sunk, because the ground BESIDE the frame at
   its foot measures #080a0c on both routes. Ending there makes the frame's last
   band the same colour as the page either side of it, so the plate stops being a
   rectangle that ends and becomes a foot that dissolves. What is left at the seam
   is the next section's own `border-top` hairline (#23272E, measured), which
   closes the hero exactly as the ticker's does on Home. */
.device--lit .browser::after,
.lit-frame .flow::after {
  height: calc(26% + 1px);
  background: linear-gradient(to bottom,
    color-mix(in srgb, var(--canvas, var(--bg)) 0%, transparent) 0%,
    color-mix(in srgb, var(--canvas, var(--bg)) 40%, transparent) 62%,
    color-mix(in srgb, var(--canvas, var(--bg)) 84%, transparent) 82%,
    var(--canvas, var(--bg)) 96%);
}
.device--sunk .browser::after {
  height: calc(36% + 1px);
  /* --canvas, not --bg. Two rules paint the body and the second one wins:
     `body { background: var(--bg) }` at the top of this file is overridden by
     `body { background: var(--canvas) }` in the chapter-layout block. So the
     ground under the hero is #08090C while --bg is #0B0D11 -- three levels
     lighter. A scrim ending on --bg therefore stops just short of the page and
     leaves a faint plate behind the foot of the frame, which is what "not dark
     enough" was. The fallback keeps this honest on any route that never defines
     --canvas. */
  background: linear-gradient(to bottom,
    color-mix(in srgb, var(--canvas, var(--bg)) 0%, transparent) 0%,
    color-mix(in srgb, var(--canvas, var(--bg)) 40%, transparent) 44%,
    color-mix(in srgb, var(--canvas, var(--bg)) 84%, transparent) 74%,
    var(--canvas, var(--bg)) 93%);
}

/* ---- 2. one measure across every route — NOT DONE, deliberately ----
   `.wrap` caps at --maxw (1280) while the homepage's chapter resolves to
   --measure (1348 at 1440), so the nine other routes sit 68px narrower than the
   page they are meant to match. Syncing them looks like a one-line change and
   is not: `.sec-full` overrides the wrap width for its own break-out, and a
   later global rule beat it, which split the chapter bars back onto two left
   edges on nine of ten routes — the exact invariant the audit had just closed.
   68px of width is not worth reopening it. Doing this properly means moving
   those routes onto the `.chapter` wrapper the homepage uses, which is a
   structural change to nine HTML files, not a CSS tweak. Left open on purpose. */

/* ---- 3. micro-label tracking ----
   0.09em -> 0.12em, the scale app/public/sales/senti-sales.css uses (0.8-1.6px
   at 10-11px). At this size uppercase needs the air to read as a label rather
   than as compressed text, and it matters more now that the labels sit in
   JetBrains beside Geist Mono display type — the two roles should not look
   like the same voice at different sizes. */
/* .field label left this list on 2026-08-19. The rule is for UPPERCASE mono
   micro-labels -- that is what the paragraph above argues for, and it is what
   .field label was until it adopted DESIGN.md §20.2.4. It is now Geist 400,
   12px, sentence-case, and .12em on sentence-case Geist at 12px is 1.44px
   between every letter: same family as the body text, visibly not the same
   voice. §20.2.4 calls `tracking-normal` load-bearing for exactly this reason.

   It sat 1900 lines below the .field label rule at equal specificity, so it won
   on order and quietly overrode the `letter-spacing: normal` set there. */
.stat__l, .works__label, .footer__col h4, .footer__status, .footer__sub,
.mobile .mlabel, .cmp__head > div, .bm-card__tag, .card__tag,
.tside__hd, .chip { letter-spacing: .12em; }

/* ===========================================================================
   Book-a-demo close, presented like the hero (2026-08-06)
   Same three layers the hero uses — an emerald signal glow entering from the
   top edge, plus the 56px grid field in both directions. Reusing --mkt-glow and
   --mkt-grid rather than new values, so the two moments are literally the same
   device and move together if either token changes.

   The glow enters from the top on both, which is deliberate: on the hero the
   light arrives where the page begins, and on the close the same arrival says
   this is where the conversation begins. A page that opens and closes on the
   same note reads as composed rather than as a document that simply stops.

   Built as a pseudo-element because the hero's version is a real <div> that
   would have to be added to ten files by hand — and a hand-added element in
   ten files is how these surfaces drift apart in the first place.
   =========================================================================== */
.final { position: relative; isolation: isolate; }
/* The block's own opaque fill sat ON TOP of the full-bleed field, so the grid
   only showed in the 46px margins either side and the effect read as a frame
   rather than a background. On the close the field IS the background, so the
   block steps back and lets it through. */
.final.sec-block { background: transparent; overflow: visible; }
/* Full-bleed, like the hero. `inset: 0` tied the field to .final's own box —
   1348px on the homepage, where .final is a .sec-block — so the glow stopped at
   the measure while the hero's ran edge to edge. Same break-out formula
   .sec-full already uses on this site, which is the one proven not to raise a
   horizontal scrollbar here. No overflow:hidden, because clipping it is exactly
   what kept it inside the measure. */
/* Two layers, because they need different treatment: the glow is shaped by its
   own radial and must not be masked, while the dither has to dissolve or it
   ends on a hard rectangle. */
.final::before, .final::after {
  content: ""; position: absolute; top: 0; bottom: 0;
  left: calc(50% - 50vw); width: 100vw;
  z-index: -1; pointer-events: none;
}
.final::before { background: radial-gradient(38% 60% at 50% 0%, var(--mkt-glow), transparent 72%); }
/* The cloud instead of the grid field. Same 4px dither and the same sand dot as
   .atmos — it is the same device, not a lookalike, so it moves if that token
   moves. A grid is architecture and says "this is a plan"; the dither is
   atmosphere and says "this is a place". The close asks for a conversation, so
   the softer of the two is the right one, and it also stops the close competing
   with the hero, which keeps the grid.

   Dissolved from the top-centre outward with mask-composite: intersect, the
   same technique .atmos--tl uses. Without it the dither ends on a hard
   rectangle at the section boundary, which is the seam the atmosphere work on
   2026-08-04 already spent a pass removing. */
.final::after {
  background-image: radial-gradient(circle at 1px 1px, rgba(231,192,139,.11) 1px, transparent 0);
  background-size: 4px 4px;
  -webkit-mask-image: radial-gradient(70% 90% at 50% 0%, #000 0%, #000 34%, transparent 88%),
                      linear-gradient(to bottom, #000 55%, transparent 100%);
          mask-image: radial-gradient(70% 90% at 50% 0%, #000 0%, #000 34%, transparent 88%),
                      linear-gradient(to bottom, #000 55%, transparent 100%);
  -webkit-mask-composite: source-in; mask-composite: intersect;
}

/* ===========================================================================
   Section separation on the nine non-homepage routes (2026-08-06)

   The whole separation device — an opaque block on the canvas, alternating
   --sec-1 / --sec-2 so consecutive chapters contrast against each other —
   shipped only on index.html. Counted: 7 uses of .sec-block on the homepage,
   0 on the other nine. Those routes are chapter after chapter of text on one
   flat canvas with a 1px rule between them, which is why they read as one
   continuous stream rather than as a sequence of arguments.

   Applied by :has() so it self-selects on any section carrying a chapter bar,
   and alternates by position so no route needs a per-chapter class.

   Divergence to flag rather than hide: on the homepage the chrome BAR sits on
   the canvas and the block starts beneath it — bar transparent, block opaque,
   which is the Blockmachine structure this layer was ported from. Here the bar
   is inside `.wrap`, so it rides on the block with it. Fixing that means moving
   the bar out into a `.sec-bar` sibling in nine HTML files, which is the same
   structural change already logged for the measure sync. The alternation is
   what the reader actually feels; the bar's surface is a refinement.
   =========================================================================== */
/* Only a full-bleed section carries a surface. An inset chapter sits on the
   canvas with nothing behind it.

   The previous pass gave every chapter a slab, which separated them but turned
   the page into a stack of panels — the eye had to parse an edge at every
   chapter, so nothing stood out. A band that spans the viewport is a different
   kind of event: it reads as the page changing register rather than as one more
   card. Fewer, louder boundaries beat many quiet ones.

   Inset chapters therefore get NO background, no radius and no slab padding;
   the measure and the gap already place them. */
main > section:has(> :is(.wrap, .narrow) > .sec-chrome) {
  background: transparent;
  border-radius: 0;
  width: min(100% - 2 * var(--page-gutter), var(--block-max));
  margin-inline: auto;
  /* Exactly the base `section` rule this file has always had:
     clamp(64px, 9vw, 120px). The band work overrode it with a smaller value and
     that override, not the bands themselves, is what made the page feel tighter
     than before the backgrounds went in. A chapter is still a section; it keeps
     the site's own section rhythm. */
  padding-block: clamp(64px, 9vw, 120px);
  /* No margin. A plain chapter is transparent on the canvas, so a gap between
     two of them separates nothing — it just makes the vertical rhythm uneven,
     which reads as a mistake rather than as spacing. Padding does the work; the
     bands are what create intervals. */
  margin-block: 0;
}
/* The band. Full-bleed, opaque, edge to edge. */
main > section.sec-full:has(> :is(.wrap, .narrow) > .sec-chrome) {
  width: 100vw; margin-inline: calc(50% - 50vw); border-radius: 0;
  background: var(--sec-1);
  /* A band still opens wider than a plain chapter at small and mid widths --
     80px floor against 64, 11vw against 9 -- because a surface with its content
     pressed against the edge reads as a box rather than as room. The ceiling
     comes down to the chapter's own 120px (owner's call, 2026-08-21): at desktop
     the 152px band was reading as a gap between chapters rather than as room
     inside one, and the extra 32px was the whole of that. */
  padding-block: clamp(80px, 11vw, 120px);
}
/* Consecutive bands alternate so two in a row still read as two. */
main > section.sec-full:has(> :is(.wrap, .narrow) > .sec-chrome)
  ~ main > section.sec-full { background: var(--sec-2); }
main > section.sec-full:nth-of-type(even):has(> :is(.wrap, .narrow) > .sec-chrome) {
  background: var(--sec-2);
}
/* The chapter bar's rule comes back on inset chapters: with no slab edge, it is
   the only thing marking where a chapter starts. Bands do not need it — their
   own edge is the boundary. */
main > section:has(> :is(.wrap, .narrow) > .sec-chrome) > :is(.wrap, .narrow) > .sec-chrome {
  border-top: 1px solid var(--border); padding-top: .8rem;
}
main > section.sec-full > :is(.wrap, .narrow) > .sec-chrome {
  border-top: 0; padding-top: 0;
}
/* Chapter marks on the homepage become badges, and give up the hairline that
   used to carry them.

   The rule above exempts .sec-full because a band's own edge is the boundary.
   Home's chapters now do the same thing by a different means: the pill is the
   thing the mark sits on, so the rule under it is a second marker for one seam.
   It also removes a doubling this pass created -- .works ends on its own
   hairline 24px above [01], and that was two lines for one boundary.

   Scoped with main:has(.hero--lede) because there is no page class to hang this
   on; .hero--lede exists on Home and nowhere else. The other nine routes keep
   the rule-and-bare-text treatment, so this is a homepage change, not a system
   one -- say the word and it moves into .sec-chrome proper.

   The box is .chip's, every value: 1px --border, 9999px radius, .35/.7rem
   padding, the same 55% --surface plate. The type is not. .chip is .72rem
   sentence case; these stay uppercase because that is the chapter chrome's voice
   across all ten routes, and a sentence-case chapter mark would read as a
   different kind of thing rather than the same thing in a pill. Tracking comes
   down .12em -> .08em: .12em was drawn for bare text on a rule, and inside a
   padded plate it presses the label into the right edge.

   No hover. .chip hovers because it sits in the hero among things you can act
   on; a chapter index that lights under the cursor promises a click with nowhere
   to send it. */
main:has(.hero--lede) :is(.sec-bar, .final__pitch) > .sec-chrome {
  border-top: 0; padding-top: 0; align-items: center;
}
/* [08]'s badge is not in a bar, so it carries the 24px itself -- var(--gap) is
   the same token .sec-bar's margin-block was spending to open it. */
main:has(.hero--lede) .final__pitch > .sec-chrome { margin-bottom: var(--gap); }
/* The counter is off on Home (owner's call, 2026-08-19). It was the second half
   of a two-part chrome -- "[01] The constraint" on the left, "/ 5 capabilities
   3 models" on the right -- and once the chapter mark became a badge the two
   stopped reading as one bar: a pill on one edge and loose text on the other.
   display:none rather than opacity or visibility, so it also leaves the
   accessibility tree; it is furniture, and a screen reader announcing a count
   nothing else refers to is noise. Home only -- the other nine routes still run
   the hairline chrome where the counter has a rule to sit on. */
main:has(.hero--lede) .sec-chrome__r { display: none; }
main:has(.hero--lede) :is(.sec-bar, .final__pitch) > .sec-chrome > .sec-chrome__l {
  display: inline-flex; align-items: center; gap: .45rem;
  /* line-height: 1, and the vertical padding is asymmetric on purpose.
     The pill inherited 1.65 from body -- a prose leading, on a one-line label,
     which is how it got a 19.8px line box around 12px type. Measured 2026-08-20
     at 1512: ink 9.63px from the top of the pill and 12.07px from the bottom, so
     the label sat 2.44px high inside its own plate.

     Half of that is the leading; the other half is the font. The label is
     uppercase, and JetBrains Mono reserves 4px of descent at 12px that capitals
     and digits never use -- the box is built for a "g" that is not there. Any
     symmetric padding therefore centres the BOX and leaves the INK high, which
     is what the eye actually reads.

     So: line-height 1 gives the label a 12px line box, and 10px/9px lands the ink
     at 11.04 above / 10.68 below -- 0.36px LOW, from 2.44px high. 10/9 is the
     best round pair: exact centring wants 9.82/9.18, and 9/10 overshoots to
     1.64px the other way. 0.36px is a third of a device pixel at 1x and is not
     worth a fractional value. These two numbers are optical compensation for
     JetBrains Mono at 12px specifically -- they are px rather than rem because
     they are tied to that metric, not to the root scale, and changing the pill's
     font-size means re-measuring them. */
  line-height: 1;
  padding: 10px .7rem 9px;
  border: 1px solid var(--border); border-radius: 9999px;
  background: color-mix(in srgb, var(--surface) 55%, transparent);
  /* .75rem = 12px, not the .72rem (11.52px) this inherited from .chip when the
     pill was first drawn. The box is .chip's; the type never was -- it is
     uppercase at .08em where .chip is sentence case -- so there was nothing
     holding the size to .chip's value except that it was copied along with it.
     11.52px also contradicted this system's own whole-pixel rule, and it was the
     last fractional size in Home's chapter chrome. */
  font-size: .75rem; letter-spacing: .08em;
}
main:has(.hero--lede) :is(.sec-bar, .final__pitch) > .sec-chrome > .sec-chrome__l > .sec-chrome__ix {
  margin-right: 0;
}
/* And the chapter body stops opening on 120px of air. `section { padding-block:
   clamp(64px, 9vw, 120px) }` is written for a section that has to separate
   itself from whatever came before; these do not, because the badge above them
   is the separator and it sits 24px away. 120px on top of that put the heading
   144px below its own chapter mark -- far enough that the mark reads as
   belonging to the section above it rather than to the one it labels.
   Top only: the bottom padding still has to close the chapter against the next
   badge. Same Home-only scope as the badges themselves; a bare `.sec-bar +
   section` would also strip /product's and /trust's chapters, which still carry
   the hairline treatment and still need the room. */
main:has(.hero--lede) .sec-bar + section { padding-top: 0; }
/* And the bar loses its own top margin (owner's call, 2026-08-20), so [01]-[07]
   read as badge-on-block rather than badge-floating-between-blocks: the chip
   now sits flush on the bottom edge of the chapter above it and keeps its 24px
   down to the heading it labels. The seam between two blocks goes from
   24 + 32 + 24 = 80px to 0 + 32 + 24 = 56px; the 120px of section padding
   inside each block is untouched, so the distance between one chapter's last
   line and the next one's heading barely moves -- what closes is the empty
   channel the badge was floating in.

   margin-top only. The 24px underneath is what ties the badge to its own
   heading, and it is the whole reason the mark reads as belonging to the block
   below rather than the one above. [01] is unaffected either way: its 80px --
   now 120px -- comes from `.works + .chapter`, which this collapses into. */
main:has(.hero--lede) .sec-bar { margin-top: 0; }
/* [08] keeps the 120px it was given on 2026-08-19 -- it follows [07]'s FAQ,
   which ends on a hairline and a collapsed row, and the close is the one block
   the reader is meant to stop at rather than scroll through. What has changed
   twice is which element carries it, and both times for the same reason: the
   badge has to travel with the heading it labels.

   As padding on the section (19th) the air opened *between* the two -- [08]
   stayed pinned under the FAQ while the heading dropped away from it. Moving
   the 120px to the bar's own top margin (20th) fixed that, but only by keeping
   the badge in a full-width bar of its own, spanning both tracks above a
   heading that occupies the left one.

   Now the badge is inside .final__pitch (owner's call, 2026-08-20), one cluster
   with the heading and the sub in the left track. There is no `.sec-bar` before
   this section any more, so the two rules that keyed off one no longer apply to
   it: `.sec-bar + section { padding-top: 0 }` above stops matching, and
   section's own `padding-block: clamp(64px, 9vw, 120px)` returns -- which is
   the requested 120px at any width from 1333 up, and the same padding every
   other chapter opens on below that. The bar's 120px top margin is gone with
   the bar; the air above the close is unchanged, because [07]'s 120px bottom
   padding plus this section's 120px top is what the bar's margin was standing
   in for.

   .final__grid đã về `align-items: center` (yêu cầu chủ sở hữu, 2026-08-25):
   chip, tiêu đề và subtext canh giữa theo hộp form bên phải. Đoạn này trước đây
   bảo vệ `start` với lý do "form cao gấp ba lần pitch, canh giữa sẽ đẩy cả cụm
   -- kể cả chip -- xuống giữa hàng của nó". Đẩy xuống giữa hàng đúng là điều
   xảy ra, và đó chính là thứ được yêu cầu. Con số thì đã cũ: đo ở 1440 hôm nay
   là form 454px so với pitch 214px, tức 2.1 lần chứ không phải 3 -- tiêu đề đã
   về 40px hai dòng và subtext về 16px kể từ lúc câu đó được viết.

   Lệch trước khi sửa: tâm cụm trái nằm CAO HƠN tâm form 120px ở 1440 (100-121px
   trong dải 981-1920). Sau khi sửa hai tâm trùng nhau ở mọi bề ngang.

   The glow is anchored at 50% 0% of the section box, so it now opens behind
   120px of padding rather than at the badge. If the heading reads dull against
   it, the stop on .final::before is the thing to move, not this. */
/* One content edge for the whole page. Home was running three at once: 185px at
   1920 for [01] (.chapter--wide), 313px for [02]-[05] and [08] on the default
   --measure, and 92px for the [06]/[07] row (.chapter--stretch, since removed
   -- both are plain chapters now). Below 1440 the
   viewport caps all three to the same number and they look deliberate; above it
   they separate, and a reader scrolling from [01] into [02] sees the column jump
   inward by 128px with nothing explaining it.

   --measure-wide is the one they unify on, because it is the widest a chapter
   here actually needs -- [01]'s two-up card grid was already sized to it, and
   widening the rest to match costs nothing, while narrowing [01] to --measure
   would squeeze the grid it was chosen for.

   Three declarations because three different rules own the edge: --chapter-max
   for a plain .chapter, an explicit width for .chapter--full (whose bar and wrap
   hardcode --measure rather than reading --chapter-max), and --block-max for the
   .rail's padding formula, which computes the same edge from the token directly. */
body:has(main > .hero--lede) { font-size: 16px; }
body:has(main > .hero--lede) .lead { font-size: 1rem; }
main:has(.hero--lede) { --block-max: var(--measure-wide); }
main:has(.hero--lede) .section-head--doc { margin-bottom: 2.5rem; }
main:has(.hero--lede) .chapter { --chapter-max: var(--measure-wide); }
main:has(.hero--lede) .chapter--full > .sec-bar,
main:has(.hero--lede) .chapter--full > section > .wrap {
  width: min(100% - 2 * var(--page-gutter), var(--measure-wide));
}
/* Chapter headings on Home come down 60px -> 40px. The clamp at .section-head--doc
   is clamp(2.25rem, 5vw, 3.75rem) and pins at its 3.75rem ceiling from 1200px up;
   2.5rem drops the ceiling and leaves the floor and the vw slope alone, so the
   scale still runs 36px -> 40px and only the wide end moves.

   Same Home-only scope as the badges and the padding above, for the same reason:
   /product/ (13 headings) and /trust/ (10) still run the hairline chrome and the
   120px chapter padding, and a 40px heading under a 120px opening is a different
   proportion than the one being tuned here. Moving all three at once on those
   routes is a separate pass. /industries/* already sits at 37.44px on its own
   narrower clamp and is untouched either way. */
main:has(.hero--lede) .section-head--doc .sh { font-size: clamp(2.25rem, 5vw, 2.5rem); }
/* 22ch was measured against the old 3.75rem heading. At 2.5rem the same 22ch is
   528px, and "One infrastructure layer. Six applications." no longer fits its
   first sentence on one line -- "Six" pulls up and "applications." drops alone,
   which is the widow the break was supposed to prevent. The cap has to follow
   the type down.

   Swept 22/24/26/28/30ch at 1920 and 1440. 24ch already returns [02] to two
   lines, but leaves [04] "Replace infrastructure drag with a runtime you do not
   operate" at three while every other heading sits at two. 28ch is the first
   value where all five land on two lines, and it also collapses [05] "Your
   trading. Your rules." to one, which is what that sentence wants. 672px of
   40px display type is 28 characters -- well inside the range headings tolerate,
   and 111px short of the 783px left track, so nothing crowds the lead.

   Between 1150 and roughly 1300 the cap stops mattering: the 1.15fr left track
   is 415px there, narrower than any of these values, and [01] [02] [03] set
   three lines regardless. That is the track talking, not the cap. */
main:has(.hero--lede) .section-head--doc .sh { max-width: 28ch; }
/* [02] is the one heading whose break has to be authored. text-wrap: balance
   minimises the longest line, and for "One infrastructure layer. Six
   applications." the most even two-line split is 18/24 -- "One infrastructure" /
   "layer. Six applications." -- which cuts mid-sentence and opens line two on a
   word that belongs to line one. The split the sentence wants is 25/17, and
   because 25 > 24 no cap width can make balance prefer it: narrow enough to
   reject the 24 split is also too narrow to fit the 25 one. Turning balance off
   does not fix it either -- greedy wrapping lands "Six" on line one at 1920 and
   leaves "applications." as a widow, and it worsens the rag on [01] and [03].

   So the break is a <br class="sh-br"> in the markup, which is what an authored
   headline break is. Balance still runs on each side of it.

   It has to stop at the narrow end. Measured down the scale: 600 still sets the
   two authored lines -- so the query stops at 599, not 600 -- but 560 cannot fit
   "One infrastructure layer." and splits
   it, so the heading goes to three lines; 430 goes to four and 390 to five,
   against the four it set with no <br> at all. Below 600 the forced break stops
   buying a cleaner shape and starts adding lines, so it is switched off there
   and balance is left to wrap all six words as one run.

   The markup keeps a space before the <br>. Without it, display: none joins the
   two sentences into "layer.Six" -- the break was carrying the word gap. With
   the br showing, that space sits at the end of a line and collapses away. */
@media (max-width: 599px) { .sh-br { display: none; } }
/* The dead right column, on Home. The comment at .sec-split names this exact
   problem -- prose capped at 44rem inside a 1333px chapter leaves 589px, 44% of
   the row, with nothing holding it -- and .sec-split solves it on all seven
   other routes. Home never got it.

   This is not .sec-split though. That primitive grids the whole .wrap and steps
   the heading down to 2.4rem, which would undo the 48px set just above it. Here
   only .section-head--doc splits, and only where it actually has a lead to move:
   :has(> .lead) matches [01] [02] [03] [05] and skips [04] [06] [07], whose
   right columns are already filled by a .cmp, a .path and a .faq. Four of seven,
   which is exactly the four that have the hole.

   Both tracks are fractional, not a fixed left column. A fixed 40rem left --
   sized to the heading's own 22ch cap, which lands at 634px -- held its full
   width as the row narrowed and starved the right one: at a 1200px viewport the
   lead came out 336px and 40 characters per line, under the 50 floor and seven
   lines tall. 1.15fr / 1fr keeps the heading the wider of the two and lets both
   give way together. .sec-split's 30rem is not usable here either way: at 30rem
   a 48px heading breaks into four lines.

   The right track caps at 36rem instead of the lead capping itself. 36rem is the
   width at which 16px lands back on 70 characters per line -- measured on the
   [01] paragraph, 17px in 704px sets 70, inside the 50-75 band, while 16px in
   the same 704px sets 93, because a smaller face fits more characters in a fixed
   width: shrinking type lengthens the measure, it does not shorten it.

   The cap has to live on the track, not on the paragraph. A max-width on .lead
   inside a 1fr track let the track keep growing past the text and left 105px of
   dead column hanging off the right edge at 1920 and wider. Capping the track
   instead ends the column exactly where the text ends, and the slack goes to the
   heading on the left, where it is already absorbed by the .sh 22ch cap. Same
   measure, no hole. Below 1600 or so the track is under 36rem anyway and the cap
   never engages.

   Once the split collapses there is no track to cap, and the site-wide 44rem on
   .section-head--doc .lead takes over at 704px -- 93 characters per line at 16px.
   So 36rem comes back as a max-width inside the media query, which is the only
   place it is still doing work.

   The two gaps are set apart. .section-head--doc holds two .lead paragraphs, not
   one, so both land in column 2 and become two grid rows -- and a single `gap`
   gave the space between those paragraphs the same 72px meant for the space
   between the columns. row-gap: 1rem restores exactly the 16px margin the second
   paragraph had before the split. */
main:has(.hero--lede) .section-head--doc:has(> .lead) {
  display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 36rem);
  column-gap: clamp(2rem, 5vw, 4.5rem); row-gap: 1rem; align-items: start;
}
main:has(.hero--lede) .section-head--doc:has(> .lead) > .sh { grid-column: 1; }
main:has(.hero--lede) .section-head--doc:has(> .lead) > .lead {
  grid-column: 2; margin-top: 0; font-size: 1rem;
}
/* Collapses at 1150, not at .sec-split's 1000. Measured on the [01] paragraph:
   the split still holds 56 characters per line at a 1200px viewport, but by 1100
   the right track is 416px and the measure drops to 46, under the 50 floor,
   while the same paragraph stacked at full width sets 70. Below the breakpoint
   the split costs reading and buys nothing, so it stops. */
@media (max-width: 1150px) {
  main:has(.hero--lede) .section-head--doc:has(> .lead) { display: block; }
  main:has(.hero--lede) .section-head--doc:has(> .lead) > .lead {
    margin-top: 1rem; max-width: 36rem;
  }
}
/* Homepage: .sec-block carries its own fill, which is the same panel-stack
   problem one level up. Only a full-bleed block keeps a surface. */
.sec-block:not(.sec-full) { background: transparent; border-radius: 0; }

/* ---- chapter interval: 120 -> 240 on [01]-[06] (2026-08-20, owner's call) ----
   Six of the eight chapters take the new interval, so it is written as the
   default and the two that keep the old one are marked .chapter--tight. [07]
   was excluded by the owner; [08] is .final, which also carries a padding-TOP
   of 120 (it has no .sec-bar sibling, so `.sec-bar + section { padding-top: 0 }`
   never matches it) and leads straight into the footer, so it was left alone
   rather than changed without being asked.

   clamp(64px, 18vw, 240px) is the base `section { padding-block: clamp(64px,
   9vw, 120px) }` with the slope and the ceiling doubled and the FLOOR left
   where it was. Doubling the floor too would put 128px of empty ground between
   sections on a 390px phone -- a third of the viewport. The ramp reaches the
   old 120px at a 667px viewport and the new 240px at 1333px; below ~356px both
   sit on their floor. */
main:has(.hero--lede) > .chapter > section { padding-bottom: clamp(64px, 18vw, 240px); }
main:has(.hero--lede) > .chapter--tight > section { padding-bottom: clamp(64px, 9vw, 120px); }

/* ---- .chapter--band — [02], [04], [06] (2026-08-20) ----
   Home ran [01]-[07] as 7,354px of one unbroken colour. The alternating
   --sec-1 / --sec-2 device the comments describe had been switched off by the
   rule directly above, and --sec-1 is #0B0D11 against a #08090C canvas anyway,
   so all seven blocks computed rgba(0,0,0,0). Nothing separated anything.

   Full-bleed, and that is not a style choice. The rule above turns .sec-block's
   fill off because a filled block sitting inside a section full of filled cards
   reads as a box inside a box. A band that runs edge to edge has no left or
   right edge to read as a box, which is why it can carry a surface where an
   inset block cannot -- the same reason /product/'s six .sec-full bands work.

   Painted by a pseudo-element rather than on .chapter itself: .chapter's width
   is min(100% - 2 * --page-gutter, --chapter-max), and that measure is what
   every child sizes against. Widening the element to reach the viewport would
   take the content's left edge with it. The ::before breaks out instead and the
   chapter keeps its measure.

   left: calc(50% - 50vw) + width: 100vw is the same break-out .sec-full uses,
   which is the one this file has already proven does not raise a scrollbar
   (html, body carry overflow-x: clip).

   [02], [04], [06] and not [01], [03], [05]: [01] is 2,902px, 36% of the whole
   document and 5.6x [06]. Banding it makes the opening three screens of tinted
   ground and leaves the rest flickering. Left on the canvas it reads as the
   page's base state, and the bands become the interruptions. */
main > .chapter--band { position: relative; --band-inset: clamp(32px, 9vw, 120px); }
main > .chapter--band > * { position: relative; z-index: 1; }
/* Inset out the top and in at the bottom, not 0 / 0. Measured: a .chapter's box
   top lands exactly on its mark pill (the bar's top margin collapses out), and
   its box bottom lands one section padding-bottom past the last line of content.
   Drawn on the box the band would open flush against the pill and close 120px
   below the content -- tinted ground above the mark: 0px, below it: 120px.

   The interval between two chapters IS that padding, so half of it splits the
   rest space evenly and the band edge falls midway between two chapters instead
   of on top of the next one's first element.

   --band-inset is the chapter interval halved, term for term. It has to be
   re-halved whenever that interval moves: it was clamp(32px, 4.5vw, 60px)
   against the 120px interval, and became clamp(32px, 9vw, 120px) when [01]-[06]
   went to 240 above. Term-for-term matters -- both clamps hit their ceiling at
   the same 1333px viewport and their floor at the same 356px, so the halving
   holds at every width rather than only in the middle of the ramp.

   A flat number does not survive this. 60px was symmetric at 1512 and nowhere
   else: the interval is fluid, so by a 390px viewport it was 64px and a 60px
   inset left 4px of band under the content. */
main > .chapter--band::before {
  content: ''; position: absolute; z-index: 0;
  top: calc(-1 * var(--band-inset)); bottom: var(--band-inset);
  left: calc(50% - 50vw); width: 100vw;
  background: var(--sec-2);
}
/* …except inside a full-bleed chapter, where the block IS the band. On the
   homepage `.chapter--full` is what spans the viewport, so the surface belongs
   to the block it wraps rather than to the section itself. */
.chapter--full > .sec-block { background: var(--sec-1); border-radius: 0; }
.chapter--full > .sec-block--2 { background: var(--sec-2); }


/* ===========================================================================
   Chapter measure widened (2026-08-07)
   A band spans the viewport, but its content stayed at --maxw (1280). Measured
   on /product at a 2000px viewport: 720px of dead margin per band, 36% of the
   surface, while the plain chapters beside them wasted only 6%. The band read
   as a wide empty stage with a narrow column stranded in the middle.

   Both band and plain move together, to --measure-wide (1616 at 2000px). They
   have to: widening only the bands puts their chapter bar on a different left
   edge from every plain chapter's, which is the invariant this file has already
   had to repair twice. The measure is shared, so the edge is shared.

   Prose keeps its own reading cap inside that measure — a wider chapter is more
   room for blocks and art, not a longer line of body text.
   =========================================================================== */
main > section:has(> :is(.wrap, .narrow) > .sec-chrome) {
  width: min(100% - 2 * var(--page-gutter), calc(var(--measure-wide) + 2 * var(--gutter)));
}
main > section.sec-full:has(> :is(.wrap, .narrow) > .sec-chrome) {
  width: 100vw;
}
/* 100vw, not 100%. The parent differs — a band is 100vw wide, a plain chapter
   is narrower — so a percentage resolves to a different number in each and the
   two wraps centre on different left edges. Anchoring to the viewport makes
   both compute the same width, which is what keeps the chapter bars on one
   shared edge. */
main > section:has(> :is(.wrap, .narrow) > .sec-chrome) > :is(.wrap, .narrow) {
  width: min(100vw - 2 * var(--page-gutter) - 2 * var(--gutter), var(--measure-wide));
  max-width: none; margin-inline: auto;
}
main > section:has(> :is(.wrap, .narrow) > .sec-chrome) :is(.lead, .hero__note, p.lead) {
  max-width: 46rem;
}

/* ===========================================================================
   /product/ chapter heading scale (2026-08-20)

   The comment above `main:has(.hero--lede) .section-head--doc .sh` said moving
   /product/ was "a separate pass". This is that pass.

   Measured before: the thirteen chapter headings on /product/ rendered at two
   different sizes for one rank. Eleven sat at 60px (the global
   clamp(2.25rem, 5vw, 3.75rem) hitting its cap at 1512), and [08] Whitelabel
   and [10] Senti Inside sat at 38.4px, because `.sec-split .sh` carries its own
   clamp(1.6rem, 2.6vw, 2.4rem) and lands later in the file at equal
   specificity. A 22px gap between two headings of the same rank reads as two
   ranks. Home had already been brought to 40px; /product/ never was, so the
   same component was also 20px apart between the two routes.

   One rule fixes both: main.page-product is 0,3,1, which outranks the 0,2,0 of
   `.sec-split .sh`, so the split sections come down with the rest. All thirteen
   now measure 40px.

   The `<main class="page-product">` hook exists because the comment at
   .sec-chrome badges is still true -- there was no page class to hang this on.
   Home's `:has(.hero--lede)` works but only by accident of that page owning a
   lede; /product/ has no equivalent tell. An explicit class is greppable and
   does not break when the hero changes.

   28ch, not the inherited 22ch: 22ch was measured against 60px type and is
   528px, which at 40px throws seven of thirteen headings to three lines. Swept
   22/24/26/28/30/32ch at 1512, 1440 and 1280 -- identical results at all three,
   because the cap binds before the track does. 26ch and 28ch give the same
   distribution (nine headings at two lines, three at three, one at one) and 28
   is the value Home already carries, so the site holds one number instead of
   two. 30ch trades [09] down to two lines but sends [13] to a lone one-liner:
   same four outliers, arranged worse.

   [08] and [10] stay at three lines at every cap. Their heading sits in the
   .sec-split left track, minmax(0, 30rem) = 480px, which is narrower than 28ch
   (672px) -- the track is talking, not the cap, and that is the split layout
   working as intended.

   margin-bottom follows the type down for the same reason it did on Home: the
   global clamp(2.25rem, 4.5vw, 3.5rem) is 56px at 1512, which was proportioned
   under a 60px heading. */
main.page-product .section-head--doc .sh {
  font-size: clamp(2.25rem, 5vw, 2.5rem);
  max-width: 28ch;
}
main.page-product .section-head--doc { margin-bottom: 2.5rem; }

/* Five .panel blocks on /product/ carried their own margin-top inline, at two
   values with nothing distinguishing them: [03] and [09] are both .panel--art
   and sat at 1.75rem and 2.5rem. The comment above the
   `:is(main > section, .sec-block):not(.sec-split)` block already called this
   out -- "the inline margins are what made it look handled" -- and the rule it
   introduced, clamp(1.75rem, 3vw, 2.75rem), was being shadowed on exactly the
   nine elements that had an inline value. Deleting the inline styles was the
   whole fix; all nine now measure 44px, the same interval .sec-chrome leaves
   under itself. No new rule is needed here, and adding one measured 44px
   anyway. */

/* ===========================================================================
   /product/ takes Home's chapter composition (2026-08-20)

   Owner's call: chrome and head only. The six .sec-full bands stay. The
   right-hand counter was kept in the first pass and hidden in a second one
   (2026-08-20, owner's call) -- see section 1 below.

   Measured difference before this block, at 1512:

     chapter mark   pill on a plate          bare text on a 1px hairline
     bar -> heading 24px                     44px
     chapter head   two columns 687/576      one column
     lead           16px on 576px            17px on 736px
     inner measure  1335px                   1287px  (left alone -- see 4 below)
   =========================================================================== */

/* ---- 1. the mark becomes a badge, and the counter is hidden ----
   The first pass kept the counter and gave it the mark's own pill, so the bar
   read as two objects of one kind rather than one object and one loose string.
   That answered Home's objection rather than dodging it, and the argument for
   keeping it was that /product/ is thirteen chapters against Home's eight.

   Hidden anyway on the owner's call, 2026-08-20. The case against it was the
   stronger one and it is worth recording: the counter is not actionable -- "13"
   does not lead anywhere -- and `@media (max-width: 560px)` has been hiding it
   from every mobile reader since the layer shipped, with nobody reporting the
   loss. A thing whose absence goes unnoticed by half the audience is furniture,
   and this page already carries the document's voice in the [01] pill.

   display: none, matching Home, so it leaves the accessibility tree too. A
   screen reader announcing a count nothing else refers to is noise.

   The pill rule below still names .sec-chrome__r, and the hide rule sits AFTER
   it rather than here. Both are deliberate. Keeping __r in the pill selector
   means the counter is one line from coming back as the mark's twin instead of
   as the loose text this page had before; and the pill rule is (0,4,1), so a
   short `main.page-product .sec-chrome__r` at (0,2,1) loses to it and the
   counter stays visible -- which is exactly what the first attempt did. The hide
   rule matches that shape to tie on specificity and wins on order. */
/* The selector repeats the :has() shape of the rule it is overriding. That rule
   -- `main > section:has(> :is(.wrap, .narrow) > .sec-chrome) > :is(...) >
   .sec-chrome` -- computes (0,4,2), and a plain `main.page-product :is(.wrap,
   .narrow) > .sec-chrome` is only (0,3,1). The first draft here used the short
   form: the pill and the 24px landed, and the hairline and its .8rem of padding
   did not, which is the worst of both -- a badge sitting under a rule that was
   supposed to be replaced by it. Prefixing the long form with .page-product
   gives (0,5,2) and settles it. */
main.page-product > section:has(> :is(.wrap, .narrow) > .sec-chrome)
  > :is(.wrap, .narrow) > .sec-chrome {
  border-top: 0; padding-top: 0; align-items: center;
  margin-bottom: var(--gap);
}
main.page-product :is(.wrap, .narrow) > .sec-chrome > :is(.sec-chrome__l, .sec-chrome__r) {
  display: inline-flex; align-items: center; gap: .45rem;
  /* line-height: 1, and the vertical padding is asymmetric on purpose.
     The pill inherited 1.65 from body -- a prose leading, on a one-line label,
     which is how it got a 19.8px line box around 12px type. Measured 2026-08-20
     at 1512: ink 9.63px from the top of the pill and 12.07px from the bottom, so
     the label sat 2.44px high inside its own plate.

     Half of that is the leading; the other half is the font. The label is
     uppercase, and JetBrains Mono reserves 4px of descent at 12px that capitals
     and digits never use -- the box is built for a "g" that is not there. Any
     symmetric padding therefore centres the BOX and leaves the INK high, which
     is what the eye actually reads.

     So: line-height 1 gives the label a 12px line box, and 10px/9px lands the ink
     at 11.04 above / 10.68 below -- 0.36px LOW, from 2.44px high. 10/9 is the
     best round pair: exact centring wants 9.82/9.18, and 9/10 overshoots to
     1.64px the other way. 0.36px is a third of a device pixel at 1x and is not
     worth a fractional value. These two numbers are optical compensation for
     JetBrains Mono at 12px specifically -- they are px rather than rem because
     they are tied to that metric, not to the root scale, and changing the pill's
     font-size means re-measuring them. */
  line-height: 1;
  padding: 10px .7rem 9px;
  border: 1px solid var(--border); border-radius: 9999px;
  background: color-mix(in srgb, var(--surface) 55%, transparent);
  font-size: .75rem; letter-spacing: .08em;
}
main.page-product .sec-chrome__ix { margin-right: 0; }
main.page-product :is(.wrap, .narrow) > .sec-chrome > .sec-chrome__r { display: none; }

/* ---- 2. the chapter head becomes two columns ----
   Same 1.15fr / 1fr, 72px column-gap and 16px row-gap as Home. Seven of the
   thirteen heads land here, so :has(> .lead) is doing real selection, not
   decoration -- [11] [12] [13] carry no lead and stay in one column, which is
   the same behaviour [04] and [07] have on Home. ([02] used to be in that group
   and joined this one when its four paragraphs became one.)

   :not(.sec-split) is kept for the rule's own safety, but as of 2026-08-21 no
   chapter on this route is .sec-split any more. [08] and [10] were, and were
   converted (owner's call) so their head splits like every other chapter's and
   their panel runs the full measure. The reason they had to convert rather than
   simply gain an in-panel illustration: a .sec-split panel sits in the right
   track, so it is 904 / 639 / 430 / 279px at 2560 / 1512 / 1280 / 1100, and a
   65/35 split inside it left the prose at 74 / 51 / 34 / 22 characters a line.
   A third column inside a box that is already inside a column is not a split,
   it is a sliver. Full width, then split once -- the way [03]-[07] and [09] do
   it -- gives the same 1191px panel and the same 65/35 that was signed off. */
main.page-product section:not(.sec-split) > :is(.wrap, .narrow)
  > .section-head--doc:has(> .lead) {
  display: grid; grid-template-columns: 1.15fr 1fr;
  column-gap: clamp(2rem, 4.75vw, 4.5rem); row-gap: 1rem;
  align-items: start;
}
main.page-product section:not(.sec-split) > :is(.wrap, .narrow)
  > .section-head--doc:has(> .lead) > .sh { grid-column: 1; grid-row: 1 / -1; }
/* The lead fills its track (owner's call, 2026-08-21). It used to stop at 36rem
   inside it, which is invisible up to about a 1540px viewport -- below that the
   track is narrower than the cap and the cap never applies -- and then opens a
   hole on anything wider. Measured at 2560: the track is 688px and the lead was
   576, so it stopped 112px short, and with the heading capped at 28ch in its own
   791px track the whole head block ended 232px inside the chapter's right edge
   while every block below it ran the full measure.

   The cost is line length on wide screens: 70ch at 1512, 84ch at 2560, against
   the 50-75 band. Below 1540 nothing changes at all. The 28ch cap on .sh is
   untouched -- a heading is display type and stays where it is. */
main.page-product section:not(.sec-split) > :is(.wrap, .narrow)
  > .section-head--doc:has(> .lead) > .lead {
  grid-column: 2; max-width: none; margin-top: 0;
}
@media (max-width: 1000px) {
  main.page-product section:not(.sec-split) > :is(.wrap, .narrow)
    > .section-head--doc:has(> .lead) { display: block; }
  main.page-product section:not(.sec-split) > :is(.wrap, .narrow)
    > .section-head--doc:has(> .lead) > .lead { max-width: 36rem; }
}

/* ---- 3. the chapter lead comes down to 16px ----
   17px in a 736px column was 93 characters a line, well past the 50-75 band.
   16px in the 576px right track is ~75. Shrinking the face alone would have made
   it worse -- a smaller face fits more characters in a fixed width -- so the
   size and the track move together or not at all. */
main.page-product .section-head--doc .lead { font-size: 1rem; }

/* ---- 4. the measure is NOT changed ----
   The first draft of this block set `--block-max: var(--measure-wide)` to pull
   /product/'s wrap out to Home's 1335px inner. It is a no-op: the wrap is sized
   by `min(100vw - 2 * --page-gutter - 2 * --gutter, --measure-wide)` at the foot
   of this file, and 1351px wins that min long before --block-max is consulted.
   /product/'s inner measure is 1287, Home's is 1335, and the 48px is the extra
   32px of wrap padding Home's chapter shell does not spend.

   Left as-is after measuring, because the 48px changes nothing worth chasing:

     head columns   650 / 565   (Home: 687 / 576)
     lead measure   ~71 characters a line at 16px, inside the 50-75 band
     .sh line count 2 2 2 2 2 2 2 3 3 3 1 2 2 -- identical to the 28ch sweep

   The .sh 28ch cap (672px) does sit outside the 650px track, so on /product/ the
   track wraps the heading and on Home the cap does. Same result at every width
   swept, so this is a fact to know rather than a defect to fix. */

/* ---- [01] on /product/: argument left, illustration right (2026-08-20) ----
   Owner's call: the subtext moves under the headline in the left column and the
   right column is reserved for an illustration.

   .sec-split already is this shape -- it sends .section-head to column 1 and
   every other direct child of .wrap to column 2 -- so [01] becomes a split
   rather than growing a second grid of its own. Two consequences fall out for
   free and are both wanted here: the two-column HEAD grid does not apply
   (it carries :not(.sec-split)), so the heading and both .lead paragraphs stack
   in one column, which is exactly the ask; and .sec-chrome still spans 1 / -1,
   so the chapter bar keeps its full width and its two pills stay on the page's
   outer edges.

   The ratio is overridden from .sec-split's own minmax(0, 30rem). 30rem is 480px
   and was measured for [08] and [10], where the left column holds a heading and
   one short lead. [01] holds a 40px heading plus two paragraphs of body, and at
   480px that column runs long while the right one holds a single image. 1.15fr /
   1fr is not a new number either -- it is the same ratio the head grid uses on
   [03]-[09], so [01]'s column boundary lands on the same vertical as theirs
   rather than introducing a third edge into the page. */
main.page-product #product-intro > .wrap {
  /* Owner's call 2026-08-20, third revision, and the one that settles it: the
     left COLUMN is the width of the text block inside it -- 36rem, the same cap
     the .lead already carries -- and the right column takes what is left for a
     product mockup of comparable size.

     The two earlier attempts both left the column and the text disagreeing.
     1.15fr / 1fr gave a 650px column holding a 576px paragraph, so 74px of the
     track was measure-shaped hole. .cx__split's 38/62 gave 465px, which fit the
     text only by squeezing it to 58 characters and threw the heading to three
     lines where every other chapter on the page sets two. A fixed 36rem ends the
     argument: the column IS the measure, so there is no leftover to explain and
     nothing is being compressed to hide one.

     Measured at 1512: 576px left, 64px gap, 647px right. The mockup is the
     wider of the two by 71px, which is what "similar size" wants -- an image
     needs a little more room than a paragraph to read at the same weight. */
  grid-template-columns: 36rem minmax(0, 1fr);
  column-gap: clamp(1.5rem, 4.4vw, 4rem);
}
/* The lead has to be capped, not left to fill the column. The head grid on
   [03]-[09] caps its right track at 36rem and lands the lead on ~71 characters;
   here the paragraphs sit in the 650px LEFT track instead, where the same 16px
   ran 81 characters a line -- past the top of the 50-75 band. 36rem is the same
   number, so every .lead on the page now measures 576px whichever column it is
   standing in. The ~74px it leaves inside the track is measure, not a hole: the
   heading above it runs the full 650. */
main.page-product #product-intro .section-head--doc .lead { max-width: 36rem; }
/* The chapter mark joins the text column instead of spanning both (owner's call,
   2026-08-20). Everywhere else on the site the bar runs the full content width,
   which is right when it labels a whole chapter; here it labels the argument on
   the left, and the mockup on the right is that argument's evidence, not another
   thing the bar is introducing. Hiding the counter is what made this possible --
   a lone 121px pill was holding open 1287px of bar.

   Explicit placement, all three items, because auto-flow gets it wrong: with the
   mark moved out of `1 / -1` the mockup would land in row 1 beside the pill and
   push the heading to row 2 underneath it.

   The mockup spans both rows and centres in them. It is the shorter of the two
   now -- 365px against 33 + 24 + 353 = 410 for the mark and text stacked -- so
   the centring is on the image, which is what has slack to give. */
/* The mark now lives INSIDE .section-head--doc (owner's call, 2026-08-20) -- one
   block holding pill, heading and both leads, rather than two grid rows that
   happen to share a column.

   The cost is that eleven rules in this file key off `:has(> :is(.wrap, .narrow)
   > .sec-chrome)`, and a nested mark satisfies none of them. Those eleven give a
   chapter its width, its band background, its band padding and its hairline
   reset. Loosening the child combinator to a descendant one would have fixed it
   in a single sed across all ten routes -- and would have changed how every
   chapter on the site resolves its own layout to suit one block. Restating the
   four declarations [01] actually loses is the smaller blast radius, and it is
   verifiable: the values below were read off the computed style before the move,
   not derived.

   The band itself has since moved off [01] and onto [02] (owner's call,
   2026-08-21): the chapter mark, the heading and the mockup are the page's
   opening, and a surface under them competed with the hero directly above.
   [02] The constraint is the first chapter that argues something, so it is the
   first place the page should change register. [01] therefore keeps neither the
   background nor the band padding -- it is a plain chapter now, on the base
   `section` rhythm -- and `.sec-full` came off its class list with them. Only
   the wrap width still needs restating, since that rule is behind the :has()
   the nested mark cannot satisfy. */
main.page-product #product-intro > .wrap {
  width: min(100vw - 2 * var(--page-gutter) - 2 * var(--gutter), var(--measure-wide));
  max-width: none; margin-inline: auto;
}
/* The pill rules key off `.wrap > .sec-chrome` for the same reason, so they need
   the nested position naming too. Same values, one level deeper. */
main.page-product #product-intro .section-head--doc > .sec-chrome {
  border-top: 0; padding-top: 0; align-items: center;
  margin-bottom: var(--gap);
}
main.page-product #product-intro .sec-chrome > :is(.sec-chrome__l, .sec-chrome__r) {
  display: inline-flex; align-items: center; gap: .45rem;
  line-height: 1;
  padding: 10px .7rem 9px;
  border: 1px solid var(--border); border-radius: 9999px;
  background: color-mix(in srgb, var(--surface) 55%, transparent);
  font-size: .75rem; letter-spacing: .08em;
}
main.page-product #product-intro .sec-chrome > .sec-chrome__r { display: none; }
/* center, and the mark rides with it: .sec-chrome is inside .section-head--doc
   now, so the pill, the heading and both leads are one grid item and one
   alignment decision. That is the point of having moved it in.

   Measured at 1512/1440/1280/1100: this changes nothing today. The text block is
   410px against the mockup's 365 and shrinking -- it is the taller item at every
   width, so it sizes the row and has no slack to centre in. The mockup is what
   centres, and it already did.

   Kept anyway because the two are one row and either can be the tall one. Crop
   the screenshot taller -- which was on the table when Home's 480px panel came
   up -- and the block starts centring against it with nothing more to change. */
main.page-product #product-intro > .wrap > .section-head--doc {
  grid-column: 1; grid-row: 1; align-self: center;
}
/* `span 2`, not `1 / -1`. This grid has no grid-template-rows, so every row is
   implicit and -1 resolves to the first line of the (empty) explicit grid rather
   than to the end of the flow. `1 / -1` therefore collapsed to a single row: the
   mockup sized row 1 to its own 365px and the heading dropped underneath it,
   which made the section 1022px instead of 726. */
/* One row now, not two: the mark moved into the head, so the left column is a
   single grid item again and `span 2` would reach into a row that does not
   exist. */
main.page-product #product-intro > .wrap > .intro__mock {
  grid-column: 2; grid-row: 1; align-self: center;
}
/* Corner to 0 (owner's call, 2026-08-20). .browser hardcodes 10px 10px 0 0 and
   was deliberately exempted from the 2026-08-19 square-corners pass -- the
   comment at --r reads "it is a picture of the real product's UI; flattening it
   would make the mockup wrong". That exemption still stands everywhere else:
   this is scoped to [01], and the same .browser keeps its 10px in [06], [09],
   the hero, and on six other routes. */
main.page-product #product-intro .browser { border-radius: 0; }
/* Equal to the column above 1000px, so it looks redundant there. It is not: the
   grid collapses below 1000px and the site-wide 44rem would take over. */

/* .device carries clamp(2.5rem, 5vw, 4.5rem) of top margin, written for a
   mockup that follows hero copy in normal flow. In this grid the row already
   starts where the heading starts, and that margin would drop the mockup 72px
   below the first line of the headline it sits beside. */
.intro__mock { margin: 0; }
@media (max-width: 1000px) {
  /* Once the split collapses the mockup is below the argument rather than beside
     it, so it keeps .device's own opening interval back. */
  .intro__mock { margin-top: clamp(2rem, 5vw, 3rem); }
}

/* ===========================================================================
   /product/ bands stop breaking their blocks out of the wrap (2026-08-21)

   `.sec-full:not(.sec-split) > .wrap > :is(.cmp, .cards, .tiers, .bm-grid,
   .panel)` widens a block to `100vw - 2 * --page-gutter` and re-centres it on
   the viewport. The wrap is `100vw - 2 * --page-gutter - 2 * --gutter` and holds
   --gutter of padding inside that, so a broken-out block lands 4 * --gutter
   wider than the wrap's CONTENT box. Measured on /product/: 64px past the text
   on each side at 1512 and 1280, 32px at 2560.

   The blocks were never clipped -- they were sitting on the chapter's own inline
   padding, with their left edge missing the chapter bar, the heading and every
   paragraph in the chapter. This file has had to repair that same misalignment
   twice before; the comment at `.sec-full > .wrap` calls a shared left edge "the
   invariant this file has already had to repair twice". The break-out is a third
   way to lose it.

   Seven blocks on three chapters: [04] Fleet Control (.cards, .cmp, .panel),
   [06] Create EA (.cards, .panel), [12] Product boundary (.cmp, .panel). [02]
   was the eighth and was fixed by id first; that rule folds into this one.

   width: auto lets each block fill the content box instead, which puts its left
   edge back on the one this file calls the chapter's edge. The blocks' own caps
   (`.cmp`'s max-width: 60rem, `.panel`'s 56rem) are already off -- the
   chapter-edge block at `main > section > .wrap > :is(.panel, .cmp, ...)` clears
   them -- so auto here means the full measure, not a narrower one.

   Route-scoped, not global: the same break-out runs on /proof/ and four
   /industries/ routes, and whether it is wanted there is a separate review. */
main.page-product section.sec-full:not(.sec-split) > :is(.wrap, .narrow)
  > :is(.cmp, .cards, .tiers, .bm-grid, .panel) {
  width: auto; margin-left: 0; transform: none;
}

/* [02]'s closing paragraph runs the full measure (owner's call).

   `main > section:has(...) :is(.lead, ...)` caps every lead on a chapter at
   46rem so prose keeps a reading measure inside a 1616px chapter. This one is
   the chapter's summation rather than its argument -- it follows the table and
   restates what the table just showed -- and at 46rem in a band that wide it
   read as a half-filled column with the right half empty.

   It was two paragraphs until 2026-08-21; the deployment-caveat sentence was a
   one-line orphan under a five-line block, and it is the same clause [01]
   already carries, so it folded into the sentence it was qualifying rather than
   holding a row of its own.

   The trade is real and is the owner's to make: at 2560 the measure is ~1568px,
   which is roughly 200 characters a line against the 50-75 band. Everything
   else on the page keeps its cap; this is one paragraph, by id. */
main.page-product #the-constraint > .wrap > .lead--full,
main.page-industry section > :is(.wrap, .narrow) > p.lead.lead--full { max-width: none; }

/* .panel--full: trong panel này, padding của panel LÀ measure.

   /industries/earning-platforms/ [05] Best fit (owner's call, 2026-08-24 --
   chủ dự án chọn cho chữ chạy hết bề ngang thay vì thêm một hình vẽ). Đo ở
   1512 trước khi sửa: panel 1287px, hộp nội dung 1191px, .checks dừng ở 736px
   (46rem) và .panel__emph dừng ở 704px (44rem).

   Hai cap đó có lý do của chúng khi danh sách nằm TRẦN trên nền chương: ở đó
   không có gì khác giữ measure, và một dòng chạy suốt 1287px là 160 ký tự.
   Trong một panel thì đã có padding 48px hai bên làm việc đó, nên cap chỉ còn
   để lại một nửa phải trống. Cùng lập luận đã dùng cho .faq__a và .lead--full.

   Modifier chứ không phải `.panel:not(.panel--art)`: dạng phủ định sẽ với sang
   panel Commercial model của Quant [06], nơi .panel__emph theo sau một danh
   sách và không ai yêu cầu đổi. Panel nào muốn thì tự nhận class.

   (0,2,1) so với (0,1,0) của `.checks` và `.panel__emph`. `> *` chứ không liệt
   kê tên: mọi thứ trong panel đều nhận cùng một mép, kể cả cấu kiện thêm sau. */
main.page-industry .panel--full > * { max-width: none; }

/* .stats__note--full: cùng ý với .lead--full và .panel--full, cho .stats__note.

   /industries/strategies-devs/ [07] Commercial model (owner's call,
   2026-08-24). Đo ở 1512 trước khi sửa: đoạn dừng ở 736px (46rem) và CĂN GIỮA,
   nằm x388 trong một chương rộng 1288px — một cột hẹp lửng lơ giữa bảng .tiers
   full width phía trên và panel Proof snapshot phía dưới.

   Bỏ luôn `text-align: center` chứ không chỉ nới cap. Căn giữa là thứ có lý khi
   đoạn hẹp: nó tự thành một chú thích dưới lưới số, đúng vai .stats__note sinh
   ra để đóng. Kéo dài 1288px mà vẫn căn giữa thì mỗi dòng lại lệch mép một kiểu
   và không còn mép trái để bám. Ba đoạn .path__note khác trên chính route này
   đều `text-align: start`; đoạn này về cùng một mép với chúng.

   Là modifier chứ không phải rule cho mọi .stats__note: bản dưới hero — "These
   figures show infrastructure activity" — vẫn phải hẹp và căn giữa, vì nó đúng
   là chú thích của lưới số. Cùng file này còn hai bản nữa ở các route khác. */
main.page-industry :is(.wrap, .narrow) > .stats__note--full {
  max-width: none; margin-inline: 0; text-align: left;
}

/* Ở đây từng có `.page-industry--rmark`, một modifier bỏ lệnh ẩn
   `.sec-chrome__r` cho /industries/vps-providers/ — route duy nhất mà dấu chương
   bên phải là chữ biên tập ("/ 6 sources of load", "/ 4 fits · 3 misfits") chứ
   không phải bộ đếm `/ 04 : 11`.

   Chủ dự án xem rồi và quyết định bỏ luôn mười một dòng đó (2026-08-24), nên
   modifier hết việc và được gỡ thay vì để lại làm CSS chết. Ghi lại ở đây vì
   thông tin đáng giữ là: markup của route đó VẪN mang mười một dòng chữ ấy, chỉ
   bị item 3 ẩn đi. Muốn bật lại thì đây là hình dạng của rule, và nó cần
   `section` trong chuỗi để ra (0,4,2) — hoà specificity rồi thắng bằng source
   order sẽ vỡ nếu ai đó chuyển khối này lên trên file. */

/* Cùng quyết định đó, lần thứ hai — /industries/earning-platforms/ [11], câu
   tổng kết đứng sau bảng .opts (owner's call, 2026-08-24). Nối vào selector
   list thay vì viết rule mới: cùng một lý do thì nên có một chỗ để đọc.

   Không ghim theo id như bản /product/ vì chương này không mang id, và nhánh
   .page-industry chỉ mở cho những đoạn ĐÃ tự nhận .lead--full trong markup —
   nên nó không phải là "bỏ cap cho cả route", mà vẫn là từng đoạn một.

   Nhánh này dài hơn nhánh /product/ vì phải thắng đúng một rule, và lần đầu
   tôi viết hụt. `main > section:has(> :is(.wrap, .narrow) > .sec-chrome)
   :is(.lead, .hero__note, p.lead)` không phải (0,2,2) như tôi tưởng:

     - `:has()` mang specificity của CẢ relative selector bên trong, không phải
       của một compound trong đó -- `:is(.wrap, .narrow)` cộng `.sec-chrome` là
       (0,2,0), chứ không phải (0,1,0);
     - `:is(.lead, .hero__note, p.lead)` lấy argument mạnh nhất là `p.lead`,
       tức (0,1,1), chứ không phải (0,1,0).

   Cộng lại là (0,3,3). Bản viết hụt của tôi là (0,3,1) và trình duyệt xử thua
   đúng luật; đo lại bằng CSS.getMatchedStylesForNode mới thấy. Bản đang dùng là
   (0,4,3) -- thêm `section`, `p` và `.lead` -- nên thắng bằng specificity chứ
   không phải bằng source order, thứ sẽ vỡ nếu ai đó chuyển khối này lên trên. */

/* ===========================================================================
   /product/ panel typography (2026-08-21)

   Owner's call, reviewed on [03] and applied to all eight .panel blocks on the
   route so the four .panel--art boxes and the four plain ones stay one
   component. The other four .panel blocks on the site (/proof/ x1,
   /industries/vps-providers/ x2, /industries/earning-platforms/ x1) are
   untouched and keep 11 / 32 / 17.

   All three values already exist in this file. None of this introduces a new
   step on the type scale -- it moves the panel onto steps the page is already
   using, which is why it reads as three separate edits and lands as one.
   =========================================================================== */

/* 11px -> 12px. `.hero--ladder .eyebrow` made this move already (see the
   comment at that rule), so /product/ was running its hero label at 12 and its
   six panel labels at 11 -- one label style at two sizes on one page. Scoped to
   the route rather than to .panel so the seventh eyebrow, the one over the
   pause/stop/disconnect table in [04], comes with them; it is the same label
   doing the same job outside a box. */
main.page-product .eyebrow { font-size: .75rem; }

/* clamp(1.4rem, 2.5vw, 2rem) -> 1.5rem. At 32px the panel headline was within
   4px of the chapter heading above it (clamp(2.25rem, 5vw, 2.5rem), 36-40px),
   so the box read as a second chapter rather than as the answer inside one. At
   24px the gap is 12-16px and the order is unambiguous.

   Flat, not a clamp: the old ceiling was the only part doing work. Its floor,
   22.4rem-worth at 896px and below, is 1.6px from the new fixed value, so a
   clamp here would be three numbers describing a straight line. */
main.page-product .panel h2 { font-size: 1.5rem; }

/* 17px -> 16px. .panel__sub and .panel__list li never set a size, so they were
   inheriting body's 17px inside a box whose own .panel__emph is 1rem and whose
   chapter lead came down to 1rem in the /product/ pass. Three kinds of prose in
   one component, two of them already at 16. */
main.page-product .panel__sub,
main.page-product .panel__list li { font-size: 1rem; }

/* ---- the panel that is nothing but its caveat ----
   Two panels on the route hold a single .panel__emph and nothing else: [04]
   Fleet Control ("Fleet Control is the operating surface...") and [12] Product
   boundary ("Funds remain at the user's broker..."). :first-child is the honest
   selector for that -- it is the structure, not a pair of ids -- and it leaves
   [06] [08] [10] alone, where .panel__emph follows a list inside the box and its
   top margin is a real separator between two things.

   margin-top 1.75rem -> 0 (owner's call, 2026-08-21). .panel__emph's 28px is
   written for an emph that closes a panel after a heading, a lead and a list.
   When the emph IS the panel there is nothing above it to be separated from, so
   the 28px stacked straight onto the panel's own 48px of padding-top and put
   the first line 76px below the box edge with 48px below it -- a box whose text
   sat visibly high in it. The panel's padding is the spacing; the margin was a
   second one for a job already done.

   max-width 44rem -> none. At 1512 the text ran 704px inside a 1191px box and
   stopped 487px short of the right edge, which reads as a half-empty callout
   rather than as a full-width note. Same call as [02]'s closing paragraph.

   The measure cost is the owner's to make, and it is worth writing the measured
   numbers rather than the expected ones. [04] is the short of the two: 2 lines
   at 90 characters at 1512, and at 2560 it stops wrapping at all -- one line of
   180 characters. [12] runs 3 lines at 58ch at 1512 and 2 at 87ch at 2560. Both
   are caveats rather than reading copy, which is why the call goes this way here
   and nowhere else on the page; [04] on a wide screen is the one to look at
   again if a single 180-character line reads as a stretched-out rule. */
main.page-product .panel > .panel__emph:first-child {
  margin-top: 0;
  max-width: none;
}

/* ===========================================================================
   /product/ .panel--art: the text column becomes a declared column (2026-08-21)

   Reported from DevTools on [03]: the text boxes overlay the illustration.
   They do. Measured at 1512 -- panel content box 1191px, float text column
   754px, and EVERY prose child reporting a 1189px box. That is not a bug in
   the float, it is what a float is: it shortens LINE boxes, never block boxes.
   So the boxes have always run the full width and only the lines stopped.

   The consequence is worse than the inspector overlay, and it is visible
   without one. A float only shortens the lines it is vertically beside. The
   drawing is 312px tall; every line below that runs the whole panel. Measured
   at 1512, .panel__emph ran 1160px -- about 157-160 characters a line --
   directly under a paragraph capped at 754px by the float. (The figures first
   written here, 126 and 154, came from a counting method later found broken:
   characters / getClientRects().length counts line FRAGMENTS, so inline
   elements inflate it. The float state is gone and cannot be re-measured; the
   range above is derived from the current 102-104 ch at 753px.) One box,
   two measures, decided by where a paragraph happened to fall.

   Fixing this means declaring the column instead of inferring it. The float
   stays (the comment at .panel__art still holds: as a grid the art has to span
   every row and its height then opens a hole between the label and the
   heading); it just stops being the only thing that knows how wide the text is.

   SUPERSEDED -- this paragraph describes a 36rem draft that the 65/35 rule
   below replaced, and its "70-77 characters" was never true of what shipped.
   The rule that shipped measures 102-104 ch at 1512 and 125-128 at 2560. Left
   in place only because the reasoning about why the column has to be DECLARED
   still holds; the numbers in it do not.

   Art: the remainder, floored at the old min(38%, 26rem) so it never shrinks
   below what it is today, and capped at 34rem so it cannot take over the box.
   At 1512 the remainder is what grows it 416 -> 544: the width the prose gives
   back goes to the drawing rather than to a 177px gutter of nothing between
   them. At 1280 and below the floor is what applies and the art is unchanged.

   The bleed term is .panel__art's own margin-right: -1.5rem. The float's text
   column is content - (art + margin-left + margin-right), and margin-right is
   negative, so it comes back as + var(--art-bleed). Both formulas below are
   that same expression; keeping them literally identical is what makes the
   prose stop exactly at the float's own edge instead of near it.

   2026-08-21, later the same day: /industries/solo-traders/ [01] took this
   component too, so the five selectors below name both page classes rather than
   being copied. The --art-* formulas are the coupling this block spent the most
   words getting right; two copies of them would drift the first time one is
   touched. Everything measured above was measured on /product/ and is left as
   written -- the numbers are that route's, the reasoning is the component's. */
main.page-product .panel--art,
main.page-industry .panel--art {
  --art-gap: clamp(1.5rem, 3vw, 2.75rem);
  --art-bleed: 1.5rem;
  /* .panel's own `padding: clamp(1.75rem, 4vw, 3rem)`, restated because the
     drawing is positioned against the PADDING box and has to convert back to
     the content box the prose measures against. Keep the two in step. */
  --art-pad: clamp(1.75rem, 4vw, 3rem);
  /* 65 / 35, owner's call 2026-08-21 (70/30 first, then 65/35). The split is of
     the panel's CONTENT box, and the two tracks plus the gap close on it
     exactly:
       (65% - gap + bleed) + gap + 35% - bleed = 100%.
     The bleed term is .panel__art's own margin-right: -1.5rem, so it has to
     appear in both formulas or the prose stops 24px short of the float's real
     edge instead of on it. */
  /* Not a bare 35%. The drawing is absolutely positioned (see below), so its
     percentage resolves against the panel's PADDING box while the prose's
     resolves against the CONTENT box -- 1287px against 1191px at 1512, which
     would have made "35%" mean 450px on one side of the split and 417px on the
     other. Subtracting the padding first puts both percentages on the same
     denominator, so 65/35 is 65/35 on both sides. */
  --art-w: calc((100% - 2 * var(--art-pad)) * .35);
  --art-text: calc(65% - var(--art-gap) + var(--art-bleed));
  position: relative;
}

/* The drawing centres on the box instead of hanging from its top edge
   (owner's call, 2026-08-21).

   It was never centred: `float: right` has no vertical alignment at all. A
   float attaches to the top of the line box that introduces it, and the art is
   the panel's first child, so it sat 5px below the content top -- its own
   `margin: .25rem` -- at every width. Measured: 5px above and 79-577px below,
   putting it 37-286px above the box's centre line. On [09] at 1100 the drawing
   started at the top and left 577px of empty column beneath it.

   The float can go now because the float is no longer what shapes the text.
   Until the rule above, the text column existed only as a side effect of the
   float and removing it would have let the prose run under the drawing. The
   prose declares its own width now, so the drawing is free to be positioned. */
main.page-product .panel__art,
main.page-industry .panel__art {
  position: absolute;
  /* `right: 0` is the padding box's edge; the content box ends --art-pad inside
     it, and the drawing bleeds --art-bleed past the content. Same edge the
     negative margin-right used to produce. */
  right: calc(var(--art-pad) - var(--art-bleed));
  top: 50%;
  transform: translateY(-50%);
  width: var(--art-w);
  float: none;
  margin: 0;
  shape-outside: none;
}
/* An absolutely positioned child contributes nothing to its parent's height, so
   a panel whose prose got short enough would let the drawing hang out of the
   bottom instead of growing the box.

   The four drawings no longer share one aspect -- each viewBox was cropped to
   its own ink on 2026-08-21 -- so the floor is set by the tallest of them.
   runtime.svg is 469.74 x 373.64, ratio 1.257, and the widest the art column
   ever gets is 35% of the route's 1456px max content = 509.6px, which makes its
   tallest possible height 405px. 26rem = 416px covers it with a little over.

   Reserving that costs nothing at any width measured -- the shortest panel is
   466px of content at 2560 -- and makes the failure impossible rather than
   merely unlikely.

   Above 980 only: below that `.panel__art` is display:none and there is no
   drawing to reserve room for. */
@media (min-width: 981px) {
  main.page-product .panel--art,
  main.page-industry .panel--art { min-height: calc(26rem + 2 * var(--art-pad)); }
}
main.page-product .panel--art > *:not(.panel__art),
main.page-industry .panel--art > *:not(.panel__art) { max-width: var(--art-text); }
/* Below 980 `.panel__art { display: none }` takes the drawing out, so there is
   no column to sit beside and no remainder to subtract. Without this reset the
   prose would keep reserving room for an element that is not there. */
@media (max-width: 980px) {
  main.page-product .panel--art > *:not(.panel__art),
  main.page-industry .panel--art > *:not(.panel__art) { max-width: none; }
}

/* ===========================================================================
   /product/ [08] Whitelabel, two leftovers from the .sec-split conversion
   (2026-08-21)

   Both are consequences of [08] having been a split chapter, and both only
   became visible once it stopped being one. `> .wrap > .lead` is an exact
   selector here: [08]'s other lead lives inside .section-head--doc, so this
   picks out the one closing paragraph and nothing else.
   =========================================================================== */

/* 1. 17px -> 16px, and the 46rem cap comes off (owner's call).

   Every lead on this route is already 1rem -- `main.page-product
   .section-head--doc .lead` set that in the /product/ pass -- but that selector
   only reaches leads inside a chapter head. This paragraph is a direct child of
   .wrap, so it never matched and kept inheriting body's 17px. It was the odd
   size in the chapter, not the intended one.

   NOTE: [02]'s closing paragraph is a direct child of .wrap for the same reason
   and is still 17px. It is now the only 17px lead left on the route. */
main.page-product #whitelabel > .wrap > .lead {
  font-size: 1rem;
  max-width: none;
}

/* 2. padding-top 0 -> 16px on the chapter's relevant-applications line.
   (12px first, then 16px -- owner's call both times.)

   Scoped to [08] on purpose (owner's call). `.quick` was given padding-block: 0
   earlier in this pass because the chapter-rhythm rule already hands it
   clamp(1.75rem, 3vw, 2.75rem) of margin-top. But that rule only fires after a
   BLOCK -- `:is(.panel, .cmp, .opts, .cards, .tiers, .flow, .path, .bm-grid,
   .checks) + :is(p, .lead, .section-head, .quick, .works__row)` -- and [08] is
   the only chapter whose .quick follows a bare <p> instead of a panel. So it
   was the only one getting nothing.

   Measured at 1512, gap between .quick and the element above it:
   [03] 41  [04] 44  [05] 41  [06] 44  [07] 41  [09] 41  |  [08] -3.

   Ink-to-ink, the gap above the line was -3 before any padding, then 14 at 12px,
   18 at 16px and 26 at 24px, against 92-95 on the other six. 24px is the owner's
   number (12 -> 16 -> 24) and it clears the overlap; it is deliberately not
   parity. If the line should sit where theirs do, the number is ~90px. */
main.page-product #whitelabel .quick { padding-top: 24px; }

/* ===========================================================================
   /product/ the "Deployment models" divider moves inside [07] (2026-08-21)

   It was a direct child of <main> carrying its own `.wrap`, and that wrap runs
   different arithmetic from a chapter's: `min(100vw - 2 * --page-gutter - 2 *
   --gutter, --measure-wide)` caps at 1216px, while a chapter's content box is
   `min(100% - 2 * --page-gutter, --block-max)` less its wrap padding and keeps
   growing. Two curves that cross at about 1400px, so the rule was never aligned
   with the chapters at any width -- and it changed SIDES. Measured, divider
   edge against chapter content edge:

     2560  168 inside   1512  35.6 inside   1280  73 outside
     1100  67.2 out     900   60.8 out      390   28.5 out

   Its box also finished 6.5px past its own wrap, so the last 6.5px of it sat on
   [08]'s band.

   Inside [07] it inherits the chapter's wrap and the numbers go to 0 at every
   width, which is the whole fix. Two consequences are worth writing down.

   1. THE SEAM BELOW GOES BACK TO 240px, and that is correct, not a regression.
      Every chapter boundary on this route is 120 + 120. The divider had been
      sitting IN that seam, splitting it 120/120 and taking one half from each
      neighbour. Moved into [07], the boundary under it is an ordinary seam
      again and the divider is simply this chapter's last block.

   2. IT NEEDS A TOP MARGIN, because nothing gives it one. The chapter-rhythm
      rule fires on `block + :is(p, .lead, .section-head, .quick, ...)` and
      .bm-split is in neither list, so it landed with its border-top flush
      against .quick -- 0px, ink to ink. Same clamp the rest of the route
      spends, so the divider sits on the chapter's own rhythm rather than a
      number invented for it.

   Scoped by `:has()` rather than by id so a second divider gets this for free.
   Left alone deliberately: .bm-split's own `grid-column: 1 / -1`, from when the
   class was a divider inside Home's card grid. It is inert here (no grid
   parent) and the rule is shared, so it is not this pass's to change. */
main.page-product > section > :is(.wrap, .narrow) > .bm-split {
  margin-top: clamp(1.75rem, 3vw, 2.75rem);
}

/* ===========================================================================
   /product/ [11] How the product works: the head follows the path in
   (2026-08-21)

   [11]'s body is `.path`, and `.path { max-width: 48rem; margin-inline: auto }`
   centres it inside the chapter. Every other chapter's body starts on the
   chapter's own left edge, so [11] is the one place where a head aligned to that
   edge is aligned to nothing underneath it -- the steps begin a long way to its
   right. Owner's call: move the head in to meet them.

   The number is NOT a fixed 392px. 392 is what the offset happens to be at a
   2560 viewport, where the chapter measures 1552 and (1552 - 768) / 2 = 392.
   `.path` is centred, so the offset moves with the chapter: measured 392 at
   2560, 260 at 1512, 151 at 1280, 67 at 1100 and 0 at 900, where the chapter is
   narrower than 48rem and the centring stops. Hard-coding 392 would put the head
   132px right of the steps at 1512 and shove it clean off a 900px chapter.

   `max(0px, (100% - 48rem) / 2)` IS that centring, written out: same 48rem, same
   halving, and the max() is the clamp the auto margins already do at 0. It
   reproduces all five measured offsets exactly. If .path's 48rem ever changes,
   this has to change with it -- that is the one coupling here.

   Selected by structure rather than by id: [11] is the only chapter on the route
   whose wrap holds a .path.

   /industries/solo-traders/ [02] How it works has the same structure and the
   same misalignment, and takes the same rule (owner's call, 2026-08-21). Its
   chapter is 1287px at 1512 against /product/'s 1287, so the offset computes to
   the same 259.5px -- but it is computed, not shared, and a route whose chapter
   measures differently gets its own correct number from the same expression. */
main.page-product section:has(> .wrap > .path) > .wrap > .sec-chrome,
main.page-product section:has(> .wrap > .path) > .wrap > .section-head--doc,
main.page-industry section:has(> .wrap > .path) > .wrap > .sec-chrome,
main.page-industry section:has(> .wrap > .path) > .wrap > .section-head--doc {
  margin-left: max(0px, (100% - 48rem) / 2);
}

/* ===========================================================================
   /product/ [13] Before the demo: the rows run the full chapter (2026-08-21)

   The same misalignment [11] has, resolved the other way. `.faq { max-width:
   54rem; margin-inline: auto }` centres the rows: measured 864px inside a 1552px
   chapter at 2560, starting 344px in, 212px at 1512, 103px at 1280 -- while the
   chapter bar and the heading above them start at 0. [11] fixed it by moving the
   head in to meet a centred body; here the owner's call is to move the body out
   to meet the head, which is what every other chapter on the route already does.

   Selected by structure, like [11]: [13] is the only chapter whose wrap holds a
   .faq.

   /industries/solo-traders/ [06] Common questions has the identical shape and
   the identical misalignment, and takes the same rule (owner's call,
   2026-08-21). Measured on that route before: rows 864px starting at x324
   inside a 1287px chapter at 1512, against a chapter bar at x112 — a 212px
   indent, the same number this comment records for /product/ at that width. At
   1000 and below the chapter is already narrower than 54rem and nothing
   changes, on either route. */
main.page-product section:has(> .wrap > .faq) > .wrap > .faq,
main.page-industry section:has(> .wrap > .faq) > .wrap > .faq {
  max-width: none;
  margin-inline: 0;
}

/* The answer runs the full row too (owner's call, 2026-08-21).

   It was capped at 46rem for one revision, on the reasoning that a full-width
   row does not have to mean a full-width line. Owner's call is the other way:
   no cap, and 120px of right padding instead of `.faq__a`'s own 7.75rem (124px)
   -- close to the same inset, stated as a round number.

   The measure this produces, stated plainly because it is the whole trade:
   1552 - 120 - 4 = 1428px at 2560, about 185 characters a line; 1163px at 1512,
   about 150. The 50-75 band is a long way behind that. These are four short
   answers of two to four sentences rather than reading copy, which is the same
   argument [02]'s and [08]'s closing paragraphs were decided on.

   The 700px reset below is restated because this selector outranks the
   `@media (max-width: 700px)` rule at .faq__a and would otherwise keep 120px of
   padding on a phone, where it is a third of the row. */
main.page-product section:has(> .wrap > .faq) .faq__a,
main.page-industry section:has(> .wrap > .faq) .faq__a {
  max-width: none;
  padding-right: 120px;
}
@media (max-width: 700px) {
  main.page-product section:has(> .wrap > .faq) .faq__a,
  main.page-industry section:has(> .wrap > .faq) .faq__a { padding-right: .25rem; }
}

/* ===========================================================================
   /industries/solo-traders/ joins the chapter pass (2026-08-21)

   Home took this pass on 2026-08-19 and /product/ on 2026-08-20. Solo Traders
   never did, so it was still rendering the base values of this file while the
   two routes it is supposed to match had already moved off them. Measured at
   1512 before this block:

                          Home      /product/   Solo Traders
     chapter heading      40px        40px      60px AND 38.4px
     chapter lead         16px        16px      17px
     chapter mark       12px pill   12px pill   11px hairline + counter
     mark -> head          0px        24px      44px
     head -> body         40px        40px      56px
     head layout       2 col (5/8)  2 col (7/13) 1 col (0/6)

   Two heading sizes for one rank is the same defect the /product/ block above
   documents: `.sec-split .sh` carries its own clamp(1.6rem, 2.6vw, 2.4rem) at
   (0,2,0), so [01] Operating model sat 21.6px under [02]-[06] with identical
   chrome over both. At 1280 the gap opened to 26.7px.

   That block also left a note saying "/industries/* already sits at 37.44px on
   its own narrower clamp and is untouched either way". That is only true of
   headings inside .sec-split -- one of Solo's six. The other five were at the
   global 3.75rem ceiling. The note is wrong and this block is the correction.

   `main.page-industry` is the same hook as `main.page-product`, chosen for the
   reason recorded there: an explicit, greppable page class rather than a
   `:has()` on whatever modifier the hero happens to carry. Every selector below
   is the /product/ selector with the class swapped, so the specificity notes in
   that block apply unchanged and are not repeated here.

   Only /industries/solo-traders/ carries the class today. The other five
   Industries routes, /trust/ and /proof/ still run the old scale; each opts in
   by adding `page-industry` to its <main>, with no CSS change. That is the
   point of a class rather than a route-shaped :has().
   =========================================================================== */

/* ---- 1. the chapter heading comes down to 40px ----
   28ch rather than the inherited 22ch, for the reason measured on /product/:
   22ch was sized against 60px type. [01] is the one head this cap does not
   reach -- it sits in the .sec-split left track, minmax(0, 30rem) = 480px,
   which is narrower than 28ch (672px), so the track wraps it and the cap does
   not. Same behaviour /product/'s [08] and [10] had before they converted. */
main.page-industry .section-head--doc .sh {
  font-size: clamp(2.25rem, 5vw, 2.5rem);
  max-width: 28ch;
}
/* The gap under the head follows the type down, same as both other routes:
   the global clamp(2.25rem, 4.5vw, 3.5rem) is 56px at 1512 and was proportioned
   under a 60px heading. */
main.page-industry .section-head--doc { margin-bottom: 2.5rem; }

/* ---- 2. the mark becomes a badge, and the counter is hidden ----
   The counter goes for the third time on the same argument: "/ 04 : 06" is not
   actionable, `@media (max-width: 560px)` has been hiding it from every mobile
   reader since the layer shipped, and display:none takes it out of the
   accessibility tree rather than leaving a screen reader to announce a count
   nothing refers to.

   The px padding is optical compensation for JetBrains Mono at 12px and is
   copied verbatim -- see the long note at the /product/ rule. It is tied to
   that metric, so changing the pill's font-size means re-measuring it. */
main.page-industry > section:has(> :is(.wrap, .narrow) > .sec-chrome)
  > :is(.wrap, .narrow) > .sec-chrome {
  border-top: 0; padding-top: 0; align-items: center;
  margin-bottom: var(--gap);
}
main.page-industry :is(.wrap, .narrow) > .sec-chrome > :is(.sec-chrome__l, .sec-chrome__r) {
  display: inline-flex; align-items: center; gap: .45rem;
  line-height: 1;
  padding: 10px .7rem 9px;
  border: 1px solid var(--border); border-radius: 9999px;
  background: color-mix(in srgb, var(--surface) 55%, transparent);
  font-size: .75rem; letter-spacing: .08em;
}
main.page-industry .sec-chrome__ix { margin-right: 0; }
main.page-industry :is(.wrap, .narrow) > .sec-chrome > .sec-chrome__r { display: none; }

/* ---- 3. the chapter head becomes two columns ----
   Same 1.15fr / 1fr and the same gaps as Home and /product/. Two of Solo's six
   chapters carry a lead and land here -- [01] does not, because it is
   .sec-split and its head already sits in the split's left track, which is
   exactly how /product/'s #product-intro behaves. :has(> .lead) is the
   selection; the four heads with no lead stay in one column. */
main.page-industry section:not(.sec-split) > :is(.wrap, .narrow)
  > .section-head--doc:has(> .lead) {
  display: grid; grid-template-columns: 1.15fr 1fr;
  column-gap: clamp(2rem, 4.75vw, 4.5rem); row-gap: 1rem;
  align-items: start;
}
main.page-industry section:not(.sec-split) > :is(.wrap, .narrow)
  > .section-head--doc:has(> .lead) > .sh { grid-column: 1; grid-row: 1 / -1; }
main.page-industry section:not(.sec-split) > :is(.wrap, .narrow)
  > .section-head--doc:has(> .lead) > .lead {
  grid-column: 2; max-width: none; margin-top: 0;
}
@media (max-width: 1000px) {
  main.page-industry section:not(.sec-split) > :is(.wrap, .narrow)
    > .section-head--doc:has(> .lead) { display: block; }
  main.page-industry section:not(.sec-split) > :is(.wrap, .narrow)
    > .section-head--doc:has(> .lead) > .lead { max-width: 36rem; }
}

/* ---- 4. the chapter lead comes down to 16px ----
   The size and the track move together or not at all, same as /product/: a
   smaller face in the same full-width column would have made the measure worse,
   not better. 17px across the 1287px wrap was ~106 characters a line on [04];
   16px in the right track is inside the band. */
main.page-industry .section-head--doc .lead { font-size: 1rem; }

/* [01] is .sec-split, so the rule above shrinks its face while its track stays
   put -- and .sec-split collapses to one column at 1000px, which is the exact
   window where that goes wrong. Measured on that paragraph:

     vw     1512  1280  1100 | 1000   900   860   760   600
     before   68    68    68 |   87    84    80    69    55  characters a line
     after    60    60    60 |   92    89    85    74    58

   Down to 1100 the split's 480px left track holds it at 60ch and the change is
   an improvement. From 1000 to about 770 there is no track left, the lead runs
   the full 736px wrap, and a smaller face in a fixed width buys more characters
   rather than fewer -- 87ch became 92ch. That is the failure the /product/
   comment named ("shrinking the face alone would have made it worse"), showing
   up on the one head its :not(.sec-split) selector cannot reach.

   Same 36rem the collapsed two-column head above uses, so both heads land on
   576px = 72ch in the same window instead of one being capped and one not. */
@media (max-width: 1000px) {
  main.page-industry .sec-split > :is(.wrap, .narrow)
    > .section-head--doc > .lead { max-width: 36rem; }
}

/* ---- 5. the .panel moves onto the same three steps ----
   Solo has one .panel, #non-custodial, and it was running 11 / 32 / 17 -- the
   values the /product/ pass moved off, for the reasons written there: one label
   style at two sizes on one page, a panel headline within 4px of the chapter
   heading above it, and three kinds of prose in one box with two of them
   already at 16.

   The note at that block counts the remaining untouched panels as four. It is
   out of date -- there are nineteen outside /product/ today, one of which is
   this one. The other eighteen are unaffected: this rule is scoped to the page
   class, not to .panel. */
main.page-industry .eyebrow { font-size: .75rem; }
main.page-industry .panel h2 { font-size: 1.5rem; }
main.page-industry .panel__sub,
main.page-industry .panel__list li,
/* `.checks` thêm 2026-08-21 khi /industries/quant-prop/ [01] vào khuôn
   .panel--art. Danh sách của nó là .checks.pain chứ không phải .panel__list, và
   cố ý như vậy: chấm của .panel__list là hình tròn accent, ngôn ngữ của một
   danh sách tính năng, còn đây là danh sách nỗi đau. Nhưng .checks li không đặt
   font-size nên nó thừa kế 17px của body, và ngồi trong một hộp mà .panel__sub
   với .panel__emph đều đã là 16px -- lại đúng cái "ba loại văn xuôi trong một
   cấu kiện" mà dòng trên vừa dọn. Giới hạn trong .panel--art: .checks ngoài
   panel (ba chương .sec-split còn lại của route này) không đổi. */
/* `.panel--art` -> `.panel`, 2026-08-24. Điều kiện thật của rule là "trong một
   hộp", và lúc viết nó thì mọi panel chứa .checks đều là --art, nên --art được
   dùng làm cách viết "trong hộp". Không còn đúng: đo ở 1512 trước khi đổi,
   .checks li đang là 17px trong SÁU hộp .panel--full trên bốn route —
   /trust/ [03] [05] [07], earning-platforms [05], strategies-devs [09],
   trading-groups-academies [06] — trong khi .panel__emph ngay bên cạnh là 16px.
   Đúng cái "ba loại văn xuôi trong một cấu kiện" mà đoạn trên vừa dọn, chỉ là
   ở modifier còn lại. Mười hộp --art không đổi một pixel: chúng đã là 16px.
   .checks NGOÀI hộp vẫn không đổi. */
main.page-industry .panel .checks li { font-size: 1rem; }

/* ---- 6. mọi dải trên route này dùng MỘT sắc ----
   Sắc dải do `main > section.sec-full:nth-of-type(even)` quyết, và :nth-of-type
   đếm MỌI <section> anh em — kể cả .stats và #non-custodial, hai section không
   phải chương. Trên /product/ sáu dải tình cờ rơi hết vào vị trí chẵn nên cả sáu
   là --sec-2 và không ai thấy vấn đề. Trên route này thì không:

     [02] là section thứ 4  -> chẵn -> --sec-2  #0E1116
     [04] là section thứ 7  -> lẻ   -> --sec-1  #0B0D11

   #0B0D11 nằm trên canvas #08090C, cách nhau ba bậc — comment ở khối dải đã tự
   ghi "--sec-1 is #0B0D11 against a #08090C canvas anyway". Nói cách khác [04]
   sẽ gần như không có dải, tức là chính cái người ta yêu cầu lại không xuất
   hiện. Vị trí chẵn/lẻ ở đây là hệ quả của việc có bao nhiêu section KHÔNG PHẢI
   chương nằm phía trên, chứ không mang ý nghĩa thiết kế nào.

   Rule luân phiên tồn tại để "hai dải liền nhau vẫn đọc ra là hai". Trên route
   này hai dải cách nhau bởi [03] và #non-custodial, không bao giờ kề nhau, nên
   ghim một sắc không lấy đi thứ rule đó bảo vệ.

   (0,4,2) — bằng đúng rule :nth-of-type nó ghi đè, thắng bằng source order, y
   như cách khối này đã dựa vào source order ở chỗ khác.) */
main.page-industry > section.sec-full:has(> :is(.wrap, .narrow) > .sec-chrome) {
  background: var(--sec-2);
}

/* ---- 7. dải thôi kéo block ra khỏi wrap ----
   Đây là "separate review" mà comment ở rule /product/ tương ứng đã hẹn lại:
   "the same break-out runs on /proof/ and four /industries/ routes, and whether
   it is wanted there is a separate review". Không thể hoãn thêm, vì [04] chứa
   .tiers và dải mới sẽ bật break-out lên đúng khối đó.

   `.sec-full:not(.sec-split) > .wrap > :is(.cmp, .cards, .tiers, ...)` đẩy block
   sang phải 50% chiều rộng wrap rồi kéo lại bằng translateX(-50%). Mọi block
   trong danh sách đó trên route này đều mang [data-reveal], mà hệ reveal ghi
   `transform` của chính nó — nên translateX(-50%) KHÔNG BAO GIỜ được áp dụng và
   chỉ còn cú đẩy. Đo ở 1512, trạng thái đã reveal xong:

     block            transform   trái   phải    thanh chương
     [03] .cmp          none      756    2171    112   -> cắt mất 659px
     [05] .cards        none      756    2171    112   -> cắt mất 659px

   `body { overflow-x: clip }` nuốt phần thừa nên không có thanh cuộn ngang báo
   động: cột "Senti cannot" của bảng [03] và thẻ thứ hai của [05] đơn giản là
   không có trên màn hình. Lỗi này CÓ SẴN trước thay đổi hôm nay, ở hai chương
   khác; đảo dải sang [04] chỉ chuyển nó sang .tiers bốn cột, nơi hai thẻ giá
   biến mất. Cùng ba khai báo /product/ đã dùng, cùng lý do: trả block về mép
   trái mà file này gọi là mép của chương. */
main.page-industry section.sec-full:not(.sec-split) > :is(.wrap, .narrow)
  > :is(.cmp, .cards, .tiers, .bm-grid, .panel) {
  width: auto; margin-left: 0; transform: none;
}

/* ---- 8. [04] Plans: bốn card thành một hàng thật ----
   `.tiers { align-items: start }` cho mỗi card tự cao theo nội dung của nó, và
   bốn gói không dài bằng nhau — Free có 4 gạch đầu dòng, ba gói kia có 3. Đo ở
   1512 trước khi sửa: cao 246 / 214 / 214 / 214, và bốn nút "Get started" nằm ở
   bốn cao độ khác nhau. Đáy hàng răng cưa, và nút — thứ duy nhất trong card cần
   được so sánh cạnh nhau — là thứ lệch nhau rõ nhất.

   `1fr` là `minmax(auto, 1fr)`, nên một card có min-content rộng hơn phần chia
   của nó sẽ đẩy cột rộng ra và bóp ba cột còn lại. Đo ở 1100: 220.5 / 207 /
   207 / 207 — cột đầu hơn 13.5px, đủ để mắt thấy card Free "to hơn" mà không
   có lý do thiết kế nào. `minmax(0, 1fr)` bỏ sàn min-content đi và bốn cột về
   đúng bằng nhau ở mọi bề rộng.

   Nút xuống đáy bằng cách cho .tier__list nhận phần cao thừa, chứ không phải
   bằng `margin-top: auto` trên .btn — nút đang có `margin-top: 1.5rem` và auto
   sẽ nuốt mất khoảng cách tối thiểu đó, dán nút vào danh sách ở card dài nhất.
   `align-content: start` giữ các gạch đầu dòng ở trên; phần thừa rơi xuống
   dưới cùng, đúng chỗ nút cần.

   Chỉ .page-industry. `.tiers` còn chạy ở /industries/strategies-devs/, route
   chưa vào đợt này — nó vào cùng lúc nhận class, như mọi rule khác ở đây. */
main.page-industry .tiers { align-items: stretch; }
main.page-industry .tiers--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
main.page-industry .tier__list { flex: 1; align-content: start; }
/* Hai breakpoint của .tiers--4 phải được viết lại ở đây. `main.page-industry
   .tiers--4` là (0,2,1) còn `.tiers--4` trong @media là (0,1,0), và media query
   KHÔNG cộng specificity — nên rule bốn cột ở trên thắng cả hai và giữ nguyên
   bốn cột xuống tận mobile. Đo lúc chưa có hai dòng dưới: cột rộng 193px ở
   1024, 132px ở 760, và 60px ở 390. Bốn cột 60px trên điện thoại.

   minmax(0, 1fr) chứ không phải 1fr, cùng lý do với rule bốn cột: sàn
   min-content là thứ vừa được gỡ ra, không có cớ gì để nó quay lại ở hai
   breakpoint hẹp hơn — nơi nó dễ bung hơn chứ không phải khó hơn. */
@media (max-width: 1024px) {
  main.page-industry .tiers--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 620px) {
  main.page-industry .tiers--4 { grid-template-columns: minmax(0, 1fr); }
}

/* ---- 9. [01]: panel ôm sát chiều cao cụm chữ (owner's call, 2026-08-21) ----
   `min-height: calc(26rem + 2 * var(--art-pad))` ở khối .panel--art bên trên là
   một sàn CỨNG, đặt theo trường hợp xấu nhất của /product/: cột ảnh rộng nhất
   có thể (35% của 1456px) cao 405px, làm tròn lên 26rem. Trên route này nó ép ở
   MỌI bề rộng, và khoảng trống nó để lại là hai thứ cộng lại:

     vw     khung trong  chữ  ảnh   dư do SÀN   dư do ảnh cao hơn chữ
     2560       416      267  405       11             138
     1512       416      267  331       85              64
     1280       416      294  271      122               0
     1100       416      347  226       69               0
     1000       416      399  202       17               0

   Ở <=1280 toàn bộ khoảng trống là do sàn: chữ đã cao hơn ảnh, sàn chỉ đang giữ
   chỗ cho một cỡ ảnh không tồn tại ở bề rộng đó. Ở >=1512 phần lớn là do ảnh
   thật sự cao hơn chữ. Bỏ sàn xử lý vế đầu; chặn chiều cao ảnh xử lý vế sau.

   HAI CAP, HAI CHIỀU ĐỀU auto -- đây là chỗ dễ sai và tôi đã đo cái sai trước
   khi viết cái đúng. Cách hiển nhiên là giữ `width: var(--art-w)` rồi thêm
   `max-height`. Một <img> có width XÁC ĐỊNH và max-height thì trình duyệt KHÔNG
   giữ tỉ lệ -- nó ép chiều cao và để nguyên chiều rộng. Đo được: tỉ lệ 1.911 /
   1.911 / 1.671 / 1.563 ở 2560 / 1920 / 1600 / 1512, so với gốc 1.260. Hộp
   isometric bị dẹt. Đặt cả width lẫn height về auto và chặn bằng max-width +
   max-height thì trình duyệt co ảnh vừa cả hai cap và giữ nguyên tỉ lệ.

   SAU:
     vw     panel  trống trên/dưới   ảnh        tỉ lệ   lệch tâm so với chữ
     2560    364        1 / 1       335x266    1.257         0
     1512    364        1 / 1       335x266    1.257         0
     1280    391        1 / 1       340x271    1.257         0
     1000    480        1 / 1       254x202    1.257         0

   CỘT 35% ĐƯỢC GIỮ CHỖ không đổi -- max-width vẫn là var(--art-w) -- nhưng ẢNH
   BÊN TRONG nó thì hẹp lại, vì giữ tỉ lệ nghĩa là cap chiều cao kéo theo chiều
   rộng. Đo ở 1512: ảnh 335px neo phải trong một cột 416px, tức 81px trống bên
   trái nó, so với /product/ [03] có ảnh lấp đầy 416px. Cột chữ 753px thì không
   đổi, nên phần 65% của tỉ lệ vẫn khớp [03] từng pixel; phần 35% chỉ còn khớp
   về chỗ được giữ, không còn khớp về thứ vẽ trong đó. Đây là hệ quả trực tiếp
   của việc panel ôm chữ, không phải tác dụng phụ ngoài dự tính.

   CÁI GIÁ, ghi lại vì nó là toàn bộ phần đánh đổi: trên ~1400px ảnh thôi lớn
   theo màn hình. Ở 1920+ nó từ 509x405 xuống 335x266 -- nhỏ hơn 34% theo cạnh
   -- và khoảng hở giữa cột chữ với ảnh nở từ 44px lên 218px. Đổi lại là một hộp
   không còn 149px trống ở đáy. Owner's call.

   TỰ HUỶ khi hết cần: max-height chỉ cắn khi ảnh cao hơn chữ. Ở <=1280 chữ đã
   cao hơn nên ảnh giữ nguyên 340x271, 284x226, 254x202 -- đúng số cũ. Và nếu ba
   slot copy còn trống của panel này (eyebrow / h2 / .panel__sub, những thứ
   /product/ [03] có mà đây không) được lấp, chữ vượt qua ảnh và rule này ngừng
   có tác dụng, ảnh trở lại cỡ đầy đủ. Áp bây giờ không khoá cửa nào.

   >=981 để khớp media query của sàn nó thay thế; dưới mốc đó `.panel__art` là
   display:none và không có ảnh nào để chặn. */
@media (min-width: 981px) {
  main.page-industry .panel--art { min-height: 0; }
  main.page-industry .panel__art {
    width: auto; height: auto;
    max-width: calc(var(--art-w) * var(--art-scale, 1));
    max-height: calc((100% - 2 * var(--art-pad)) * var(--art-scale, 1));
  }
}

/* ---- 10. `.panel--art-lg`: hình vẽ chạy lớn hơn 10% (owner's call, 2026-08-21)
   Sau khi mục 9 cho panel ôm sát chiều cao cụm chữ, kích thước hình vẽ do CAP
   NÀO CẮN TRƯỚC quyết định, và hai panel cắn hai cap khác nhau. Đo trên
   /industries/quant-prop/ ở 1512:

     chương   ảnh        cap đang cắn      còn cách cap kia
     [01]     276x220    chiều cao          rộng 276 / trần 416
     [04]     413x310    chiều cao (sát)    rộng 413 / trần 416
     [06]     416x206    CHIỀU RỘNG         cao 206 / trần 312

   Nên nhân 10% vào một cap là không đủ: với [01] nới trần rộng không đổi gì (cap
   cao mới là thứ cắn), còn với [04] nới trần cao lên 343 thì chiều rộng suy ra
   là 457 và đập vào trần rộng 416, chỉ lớn thêm 0.7%. Phải nới cả hai cap thì
   hình mới thật sự lớn thêm 10%, và vì tỉ lệ được giữ nên nó lớn đúng 10% theo
   cạnh chứ không méo.

   --art-scale là một biến chứ không phải hai giá trị viết cứng, vì hai cap phải
   nhân cùng một hệ số hoặc tỉ lệ sẽ đổi. Mặc định 1 nằm ngay trong var() để một
   .panel--art không mang modifier không cần khai báo gì.

   Hình lớn ra bằng cách LẤN VÀO PADDING DỌC của panel, không phải bằng cách kéo
   panel cao lên -- panel vẫn ôm sát cụm chữ như mục 9. Ở 1512 [01] thò 11px trên
   và dưới content box, [04] thò 15px, trong khi padding là 48px; hình vẫn cách
   mép panel lần lượt 37px và 33px. Chỗ trống ở đáy vẫn là 1px.

   Chỉ [01] và [04] mang modifier. [06] thì không: cap đang cắn ở đó là chiều
   RỘNG, nên nới thêm 10% sẽ lấy chỗ của cột chữ chứ không lấy chỗ của khoảng
   trống dọc. Muốn [06] lớn theo là một phép đo khác, về cột chữ. */
main.page-industry .panel--art-lg { --art-scale: 1.1; }

/* ---- 11. `.lead` trên route Industries về 16px, mọi vị trí ----
   `.lead` khai báo 1.0625rem = 17px. Đoạn .lead nằm TRONG .section-head--doc đã
   là 16px từ lâu -- nhưng qua khối lưới hai cột ở trên
   (`main:has(.hero--lede) .section-head--doc:has(> .lead) > .lead`), tức là nó
   16px vì lưới, không phải vì nó là .lead. Đoạn .lead đứng ngoài head thì lưới
   không với tới và ở lại 17px.

   Đo trên /industries/vps-providers/ ở 1512 trước khi sửa:

     p.lead                (trong head)   6 đoạn   16px
     p.lead.lead--full     (ngoài head)   6 đoạn   17px

   Cùng một class, hai cỡ, cách nhau vài trăm pixel trên cùng một màn. Đó là
   thứ mục này dọn -- không phải "làm chữ nhỏ đi", mà là bỏ điều kiện vị trí ra
   khỏi cỡ chữ.

   Phạm vi thật sự chỉ 7 đoạn trên 2 route: 6 ở vps-providers và 1 ở
   earning-platforms ([11]). Bốn route Industries còn lại không có .lead nào
   đang ở 17px, nên rule này không đổi một pixel nào của chúng (đã đo cả sáu
   trước và sau).

   `.lead` chứ không `p.lead.lead--full`: cỡ chữ không nên phụ thuộc vào việc
   đoạn đó có mang modifier bề ngang hay không, đúng cái lỗi vừa mô tả. (0,2,1)
   so với (0,1,0) của `.lead`, và trùng giá trị với lưới hai cột nên thứ tự
   nguồn không quyết định gì.

   KHÔNG đụng `.final__sub` -- đoạn đó cũng 17px, nhưng nó nằm trong khối CTA
   cuối trang dùng chung cho cả 12 trang của site, nên hạ nó là một quyết định
   typography toàn site chứ không phải một sửa route. */
main.page-industry .lead { font-size: 1rem; }

/* ---------- cỡ nền 16px cho Product / Industries / Trust ----------
   Rà toàn bộ text đang render ở 17px trên bốn phạm vi Home, Product,
   Industries, Trust (đo Chrome 1440, cuộn hết trang để bắn [data-reveal]).
   Kết quả 36 nút, và điều đáng nói là Home không đóng góp một nút nào:

     body:has(main > .hero--lede) { font-size: 16px; }        (dòng 3419)

   Home đã hạ NỀN từ 2026-08-19. Chín route còn lại vẫn chạy `body { 17px }`
   của dòng 91, và mọi thứ 17px tìm được đều là cùng một nguyên nhân: phần tử
   không tự khai cỡ nên thừa kế nền.

     .final__sub          8 route   640px   khối CTA cuối trang
     .checks li           solo      547px   nằm trong .card, không trong .panel
     .panel > p           groups, mql5   1122px   panel proof, <p> trần
     p.lead.lead--full    product   1220px   ~120 ký tự một dòng
     b / strong / a       10 nút    thừa kế từ bốn dòng trên

   Ba đợt sửa trước đã vá lẻ từng consumer: `.panel__sub`, `.panel__list li`,
   `.panel .checks li`, `.section-head--doc .lead`, `main.page-industry .lead`.
   Mỗi lần một selector mới, và lần nào cũng còn sót -- vì danh sách "thứ thừa
   kế nền" không đóng. Sửa ở nền thì nó đóng.

   Không đụng `body { 17px }` gốc: /proof/ không mang page class, đó là route
   duy nhất ngoài phạm vi người dùng nêu, và nó giữ nguyên 17px. Khi nào /proof/
   vào hệ thống chương thì nó nhận cùng lúc cả hai thứ.

   `body:has(...)` chứ không `main.page-* { font-size }`: nav và footer nằm
   NGOÀI <main>. Hôm nay không nút nào ở đó thừa kế nền (đã đo -- tất cả đều tự
   khai bằng rem), nên hai cách cho cùng kết quả; nhưng đặt ở body thì nav và
   footer không phải là ngoại lệ chờ vỡ, và trùng đúng hình dạng rule Home đang
   dùng.

   Các rule vá lẻ ở trên giữ nguyên, không xoá: mỗi cái là một bảo đảm độc lập,
   và chúng còn chặn cả trường hợp một route rớt mất page class. */
body:has(main.page-product), body:has(main.page-industry) { font-size: 16px; }

/* `.lead` khai 1.0625rem = 17px, mà rem đọc từ <html> nên hạ nền body không
   với tới nó. Industries đã có rule này (mục 11 ở trên); Product thì chỉ có
   bản hẹp `.section-head--doc .lead`, nên đúng một đoạn -- .lead--full ở [06]
   "Those five are the capabilities..." -- đứng ngoài head và ở lại 17px trên
   cột 1220px. Cùng class, hai cỡ, cùng một màn: đúng lỗi mục 11 mô tả. */
main.page-product .lead { font-size: 1rem; }

/* ---------- tiêu đề CTA đóng trang: 40px, bằng tiêu đề chương ----------
   `.final h2` là clamp(1.5rem, 3vw, 2.35rem) = 37.6px. Tiêu đề chương trên
   Product và Industries là clamp(2.25rem, 5vw, 2.5rem) = 40px. Đo ở 1440 trước
   khi sửa, tám route lệch cùng một kiểu:

     product, solo, quant, earning, groups, mql5, vps, trust
       CTA 37.6px   |   chương 40px

   Home đã hết lệch từ hôm qua (`.final.sec-block h2` về 2.5rem). Đây là phần
   còn lại của cùng một việc: đóng trang là một chương, nên tiêu đề nó đọc cùng
   cỡ với bảy chương phía trên nó.

   Vì sao KHÔNG sửa thẳng `.final h2` ở dòng 525: /proof/ cũng dùng nó, nhưng
   /proof/ không nằm trong hệ thống chương -- tiêu đề chương của nó vẫn là bản
   gốc clamp(2.25rem, 5vw, 3.75rem) = 60px. Con số 40 lấy nghĩa từ tiêu đề
   chương; ở một route mà tiêu đề chương là 60px thì 40 không nói lên điều gì,
   nó chỉ là một con số khác. Nên rule này bám page class, đúng hình dạng của
   `body:has(main.page-*)` ở khối 16px bên trên: route nào vào hệ thống chương
   thì nhận, /proof/ giữ 37.6px cho tới khi nó vào.

   Chỉ nâng trần, giữ nguyên 1.5rem và 3vw: dưới ~1333px thì 3vw còn nhỏ hơn
   40px và cả hai bên đều đang co, không có gì để đồng bộ ở đó. */
main.page-product .final h2,
main.page-industry .final h2 { font-size: clamp(1.5rem, 3vw, 2.5rem); }


/* ---------- .legal__dl — term/definition rows on /privacy/ (2026-09-07) ----
   The source page in the Senti app renders these as a two-column <dl>; the
   pairing is the content (a data category and what it means), so a <ul> would
   lose it. Collapses to stacked rows on narrow screens where a 140px term
   column starves the definition. */
.legal__dl { display: grid; grid-template-columns: minmax(9rem, max-content) 1fr;
  column-gap: 1.5rem; row-gap: .55rem; margin: 0; max-width: var(--measure); }
.legal__dl dt { font-family: var(--ff-mono); font-size: .78rem; color: var(--fg);
  padding-top: .12rem; letter-spacing: .01em; }
.legal__dl dd { margin: 0; font-size: .95rem; line-height: 1.65; color: var(--fg-muted); }
@media (max-width: 640px) {
  .legal__dl { grid-template-columns: 1fr; row-gap: .2rem; }
  .legal__dl dd { margin-bottom: .85rem; }
}

/* /privacy/ and any future legal route read as a document, not as a pitch.
   The chapter chrome stays — it is what makes the page part of this site — but
   the marketing scale does not: at the shared 60px the eight section headings
   outweigh the clauses they label, and the page ran 6550px for what the source
   renders as one 720px prose column. Heading down to ~34px and the band
   rhythm down to ~72px keeps the system and drops roughly a third of the
   scroll without touching a word of the text. */
main.page-legal .sh { font-size: clamp(1.35rem, 2.1vw, 2.1rem); line-height: 1.15; }
/* Phải khớp hình dạng của luật band (`main > section:has(> .wrap > .sec-chrome)`,
   đặt 120px) nếu không sẽ thua specificity của :has() và không có hiệu lực —
   đúng cái bẫy đã làm bản đầu chỉ giảm được 52px trên 8529px. */
/* Luật khép seam của hero (.hero + section) thêm một border-top VÀ một mảng
   nền gradient. Trên trang legal cả hai đều thừa: chrome bar [01] ngay dưới đã
   tự có vạch, nên thành hai đường cách nhau ~40px ở ngay đầu trang, và mảng
   nền chính là thứ band đã bị bỏ ở mọi mục khác. Hero ở đây đóng bằng chính
   vạch của mục đầu tiên.
   Phải mang theo cả hai `:not()` của luật gốc: không có chúng selector là
   (0,1,2) và thua (0,3,1), y hệt cái bẫy đã làm luật siết nhịp ở trên vô
   hiệu. Trong file này, override một luật `:has()`/`:not()` thì phải khớp
   đúng hình dạng của nó, không chỉ thêm tổ tiên. */
main.page-legal > .hero + section:not(.stats):not(.works) { border-top: 0; background: none; }

main.page-legal > section:has(> :is(.wrap, .narrow) > .sec-chrome),
main.page-legal > section { padding-block: clamp(26px, 3vw, 40px); }
/* Không dùng band xen kẽ ở đây. Banding được thiết kế cho chương marketing có
   nội dung dày; đặt lên 13-14 mục ngắn của một văn bản pháp lý thì thành sọc
   vằn và đẩy trang dài gấp đôi. Văn bản gốc là một dòng chảy liên tục, nên
   thứ phân mục đúng là vạch kẻ của chính chrome bar, không phải mảng nền.

   Bản đầu còn thêm `section + section { border-top }` ở đây — thừa: .sec-chrome
   đã tự mang border-top rồi, nên mỗi ranh giới mục có HAI vạch cách nhau ~40px.
   26 vạch trên một trang 13 mục. Vạch của chrome bar là đủ và là vạch duy nhất
   đúng ngôn ngữ của site. */
main.page-legal .section-head { margin-bottom: 1.1rem; }
main.page-legal .lead { font-size: .95rem; }

/* Cụm dẫn trong trang pháp lý: "We may share data with:", "Users must NOT".
   Nguồn dùng chúng như nhãn của danh sách ngay dưới, không phải câu văn, nên
   chúng phải dính vào danh sách chứ không trôi như một đoạn prose. */
.legal__sub { font-family: var(--ff-mono); font-size: .78rem; letter-spacing: .04em;
  color: var(--fg); margin: 1.35rem 0 .5rem; }
.legal__sub:first-child { margin-top: 0; }
main.page-legal .checks { margin-block-start: 0; }
main.page-legal .checks + .legal__sub,
main.page-legal .legal__dl + .legal__sub { margin-top: 1.6rem; }

/* ---------- lead form: trạng thái gửi ---------------------------------------
   .lead__trap là honeypot — phải ẩn với người nhưng KHÔNG dùng display:none
   hay hidden, vì bot đọc được cả hai và sẽ bỏ qua đúng cái bẫy. Đẩy ra ngoài
   khung nhìn là cách duy nhất còn lừa được chúng. */
.lead__trap { position: absolute; left: -9999px; width: 1px; height: 1px;
  opacity: 0; pointer-events: none; }
.lead__status { grid-column: 1 / -1; margin: .5rem 0 0; font-size: .85rem;
  color: var(--fg-muted); min-height: 0; }
.lead__status[data-state="err"] { color: #F2A5A5; }
.lead__status[data-state="ok"]  { color: var(--accent); }
.lead__status:empty { display: none; }
.lead__consent { grid-column: 1 / -1; margin: .6rem 0 0; font-size: .78rem;
  line-height: 1.55; color: var(--fg-faint); }
.lead__consent a { color: var(--fg-muted); text-decoration: underline;
  text-underline-offset: 2px; }
.lead__consent a:hover { color: var(--accent); }
.lead__done { grid-column: 1 / -1; border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: clamp(1.5rem, 3vw, 2.25rem);
  background: color-mix(in srgb, var(--accent) 6%, transparent); }
.lead__done-t { margin: 0; font-family: var(--ff-display); font-size: 1.05rem;
  color: var(--fg); }
.lead__done-d { margin: .4rem 0 0; font-size: .9rem; color: var(--fg-muted); }
.btn[disabled] { opacity: .6; cursor: progress; }
