/* ============================================================================
   Molecular AI — marketing component kit
   Requires tokens.css. Colours come ONLY from tokens — the same rule the
   product's workbench.css holds itself to. Alpha derivations use
   color-mix(in srgb, <token> N%, transparent), never hardcoded rgba.

   Class prefix is `mk-` (marketing kit) so this can never collide with the
   product's `wb-` sheet if the two are ever loaded together.
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

body {
  margin: 0;
  background: var(--color-canvas);
  color: var(--color-text);
  font-family: var(--font-family);
  font-size: var(--fs-base);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { margin: 0; line-height: var(--lh-snug); font-weight: var(--weight-semibold); }
h1 { font-size: var(--fs-4xl); line-height: var(--lh-tight); letter-spacing: var(--tracking-display); text-wrap: balance; }
h2 { font-size: var(--fs-2xl); letter-spacing: -0.012em; text-wrap: balance; }
h3 { font-size: var(--fs-xl); letter-spacing: -0.006em; text-wrap: balance; }
p { margin: 0; }
ul, ol { margin: 0; padding-left: var(--space-6); }
/* List item spacing is scoped to prose contexts ONLY. A global `li + li`
   margin-top silently misaligns every flex list — it pushed the 2nd and 3rd
   nav links 6px below the first, and double-spaced the footer columns on top
   of their own gap. Flex lists space themselves with `gap`; leave them alone. */
:where(.mk-prose, .mk-scope__val, .mk-track__list, .mk-callout__body) li + li {
  margin-top: var(--space-2);
}
img { max-width: 100%; height: auto; display: block; }

a { color: var(--color-brand-text-aa); text-underline-offset: 3px;
    text-decoration-color: color-mix(in srgb, var(--color-brand-text-aa) 40%, transparent); }
a:hover { text-decoration-color: var(--color-brand-text-aa); }

code, kbd, samp, pre { font-family: var(--font-family-mono); font-size: 0.92em; }

/* Focus: the product's ring, 3px brand tint plus a solid edge for the 3:1 rule */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--color-brand);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

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

.mk-skip {
  position: absolute; left: var(--space-4); top: -6rem; z-index: 100;
  background: var(--color-surface-raised); color: var(--color-text);
  padding: var(--space-3) var(--space-5);
  border: 1px solid var(--color-border-strong); border-radius: var(--radius-md);
  transition: top var(--duration-base) var(--ease);
}
.mk-skip:focus-visible { top: var(--space-4); }

/* ---- Layout -------------------------------------------------------------- */
.mk-container { width: 100%; max-width: var(--container); margin-inline: auto;
                padding-inline: var(--gutter); }
.mk-container--narrow { max-width: var(--container-narrow); }
/* Wider than the standard container, used by the hero only: the two
   screenshots share a row there, so every extra pixel of container width is
   split between them. The rest of the site stays at --container. */
.mk-container--wide { max-width: 80rem; }
.mk-section { padding-block: var(--section-y); }
.mk-section--tight { padding-block: calc(var(--section-y) * .32); }
/* A rule where the preceding block is not a .mk-section — the hero is
   .mk-hero, so the `.mk-section + .mk-section` border never fires after it. */
.mk-section--rule { border-top: 1px solid var(--color-border); }
.mk-section + .mk-section { border-top: 1px solid var(--color-border); }

/* Alternating grounds down the page. Counted on `section` so the hero is #1 and
   the bands start on the section after it.

   Where the ground changes the border is removed: two devices doing the same
   separating job read as a seam. The rule stays between same-ground sections,
   which is where it is still doing work. */
main > section:nth-of-type(2n) { background: var(--color-canvas-alt); }
main > section:nth-of-type(2n),
main > section:nth-of-type(2n) + section { border-top-color: transparent; }
.mk-stack { display: flex; flex-direction: column; }
.mk-stack > * + * { margin-top: var(--flow, var(--space-5)); }
.mk-prose { max-width: var(--measure); color: var(--color-text-secondary); }

/* ---- The signature device: uppercase tracked mono label ------------------ */
/* Straight from the product — IBM Plex Mono, 600, 0.16em, uppercase, muted.
   It heads every card and section over there; it does the same job here. */
.mk-label {
  font-family: var(--font-family-mono);
  font-size: var(--fs-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--letter-spacing-label);
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 0;
}
.mk-label--brand { color: var(--color-brand-text-aa); }

/* ---- Nav ----------------------------------------------------------------- */
/* NOTE: this is the marketing nav — a light bar. It is deliberately NOT the
   product's dark sidebar slab, which is app chrome for an authenticated
   workspace. The two meet at the typeface and the brand, not the layout. */
.mk-nav {
  position: sticky; top: 0; z-index: 50;
  min-height: var(--nav-height);
  display: flex; align-items: center;
  background: color-mix(in srgb, var(--color-surface-raised) 90%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--color-border);
}
@supports not (backdrop-filter: blur(1px)) {
  .mk-nav { background: var(--color-surface-raised); }
}
.mk-nav__inner { display: flex; align-items: center; gap: var(--space-6); width: 100%; }
.mk-brand {
  display: flex; align-items: center; gap: var(--space-3);
  color: var(--color-text); text-decoration: none; margin-right: auto; flex: none;
}
.mk-brand__mark { width: 26px; height: 26px; flex: none; }
/* The wordmark is live text, matching the product's own .nav-logo, which is
   also text rather than the PNG. Name and tracking follow it. */
.mk-brand__name {
  font-size: var(--app-fs-md); font-weight: var(--weight-bold);
  letter-spacing: 0.22em; text-transform: uppercase; line-height: 1;
}
.mk-brand__sub {
  font-family: var(--font-family-mono); font-size: 8.5px; font-weight: var(--weight-semibold);
  letter-spacing: 0.2em; text-transform: uppercase; line-height: 1;
  color: var(--color-brand-text-aa); display: block; margin-top: 3px;
}
.mk-nav__links { display: flex; align-items: center; gap: var(--space-6);
                 list-style: none; margin: 0; padding: 0; }
.mk-nav__link { color: var(--color-text-muted); text-decoration: none;
                font-size: var(--fs-sm); transition: color var(--duration-fast) var(--ease); }
.mk-nav__link:hover, .mk-nav__link[aria-current="page"] { color: var(--color-text); }

@media (max-width: 640px) {
  .mk-nav { position: static; }
  .mk-nav__inner { flex-wrap: wrap; gap: var(--space-3); padding-block: var(--space-3); }
  .mk-brand { margin-right: 0; width: 100%; }
  .mk-nav .mk-btn { margin-left: auto; }
}

/* ---- Theme toggle — same mechanism and storage key as the product -------- */
.mk-theme {
  display: inline-flex; border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-md); overflow: hidden;
  background: var(--color-surface); flex: none;
}
.mk-theme__seg {
  width: 32px; height: 28px; display: grid; place-items: center;
  background: transparent; border: 0; cursor: pointer; padding: 0;
  color: var(--color-text-muted); transition: color var(--duration-fast) var(--ease);
}
.mk-theme__seg[aria-pressed="true"] {
  background: var(--color-surface-raised);
  color: var(--color-brand-text-aa);
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--color-brand) 40%, transparent);
}
.mk-theme__seg svg { width: 13px; height: 13px; }

/* ---- Buttons ------------------------------------------------------------- */
.mk-btn {
  /* --btn-bd is --color-border-control, NOT --color-border-strong. A secondary
     button is identified by its border alone, so WCAG 1.4.11's 3:1 applies —
     accessibility.md makes exactly this argument, but against the pre-migration
     token set. --color-border-strong measures 1.30 (light) / 1.33 (dark) and
     was the default here until 2026-08-23. */
  --btn-bg: transparent; --btn-fg: var(--color-text); --btn-bd: var(--color-border-control);
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  padding: 10px var(--space-6);
  font: inherit; font-size: var(--fs-sm); font-weight: var(--weight-semibold);
  line-height: 1.2;
  color: var(--btn-fg); background: var(--btn-bg);
  border: 1px solid var(--btn-bd); border-radius: var(--radius-md);
  text-decoration: none; cursor: pointer; white-space: nowrap;
  transition: background var(--duration-fast) var(--ease),
              border-color var(--duration-fast) var(--ease),
              color var(--duration-fast) var(--ease),
              filter var(--duration-fast) var(--ease);
}
/* The product's signature hover on solid fills. Bordered variants cancel it. */
.mk-btn--primary {
  --btn-bg: var(--color-brand-fill-aa); --btn-fg: var(--color-brand-contrast);
  --btn-bd: var(--color-brand-fill-aa);
}
.mk-btn--primary:hover { filter: brightness(1.08); }
.mk-btn--secondary { --btn-bg: var(--color-surface-raised); --btn-fg: var(--color-text); }
.mk-btn--secondary:hover { filter: none; --btn-bg: var(--color-surface); }
.mk-btn--link { --btn-bd: transparent; --btn-bg: transparent;
                --btn-fg: var(--color-brand-text-aa); padding-inline: var(--space-2); }
.mk-btn--link:hover { text-decoration: underline; }
.mk-btn--lg { padding: 13px var(--space-7); font-size: var(--fs-base); }
.mk-btn[aria-disabled="true"] { opacity: .55; pointer-events: none; }
.mk-btn-group { display: flex; flex-wrap: wrap; gap: var(--space-4); }

/* ---- Cards --------------------------------------------------------------- */
/* 10px, the product's real card radius — not one of its own radius tokens. */
.mk-card {
  display: flex; flex-direction: column; gap: var(--space-3);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  padding: var(--space-6);
  box-shadow: var(--shadow-node);
}
.mk-card__title { font-size: var(--fs-lg); font-weight: var(--weight-semibold); }
.mk-card__body { font-size: var(--fs-sm); color: var(--color-text-secondary); }
a.mk-card { text-decoration: none; color: inherit;
  transition: border-color var(--duration-fast) var(--ease),
              transform var(--duration-fast) var(--ease); }
a.mk-card:hover { border-color: var(--color-border-strong); transform: translateY(-2px); }
a.mk-card::after { content: "→"; color: var(--color-brand-text-aa);
                   font-size: var(--fs-sm); margin-top: auto; }

/* The flagship engagement spans the grid instead of taking a louder colour —
   the ordering is the emphasis. Set by "flagship": true in engagements.json. */
.mk-card--flagship {
  border-color: color-mix(in srgb, var(--color-brand) 35%, transparent);
  background: color-mix(in srgb, var(--color-brand) 4%, var(--color-surface-raised));
}
@media (min-width: 720px) { .mk-card--flagship { grid-column: 1 / -1; } }
.mk-card__flag {
  font-family: var(--font-family-mono); font-size: var(--fs-2xs);
  font-weight: var(--weight-semibold); letter-spacing: var(--letter-spacing-label);
  text-transform: uppercase; color: var(--color-brand-text-aa);
}

/* ---- Two-track hero ------------------------------------------------------ */
/* The structural consequence of the product/services decision: two routes,
   equal weight, each with its own availability stated plainly.

   UNUSED AS OF 2026-08-23. Its only consumer was the landing page, where it
   was replaced by .mk-cta — the two said the same two things at very
   different lengths. Kept rather than deleted because the component is
   documented in design/components.md and the decision it encodes could come
   back; delete it with that entry if it does not. */
.mk-hero { padding-block: clamp(1.25rem, 0.75rem + 2vw, 2rem) var(--space-6); }
.mk-hero__lead { max-width: 48rem; display: flex; flex-direction: column; gap: var(--space-5); }
.mk-hero__sub { font-size: var(--fs-lg); color: var(--color-text-secondary); max-width: 54ch; }
.mk-tracks { display: grid; gap: var(--space-5); margin-top: var(--space-8); }
@media (min-width: 860px) { .mk-tracks { grid-template-columns: 1fr 1fr; gap: var(--space-6); } }
.mk-track {
  display: flex; flex-direction: column; gap: var(--space-4);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  padding: var(--space-7);
  box-shadow: var(--shadow-node);
}
.mk-track--primary { border-color: color-mix(in srgb, var(--color-brand) 35%, transparent); }
.mk-track__avail {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-family: var(--font-family-mono); font-size: var(--fs-2xs);
  font-weight: var(--weight-semibold); letter-spacing: 0.08em; text-transform: uppercase;
}
.mk-track__avail--now { color: var(--color-ok-text); }
.mk-track__avail--gated { color: var(--color-text-muted); }
.mk-track__title { font-size: var(--fs-xl); font-weight: var(--weight-semibold); }
.mk-track__body { font-size: var(--fs-sm); color: var(--color-text-secondary); flex: 1; }
.mk-track__list { font-size: var(--fs-sm); color: var(--color-text-secondary);
                  padding-left: var(--space-5); }

/* ---- Audience split ------------------------------------------------------
   Two readers of the same capability, side by side. Deliberately text with a
   rule rather than cards: .mk-track further down the page is already a pair of
   cards, and repeating that container would read as the same section twice. */
.mk-audience { display: grid; gap: var(--space-6); }
@media (min-width: 860px) {
  .mk-audience { grid-template-columns: 1fr 1fr; gap: var(--space-8); }
}
.mk-audience__col {
  display: flex; flex-direction: column; gap: var(--space-3);
  border-left: 2px solid var(--color-border-strong); padding-left: var(--space-5);
}
.mk-audience__title { font-size: var(--fs-base); font-weight: var(--weight-semibold); }
.mk-audience__body { font-size: var(--fs-sm); color: var(--color-text-secondary); }
/* Same unit, 2×2. Used for the differentiators so the page does not acquire a
   third container style for what is the same kind of content. */
@media (min-width: 860px) {
  .mk-audience--2x2 { grid-template-columns: repeat(2, minmax(0,1fr)); gap: var(--space-7) var(--space-8); }
}

/* ---- Runs on — engine support, stated per vendor -------------------------
   ⚠ NOTHING USES .mk-runs AS OF 2026-08-18. Its last consumer was the
   "Engines and fit" list on /services, replaced by .mk-marks when the
   per-vendor Available / Source only / In development statuses were dropped.
   Kept, not deleted, because that was a claim decision taken on the
   understanding that both outstanding connectors land shortly — and restoring
   the status cards should be markup only, not markup plus recovering this
   block from git. If the connectors ship and the cards are not coming back,
   delete this whole section.

   LOGO SLOT. .mk-runs__logo is a fixed-height box; swapping the wordmark for
   an official vendor SVG does not move the layout. Drop the asset in as an
   <img src="logos/<vendor>.svg" alt=""> or an inline <svg>, and delete the
   sibling .mk-runs__wordmark span. See logos/README.md before you do — every
   one of these marks is a registered trademark with usage terms attached. */
.mk-runs { display: grid; gap: var(--space-4); list-style: none; margin: 0; padding: 0; }
@media (min-width: 640px)  { .mk-runs { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@media (min-width: 1000px) { .mk-runs { grid-template-columns: repeat(4, minmax(0,1fr)); } }
.mk-runs__item {
  display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-5); border: 1px solid var(--color-border);
  border-radius: var(--radius-card); background: var(--color-surface-raised);
}
.mk-runs__item--soon { border-style: dashed; background: transparent; box-shadow: none; }
.mk-runs__logo { display: flex; align-items: center; min-height: 32px; }
.mk-runs__logo img, .mk-runs__logo svg { max-height: 32px; max-width: 100%; width: auto; }
.mk-runs__wordmark { font-size: var(--fs-lg); font-weight: var(--weight-semibold);
                     letter-spacing: -0.01em; }
.mk-runs__item--soon .mk-runs__wordmark { color: var(--color-text-muted); }
.mk-runs__note { font-size: var(--fs-sm); color: var(--color-text-secondary); }
.mk-runs__status {
  display: inline-flex; align-items: center; gap: var(--space-2); margin-top: auto;
  font-family: var(--font-family-mono); font-size: var(--fs-2xs);
  font-weight: var(--weight-semibold); letter-spacing: var(--letter-spacing-label);
  text-transform: uppercase;
}
.mk-runs__status--live { color: var(--color-ok-text); }
.mk-runs__status--soon { color: var(--color-text-muted); }

/* ---- Inline demo loop ----------------------------------------------------
   A short screen recording inside the same frame as the product screenshots,
   so the page keeps one visual language for "this is the app". Distinct from
   the hero scrub above: that one carries the whole rearchitecture, this is a
   few seconds of a single interaction.

   TO FILL IT — two routes, both keep the frame, bar, caption and aspect ratio:

   1. GIF or animated WebP. Drop light and dark files into screenshots/ and
      uncomment the .mk-demo__media--metric rules under the screenshot section.
      background-image animates and picks up the theme through the same
      three-state pattern as .mk-shot__img. Delete the --empty modifier and the
      child div.
   2. MP4/WebM — far smaller than a GIF for a screen recording, and the reason
      to prefer it: an autoplaying GIF cannot be stopped, so it ignores
      prefers-reduced-motion. With <video>, gate autoplay on that query and
      show a poster frame instead. Replace the media div with
      <video muted loop playsinline poster="...">, keep the class.

   Either way the recording must be of the real product. A mocked-up
   interaction here is a fabricated claim under MOL-239, not a design asset. */
.mk-demo { margin: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.mk-demo__media {
  aspect-ratio: 1460 / 688;
  background-size: cover; background-position: center top; background-repeat: no-repeat;
}
.mk-demo__media video, .mk-demo__media img {
  width: 100%; height: 100%; object-fit: cover; object-position: center top; display: block;
}
.mk-demo__media--empty {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  background: var(--color-surface);
}
/* Inset dashed rule. Without it the empty slot reads as a broken image rather
   than a slot that is waiting for an asset. Goes away with the --empty class. */
.mk-demo__media--empty::before {
  content: ""; position: absolute; inset: var(--space-5);
  border: 1px dashed var(--color-border-strong); border-radius: var(--radius-md);
}
.mk-demo__empty {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: var(--space-3);
  text-align: center; padding: var(--space-6);
}
.mk-demo__empty p { font-size: var(--fs-sm); color: var(--color-text-muted); max-width: 48ch; }
/* Half-width slots want a taller crop — a 2.12:1 frame at 600px is 283px high,
   which is not enough rows to show a dense table view. */
.mk-demo__media--tall { aspect-ratio: 4 / 3; }



/* ---- Steps / features ---------------------------------------------------- */
.mk-steps { display: grid; gap: var(--space-6); counter-reset: step; }
@media (min-width: 720px) { .mk-steps { grid-template-columns: repeat(2, minmax(0,1fr)); } }
/* One row across, with every band aligned via subgrid rather than by hoping the
   copy happens to wrap to the same number of lines.

   The count is spelled out, and has to be: repeat()'s first argument must be a
   literal integer and var() is not substitutable there, so repeat(var(--n), …)
   makes the whole declaration invalid and the grid silently drops back to the
   two-column rule above. If the number of stages changes again, add the class
   rather than reaching for a custom property.

   `margin-top: auto` on .mk-step__you flushes the BOTTOMS, which is not the
   same thing: each "your side" block is a different height, so its top — and
   therefore its hairline rule — lands somewhere different in every column, and
   five ragged rules across one row read as a bug. Subgrid pins the number,
   title, body and your-side bands to shared rows, so the rules line up and the
   1fr body row absorbs the difference. */
@media (min-width: 1000px) {
  .mk-steps--5 {
    grid-template-columns: repeat(5, minmax(0,1fr));
    grid-template-rows: auto auto 1fr auto;
    column-gap: var(--space-5);
    row-gap: var(--space-3);
  }
  .mk-steps--5 .mk-step {
    display: grid;
    grid-template-rows: subgrid;
    grid-row: span 4;          /* ::before, title, body, your-side */
    gap: 0;                    /* rows come from the parent now */
  }
  /* Top of its own row, not bottom: the whole point is that the rule above it
     sits at one height across all five. */
  .mk-steps--5 .mk-step__you { margin-top: 0; align-self: start; }
}
.mk-step { counter-increment: step; display: flex; flex-direction: column; gap: var(--space-3); }
.mk-step::before {
  content: counter(step, decimal-leading-zero);
  font-family: var(--font-family-mono); font-size: var(--fs-2xs);
  font-weight: var(--weight-semibold); letter-spacing: var(--letter-spacing-label);
  color: var(--color-brand-text-aa);
  padding-bottom: var(--space-3); border-bottom: 1px solid var(--color-border);
}
.mk-step__title { font-size: var(--fs-base); font-weight: var(--weight-semibold); }
.mk-step__body { font-size: var(--fs-sm); color: var(--color-text-secondary); }
/* What the customer does at this stage, as opposed to what we do. A services
   buyer's real second question is "how much of my team does this cost me", and
   answering it per stage is more honest than one blanket sentence. Pushed to
   the bottom of the step so the stages line up across a five-column row even
   when the bodies above them are uneven. */
.mk-step__you {
  margin-top: auto; padding-top: var(--space-3);
  border-top: 1px solid var(--color-border-subtle);
  font-size: var(--fs-xs); color: var(--color-text-subtle);
}
.mk-step__you b {
  font-family: var(--font-family-mono); font-size: var(--fs-2xs);
  font-weight: var(--weight-semibold); letter-spacing: var(--letter-spacing-label);
  text-transform: uppercase; color: var(--color-text-muted);
  display: block; margin-bottom: 2px;
}

/* ---- FAQ -----------------------------------------------------------------
   <details>/<summary> rather than a JS accordion: the answers stay in the DOM
   for anything crawling the page (MOL-129 counts indexable words, and MOL-102
   wants question-verbatim text), keyboard and screen-reader behaviour comes
   free, and the page carries no script it would otherwise need. */
.mk-faq { display: flex; flex-direction: column;
          border-top: 1px solid var(--color-border); }
.mk-faq__item { border-bottom: 1px solid var(--color-border); }
.mk-faq__q {
  display: flex; align-items: baseline; gap: var(--space-4);
  padding: var(--space-5) 0; cursor: pointer;
  font-size: var(--fs-base); font-weight: var(--weight-semibold);
  list-style: none;
}
.mk-faq__q::-webkit-details-marker { display: none; }
.mk-faq__q::after {
  content: "+"; margin-left: auto; flex: none;
  font-family: var(--font-family-mono); color: var(--color-brand-text-aa);
}
.mk-faq__item[open] .mk-faq__q::after { content: "\2212"; }
/* No :focus-visible rule here on purpose — the global one at the top of this
   file already lists `summary`, and it is written with :where() so it carries
   zero specificity. Any class-level override would silently win over it. */
.mk-faq__q:hover { color: var(--color-brand-text-aa); }
.mk-faq__a { padding: 0 0 var(--space-6); max-width: var(--measure);
             font-size: var(--fs-sm); color: var(--color-text-secondary); }
.mk-faq__a > * + * { margin-top: var(--space-4); }
.mk-features { display: grid; gap: var(--space-5); }
@media (min-width: 720px) { .mk-features { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@media (min-width: 1000px) { .mk-features--3 { grid-template-columns: repeat(3, minmax(0,1fr)); } }

/* ---- The engagement ladder ----------------------------------------------
   Six engagements used to render as a flat grid of six equal cards, which
   makes the visitor do the sorting. The offer is not flat: the audit gates
   everything else ("The audit first — it is what makes the rebuild possible to
   scope"), three engagements are siblings that differ by trigger rather than
   by delivery, and one is all of it bought together. Three rungs say that in
   the layout, so the page answers "where do I start" without being asked —
   which is also what the closing CTA already says.

   Each rung is just a labelled group; the cards inside are ordinary .mk-card.
   A rung holding one card lets it run full width as a block, so there is no
   half-empty grid cell and .mk-card--flagship's grid-column never applies. */
.mk-ladder { display: flex; flex-direction: column; gap: var(--space-9); }
.mk-ladder__rung { display: flex; flex-direction: column; gap: var(--space-5); }
.mk-ladder__head {
  display: flex; align-items: baseline; gap: var(--space-4); flex-wrap: wrap;
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--color-border);
}
.mk-ladder__step {
  font-family: var(--font-family-mono); font-size: var(--fs-2xs);
  font-weight: var(--weight-semibold); letter-spacing: var(--letter-spacing-label);
  text-transform: uppercase; color: var(--color-brand-text-aa);
  white-space: nowrap;
}
.mk-ladder__title { font-size: var(--fs-base); font-weight: var(--weight-semibold); }
.mk-ladder__note {
  font-size: var(--fs-sm); color: var(--color-text-subtle);
  margin-left: auto; max-width: 46ch;
}

/* ---- Scope list (S2's workhorse) ----------------------------------------- */
.mk-scope { display: grid; border: 1px solid var(--color-border);
            border-radius: var(--radius-card); overflow: hidden;
            background: var(--color-surface-raised); }
.mk-scope__row { display: grid; grid-template-columns: 1fr; gap: var(--space-2);
                 padding: var(--space-5) var(--space-6);
                 border-bottom: 1px solid var(--color-border-subtle); }
.mk-scope__row:last-child { border-bottom: 0; }
@media (min-width: 640px) {
  .mk-scope__row { grid-template-columns: 12rem 1fr; gap: var(--space-5); align-items: start; }
}
.mk-scope__key { font-family: var(--font-family-mono); font-size: var(--fs-2xs);
                 font-weight: var(--weight-semibold); text-transform: uppercase;
                 letter-spacing: var(--letter-spacing-label);
                 color: var(--color-text-muted); padding-top: .35em; }
.mk-scope__val { font-size: var(--fs-sm); color: var(--color-text-secondary); }
.mk-scope__row--out { background: var(--color-danger-tint); }
.mk-scope__row--out .mk-scope__key { color: var(--color-danger-text); }

/* Status is never colour alone — every marker carries its word. */
.mk-marker { display: inline-block; font-family: var(--font-family-mono);
             font-size: var(--fs-2xs); font-weight: var(--weight-semibold);
             padding: 2px 7px; border-radius: var(--radius-sm);
             border: 1px solid currentColor; white-space: nowrap; }
.mk-marker--in  { color: var(--color-ok-text); }
.mk-marker--out { color: var(--color-danger-text); }
.mk-marker--roadmap { color: var(--color-warn-strong-aa); }

/* The scope label — "In Scope:" / "Out of Scope:" — reads as a definition, not
   a tag, so it drops the pill and sits on its own line above the sentence it
   governs. Three reasons it is not just .mk-marker with longer text:

     1. At that length the border stops reading as a status chip and starts
        reading as a button. A bordered box containing a colon is a mixed
        metaphor — the colon already promises that something follows.
     2. `white-space: nowrap` on a ~11ch pill eats most of the first line in a
        two-column card, so every card's opening sentence broke after two or
        three words. On its own line the sentence gets its full measure back.
     3. It is the same shape as .mk-scope__key on the engagement pages, so the
        card and the page it links to now use one vocabulary for one idea.

   The colour still carries no meaning on its own — accessibility.md's rule
   holds, because the word is the label. */
.mk-marker--label {
  display: block; border: 0; padding: 0;
  margin-bottom: var(--space-2);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-label);
}

/* ---- Stats — designed around the empty state, which is the shipped one --- */
.mk-proof { display: grid; gap: var(--space-5); }
@media (min-width: 720px) { .mk-proof { grid-template-columns: repeat(3, minmax(0,1fr)); } }
.mk-stat {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-6); border: 1px solid var(--color-border);
  border-radius: var(--radius-card); background: var(--color-surface-raised);
  min-height: 8.5rem; justify-content: center;
}
/* Numerals in mono, matching how the product sets every figure. */
.mk-stat__value { font-family: var(--font-family-mono); font-size: var(--app-fs-stat);
                  font-weight: var(--weight-semibold); font-variant-numeric: tabular-nums;
                  line-height: 1.2; }
.mk-stat__label { font-size: var(--fs-sm); color: var(--color-text-secondary); }
.mk-stat__source { font-family: var(--font-family-mono); font-size: var(--fs-2xs);
                   color: var(--color-text-subtle); }
.mk-stat--empty { background: transparent; border-style: dashed;
                  border-color: var(--color-border-dashed); box-shadow: none; }
.mk-stat--empty .mk-stat__value { font-family: var(--font-family); font-size: var(--fs-lg);
                                  font-weight: var(--weight-medium); color: var(--color-text-muted); }

/* ---- Callout ------------------------------------------------------------- */
/* The product's banner recipe: 5–6% wash, 30–35% border, -text ink. */
.mk-callout {
  display: flex; flex-direction: column; gap: var(--space-3);
  border: 1px solid color-mix(in srgb, var(--color-brand) 30%, transparent);
  background: color-mix(in srgb, var(--color-brand) 5%, transparent);
  border-radius: var(--radius-lg); padding: var(--space-6);
}
.mk-callout__title { font-size: var(--fs-base); font-weight: var(--weight-semibold);
                     color: var(--color-brand-text-aa); }
.mk-callout__body { font-size: var(--fs-sm); color: var(--color-text-secondary);
                    max-width: var(--measure); }
.mk-callout--roadmap { border-color: color-mix(in srgb, var(--color-warn) 35%, transparent);
                       background: color-mix(in srgb, var(--color-warn) 6%, transparent); }
.mk-callout--roadmap .mk-callout__title { color: var(--color-warn-strong-aa); }

/* ---- Screenshot frame ---------------------------------------------------- */
/* Product screenshots are the real UI; the frame says "this is the app", and
   the caption carries the source, because a screenshot with numbers in it is
   a quantitative claim (MOL-239). */
.mk-shot { display: flex; flex-direction: column; gap: var(--space-3); }
.mk-shot__frame {
  border: 1px solid var(--color-border); border-radius: var(--radius-card);
  overflow: hidden; background: var(--color-surface-raised);
  box-shadow: var(--shadow-overlay);
}
.mk-shot__bar {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--color-border-subtle);
  background: var(--color-surface);
  font-family: var(--font-family-mono); font-size: var(--fs-2xs);
  letter-spacing: 0.04em; color: var(--color-text-subtle);
}
.mk-shot__dot { width: 8px; height: 8px; border-radius: var(--radius-pill);
                background: var(--color-brand); flex: none; }

/* The before/after pair that lived here — .mk-shots, .mk-shot__img and its
   --pair / --before / --after theme-swapped backgrounds — came out with the
   S1 hero frames on 2026-08-18. It was the last consumer of the
   background-image theme technique; the surviving themed images (.mk-mark,
   .mk-demo__loop) all use paired elements instead, which is why no rule here
   reads prefers-color-scheme any more.

   .mk-shot__frame / __bar / __dot above are still live: index.html's hero
   figure uses them. The state modifiers on the dot went with the pair, since
   only a before/after needed a red one and a green one. */

/* The plain-English demo loop — filled 2026-08-12, both themes together.
   Went with route 2 (MP4) rather than the GIF the commented-out rules above
   anticipated, so the recording can be gated on prefers-reduced-motion. That
   means the theme swap toggles element visibility rather than a background
   image, but it is the same three-state logic: bare :root is light, an
   explicit data-theme wins in both directions, and prefers-color-scheme only
   decides when the visitor has expressed no preference.

   16/9, not the 1460/688 the other media uses: the payoff of this loop is the
   "not from a governed metric" panel beneath the chart, and a 2.12:1 crop of a
   1920x1080 recording cuts it off. */
.mk-demo__media--loop { aspect-ratio: 16 / 9; background: var(--color-surface); }
/* Every one of these is scoped through .mk-demo__media on purpose. The generic
   `.mk-demo__media video` rule further up is (0,1,1); a bare `.mk-demo__loop--dark`
   is (0,1,0) and loses to it, so the dark clip stayed visible in light mode and
   the two stacked. Matching the parent buys the extra specificity. */
.mk-demo__media .mk-demo__loop {
  width: 100%; height: 100%; object-fit: cover; object-position: center top; display: block;
}
.mk-demo__media .mk-demo__loop--dark { display: none; }

:root[data-theme="dark"] .mk-demo__media .mk-demo__loop--light { display: none; }
:root[data-theme="dark"] .mk-demo__media .mk-demo__loop--dark  { display: block; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) .mk-demo__media .mk-demo__loop--light { display: none; }
  :root:not([data-theme="light"]):not([data-theme="dark"]) .mk-demo__media .mk-demo__loop--dark  { display: block; }
}

/* prefers-reduced-motion for this loop is enforced in JS, not here: a <video>
   playing is not an animation, so no CSS rule can stop it. The markup omits
   the autoplay attribute and a script at the end of the page starts playback
   only when reduced motion is NOT requested. Everyone else keeps the poster
   frame, which is the finished heatmap — so opting out of the motion costs
   the result, not the answer. */

.mk-demo__caption { font-size: var(--fs-xs); color: var(--color-text-subtle);
                    max-width: var(--measure); }

/* A line break that only applies once the halves are side by side. On a phone
   the column is narrow enough to break on its own, and a forced break there
   would leave a short orphan line. */
.mk-br-lg { display: none; }
@media (min-width: 900px) { .mk-br-lg { display: inline; } }

/* ---- Layer chips (medallion) --------------------------------------------- */
.mk-layers { display: flex; flex-wrap: wrap; gap: var(--space-3);
             list-style: none; padding: 0; margin: 0; }
.mk-layer { display: inline-flex; align-items: center; gap: var(--space-2);
            padding: var(--space-2) var(--space-4);
            border: 1px solid var(--color-border); border-radius: var(--radius-pill);
            background: var(--color-surface-raised); font-size: var(--fs-sm);
            color: var(--color-text-secondary); }
.mk-layer::before { content: ""; width: 8px; height: 8px; border-radius: var(--radius-pill);
                    background: currentColor; flex: none; }
.mk-layer--bronze { color: var(--layer-bronze-text); }
.mk-layer--silver { color: var(--layer-silver-text); }
.mk-layer--gold   { color: var(--layer-gold-text); }

/* ---- Engines ------------------------------------------------------------- */
.mk-engines { display: flex; flex-wrap: wrap; gap: var(--space-3);
              list-style: none; padding: 0; margin: 0; }
.mk-engine { display: inline-flex; align-items: center; gap: var(--space-2);
             padding: var(--space-3) var(--space-5);
             border: 1px solid var(--color-border); border-radius: var(--radius-pill);
             background: var(--color-surface-raised); font-size: var(--fs-sm);
             color: var(--color-text); }
.mk-engine--supported { border-color: color-mix(in srgb, var(--color-ok) 35%, transparent); }
.mk-engine__tick { color: var(--color-ok-text); font-family: var(--font-family-mono); }
.mk-engine--roadmap { border-style: dashed; color: var(--color-text-muted); }

/* ---- CTA band ------------------------------------------------------------ */
.mk-cta {
  display: flex; flex-direction: column; gap: var(--space-5); align-items: flex-start;
  background: color-mix(in srgb, var(--color-brand) 6%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-brand) 25%, transparent);
  border-radius: var(--radius-card); padding: var(--space-9) var(--space-7);
}
@media (min-width: 800px) {
  .mk-cta { flex-direction: row; align-items: center; justify-content: space-between;
            gap: var(--space-7); }
  .mk-cta__copy { max-width: 48ch; }
}
.mk-cta__sub { font-size: var(--fs-sm); color: var(--color-text-secondary);
               margin-top: var(--space-3); }
/* An eyebrow above the h2. Everywhere else on the site that pairing is spaced
   by .mk-stack's --flow; .mk-cta__copy is a plain max-width box, and both
   elements zero their own margin, so without this they touch. */
.mk-cta__copy .mk-label { margin-bottom: var(--space-3); }

/* ---- Footer -------------------------------------------------------------- */
.mk-footer { border-top: 1px solid var(--color-border); padding-block: var(--space-9);
             margin-top: var(--section-y); background: var(--color-surface); }
.mk-footer__inner { display: grid; gap: var(--space-7); }
@media (min-width: 720px) { .mk-footer__inner { grid-template-columns: 1.5fr repeat(3, minmax(0,1fr)); } }
.mk-footer__col-title { font-family: var(--font-family-mono); font-size: var(--fs-2xs);
                        font-weight: var(--weight-semibold); text-transform: uppercase;
                        letter-spacing: var(--letter-spacing-label);
                        color: var(--color-text-muted); margin-bottom: var(--space-4); }
.mk-footer__list { list-style: none; margin: 0; padding: 0;
                   display: flex; flex-direction: column; gap: var(--space-3); }
.mk-footer__list a { color: var(--color-text-secondary); text-decoration: none;
                     font-size: var(--fs-sm); }
.mk-footer__list a:hover { color: var(--color-text); }
/* A constant that has not landed yet: keep the label, lose the link. Set by
   data-url-empty="mute" in constants.js. */
.mk-footer__list a[aria-disabled="true"] { color: var(--color-text-subtle); cursor: default; }
.mk-footer__list a[aria-disabled="true"]:hover { color: var(--color-text-subtle); }
.mk-footer__list a[aria-disabled="true"]::after {
  content: " · soon"; font-family: var(--font-family-mono); font-size: var(--fs-2xs);
  letter-spacing: var(--letter-spacing-label);
}
.mk-footer__legal { margin-top: var(--space-7); padding-top: var(--space-6);
                    border-top: 1px solid var(--color-border);
                    font-size: var(--fs-xs); color: var(--color-text-subtle);
                    display: flex; flex-wrap: wrap; gap: var(--space-5); }

/* ---- Meta row (S2) ------------------------------------------------------- */
.mk-meta { display: flex; flex-wrap: wrap; gap: var(--space-7);
           padding: var(--space-5) 0;
           border-block: 1px solid var(--color-border); }
.mk-meta div { display: flex; flex-direction: column; gap: 2px; }
.mk-meta dt { font-family: var(--font-family-mono); font-size: var(--fs-2xs);
              font-weight: var(--weight-semibold); text-transform: uppercase;
              letter-spacing: var(--letter-spacing-label); color: var(--color-text-muted); }
.mk-meta dd { margin: 0; font-size: var(--fs-sm); color: var(--color-text); }

/* ---- Split hero: the headline and the evidence in one grid ---------------
   The h1's two halves sit above the two screenshots they describe, so the
   sentence and the images read as one statement rather than a claim followed
   by proof.

   Mechanics: one grid, and the h1 spans it using `subgrid` so its two spans
   land on exactly the same column tracks as the figures below. Falls back to
   an identical two-column definition where subgrid is unsupported — the gap
   must match the parent's or the halves drift off their images.

   Source order is h1 (both halves) then the two figures. On mobile that is
   interleaved so each half of the sentence sits above the image it describes
   — see the max-width block below. */
/* ---- Hero: the rearchitecture, played once --------------------------------
   One variable, --p (0 -> 1), written by JS from ELAPSED TIME once the visitor
   scrolls. It drives the frame index AND the layout, so the picture narrowing
   and the sentence completing are one gesture rather than two animations that
   have to be kept in step.

   This used to be scrubbed by scroll through a 320vh track. Scroll position is
   a terrible clock: it stalls when the visitor stops, runs at whatever speed
   they spin the wheel, and reverses. Working around that took a commit/settle
   machine that had to ignore scroll input mid-settle, which read as the
   animation hanging and then changing pace. A fixed clock has none of those
   states -- it plays the same way every time, for everyone.

   Why the frame changes shape: the sprawl is genuinely wide and short — ten
   columns — and the medallion it collapses into is narrow and tall, sitting at
   26% zoom in about a quarter of the captured canvas. So the window GROWS in
   height while it NARROWS in width, cropping dead canvas away instead of
   scaling the picture down. The graph ends up ~670px tall against the 280px
   the two old stills managed, and the width it gives up is exactly where the
   copy goes.

   Frames are a real capture of the product's Current -> Planned transition,
   recorded at 6s via the __ARCH_TWEEN_MS seam. Nothing here is illustrated. */
:root { --p: 0; }

/* One screen, in normal flow. The track is gone with the scrub that needed it:
   320vh of scroll only ever existed to give the wheel something to drive, and
   left behind it would be 320vh of nothing happening.

   The nav is sticky and therefore still IN FLOW, so the hero already starts
   below it — min-height only has to claim the space the nav leaves for the
   stage to fill the first screen with equal air above and below its contents.
   min-height rather than height: at --p: 1 the figure is at its tallest, and a
   short viewport should grow rather than crop it. */
/* The screen minimum moved OFF the stage and onto the section, and the section
   became a column. That is what makes the compatibility row a footer: the stage
   takes whatever is left of the first screen, the row sits under it, and the
   two together are one viewport. flex-shrink: 0 on the stage so a tall figure
   at --p: 1 grows the hero rather than being squeezed against the footer. */
.mk-hero--scrub {
  position: relative; padding: 0;
  min-height: calc(100vh - var(--nav-height));
  display: flex; flex-direction: column;
}
.mk-hero__stage {
  flex: 1 0 auto;
  display: flex; align-items: center;
  padding-block: var(--space-8);
  overflow: hidden;
}
/* display:contents drops the h1 box so its two spans become grid children and
   can be placed at opposite ends of the transition. The heading stays ONE
   element — no duplicate, nothing hidden from screen readers. */
.mk-hero__head { display: contents; }
.mk-hero__inner {
  display: grid; align-items: center;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: calc(var(--p) * var(--space-8));
  row-gap: var(--space-4);
  grid-template-areas:
    "lede lede"
    ".    fig"
    "tail fig"
    "body fig"
    "cta  fig";
  /* The figure spans everything below the lede and is far taller than the copy
     beside it, so the slack has to go SOMEWHERE. Left to the default it is
     split evenly between the three copy rows and the column comes apart —
     heading, 200px, paragraph, 200px, buttons, reading as a mistake.

     A flexible row above, and the BUTTON row flexible below, take all of it
     instead. Heading and paragraph then sit at their own heights as one block,
     with the buttons hanging directly under the paragraph (align-self: start,
     below) inside the row that absorbs the rest.

     Why the buttons absorb rather than sit in a fixed row between two spacers:
     the copy column grows from ~290px to ~710px across a run, and two buttons
     that need ~390px therefore stop wrapping part-way through it. That is a
     52px change in the row's height, and with the slack split evenly it moved
     the heading 26px in a single frame — which is what read as the text
     jumping into position rather than arriving. Only the heading and paragraph
     are measured now, and both have a fixed measure of their own, so nothing
     above the buttons can be moved by what the buttons do. The unwrap itself
     lands around --p 0.67, where the buttons are still ~6% opaque. */
  grid-template-rows: auto 1fr auto auto 1fr;
}
/* Full width rather than in the copy column, because at rest the copy column is
   ~120px — the figure's `auto` track takes the rest — and a button row put
   there would be crushed. Spanning both tracks is the only placement that holds
   at BOTH ends of the transition.

   width: 0 + min-width: 100% is load-bearing, not a flourish. A spanning item
   contributes its min-content width to the tracks it crosses, so the widest
   button was widening the copy track from ~122px to ~212px and taking those
   90px off the figure — which at --p: 0 is sized to show the frame EXACTLY,
   1280x442 with nothing cropped, so the first thing it cost was the leftmost
   column of table names. A definite width takes the row out of track sizing
   altogether; the percentage min-width, which cannot resolve during that pass,
   gives it the full row back at layout. Take these off and the sprawl loses
   its edges again. */
.mk-hero__cta {
  grid-area: cta; width: 0; min-width: 100%;
  /* Hangs from the top of the row that absorbs the slack, so it stays under the
     paragraph it belongs to instead of drifting to the bottom of the picture —
     and so its own height is free to change without moving anything above it. */
  align-self: start;
  margin-top: var(--space-4);
  /* ARRIVES with the resolution, a beat behind the body copy it follows — the
     ask lands after the claim that earns it, not before. width: 0 still
     matters at the other end: at --p: 0 these buttons are invisible but a
     collapsed box still contributes its min-content WIDTH, and left to do so
     the widest button sets the copy track and crops the figure. */
  opacity: clamp(0, calc((var(--p) - 0.65) * 3.2), 1);
  transform: translateY(calc((1 - clamp(0, calc((var(--p) - 0.65) * 3.2), 1)) * 14px));
  max-height: calc(clamp(0, calc(var(--p) * 3), 1) * 8rem); overflow: hidden;
}
/* Text collapses by MAX-HEIGHT, not just opacity. A transparent element still
   lays out: with the media at full width the copy column is ~56px, and a
   heading left in it wraps into a 56px ribbon 274px tall, which both looks
   broken and blows out the row heights. Collapsing to zero lets
   minmax(0, 1fr) give the whole width to the figure — and lets it come back. */
.mk-hero__head span:nth-of-type(1) {
  grid-area: lede; font-size: var(--fs-3xl); line-height: var(--lh-tight);
  letter-spacing: var(--tracking-display); text-wrap: balance;
  opacity: clamp(0, calc(1 - var(--p) * 3.2), 1);
  /* This collapse is choreography, not bookkeeping, and both numbers matter.

     WHEN: between --p 0.32 and 0.45. It starts after the fade above has already
     reached zero, so the line is never seen being guillotined, and it finishes
     as the second half begins to appear — so the row this vacates is gone
     BEFORE the arriving text is visible, not while it is. Collapsing against
     20rem, as this did, meant the row still held its full 45.6px until --p 0.86
     and then gave it up over the last 14% of the run: everything below jumped
     up half a line, late, in full view. That was the second half of the
     reported stutter.

     AGAINST WHAT: --lede-h, not a large safe ceiling. max-height only does
     anything once it drops below the element's own height, so scaled against
     20rem the entire visible travel bunches into the last few frames of the
     ramp however wide you make the window. Sized close to the real height, the
     ramp and the movement are the same thing.

     The coupling is the cost: --lede-h must stay >= this line's rendered
     height. It is one line at 40px across every desktop width, because
     --fs-3xl is already at its clamp ceiling by 765px and the narrow layout
     takes over below 900px. Re-measure if the headline or --fs-3xl changes. */
  --lede-h: 3rem;
  max-height: calc(clamp(0, calc((0.45 - var(--p)) / 0.13), 1) * var(--lede-h));
  overflow: hidden;
}
/* The arriving copy is laid out at its FINAL width from the start, and its
   height is settled well before it becomes visible. Both matter:

   A width tied to the grid track would rewrap on every scroll frame as the
   column grows, so the text would visibly reflow word by word rather than
   arrive. Fixing the width means the line breaks are decided once.

   The max-height reaches full by p=0.33 while the fade only starts at p=0.45,
   so the block is never seen mid-wipe — it fades in already whole. It still
   collapses to zero at rest, which is what lets minmax(0, 1fr) hand the full
   width to the figure at the top of the scroll. */
.mk-hero__head span:nth-of-type(2) {
  grid-area: tail; font-size: var(--fs-3xl); line-height: var(--lh-tight);
  letter-spacing: var(--tracking-display); text-wrap: balance;
  color: var(--color-brand-text-aa);
  width: min(40rem, 46vw);
  opacity: clamp(0, calc((var(--p) - 0.45) * 3.2), 1);
  transform: translateY(calc((1 - clamp(0, calc((var(--p) - 0.45) * 3.2), 1)) * 14px));
  max-height: calc(clamp(0, calc(var(--p) * 3), 1) * 20rem); overflow: hidden;
}
.mk-hero__body {
  grid-area: body; color: var(--color-text-secondary);
  width: min(40rem, 46vw);
  opacity: clamp(0, calc((var(--p) - 0.55) * 3.2), 1);
  transform: translateY(calc((1 - clamp(0, calc((var(--p) - 0.55) * 3.2), 1)) * 14px));
  max-height: calc(clamp(0, calc(var(--p) * 3), 1) * 20rem); overflow: hidden;
}
/* The picture is held off the half-sentence above it for as long as that half
   is the thing it sits under, and closes back up as the sentence resolves
   BESIDE it — at --p: 1 the figure's neighbour is the copy column, not the
   lede, and the plain row gap is the right gap again. Driven by --p rather than
   set as a fixed margin so both states get their own spacing out of one
   declaration, which is how the rest of this hero is written. */
.mk-hero__fig {
  grid-area: fig; min-width: 0;
  margin: calc((1 - var(--p)) * var(--space-8)) 0 0;
}
/* Height and aspect are driven; width is derived. The <img> is height-locked
   and centred, so narrowing the aspect crops empty canvas rather than shrinking
   the graph. */
.mk-hero__frames {
  /* Viewport-relative with bounds, not fixed pixels. At a fixed 400->660px the
     end state left 12px of air on an 800px viewport and would have overflowed
     anything shorter, because the stage is a fraction of the window but the
     figure was not. Clamped so it still has presence on a tall display and
     still fits a short one. */
  height: calc(clamp(300px, 44vh, 460px) + clamp(180px, 28vh, 300px) * var(--p));
  aspect-ratio: calc((1280 - 940 * var(--p)) / 442);
  width: auto; max-width: 100%;
  position: relative; overflow: hidden; background: var(--color-surface);
}
.mk-hero__frames img {
  position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  height: 100%; width: auto; max-width: none; display: block;
}

/* ENGINE STRIP. Two sets, deliberately not one list: the divider and the muted
   colour are what keep "Redshift" from reading as "Redshift is supported",
   which MOL-239 does not permit and no connector in molecular_app backs.
   .mk-hero__engine is a fixed-height slot — 20px here against the Runs-on
   strip's 32px — so dropping an official SVG in swaps the mark without moving
   a pixel. See design/logos/README.md before you do. */
/* ---- Compatibility marquee (hero footer) --------------------------------
   A standing statement rather than a beat in the hero's sentence, so nothing
   here reads --p. It is the same row before and after the transition. */
/* Full-bleed band, contained row: the rule runs the whole width of the page
   like a footer should, while the label inside lines up with the headline
   above it. */
.mk-compat { border-top: 1px solid var(--color-border-subtle); padding-block: var(--space-5); }
.mk-compat__inner { display: flex; align-items: center; gap: var(--space-6); }
.mk-compat__label { flex: none; color: var(--color-text-muted); }
/* The viewport clips; the track slides. Masked at both edges so marks enter and
   leave by fading rather than being guillotined at the container border. */
.mk-compat__viewport {
  flex: 1; min-width: 0; overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 4rem, #000 calc(100% - 4rem), transparent);
          mask-image: linear-gradient(to right, transparent, #000 4rem, #000 calc(100% - 4rem), transparent);
}
.mk-compat__track {
  display: flex; align-items: center; gap: var(--space-10);
  list-style: none; margin: 0; padding: 0; width: max-content;
  animation: mk-compat-scroll 38s linear infinite;
}
/* Exactly -50%: the track holds the set twice, so at the end of a cycle the
   second set is standing precisely where the first started and the jump back
   to 0 is invisible. Any other distance shows a seam. */
@keyframes mk-compat-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
/* Pause on hover so a visitor trying to read a mark can. */
.mk-compat:hover .mk-compat__track { animation-play-state: paused; }
.mk-compat__item { display: flex; align-items: center; flex: none; }
/* ---- Vendor mark (shared) -----------------------------------------------
   Height-locked, width derived — the one rule that lets marks of completely
   different proportions share a row without any of them being squashed. The
   width/height attributes on each <img> are the file's own, so the aspect is
   reserved before the asset lands and the row cannot reflow.

   Shared rather than scoped to the marquee: /services shows the same six marks
   as a static row, and a second copy of the theme-swap rules below is the kind
   of duplicate that goes stale on one page only. Height is the one thing each
   context sets for itself. */
.mk-mark { height: 30px; width: auto; max-width: none; display: block; }

/* THEME-MATCHED MARKS.

   Five of the six lockups carry a near-black wordmark. On the dark surface
   their icons kept reading and their names disappeared, which looks like a
   half-loaded image rather than a logo. Each therefore ships a second file in
   which ONLY the wordmark is reversed to white — every icon keeps its own
   colour, because a blanket invert turns the Databricks bricks cyan and the
   Athena chart blue. dbt supplies its own official white lockup and that is
   what is used for it. Snowflake is `#29B5E8` throughout, reads on both
   surfaces, and has no variant.

   Two elements rather than one with a swapped source, and for the same reason
   as .mk-demo__loop below: the theme is carried by a data-theme attribute,
   which <picture> and media queries cannot read. display:none also drops the
   inactive one out of the accessibility tree, so each vendor name is announced
   once rather than twice.

   Three-state, like every other themed pair in this file: an explicit
   data-theme wins in both directions, and prefers-color-scheme only decides
   for a visitor who has not chosen. */
.mk-mark--dark { display: none; }
:root[data-theme="dark"] .mk-mark--light { display: none; }
:root[data-theme="dark"] .mk-mark--dark  { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) .mk-mark--light { display: none; }
  :root:not([data-theme="light"]):not([data-theme="dark"]) .mk-mark--dark  { display: block; }
}

/* A static row of the same marks, for a section rather than a hero footer.
   Nothing scrolls here: on /services this sits inside the fit section, where a
   reader is checking a list against their own stack rather than watching a
   band go by. */
.mk-marks {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-6) var(--space-8);
  list-style: none; margin: 0; padding: 0;
}
.mk-marks .mk-mark { height: 34px; }
/* .mk-stack owns the space above this row, and has to be told so. A bare
   `margin: 0` list reset carries the same specificity as `.mk-stack > * + *`
   and sits later in this file, so it silently wins the top margin — which left
   the row flush against the paragraph above it. Restated here rather than
   weakened there, because the reset is right and the stack rule is shared. */
.mk-stack > .mk-marks { margin-top: var(--flow, var(--space-5)); }
@media (max-width: 640px) { .mk-marks .mk-mark { height: 26px; } }

@media (max-width: 899px) {
  .mk-compat__inner { flex-direction: column; align-items: flex-start; gap: var(--space-3); }
  .mk-compat__viewport { width: 100%; }
  .mk-compat .mk-mark { height: 24px; }
}

@media (prefers-reduced-motion: reduce) {
  /* No travel. The row still has to show all six, and it is wider than a phone,
     so it becomes a strip the visitor scrolls by hand — and the duplicate set
     comes out, because with nothing moving it is just the same logos twice. */
  .mk-compat__track { animation: none; }
  .mk-compat__viewport { overflow-x: auto; }
  .mk-compat__item[aria-hidden="true"] { display: none; }
}

/* The clause that only becomes true once the overlay it names is on screen.
   Laid out from the start and only faded, so nothing in the bar moves. */
.mk-hero__bar-more { opacity: clamp(0, calc((var(--p) - 0.5) * 4), 1); }

/* SCROLL CUE. Leaves as the thing it points at arrives — including its own
   top margin, or a collapsed cue would still be pushing the figure up off
   centre at --p: 1. */
.mk-hero__cue {
  display: flex; align-items: center; justify-content: center; gap: var(--space-2);
  font-family: var(--font-family-mono); font-size: var(--fs-2xs);
  font-weight: var(--weight-semibold); letter-spacing: var(--letter-spacing-label);
  text-transform: uppercase; color: var(--color-text-muted);
  margin-top: calc((1 - var(--p)) * var(--space-5));
  opacity: clamp(0, calc(1 - var(--p) * 4), 1);
  max-height: calc((1 - var(--p)) * 2rem); overflow: hidden;
}
.mk-hero__cue-chevron { width: 13px; height: 13px; flex: none;
  animation: mk-hero-cue 2.4s var(--ease) infinite; }
@keyframes mk-hero-cue {
  0%, 100% { transform: translateY(-2px); opacity: .55; }
  50%      { transform: translateY(2px);  opacity: 1; }
}

/* A phone gets the resolved state with the whole sentence intact above it. The
   stacked layout is already taller than a screen, so claiming a screen minimum
   would only add dead air. */
@media (max-width: 899px) {
  .mk-hero__stage { min-height: 0; padding-block: var(--section-y); }
  .mk-hero__inner { grid-template-columns: 1fr;
    grid-template-areas: "lede" "tail" "body" "cta" "fig"; row-gap: var(--space-5);
    /* No figure to centre against in a single column — the spacer rows the
       desktop grid needs would only be dead air here. */
    grid-template-rows: none; }
  /* Nothing to trigger here — a phone is served the resolved state — so an
     invitation to scroll for it would be a lie. Held off JS too: with the
     script blocked --p stays 0 and the cue would otherwise stand. */
  .mk-hero__cue { display: none; }
  /* The figure follows the body copy rather than the headline in this order,
     and the stack's own row gap is the right gap for that. */
  .mk-hero__fig { margin-top: 0; }
  .mk-hero__head span, .mk-hero__body, .mk-hero__cta {
    opacity: 1 !important; transform: none !important;
    max-height: none !important; width: auto !important; }
  .mk-hero__frames { width: 100%; height: auto; aspect-ratio: 340 / 442; }
}

@media (prefers-reduced-motion: reduce) {
  /* Nothing plays. The resolved state and the complete sentence, both at once —
     the information survives, the motion does not. */
  .mk-hero__stage { min-height: 0; padding-block: var(--section-y); }
  .mk-hero__head span, .mk-hero__body, .mk-hero__cta {
    opacity: 1 !important; transform: none !important; max-height: none !important; }
  /* Same reason as narrow: this end state is where they already are. */
  .mk-hero__cue { display: none; }
  .mk-hero__cue-chevron { animation: none; }
}

.mk-split { display: grid; gap: var(--space-6); align-items: start; }
.mk-split__head { margin: 0; }
.mk-split__head span { display: block; }
/* Second half is the turn in the sentence; it gets the accent to mark it. */
.mk-split__head span + span { color: var(--color-brand-text-aa); }

/* Mobile: half, image, half, image — the same pairing the desktop layout makes
   with columns, made with source order instead.

   `display: contents` on the h1 drops its box so the two spans become grid
   items of .mk-split directly, which is what allows a figure to sit between
   them. The heading stays ONE element: no duplicate h1, no split heading, no
   aria-hidden copy. Modern engines keep a display:contents element in the
   accessibility tree (the old Chrome/WebKit bug that dropped it is fixed), so
   the h1 is still announced as a level-1 heading.

   nth-of-type is DOM-based, not layout-based, so it keeps working on the
   figures even though their parent's child list has effectively changed. */
@media (max-width: 899px) {
  .mk-split__head { display: contents; }
  .mk-split__head span:nth-of-type(1)  { order: 1; }
  .mk-split__fig:nth-of-type(1)        { order: 2; }
  .mk-split__head span:nth-of-type(2)  { order: 3; }
  .mk-split__fig:nth-of-type(2)        { order: 4; }
}

/* Desktop: both halves of the sentence across the top, both screenshots
   beneath them, each half sitting directly above the image it describes.

   This arrangement is what lets the screenshots be large. Side by side they
   cost ONE image height of vertical budget instead of two, so the whole hero
   through its closing paragraph still clears the fold on a 14-inch MacBook
   while the images are meaningfully bigger than a stacked layout allows.

   Columns are equal — the two screenshots are the same size and share a row.

   The h1 spans both columns and adopts their tracks via `grid-template-columns:
   subgrid`, so each half lands on exactly the column its image occupies. That
   keeps the heading a single element rather than splitting it or hiding a
   duplicate for screen readers.

   Source order is h1 (both halves) then the two figures, so the mobile
   collapse reads as the complete sentence followed by both images — the right
   order on a phone rather than a compromise. */
@supports (grid-template-columns: subgrid) {
  @media (min-width: 900px) {
    .mk-split {
      grid-template-columns: 1fr 1fr;
      column-gap: var(--space-7);
      row-gap: var(--space-5);
    }
    /* One step down from full display size: at --fs-4xl each half wraps to
       three lines in half a container and pushes the images off the fold. */
    .mk-split__head {
      font-size: var(--fs-3xl);
      grid-column: 1 / -1;
      display: grid;
      grid-template-columns: subgrid;
      column-gap: var(--space-7);
      align-items: end;
    }
    .mk-split__head span { display: block; text-wrap: balance; }
    .mk-split__fig:nth-of-type(1) { grid-column: 1; }
    .mk-split__fig:nth-of-type(2) { grid-column: 2; }
  }
}

.mk-split__fig { margin: 0; display: flex; flex-direction: column; gap: var(--space-3); }

/* ============================================================================
   FORMS AND AUTH — added 2026-08-23
   Closes the gap NOT-DESIGNED.md called "Any form control. No inputs, selects,
   checkboxes or validation states." Until now the six form-bearing pages
   (login, signup, forgot, reset, waitlist, contact) ran on
   assets/css/styles.css — a separate stealth-era sheet with its own retired
   --teal #2097A5, Avenir Next, a pure-black ground and no theme support. A
   visitor clicking the homepage's primary CTA landed on what read as a
   different company's site. These classes replace that sheet.

   Input borders use --color-border-control, NOT --color-border-strong. An
   input is identified by its border alone, so WCAG 1.4.11's 3:1 applies —
   the reason accessibility.md gives for the same rule on buttons.
   ========================================================================== */

/* ---- Auth page shell ----------------------------------------------------- */
/* Nav and footer stay, so an auth page is a page OF the site rather than a
   detour off it. body is the column; main takes the slack, which centres the
   card without a magic min-height that a short viewport would overflow. */
.mk-page--auth { min-height: 100vh; display: flex; flex-direction: column; }
.mk-page--auth > main {
  flex: 1 0 auto;
  display: grid; place-items: center;
  padding-block: var(--space-10) var(--space-9);
  padding-inline: var(--gutter);
}

.mk-auth {
  width: 100%; max-width: 27rem;
  display: flex; flex-direction: column; gap: var(--space-6);
}
/* The waitlist form is four fields wide-ish; give it a little more room. */
.mk-auth--wide { max-width: 31rem; }

.mk-auth__card {
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-node);
  padding: var(--space-8);
  display: flex; flex-direction: column; gap: var(--space-6);
}
@media (max-width: 480px) { .mk-auth__card { padding: var(--space-6); } }

.mk-auth__head { display: flex; flex-direction: column; gap: var(--space-3); }
/* NOT the global h1 — --fs-4xl is 36–52px and would dwarf a 432px card. */
.mk-auth__title {
  font-size: var(--fs-2xl); line-height: var(--lh-snug);
  letter-spacing: -0.012em; font-weight: var(--weight-semibold);
}
.mk-auth__sub { font-size: var(--fs-sm); color: var(--color-text-secondary); }

/* The link row under the card, outside it — secondary routes should not read
   as part of the form they are an alternative to. */
.mk-auth__foot {
  display: flex; flex-direction: column; gap: var(--space-3);
  text-align: center; font-size: var(--fs-sm);
  color: var(--color-text-secondary);
}
.mk-auth__foot a { font-weight: var(--weight-medium); }

/* ---- Form ---------------------------------------------------------------- */
.mk-form { display: flex; flex-direction: column; gap: var(--space-5); }
.mk-field { display: flex; flex-direction: column; gap: var(--space-2); }

/* The product's label treatment, one step down in tracking: at 11px mono with
   0.16em a four-word label ("Confirm new password") runs wider than the field.
   0.08em keeps the family and the case and still fits. */
.mk-field__label {
  font-family: var(--font-family-mono);
  font-size: var(--fs-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}
.mk-field__hint { font-size: var(--fs-xs); color: var(--color-text-muted); }
/* "optional" rides inside the label rather than sitting under the field: the
   distinction the visitor needs is which fields they may skip, and reading it
   after the input is reading it too late. Lowercase and untracked so it does
   not compete with the label it qualifies. */
.mk-field__optional {
  font-family: var(--font-family); font-size: var(--fs-xs);
  font-weight: var(--weight-regular); letter-spacing: 0;
  text-transform: none; color: var(--color-text-subtle);
}
.mk-auth__aside { text-align: center; font-size: var(--fs-sm); }

.mk-input {
  width: 100%;
  font: inherit; font-size: var(--fs-sm);
  color: var(--color-text);
  background: var(--color-surface);
  border: 1px solid var(--color-border-control);
  border-radius: var(--radius-md);
  padding: 10px var(--space-4);
  transition: border-color var(--duration-fast) var(--ease),
              background var(--duration-fast) var(--ease);
}
/* --color-text-muted, not --color-text-placeholder. The product's placeholder
   token measures 2.96 (dark) / 2.36 (light) on the input ground, and WCAG
   treats placeholder text as text. No field on these pages sets a placeholder
   today — every one has a real <label> — so this changes nothing on screen; it
   means the component is safe the first time somebody adds one. */
.mk-input::placeholder { color: var(--color-text-muted); opacity: 1; }
.mk-input:hover { border-color: var(--color-text-muted); }
.mk-input:focus { border-color: var(--color-brand); background: var(--color-surface-raised); }
/* :focus-visible in the base sheet draws the ring; keep it, drop the doubled
   border-radius rounding that fires on a control already carrying --radius-md. */
.mk-input:focus-visible { outline: 2px solid var(--color-brand); outline-offset: 1px; }
.mk-input:disabled { opacity: .6; cursor: not-allowed; }
/* Beta keys and passwords are read character by character. */
.mk-input--mono { font-family: var(--font-family-mono); letter-spacing: 0.04em; }

/* Chrome/Safari paint their own autofill ground and it is always light blue,
   which looks broken on a dark card. Repaint it with the token colours. */
.mk-input:-webkit-autofill,
.mk-input:-webkit-autofill:hover,
.mk-input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--color-text);
  -webkit-box-shadow: 0 0 0 40px var(--color-surface) inset;
  caret-color: var(--color-text);
}

/* ---- Select -------------------------------------------------------------- */
/* A native select cannot be styled consistently across browsers, and its own
   arrow is drawn in the OS chrome colour — which is black on a dark card in
   Chrome on macOS. appearance: none removes it; the chevron is the wrapper's
   ::after so it inherits currentColor and follows the theme, which a
   background-image data URI cannot do. */
.mk-select { position: relative; display: block; }
.mk-select::after {
  content: ""; position: absolute; right: var(--space-4); top: 50%;
  width: 8px; height: 8px; margin-top: -6px;
  border-right: 1.5px solid var(--color-text-muted);
  border-bottom: 1.5px solid var(--color-text-muted);
  transform: rotate(45deg);
  pointer-events: none;
}
.mk-select .mk-input {
  appearance: none; -webkit-appearance: none;
  padding-right: var(--space-9);
  cursor: pointer;
}
/* The options list is painted by the OS, not by this sheet. Naming the colours
   is what stops Chrome rendering white-on-white on a dark page. */
.mk-select .mk-input option {
  background: var(--color-surface-raised); color: var(--color-text);
}
/* Nothing chosen yet: the prompt should read as a placeholder, not an answer. */
.mk-select .mk-input:invalid,
.mk-select .mk-input[data-empty="true"] { color: var(--color-text-muted); }

/* A submit button is the only control in its row, so it takes the row. */
.mk-btn--block { width: 100%; }
.mk-btn:disabled { opacity: .55; cursor: not-allowed; filter: none; }

/* ---- Alerts / validation ------------------------------------------------- */
/* [hidden] must beat the flex display these carry, or an error banner that JS
   has hidden stays on screen. */
.mk-alert[hidden] { display: none; }
.mk-alert {
  display: flex; gap: var(--space-3);
  font-size: var(--fs-sm);
  padding: var(--space-4) var(--space-5);
  border: 1px solid; border-radius: var(--radius-md);
}
.mk-alert::before { font-family: var(--font-family-mono); font-weight: var(--weight-semibold); }
.mk-alert--error {
  color: var(--color-danger-text);
  background: var(--color-danger-tint);
  border-color: color-mix(in srgb, var(--color-danger) 45%, transparent);
}
.mk-alert--error::before { content: "!"; }
.mk-alert--ok {
  color: var(--color-ok-text);
  background: var(--color-ok-tint);
  border-color: color-mix(in srgb, var(--color-ok) 45%, transparent);
}
.mk-alert--ok::before { content: "\2713"; }

/* ---- Result panel (submitted / check your email / invalid link) ---------- */
.mk-result { display: flex; flex-direction: column; gap: var(--space-5); }
.mk-result[hidden] { display: none; }

/* ---- Slim footer --------------------------------------------------------- */
/* Auth and utility pages carry the legal row only — a four-column sitemap
   under a 432px sign-in card is more footer than page. The legal row's own
   margin/padding/border exist to separate it FROM those columns; with nothing
   above it they draw a rule against the footer's own top border. */
.mk-footer--slim { margin-top: 0; padding-block: var(--space-6); }
.mk-footer--slim .mk-footer__legal {
  margin-top: 0; padding-top: 0; border-top: 0;
  justify-content: center;
}

/* iOS Safari zooms the whole page when a focused input's font-size is under
   16px, and does not zoom back out. --fs-sm is 15px, so every field on every
   auth page would punt a phone visitor into a zoomed viewport mid-sign-in.
   Bumped for touch only — the 15px is right on a pointer device. */
@media (pointer: coarse) {
  .mk-input { font-size: 1rem; }
}
