/* Industry marketing — rebased onto marketing.css's warm-ink palette (green
   --action, blue reserved for the logo mark only, Fraunces/DM Sans/IBM Plex
   Mono, full light+dark via prefers-color-scheme). marketing.css supplies
   the palette and fonts but declares no spacing/radius/type scale, so this
   file owns its own --im-* scale (Prism §2), mapped onto marketing.css's
   tokens so industry pages land on the same rhythm as /features and
   /pricing. Nav and footer are the shared chrome (marketing-chrome.js) —
   this file owns nothing above the hero. */

.im-page {
  --im-gutter: 40px;
  --im-max: 1120px;
  /* Prism round 2 §4: ch not px — a measure is a character count, and 640px
     drifts against font-size the way a fixed pixel measure always does. */
  --im-measure: 62ch;
  --im-1: 4px; --im-2: 8px; --im-3: 12px; --im-4: 16px; --im-5: 20px;
  --im-6: 24px; --im-8: 32px; --im-10: 40px; --im-12: 48px; --im-16: 64px; --im-20: 80px;
  /* 88 -> 72: the new .dot-rule between sections occupies the recovered
     rhythm instead of two adjacent bare gaps (Prism round 2 §4). */
  --im-section-y: 72px;
  --im-display: clamp(34px, 5.2vw, 54px);
  --im-h2: clamp(28px, 3.2vw, 38px);
  --im-h3: 21px; --im-body: 17px; --im-small: 15px; --im-label: 11px;
  /* NEW (Prism round 2 §4): a section's lead paragraph (first sentence,
     .im-flow-heading > p:last-child / .im-section-body > p:first-child) and
     a pull-quote step, one size apart from body so the first sentence after
     a heading doesn't read as the same wall as everything under it. */
  --im-lead: 19px;
  --im-quote: clamp(22px, 2.6vw, 28px);
  --im-lh-display: 1.12; --im-lh-h2: 1.2; --im-lh-body: 1.7; --im-lh-lead: 1.6; --im-lh-quote: 1.3;
  --im-track-display: -0.02em; --im-track-h2: -0.01em; --im-track-label: 2px;
  --im-r-sm: 6px; --im-r-md: 10px; --im-r-lg: 14px; --im-r-xl: 18px; --im-r-full: 9999px;
  --im-shadow-card: 0 2px 8px rgba(22, 19, 14, 0.05);
  --im-shadow-lift: 0 8px 32px rgba(22, 19, 14, 0.10), 0 2px 6px rgba(22, 19, 14, 0.05);
  --im-accent: var(--action);
  --im-accent-text: var(--action-text);
  --im-accent-wash: var(--action-wash);
  /* Artwork strokes (Prism §6 scenes). --ink-300 measured 2.10:1 light /
     1.91:1 dark against --paper — the ink scale inverts between themes, so
     no single --ink-300 literal clears 3:1 in both. --ink-400 (2.91 both)
     still falls short; --ink-500 clears it (4.55 light / 5.05 dark),
     measured 2026-08-13. These SVGs are aria-hidden="true" so this is a
     visual-quality fix, not a compliance one. */
  --im-line: var(--ink-500);
  margin: 0;
  background: var(--paper);
  color: var(--ink-900);
  font-family: var(--font-body);
  font-size: var(--im-body);
  line-height: var(--im-lh-body);
}

@media (prefers-color-scheme: dark) {
  .im-page {
    --im-shadow-card: 0 2px 8px rgba(0, 0, 0, 0.35);
    --im-shadow-lift: 0 10px 36px rgba(0, 0, 0, 0.45);
  }
}

.im-page *,
.im-page *::before,
.im-page *::after { box-sizing: border-box; }

/* Generic link-color fallback for any anchor under .im-page that doesn't
   carry its own component class. `:where(.im-page)` contributes ZERO
   specificity (unlike a plain `.im-page` prefix, which is (0,1,1) combined
   with `a` — enough to beat marketing.css's `.cta-button { color: #FFFFFF }`
   (0,1,0), `.nav-link` (0,1,0), `.footer-col a` (0,1,1) and this file's own
   `.im-industry-card` / `.im-related-card` (0,1,0), independent of file
   load order). With `:where()`, this rule's total specificity is just `a`
   (0,0,1) — lower than every one of those — so any component-specific color
   wins regardless of where it's declared, and this only ever applies to an
   anchor with no more specific rule of its own (there are none in current
   markup; this is a forward-safe default for e.g. a future inline FAQ-answer
   link). This is the fix for the "hero/final CTA text renders in --action
   green instead of white/ink" defect — see git history for the pre-fix
   selector and measured contrast numbers. */
:where(.im-page) a { color: var(--action); }
:where(.im-page) a:hover { color: var(--action-hover); }
:where(.im-page) a:focus-visible,
:where(.im-page) summary:focus-visible {
  outline: 2px solid var(--action);
  outline-offset: 2px;
  border-radius: var(--im-r-sm);
}

.im-skip-link {
  position: fixed;
  inset-block-start: var(--im-2);
  inset-inline-start: var(--im-2);
  z-index: 100;
  padding: var(--im-3) var(--im-4);
  border-radius: var(--im-r-md);
  background: var(--card);
  color: var(--ink-900);
  transform: translateY(-80px);
}
.im-skip-link:focus { transform: translateY(0); }

.im-shell { width: min(var(--im-max), 100% - var(--im-10) * 2); margin-inline: auto; }

/* Full-bleed sections (D1): the background spans the viewport via the
   standard center-and-translate trick — robust regardless of the shell's
   own gutter math, which is what a negative-margin bleed inside a
   fixed-width shell got wrong at 768 (20px overflow). A nested .im-shell
   re-establishes the same reading column every sibling section uses. */
.im-band {
  width: 100vw;
  position: relative;
  left: 50%;
  transform: translateX(-50%);
}

.im-breadcrumbs { padding-block: var(--im-5); }
.im-breadcrumbs ol { list-style: none; display: flex; flex-wrap: wrap; align-items: center; gap: var(--im-2); margin: 0; padding: 0; font-size: 13px; }
.im-breadcrumbs li + li::before { content: '/'; margin-inline-end: var(--im-2); color: var(--ink-400); }
.im-breadcrumbs a,
.im-breadcrumbs span { min-height: 44px; display: inline-flex; align-items: center; }
.im-breadcrumbs a { color: var(--ink-600); text-decoration: none; }
.im-breadcrumbs a:hover { color: var(--ink-900); }

/* ── Hero — paper, not dark. Same opening move as /pricing and /features. ─ */
.im-hero { padding-block: var(--im-16) var(--im-12); }
/* 1.15fr/.85fr @ 220px min starved the hero art (Prism measured ~60x80px of
   ink in a 340px box); a more even column split gives the scene room to
   actually fill its box once passes B/C put motion in it. */
.im-hero-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(300px, .9fr); gap: var(--im-12); align-items: center; }
.im-hero-copy { max-width: 720px; }
/* .dot-mark (P6): a static 6px --action dot precedes every eyebrow. No
   motion — this one never needs data-reveal. Pain/limitations sections
   override the color to --copper below (copper = what today costs you). */
.im-eyebrow {
  position: relative;
  margin: 0 0 var(--im-3);
  padding-inline-start: var(--im-5);
  color: var(--action-text);
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: var(--im-track-label);
  text-transform: uppercase;
}
.im-eyebrow::before {
  content: '';
  position: absolute;
  inset-inline-start: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--action);
}
.im-pain-section .im-eyebrow,
.im-section--limitations .im-eyebrow { color: var(--copper); }
.im-pain-section .im-eyebrow::before,
.im-section--limitations .im-eyebrow::before { background: var(--copper); }
.im-hero h1 {
  margin: 0;
  color: var(--ink-900);
  font-family: var(--font-display);
  font-size: var(--im-display);
  font-weight: 400;
  font-style: italic;
  line-height: var(--im-lh-display);
  letter-spacing: var(--im-track-display);
  text-wrap: balance;
}
.im-hero-audience { max-width: 620px; margin: var(--im-5) 0 0; color: var(--ink-900); font-size: var(--im-h3); }
.im-hero-job { max-width: 620px; margin: var(--im-3) 0 0; color: var(--ink-600); }
.im-document-strip { display: flex; flex-wrap: wrap; gap: var(--im-3); margin-top: var(--im-8); padding-top: var(--im-5); border-top: 1px solid var(--card-border); }
.im-document-strip span { color: var(--action-text); font-family: var(--font-mono); font-size: var(--im-label); font-weight: 700; text-transform: uppercase; letter-spacing: var(--im-track-label); }
.im-document-strip p { margin: 0; color: var(--ink-600); font-size: var(--im-small); }

.im-cta-group { display: flex; flex-wrap: wrap; align-items: center; gap: var(--im-5); margin-top: var(--im-8); }
.im-cta-group--final { justify-content: center; margin-top: var(--im-6); }

/* Hero CTAs reuse marketing.css's .cta-button/.cta-secondary styling, but that
   file also defines them opacity:0 by default, revealed only inside a
   .cta-section that has picked up .visible from a scroll observer (see
   marketing-chrome.js). The hero CTA group sits in .im-hero, above the fold,
   never inside a .cta-section — a fade-in on the page's primary conversion
   element before the user has scrolled at all is wrong even when the reveal
   works, so it gets an unconditional override instead of ever being wired
   into the observer. */
.im-cta-group--hero .cta-button,
.im-cta-group--hero .cta-secondary {
  opacity: 1;
  transform: none;
  transition: none;
}

/* Secondary CTA (Prism round 2, tour fix 7) — DIAGNOSED as a hierarchy
   problem, not a contrast one: marketing.css's plain .cta-secondary is
   6.47:1, legible, and styled identically to body prose — no affordance
   says "click me". Outlined pill, same radius as the primary button, so it
   reads as an alternate action rather than a footnote. Reuses --action-text
   (already proven >=4.5:1 on paper/card/well in both themes) for text,
   border, and hover fill — see tests/unit/marketing-cta-contrast.test.ts
   for why these two classes can coexist on one element without a
   specificity collision. */
.im-cta-secondary {
  display: inline-flex;
  align-items: center;
  gap: var(--im-2);
  margin-top: 0;
  padding: 15px 28px;
  border: 1.5px solid var(--action-text);
  border-radius: 12px;
  color: var(--action-text);
  font-size: 16px;
  font-weight: 600;
  background: transparent;
  transition-property: background-color, border-color, transform, color;
  transition-duration: 0.2s;
  transition-timing-function: var(--ease-standard);
}
.im-cta-secondary:hover {
  background: var(--action-wash);
  border-color: var(--action);
  color: var(--action-text);
  transform: translateY(-2px);
}
.im-cta-secondary .cta-arrow { transition: transform 0.3s var(--spring); }
.im-cta-secondary:hover .cta-arrow { transform: translateX(5px); }

.im-hero-art { display: flex; align-items: center; justify-content: center; }
/* Stroke color is set by the shared .im-industry-scene rule below (--im-line)
   — cascade order (that rule is declared later, same specificity) already
   won over any color set here, so this only constrains sizing. 340px cap
   dropped (Prism round 2 §4/§6.3) — the scene should fill its column, not
   float as a small mark in a large box; the scenes themselves land in
   passes B/C, this only removes the ceiling that was starving them.
   James round-3 fix — this rule alone was never enough: the hero <svg>
   also carries `.im-industry-scene` (renderIndustryScene always adds it,
   `extraClass` only ever ADDS a second class, never swaps it), and that
   shared rule's `max-height: 160px` sits later in the cascade at equal
   specificity, so it silently won and halved every hero scene's rendered
   size regardless of what this rule said. `max-height: none` here,
   declared AFTER the shared rule (see the `:not(.im-hero-scene)` guard
   below), is the actual fix — width 100% + the 480:320 intrinsic aspect
   ratio (no explicit height attribute on the <svg>) is what lets the
   scene fill its grid column instead of letterboxing to a fixed cap. */
.im-hero-scene { width: 100%; max-width: 100%; max-height: none; }

/* ── Flow sections ─────────────────────────────────────────────────────── */
.im-flow-section { padding-block: var(--im-section-y); }
.im-flow-heading { max-width: 760px; margin-bottom: var(--im-8); }
.im-flow-heading h2,
.im-section-heading h2,
.cta-headline#final-cta {
  margin: 0;
  color: var(--ink-900);
  font-family: var(--font-display);
  font-size: var(--im-h2);
  font-weight: 400;
  line-height: var(--im-lh-h2);
  letter-spacing: var(--im-track-h2);
}
/* Section lead (Prism round 2 §4): the sentence right under a heading gets
   one size step up from body and a warmer ink, so the jump from display
   heading to body prose isn't a single flat wall. */
.im-flow-heading > p:last-child {
  margin: var(--im-3) 0 0;
  color: var(--ink-700);
  font-size: var(--im-lead);
  line-height: var(--im-lh-lead);
}

.im-pain-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--im-6); align-items: start; }
.im-pain-card { padding-top: var(--im-4); border-top: 1px solid var(--card-border); }
/* Copper numeral (Prism round 2 §2): pain + capability lists are the only
   place a plain numeral carries the "cost of today" voice. */
.im-pain-card span { color: var(--copper); font-family: var(--font-mono); font-size: var(--im-label); font-weight: 700; }
.im-pain-card p { margin: var(--im-3) 0 0; }

/* ── Product proof — a real .pm-* mockup, floated on a --well plinth (as
   /features does: a light pm-root card on a themed surface). ────────────── */
.im-product-proof { background: linear-gradient(135deg, var(--action-wash), var(--paper) 64%); }
/* Tour fix 6 (Prism round 2 §6.6) — on a wide desktop viewport the mockup
   used the full 1120px shell, stretching a card designed around a phone-
   sized product screen into an oddly wide, sparse panel. Capped and
   centered like the pain/capability lists already are. */
.im-product-snapshot { margin: 0 auto; max-width: 920px; }
/* The SaaS mockup's four review lanes (pm-rail rows) rendered as a plain
   flex column with no explicit rhythm between rows — an even 1fr grid
   guarantees every lane gets the same share of the block instead of
   drifting with its own content length. Scoped to this mockup (pm-rail is
   also used by the unrelated scroll-experience harness). */
.im-product-snapshot .pm-rail { display: grid; grid-template-columns: 1fr; }
.im-product-snapshot .pm-root {
  border: 1px solid var(--card-border);
  border-radius: var(--im-r-xl);
  background: var(--well);
  box-shadow: var(--im-shadow-lift);
  /* Desktop --im-8 / mobile --im-4 (Prism round 2 §4) — the mobile value is
     set in the 48rem breakpoint below, alongside the section's own padding
     collapse. */
  padding: var(--im-8);
  overflow: hidden;
}
.im-product-snapshot .pm-mock { border-radius: var(--im-r-lg); overflow: hidden; box-shadow: var(--im-shadow-card); }
.im-snapshot-caption {
  margin: var(--im-4) 0 0;
  color: var(--ink-600);
  font-family: var(--font-mono);
  font-size: 13px;
}
/* Tour fix 4 (Prism round 2 §6.4) — the amber "In the example packet..."
   box read as a system warning next to a mockup that already carries real
   amber "Needs review" chips (product-mockups.css's --color-amber-*); two
   amber signals on one screen fought for attention and copper/amber were
   sitting adjacent, which §2's color rules ban outright. Restyled as a
   quiet caption merged into the same block as .im-snapshot-caption above —
   no background, no border, a neutral left rule instead of a box. Saves
   ~110px at mobile (no padding/border box) and stops competing with the
   mockup's own chips for the reader's attention. */
.im-snapshot-needs-review {
  margin: var(--im-2) 0 0;
  padding-inline-start: var(--im-4);
  border-inline-start: 2px solid var(--ink-200);
  color: var(--ink-600);
  font-size: var(--im-small);
  line-height: 1.6;
}

.im-capability-list { border-top: 1px solid var(--card-border); }
.im-capability-list article { display: grid; grid-template-columns: var(--im-10) minmax(0, 1fr); gap: var(--im-5); padding-block: var(--im-5); border-bottom: 1px solid var(--card-border); }
/* Copper numeral (Prism round 2 §2) — same voice as the pain grid. */
.im-capability-list article span { color: var(--copper); font-family: var(--font-mono); font-size: var(--im-label); font-weight: 700; }
.im-capability-list article p { max-width: 640px; margin: 0; }
/* D15: no hover translate — .im-capability-list article isn't interactive. */

/* "Your team stays in control." was a solid --action-wash box with an
   --action border — Prism's diagnosis: "reads as a system notice", the
   wrong register for a human reassurance. Replaced with .im-pullquote
   (Prism round 2 §4/P7): no box, no background, just the copper rule +
   mark and a larger italic line. */
.im-human-boundary { margin-top: var(--im-8); }

/* .im-assurance ground (Prism round 2 §2): a tonal wash rather than flat
   --well grey — the trust argument should feel like the brand speaking. */
.im-assurance { background: linear-gradient(180deg, var(--action-wash), var(--paper)); }
.im-assurance-body { max-width: var(--im-measure); }

/* Assurance checklist (Prism round 2 §4, tour fix 5) — real .dot-tick marks,
   not a generic bullet. First clause carries the weight, the rest reads as
   elaboration (§4: "first clause w600 --ink-900, remainder --ink-600").
   data-reveal-stagger on the list sets --reveal-i per <li> (marketing-
   chrome.js), which the .dot-tick rules below read for the 90ms stagger. */
.im-assurance-points { list-style: none; margin: var(--im-6) 0 0; padding: 0; display: grid; gap: var(--im-4); max-width: var(--im-measure); }
.im-assurance-points li {
  display: flex;
  align-items: flex-start;
  gap: var(--im-3);
  color: var(--ink-600);
}
.im-assurance-points li .dot-tick { margin-top: 3px; }
.im-assurance-points li strong { color: var(--ink-900); font-weight: 600; }

/* ── Generic section (capacity / limitations / faq) — D3: full-width
   heading, body capped at --im-measure. No two-column grid below 1440. ──── */
.im-section { padding-block: var(--im-section-y); }
.im-section-heading { max-width: 760px; margin-bottom: var(--im-6); }
.im-section-body { max-width: var(--im-measure); color: var(--ink-700); }
.im-section-body > p { margin: 0; }
.im-section-body > p + p { margin-top: var(--im-4); }
/* Section lead — same treatment as .im-flow-heading > p:last-child above. */
.im-section-body > p:first-child { font-size: var(--im-lead); line-height: var(--im-lh-lead); }
.im-check-list { list-style: none; margin: var(--im-4) 0 0; padding: 0; display: grid; gap: var(--im-2); }
.im-check-list li { position: relative; padding-inline-start: var(--im-6); }
.im-check-list li::before { content: '—'; position: absolute; inset-inline-start: 0; color: var(--ink-400); }
.im-section--capacity { background: var(--well); }
.im-section--capacity .im-shell { padding-block: var(--im-section-y); }
.im-section--limitations { background: var(--paper); }

/* FAQ — single column, disclosure affordance (D11), same pattern as
   .page-pricing .faq-item: rotating plus, suppressed native marker. */
.im-faq-list { display: grid; gap: 0; margin-top: var(--im-6); max-width: var(--im-measure); }
.im-faq-item { padding: var(--im-5) 0; border-bottom: 1px solid var(--card-border); }
.im-faq-item:first-of-type { padding-top: 0; }
.im-faq-item:last-of-type { border-bottom: none; }
.im-faq-item summary {
  position: relative;
  min-height: 44px;
  display: flex;
  align-items: center;
  padding-inline-end: var(--im-8);
  color: var(--ink-900);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}
.im-faq-item summary::-webkit-details-marker { display: none; }
.im-faq-item summary::after {
  content: '';
  position: absolute;
  inset-inline-end: 4px;
  width: 12px;
  height: 12px;
  background:
    linear-gradient(var(--ink-500), var(--ink-500)) center / 100% 1.5px no-repeat,
    linear-gradient(var(--ink-500), var(--ink-500)) center / 1.5px 100% no-repeat;
  transition: transform 0.25s var(--ease-standard);
}
.im-faq-item[open] summary::after { transform: rotate(45deg); }
.im-faq-item p { margin: var(--im-3) 0 0; padding-inline-end: var(--im-8); color: var(--ink-600); }

/* ── Industry atlas ────────────────────────────────────────────────────── */
.im-industry-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--im-6); margin-top: var(--im-8); }
.im-industry-card {
  display: grid;
  /* B5 (Prism polish spec Part 2) — the scene column widened (.7fr -> .85fr)
     so a 220px-capped scene (see the max-height rule below, FLAG 2) has
     room to actually reach that height instead of being width-clamped
     first. Copy column narrows accordingly (1.3fr -> 1.15fr, ~354.9px ->
     314px at 1440) — re-measured against #9's 28px h3 below. */
  grid-template-columns: minmax(0, 1.15fr) minmax(140px, .85fr);
  align-items: stretch;
  min-height: 200px;
  border: 1px solid var(--card-border);
  border-radius: var(--im-r-xl);
  background: linear-gradient(135deg, var(--action-wash), var(--card) 60%);
  color: var(--ink-900);
  text-decoration: none;
  box-shadow: var(--im-shadow-card);
  overflow: hidden;
  transition: border-color 0.2s var(--ease-standard), box-shadow 0.2s var(--ease-standard), transform 0.2s var(--ease-standard);
}
.im-industry-card:hover { border-color: var(--action); color: var(--ink-900); box-shadow: var(--im-shadow-lift); transform: translateY(-2px); }
.im-industry-card:focus-visible { outline: 2px solid var(--action); outline-offset: 2px; border-radius: var(--im-r-xl); }
.im-industry-card-copy { display: flex; flex-direction: column; align-items: flex-start; justify-content: center; padding: var(--im-6) var(--im-3) var(--im-6) var(--im-6); }
.im-card-problem {
  margin: 0 0 var(--im-2);
  color: var(--action-text);
  font-family: var(--font-mono);
  font-size: var(--im-label);
  font-weight: 700;
  letter-spacing: var(--im-track-label);
  text-transform: uppercase;
}
.im-industry-card h3 { max-width: 320px; margin: 0; color: var(--ink-900); font-family: var(--font-display); font-size: 28px; font-weight: 400; line-height: 1.1; }
.im-industry-summary { max-width: 320px; margin-top: var(--im-3); color: var(--ink-600); font-size: var(--im-small); line-height: 1.5; }
.im-industry-action { min-height: 44px; display: inline-flex; align-items: center; gap: var(--im-2); margin-top: var(--im-3); color: var(--action-text); font-size: var(--im-small); font-weight: 700; }
.im-industry-action i { font-style: normal; transition: transform 0.2s var(--ease-standard); }
.im-industry-card:hover .im-industry-action i { transform: translateX(4px); }
.im-industry-scene { align-self: center; width: 100%; padding: var(--im-5); color: var(--im-line); stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
/* Hub tiles (and the FAQ/related-card contexts) get the 220px tile cap; the
   hero scene (also `.im-industry-scene`, always — see the note above
   `.im-hero-scene`) is excluded so it can fill its column instead of being
   letterboxed by a rule meant for a 200px-tall card.
   B5 / FLAG 2 (Prism polish spec Part 2) — MANDATORY alongside the 480×440
   viewBox change (render-blocks.ts). At the old 160px cap the taller
   viewBox height-clamps the hub tile and its tick renders at 6.18px —
   WORSE than the pre-spec 6.3px. 220px keeps the tick at width-bound scale:
   8.67px at 1440. Padding also drops im-5 -> im-3 for hub tiles only (same
   `:not(.im-hero-scene)`, later in the cascade at equal specificity, so it
   wins here exactly as max-height already does) — more of the 220px cap
   goes to the scene itself rather than its padding. */
/* 230px, not 220. The tick mark and the open ring were equalised to r=9 for
   optical parity, which cost the hub tile ~0.5px of rendered mark size and put
   it under the 8px legibility floor. Recovering it by un-equalising the marks
   would trade a defect you can see on every tile for one you can see on two,
   so the cap absorbs it instead: 230px puts the scene scale at ~0.453 and the
   mark at ~8.2px. Raising this further starts pushing the 2x2 card grid rows
   out of alignment again — see the h3 sizing note above. */
.im-industry-scene:not(.im-hero-scene) { max-height: 230px; padding: var(--im-3); }
.im-industry-scene .im-scene-fill { fill: var(--action-wash); stroke: var(--im-line); }
.im-industry-scene .im-scene-action { stroke: var(--action); stroke-width: 2; }
/* Refinement #1 (Prism polish spec, highest value) — strokes must not scale
   with the scene. Without this, one authored stroke-width renders at three
   different pixel weights across desktop hero / mobile hero / hub tile
   (measured 1.46 / 0.66 / 0.47px — a 3x spread) because the SVG scales as a
   whole and stroke-width scales with it. vector-effect: non-scaling-stroke
   pins every stroke to its authored px regardless of viewBox-to-viewport
   ratio. Does not inherit, hence the explicit element list rather than `*` —
   covers nested .dot-open-ring and .dot-tick-check too. */
.im-industry-scene rect,
.im-industry-scene line,
.im-industry-scene path,
.im-industry-scene circle { vector-effect: non-scaling-stroke; }

/* ── Related links row (pass 3) — landed between the FAQ and the final CTA.
   Uniform 3-up cards: two sibling industries plus one escape hatch. No
   artwork (renderIndustryScene is hub-tile scale and would blow the density
   budget here), so these are lighter than .im-industry-card — text only. ── */
.im-related-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--im-5); margin-top: var(--im-8); }
.im-related-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  padding: var(--im-6);
  border: 1px solid var(--card-border);
  border-radius: var(--im-r-lg);
  background: var(--card);
  color: var(--ink-900);
  text-decoration: none;
  box-shadow: var(--im-shadow-card);
  transition: border-color 0.2s var(--ease-standard), box-shadow 0.2s var(--ease-standard), transform 0.2s var(--ease-standard);
}
.im-related-card:hover { border-color: var(--action); color: var(--ink-900); box-shadow: var(--im-shadow-lift); transform: translateY(-2px); }
.im-related-card:focus-visible { outline: 2px solid var(--action); outline-offset: 2px; }
.im-related-card h3 { margin: 0; color: var(--ink-900); font-family: var(--font-display); font-size: var(--im-h3); font-weight: 400; }
.im-related-summary { margin: var(--im-2) 0 0; color: var(--ink-600); font-size: var(--im-small); line-height: 1.5; }
.im-related-card .im-industry-action { margin-top: var(--im-3); }
.im-related-card:hover .im-industry-action i { transform: translateX(4px); }
/* Escape hatch: no eyebrow, muted border/background — visually the "none of
   these" option, not a third sibling. */
.im-related-card--escape { background: var(--well); border-style: dashed; }
.im-related-card--escape:hover { border-color: var(--ink-400); box-shadow: none; transform: none; }
.im-related-card--escape .im-industry-action { color: var(--ink-600); }

/* Hub-only: the same escape hatch, directly under the industry grid — the
   hub previously had no path at all for a non-matching visitor. */
.im-escape-row { display: flex; justify-content: center; margin-top: var(--im-6); }
.im-escape-row .im-related-card { width: 100%; max-width: 460px; }

/* ── Responsive ────────────────────────────────────────────────────────── */
/* Tour fix 3 (Prism round 2 §6.3) / mobile density §5 — the hero art used to
   go `display: none` below 56.25rem entirely, which is exactly why mobile
   read as pure text with zero second visual voice. It returns capped at
   ~180px tall (width auto, centered) rather than stretching to the full
   shell width the way it does on desktop — the animated scenes (Prism
   round 2 §3) still play and still land on their honest static end-state,
   just smaller. */
@media (max-width: 56.25rem) {
  .im-hero-layout { grid-template-columns: minmax(0, 1fr); }
  .im-hero-art { display: flex; margin-top: var(--im-6); }
  /* B4 (Prism polish spec Part 2) — FLAG 1: 240px was correct arithmetic
     against the old 1.5:1 (480x320) viewBox only. At 480x440 and a 343px
     art column (375 viewport), 240px gives ink 218.2x200 = 63.6% of the
     column — barely better than the pre-spec 61.2%. 320px gives ink
     303x277.8 = 88.3%, box height 317.8px. width:auto/max-width:100% stay
     as authored — at 480x440 the height cap binds. */
  .im-hero-scene { width: auto; max-width: 100%; max-height: 320px; }
}

@media (max-width: 48rem) {
  .im-shell { width: min(100% - var(--im-5) * 2, var(--im-max)); }
  .im-hero { padding-block: var(--im-12) var(--im-10); }
  /* Mobile density §5: --im-section-y mobile 48px -> 40px — the .dot-rule
     between sections already occupies part of the recovered rhythm, same
     reasoning as the 88->72 desktop cut above. */
  .im-flow-section,
  .im-section { padding-block: var(--im-10); }
  .im-section--capacity .im-shell { padding-block: var(--im-10); }
  .im-faq-list,
  .im-industry-grid,
  .im-related-grid { grid-template-columns: minmax(0, 1fr); }
  .im-product-snapshot .pm-root { padding: var(--im-4); }
  /* Subline elision (Sable-approved, Prism round 2 §5): the job line is
     supporting detail under the audience line and the h1 — two lines is
     enough for a mobile reader to get the gist without another paragraph
     of vertical space. */
  .im-hero-job {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
}

@media (max-width: 40rem) {
  .im-shell { width: min(100% - var(--im-4) * 2, var(--im-max)); }
  .im-pain-grid { grid-template-columns: minmax(0, 1fr); gap: var(--im-5); }
  .im-industry-card { grid-template-columns: minmax(0, 1fr); min-height: 0; }
  /* Hub tile cap only — hero scene keeps its own mobile cap set in the
     56.25rem query above (`.im-hero-scene { max-height: 180px }`), which
     this 40rem block would otherwise clobber (equal specificity, declared
     later in the file, matches the same element since it also carries
     `.im-industry-scene`). */
  .im-industry-scene:not(.im-hero-scene) { max-height: 90px; padding: 0 var(--im-3) var(--im-3); }
  /* Hub density: the industry-grid's 4 cards were the single largest
     contributor to the hub's 375px total (not on Prism's literal
     per-section list, but the hub's own 6,300px budget can't be hit
     without it). Copy padding and grid rhythm tighten; nothing about a
     card's content — problem tag, name, summary, action — is removed. */
  .im-industry-grid { gap: var(--im-4); margin-top: var(--im-6); }
  .im-industry-card-copy { padding: var(--im-3); }
  .im-industry-card h3 { font-size: var(--im-h3); line-height: 1.15; }
  .im-industry-summary {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  /* Mobile density §5: product-proof 1,692->1,120. pm-field-grid's own
     2-column collapse to 1 (product-mockups.css's `@container pm (max-width:
     420px)`) is the single biggest contributor to the baseline's 8,875px —
     restoring 2 columns here is called out as the biggest individual win.
     pm-root padding 24px -> 12px on top of that. */
  .im-product-snapshot .pm-field-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .im-product-snapshot .pm-root { padding: var(--im-3); }

  /* Mobile density §5: capabilities 990->700, pain 783->560 — the numeral
     becomes an inline lead-in ahead of the sentence instead of its own
     grid column/line. Desktop keeps the spacious vertical layout; this
     only fires under 40rem. */
  .im-pain-card { padding-top: var(--im-3); }
  .im-pain-card span,
  .im-pain-card p { display: inline; }
  .im-pain-card span { margin-inline-end: var(--im-2); }
  .im-pain-card p { margin: 0; }
  .im-capability-list article {
    display: block;
    grid-template-columns: none;
    padding-block: var(--im-3);
  }
  .im-capability-list article span,
  .im-capability-list article p { display: inline; }
  .im-capability-list article span { margin-inline-end: var(--im-2); }
  .im-capability-list article p { margin: 0; }

  /* Mobile density §5: related-links 912->560 — sibling cards collapse from
     boxed cards into compact rows (eyebrow + h3 + arrow, one line each);
     the summary paragraph drops per spec ("drop summary <=40rem"). The
     escape-hatch card loses its heading too, reading as a single text
     link (question folded into the action line) rather than a fourth card. */
  .im-related-grid { gap: 0; margin-top: var(--im-5); }
  .im-related-card {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    min-height: 44px;
    padding: var(--im-3) 0;
    border: none;
    border-bottom: 1px solid var(--card-border);
    border-radius: 0;
    background: transparent;
    box-shadow: none;
  }
  .im-related-card:hover { border-color: transparent; border-bottom-color: var(--action); box-shadow: none; transform: none; }
  .im-related-card:last-of-type { border-bottom: none; }
  .im-related-summary { display: none; }
  .im-related-card h3 { font-size: var(--im-body); }
  .im-related-card .im-card-problem { margin-bottom: 0; }
  .im-related-card .im-industry-action { margin-top: var(--im-1); }
  .im-related-card--escape h3 { display: none; }
  .im-escape-row .im-related-card { max-width: none; }

  /* Further mobile-density tightening (measured pass, not in Prism's
     literal spec but within its spirit): the section budgets in §5 assume
     more headroom than the generic flow-heading/pullquote/checklist
     defaults left on the table. None of this touches copy, honesty
     content, or hides anything — it's font-size/line-height/spacing only,
     same content, smaller. */
  .im-flow-heading,
  .im-section-heading { margin-bottom: var(--im-5); }
  .im-flow-heading > p:last-child { font-size: var(--im-body); line-height: var(--im-lh-body); }
  .im-pain-card span,
  .im-pain-card p,
  .im-capability-list article span,
  .im-capability-list article p { font-size: var(--im-small); }
  .im-pain-card p,
  .im-capability-list article p { line-height: 1.55; }
  .im-pullquote { font-size: 19px; }
  .im-product-snapshot .pm-checklist-row { padding: 4px 0; }
  .im-product-snapshot .pm-checklist-label { font-size: 11px; line-height: 1.4; }
  .im-product-snapshot .pm-panel-head { padding: var(--im-2) var(--im-3); }
  .im-product-snapshot .pm-field { padding: var(--im-2) var(--im-3); min-height: 52px; }
  .im-capability-list article,
  .im-pain-card { padding-block: var(--im-2); }
  .im-pain-card { padding-top: var(--im-2); }
  .im-pullquote { font-size: 18px; }
  .im-faq-item { padding: var(--im-4) 0; }
  /* .cta-section is marketing.css's shared final-band component (also used
     by /features, /pricing, /technology) — scoped to .im-page rather than
     edited globally, so this mobile-density pass doesn't change those
     pages' rhythm. */
  .im-page .cta-section { padding: var(--im-10) var(--im-4) var(--im-12); }
  /* The merged caption block (tour fix 4) reads as one unit with
     .im-snapshot-caption above it — matching its mono/13px sizing at
     mobile closes the gap toward Prism's ~110px estimate for this
     specific change, which assumed a tighter caption than --im-small/1.6
     produces in a narrow column. Desktop keeps the spec's literal
     --im-small/lh:1.6 sizing. */
  .im-snapshot-needs-review { font-family: var(--font-mono); font-size: 12px; line-height: 1.5; }
}

@media (prefers-reduced-motion: reduce) {
  /* transition-duration:0 alone left every scene keyframe animation running
     (dotFlowTravel from pass A, plus round 2 pass B's sceneTotalFlash /
     sceneOpenPulse / sceneLaneBreathe) — `animation`, not `transition`, so
     the reduce query didn't touch it. animation-duration:0 collapses each
     one to its instant end state (fill:both keeps it there), which for
     every one of these is the scene's already-correct finished picture —
     satisfies the same "reduced motion -> complete scene, no animation"
     contract [data-reveal]'s own reduce rule (marketing.css) gives the
     fade+rise baseline. */
  .im-page *,
  .im-page *::before,
  .im-page *::after {
    scroll-behavior: auto !important;
    transition-duration: 0s !important;
    animation-duration: 0s !important;
    animation-delay: 0s !important;
  }
}

@media (forced-colors: active) {
  .im-product-snapshot .pm-root,
  .im-pain-card,
  .im-pullquote,
  .im-faq-item,
  .im-industry-card,
  .im-related-card { border: medium solid CanvasText; }
}

/* ==========================================================================
   SIX PRIMITIVES (Prism round 2, §1 primitives list) — a small system of
   named, reusable dot/trace components, built once here so passes B and C
   (and eventually /features, /pricing, /technology — Prism round 2 §7) can
   consume them without re-deriving the grammar. Two are wired live in this
   pass (.dot-rule between sections, .dot-mark on eyebrows above and the
   pull-quote below); the rest (.al-trace, .dot-flow, .dot-tick, .dot-meter)
   are defined completely but only .dot-tick is consumed yet (the assurance
   checklist, tour fix 5) — .al-trace/.dot-flow/.dot-meter wait for the
   animated scenes in passes B/C.

   Honesty bindings are load-bearing, not decoration: .dot-tick only ever
   marks a check that actually passed; an open/failing item is .dot-open — a
   plain unfilled ring in --im-line, never red, never animated. .dot-meter
   never scales to 1 and never reads as green-complete; a component consuming
   it sets --meter-value below 1 and writes a label naming what's missing.
   Motion is compositor-friendly (transform/opacity) everywhere except
   .al-trace, which accepts the stroke-dashoffset paint cost deliberately,
   bounded (Prism round 2 §1 P2): <=3 concurrent per page, <=900ms each, no
   will-change left standing after the transition settles (none is ever set
   here, so there is nothing to clean up).
   ========================================================================== */

/* P1 — .dot-rule: three dots between sections, replacing two adjacent bare
   --im-section-y gaps with one gap + a mark. Center dot is --action, outer
   two are --ink-300 — small deliberate asymmetry the plain three-dot row
   didn't have (.dot-divider in marketing.css keeps all three matched grey;
   this one names the middle dot as the brand's). Default state (no JS, or
   JS hasn't reached .is-revealed yet) is fully visible per-dot — the
   entrance below only ever plays once .js-reveal is present. */
.dot-rule {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--im-3);
  padding-block: var(--im-2);
}
.dot-rule span {
  display: block;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--ink-300);
  transform: scale(1);
  transition: transform 0.4s var(--spring);
}
.dot-rule span:nth-child(2) { width: 6px; height: 6px; background: var(--action); }
.js-reveal .dot-rule[data-reveal] span { transform: scale(0); }
.js-reveal .dot-rule[data-reveal].is-revealed span:nth-child(1) { transform: scale(1); transition-delay: 0ms; }
.js-reveal .dot-rule[data-reveal].is-revealed span:nth-child(2) { transform: scale(1); transition-delay: 80ms; }
.js-reveal .dot-rule[data-reveal].is-revealed span:nth-child(3) { transform: scale(1); transition-delay: 160ms; }

/* P2 — .al-trace: an SVG path that inks itself from a source to a target.
   Consuming markup is an inline <svg class="al-trace" data-reveal> with a
   single <path>; the caller sets --trace-len (inline style) to the path's
   real length (path.getTotalLength()) once JS can measure it — passes B/C
   wire this. Static fallback (no JS, or --trace-len never set) is the path
   fully drawn — offset 0 is the BASE state, matching the inversion rule:
   the finished picture is what renders with no script running. */
.al-trace { overflow: visible; }
.al-trace path {
  fill: none;
  stroke: var(--action);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-dashoffset: 0;
}
.js-reveal .al-trace[data-reveal] path { stroke-dasharray: var(--trace-len, 1px); stroke-dashoffset: var(--trace-len, 0px); }
.js-reveal .al-trace[data-reveal].is-revealed path {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 700ms var(--ease-standard);
}

/* P3 — .dot-flow: 2-3 dots travel a trace path after it completes, once,
   never looping. Consuming markup: a positioned .dot-flow wrapper holding
   .dot-flow-dot spans, each with its own offset-path (inline style, set
   by the caller to the same path the sibling .al-trace draws). Static
   fallback / no offset-path support: dots sit authored at the TARGET end —
   "arrived" is the truth the static page tells.
   `position: absolute; inset: 0` was dropped from the wrapper (round 2 pass
   B): it's an HTML-div affordance, and applied to the inline-SVG <g> this
   primitive's only real consumers (RE + SaaS hero scenes) use, it collapsed
   the group to a zero-area box pinned to the document origin — a target
   with zero intersection area never crosses IntersectionObserver's 0.15
   threshold, so `.is-revealed` never landed and the dots sat at opacity 0
   forever (worse than the no-JS state, which is complete). The group is
   unpositioned now, sized by its own circles' real geometry — which,
   before reveal, is a 2-3px dot cluster at the path's start (offset-
   distance:0%). That was still too small/borderline for some
   IntersectionObserver implementations to reliably cross the 0.15
   threshold, so the real-estate consumer (the only one today) adds a
   transparent `.im-scene-observer-anchor` rect sized to the connector's
   real footprint, purely so `.dot-flow` has an honest, generously-sized
   target to observe — it paints nothing. */
.dot-flow { pointer-events: none; }
.im-scene-observer-anchor { fill: transparent; }
.dot-flow-dot {
  position: absolute;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--action);
  offset-distance: 100%;
  opacity: 1;
}
.js-reveal .dot-flow[data-reveal] .dot-flow-dot { offset-distance: 0%; opacity: 0; }
.js-reveal .dot-flow[data-reveal].is-revealed .dot-flow-dot {
  animation: dotFlowTravel 1.1s var(--ease-standard) forwards;
}
.js-reveal .dot-flow[data-reveal].is-revealed .dot-flow-dot:nth-child(2) { animation-delay: 180ms; }
.js-reveal .dot-flow[data-reveal].is-revealed .dot-flow-dot:nth-child(3) { animation-delay: 360ms; }
@keyframes dotFlowTravel {
  0% { offset-distance: 0%; opacity: 0; }
  12% { opacity: 1; }
  88% { opacity: 1; }
  100% { offset-distance: 100%; opacity: 0; }
}
@supports not (offset-path: path('M0 0')) {
  /* No offset-path support: skip the animation entirely, land on the
     honest static state (dots at the target) rather than an inert dot
     stuck at the origin. */
  .js-reveal .dot-flow[data-reveal] .dot-flow-dot,
  .js-reveal .dot-flow[data-reveal].is-revealed .dot-flow-dot {
    offset-distance: 100%;
    opacity: 1;
    animation: none;
  }
}

/* P4 — .dot-tick / .dot-open: a 16px mark for one checklist row. Markup:
   <span class="dot-tick" aria-hidden="true"><svg viewBox="0 0 20 20">
     <circle class="dot-tick-fill" cx="10" cy="10" r="10"/>
     <path class="dot-tick-check" d="M5 10.5L8.5 14L15 6.5"/>
   </svg></span>
   — check geometry copied from dot-morph.js's ICON_PATHS "check" points
   ([[5,10.5],[8.5,14],[15,6.5]]), as path DATA only (no engine loaded).
   .dot-open is the honest twin for anything that has NOT passed: an
   unfilled ring in --im-line, no fill, no check, no animation, never red —
   swap the two classes on the wrapper span, never style .dot-tick to
   "look open" or vice versa. */
.dot-tick,
.dot-open {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}
.dot-tick svg,
.dot-open svg { width: 100%; height: 100%; overflow: visible; }
.dot-tick-fill { fill: var(--action); transform-origin: 10px 10px; transform: scale(1); }
.dot-tick-check {
  fill: none;
  stroke: var(--card);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 15;
  stroke-dashoffset: 0;
}
.dot-open-ring { fill: none; stroke: var(--im-line); stroke-width: 1.5; }
.js-reveal [data-reveal] .dot-tick-fill { transform: scale(0); }
.js-reveal [data-reveal] .dot-tick-check { stroke-dashoffset: 15; }
.js-reveal [data-reveal].is-revealed .dot-tick-fill {
  transform: scale(1);
  transition-property: transform;
  transition-duration: 320ms;
  transition-timing-function: var(--spring);
}
.js-reveal [data-reveal].is-revealed .dot-tick-check {
  stroke-dashoffset: 0;
  transition-property: stroke-dashoffset;
  transition-duration: 260ms;
  transition-timing-function: var(--ease-standard);
  transition-delay: 120ms;
}
/* Sibling stagger (90ms) inside any data-reveal-stagger container — the CSS
   var is set per direct child by marketing-chrome.js's applyRevealStagger()
   and inherits down to the tick inside it. */
[data-reveal-stagger] > * .dot-tick-fill {
  transition-delay: calc(var(--reveal-i, 0) * 90ms);
}
[data-reveal-stagger] > * .dot-tick-check {
  transition-delay: calc(var(--reveal-i, 0) * 90ms + 120ms);
}

/* P5 — .dot-meter: a completeness bar that only ever contracts. Markup:
   <div class="dot-meter" data-reveal style="--meter-value:0.8">
     <div class="dot-meter-fill"></div>
   </div>
   Content rule, not just a CSS one: --meter-value must never be authored as
   1 — the label naming what's still missing is the actual product claim,
   and a bar that reads full contradicts it. */
.dot-meter {
  position: relative;
  height: 6px;
  border-radius: var(--im-r-full);
  background: var(--ink-100);
  overflow: hidden;
}
.dot-meter-fill {
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: 100%;
  transform-origin: left center;
  transform: scaleX(var(--meter-value, 0.8));
  background: var(--action);
  border-radius: inherit;
}
.js-reveal .dot-meter[data-reveal] .dot-meter-fill { transform: scaleX(0); }
.js-reveal .dot-meter[data-reveal].is-revealed .dot-meter-fill {
  transform: scaleX(var(--meter-value, 0.8));
  transition: transform 900ms var(--ease-standard);
}

/* P6 — .dot-mark: a static 6px --action dot. No motion, ever — this is the
   one primitive with no [data-reveal] variant. Used inline (eyebrows, via
   ::before above) or as a standalone element (the pull-quote's opening
   mark below, where it takes the --dot-mark-color override to go copper). */
.dot-mark {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--dot-mark-color, var(--action));
  flex-shrink: 0;
}

/* P7 — .im-pullquote (Prism round 2 §4, typography table): a boundary-line
   statement set as a real quote, not a colored box. Fraunces italic at
   --im-quote, copper rule + mark (copper is the one non-status use of the
   color the spec allows outside numerals/eyebrows) — no background, so it
   never again reads as "system notice". */
.im-pullquote {
  position: relative;
  margin: 0;
  max-width: 30ch;
  padding-inline-start: var(--im-6);
  border-inline-start: 2px solid var(--copper);
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: var(--im-quote);
  line-height: var(--im-lh-quote);
  color: var(--ink-900);
}
.im-pullquote .dot-mark {
  --dot-mark-color: var(--copper);
  position: absolute;
  inset-inline-start: calc(-1 * var(--im-6) - 4px);
  top: 10px;
}
/* Refinement #8 — inside .im-human-boundary the pull-quote's -28px offset
   collides with the 56px human-boundary motif that already sits to its
   left (20px gap), so the copper dot lands INSIDE the motif's bbox and
   reads as part of the illustration. .im-pullquote has exactly one
   consumer (renderCapabilityHighlights) and it always carries the motif,
   so the standalone mark never renders correctly today — the motif is
   already that block's opening mark. */
.im-human-boundary .im-pullquote .dot-mark { display: none; }

/* Hub tour fix 1 (Prism round 2 §6.1, highest-value fix) — "Which of these
   is your Tuesday?" as four real links, not four inert <li> strings.
   .dot-mark starts neutral (--ink-300) and turns --action on hover/focus —
   the inverse of the pull-quote's static copper mark, because this one
   needs to read as interactive. Custom-property override on the anchor
   cascades down to the .dot-mark span (custom properties inherit). */
.im-tuesday-list { list-style: none; margin: var(--im-4) 0 0; padding: 0; display: grid; gap: var(--im-1); max-width: var(--im-measure); }
.im-tuesday-link {
  --dot-mark-color: var(--ink-300);
  display: flex;
  align-items: center;
  gap: var(--im-3);
  min-height: 44px;
  padding-block: var(--im-2);
  color: var(--ink-900);
  text-decoration: none;
}
.im-tuesday-link .dot-mark { transition: background-color 0.2s var(--ease-standard); }
.im-tuesday-link:hover,
.im-tuesday-link:focus-visible { --dot-mark-color: var(--action); color: var(--ink-900); }
.im-tuesday-text { flex: 1; }
.im-tuesday-arrow {
  flex-shrink: 0;
  font-style: normal;
  color: var(--action-text);
  transition: transform 0.2s var(--ease-standard);
}
.im-tuesday-link:hover .im-tuesday-arrow,
.im-tuesday-link:focus-visible .im-tuesday-arrow { transform: translateX(4px); }

/* Mobile-density disclosure (Prism round 2 §5) — a native <details> with no
   `open` attribute authored (mobile default: collapsed, one tap to read the
   prose). At >=48.0625rem the toggle affordance is inert and the body is
   forced visible regardless of [open] — the desktop reading experience is
   unchanged from before this landed. NEVER used on FAQ or on honesty
   content (checklist / limitations items always render as plain siblings,
   never inside .im-disclosure — see render-blocks.ts's renderDisclosure
   callers). */
.im-disclosure { margin: 0; }
.im-disclosure summary { display: none; }
.im-disclosure-body p { margin: 0; }
.im-disclosure-body p:first-child { font-size: var(--im-lead); line-height: var(--im-lh-lead); color: var(--ink-700); }
.im-disclosure-body p + p { margin-top: var(--im-4); }
@media (max-width: 48rem) {
  .im-disclosure summary {
    display: flex;
    align-items: center;
    gap: var(--im-2);
    min-height: 44px;
    color: var(--ink-900);
    font-size: var(--im-small);
    font-weight: 600;
    cursor: pointer;
    list-style: none;
  }
  .im-disclosure summary::-webkit-details-marker { display: none; }
  .im-disclosure summary::after {
    content: '';
    flex-shrink: 0;
    margin-inline-start: auto;
    width: 12px;
    height: 12px;
    background:
      linear-gradient(var(--ink-500), var(--ink-500)) center / 100% 1.5px no-repeat,
      linear-gradient(var(--ink-500), var(--ink-500)) center / 1.5px 100% no-repeat;
    transition: transform 0.25s var(--ease-standard);
  }
  .im-disclosure[open] summary::after { transform: rotate(45deg); }
}
/* No desktop force-open rule: closed <details> content is hidden at the
   rendering level in Chromium (not via an overridable UA display rule — an
   author display:block measured 0px height), so the markup ships [open] and
   marketing-chrome.js removes the attribute on small screens when JS runs.
   Desktop and every no-JS visitor therefore always see the content. */

/* ==========================================================================
   PASS B — the four animated hero scenes + hub compact variants
   (render-blocks.ts's sceneRealEstate/sceneAccounting/sceneContracting/
   sceneSaas), plus the three inline lower-half motifs. Every keyframe below
   is one-shot: `animation-fill-mode: both` (never `alternate`, never
   `infinite`), gated behind `.is-revealed` exactly like the six primitives
   above, and lands on a state that is ALREADY correct in the base markup —
   these rules only ever add motion on top of a scene that renders complete
   without them. See render-blocks.ts's scene builders for the honesty
   bindings (which rows tick, which stay open, what the meter stops at).
   ========================================================================== */

/* .dot-open-ring needs to scale around its OWN center wherever it lands in a
   larger scene (the contracting open-ring pulse), not the nested <svg>'s
   local (0,0) — transform-box:fill-box makes transform-origin relative to
   the ring's own bounding box. Harmless where .dot-open is used unscaled
   (the assurance list has no animation on this element). */
.dot-open-ring { transform-box: fill-box; transform-origin: center; }

/* .dot-meter-fill needs `fill` (not just `background`) when the primitive is
   consumed as a nested <svg> rect inside a scene, rather than the HTML div
   markup P5's own doc comment shows — both properties coexist harmlessly
   (each is a no-op on the element type the other targets). */
.dot-meter-fill { fill: var(--action); }
.im-scene-track { fill: var(--ink-100); stroke: var(--im-line); stroke-width: 1; }

.im-scene-label { fill: var(--im-line); stroke: none; font-family: var(--font-mono); font-size: 12px; }

/* Real estate — badge fade-in once the connector + flow finish arriving. */
.im-scene-badge rect { fill: var(--action-wash); stroke: none; }
.im-scene-badge text { fill: var(--action-text); stroke: none; font-family: var(--font-mono); font-size: 11px; font-weight: 700; text-anchor: middle; }

/* Accounting — the total pill flashes once, honestly, after both real ticks
   (tax arithmetic, PO match) have resolved — a flash is attention, not a
   verdict, and it returns to `fill:none` rather than holding any color. */
.im-scene-total-flash rect { fill: none; stroke: var(--im-line); }
.im-scene-total-flash text { fill: var(--ink-900); stroke: none; font-family: var(--font-mono); font-size: 12px; font-weight: 600; }
.js-reveal .im-scene-total-flash[data-reveal].is-revealed rect {
  animation: sceneTotalFlash 500ms var(--ease-standard) 620ms both;
}
@keyframes sceneTotalFlash {
  0%, 100% { fill: none; }
  35% { fill: var(--action-wash); }
}

/* Contracting — the one open ring (signed lien waiver, still outstanding)
   pulses once after the completeness meter settles. It never fills and
   never turns --action — pulsing size says "still open", not "resolved". */
.js-reveal .im-scene-open-pulse[data-reveal].is-revealed .dot-open-ring {
  animation: sceneOpenPulse 400ms var(--spring) 900ms both;
}
@keyframes sceneOpenPulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.25); }
}

/* SaaS — lanes 2+3 (legal, security — the two still in flux) breathe in once
   their connecting trace lands, then HOLD at full opacity (`both`, no
   `alternate`, no `infinite`). A permanent pulse would read as "still live",
   which is the one thing these two lanes are not — the dashed/filled state
   underneath is unaffected by this animation and stays the honest read. */
.im-scene-lane-breathe { opacity: .4; }
.js-reveal .im-scene-lane-breathe[data-reveal].is-revealed {
  animation: sceneLaneBreathe 600ms var(--ease-standard) both;
}
@keyframes sceneLaneBreathe {
  0% { opacity: .4; }
  100% { opacity: 1; }
}

/* Hub compact variants (render-blocks.ts's 'hub' scene variant) get an
   entrance only — trace/flow/meter-fill/lane-breathe motion is omitted
   entirely in that variant's markup, so the generic [data-reveal] fade+rise
   this file's tick/open primitives already share is the only motion a hub
   tile ever plays. Staggered per card via the shared data-reveal-stagger
   mechanism (marketing-chrome.js's applyRevealStagger). */
[data-reveal-stagger] > .im-industry-card .im-industry-scene {
  transition-delay: calc(var(--reveal-i, 0) * 80ms);
}

/* ── Inline motifs — the text-heavy lower half (assurance, capacity,
   human-boundary) so no stretch of the page is image-free. ~56px,
   --im-line stroke plus exactly one --action element, static (no [data-
   reveal] of their own — the section around them already reveals). ────── */
.im-motif {
  flex-shrink: 0;
  width: 56px;
  height: 56px;
  color: var(--im-line);
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.im-motif .im-scene-action { fill: var(--action); stroke: none; }
.im-motif .im-scene-open-mark { fill: none; stroke: var(--action); }
.im-motif .im-scene-fill { fill: var(--action-wash); stroke: var(--im-line); }
.im-flow-heading--with-motif,
.im-human-boundary {
  display: flex;
  align-items: flex-start;
  gap: var(--im-5);
}
.im-flow-heading--with-motif .im-motif { margin-top: var(--im-1); }
.im-human-boundary .im-motif { margin-top: 6px; }
