/* Page styles for templates/landing.html — the marketing landing page at `/`
   (the application lives at /analyze and never loads this file). Page-specific
   rules only; shared patterns belong in styles.css. Everything is tokens, so
   dark mode needs no rules of its own — except the fake invoice paper in the
   demo, which is white in both themes because a document is white paper.
   All selectors stay scoped under .mk-* or .landing-page — bare element rules
   at this level of styles.css have leaked site-wide before.

   2026-09 UX audit: an outcome-led introduction, interactive source evidence,
   factual plan information and unframed explanatory sections. The sample and
   plans are distinct objects; ordinary prose does not need card chrome. */

/* The page's one content column, declared once so the top bar, the main
   sections and the shared footer cannot drift apart.

   --mk-topnav-h is the sticky bar's approximate height (10px padding twice
   plus a ~40px control row); nothing in CSS can read it, and erring high only
   leaves an anchored heading a little more air. */
.landing-page {
  --mk-content-max: 1200px;
  --mk-gutter: 24px;
  --mk-topnav-h: 60px;
  /* The demo's fake invoice paper — white in both themes. */
  --mk-paper: #ffffff;
  --mk-paper-ink: #1a1d26;
  /* 4.98:1 on white — the paper's secondary text must hold AA on its own,
     because the paper ignores the theme. */
  --mk-paper-soft: #5f6880;
  --mk-paper-line: #e6e8f0;
}

/* WCAG 1.4.10 Reflow — at 320 px viewport the German landing overflows by
   84 px of horizontal scroll (scrollWidth 389 vs clientWidth 305). The root
   cause is .mk-hero h1: min-content width 340.9 px vs 312 px of available
   column — German compound nouns like "Geschäftsdokumenten" cannot break.
   At 375 px the excess is still 29 px. English is clean (0 px overshoot).

   It must be `anywhere`, NOT `break-word`. Both break a too-long word when
   rendering a line, but per CSS Text 3 only `anywhere` is allowed to affect
   the element's INTRINSIC min-content size — and intrinsic size is what is
   wrong here. `break-word` was measured on this exact page and changed
   nothing: the h1 min-content stayed 340.9 px, still forced .mk-landing to
   388.9 px, and the 84 px overflow survived. Switching to `anywhere` drops
   the h1 min-content to 31.2 px and scrollWidth to 305 = clientWidth.

   hyphens: auto is kept for typography, not for the fix — where the browser
   ships a dictionary for the <html lang> it breaks at syllable boundaries
   with a hyphen, which reads far better than a mid-word split. It is NOT
   load-bearing: headless Chromium has no German dictionary and hyphenated
   nothing, which is precisely why the guarantee has to come from `anywhere`.
   -webkit-hyphens is required for Safari. */
.landing-page :is(h1, h2, h3) {
  overflow-wrap: anywhere;
  -webkit-hyphens: auto;
  hyphens: auto;
}

/* Jumping to #how-it-works aligns the section's top edge with the viewport's,
   which the sticky bar covers — the heading and the first step landed behind
   it. scroll-margin is inert until an element is actually scrolled to, so
   claiming every id on the page costs nothing and covers anchors added later. */
.landing-page [id] {
  scroll-margin-top: calc(var(--mk-topnav-h) + var(--space-3));
}

/* ── Top navigation ───────────────────────────────────────────────────── */

.mk-topnav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}

.mk-topnav-inner {
  max-width: var(--mk-content-max);
  margin: 0 auto;
  padding: 10px var(--mk-gutter);
  display: flex;
  align-items: center;
  gap: var(--space-6);
}

/* Spacing/alignment of the brand tile lives on .logo-mark (styles.css) so the
   sidebar and this top nav cannot drift; only the SIZE differs here — and the
   marketing bar is the one surface where the brand has to be recognised by a
   stranger in the first viewport, beside a filled CTA and five nav links.

   It sat a step BELOW the shared 1.25rem (--text-xl, 1.1rem) with a 24px tile,
   which made it the quietest thing in its own bar. Restored to the shared
   --text-2xl and given a proportionate 30px tile: the wordmark and the mark
   now read as one lockup and the bar's height is unchanged (the tile is still
   shorter than the CTA). Nothing else about the bar changes, and the app
   sidebar's logo is untouched — every rule here is scoped to .mk-topnav-logo.

   NOT display:flex on the anchor: "Doc" is a bare text node between the mark
   and .logo-accent, so any flex context would run it as an anonymous item and
   split the wordmark into two boxes. The anchor stays inline and the tile
   keeps aligning itself, exactly as in the sidebar. */
.mk-topnav-logo {
  font-size: var(--text-2xl);
  letter-spacing: -0.028em;
  /* The lockup is a flex item in .mk-topnav-inner and the bigger wordmark made
     it shrinkable: at 960px in German — where the nav labels and the CTA both
     wrap — the browser broke it between the mark and "DocSolved", two lines.
     A brand lockup does not wrap and does not shrink. The nav links beside it
     already wrap, which is the right thing to give way. */
  flex: 0 0 auto;
  white-space: nowrap;
}
.mk-topnav-logo .logo-mark {
  width: 30px;
  height: 30px;
  border-radius: 9px;
  font-size: 15px;
  margin-right: 10px;
}

.mk-topnav-menu {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  margin-left: auto;
}

.mk-topnav-links {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

/* Note: --text is an alias of --ink (styles.css sets --text: var(--ink) in
   every theme block). Using var(--text) as the rest colour and var(--ink) on
   hover is therefore a no-op — both values resolve to the same computed
   colour and the hover has zero visible effect. Use var(--ink-soft) for the
   rest state so the hover-to-var(--ink) transition actually reads. This
   matches the .nav-link-btn pattern in styles.css (rest --ink-soft, hover
   --ink). WCAG AA contrast check: #5a6178 on #f6f7fb (light) = 5.74:1 ✓;
   #9aa0b4 on #0a0c12 (dark) = 7.50:1 ✓. */
.mk-topnav-links a {
  color: var(--ink-soft);
  text-decoration: none;
  font-weight: 500;
  font-size: var(--text-md);
  padding: 8px 2px;
}
.mk-topnav-links a:hover { color: var(--ink); }

.mk-topnav-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.mk-topnav-lang {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--muted);
}
.mk-topnav-lang select {
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: var(--text-sm);
  padding: 6px 8px;
}

/* The two quiet, text-only actions in the bar: "Sign in" for a stranger,
   "Sign out" for a signed-in visitor. Same slot, same weight — the primary
   button beside them is the one that should draw the eye in both states.
   Rest colour is var(--ink-soft), not var(--text): --text aliases --ink
   (styles.css sets --text: var(--ink) in every theme block), so a
   rest=--text / hover=--ink pair is a no-op with zero visible hover
   feedback. var(--ink-soft) at rest gives the hierarchy gap the design
   needs and lets the hover-to-ink transition land. */
.mk-topnav-signin,
.mk-topnav-signout {
  background: none;
  border: none;
  cursor: pointer;
  font: inherit;
  font-weight: 500;
  font-size: var(--text-md);
  color: var(--ink-soft);
  padding: 8px 10px;
}
.mk-topnav-signin:hover,
.mk-topnav-signout:hover { color: var(--ink); }

/* The bar must never force a horizontal scroll: long locale strings wrap
   inside the buttons rather than widening the page. */
.mk-topnav-cta { white-space: normal; }

/* The two auth variants of the top bar's action slot. Only one is ever
   unhidden (clerk.js reconciles [data-auth-state] against the live session),
   and this wrapper reproduces the row .mk-topnav-actions gave the buttons when
   they were its direct children — same gap, same centring.

   The inline-flex does NOT need a matching [hidden] guard: styles.css declares
   `[hidden], .hidden { display: none !important; }` once, globally, precisely
   so a new JS-toggled block never has to remember one. */
.mk-topnav-authset {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
}

.mk-nav-toggle {
  display: none;
  background: none;
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  color: var(--text);
  cursor: pointer;
  padding: 8px;
  margin-left: auto;
}

/* ── Page scaffold ────────────────────────────────────────────────────── */

.mk-landing {
  max-width: var(--mk-content-max);
  margin: 0 auto;
  padding: var(--space-10) var(--mk-gutter) var(--space-12);
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
}

/* Act boundaries. The page argues in three movements — understand (hero +
   facts), trust (uncertainty, corrections, structured data), integrate
   (lifecycle + your stack) — and then hands over to evidence, price and
   questions. A uniform gap made all nine sections read as one long column, so
   the two sections that OPEN a movement get extra air above them and a hairline
   to land the eye on. Spacing only: no copy, no new element, and the rule names
   the two sections rather than an nth-child, so reordering cannot silently move
   a boundary. */
.mk-exception,
.mk-handoff {
  margin-top: var(--space-6);
  padding-top: var(--space-10);
  border-top: 1px solid var(--border);
}

.mk h2 {
  margin: 0 0 var(--space-3);
  font-weight: 700;
  letter-spacing: -0.028em;
  font-size: clamp(1.45rem, 2.6vw, 2.125rem);
  line-height: 1.2;
  color: var(--ink);
}

/* Base paragraph for `.mk` sections. The `:where()` is load-bearing: written
   plainly as `.mk > p` this rule scores 0-1-1 and silently outranks EVERY
   single-class paragraph rule below it (.mk-hero-badge, .mk-lead,
   .mk-hero-note, .mk-eyebrow, .mk-evidence-sub, .mk-trust-sub,
   .mk-pricing-sub, .mk-links) — it zeroed all their margins and flattened the
   page to one 14px/--text paragraph style, so the hero note sat glued to the
   buttons with a 64px void under it and "LIVE DEMO" rendered as plain dark
   14px instead of the small uppercase accent line. :where() drops it to 0-0-1:
   it still supplies the defaults a classed paragraph does NOT declare (that is
   how .mk-pricing-sub and .mk-links keep their font-size), while each class
   wins the properties it does declare. Do not unwrap it. */
:where(.mk) > p {
  margin: 0;
  max-width: 720px;
  font-size: var(--text-md);
  line-height: 1.6;
  color: var(--text);
}

/* `:where(.mk) > p` is margin-0 because most sections carry a single lead
   paragraph. A section's second paragraph (the handoff's tool note) would
   otherwise run into the figure above it. */
.mk-handoff-tools { margin-top: var(--space-6); }

/* Figures are the explanatory product stories (exception, supplier notes,
   XML, handoff): real <figure>/<figcaption>, so the caption is the story a
   screen reader gets. The UA figure margin is reset once here; each figure
   sets its own spacing below. */
.mk figure { margin: var(--space-6) 0 0; }
.mk figcaption {
  margin-top: var(--space-3);
  max-width: 720px;
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--ink-soft);
}

.mk-links { margin: var(--space-4) 0 0; display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-5); }

/* ── Action links ─────────────────────────────────────────────────────────
   The standalone "go look at this" links (API docs, benchmarks, a plan
   comparison): a label plus a directional arrow, never the raw underlined
   blue-link look. Shared by `.mk-links` (inline groups of 1-3) and
   `.mk-proof-list` (the evidence rows, styled just below) so both read as
   one system. Label carries the weight in --ink (not --accent-text: the
   brief is restrained purple, not a page of purple links); the arrow is the
   quiet affordance that turns to accent on interaction. No permanent
   underline — text-decoration only arrives with hover/focus, alongside the
   arrow's nudge, so the two cues land together. */
.mk-links a,
.mk-proof-list a {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 2px;
  border-radius: 4px;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
}
.mk-links a::after,
.mk-proof-list a::after {
  content: "\2192";
  color: var(--muted);
  transition: transform 0.15s ease, color 0.15s ease;
}
.mk-links a:hover,
.mk-links a:focus-visible,
.mk-proof-list a:hover,
.mk-proof-list a:focus-visible {
  color: var(--accent-text);
  text-decoration: underline;
  text-decoration-color: currentColor;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
.mk-links a:hover::after,
.mk-links a:focus-visible::after,
.mk-proof-list a:hover::after,
.mk-proof-list a:focus-visible::after {
  color: var(--accent-text);
  transform: translateX(2px);
}
.mk-links a:focus-visible,
.mk-proof-list a:focus-visible { outline-offset: 2px; border-radius: 4px; }
@media (prefers-reduced-motion: reduce) {
  .mk-links a::after,
  .mk-proof-list a::after { transition: none; }
}

/* Small uppercase section opener (mock: "LIVE DEMO"). --accent-text, not
   --accent: in dark mode #7b68f0 on the page ground is only 4.77:1 while the
   text variant clears it with margin in both themes. */
.mk-eyebrow {
  margin: 0 0 var(--space-3);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent-text);
}

/* ── Motion primitives ────────────────────────────────────────────────── */

/* Two of the page's three motion primitives are declared once, here, and used
   by name everywhere below. The third (STORY) has no shared look: each figure
   spells out its own beats beside its own rules.

   Everything in this block hangs off html[data-mk-motion], which the inline
   boot in templates/landing.html writes before first paint and withdraws after
   3 s if static/marketing-motion.js never arrives. That gate is the whole
   safety story: without it, nothing here selects anything, so JavaScript that
   fails, a reduced-motion preference and a crawler all get the finished page —
   no hidden text, no empty groups, no dependence on a script. */

/* REVEAL — a one-shot staggered entrance for a group of peers. The marker
   (data-mk-reveal) is server-rendered on the container; the controller only
   adds .is-revealed when the group is meaningfully in view, and never removes
   it. Transform and opacity only: no width, no height, nothing that could
   reflow the section around it. */
[data-mk-motion] [data-mk-reveal] > * {
  transition: opacity 0.5s cubic-bezier(0.2, 0.7, 0.2, 1), transform 0.5s cubic-bezier(0.2, 0.7, 0.2, 1);
}
[data-mk-motion] [data-mk-reveal]:not(.is-revealed) > * {
  opacity: 0;
  transform: translateY(10px);
}
/* The stagger. Six steps is every group this page has; a seventh item would
   simply arrive with the sixth, which is the right failure. Deliberately
   short — 70 ms — so the row reads as ordered, not as a queue. */
[data-mk-motion] [data-mk-reveal] > *:nth-child(2) { transition-delay: 0.07s; }
[data-mk-motion] [data-mk-reveal] > *:nth-child(3) { transition-delay: 0.14s; }
[data-mk-motion] [data-mk-reveal] > *:nth-child(4) { transition-delay: 0.21s; }
[data-mk-motion] [data-mk-reveal] > *:nth-child(5) { transition-delay: 0.28s; }
[data-mk-motion] [data-mk-reveal] > *:nth-child(n + 6) { transition-delay: 0.35s; }

/* AMBIENT — the page's only repeating animation, and the only one allowed to
   be: a slow breath on the hero's delivery dot once the frame says delivered.
   It runs ONLY on the explicit "on" state, which marketing-motion.js writes
   while the frame is in the viewport and the tab is visible, so it can never
   keep running for something nobody can see. */
@keyframes mk-ambient-pulse {
  0% { opacity: 0.55; transform: scale(0.65); }
  60%, 100% { opacity: 0; transform: scale(1.6); }
}
/* A ring on a pseudo-element, not the dot itself: the dot stays exactly the
   size and colour the static render gives it, and the whole file bar it sits
   in is already aria-hidden decoration. */
.mk-demo[data-mk-ambient="on"] .mk-demo-statusdot::after {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: 50%;
  border: 1px solid var(--success);
  animation: mk-ambient-pulse 3.2s ease-out infinite;
}

/* Belt and braces: a preference that changes after load (a system setting
   flipped in another window) silences every primitive immediately, without
   waiting for a reload. */
@media (prefers-reduced-motion: reduce) {
  [data-mk-motion] [data-mk-reveal] > * { transition: none; }
  [data-mk-motion] [data-mk-reveal]:not(.is-revealed) > * { opacity: 1; transform: none; }
  .mk-demo[data-mk-ambient="on"] .mk-demo-statusdot::after { animation: none; display: none; }
}

/* ── Hero stage ───────────────────────────────────────────────────────── */

/* The hero sits on a deliberate dark stage in BOTH themes (decision-log,
   2026-09-15): a near-black/navy ground with one static spectral halo behind
   the product frame. Everything inside keeps using tokens — the stage simply
   re-declares the dark palette on itself, so no child rule needs a theme
   branch and nothing here can leak into the light page below.

   The three --mk-stage-* colours exist ONLY on this element (a test pins
   that). They are lighting for the frame, not decoration: two pseudo-elements,
   gradient-stop falloff (no filter: blur, no backdrop-filter), no animation,
   clipped by overflow so the lobes never widen the page. */
.mk-stage {
  --bg: #0a0c12;
  --surface: #141826;
  --surface-soft: #1a1f30;
  --surface-soft-hover: #242a3e;
  --ink: #edeff6;
  --ink-soft: #9aa0b4;
  --accent: #7b68f0;
  --accent-strong: #8f7ef3;
  --accent-light: rgba(123, 104, 240, 0.15);
  --accent-border: rgba(123, 104, 240, 0.45);
  --accent-cta: #6450e3;
  --accent-cta-hover: #5843d6;
  --accent-text: #a296f6;
  --border: #232839;
  --border-light: #1b2030;
  --border-strong: #2e3448;
  --success: #3ecf8e;
  --success-text: #3ecf8e;
  --success-soft: rgba(62, 207, 142, 0.14);
  --warn-text: #f0b429;
  --warn-soft: rgba(240, 180, 41, 0.14);
  --shadow: 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2);
  --shadow-lg: 0 24px 60px -20px rgba(0, 0, 0, 0.65);
  --text: var(--ink);
  --muted: var(--ink-soft);
  /* Stage-only lighting colours (space-separated RGB for rgb(... / a)). */
  --mk-stage-cyan: 96 214 255;
  --mk-stage-violet: 123 104 240;
  --mk-stage-magenta: 232 96 200;

  position: relative;
  isolation: isolate;
  overflow: hidden;
  color: var(--ink);
  background: linear-gradient(180deg, #0c1120 0%, #0a0c12 100%);
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

.mk-stage::before,
.mk-stage::after {
  content: "";
  position: absolute;
  z-index: 0;
  pointer-events: none;
  border-radius: 50%;
  mix-blend-mode: screen;
  transform: translate(-50%, -50%);
}
/* Primary lobe: a violet core cooling to cyan, centred on the frame's column
   (right ~60% of the stage) and wider than the frame so it reads as light
   spilling from behind it. Peak alpha stays under 0.2. */
.mk-stage::before {
  width: min(1240px, 130vw);
  height: 780px;
  left: 64%;
  top: 46%;
  background: radial-gradient(
    ellipse at 50% 50%,
    rgb(var(--mk-stage-violet) / 0.36) 0%,
    rgb(var(--mk-stage-cyan) / 0.19) 30%,
    rgb(var(--mk-stage-cyan) / 0.06) 52%,
    transparent 70%
  );
}
/* Secondary lobe: an extremely faint magenta warmth low on the left, so the
   copy column is not lit flat. Its alpha is a fraction of the primary's. */
.mk-stage::after {
  width: 760px;
  height: 540px;
  left: 24%;
  top: 88%;
  background: radial-gradient(
    ellipse at 50% 50%,
    rgb(var(--mk-stage-magenta) / 0.13) 0%,
    rgb(var(--mk-stage-magenta) / 0.045) 40%,
    transparent 66%
  );
}

/* ── Hero ─────────────────────────────────────────────────────────────── */

/* Layout shared with the signed-out upload gate (analyze_guest.html reuses
   .mk-hero and overrides it to a single column); the stage-only sizing is
   scoped below so that page is untouched. */
.mk-hero {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  column-gap: var(--space-12);
  align-items: center;
  text-align: left;
}
.mk-stage .mk-hero {
  position: relative;
  z-index: 1;
  max-width: var(--mk-content-max);
  margin: 0 auto;
  padding: var(--space-12) var(--mk-gutter);
  min-height: min(calc(100vh - var(--mk-topnav-h)), 720px);
}

.mk-hero-copy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  min-width: 0;
}

.mk-hero-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-5);
  padding: 0;
  font-size: var(--text-xs);
  color: var(--ink-soft);
}
.mk-stage .mk-hero-badge {
  padding: 4px 10px 4px 8px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.03);
}
.mk-hero-badge-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--success);
  flex-shrink: 0;
}

/* The hero clamp() is the one font-size allowed outside the --text-* scale
   (design-tokens-guidelines.md). Slightly below the old 3.25rem cap: the
   headline now shares the row with the product frame. */
.mk-hero h1 {
  margin: 0 0 var(--space-4);
  font-weight: 700;
  letter-spacing: -0.035em;
  font-size: clamp(2rem, 3.4vw, 3rem);
  line-height: 1.06;
  color: var(--ink);
  text-wrap: balance;
}

.mk-lead {
  margin: 0 0 var(--space-6);
  max-width: 520px;
  font-size: var(--text-lg);
  line-height: 1.6;
  color: var(--ink-soft);
  text-wrap: pretty;
}

.mk-hero-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  gap: var(--space-3);
  align-items: center;
}
/* Dark stage: the neutral secondary button needs a visible edge, and the
   primary a faint accent halo so it reads as the one action. */
.mk-stage .mk-hero-actions .primary-btn { box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.06), 0 8px 24px -12px rgba(123, 104, 240, 0.7); }
.mk-stage .mk-hero-actions .secondary-btn { border-color: var(--border-strong); }

.mk-hero-note {
  margin: var(--space-4) 0 0;
  font-size: var(--text-sm);
  color: var(--muted);
}

/* Short desktop viewports (1280×720 laptops): trade stage air for the whole
   frame staying inside the first screen. */
@media (min-width: 1101px) and (max-height: 780px) {
  .mk-stage .mk-hero { padding-top: var(--space-6); padding-bottom: var(--space-6); }
}

/* ── Product frame (the demo) ─────────────────────────────────────────── */

/* A miniature of the real review workspace: file bar, document preview beside
   the extracted-field list, the API output underneath. Same 1px-seam grid as
   the app's containers. It is the page's visual subject, so it carries the
   only large shadow on the stage. */
.mk-demo {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg), 0 0 0 1px rgba(255, 255, 255, 0.025);
  overflow: hidden;
  text-align: left;
}

.mk-demo-bar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  font-size: var(--text-xs);
  min-width: 0;
}
.mk-demo-file {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  color: var(--ink);
}
.mk-demo-file svg { color: var(--success); flex-shrink: 0; }
.mk-demo-docname {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mk-demo-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--ink-soft);
  white-space: nowrap;
}
.mk-demo-statusdot {
  position: relative; /* anchors the ambient ring (see Motion primitives) */
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--success);
}
.mk-demo-docmeta {
  margin-left: auto;
  font-size: var(--text-2xs);
  color: var(--ink-soft);
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  padding: 2px 7px;
  white-space: nowrap;
}
/* Replay sits in the Output head (see .mk-demo-outend); the file bar has no
   free width on desktop, so it never goes there. */
.mk-demo-replay {
  font: inherit;
  font-size: var(--text-2xs);
  font-weight: 600;
  color: var(--ink-soft);
  background: transparent;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  padding: 2px 8px;
  white-space: nowrap;
  flex-shrink: 0;
  cursor: pointer;
}
.mk-demo-replay:hover { color: var(--ink); background: var(--surface-soft); }

.mk-demo-body {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 54fr) minmax(0, 46fr);
  gap: 1px;
  background: var(--border);
}
.mk-demo-doc,
.mk-demo-fields {
  background: var(--surface);
  min-width: 0;
}
.mk-demo-doc { padding: var(--space-3) var(--space-4) var(--space-4); }
.mk-demo-fields { display: flex; flex-direction: column; padding: var(--space-3) var(--space-2) var(--space-2); }

.mk-demo-panehead {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
  margin: 0 0 var(--space-3);
  padding: 0 var(--space-2);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.mk-demo-doc .mk-demo-panehead { padding: 0; }
.mk-demo-panehead span + span {
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  text-align: right;
}
.mk-demo-fields .mk-demo-panehead { flex-direction: column; align-items: flex-start; gap: 1px; }
.mk-demo-fields .mk-demo-panehead span + span { text-align: left; }

/* The fake invoice. White in both themes (see --mk-paper above); every text
   colour inside is therefore fixed, not tokenized. */
.mk-demo-paper {
  background: var(--mk-paper);
  color: var(--mk-paper-ink);
  border-radius: 6px;
  padding: var(--space-4) 18px var(--space-3);
  font-size: 0.7rem;
  line-height: 1.6;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35), 0 0 0 1px rgba(0, 0, 0, 0.25);
}

.mk-demo-paperhead {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}
.mk-demo-papertitle {
  display: block;
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.2;
}
.mk-demo-papersub {
  display: block;
  font-size: 0.6rem;
  letter-spacing: 0.08em;
  color: var(--mk-paper-soft);
}
.mk-demo-paperseller {
  text-align: right;
  font-size: 0.62rem;
  line-height: 1.5;
  color: var(--mk-paper-soft);
}

.mk-demo-rows {
  display: grid;
  grid-template-columns: minmax(84px, 110px) minmax(0, 1fr);
  gap: 1px var(--space-3);
  align-items: baseline;
}
.mk-demo-rowlabel { color: var(--mk-paper-soft); }
.mk-demo-rowvalue { min-width: 0; overflow-wrap: anywhere; }
.mk-demo-rowlabel-strong { color: var(--mk-paper-ink); font-weight: 600; }
.mk-demo-rowvalue-strong { font-weight: 600; }

.mk-demo-items {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto auto;
  gap: 1px var(--space-3);
  margin: var(--space-2) 0;
  padding: 3px 0;
  border-top: 1px solid var(--mk-paper-line);
  border-bottom: 1px solid var(--mk-paper-line);
  font-size: 0.62rem;
  color: var(--mk-paper-ink);
}
.mk-demo-items span:nth-child(4n) { text-align: right; }

.mk-demo-totals { max-width: 240px; margin-left: auto; margin-top: var(--space-1); }

/* A source mark on the page: the bounding box the value was read from. Rest:
   a quiet accent outline with a faint tint (every marked value is a box);
   active: solid fill, the money shot. Presentational — aria-hidden and no
   pointer events; the field list is the control. --accent-cta for the fill:
   white-on-#7b68f0 is only 4.13:1 on the always-white paper. */
.mk-mark {
  display: inline;
  border-radius: 2px;
  padding: 0 3px;
  margin: 0 -3px;
  color: inherit;
  background: rgba(91, 69, 224, 0.07);
  box-shadow: 0 0 0 1px rgba(91, 69, 224, 0.35);
  pointer-events: none;
  transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
  overflow-wrap: anywhere;
}
.mk-mark.is-active {
  background: var(--accent-cta);
  color: #fff;
  box-shadow: 0 0 0 1.5px var(--accent-cta), 0 0 0 4px rgba(91, 69, 224, 0.22);
}

/* Anchored but NOT selectable: every other value this sample located on the
   page (each one a fixture field with a real bounding box). Without these the
   five source marks make the rest of the paper read as ignored — which is the
   opposite of what the extraction did. A neutral hairline, no tint and no
   fill, so the hierarchy stays unmistakable:

     hairline        the extraction located this value here
     accent outline  … and Source check can select it
     accent fill     … and it is the selected one (the connector's end)

   Never a check mark at any strength: a tick means VERIFIED in this product,
   and a person does that. Presentational — the paper is one role="img". */
.mk-anchor {
  display: inline;
  border-radius: 2px;
  padding: 0 3px;
  margin: 0 -3px;
  box-shadow: 0 0 0 1px var(--mk-paper-line);
  overflow-wrap: anywhere;
}

/* Right pane: the extracted-field list — the only control for selection. */
.mk-demo-fieldlist {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.mk-demo-field {
  display: block;
  width: 100%;
  text-align: left;
  font: inherit;
  cursor: pointer;
  padding: 6px 10px;
  border-radius: 8px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--ink);
  transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.mk-demo-field:hover { background: var(--surface-soft); }
.mk-demo-field.is-active {
  border-color: var(--accent-border);
  background: var(--accent-light);
  box-shadow: inset 2px 0 0 0 var(--accent);
}

.mk-demo-fieldrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: 1px;
}
.mk-demo-fieldlabel { font-size: var(--text-xs); color: var(--ink-soft); }
.mk-demo-fieldvalue {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-md);
  font-weight: 500;
  /* Explicit: the mono 600 face is 5px taller at line-height:normal, so the
     selected row (600) would grow and move the list on every selection. */
  line-height: 1.3;
  letter-spacing: -0.01em;
  overflow-wrap: anywhere;
}
.mk-demo-field.is-active .mk-demo-fieldvalue { font-weight: 600; }

/* Confidence chip — mono, status-tinted. Text tokens, never the fill hues:
   the pinned ratios live on --success-text / --warn-text. */
.mk-conf {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: 600;
  padding: 1px 5px;
  border-radius: 5px;
  flex-shrink: 0;
}
.mk-conf-ok { color: var(--success-text); background: var(--success-soft); }
.mk-conf-warn { color: var(--warn-text); background: var(--warn-soft); }

/* Field → source connector: one restrained stroke from the selected row's
   left edge to the right edge of its mark, laid over both panes. Measured by
   hero-demo.js; hidden until it has a path and on stacked layouts. */
.mk-demo-link {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  z-index: 2;
  display: none;
}
.mk-demo-link[data-mk-link="on"] { display: block; }
.mk-demo-linkpath {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-linecap: round;
  opacity: 0.9;
}
.mk-demo-linkdot { fill: var(--accent); }

/* Output strip: JSON / webhook tabs in the same mono the app uses for machine
   values. Fixed panel height so switching tabs never moves the frame. */
.mk-demo-out {
  border-top: 1px solid var(--border);
  background: var(--surface-soft);
}
/* Head row: the tablist on the left, the delivery confirmation on the right.
   The head owns the rule and the side padding so both sit on one baseline. */
.mk-demo-outhead {
  display: flex;
  align-items: flex-end;
  gap: var(--space-3);
  padding: 0 10px;
  border-bottom: 1px solid var(--border);
  min-width: 0;
}
.mk-demo-tabs {
  display: flex;
  gap: 2px;
  padding-top: 4px;
  flex-shrink: 0;
}
/* Right end of the head: the DELIVERED confirmation (in the webhook record's
   own words) and Replay. Both are always laid out — opacity carries the
   reveal — so nothing here ever moves the frame. */
.mk-demo-outend {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-left: auto;
  min-width: 0;
  padding: 4px 0 7px;
}
.mk-demo-delivered {
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--success-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mk-demo-deliveredmark { font-weight: 700; margin-right: 5px; }
.mk-demo-tab {
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink-soft);
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  padding: 6px 8px 7px;
  margin-bottom: -1px;
  cursor: pointer;
}
.mk-demo-tab:hover { color: var(--ink); }
.mk-demo-tab[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--accent); }

/* A compact developer preview, not a third pane: tight leading and padding.
   The JSON's seven lines set the height and min-height is exactly that
   (7 lines + vertical padding), so the shorter webhook record never moves
   the frame on a tab switch. */
.mk-demo-outpanel {
  padding: 5px 14px 6px;
  min-height: calc(11 * 1.2em + 11px); /* the JSON excerpt's eleven desktop lines */
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.2;
  color: var(--ink-soft);
}
/* Wrap rather than scroll: a scrollable <pre> would need its own tab stop
   (axe scrollable-region-focusable) and at 320px the lines do not fit. */
.mk-demo-json { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; overflow: hidden; }
.mk-demo-json code { font: inherit; }
.mk-json-key { color: var(--accent-text); }
.mk-json-str { position: relative; color: var(--ink); }
/* The story's placeholder for a value not extracted yet, laid over the start
   of the real value (which keeps its full width, so resolving never reflows
   the panel). Invisible in the static render and once the value is in. */
.mk-json-ghost {
  position: absolute;
  left: 0;
  top: 0;
  /* Never wraps: it inherits overflow-wrap: anywhere and at 320px a value
     split across two lines would give it a one-character box. */
  white-space: nowrap;
  opacity: 0;
  color: var(--ink-soft);
  pointer-events: none;
  user-select: none;
}
/* One block per line, sized to its text: a hidden line (rank 2 on phones)
   takes its line break with it, the highlight covers the text as an inline
   would, and the story can move a line (transform needs a box). */
.mk-json-line {
  display: block;
  width: max-content;
  max-width: 100%;
  /* Hanging indent: a value too long for the panel (the line-item object on a
     phone, buyer_name at 320px) wraps UNDER its own key instead of back to
     column zero, where it reads as a new line of JSON. */
  padding-left: 4ch;
  text-indent: -4ch;
  border-radius: 4px;
  transition: background 0.15s ease;
}
.mk-json-line.is-active {
  background: var(--accent-light);
  box-shadow: 0 0 0 2px var(--accent-light);
}
/* The one MULTI-LINE block in the excerpt. The hanging indent above only
   indents a block's FIRST line, so its own hard-wrapped lines (the nested
   object, the closing bracket) inherited the 4ch padding on top of the
   literal indentation they already carry and read four columns too deep.
   This block carries its structure in the payload itself, so it opts out. */
.mk-json-line[data-mk-json="line_items"] {
  padding-left: 0;
  text-indent: 0;
}

.mk-demo-hook {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 4px var(--space-3);
  align-items: baseline;
}
.mk-demo-hook code { font: inherit; color: var(--ink); overflow-wrap: anywhere; }
.mk-demo-hookkey {
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.mk-demo-hookstatus {
  padding: 0 6px;
  border-radius: 5px;
  color: var(--success-text) !important;
  background: var(--success-soft);
}

/* The one true inline link on the page — a sentence in the demo card's own
   caption voice, so it keeps that voice's size and colour. Only the
   underline itself is softened: a quiet line at rest, thickened on
   hover/focus, rather than the browser's default heavy rule. */
.mk-demo-open {
  font-size: var(--text-2xs);
  font-weight: 600;
  color: var(--accent-text);
  text-decoration: underline;
  text-decoration-color: rgba(162, 150, 246, 0.45);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  white-space: nowrap;
  transition: text-decoration-color 0.15s ease;
}
.mk-demo-open:hover,
.mk-demo-open:focus-visible {
  text-decoration-color: currentColor;
}

/* Head of the field pane: the document's result is DONE. A dot, never a
   check mark — ✓ and green mean VERIFIED in this product, which a person
   does, and nothing in this frame is verified. Success colour is fine: the
   claim is "the extraction finished", the same claim the file bar's status
   makes (and that one is hidden on phones, which is why this exists). */
.mk-demo-extracted {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 var(--space-2);
  padding: 0 var(--space-2);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--success-text);
}
.mk-demo-extracteddot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--success);
  flex-shrink: 0;
}

/* Legend under the paper: an outline means "selectable in Source check", not
   "extracted" — without it, a plain value on the page reads as one DocAI
   missed. Quiet by construction; it is a caption, not a claim. */
.mk-demo-paperlegend {
  margin: 6px 0 0;
  font-size: var(--text-2xs);
  line-height: 1.4;
  color: var(--ink-soft);
}

/* Foot of the field pane — RESULT: the size of the same sample's result, so
   the five source-check rows above read as the subset they are. Plain text on
   purpose (no buttons, no tab stops, no marks). Pushed to the bottom of the
   pane so the list keeps its rhythm. It stays HERE, next to the rows it
   qualifies, rather than becoming a full-width strip under both panes: full
   width the names fit on fewer lines but the strip is additive, and the frame
   ends up taller than it is with the block absorbing the field pane's slack. */
.mk-demo-also {
  margin: auto 0 0;
  padding: var(--space-2) 10px 0;
  border-top: 1px solid var(--border);
}
.mk-demo-alsohead {
  margin: 0 0 3px;
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
/* The count, then what it includes. Two lines on every layout (the note is a
   block), so the phone never has to fit them side by side and the block keeps
   the same height in every language. Mono for the number — it is a value read
   off a result, not a marketing figure. */
.mk-demo-resultline {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.mk-demo-resultcount {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink);
}
.mk-demo-resultnote {
  font-size: var(--text-2xs);
  line-height: 1.45;
  color: var(--ink-soft);
}

/* Output head label: the same small uppercase voice as the pane heads,
   sitting on the tabs' baseline. Phones drop it (the head has one free slot
   there) and the footer under the panels carries the word instead. */
.mk-demo-outlabel {
  align-self: center;
  padding: 4px 0 0;
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  white-space: nowrap;
  flex-shrink: 0;
}
.mk-demo-outfoot {
  margin: 0;
  padding: 5px 14px 7px;
  font-size: var(--text-2xs);
  line-height: 1.5;
  color: var(--ink-soft);
  border-top: 1px solid var(--border);
}
/* The link lives in this sentence (it moved out of the file bar, which had no
   width for it on desktop and put it nowhere near the reason to press it). It
   inherits the foot's size, so .mk-demo-open only carries weight and colour. */
.mk-demo-outfoot .mk-demo-open { font-size: inherit; white-space: normal; }

/* ── Motion layer: the one-pass story ─────────────────────────────────── */

/* hero-demo.js owns time and writes ONE attribute on the frame,
   data-hero-state (idle → document-in → extracting → source-link →
   structured → delivered → complete), plus .is-in on the rows, marks and
   JSON lines it has revealed so far. Everything below reacts to those and
   nothing else. Presentation only — opacity and transform, never display,
   hidden or aria: the completed content stays in the DOM and readable the
   whole time. Without the attribute (JavaScript off, reduced motion, an
   unsupported browser, the boot's escape) none of this applies and the frame
   is the static COMPLETE render. The halo never moves. */

/* Transitions exist only once the story has claimed the frame. */
.mk-demo[data-hero-state] .mk-demo-paper {
  transition: opacity 0.6s cubic-bezier(0.2, 0.7, 0.2, 1), transform 0.6s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.mk-demo[data-hero-state] .mk-demo-fieldvalue,
.mk-demo[data-hero-state] .mk-conf {
  transition: opacity 0.32s cubic-bezier(0.2, 0.7, 0.2, 1), transform 0.32s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.mk-demo[data-hero-state] .mk-json-line {
  transition: opacity 0.32s cubic-bezier(0.2, 0.7, 0.2, 1), background 0.15s ease, box-shadow 0.15s ease;
}
.mk-demo[data-hero-state] .mk-json-str { transition: color 0.32s cubic-bezier(0.2, 0.7, 0.2, 1); }
.mk-demo[data-hero-state] .mk-json-sep,
.mk-demo[data-hero-state] .mk-json-ghost { transition: opacity 0.32s cubic-bezier(0.2, 0.7, 0.2, 1); }
.mk-demo[data-hero-state] .mk-demo-status,
.mk-demo[data-hero-state] .mk-demo-delivered,
.mk-demo[data-hero-state] .mk-demo-docname { transition: opacity 0.3s ease-out; }

/* Replay: rendered only once the story has claimed the frame (the static
   render never shows a dead control), and invisible — visibility, so its
   space is reserved from the boot on and its arrival never moves the head,
   and it is unfocusable meanwhile — until hero-demo.js marks the first
   COMPLETE with .is-ready. Once shown it stays shown — through a replay too,
   so the button the visitor just pressed never vanishes under their focus.
   (Not the hidden attribute: the global [hidden] rule is display:none
   !important and would drop the reservation.) */
.mk-demo:not([data-hero-state]) .mk-demo-replay { display: none; }
.mk-demo[data-hero-state] .mk-demo-replay:not(.is-ready) { visibility: hidden; }
/* Its arrival is a short fade (opacity only); on phones it waits for the
   confirmation, which owns the same slot, to fade out first. */
.mk-demo[data-hero-state] .mk-demo-replay { transition: opacity 0.25s ease-out; }
.mk-demo[data-hero-state] .mk-demo-replay:not(.is-ready) { opacity: 0; }

/* IDLE: the document has not arrived. */
.mk-demo[data-hero-state="idle"] .mk-demo-paper { opacity: 0; transform: translateY(10px) scale(0.985); }
.mk-demo[data-hero-state="idle"] .mk-demo-docname { opacity: 0.55; }

/* EXTRACTING populates the rows: until a row is in, its value and confidence
   are not there yet (the label stays — pending fields look like this in the
   workspace too) and its mark on the paper carries no tint. */
.mk-demo[data-hero-state]:not([data-hero-state="complete"]) .mk-demo-field:not(.is-in) .mk-demo-fieldvalue,
.mk-demo[data-hero-state]:not([data-hero-state="complete"]) .mk-demo-field:not(.is-in) .mk-conf {
  opacity: 0;
  transform: translateY(4px);
}
.mk-demo[data-hero-state]:not([data-hero-state="complete"]) .mk-mark:not(.is-in) {
  background: transparent;
  color: inherit;
  box-shadow: none;
}

/* …and the rest of the page is anchored in the same pass. The hairlines are
   not per-row (they have no row to be revealed WITH), so they arrive together
   with EXTRACTING and read as one sweep of the page rather than as five
   fields being found: the point they teach is breadth. Two groups, top block
   then totals block, so the sweep runs down the page. Box-shadow only — the
   space is theirs from the first frame, nothing moves. */
.mk-demo:is([data-hero-state="idle"], [data-hero-state="document-in"]) .mk-anchor { box-shadow: none; }
.mk-demo[data-hero-state] .mk-anchor { transition: box-shadow 0.4s ease-out 0.15s; }
.mk-demo[data-hero-state] .mk-demo-totals .mk-anchor { transition-delay: 0.4s; }

/* "Extraction complete" and everything under ALSO EXTRACTED are the result's
   own breadth, so the story must not spend its whole run teaching "five
   fields were extracted": both arrive at SOURCE-LINK, once the rows are
   populated and the extraction really is done, and stay for the rest of the
   run. Opacity only — the space is reserved from the first frame, so their
   arrival never moves the frame and never fabricates layout shift. */
.mk-demo:is([data-hero-state="idle"], [data-hero-state="document-in"], [data-hero-state="extracting"]) .mk-demo-extracted,
.mk-demo:is([data-hero-state="idle"], [data-hero-state="document-in"], [data-hero-state="extracting"]) .mk-demo-also {
  opacity: 0;
}
.mk-demo[data-hero-state] .mk-demo-extracted,
.mk-demo[data-hero-state] .mk-demo-also { transition: opacity 0.32s cubic-bezier(0.2, 0.7, 0.2, 1); }

/* Before SOURCE-LINK the selection is not shown yet. Only its look waits:
   aria-pressed and the roving tabindex are the server's, untouched. */
.mk-demo:is([data-hero-state="idle"], [data-hero-state="document-in"], [data-hero-state="extracting"]) .mk-demo-field.is-active {
  border-color: transparent;
  background: transparent;
  box-shadow: none;
}
.mk-demo:is([data-hero-state="idle"], [data-hero-state="document-in"], [data-hero-state="extracting"]) .mk-demo-field.is-active .mk-demo-fieldvalue { font-weight: 500; }
.mk-demo:is([data-hero-state="idle"], [data-hero-state="document-in"], [data-hero-state="extracting"]) .mk-mark.is-active {
  background: rgba(91, 69, 224, 0.07);
  color: inherit;
  box-shadow: 0 0 0 1px rgba(91, 69, 224, 0.35);
}

/* The JSON is the destination from the first frame: every key is there, and
   a value the extraction has not produced yet reads as a ghost ("…" over the
   transparent real value; the comma waits with it). EXTRACTING resolves each
   line with its row (.is-resolved, from hero-demo.js); the line stays muted
   — the panel is secondary to the field↔source link — until STRUCTURED
   brings the lines fully in one after another (.is-in). Opacity only. */
.mk-demo[data-hero-state]:not([data-hero-state="complete"]) .mk-json-line:not(.is-in) { opacity: 0.55; }
.mk-demo[data-hero-state]:not([data-hero-state="complete"]) .mk-json-line:not([data-mk-extra]):not(.is-resolved) .mk-json-str { color: transparent; }
.mk-demo[data-hero-state]:not([data-hero-state="complete"]) .mk-json-line:not([data-mk-extra]):not(.is-resolved) .mk-json-sep { opacity: 0; }
.mk-demo[data-hero-state]:not([data-hero-state="complete"]) .mk-json-line:not([data-mk-extra]):not(.is-resolved) .mk-json-ghost { opacity: 1; }
/* The breadth lines (a field no row shows, and the line-item table) have no
   row to resolve against, so they never ghost — they simply are not there
   until the STRUCTURED stagger brings them in, which is exactly the beat that
   says "the structured result is bigger than the five you just checked".
   Opacity only: the lines hold their space from the first frame. */
.mk-demo[data-hero-state]:not([data-hero-state="complete"]) .mk-json-line[data-mk-extra]:not(.is-in) { opacity: 0; }
/* Total's line joins the violet relationship (row, mark, connector) at
   SOURCE-LINK, not before: the same wait as the row's own selected look. */
.mk-demo:is([data-hero-state="idle"], [data-hero-state="document-in"], [data-hero-state="extracting"]) .mk-json-line.is-active {
  background: transparent;
  box-shadow: none;
}

/* DELIVERED: the confirmation and the file-bar status arrive together. */
.mk-demo:is([data-hero-state="idle"], [data-hero-state="document-in"], [data-hero-state="extracting"], [data-hero-state="source-link"], [data-hero-state="structured"]) .mk-demo-status,
.mk-demo:is([data-hero-state="idle"], [data-hero-state="document-in"], [data-hero-state="extracting"], [data-hero-state="source-link"], [data-hero-state="structured"]) .mk-demo-delivered { opacity: 0; }

/* ── Facts band ───────────────────────────────────────────────────────── */

.mk-stats-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  list-style: none;
  margin: 0;
  padding: 0;
}

.mk-stats-grid li {
  background: var(--surface);
  padding: var(--space-6) var(--space-5);
  display: flex;
  flex-direction: column;
}

.mk-stat-n {
  font-size: clamp(1.8rem, 3vw, 2.375rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  color: var(--ink);
  margin-bottom: var(--space-1);
}
.mk-stat-t {
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--ink);
  margin-bottom: var(--space-1);
}
.mk-stat-d {
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--ink-soft);
}
/* The e-invoice fact has no honest number (dialect naming makes any count
   ambiguous), so its figure slot holds a word in the same weight. */
.mk-stat-n-word { letter-spacing: -0.02em; }

/* Figure + unit on one baseline, for the one fact whose numeral and label
   measure different things ($29 is a price, "1,000 pages per month" is the
   allowance). The wrapper carries the margin so the row keeps the same
   rhythm as the three cards without a unit. */
.mk-stat-figure {
  display: flex;
  align-items: baseline;
  gap: 5px;
  margin-bottom: var(--space-1);
}
.mk-stat-figure .mk-stat-n { margin-bottom: 0; }
.mk-stat-u {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--muted);
}

/* ── Exception + routing ──────────────────────────────────────────────── */

/* A static, completed review: the frame shows the four fields as the
   workspace lists them (label, mono value, state) and, beside them, the one
   exception explained — source, initial extraction with its confidence,
   outcome. Same 1px-seam construction as the demo frame. No control in here:
   the figure is a picture of a finished state. */
.mk-exception-figure { max-width: 860px; }
.mk-exception-frame {
  display: grid;
  grid-template-columns: minmax(0, 11fr) minmax(0, 9fr);
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.mk-exception-head {
  grid-column: 1 / -1;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin: 0;
  padding: 10px var(--space-4);
  background: var(--surface);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink);
}
.mk-exception-docstate { font-weight: 600; color: var(--success-text); white-space: nowrap; }
/* The document's progress toward "Reviewed", in the workspace's own words. Its
   own row so the head never has to fit three things on one line in de/pl. */
.mk-exception-progress .mk-exception-swap,
.mk-exception-value .mk-exception-swap { justify-items: start; }
.mk-exception-progress {
  grid-column: 1 / -1;
  margin: -4px 0 0;
  padding: 0 var(--space-4) 8px;
  background: var(--surface);
  font-size: var(--text-2xs);
  color: var(--ink-soft);
}

/* One cell, N stacked states. The completed state is the flow child that sizes
   the cell; every earlier state is laid over it and is decoration
   (aria-hidden). Nothing here moves — the swap is opacity only — so the story
   can never shift the frame, and with no story running only the completed
   child is visible, which is exactly the static render. */
.mk-exception-swap {
  display: inline-grid;
  grid-template-areas: "state";
  justify-items: end;
  align-items: baseline;
}
.mk-exception-swap > * {
  grid-area: state;
  white-space: nowrap;
}
/* The cell is sized to the WIDEST state, not to the completed one: an
   absolutely positioned earlier state would overflow the frame in any language
   where it is the longer string ("Needs review" > "Reviewed" already in en). */
.mk-exception-was {
  opacity: 0;
  pointer-events: none;
}
/* An uncertain field is warn, never success — .mk-exception-state is the
   success colour because three of the four rows are verified. */
.mk-exception-flag { color: var(--warn-text); }
.mk-exception-flag .mk-conf { margin-right: 4px; }
.mk-exception-doclabel {
  font-weight: 600;
  font-size: var(--text-2xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.mk-exception-fields {
  list-style: none;
  margin: 0;
  padding: var(--space-2) var(--space-2);
  background: var(--surface);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.mk-exception-field {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "label state" "value state";
  column-gap: var(--space-3);
  align-items: center;
  padding: 6px 10px;
  border-radius: 8px;
  border: 1px solid transparent;
}
.mk-exception-field.is-exception {
  border-color: var(--accent-border);
  background: var(--accent-light);
  box-shadow: inset 2px 0 0 0 var(--accent);
}
.mk-exception-label { grid-area: label; font-size: var(--text-xs); color: var(--ink-soft); }
.mk-exception-value {
  grid-area: value;
  font-family: var(--font-mono);
  font-size: var(--text-md);
  font-weight: 500;
  line-height: 1.3;
  color: var(--ink);
  overflow-wrap: anywhere;
}
.mk-exception-field.is-exception .mk-exception-value { font-weight: 600; }
.mk-exception-state {
  grid-area: state;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--success-text);
  white-space: nowrap;
}
.mk-exception-detail {
  background: var(--surface-soft);
  padding: var(--space-4);
  min-width: 0;
}
.mk-exception-detailhead {
  margin: 0 0 var(--space-3);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent-text);
}
.mk-exception-dl {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-2) var(--space-4);
  margin: 0;
  font-size: var(--text-sm);
}
.mk-exception-dl dt { color: var(--ink-soft); }
.mk-exception-dl dd {
  margin: 0;
  color: var(--ink);
  font-family: var(--font-mono);
  overflow-wrap: anywhere;
}
.mk-exception-dl dd:last-child { font-family: var(--font-ui); }
.mk-exception-dl s { color: var(--ink-soft); }
/* The source value as the page shows it: a small paper swatch with the mark
   the review workspace draws around the value's box. Decorative. */
.mk-exception-mark {
  display: inline-block;
  width: 10px;
  height: 10px;
  margin-right: 6px;
  vertical-align: -1px;
  border-radius: 2px;
  background: var(--mk-paper);
  box-shadow: 0 0 0 1.5px var(--accent-cta);
}

/* ── Exception: the review story (marketing-motion.js) ───────────────── */

/* One attribute on the <figure>, data-mk-story, drives everything below:
   validating → flagged → compared → corrected → verified → complete. NO rule
   may select [data-mk-story="complete"] — the settled figure has to BE the
   server's render, which is what makes the no-JS and reduced-motion paths the
   same figure rather than a lesser one. A test pins that.

   Opacity and colour only. Every state the story shows is already laid out. */
.mk-exception-figure[data-mk-story] :is(.mk-exception-now, .mk-exception-was) {
  transition: opacity 0.35s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.mk-exception-figure[data-mk-story] :is(.mk-exception-detail, .mk-exception-field, .mk-exception-docstate, .mk-exception-state) {
  transition: opacity 0.35s ease-out, background 0.35s ease-out, border-color 0.35s ease-out, box-shadow 0.35s ease-out, color 0.35s ease-out;
}
.mk-exception-figure[data-mk-story] :is(.mk-exception-dl :is(dt, dd), .mk-exception-detailhead) { transition: opacity 0.35s ease-out; }
.mk-exception-figure[data-mk-story] .mk-exception-dl s { transition: text-decoration-color 0.35s ease-out; }

/* Which of a cell's stacked states is showing, beat by beat. Read as a table:
   the completed state (.mk-exception-now) is the default and every rule below
   is an EARLIER state taking the cell for a while.

     beat        document head   progress   total value   total status
     validating  —               0 of 4     3 458.80      —
     flagged     Needs review    3 of 4     3 458.80      58% Needs verification
     compared    Needs review    3 of 4     3 458.80      58% Needs verification
     corrected   Needs review    3 of 4     3 456.80      Edited
     verified    Needs review    4 of 4     3 456.80      ✓ Verified
     complete    Reviewed        4 of 4     3 456.80      ✓ Verified

   Correction and verification are two rows of that table, and the document is
   not Reviewed until after the last required field is. Neither is a thing a
   confidence did.

   VALIDATING is the section's headline in motion, and the reason it is a beat
   rather than an assumption: for one beat the result exists and nothing about
   it has been decided. What happens NEXT is the whole argument — three fields
   pass and are verified, one does not and is routed to a person. A figure that
   opens on "3 of 4 verified" shows the outcome and never the rule. */

/* VALIDATING — validation is still running: no field carries a state, the
   document carries none either, and nothing has been counted. The values are
   there the whole time; only the verdicts wait. */
.mk-exception-figure[data-mk-story="validating"] .mk-exception-state { opacity: 0; }
.mk-exception-figure[data-mk-story="validating"] .mk-exception-docstate { opacity: 0; }
/* …and the exception row is not the exception yet. Leaving its accent rule and
   tint on at this beat would point at the answer before the check that finds
   it, which is the one thing the beat exists to avoid. Emphasis is ADDED at
   `flagged`, never removed from the other three rows. */
.mk-exception-figure[data-mk-story="validating"] .mk-exception-field.is-exception {
  border-color: transparent;
  background: transparent;
  box-shadow: none;
}
/* The detail panel names the exception in its head, so the head waits with the
   rest of the panel: the reviewer's side of the frame opens as the review
   does. */
.mk-exception-figure[data-mk-story="validating"] .mk-exception-detailhead { opacity: 0; }
.mk-exception-figure[data-mk-story="validating"] .mk-exception-progress .mk-exception-none { opacity: 1; }

/* The document head trails the fields: it is only Reviewed once the review is,
   which is the final beat. */
.mk-exception-figure:not([data-mk-story="complete"])[data-mk-story] .mk-exception-docstate .mk-exception-now { opacity: 0; }
.mk-exception-figure:not([data-mk-story="complete"])[data-mk-story] .mk-exception-docstate .mk-exception-was { opacity: 1; }
.mk-exception-figure:not([data-mk-story="complete"])[data-mk-story] .mk-exception-docstate { color: var(--warn-text); }

/* The count follows the fields: it catches up the moment the fourth is
   verified, one beat before the document state settles. */
.mk-exception-figure:is([data-mk-story="validating"], [data-mk-story="flagged"], [data-mk-story="compared"], [data-mk-story="corrected"]) .mk-exception-progress .mk-exception-now { opacity: 0; }
.mk-exception-figure:is([data-mk-story="flagged"], [data-mk-story="compared"], [data-mk-story="corrected"]) .mk-exception-progress .mk-exception-partial { opacity: 1; }

/* The value is the extraction's until the reviewer corrects it. */
.mk-exception-figure:is([data-mk-story="validating"], [data-mk-story="flagged"], [data-mk-story="compared"]) .mk-exception-value .mk-exception-now { opacity: 0; }
.mk-exception-figure:is([data-mk-story="validating"], [data-mk-story="flagged"], [data-mk-story="compared"]) .mk-exception-value .mk-exception-was { opacity: 1; }
/* …and it is not history yet, so it is not struck through yet either. */
.mk-exception-figure:is([data-mk-story="validating"], [data-mk-story="flagged"], [data-mk-story="compared"]) .mk-exception-dl s { text-decoration-color: transparent; }

/* The status: uncertain, then EDITED, then verified. The middle one is the
   beat this whole figure exists for — an edit does not verify a field. */
.mk-exception-figure:is([data-mk-story="validating"], [data-mk-story="flagged"], [data-mk-story="compared"], [data-mk-story="corrected"]) .mk-exception-state .mk-exception-now { opacity: 0; }
.mk-exception-figure:is([data-mk-story="flagged"], [data-mk-story="compared"]) .mk-exception-flag { opacity: 1; }
.mk-exception-figure[data-mk-story="corrected"] .mk-exception-edited { opacity: 1; }
.mk-exception-edited { font-weight: 600; color: var(--ink-soft); }

/* FLAGGED: the detail panel is where the reviewer is about to look — and they
   have not looked yet. The three verified rows are NOT faded to make the
   exception stand out: faded text is text below the contrast floor, and the
   exception row already carries the accent border, tint and warn status that
   single it out. */
.mk-exception-figure:is([data-mk-story="validating"], [data-mk-story="flagged"]) .mk-exception-dl :is(dt, dd) { opacity: 0; }

/* The outcome is only true once both acts have happened. */
.mk-exception-figure:is([data-mk-story="validating"], [data-mk-story="flagged"], [data-mk-story="compared"], [data-mk-story="corrected"]) .mk-exception-outcome { opacity: 0; }

/* ── Supplier notes + structured data pair ───────────────────────────── */

.mk-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-12);
}

.mk-pair > .mk {
  border-top: 1px solid var(--border-strong);
  padding: var(--space-6) 0;
  min-width: 0;
}

.mk-pair .mk h2 { font-size: var(--text-2xl); letter-spacing: -0.022em; }
.mk-pair .mk > p { font-size: var(--text-md); color: var(--ink-soft); }

.mk-supplier-visual {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
}
.mk-supplier-visual figcaption { flex-basis: 100%; margin-top: 0; }

.mk-minidoc {
  flex: 1;
  border: 1px solid var(--border);
  border-radius: 11px;
  background: var(--surface-soft);
  padding: var(--space-4);
  min-width: 0;
}
.mk-minidoc-next { border-color: var(--accent-border); }

.mk-minidoc-name {
  margin: 0 0 var(--space-2);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--muted);
}

.mk-minidoc-field {
  margin: 0 0 var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.6;
  color: var(--ink);
  overflow-wrap: anywhere;
}
/* The field's LABEL is the one unclassed span in the row ("IBAN"). Scoped to
   :not([class]) because the row now also holds the two value spans the story
   moves (.mk-minidoc-new, .mk-minidoc-applied); a bare `span` selector styled
   those as labels too — block, UI font, uppercase — which silently restyled
   both IBANs. */
.mk-minidoc-field > span:not([class]) {
  display: block;
  font-family: var(--font-ui);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}
.mk-minidoc-field s { color: var(--muted); margin-right: var(--space-1); }

.mk-minidoc-tag {
  display: inline-block;
  margin: 0;
  padding: 3px 8px;
  border-radius: 6px;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}
.mk-tag-fixed { background: var(--warn-soft); color: var(--warn-text); }
.mk-tag-hint { background: var(--success-soft); color: var(--success-text); }

.mk-minidoc-arrow {
  flex: 0 0 auto;
  font-size: var(--text-xl);
  color: var(--muted);
}

/* ── Supplier notes: the cause-and-effect story (marketing-motion.js) ─── */

/* One attribute on the <figure>, data-mk-story, drives everything below:
   extracted → corrected → saved → reapplied → complete. COMPLETE is the
   static render and no rule selects it.

   What each beat is allowed to move: the corrected value (a short mono token
   that also appears on the second document, so nothing is only here), the two
   status chips, the arrow, and the SECOND document's emphasis. Never a whole
   block of prose, and never a partial opacity on text — a beat either renders
   something or it does not. The layout is settled from the first frame: every
   element keeps its box, so no beat can move a line. */
.mk-supplier-visual[data-mk-story] :is(.mk-minidoc-new, .mk-minidoc-tag, .mk-minidoc-arrow) {
  transition: opacity 0.35s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.mk-supplier-visual[data-mk-story] .mk-minidoc-field s {
  transition: text-decoration-color 0.35s ease-out, color 0.35s ease-out;
}
.mk-supplier-visual[data-mk-story] :is(.mk-minidoc-next, .mk-minidoc-applied) {
  transition: border-color 0.4s ease-out, background 0.4s ease-out, box-shadow 0.4s ease-out, color 0.4s ease-out;
}

/* EXTRACTED — one document, one value, nothing yet done to it. The value the
   reviewer will type is not on the page, and the value it replaces is not
   history yet, so it is not struck through yet either. */
.mk-supplier-visual:is([data-mk-story="extracted"]) .mk-minidoc-new { opacity: 0; }
.mk-supplier-visual:is([data-mk-story="extracted"]) .mk-minidoc-field s {
  text-decoration-color: transparent;
  color: var(--ink);
}

/* The chips are the two claims this figure makes — "a note was saved" and "a
   note was applied" — so each waits for the act that earns it. */
.mk-supplier-visual:is([data-mk-story="extracted"], [data-mk-story="corrected"]) .mk-tag-fixed { opacity: 0; }
.mk-supplier-visual:is([data-mk-story="extracted"], [data-mk-story="corrected"], [data-mk-story="saved"]) .mk-tag-hint { opacity: 0; }

/* REAPPLIED is the only beat that touches the second document: until the note
   exists there is nothing to carry across, so the arrow and the next
   document's accent wait for it. */
.mk-supplier-visual:is([data-mk-story="extracted"], [data-mk-story="corrected"], [data-mk-story="saved"]) .mk-minidoc-arrow { opacity: 0; }
.mk-supplier-visual:is([data-mk-story="extracted"], [data-mk-story="corrected"], [data-mk-story="saved"]) .mk-minidoc-next {
  border-color: var(--border);
}
.mk-supplier-visual[data-mk-story="reapplied"] .mk-minidoc-applied {
  background: var(--accent-light);
  box-shadow: 0 0 0 3px var(--accent-light);
  border-radius: 3px;
  color: var(--accent-text);
}

/* Mono snippet box, shared by the XML→JSON pair and the MCP install command.
   `overflow-wrap: anywhere` is what keeps the long npx command from pushing the
   section wider than the viewport on narrow screens. */
.mk-xml-snippet,
.mk-cmd {
  background: var(--surface-soft);
  border: 1px solid var(--border);
  border-radius: 11px;
  padding: var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.85;
  color: var(--ink-soft);
  overflow-wrap: anywhere;
}
.mk-cmd { margin-top: var(--space-4); }
.mk-xml-snippet span,
.mk-cmd span { color: var(--accent-text); }
.mk-xml-snippet .mk-xml-value { color: var(--ink); }
/* Replaces .mk-cmd on the homepage while the MCP package is unpublished. */
.mk-note { margin-top: var(--space-4); color: var(--muted); }

/* XML element above, JSON key below, one arrow between: the value moves
   from the source data to the result without touching a page. */
.mk-xml-figure { display: flex; flex-direction: column; gap: var(--space-1); }
.mk-xml-lang {
  margin: 0 0 2px;
  font-family: var(--font-ui);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.mk-xml-arrow { align-self: center; color: var(--muted); font-size: var(--text-lg); line-height: 1; }

/* ── Structured data: the mapping story (marketing-motion.js) ─────────── */

/* source → mapped → landed → complete. The quietest story on the page on
   purpose: no reveal, no entrance, nothing hidden. Both snippets are fully
   legible at every beat and the only thing that moves is WHICH value is
   marked — because the claim is that one value travels from an element to a
   result key, with no page and no recognition in between. */
.mk-xml-figure[data-mk-story] [data-mk-map] {
  border-radius: 3px;
  transition: background 0.35s ease-out, box-shadow 0.35s ease-out, color 0.35s ease-out;
}
.mk-xml-figure[data-mk-story] .mk-xml-arrow {
  transition: color 0.35s ease-out, transform 0.45s cubic-bezier(0.3, 0.8, 0.3, 1);
}
.mk-xml-figure:is([data-mk-story="source"], [data-mk-story="mapped"]) [data-mk-map="source"],
.mk-xml-figure:is([data-mk-story="mapped"], [data-mk-story="landed"]) [data-mk-map="target"] {
  background: var(--accent-light);
  box-shadow: 0 0 0 3px var(--accent-light);
  color: var(--accent-text);
}
/* The arrow carries the value down: it is lit and nudged for exactly the beat
   in which the value is in both places at once. */
.mk-xml-figure[data-mk-story="mapped"] .mk-xml-arrow {
  color: var(--accent-text);
  transform: translateY(3px);
}

/* ── Handoff ──────────────────────────────────────────────────────────── */

/* A structural boundary, static: three columns in one seamed frame —
   DocAI's lifecycle, the event that crosses the boundary, the buyer's own
   stack. The middle column is the subject and carries the accent rule. */
.mk-handoff-cols {
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 6fr) minmax(0, 4fr);
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.mk-handoff-col {
  background: var(--surface);
  padding: var(--space-5);
  min-width: 0;
}
.mk-handoff-boundary { background: var(--surface-soft); border-top: 2px solid var(--accent); margin-top: -1px; }
/* The head, then a reserved band, then the column's content. The band is what
   the story's chips travel in — the document chip on the DocAI side, the event
   chip on the buyer's — and it is reserved in EVERY column and in the static
   render, so neither chip can ever land on a word and neither can shift a
   list. Empty in the static figure by design: a story that needs room has to
   pay for it up front, not take it from the layout when it runs. */
.mk-handoff-colhead {
  margin: 0 0 calc(var(--space-3) + var(--mk-handoff-band));
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.mk-handoff-boundary .mk-handoff-colhead { color: var(--accent-text); }
.mk-handoff-steps,
.mk-handoff-targets {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-sm);
  line-height: 1.7;
  color: var(--ink);
}
.mk-handoff-steps { counter-reset: mk-step; }
/* Every row already carries the padding and the (transparent) accent rule the
   story will colour in, so marking one reached moves nothing. */
.mk-handoff-steps li,
.mk-handoff-targets li,
.mk-handoff-events li {
  padding-left: var(--space-2);
  margin-left: calc(var(--space-2) * -1);
  border-left: 2px solid transparent;
  border-radius: 0 4px 4px 0;
}
.mk-handoff-steps li { display: flex; gap: var(--space-2); align-items: baseline; }
.mk-handoff-steps li::before {
  counter-increment: mk-step;
  content: counter(mk-step, decimal-leading-zero);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--ink-soft);
  flex-shrink: 0;
}
.mk-handoff-targets li::before { content: "·"; margin-right: var(--space-2); color: var(--muted); }
.mk-handoff-events {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.mk-handoff-events li { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.mk-handoff-events code {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink);
  overflow-wrap: anywhere;
}
.mk-handoff-events span { font-size: var(--text-xs); line-height: 1.5; color: var(--ink-soft); }
.mk-handoff-prims {
  margin: var(--space-4) 0 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  line-height: 1.7;
  color: var(--ink-soft);
}

/* Mobile-only connector between the stacked stages (see the ≤900px query
   below). Hidden here so it is never a 4th/5th item in the desktop grid —
   grid-template-columns above defines exactly 3 tracks and auto-placement
   would otherwise shove the third column onto a second row. */
.mk-handoff-flow-arrow { display: none; }

/* The two chips the story moves. Both are decoration and neither exists in the
   static render (the rules that show them are all [data-mk-story]-scoped), so
   they are laid out absolutely and can never take space from a column. */
/* Two chips tall: the uncertain result crosses as a PAIR (completed, and
   review_required beside it), and a chip that outgrows its band lands on the
   list below it. Sized once, here. */
.mk-handoff-cols { --mk-handoff-band: 2.8rem; }
.mk-handoff-lifecycle,
.mk-handoff-stack { position: relative; }
.mk-handoff-doc,
.mk-handoff-cross {
  position: absolute;
  top: calc(var(--space-5) + 1.45rem);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  line-height: 1.4;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
}
.mk-handoff-doc {
  left: var(--space-5);
  padding: 2px 7px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-soft);
  color: var(--ink-soft);
}
.mk-handoff-cross {
  left: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-items: flex-start;
}
.mk-handoff-cross code {
  padding: 2px 7px;
  border-radius: 999px;
  border: 1px solid var(--accent-border);
  background: var(--accent-light);
  font-family: inherit;
  font-size: inherit;
  font-weight: 600;
  color: var(--accent-text);
}

/* ── Handoff: the system-boundary story (marketing-motion.js) ─────────── */

/* Two runs of one lifecycle, told with dimming and one crossing chip:

     clean-run     invoice.pdf · steps 1–4 reach `route`
     clean-event   document.completed raised; the chip appears over the boundary
     clean-done    the chip has crossed; ERP, database, automation take it
     review-run    scan.jpg · the same steps 1–4
     review-event  document.completed AND document.review_required, together
     review-done   the pair has crossed; a person gets it
     complete      the settled figure — the one the server rendered

   As in the exception story, NO rule may select [data-mk-story="complete"]:
   the end of the animation is the static figure, not a variant of it. Opacity
   and transform only. document.approved never lights: it needs an approver in
   a workspace with approval enabled, and a beat would teach otherwise. */
.mk-handoff-figure[data-mk-story] :is(.mk-handoff-steps li, .mk-handoff-events li, .mk-handoff-targets li) {
  transition: background 0.4s ease-out, border-color 0.4s ease-out, color 0.4s ease-out;
}
.mk-handoff-figure[data-mk-story] .mk-handoff-doc { transition: opacity 0.4s ease-out; }
/* The crossing is the one transform on the page's below-fold motion, and the
   only thing it has to be is unmistakably directional. */
.mk-handoff-figure[data-mk-story] .mk-handoff-cross {
  transition: opacity 0.35s ease-out, transform 0.8s cubic-bezier(0.3, 0.8, 0.3, 1);
}

/* Reached. Nothing is ever DIMMED: text at 0.35 opacity is text below the
   contrast floor, and a WCAG failure does not stop being one because it only
   lasts a second (axe caught exactly that on the first cut of this figure).
   So the story ADDS emphasis — the accent rule and tint this product already
   uses for a selected row — and everything it has not reached simply stays as
   the static figure drew it, fully legible from the first frame.

   Steps 1–4 are what both runs reach, and they light one after another so the
   column reads as a document travelling down it rather than as four rows
   switching on. */
.mk-handoff-figure[data-mk-story]:not([data-mk-story="complete"]) .mk-handoff-steps li:nth-child(-n + 4) {
  border-left-color: var(--accent);
  background: var(--accent-light);
}
.mk-handoff-figure[data-mk-story="clean-run"] .mk-handoff-steps li:nth-child(1) { transition-delay: 0s; }
.mk-handoff-figure[data-mk-story="clean-run"] .mk-handoff-steps li:nth-child(2) { transition-delay: 0.18s; }
.mk-handoff-figure[data-mk-story="clean-run"] .mk-handoff-steps li:nth-child(3) { transition-delay: 0.36s; }
.mk-handoff-figure[data-mk-story="clean-run"] .mk-handoff-steps li:nth-child(4) { transition-delay: 0.54s; }
/* The uncertain run goes one step further and stops there. That stop IS the
   story: it is why the second event exists. */
.mk-handoff-figure:is([data-mk-story="review-event"], [data-mk-story="review-done"]) .mk-handoff-steps li:nth-child(5) {
  border-left-color: var(--accent);
  background: var(--accent-light);
}

/* Whose document the lifecycle is carrying. */
.mk-handoff-figure:is([data-mk-story="clean-run"], [data-mk-story="clean-event"], [data-mk-story="clean-done"]) .mk-handoff-doc[data-mk-flow="clean"],
.mk-handoff-figure:is([data-mk-story="review-run"], [data-mk-story="review-event"], [data-mk-story="review-done"]) .mk-handoff-doc[data-mk-flow="review"] {
  opacity: 1;
}

/* The events. document.completed on every successful job; review_required
   raised WITH it, never instead of it — so at the uncertain beats both rows
   are lit at once, and the crossing chip carries both names for the same
   reason. */
.mk-handoff-figure:is([data-mk-story="clean-event"], [data-mk-story="clean-done"], [data-mk-story="review-event"], [data-mk-story="review-done"]) .mk-handoff-events li[data-mk-event="completed"],
.mk-handoff-figure:is([data-mk-story="review-event"], [data-mk-story="review-done"]) .mk-handoff-events li[data-mk-event="review"] {
  border-left-color: var(--accent);
}
.mk-handoff-figure:is([data-mk-story="clean-event"], [data-mk-story="clean-done"], [data-mk-story="review-event"], [data-mk-story="review-done"]) .mk-handoff-events li[data-mk-event="completed"] code,
.mk-handoff-figure:is([data-mk-story="review-event"], [data-mk-story="review-done"]) .mk-handoff-events li[data-mk-event="review"] code {
  color: var(--accent-text);
}

/* The crossing. The chip starts one column gap LEFT of this column's content
   box — over the boundary it belongs to — and slides to zero. A translate of
   its own width needs no measured geometry, so it lands correctly at every
   column width and in every language. */
.mk-handoff-cross { transform: translateX(calc(-100% - var(--space-5) * 2 - 1px)); }
.mk-handoff-figure:is([data-mk-story="clean-event"], [data-mk-story="clean-done"]) .mk-handoff-cross[data-mk-flow="clean"],
.mk-handoff-figure:is([data-mk-story="review-event"], [data-mk-story="review-done"]) .mk-handoff-cross[data-mk-flow="review"] {
  opacity: 1;
}
.mk-handoff-figure[data-mk-story="clean-done"] .mk-handoff-cross[data-mk-flow="clean"],
.mk-handoff-figure[data-mk-story="review-done"] .mk-handoff-cross[data-mk-flow="review"] {
  transform: translateX(0);
}

/* Arrival. The buyer's systems continue the business process; the uncertain
   result goes to a person first. DocAI is not shown doing either. */
.mk-handoff-figure:is([data-mk-story="clean-done"], [data-mk-story="review-run"], [data-mk-story="review-event"], [data-mk-story="review-done"]) .mk-handoff-targets li[data-mk-target="clean"],
.mk-handoff-figure[data-mk-story="review-done"] .mk-handoff-targets li[data-mk-target="review"] {
  border-left-color: var(--accent);
  background: var(--accent-light);
}

/* Stacked layouts: the columns are rows, so the crossing runs down instead of
   across and keeps the DocAI → boundary → your stack reading order. */
@media (max-width: 900px) {
  .mk-handoff-cross { transform: translateY(calc(-100% - var(--space-5) * 2 - 1px)); }
  /* Stacked, the columns are rows, so their content no longer has to start at
     the same height — and three reserved bands one under the other are three
     visible voids. Only the column a chip actually lands in keeps its band;
     the document chip goes back beside its head, where it began. */
  .mk-handoff-boundary .mk-handoff-colhead,
  .mk-handoff-lifecycle .mk-handoff-colhead { margin-bottom: var(--space-3); }
  .mk-handoff-doc {
    top: calc(var(--space-5) - 3px);
    left: auto;
    right: var(--space-5);
  }
  .mk-handoff-figure[data-mk-story="clean-done"] .mk-handoff-cross[data-mk-flow="clean"],
  .mk-handoff-figure[data-mk-story="review-done"] .mk-handoff-cross[data-mk-flow="review"] {
    transform: translateY(0);
  }
}

/* Stacked, at widths where the chip still shows: beside its head, the chip
   shares the lifecycle column head's band — so the head leaves the chip's
   zone free (chip ~90px wide plus its right offset). Without the reserve the
   heading box runs under the chip and its last word disappears for the whole
   run. Scoped to the band above the hide below, so phone widths don't
   reserve space for a chip that isn't there. */
@media (min-width: 521px) and (max-width: 900px) {
  .mk-handoff-lifecycle .mk-handoff-colhead { padding-right: calc(90px + var(--space-5)); }
}

/* Phone widths: the document chip goes away entirely. Its band is the column
   head's, and a heading plus a ~90px chip cannot both fit a ~290-380px card
   interior without the chip covering the head's last word (measured colliding
   from 320px up to ~505px). The chip is decoration — aria-hidden, and the
   "which document" fact it shows is the column's own reading copy — so below
   the width where both fit, it is removed rather than squeezed; the lists it
   highlights carry the story on their own. The crossing chips are unaffected:
   they land on reserved bands, not on the head's. */
@media (max-width: 520px) {
  .mk-handoff-doc { display: none; }
}

/* ── Proof ────────────────────────────────────────────────────────────── */

/* An evidence hub, not a paragraph of hyperlinks: each item is a row with a
   hairline seam below it (the same restrained separator the FAQ and demo
   frame use elsewhere on this page), never a marketing card. */
.mk-proof-list {
  margin: var(--space-4) 0 0;
  padding: 0;
  list-style: none;
  max-width: 720px;
  display: flex;
  flex-direction: column;
  font-size: var(--text-md);
  line-height: 1.6;
}
.mk-proof-list li {
  display: flex;
  flex-wrap: wrap;
  gap: 2px var(--space-3);
  align-items: baseline;
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border);
}
.mk-proof-list li:last-child { border-bottom: none; }
.mk-proof-list span { flex-basis: 100%; font-size: var(--text-sm); color: var(--ink-soft); }

/* ── Pricing preview ──────────────────────────────────────────────────── */

.mk-pricing-sub {
  margin: 0 0 var(--space-5);
  max-width: 720px;
  color: var(--ink-soft);
  line-height: 1.6;
}

.mk-pricing-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-4);
}

.mk-plan {
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  padding: var(--space-6);
}
/* Badge straddles the top border (translateY(-50%)) so it sits outside the
   card's content area. The card needs no extra top padding: all mini-plan
   cards start their content at the same offset. */
.mk-plan-featured {
  border-color: var(--accent-border);
  box-shadow: 0 0 0 3px var(--accent-light);
}

.mk-plan-tag {
  position: absolute;
  top: 0;
  right: var(--space-5);
  transform: translateY(-50%);
  margin: 0;
  font-size: var(--text-2xs);
  font-weight: 600;
  color: var(--accent-text);
  background: var(--accent-light);
  padding: 4px 9px;
  border-radius: 6px;
}

.mk-plan-name { margin: 0 0 var(--space-2); font-weight: 600; color: var(--ink); }
.mk-plan-price {
  margin: 0 0 var(--space-3);
  font-size: var(--text-display);
  font-weight: 700;
  letter-spacing: -0.035em;
  color: var(--ink);
}
.mk-plan-price span { font-size: var(--text-sm); font-weight: 500; letter-spacing: 0; color: var(--muted); }

.mk-plan ul {
  margin: 0;
  padding-left: 18px;
  font-size: var(--text-sm);
  line-height: 1.7;
  color: var(--ink-soft);
}

/* ── FAQ ──────────────────────────────────────────────────────────────── */

/* One joined list — 1px --border seams between --surface rows inside a
   clipped rounded frame, same construction as the demo panel. Overrides the
   global details/summary card look, so every rule is scoped to .mk-faq-*. */
/* The FAQ is the one section on the page whose column reads better centered
   than left-hung against the full 1200px content width — its accordion is
   narrower than every other section's figures, and left-aligned it read as
   shifted rather than positioned. The heading shares the same column so the
   two read as one unit; text inside stays left-aligned (a centered column of
   left-aligned reading is the standard accordion pattern, not full
   text-align: center). Reuses the existing max-width, just adds the margin
   that centers it — no new token, no magic offset. */
.mk-faq h2,
.mk-faq-list {
  max-width: 820px;
  margin-left: auto;
  margin-right: auto;
}

.mk-faq-list {
  display: flex;
  flex-direction: column;
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
}

.mk-faq-item,
.mk-faq-item:first-child {
  margin: 0;
  border: 0;
  border-radius: 0;
  background: var(--surface);
}

.mk-faq-item > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  min-height: 44px;
  padding: var(--space-4) var(--space-5);
  font-size: var(--text-md);
  font-weight: 500;
  line-height: 1.4;
  color: var(--ink);
  cursor: pointer;
  list-style: none;
}
.mk-faq-item > summary::-webkit-details-marker { display: none; }
.mk-faq-item > summary::after {
  content: "+";
  font-size: var(--text-xl);
  line-height: 1;
  color: var(--muted);
  flex-shrink: 0;
  transition: transform 0.2s ease;
}
.mk-faq-item[open] > summary::after { transform: rotate(45deg); }
.mk-faq-item > summary:hover { background: var(--surface-soft); }
.mk-faq-item > summary { transition: background 0.15s ease; }

/* Opening motion, and nothing more: no autonomous story lives in the FAQ.
   <details> toggles its content's display instantly, so the answer is
   animated on ARRIVAL with a short entrance rather than by animating a
   height (which would need a measured geometry and could not be interrupted
   cleanly). Native semantics, native keyboard behaviour and the summary's
   own focus ring are all untouched — this is presentation on an element the
   browser has already revealed. */
@keyframes mk-faq-answer-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: none; }
}
.mk-faq-item[open] > p { animation: mk-faq-answer-in 0.22s cubic-bezier(0.2, 0.7, 0.2, 1) both; }

@media (prefers-reduced-motion: reduce) {
  .mk-faq-item > summary::after { transition: none; }
  .mk-faq-item[open] > p { animation: none; }
}

.mk-faq-item > p {
  margin: 0;
  padding: 0 var(--space-5) var(--space-5);
  max-width: 680px;
  font-size: var(--text-md);
  line-height: 1.7;
  color: var(--ink-soft);
}

/* ── Final CTA + AI disclosure ────────────────────────────────────────── */

.mk-final {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
  border-top: 1px solid var(--border-strong);
  padding: var(--space-8) 0;
}

.mk-final .mk-pricing-sub {
  max-width: 560px;
}

/* ── Final CTA: the closing cue (marketing-motion.js) ─────────────────── */

/* A single slim strip under the CTA: document → two extracted fields → one
   verified field. Decoration (the element is aria-hidden) and the page's
   argument in miniature, so the last thing a visitor sees restates the
   product rather than a flourish. Wraps rather than scrolls; every chip
   keeps its box at every beat, so the strip cannot move the buttons above
   it. */
.mk-final-cue {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  margin: var(--space-6) 0 0;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--ink-soft);
}
.mk-final-doc {
  border: 1px solid var(--border-strong);
  border-radius: 7px;
  padding: 5px 10px;
  color: var(--ink);
}
.mk-final-arrow { color: var(--muted); }
.mk-final-field {
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
  border: 1px solid var(--border);
  border-radius: 7px;
  padding: 5px 10px;
  background: var(--surface);
  font-family: var(--font-ui);
  color: var(--muted);
}
.mk-final-field b { font-family: var(--font-mono); font-weight: 500; color: var(--ink); }
.mk-final-verified {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  border-radius: 7px;
  padding: 5px 10px;
  background: var(--success-soft);
  color: var(--success-text);
  font-family: var(--font-ui);
  font-weight: 600;
}

/* document → extracted → verified → complete. COMPLETE is the static strip
   and no rule selects it. Opacity and a small translate only. */
.mk-final-cue[data-mk-story] > * {
  transition: opacity 0.4s cubic-bezier(0.2, 0.7, 0.2, 1), transform 0.4s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.mk-final-cue:is([data-mk-story="document"]) :is(.mk-final-arrow, .mk-final-field, .mk-final-verified),
.mk-final-cue:is([data-mk-story="extracted"]) .mk-final-verified {
  opacity: 0;
  transform: translateX(-6px);
}
/* The two fields arrive in order, so the strip reads left to right rather
   than appearing all at once. */
.mk-final-cue[data-mk-story="extracted"] .mk-final-field + .mk-final-field { transition-delay: 0.12s; }

.mk-disclosure-row {
  display: flex;
  justify-content: center;
}

/* ── Shared footer ────────────────────────────────────────────────────── */

/* _footer.html is shared with the app pages, where it is hidden behind
   body:has(#app-sidebar) — the landing is its first and only visible consumer,
   and it arrived here 1200px wide against the page's 1080px column, overhanging
   every other element on the page by ~60px a side.

   The two structures put their gutter in different places: .mk-topnav-inner and
   .mk-landing are 1080px border-box WITH the 24px padding inside, while
   .site-footer-inner has no padding at all — .site-footer holds it. So the
   footer's row has to be the CONTENT width, gutters already subtracted, or its
   text would line up a gutter outside the logo above it. */
.landing-page .site-footer {
  padding-left: var(--mk-gutter);
  padding-right: var(--mk-gutter);
}

.landing-page .site-footer-inner {
  max-width: calc(var(--mk-content-max) - 2 * var(--mk-gutter));
}

/* ── Responsive ───────────────────────────────────────────────────────── */

@media (max-width: 1000px) {
  .mk-stats-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 1100px) {
  /* Copy above the frame. The stage stops chasing the viewport height and
     the frame takes the full column, panes still side by side. */
  .mk-hero,
  .mk-stage .mk-hero {
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--space-8);
    min-height: 0;
    padding-top: var(--space-10);
    padding-bottom: var(--space-10);
  }
  .mk-hero-copy { max-width: 640px; }
  .mk-stage::before { left: 50%; top: 62%; height: 640px; }
  .mk-stage::after { display: none; }
}

@media (max-width: 900px) {
  .mk-nav-toggle { display: inline-flex; }

  /* Collapsed by default; the toggle discloses one stacked panel holding the
     links AND the auth actions, anchored under the sticky bar. */
  .mk-topnav-menu {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-4);
    margin: 0;
    padding: var(--space-4) 24px var(--space-5);
    background: var(--bg);
    border-bottom: 1px solid var(--border);
    box-shadow: var(--shadow-lg);
  }
  .mk-topnav-menu.is-open { display: flex; }

  .mk-topnav-links {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }
  .mk-topnav-links a { padding: 12px 2px; }

  .mk-topnav-actions {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
  }
  /* The auth buttons are wrapped in .mk-topnav-authset (one variant per auth
     state), so the column above now applies to the WRAPPER, not to them. The
     wrapper has to hand the stacking on, or the two CTAs sit side by side in a
     panel where every other row is full-width — worst in de/pl, where
     "Kostenloses Konto erstellen" then wraps inside a half-width pill. */
  .mk-topnav-authset {
    flex-direction: column;
    align-items: stretch;
  }
  .mk-topnav-lang { justify-content: flex-start; }
  /* Sign in/out have no button chrome (no background, no border) — they render
     as plain text rows. Left-align to match the nav links above them. The
     primary-btn CTA has a filled background so centered text is fine there. */
  .mk-topnav-signin,
  .mk-topnav-signout { text-align: left; }

  .mk-landing { gap: var(--space-10); }

  .mk-pair { grid-template-columns: 1fr; }

  /* ── Handoff: an intentional vertical flow, not the desktop grid shrunk ──
     The three columns keep their exact DOM order and every motion selector
     above still targets the same elements — only the box model changes: a
     real gap between three separately-bordered stages (with the decorative
     arrows between them) reads as a sequence of steps, where the desktop's
     1px-seam grid stacked would just read as three panels glued together
     with no sense of "and then". */
  .mk-handoff-cols {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3);
    background: none;
    border: 0;
    border-radius: 0;
  }
  .mk-handoff-col {
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
  }
  .mk-handoff-boundary {
    border: 1px solid var(--accent-border);
    margin-top: 0;
  }
  .mk-handoff-flow-arrow {
    display: flex;
    justify-content: center;
    color: var(--muted);
    font-size: var(--text-lg);
    line-height: 1;
  }
}

@media (max-width: 700px) {
  /* Phone frame: not the desktop one scaled down. The paper keeps only the
     rows a reader needs to find the total (dates, buyer and line items go),
     the list keeps the three rank-1 fields, the JSON the same three keys,
     and the connector is replaced by the shared highlight alone — a line
     between stacked panes would cross the whole frame. */
  .mk-demo-body { grid-template-columns: minmax(0, 1fr); }
  .mk-demo-link { display: none !important; }
  .mk-demo [data-mk-rank="2"],
  .mk-demo [data-mk-minor],
  .mk-demo-items,
  .mk-demo-status { display: none; }
  .mk-demo-paper { padding: var(--space-4); }
  .mk-demo-paperhead { margin-bottom: var(--space-2); }
  .mk-demo-totals { margin-top: var(--space-2); }
  .mk-demo-fields { padding-bottom: var(--space-2); }
  /* The paper drops rows and line items here, so ALSO EXTRACTED is the only
     breadth signal left — it stays whole, at every width. Only the paper's
     legend goes: with fewer marked rows visible it explains less than it
     costs in height. */
  .mk-demo-paperlegend { display: none; }
  .mk-demo-field { min-height: 44px; }
  .mk-demo-outpanel { min-height: calc(10 * 1.2em + 11px); } /* nine phone lines; the line-item object wraps to two */
  /* The preview words stay on the phone frame (head + foot of the field
     pane, foot of the output); only the head label of the output goes, its
     slot is needed for the tabs and the confirmation. */
  .mk-demo-outlabel { display: none; }
  .mk-demo-bar { flex-wrap: wrap; row-gap: 4px; }
  /* The Output head has one slot right of the tabs on a phone. The
     confirmation drops its status code, shows through DELIVERED, and hands
     the slot to Replay at COMPLETE (the static render — no motion — keeps
     the confirmation and never shows Replay, as before). */
  .mk-demo-deliveredcode { display: none; }
  .mk-demo[data-hero-state] .mk-demo-replay { position: absolute; right: 0; top: 50%; transform: translateY(-50%); }
  .mk-demo-outend:has(.mk-demo-replay.is-ready) .mk-demo-delivered { opacity: 0; }
  .mk-demo[data-hero-state] .mk-demo-replay { transition-delay: 0.3s; }
  .mk-hero,
  .mk-stage .mk-hero { padding-top: var(--space-8); padding-bottom: var(--space-8); }
  .mk-hero h1 { font-size: clamp(1.9rem, 8vw, 2.4rem); }
  .mk-lead { margin-bottom: var(--space-5); }
  /* Motion distance scales with the column. A 10px rise reads as a considered
     entrance across a 1152px grid and as a jolt in a 343px one, where the
     items are stacked and each one travels through the row above it. */
  [data-mk-motion] [data-mk-reveal]:not(.is-revealed) > * { transform: translateY(6px); }
  [data-mk-motion] [data-mk-reveal] > *:nth-child(n + 4) { transition-delay: 0.21s; }

  /* ── Mobile typography floor ──────────────────────────────────────────
     Each section's one explanatory paragraph is that section's PRIMARY
     reading copy on a phone (there is no smaller secondary tier below it
     here), so it gets the ~16px floor and a slightly looser line-height —
     14px/1.6 read fine on a 720px desktop column but tighten uncomfortably
     once the same text wraps a 300px phone column. Figcaptions, the FAQ
     and product-UI chrome (the demo, confidence chips) are untouched: they
     already sit at or above the 12-13px caption / 14px secondary floors the
     brief sets, and the demo mirrors a real, deliberately compact UI. */
  :where(.mk) > p,
  /* .mk-pair .mk > p (0-2-1, the two-column desktop rule) outranks the
     :where() base (0-0-1) at every width, so without naming it here the two
     sections inside .mk-pair keep 14px and the floor silently does not apply
     to them. Matched, not raised: the desktop rule stays the only owner of
     that paragraph's size above this breakpoint. */
  .mk-pair .mk > p {
    font-size: var(--text-lg);
    line-height: 1.7;
  }

  /* The lifecycle terms are the section's real content, not chrome —
     legible at the same floor as the paragraphs around them. */
  .mk-handoff-steps,
  .mk-handoff-targets { font-size: var(--text-md); }

  /* Action links are rows of tappable text on a phone, so they get the same
     44px interaction floor as the FAQ summaries above. min-height stretches
     the existing inline-flex row (align-items: center comes from the base
     rule); padding, type, arrow and hover/focus treatment are unchanged —
     this raises the tap area, it does not dress the links up as buttons. */
  .mk-links a,
  .mk-proof-list a { min-height: 44px; }

  /* ── Section separation ────────────────────────────────────────────────
     .mk-landing's gap already puts 40px between every top-level section,
     and most of them additionally start with their own visual box (the
     facts grid, the bordered figures, the pricing cards, the FAQ frame) or
     their own border-top (.mk-exception, .mk-handoff — see "Act
     boundaries" above). Evidence and pricing are the two that are still
     bare heading+paragraph at the point they meet the section above, where
     a phone has no side-by-side column left to keep them apart — a little
     extra air, from the existing scale, is enough. */
  .mk-proof,
  .mk-pricing { margin-top: var(--space-4); }
}

/* ── Proof strip: compact 2x2 on phones ───────────────────────────────────
   Four blocks stacked one-per-row was the single biggest source of scroll
   length on a phone. Two columns fit comfortably down to narrow phone
   widths — the description text wraps to 2-3 lines, same as it already
   does in the 2-column tablet layout above — so the grid stays 2x2 instead
   of collapsing to 1 column; only genuinely tiny widths fall back. */
@media (max-width: 700px) {
  .mk-stats-grid li { padding: var(--space-5) var(--space-4); }
}

@media (max-width: 560px) {
  /* Stack hero CTAs as equal-width column. inline-block flex items stretch to
     the container's cross-axis width when align-items is stretch (the column
     default). text-align:center is already declared on .primary-btn and
     .secondary-btn so centered content is preserved inside each button. */
  .mk-hero-actions {
    flex-direction: column;
    align-items: stretch;
  }
  .mk-supplier-visual { flex-direction: column; align-items: stretch; }
  .mk-minidoc-arrow { align-self: center; transform: rotate(90deg); }
  .mk-exception-frame { grid-template-columns: minmax(0, 1fr); }
  /* Label, value, status — one under the other. Side by side, an `auto` status
     column cannot shrink, so "58% Verifizierung erforderlich" at 320px sat on
     top of the value it describes. Stacked, the status wraps instead, and the
     cell still reserves the tallest of its three states so nothing moves. */
  .mk-exception-field {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "label" "value" "state";
    row-gap: 2px;
  }
  .mk-exception-swap { justify-items: start; }
  .mk-exception-swap > * { white-space: normal; }
  /* The story's FLAGGED beat empties the detail panel on wide layouts, where
     it sits beside the fields and reads as "the reviewer has not looked yet".
     Stacked it is a full-width void under them for a second, so the panel
     keeps its comparison from the first beat here; the highlighted row carries
     the beat on its own. The outcome still waits for both acts. */
  .mk-exception-figure:is([data-mk-story="validating"], [data-mk-story="flagged"]) .mk-exception-dl :is(dt, dd) { opacity: 1; }
  .mk-exception-figure[data-mk-story="validating"] .mk-exception-detailhead { opacity: 1; }
  /* …but not the outcome: it is a claim about two acts that have not happened.
     Specificity has to beat the line above, hence the doubled selector. */
  .mk-exception-figure:is([data-mk-story="validating"], [data-mk-story="flagged"]) .mk-exception-dl :is(dt, dd).mk-exception-outcome { opacity: 0; }
  .mk-exception-dl { grid-template-columns: minmax(0, 1fr); gap: var(--space-1); }
  .mk-exception-dl dt { font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: 0.06em; }
  .mk-exception-dl dd + dt { margin-top: var(--space-2); }
  .mk-pair > .mk { padding: var(--space-5) 0; }
  .mk-demo-bar { padding: 8px 12px; }
  .mk-demo-docmeta { font-size: 0.6rem; }
}

/* Below the 2x2 grid's comfortable floor: graceful fallback for widths
   narrower than any common phone (verified 2x2 holds with no overflow down
   to 320px), not overflow. */
@media (max-width: 300px) {
  .mk-stats-grid { grid-template-columns: 1fr; }
}

/* The signed-out upload gate is a focused entry form, not a marketing hero. */
.landing-page:has(#guest-title) .mk-landing {
  width: 100%;
  max-width: 720px;
}
.landing-page:has(#guest-title) .mk-hero {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4);
}
.landing-page:has(#guest-title) .mk-hero h1 { font-size: var(--text-display); }
.landing-page:has(#guest-title) .mk-hero :is(.mk-lead, .mk-hero-badge, h1) { margin-bottom: 0; }
.landing-page:has(#guest-title) .ai-disclosure-note { margin: 0; padding: 0; background: none; }

/* Keep the no-toggle guest navigation reachable at the marketing breakpoint. */
@media (max-width: 900px) {
  .landing-page:has(#guest-title) .mk-topnav-inner { flex-wrap: wrap; gap: var(--space-3); }
  .landing-page:has(#guest-title) .mk-topnav-menu {
    position: static;
    display: flex;
    flex: 1 1 100%;
    padding: 0;
    border: 0;
    box-shadow: none;
    gap: var(--space-3);
  }
  .landing-page:has(#guest-title) .mk-topnav-links { flex-direction: row; flex-wrap: wrap; gap: var(--space-4); }
  .landing-page:has(#guest-title) .mk-topnav-links a { padding: var(--space-2) 0; }
  .mk-hero-actions { width: 100%; }
}
