@layer myrmid.core, myrmid.tokens, myrmid.foundation, myrmid.components;
/* ──────────────────────────────────────────────────────────────────────────
   tokens/index.css — single entry point for landing-page's CSS cascade.
   PostCSS resolves these @import directives at build time (postcss-import),
   so dist/tokens.css is a single concatenated file with NO runtime @import
   chain. See architecture.md §Token File Organization (lines 537–550).

   Migrated 2026-05-29; repointed 2026-06-07 to the @myrmid/brand layer of the
   v0.5.0 workspace split. Brand tokens (color, type, space + foundation's
   motion, self-resolving) and the brand foundation primitives (layout-base,
   stigmergy, cta-interaction) now ship from @myrmid/brand. Brand wraps its
   output in cascade layers (@layer myrmid.core/tokens/foundation/components);
   landing-page's own CSS below stays UNLAYERED and therefore wins over every
   myrmid.* layer (the intended consumer model). The landing-page-specific
   supplements (site-chrome.css, crosslinks.css) load alongside the foundation
   files they extend.
   ────────────────────────────────────────────────────────────────────────── */

/* Brand tokens — color + space + type + motion (light + dark, locale-aware) */
@layer myrmid.core {
:root {
  --myr-core: 3b84214ec757ac54;
}
/* ──────────────────────────────────────────────────────────────────────────
   color-tokens.css — three-axis colour system, two-layer per Story 1.3 D1.

   Axes:        --color-{mode}-{aspect}-{surface}
                  mode    ∈ { light, dark }                        (data-theme on <html>)
                  aspect  ∈ { primary, secondary, accent, surface, text, border }
                  surface ∈ { base, enterprise, smb, developer, manifesto, dpp }

   Foundation (Core) now ships ONLY the raw axis tokens. The semantic colour
   aliases (--surface-page, --ink-primary, --accent-eu-blue, …) live per-channel
   — @myrmid/brand declares the editorial values, @myrmid/app the dense values,
   both as var(--color-…) over these raw axes (Story 2.2, architecture AR-9).

   Source-of-truth values: _bmad-output/implementation-artifacts/manifesto-preview.html
   (Tom-signed canonical visual reference — UX line 551).

   Mode resolution priority (architecture.md §Theme Detection 619–627):
     1. localStorage.theme  → :root[data-theme="dark"|"light"]
     2. prefers-color-scheme → @media block (only if user hasn't forced light)
     3. light (canonical fallback baked into :root)

   Story 1.3 declares ONLY the (aspect × surface) cells the manifesto-preview
   actually uses. Aspect drift (enterprise / smb / developer / dpp) lands in
   Stories 2.x / 3.x as those surfaces are built. Don't backfill.
   ────────────────────────────────────────────────────────────────────────── */

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Light mode — canonical fallback. Raw axis tokens.                      │
   └────────────────────────────────────────────────────────────────────────┘ */
:root {
  /* Native form controls / scrollbars match the active theme.
     Without this declaration browsers render scrollbars in their default
     light styling under data-theme="dark" — visible white slab on the page. */
  color-scheme: light dark;

  /* — surface aspect — */
  --color-light-surface-base:      #F6F6F5;  /* whitish-white substrate */
  --color-light-surface-card:      #FAFAFA;

  /* — text aspect — */
  --color-light-text-base:         #1C1C20;  /* anthracite, near-black */
  --color-light-text-secondary:    #4A4A52;
  --color-light-text-quiet:        #717178;

  /* — border aspect — */
  --color-light-border-quiet:      #E4E4E2;

  /* — accent aspect — */
  --color-light-accent-eu-blue:    #1B2D5C;  /* EU sovereign blue */
  --color-light-accent-eu-gold:    #A07C2C;  /* EU gold, AA-clean */
  --color-light-accent-eu-gold-foil: #C9A24A;  /* EU gold decorative */
  /* Copper: the charge (glow, trails, hover, focus) and the principal accent; gold is Cairn's. */
  --color-light-accent-copper:     #C2410C;
  --color-light-accent-copper-ink: #9A3412;  /* copper set as text on light */
  --color-light-accent-primary:    var(--color-light-accent-copper);

  /* — secondary aspect (metallic finishes, structural-only) — */
  --color-light-secondary-platine: #B0B5BD;
  --color-light-secondary-steel:   #8A8E9A;

  /* — primary aspect — brand-mark channels, B&W mode-adaptive — */
  --color-light-primary-brand-bg:  #1C1C20;
  --color-light-primary-brand-fg:  #F6F6F5;

  /* — stigmergy texture (substrate dot-grid + waypoint marks + trails) — */
  --color-light-surface-grid-dot:  rgba(28, 28, 32, 0.07);

  /* — functional working palette (chromatic, structural-only at brand layer) —
     7 hues (copper first) × base/soft/strong/on. Base clears AA
     4.5:1 as text on the light substrate AND white as text on the base fill. */
  --color-light-copper-base:       #C2410C;
  --color-light-copper-soft:       #FBEAE1;
  --color-light-copper-strong:     #9A3412;
  --color-light-copper-on:         #FFFFFF;
  /* The working blue is EU blue, stepped down from a brand accent (2026-10-15). */
  --color-light-blue-base:         #1B2D5C;
  --color-light-blue-soft:         #E6EAF3;
  --color-light-blue-strong:       #121F42;
  --color-light-blue-on:           #FFFFFF;
  --color-light-red-base:          #B23A36;
  --color-light-red-soft:          #FBE7E5;
  --color-light-red-strong:        #8F2A27;
  --color-light-red-on:            #FFFFFF;
  --color-light-green-base:        #1D6D52;
  --color-light-green-soft:        #E0F0EB;
  --color-light-green-strong:      #145741;
  --color-light-green-on:          #FFFFFF;
  --color-light-purple-base:       #6A4C9C;
  --color-light-purple-soft:       #EEE8F7;
  --color-light-purple-strong:     #4F3A78;
  --color-light-purple-on:         #FFFFFF;
  --color-light-turquoise-base:    #136B76;
  --color-light-turquoise-soft:    #E0F0F2;
  --color-light-turquoise-strong:  #0C525C;
  --color-light-turquoise-on:      #FFFFFF;
  --color-light-amber-base:        #B07E07;
  --color-light-amber-soft:        #FCF2D9;
  --color-light-amber-strong:      #7A5605;
  --color-light-amber-on:          #241A04;
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Dark mode — explicit user toggle (data-theme="dark") wins outright.    │
   │ Redeclares the raw axis tokens; channels redeclare their own aliases.  │
   └────────────────────────────────────────────────────────────────────────┘ */
:root[data-theme="dark"] {
  /* Pin native form controls / scrollbars to dark even when OS prefers light. */
  color-scheme: dark;

  --color-dark-surface-base:        #141414;  /* deep anthracite */
  --color-dark-surface-card:        #1C1C20;

  --color-dark-text-base:           #F0F0EE;  /* whitish-white, off-neutral */
  --color-dark-text-secondary:      #C4C4C6;
  --color-dark-text-quiet:          #888890;

  --color-dark-border-quiet:        #282828;

  --color-dark-accent-eu-blue:      #5D78B5;  /* lighter signal in dark */
  --color-dark-accent-eu-gold:      #D9B65C;
  --color-dark-accent-eu-gold-foil: #E0C77A;
  --color-dark-accent-copper:       #FB923C;
  --color-dark-accent-copper-ink:   #FB923C;
  --color-dark-accent-primary:      var(--color-dark-accent-copper);

  --color-dark-secondary-platine:   #5A5A62;
  --color-dark-secondary-steel:     #484850;

  --color-dark-primary-brand-bg:    #F0F0EE;
  --color-dark-primary-brand-fg:    #1C1C20;

  --color-dark-surface-grid-dot:    rgba(240, 240, 238, 0.05);

  /* — functional working palette — dark (lighter signals on anthracite) — */
  --color-dark-copper-base:        #FB923C;
  --color-dark-copper-soft:        rgba(251, 146, 60, 0.16);
  --color-dark-copper-strong:      #FDBA74;
  --color-dark-copper-on:          #1C1C20;
  /* EU blue's dark #5D78B5 is 4.2:1 on the page; nudged to the nearest shade that clears AA. */
  --color-dark-blue-base:          #6A84BF;
  --color-dark-blue-soft:          rgba(106, 132, 191, 0.16);
  --color-dark-blue-strong:        #8EA3D3;
  --color-dark-blue-on:            #1C1C20;
  --color-dark-red-base:           #E8938A;
  --color-dark-red-soft:           rgba(232, 147, 138, 0.16);
  --color-dark-red-strong:         #F0AEA6;
  --color-dark-red-on:             #1C1C20;
  --color-dark-green-base:         #6FBEA4;
  --color-dark-green-soft:         rgba(111, 190, 164, 0.16);
  --color-dark-green-strong:       #8DD3BC;
  --color-dark-green-on:           #1C1C20;
  --color-dark-purple-base:        #B79BE0;
  --color-dark-purple-soft:        rgba(183, 155, 224, 0.16);
  --color-dark-purple-strong:      #CBB4EC;
  --color-dark-purple-on:          #1C1C20;
  --color-dark-turquoise-base:     #5CB8C4;
  --color-dark-turquoise-soft:     rgba(92, 184, 196, 0.16);
  --color-dark-turquoise-strong:   #82CBD4;
  --color-dark-turquoise-on:       #1C1C20;
  --color-dark-amber-base:         #D9A85C;
  --color-dark-amber-soft:         rgba(217, 168, 92, 0.16);
  --color-dark-amber-strong:       #E5BC7B;
  --color-dark-amber-on:           #1C1C20;
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Dark mode — system preference, only when user hasn't forced light.    │
   │ Same raw axis tokens as the explicit branch above.                    │
   └────────────────────────────────────────────────────────────────────────┘ */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --color-dark-surface-base:        #141414;
    --color-dark-surface-card:        #1C1C20;

    --color-dark-text-base:           #F0F0EE;
    --color-dark-text-secondary:      #C4C4C6;
    --color-dark-text-quiet:          #888890;

    --color-dark-border-quiet:        #282828;

    --color-dark-accent-eu-blue:      #5D78B5;
    --color-dark-accent-eu-gold:      #D9B65C;
    --color-dark-accent-eu-gold-foil: #E0C77A;
    --color-dark-accent-copper:       #FB923C;
    --color-dark-accent-copper-ink:   #FB923C;
    --color-dark-accent-primary:      var(--color-dark-accent-copper);

    --color-dark-secondary-platine:   #5A5A62;
    --color-dark-secondary-steel:     #484850;

    --color-dark-primary-brand-bg:    #F0F0EE;
    --color-dark-primary-brand-fg:    #1C1C20;

    --color-dark-surface-grid-dot:    rgba(240, 240, 238, 0.05);

    /* — functional working palette — dark (system-preference branch) — */
    --color-dark-copper-base:        #FB923C;
    --color-dark-copper-soft:        rgba(251, 146, 60, 0.16);
    --color-dark-copper-strong:      #FDBA74;
    --color-dark-copper-on:          #1C1C20;
    /* EU blue's dark #5D78B5 is 4.2:1 on the page; nudged to the nearest shade that clears AA. */
    --color-dark-blue-base:          #6A84BF;
    --color-dark-blue-soft:          rgba(106, 132, 191, 0.16);
    --color-dark-blue-strong:        #8EA3D3;
    --color-dark-blue-on:            #1C1C20;
    --color-dark-red-base:           #E8938A;
    --color-dark-red-soft:           rgba(232, 147, 138, 0.16);
    --color-dark-red-strong:         #F0AEA6;
    --color-dark-red-on:             #1C1C20;
    --color-dark-green-base:         #6FBEA4;
    --color-dark-green-soft:         rgba(111, 190, 164, 0.16);
    --color-dark-green-strong:       #8DD3BC;
    --color-dark-green-on:           #1C1C20;
    --color-dark-purple-base:        #B79BE0;
    --color-dark-purple-soft:        rgba(183, 155, 224, 0.16);
    --color-dark-purple-strong:      #CBB4EC;
    --color-dark-purple-on:          #1C1C20;
    --color-dark-turquoise-base:     #5CB8C4;
    --color-dark-turquoise-soft:     rgba(92, 184, 196, 0.16);
    --color-dark-turquoise-strong:   #82CBD4;
    --color-dark-turquoise-on:       #1C1C20;
    --color-dark-amber-base:         #D9A85C;
    --color-dark-amber-soft:         rgba(217, 168, 92, 0.16);
    --color-dark-amber-strong:       #E5BC7B;
    --color-dark-amber-on:           #1C1C20;
  }
}

/* ──────────────────────────────────────────────────────────────────────────
   space-tokens.css — spacing scale. Mode axis irrelevant for spacing;
   single layer suffices.

   Raw axis layer:    --space-base-{1..7}  (canonical scale)

   The semantic alias layer (--space-{1..7}) now lives per-channel — brand and
   app each declare --space-N as var(--space-base-N) over this raw axis
   (Story 2.2). Foundation ships the raw scale only.

   Source-of-truth values: manifesto-preview.html lines 55–61.
   ────────────────────────────────────────────────────────────────────────── */

:root {
  /* — raw axis layer — */
  --space-base-1: 0.5rem;
  --space-base-2: 1rem;
  --space-base-3: 1.5rem;
  --space-base-4: 2rem;
  --space-base-5: 3rem;
  --space-base-6: 4.5rem;
  --space-base-7: 6.5rem;
}

/* ──────────────────────────────────────────────────────────────────────────
   type-tokens.css — three-axis typography system, two-layer per Story 1.3 D1.

   Raw axis layer:  --type-{mode}-text-{surface}, --type-size-*, --type-weight-*,
                    --type-line-height-*. Mode axis here mostly tweaks weight/
                    tracking to compensate for substrate contrast; full mode
                    drift lands when actual surfaces consume it.
   Core font aliases: the sans + mono spine (--font-sans, --font-mono) — shared
                     by every channel, so they stay in Core. The editorial
                     serif (--font-serif), reading measure (--measure), and the
                     editorial-serif @font-face faces moved to @myrmid/brand
                     (Story 2.2).

   Per-locale micro-adjustments (German tracking, French line-height) are
   placeholder-stubbed; Story 2.5 (locale switcher) wires lang-attribute
   selectors. NO live html[lang="…"] selectors at this story.

   Source-of-truth values: manifesto-preview.html lines 50–52, 282–303.
   ────────────────────────────────────────────────────────────────────────── */

:root {
  /* — raw axis layer: text aspect (mode dimension) — */
  --type-light-text-base:       400;   /* regular weight, light substrate */
  --type-dark-text-base:        400;   /* same — anthracite ink unchanged */

  /* — raw axis layer: typographic scale — */
  --type-size-h1-base:          clamp(2rem, 6vw, 3.25rem);
  --type-size-body-base:        1.0625rem;        /* 17px mobile */
  --type-size-body-comfortable: 1.125rem;         /* ≥768px */
  --type-size-eyebrow-base:     0.75rem;
  --type-size-meta-base:        0.8125rem;
  --type-size-small-base:       0.875rem;

  --type-weight-regular:        400;
  --type-weight-medium:         500;
  --type-weight-semibold:       600;

  --type-line-height-tight:     1.12;
  --type-line-height-base:      1.65;
  --type-line-height-loose:     1.7;

  /* — Core font aliases: the sans + mono spine (system-font fallback chain
       backs the @font-face declarations below). The editorial serif
       (--font-serif) lives in @myrmid/brand. — */
  --font-sans:  "Geist Sans", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono:  "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
}

/* ──────────────────────────────────────────────────────────────────────────
   Self-hosted WOFF2 declarations — Story 1.4 D2 path (a). UX-DR8.

   • Geist Sans (regular, medium, semibold) — SIL OFL 1.1, Vercel/basement.studio;
     procured from vercel/geist-font 2026-06-07 (Story 2.1).
   • Geist Mono (regular)                 — SIL OFL 1.1, Vercel/basement.studio.

   The editorial serif and its three @font-face blocks moved to @myrmid/brand
   (Story 2.2) — Core ships only the sans + mono spine.

   Operational notes:
   • font-display: swap — system-fallback renders immediately, WOFF2 swaps in
     when ready. Avoids invisible-text FOUT.
   • License trail: public/fonts/LICENSE.md (OFL bundling clause for the Geist
     families).
   • <link rel="preload" as="font" type="font/woff2" crossorigin> tags for
     each face are emitted by templates/layouts/base-layout.eta.
   ────────────────────────────────────────────────────────────────────────── */
@font-face {
  font-family: "Geist Sans";
  src: url("/fonts/geist-sans-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist Sans";
  src: url("/fonts/geist-sans-medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist Sans";
  src: url("/fonts/geist-sans-semibold.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("/fonts/geist-mono-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Per-locale micro-adjustments — Story 2.5.                             │
   ├────────────────────────────────────────────────────────────────────────┤
   │ German compound words pack tightly; a -0.01em tracking nudge keeps    │
   │ heading rhythm honest without compressing body legibility (AC: "DE    │
   │ type tokens use slightly tighter tracking to accommodate compound     │
   │ words"). Tracking applies via --type-tracking-tight which heading-    │
   │ level CSS consumers reference; body type stays at the default 0.     │
   │                                                                        │
   │ French diacritics ride high; bumping --type-line-height-loose from   │
   │ 1.7 → 1.75 prevents accent ascenders colliding with the descender    │
   │ row above (AC: "FR type tokens use slightly looser line-height to    │
   │ accommodate diacritics"). Body and prose blocks consume --type-      │
   │ line-height-loose at ≥768px (layout-base.css) so the lift lands      │
   │ where diacritic stacking is densest.                                  │
   └────────────────────────────────────────────────────────────────────────┘ */
:root {
  /* Default tracking (used by headings; consumers opt in). */
  --type-tracking-tight: 0;
}
html[lang="de"] {
  --type-tracking-tight: -0.01em;
}
html[lang="fr"] {
  --type-line-height-loose: 1.75;
}

/* ──────────────────────────────────────────────────────────────────────────
   motion-tokens.css — motion durations + easings, with the AC4
   reduced-motion gate at the token layer (UX line 423).

   Raw axis layer:    --motion-duration-* + --motion-easing-*
   Semantic aliases:  --motion-quick, --motion-base, --easing
                       (preserves manifesto-preview's component CSS unchanged)

   Source-of-truth values: manifesto-preview.html lines 63–66, 146–152.

   Reduced-motion gate operates in two layers:
     1. Token-layer override (spec-mandated) — re-resolves duration tokens to
        0ms when prefers-reduced-motion: reduce. Aliases inherit via var().
     2. Global escape hatch — defends against any third-party / not-yet-
        tokenized CSS by zeroing every transition-duration / animation-duration.
   ────────────────────────────────────────────────────────────────────────── */

:root {
  /* — raw axis layer — */
  --motion-duration-quick: 120ms;
  --motion-duration-base:  200ms;
  /* Story 2.8 — scroll-cue ant pulse uses --motion-duration-slow as the
     full breathing cycle (opacity 0.55↔0.85, scale 1.0↔1.06). The exit
     easing decelerates the fade-out when the visitor scrolls past the
     hero — easeIn shape so the ant disappears decisively rather than
     lingering. */
  --motion-duration-slow:  600ms;
  --motion-easing-base:    cubic-bezier(0.2, 0, 0, 1);
  --motion-easing-exit:    cubic-bezier(0.4, 0, 1, 1);

  /* — semantic alias layer — */
  --motion-quick: var(--motion-duration-quick);
  --motion-base:  var(--motion-duration-base);
  --motion-slow:  var(--motion-duration-slow);
  --easing:       var(--motion-easing-base);
  --easing-exit:  var(--motion-easing-exit);
}

/* AC4 — reduced-motion at the token layer.
   Aliases above inherit via var(), so redeclaring the raw axis tokens here
   collapses every alias-consumer's transition to 0ms automatically. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --motion-duration-quick: 0ms;
    --motion-duration-base:  0ms;
    --motion-duration-slow:  0ms;
  }
}

/* Global escape hatch — defends against any rule that hardcodes durations
   (third-party CSS, or component CSS that hasn't yet been migrated to tokens).
   Belt-and-braces with the token-layer override above; both intentional.

   Zeroing duration alone is not enough: a 5s-delayed animation still fires
   visibly, and an `iteration-count: infinite` animation with duration 0 can
   wedge the rendering thread on some engines. Override delays + clamp
   iteration to 1 alongside duration. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0ms !important;
    transition-delay:    0ms !important;
    animation-duration:  0ms !important;
    animation-delay:     0ms !important;
    animation-iteration-count: 1 !important;
  }
}

}
@layer myrmid.tokens {
/* ──────────────────────────────────────────────────────────────────────────
   color-tokens.css (brand channel) — editorial semantic colour aliases.

   These are the semantic vocabulary consumed by brand component CSS
   (--surface-page, --ink-primary, --accent-eu-blue, …). Each alias resolves to
   a raw axis token owned by @myrmid/foundation (--color-{mode}-{aspect}-{surface}).
   Brand's build feeds the emitter [foundation raw tokens, THEN these aliases],
   so the var() chains resolve per-mode against the inlined foundation raw.

   Values ARE the current (editorial / manifesto) palette — brand is the public
   landing/blog/team-store identity. App (Story 2.3) declares the SAME alias
   NAMES with dense/functional values; brand⇄app key parity is what the Epic-3
   contract test (3.3) enforces (architecture AR-9).

   Mode resolution mirrors foundation's three-branch structure:
     1. :root[data-theme="dark"]                       (explicit toggle)
     2. @media (prefers-color-scheme: dark) :root:not([data-theme="light"])
     3. :root                                            (light, canonical)
   ────────────────────────────────────────────────────────────────────────── */

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Light mode — canonical fallback. Aliases over light raw axis.          │
   └────────────────────────────────────────────────────────────────────────┘ */
:root {
  /* ── Semantic aliases — manifesto-preview vocabulary ── */
  --surface-page:        var(--color-light-surface-base);
  --surface-card:        var(--color-light-surface-card);
  --rule-quiet:          var(--color-light-border-quiet);

  --ink-primary:         var(--color-light-text-base);
  --ink-secondary:       var(--color-light-text-secondary);
  --ink-quiet:           var(--color-light-text-quiet);

  --accent-eu-blue:      var(--color-light-accent-eu-blue);
  --accent-eu-gold:      var(--color-light-accent-eu-gold);
  --accent-eu-gold-foil: var(--color-light-accent-eu-gold-foil);
  --accent-primary:      var(--color-light-accent-primary);
  --accent-glow:         var(--color-light-accent-copper);
  --accent-ink:          var(--color-light-accent-copper-ink);
  --accent-gold:         var(--accent-eu-gold);

  --metal-platine:       var(--color-light-secondary-platine);
  --metal-steel:         var(--color-light-secondary-steel);

  --brand-bg:            var(--color-light-primary-brand-bg);
  --brand-fg:            var(--color-light-primary-brand-fg);

  --grid-dot:            var(--color-light-surface-grid-dot);
  --trail-stroke:        var(--rule-quiet);
  --waypoint-fill:       var(--accent-primary);

  /* Text on --accent-primary (copper) fills: white clears 5.2:1. */
  --ink-on-accent:       #FFFFFF;

  /* ── Functional working palette — additive chromatic roles (light) ──
     Same alias NAMES as @myrmid/app (parity); same hues (one EU palette). ── */
  --accent-copper:           var(--color-light-copper-base);
  --accent-copper-soft:      var(--color-light-copper-soft);
  --accent-copper-strong:    var(--color-light-copper-strong);
  --accent-copper-on:        var(--color-light-copper-on);
  --accent-blue:             var(--color-light-blue-base);
  --accent-blue-soft:        var(--color-light-blue-soft);
  --accent-blue-strong:      var(--color-light-blue-strong);
  --accent-blue-on:          var(--color-light-blue-on);
  --accent-red:              var(--color-light-red-base);
  --accent-red-soft:         var(--color-light-red-soft);
  --accent-red-strong:       var(--color-light-red-strong);
  --accent-red-on:           var(--color-light-red-on);
  --accent-green:            var(--color-light-green-base);
  --accent-green-soft:       var(--color-light-green-soft);
  --accent-green-strong:     var(--color-light-green-strong);
  --accent-green-on:         var(--color-light-green-on);
  --accent-purple:           var(--color-light-purple-base);
  --accent-purple-soft:      var(--color-light-purple-soft);
  --accent-purple-strong:    var(--color-light-purple-strong);
  --accent-purple-on:        var(--color-light-purple-on);
  --accent-turquoise:        var(--color-light-turquoise-base);
  --accent-turquoise-soft:   var(--color-light-turquoise-soft);
  --accent-turquoise-strong: var(--color-light-turquoise-strong);
  --accent-turquoise-on:     var(--color-light-turquoise-on);
  --accent-amber:            var(--color-light-amber-base);
  --accent-amber-soft:       var(--color-light-amber-soft);
  --accent-amber-strong:     var(--color-light-amber-strong);
  --accent-amber-on:         var(--color-light-amber-on);
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Dark mode — explicit user toggle (data-theme="dark") wins outright.    │
   │ Aliases re-bind to dark-side raw tokens.                               │
   └────────────────────────────────────────────────────────────────────────┘ */
:root[data-theme="dark"] {
  --surface-page:        var(--color-dark-surface-base);
  --surface-card:        var(--color-dark-surface-card);
  --rule-quiet:          var(--color-dark-border-quiet);

  --ink-primary:         var(--color-dark-text-base);
  --ink-secondary:       var(--color-dark-text-secondary);
  --ink-quiet:           var(--color-dark-text-quiet);

  --accent-eu-blue:      var(--color-dark-accent-eu-blue);
  --accent-eu-gold:      var(--color-dark-accent-eu-gold);
  --accent-eu-gold-foil: var(--color-dark-accent-eu-gold-foil);
  --accent-primary:      var(--color-dark-accent-primary);
  --accent-glow:         var(--color-dark-accent-copper);
  --accent-ink:          var(--color-dark-accent-copper-ink);
  --accent-gold:         var(--accent-eu-gold);

  --metal-platine:       var(--color-dark-secondary-platine);
  --metal-steel:         var(--color-dark-secondary-steel);

  --brand-bg:            var(--color-dark-primary-brand-bg);
  --brand-fg:            var(--color-dark-primary-brand-fg);

  --grid-dot:            var(--color-dark-surface-grid-dot);
  /* --trail-stroke and --waypoint-fill chain via their own aliases — no redeclare. */

  /* Anthracite on dark copper clears 7.5:1. */
  --ink-on-accent:       #1C1C20;

  /* ── Functional working palette — dark bindings ── */
  --accent-copper:           var(--color-dark-copper-base);
  --accent-copper-soft:      var(--color-dark-copper-soft);
  --accent-copper-strong:    var(--color-dark-copper-strong);
  --accent-copper-on:        var(--color-dark-copper-on);
  --accent-blue:             var(--color-dark-blue-base);
  --accent-blue-soft:        var(--color-dark-blue-soft);
  --accent-blue-strong:      var(--color-dark-blue-strong);
  --accent-blue-on:          var(--color-dark-blue-on);
  --accent-red:              var(--color-dark-red-base);
  --accent-red-soft:         var(--color-dark-red-soft);
  --accent-red-strong:       var(--color-dark-red-strong);
  --accent-red-on:           var(--color-dark-red-on);
  --accent-green:            var(--color-dark-green-base);
  --accent-green-soft:       var(--color-dark-green-soft);
  --accent-green-strong:     var(--color-dark-green-strong);
  --accent-green-on:         var(--color-dark-green-on);
  --accent-purple:           var(--color-dark-purple-base);
  --accent-purple-soft:      var(--color-dark-purple-soft);
  --accent-purple-strong:    var(--color-dark-purple-strong);
  --accent-purple-on:        var(--color-dark-purple-on);
  --accent-turquoise:        var(--color-dark-turquoise-base);
  --accent-turquoise-soft:   var(--color-dark-turquoise-soft);
  --accent-turquoise-strong: var(--color-dark-turquoise-strong);
  --accent-turquoise-on:     var(--color-dark-turquoise-on);
  --accent-amber:            var(--color-dark-amber-base);
  --accent-amber-soft:       var(--color-dark-amber-soft);
  --accent-amber-strong:     var(--color-dark-amber-strong);
  --accent-amber-on:         var(--color-dark-amber-on);
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Dark mode — system preference, only when user hasn't forced light.    │
   │ Same aliases as the explicit branch above.                            │
   └────────────────────────────────────────────────────────────────────────┘ */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --surface-page:        var(--color-dark-surface-base);
    --surface-card:        var(--color-dark-surface-card);
    --rule-quiet:          var(--color-dark-border-quiet);

    --ink-primary:         var(--color-dark-text-base);
    --ink-secondary:       var(--color-dark-text-secondary);
    --ink-quiet:           var(--color-dark-text-quiet);

    --accent-eu-blue:      var(--color-dark-accent-eu-blue);
    --accent-eu-gold:      var(--color-dark-accent-eu-gold);
    --accent-eu-gold-foil: var(--color-dark-accent-eu-gold-foil);
    --accent-primary:      var(--color-dark-accent-primary);
    --accent-glow:         var(--color-dark-accent-copper);
    --accent-ink:          var(--color-dark-accent-copper-ink);
    --accent-gold:         var(--accent-eu-gold);

    --metal-platine:       var(--color-dark-secondary-platine);
    --metal-steel:         var(--color-dark-secondary-steel);

    --brand-bg:            var(--color-dark-primary-brand-bg);
    --brand-fg:            var(--color-dark-primary-brand-fg);

    --grid-dot:            var(--color-dark-surface-grid-dot);

    --ink-on-accent:       #1C1C20;

    /* ── Functional working palette — dark bindings (system-preference) ── */
    --accent-copper:           var(--color-dark-copper-base);
    --accent-copper-soft:      var(--color-dark-copper-soft);
    --accent-copper-strong:    var(--color-dark-copper-strong);
    --accent-copper-on:        var(--color-dark-copper-on);
    --accent-blue:             var(--color-dark-blue-base);
    --accent-blue-soft:        var(--color-dark-blue-soft);
    --accent-blue-strong:      var(--color-dark-blue-strong);
    --accent-blue-on:          var(--color-dark-blue-on);
    --accent-red:              var(--color-dark-red-base);
    --accent-red-soft:         var(--color-dark-red-soft);
    --accent-red-strong:       var(--color-dark-red-strong);
    --accent-red-on:           var(--color-dark-red-on);
    --accent-green:            var(--color-dark-green-base);
    --accent-green-soft:       var(--color-dark-green-soft);
    --accent-green-strong:     var(--color-dark-green-strong);
    --accent-green-on:         var(--color-dark-green-on);
    --accent-purple:           var(--color-dark-purple-base);
    --accent-purple-soft:      var(--color-dark-purple-soft);
    --accent-purple-strong:    var(--color-dark-purple-strong);
    --accent-purple-on:        var(--color-dark-purple-on);
    --accent-turquoise:        var(--color-dark-turquoise-base);
    --accent-turquoise-soft:   var(--color-dark-turquoise-soft);
    --accent-turquoise-strong: var(--color-dark-turquoise-strong);
    --accent-turquoise-on:     var(--color-dark-turquoise-on);
    --accent-amber:            var(--color-dark-amber-base);
    --accent-amber-soft:       var(--color-dark-amber-soft);
    --accent-amber-strong:     var(--color-dark-amber-strong);
    --accent-amber-on:         var(--color-dark-amber-on);
  }
}

/* ──────────────────────────────────────────────────────────────────────────
   space-tokens.css (brand channel) — spacing semantic aliases.

   Semantic alias layer (--space-{1..7}) over @myrmid/foundation's raw axis
   (--space-base-{1..7}). Mode axis is irrelevant for spacing; single layer.
   App (Story 2.3) declares the same --space-N names; values may diverge.

   Source-of-truth values: manifesto-preview.html lines 55–61.
   ────────────────────────────────────────────────────────────────────────── */

:root {
  /* — semantic alias layer (manifesto-preview vocabulary) — */
  --space-1: var(--space-base-1);
  --space-2: var(--space-base-2);
  --space-3: var(--space-base-3);
  --space-4: var(--space-base-4);
  --space-5: var(--space-base-5);
  --space-6: var(--space-base-6);
  --space-7: var(--space-base-7);
}

/* ──────────────────────────────────────────────────────────────────────────
   type-tokens.css (brand channel) — editorial serif + reading measure + the
   Charter self-hosted faces.

   Brand owns the editorial serif spine. The Core sans + mono aliases
   (--font-sans / --font-mono) and the raw --type-* scale stay in
   @myrmid/foundation; this file adds only the brand-specific type vocabulary.

   • --font-serif — Charter-led stack; system serif fallback retained so text
     renders immediately under font-display: swap.
   • --measure    — editorial reading measure.

   Source-of-truth values: manifesto-preview.html lines 50–52, 282–303.
   ────────────────────────────────────────────────────────────────────────── */

:root {
  /* — editorial serif stack (system-font fallback chain backs the @font-face
       declarations below). — */
  --font-serif: Charter, "Iowan Old Style", "Source Serif Pro", "Source Serif", Cambria, Georgia, "Times New Roman", Times, serif;

  /* — reading measure — */
  --measure: 60rem;
}

/* ──────────────────────────────────────────────────────────────────────────
   Self-hosted Charter WOFF2 declarations — UX-DR8 (EU-resident, no CDN).

   • Charter (regular, italic, bold) — Bitstream Charter license, free
     redistribution; procured from practicaltypography.com 2026-05-02. Relocated
     from @myrmid/foundation into @myrmid/brand in Story 2.2.

   Operational notes:
   • font-display: swap — system-fallback renders immediately, WOFF2 swaps in
     when ready. Avoids invisible-text FOUT; CLS impact is minimal because
     Charter and Iowan Old Style have similar metrics.
   • src is root-absolute (/fonts/charter-*.woff2) — resolves on the consumer's
     own host, which copies the WOFF2 into public/fonts/. No https://, no CDN.
   • License trail: src/assets/fonts/LICENSE.md (Bitstream notice preservation
     required by the Charter license).
   ────────────────────────────────────────────────────────────────────────── */
@font-face {
  font-family: "Charter";
  src: url("/fonts/charter-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Charter";
  src: url("/fonts/charter-italic.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Charter";
  src: url("/fonts/charter-bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

}
/* The landing's accent comes from the brand tokens: copper is --accent-primary, the glow and
   the copper text (--accent-glow, --accent-ink). Buttons are ink at rest, copper under the hand. */
:root {
  --cta-face: var(--ink-primary);
  --cta-label: var(--surface-page);
  /* Under the hand: a CTA's face, or any other button's frame and text, turn copper in a copper glow. */
  --hover-glow: 0 0 14px 1px color-mix(in srgb, var(--accent-glow) 45%, transparent);
} /* button faces: ink at rest, copper under the hand */

/* Brand foundation primitives — body substrate + skip-link + stigmergy texture system */
@layer myrmid.foundation {
/* ──────────────────────────────────────────────────────────────────────────
   layout-base.css — substrate, body type, skip-link.

   Foundation layout primitives consumed by every Myrmid surface:
     • Box-sizing reset.
     • <html> text-size-adjust + scroll-behavior (with reduced-motion).
     • <body> substrate (radial dot-grid + brand fonts + AA-clean type colour).
     • ::selection — inverted ink/surface for high contrast in both themes.
     • .skip-link — WCAG 2.4.1 bypass-blocks affordance.

   Lifted from landing-page/tokens/layout-base.css on 2026-05-29. Landing-page-
   specific chrome (header.site, .site-inner, .brand*, .nav, hero-reveal state
   machine, #companion-root rules, .consent-* rules) was deliberately NOT migrated;
   those belong in the consumer that ships those markup patterns.

   Body substrate per Brand Visual System §Stigmergy Texture System Primitive 1:
     • radial-gradient(circle, var(--grid-dot) 0.7px, transparent 1.4px)
     • size: var(--grid-cadence) (28px default)
     • Mode-aware via the cascade — --grid-dot redeclares per data-theme.
     • AA constraint: dot opacity is calibrated for ≥4.5:1 body-text contrast.
   ────────────────────────────────────────────────────────────────────────── */

:root {
  --grid-cadence: 28px;
}

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background-color: var(--surface-page);
  background-image: radial-gradient(circle, var(--grid-dot) 0.7px, transparent 1.4px);
  background-size: var(--grid-cadence) var(--grid-cadence);
  background-position: 0 0;
  color: var(--ink-primary);
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-base);
  font-feature-settings: "kern" 1, "liga" 1;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  transition: background-color var(--motion-base) var(--easing),
              color            var(--motion-base) var(--easing);
}

@media (min-width: 768px) {
  body {
    font-size:   var(--type-size-body-comfortable);
    line-height: var(--type-line-height-loose);
  }
}

::selection { background-color: var(--ink-primary); color: var(--surface-page); }

.skip-link {
  position: absolute;
  top: -100px;
  left: 1rem;
  padding: var(--space-1) 0.75rem;
  background: var(--surface-card);
  color: var(--ink-primary);
  font-family: var(--font-sans);
  font-size: var(--type-size-small-base);
  text-decoration: none;
  border: 1px solid var(--rule-quiet);
  border-radius: 4px;
  z-index: 100;
}
.skip-link:focus {
  top: 0.75rem;
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}
}
@layer myrmid.foundation {
/* ──────────────────────────────────────────────────────────────────────────
   stigmergy.css — reusable Stigmergy Texture System primitives.

   Three reusable primitives consumed across long-form prose and section
   surfaces:
     • .stigmergy-rail — Primitive 2 (waypoint) + Primitive 3 (pheromone
       trail). Every consumer renders
         <svg class="stigmergy-rail__waypoint">
           <use href="#stigmergy-waypoint"/>
         </svg>
       as the FIRST CHILD of the .stigmergy-rail block. A rail without a
       waypoint reads as an orphan hairline, not a stigmergy waypoint —
       that is the wrong gesture (browser-tested 2026-05-13 by Tom:
       "the rail must be the rail plus an ant like in the manifesto").
     • .stigmergy-divider-strong — Primitive 4 wrapper. 240×38 viewBox,
       full ant-body row (5 columns × 3 dots, V-rhythm centre dip).
       Section-boundary marker.
     • .stigmergy-divider-soft — Primitive 5 wrapper. 200×40 render box
       (1.67× the 120×24 viewBox) for sub-pixel legibility at scroll-
       reading distance — thought-pause marker between paragraph blocks.

   Token discipline:
     • --trail-stroke → --rule-quiet by cascade (color-tokens.css).
     • --waypoint-fill → --accent-primary (EU blue light, EU gold dark).
     • --accent-primary on all divider dots — the charge; the metallic finish
       is retired (vision/visual-system.md, 2026-09-02).

   Accessibility: every consuming <svg> MUST carry aria-hidden="true" +
   focusable="false". The decorative role is encoded at the consumer,
   not the symbol, so the same #stigmergy-* symbols stay reusable.

   Lifted from landing-page/tokens/stigmergy.css on 2026-05-29. Landing-page-
   specific page-foot navigation (.crosslinks*, .crosslinks-table*,
   .crosslinks-cta-button) was deliberately NOT migrated; that pattern is a
   landing-page surface concern, not a foundation primitive.
   ────────────────────────────────────────────────────────────────────────── */

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ .stigmergy-rail — side-line + required waypoint at the head. Apply to  │
   │ <section> or <article> elements that should read as waypoint stops on  │
   │ a trail.                                                                │
   └────────────────────────────────────────────────────────────────────────┘ */
/* Symmetric: the left padding is the waypoint column's room, and a bare
   padding-left pushes the block's whole text mass half that off page centre. */
.stigmergy-rail {
  position: relative;
  padding-inline: var(--space-3);
}

.stigmergy-rail::before {
  content: "";
  position: absolute;
  top: var(--space-3);
  bottom: var(--space-3);
  left: 3px;
  width: 1px;
  background: linear-gradient(to bottom, var(--trail-stroke), transparent 92%);
  pointer-events: none;
}

.stigmergy-rail__waypoint {
  position: absolute;
  top: 0;
  left: 0;
  width: 7px;
  height: 22px;
  opacity: 0.85;
  /* A body carries a charge: a hair of glow, never a bare dot. */
  filter: drop-shadow(0 0 2px color-mix(in srgb, var(--waypoint-fill) 26%, transparent));
  pointer-events: none;
}

/* ── Neon trail ────────────────────────────────────────────────────────────
   Dividers and ornaments are among the few surfaces vision's structural-only
   discipline lets carry chromatic treatment, so the glow lives here and never
   on type. A tube reads as a hot core in a wide, saturated halo: three shadows
   out to 18px, built from --accent-primary so it follows light/dark. */
.stigmergy-divider-strong,
.stigmergy-divider-soft {
  filter:
    drop-shadow(0 0 2px var(--accent-primary))
    drop-shadow(0 0 7px var(--accent-primary))
    drop-shadow(0 0 18px color-mix(in srgb, var(--accent-primary) 70%, transparent));
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ .stigmergy-divider-strong — Primitive 4 wrapper. Centred, sized to     │
   │ editorial column. Strong section-boundary marker.                      │
   └────────────────────────────────────────────────────────────────────────┘ */
.stigmergy-divider-strong {
  display: block;
  width: 240px;
  height: 38px;
  margin: var(--space-5) auto;
}
@media (min-width: 768px) {
  .stigmergy-divider-strong {
    margin: var(--space-6) auto;
  }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ .stigmergy-divider-soft — Primitive 5 wrapper. Smaller envelope than   │
   │ strong, less vertical breathing. Thought-pause marker between          │
   │ paragraph blocks. Render box (200×40) is ~1.67× the 120×24 viewBox so  │
   │ the dots + rule actually read at scroll-reading distance.              │
   └────────────────────────────────────────────────────────────────────────┘ */
.stigmergy-divider-soft {
  display: block;
  width: 200px;
  height: 40px;
  margin: var(--space-3) auto;
}
@media (min-width: 768px) {
  .stigmergy-divider-soft {
    margin: var(--space-4) auto;
  }
}
}

/* Landing-page supplements to the foundation */
/* ──────────────────────────────────────────────────────────────────────────
   site-chrome.css — landing-page header chrome + hero-reveal state machine.

   Renamed 2026-05-29 from layout-base.css. The substrate + body type +
   ::selection + .skip-link primitives that used to live alongside this
   chrome now ship from @myrmid/design-system/foundation/layout-base.css.
   This file is the landing-page-specific supplement: header markup,
   the hero-reveal coordination with public/js/hero-reveal.js, and the
   #companion-root substrate suppression.

   Sticky positioning per UX spec §Section Nav line 2123 ("Locale-aware;
   sticky"). z-index pins above page content but stays below the Vompanion
   widget root (which manages its own stacking inside #companion-root).
   `contain: layout` keeps the sticky element from triggering layout
   recalculation in the surrounding content (CLS = 0 invariant per AC).
   ────────────────────────────────────────────────────────────────────────── */

header.site {
  position: sticky;
  top: 0;
  z-index: 30;
  /* Thin bar: the chrome should not compete with the frame under it. */
  padding: var(--space-1) 0;
  /* Frosted, on a pseudo-element: a backdrop-filter on the header itself would
     make it the backdrop root and its dropdowns could not blur the page. */
  background: transparent;
  /* A neon tube under the bar (MF-448), in the page's glow: the hairline is
     the filament, the two shadows its bleed. */
  border-bottom: 1px solid color-mix(in srgb, var(--accent-glow) 60%, var(--rule-quiet));
  box-shadow:
    0 1px 6px -1px color-mix(in srgb, var(--accent-glow) 45%, transparent),
    0 3px 18px -6px color-mix(in srgb, var(--accent-glow) 32%, transparent);
  contain: layout;
  transition:
    opacity var(--motion-duration-base) var(--motion-easing-base),
    transform var(--motion-duration-base) var(--motion-easing-base);
}

header.site::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--glass-frost-fill);
  backdrop-filter: var(--glass-frost-lens);
  -webkit-backdrop-filter: var(--glass-frost-lens);
  pointer-events: none;
}
@media (prefers-reduced-transparency: reduce) {
  header.site::before {
    background: var(--surface-page);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  header.site::before {
    background: var(--surface-page);
  }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Hero-reveal state machine — coordinated with                           │
   │ public/js/hero-reveal.js + templates/layouts/base-layout.eta inline    │
   │ bootstrap. While the visitor is reading the hero (data-hero-mode=     │
   │ "initial"), header.site and the floating Vompanion button are hidden so   │
   │ the hero owns the viewport. On first scroll input, JS flips the body  │
   │ to data-hero-mode="scrolled" — chrome fades back in and stays.        │
   │                                                                        │
   │ Progressive enhancement: if JS is disabled the body attribute is set  │
   │ by the inline <head> bootstrap (which is synchronous and reliable);   │
   │ if even that fails (no-script env), the absence of any                │
   │ data-hero-mode value leaves chrome at its default — visible.          │
   └────────────────────────────────────────────────────────────────────────┘ */
body[data-hero-mode="initial"] header.site {
  opacity: 0;
  transform: translateY(-100%);
  pointer-events: none;
}
/* No main pull-up trick. Previously body[data-hero-mode="initial"] main
   had `margin-top: -header-height` to put the hero's geometric centre
   at the true viewport centre while the header was hidden. That
   produced a 38-px vertical jump between cinematic and non-cinematic
   pages (the centre of full 100vh vs. the centre of the area below a
   visible header). Hero is now sized as `calc(100vh - header-height)`
   in hero.css, which centres its content in the area below the
   reserved-header space on EVERY state — no jump when navigating, and
   the bottom-positioned scroll-cue stays in the viewport. The header
   still fades + slides in on first reveal; the hero just doesn't
   shift to coordinate with it. */
body[data-hero-mode="initial"] .companion-cta,
body[data-hero-mode="initial"] #companion-root,
body[data-hero-mode="initial"] .consent-dialog,
body[data-hero-mode="initial"] .consent-trigger {
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--motion-duration-base) var(--motion-easing-base);
}
body[data-hero-mode="scrolled"] .companion-cta,
body[data-hero-mode="scrolled"] #companion-root,
body[data-hero-mode="scrolled"] .consent-dialog,
body[data-hero-mode="scrolled"] .consent-trigger {
  opacity: 1;
}
/* Consent + persistent trigger MUST surface eventually even if the
   visitor never scrolls — privacy choice must be reachable. hero-
   reveal.js sets data-consent-revealed="true" 6s after load; this rule
   restores opacity + pointer-events on the consent affordances
   regardless of hero-mode. */
body[data-consent-revealed="true"] .consent-dialog,
body[data-consent-revealed="true"] .consent-trigger {
  opacity: 1;
  pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) {
  header.site,
  body[data-hero-mode="initial"] .companion-cta,
  body[data-hero-mode="initial"] #companion-root {
    transition: none;
  }
  body[data-hero-mode="initial"] header.site {
    /* No slide-in; just visibility step. */
    transform: none;
    visibility: hidden;
  }
  body[data-hero-mode="initial"] .consent-dialog,
  body[data-hero-mode="initial"] .consent-trigger {
    visibility: hidden;
  }
  body[data-consent-revealed="true"] .consent-dialog,
  body[data-consent-revealed="true"] .consent-trigger {
    visibility: visible;
  }
}
.site-inner {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  max-width: var(--page-measure);
  margin: 0 auto;
  padding: 0 var(--space-3);
}
@media (min-width: 768px) {
  .site-inner { padding: 0 var(--space-4); }
}

/* Sticky footer: a page shorter than the screen (the closed-cohort /partners,
   a thin legal page) left the footer floating with dead background under it.
   The body becomes a column at least one screen tall and <main> takes the
   slack, so the footer sits at the bottom of the SCREEN or the bottom of the
   CONTENT, whichever is lower. */
body {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

main#main {
  flex: 1 0 auto;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  text-decoration: none;
  color: var(--ink-primary);
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: var(--type-weight-semibold);
}
.brand-mark { width: 22px; height: 22px; flex: 0 0 22px; }
/* The wordmark is set in Charter (--font-serif), the brand's own text face —
   the lockup reads as the brand rather than as UI chrome. */
.brand-name {
  font-family: var(--font-serif);
  letter-spacing: 0.02em;
  /* Two corrections, because a line box is not the word.
     1. line-height:1 stops the box centring on the LEADING.
     2. The box is still ascender-to-descender, and "Myrmid" only reaches
        below the baseline with its y, so centring the BOX hangs the word low
        against the mark. The nudge puts the word's CAP-HEIGHT box — cap top
        to baseline, which is what the eye reads as the word's block — on the
        mark's axis. 0.245em was measured off the rendered pixels at 4x, not
        derived from font metrics: canvas ascent/descent do not describe how
        the browser lays the inline box out at line-height 1. */
  line-height: 1;
  display: inline-block;
  transform: translateY(-0.245em);
}

.nav { display: flex; align-items: center; gap: var(--space-2); }

/* Vompanion widget surface dot-grid suppression — UX:522.
   The widget is its own owned surface; the stigmergy texture frames her,
   never invades her. Scope to the canonical mount root. */
#companion-root,
#companion-root * {
  background-image: none;
} /* header.site sticky chrome + hero-reveal state machine + .site-inner + .brand* + .nav + #companion-root substrate suppression */
/* ──────────────────────────────────────────────────────────────────────────
   crosslinks.css — landing-page page-foot navigation pattern.

   Renamed 2026-05-29 from stigmergy.css. The reusable Stigmergy Texture
   System primitives (.stigmergy-rail, .stigmergy-divider-strong,
   .stigmergy-divider-soft) that used to share this file now ship from
   @myrmid/design-system/foundation/stigmergy.css. The page-foot
   navigation block stays here because it is a landing-page surface
   concern — every public page has it, no other Myrmid consumer needs
   the same table layout.

   The .crosslinks block consolidates the six per-page crosslink class
   trees into one primitive. Adopted pricing's "talk-to-us" 2-col table
   shape (question on the left, outline-button CTA on the right) so
   every page foot reads the same: an <h2> "Want to know more?" header
   sits above a .crosslinks-table whose rows pair an intro <th> with an
   <a class="crosslinks-cta-button"> CTA. The block sits at page bottom;
   the strong stigmergy divider above is the visual seam, so no
   border-top here — the divider is the page-foot punctuation.
   ────────────────────────────────────────────────────────────────────────── */

.crosslinks {
  width: 100%;
  max-width: var(--page-measure); /* one page measure with every other section */
  margin: 0 auto;
  /* No padding-top: the strong-divider above provides the visual gap via its
     own margin-bottom, and zero top padding aligns the .crosslinks__h2 with
     the .stigmergy-rail__waypoint (which sits at top: 0 of the rail block). */
  padding: 0 var(--space-3) var(--space-7);
}
@media (min-width: 768px) {
  .crosslinks {
    padding-left: var(--space-4);
    padding-right: var(--space-4);
  }
}
.crosslinks__h2 {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-semibold);
  font-size: 1.5rem;
  line-height: var(--type-line-height-tight);
  color: var(--ink-primary);
  margin: 0 0 var(--space-3);
}
.crosslinks-table-wrapper {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.crosslinks-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  color: var(--ink-primary);
}
.crosslinks-table tbody th {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-semibold);
  text-align: left;
  padding: var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--rule-quiet);
  color: var(--ink-primary);
  vertical-align: middle;
}
.crosslinks-table tbody td {
  padding: var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--rule-quiet);
  vertical-align: middle;
  white-space: nowrap;
  text-align: right;
  width: 1%; /* shrink to content; the <th> column takes the slack */
}
.crosslinks-table__row:last-child th,
.crosslinks-table__row:last-child td {
  border-bottom: none;
}
.crosslinks-table__cta-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-2);
}
/* Coloured + semibold text-link CTA. Softened 2026-05-14 from outlined
   button to a text link; coloured/bolded 2026-05-14 so it still reads
   as a destination cue alongside the <th> question. Accent on idle,
   ink-primary on hover (text darkens, underline holds accent). */
.crosslinks-cta-button {
  display: inline-block;
  min-height: 44px;
  padding: 0.5rem 0;
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  font-weight: var(--type-weight-semibold);
  color: var(--accent-primary);
  text-decoration: underline;
  text-decoration-color: var(--accent-primary);
  text-underline-offset: 0.25em;
  transition:
    color var(--motion-quick) var(--easing),
    text-decoration-color var(--motion-quick) var(--easing);
}
.crosslinks-cta-button:hover,
.crosslinks-cta-button:focus-visible {
  color: var(--ink-primary);
  text-decoration-color: var(--ink-primary);
}
.crosslinks-cta-button:focus-visible {
  outline: 2px solid var(--ink-primary);
  outline-offset: 2px;
  border-radius: 2px;
} /* unified page-foot navigation block (two-col table — question + CTA) */

/* Landing-page surface composition (page-by-page consumer rules) */
/* ──────────────────────────────────────────────────────────────────────────
   hero.css — Unified hero block consumer rules.

   Single source of truth for the hero composition shared by every
   "hero surface" — landing, /products + 6 product pages, /solutions +
   9 solution pages, /about, /about/team. Hero markup is generated by
   templates/partials/hero-block.eta with the locked element order:

       logo → descriptor → H1 → sub → italic brand-mark close → CTA

   FULL-BLEED CINEMATIC BY DEFAULT. On any [data-hero] section the hero
   occupies the full viewport and content sits vertically centred. The
   page header (header.site) and floating Vompanion button are hidden until
   the visitor scrolls; see layout-base.css for the reveal rules and
   public/js/hero-reveal.js for the state machine.

   Brand-mark italic close is copper text (--accent-ink), AA in both modes.

   Lives under tokens/** so the four-prefix stylelint regex exemption
   (Story 1.1) covers consumer rules using ad-hoc class names.
   ────────────────────────────────────────────────────────────────────── */

/* Base hero is content-sized. The full-bleed 100vh treatment + scroll-cue
   are SECTION-INDEX ONLY (UX spec §Hero + Use-Case Scroll Pattern): the five
   section indexes (/, /products, /solutions, /about, /pricing) open full-bleed
   to give entry surfaces their gravity, while leaf pages (each product / each
   solution / about-team / careers / partners / workspace / team-store) are
   reading/decision surfaces sized to their content so a 100vh hero does not
   add a wasted scroll step. The split keys off body[data-section-index], which
   base-layout.eta emits for exactly the five indexes (from surface.sectionIndex
   in build-static.ts). */
.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--space-5) 0 calc(var(--space-6) + 24px);
  text-align: center;
  contain: layout;
}

/* Section indexes only — full-bleed below the sticky header. header.site is
   `position: sticky` so it reserves header-height of vertical space whether
   it's opacity:0 (initial state) or visible; subtracting that reservation puts
   the hero's geometric centre at the same viewport Y on every index page (no
   jump when navigating) AND keeps the bottom-positioned scroll-cue inside the
   viewport. --header-height is written on <body> by hero-reveal.js; the 5rem
   fallback ≈ 81px tracks the header's typical computed height so the rule works
   before JS parses too. */
body[data-section-index] .hero {
  min-height: calc(100vh - var(--header-height, 5rem));
  /* Trim the bottom padding (the ≥768px base sets space-7 + 24px = 128px) so
     the content box + the bottom-anchored scroll cue both fit within
     100vh - header-height. The header grew to ~93px (dbb0a05), leaving 627px
     at a 720px viewport; the full 128px bottom padding inflated the hero box
     to 662px and dragged the absolute `bottom:16px` cue ~19px below the fold.
     space-6 + 24px (96px) keeps the cue in-fold while preserving cue
     clearance. Section-index only — leaf heroes keep the fuller padding. */
  padding-bottom: calc(var(--space-6) + 24px);
}
/* The 2026-09 hero copy runs a line taller than what the padding above was
   tuned against, which pushed the cue ~17px below a 720px fold again. Short
   viewports trade the bottom rhythm for keeping the cue in view; taller ones
   keep the fuller spacing. Guarded by hero-scroll-cue.spec.ts. */
@media (max-height: 780px) {
  body[data-section-index] .hero {
    padding-bottom: calc(var(--space-5) + 24px);
  }
}

/* Short screens: the hero has to fit below the header, which is visible at the top of the
   page on every visit after the first, so it is never pulled up underneath it. */
@media (max-height: 760px) {
  body[data-section-index] .hero {
    min-height: calc(100dvh - var(--header-height, 5rem));
    padding-top: var(--space-3);
    padding-bottom: calc(var(--space-4) + 24px);
  }
}

/* The shortest screens (640px and under) need the last few pixels too, and
   they have to come out of the CONTENT: the hero is centred inside its
   min-height, so trimming its padding only re-centres the same block. */
@media (max-height: 668px) {
  body[data-section-index] .hero {
    padding-top: var(--space-2);
    padding-bottom: calc(var(--space-2) + 24px);
  }

  body[data-section-index] .hero .hero-brand-mark {
    margin-block: var(--space-2);
  }

  body[data-section-index] .hero .hero-cta {
    margin-top: var(--space-2);
  }
}
@media (min-width: 768px) {
  .hero {
    padding: var(--space-6) 0 calc(var(--space-7) + 24px);
  }
}
.hero-inner {
  width: 100%;
  /* One page measure with every other section (tokens/surfaces.css) — the hero
     used to sit 12rem narrower, so its content edge never lined up with the
     chapter below it. Text blocks inside still cap themselves by ch. */
  max-width: var(--page-measure);
  margin: 0 auto;
  padding: 0 var(--space-3);
}
@media (min-width: 768px) {
  .hero-inner { padding: 0 var(--space-4); }
}

/* Logo: matches the SVG HTML width/height attributes (64×64) at the
   smallest breakpoint so the browser reserves the right box for CLS
   before CSS parses; ≥768px scales up to 96px. */
.hero-mark {
  display: block;
  width: 64px;
  height: 64px;
  margin: 0 auto var(--space-3);
}
@media (min-width: 768px) {
  .hero-mark { width: 96px; height: 96px; }
}

.hero .eyebrow {
  font-family: var(--font-sans);
  font-size: var(--type-size-eyebrow-base);
  font-weight: var(--type-weight-medium);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  /* Coloured text, as a hued section's eyebrow is. */
  color: var(--accent-ink);
  margin: 0 auto var(--space-2);
  max-width: 70ch;
}

.hero h1 {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-regular);
  font-size: var(--type-size-h1-base);
  /* 1.2, not --type-line-height-tight (1.12): the ::selection highlight box
     height equals line-height, and 1.12 cropped the serif's descenders. */
  line-height: 1.2;
  letter-spacing: -0.02em;
  margin: 0 auto var(--space-3);
  color: var(--ink-primary);
  max-width: 70ch;
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Landing hero two-line swap animation (heroLineReveal flag).            │
   │                                                                        │
   │ Landing only: the h1 renders as two <span class="hero-h1-line"> rows  │
   │ (data-hero-h1-reveal), each carrying its replacement copy in           │
   │ data-h1-alt. public/js/hero-reveal.js drives data-h1-anim through      │
   │ exiting → enter-prep → entering on the first-interaction reveal.       │
   │                                                                        │
   │ Exit:  upper slides right + fades, lower slides left + fades.          │
   │ Enter: new upper slides in from the left, new lower from the right.    │
   │ overflow-x: clip contains the horizontal slide (no scrollbar) while    │
   │ leaving the y-axis visible so the serif descenders are never cropped.  │
   │                                                                        │
   │ Reduced motion is handled two ways and both yield an instant swap: the │
   │ motion-tokens layer zeroes --motion-duration-base, and hero-reveal.js  │
   │ takes an explicit no-animation path (matchMedia) that swaps text only. │
   └────────────────────────────────────────────────────────────────────────┘ */
.hero h1[data-hero-h1-reveal] {
  overflow-x: clip;
  /* Explicit so the x-only clip cannot coerce the y-axis to auto on engines
     that quirk the single-axis overflow rule — descenders stay visible. */
  overflow-y: visible;
  --hero-h1-slide: 2rem;
}
.hero-h1-line {
  display: block;
  transition:
    transform var(--motion-duration-base) var(--motion-easing-exit),
    opacity var(--motion-duration-base) var(--motion-easing-exit);
}
[data-h1-anim="exiting"] .hero-h1-line:first-child {
  transform: translateX(var(--hero-h1-slide));
  opacity: 0;
}
[data-h1-anim="exiting"] .hero-h1-line:last-child {
  transform: translateX(calc(-1 * var(--hero-h1-slide)));
  opacity: 0;
}
/* enter-prep: place the swapped lines on the opposite side with NO transition
   so the jump to the start offset is not itself animated. */
[data-h1-anim="enter-prep"] .hero-h1-line {
  transition: none;
  opacity: 0;
}
[data-h1-anim="enter-prep"] .hero-h1-line:first-child {
  transform: translateX(calc(-1 * var(--hero-h1-slide)));
}
[data-h1-anim="enter-prep"] .hero-h1-line:last-child {
  transform: translateX(var(--hero-h1-slide));
}
/* entering: settle to rest with the decelerate (entrance) easing. */
[data-h1-anim="entering"] .hero-h1-line {
  transition:
    transform var(--motion-duration-base) var(--motion-easing-base),
    opacity var(--motion-duration-base) var(--motion-easing-base);
  transform: translateX(0);
  opacity: 1;
}

.hero-sub {
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-base);
  color: var(--ink-secondary);
  margin: 0 auto var(--space-2);
  max-width: 70ch;
}
@media (min-width: 768px) {
  .hero-sub { font-size: var(--type-size-body-comfortable); }
}

/* Brand-mark italic close, always in the accent. */
.hero-brand-mark {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--type-size-body-base);
  color: var(--accent-ink);
  margin: var(--space-3) auto;
  max-width: 70ch;
}

.hero-cta {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: stretch;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
/* On a phone the pair still sits side by side: equal halves of the row, the
   label wrapping inside rather than pushing the second button under the first
   (fr's "Rejoindre la liste d'attente" does not fit on one line at 390px). */
@media (max-width: 479px) {
  .hero-cta:has(.cta-companion + .cta-companion) > .cta-companion {
    flex: 1 1 0;
    min-width: 0;
    padding-inline: 0.75rem;
    text-align: center;
    white-space: normal;
  }
}

.cta-companion {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-sans);
  font-size: var(--type-size-body-base);
  font-weight: var(--type-weight-medium);
  background: var(--cta-face);
  color: var(--cta-label);
  border: 1px solid var(--cta-face);
  border-radius: 6px;
  padding: 0.625rem 1.125rem;
  min-height: 48px;
  cursor: pointer;
  transition:
    border-color var(--motion-quick) var(--easing),
    color var(--motion-quick) var(--easing),
    background-color var(--motion-quick) var(--easing);
}
/* Ghost variant — same shape, transparent fill, ink text + border. The
   shine + scale hover treatment is inherited via the base `.cta-companion`
   selector list in tokens/cta-interaction.css. */
.cta-companion--secondary {
  background: transparent;
  color: var(--ink-primary);
}
.cta-companion:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .cta-companion { transition: none; }
}

/* Buttons are ink at rest. Under the hand or focus a CTA's face turns copper; any other button
   keeps its face and turns its frame and text copper. Both in a copper glow, not the shine. */
.cta-companion::before,
.product-cta-button::before,
.consent-button::before {
  display: none;
}
.cta-companion:not(.cta-companion--secondary):hover,
.cta-companion:not(.cta-companion--secondary):focus-visible,
.product-cta-button--primary:hover,
.product-cta-button--primary:focus-visible,
.consent-button--save:hover,
.consent-button--save:focus-visible,
.companion-flow__submit:not(:disabled):hover,
.companion-flow__submit:focus-visible {
  background: var(--accent-primary);
  border-color: var(--accent-primary);
  color: var(--ink-on-accent);
  box-shadow: var(--hover-glow);
}
.cta-companion--secondary:hover,
.cta-companion--secondary:focus-visible,
.product-cta-button--secondary:hover,
.product-cta-button--secondary:focus-visible,
.consent-button--reject:hover,
.consent-button--reject:focus-visible {
  border-color: var(--accent-primary);
  color: var(--accent-ink);
  box-shadow: var(--hover-glow);
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Scroll cue. The cue proper is the colony's doing (public/js/grid-ants  │
   │ .js): two files of six ants walk down the lattice from either side of  │
   │ the hero, converging on the centre and leaving by the bottom of the    │
   │ screen — towards the page below. This block is the state-machine host  │
   │ plus the STATIC fallback: one short column of ants, head down, for     │
   │ when the canvas does not run (reduced motion, Save-Data, a weak        │
   │ device). Nothing here animates.                                        │
   │                                                                        │
   │ Visibility gates on body[data-hero-cue]: hidden by default, fades in  │
   │ once hero-reveal.js sets the visible state ~2.5s after load. Fades    │
   │ out (and detaches) on body[data-hero-mode="scrolled"].                │
   └────────────────────────────────────────────────────────────────────────┘ */
.hero-scroll-cue-cluster {
  position: absolute;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  /* The fallback is the same V the colony walks, so the box is its width. */
  width: 104px;
  color: var(--ink-primary);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--motion-duration-base) var(--motion-easing-exit);
}
/* Cue is a "scroll past the full-bleed hero" affordance — section-index only.
   On content-sized leaf heroes it has no meaning, so hide it there. */
body:not([data-section-index]) .hero-scroll-cue-cluster {
  display: none;
}

.hero-scroll-cue-cluster__column {
  display: block;
  width: 104px;
  height: 64px;
  filter: drop-shadow(0 0 2px color-mix(in srgb, currentColor 45%, transparent));
}

/* The canvas cue is live: the fallback stands down. */
html[data-ant-cue="on"] .hero-scroll-cue-cluster__column {
  visibility: hidden;
}

/* Cue visibility is gated on the body state machine. */
body[data-hero-cue="visible"] .hero-scroll-cue-cluster {
  opacity: 1;
}
body[data-hero-mode="scrolled"] .hero-scroll-cue-cluster {
  opacity: 0;
}

/* Landing hero chat: the first message to Vompanion, typed or picked. */
.hero-chat {
  width: min(660px, 100%);
  margin: 0 auto;
}

/* The ask screen. Every set sits in the same grid cell, so the block keeps the height of the
   tallest and a turn never moves the page. */
.hero-ask__stack {
  display: grid;
}
.hero-ask__stack > * {
  grid-area: 1 / 1;
  transition: opacity 600ms var(--easing), visibility 600ms;
}
.hero-ask__stack > [data-ask-off] {
  opacity: 0;
  visibility: hidden;
}
.hero-ask__q {
  font-family: var(--font-serif);
  font-size: var(--type-size-h1-base);
  line-height: 1.12;
  color: var(--ink-primary);
  text-align: center;
  text-wrap: balance;
  margin: 0;
}
/* Four pills on a phone: keep the dots clear of the scroll cue. */
@media (max-width: 599px) {
  .hero-ask { padding-bottom: 88px; }
  .hero-ask .hero-chat__input { min-height: 4.5rem; }
  .hero-ask .hero-chat__prompts { gap: var(--space-1); }
  .hero-ask .hero-chat__prompt { padding: 0.5rem 1rem; }
}
@media (max-width: 599px) and (max-height: 720px) {
  .hero-ask { padding-bottom: 0; }
  .hero-ask .hero-scroll-cue-cluster { display: none; }
}
.hero-ask__accent {
  font-style: normal;
  color: var(--accent-ink);
}
/* Two lines on desktop: the question runs wider than the box under it. */
@media (min-width: 1024px) {
  .hero-ask__q { margin-inline: calc((100% - min(1120px, 92vw)) / 2); }
}
/* The composer keeps one size whichever preview fills it; what is typed past it scrolls. */
.hero-ask .hero-chat__input {
  field-sizing: fixed;
  height: 7.5rem;
  min-height: 0;
  overflow-y: auto;
}
@media (max-width: 599px) {
  .hero-ask .hero-chat__input { height: 10.25rem; }
}
.hero-ask .hero-brand-mark {
  margin: var(--space-3) auto 0;
  text-align: center;
}
/* The suggestions sit between the question and the box, and read as answers on a desktop. */
.hero-ask .hero-chat__prompts { margin: var(--space-4) 0 var(--space-3); }
@media (min-width: 1024px) {
  .hero-ask .hero-chat__prompts {
    gap: var(--space-3);
    margin-inline: calc((100% - min(1120px, 92vw)) / 2);
  }
  .hero-ask .hero-chat__prompt {
    font-size: var(--type-size-body-base);
    padding: 0.875rem 1.25rem;
  }
}
/* The ring (hero-chat.js): pills placed around the centred one, the next ones out cut by the faded
   edge. Without JS the pills stay a wrapped row. */
.hero-ask__ring[data-ring-on] .hero-chat__prompts {
  position: relative;
  display: block;
  overflow: hidden;
  padding: 0;
  mask-image: linear-gradient(90deg, transparent, rgb(0 0 0 / 30%) 12%, #000 28%, #000 72%, rgb(0 0 0 / 30%) 88%, transparent);
}
@media (min-width: 600px) {
  .hero-ask .hero-ask__ring[data-ring-on] .hero-chat__prompts { margin-inline: calc((100% - min(1120px, 92vw)) / 2); }
}
@media (max-width: 599px) {
  .hero-ask__ring[data-ring-on] .hero-chat__prompts {
    mask-image: linear-gradient(180deg, transparent, rgb(0 0 0 / 25%) 18%, #000 36%, #000 64%, rgb(0 0 0 / 25%) 82%, transparent);
  }
}
/* Desktop: a horizontal drag turns the row, a vertical one still scrolls the page. */
.hero-ask__ring[data-ring-on] .hero-chat__prompts {
  touch-action: pan-y;
  cursor: grab;
  user-select: none;
}
.hero-ask__handle { display: none; }
/* Phone: the column scrolls the page like any content; the handles beside it turn it. */
@media (max-width: 599px) {
  .hero-ask__ring[data-ring-on] {
    display: grid;
    grid-template-columns: 2.25rem 1fr 2.25rem;
    align-items: center;
    gap: var(--space-1);
  }
  .hero-ask__ring[data-ring-on] .hero-chat__prompts {
    touch-action: auto;
    cursor: auto;
  }
  .hero-ask__ring[data-ring-on] .hero-ask__handle {
    display: block;
    align-self: stretch;
    margin-block: var(--space-4) var(--space-3);
    border: 1px solid var(--color-glass-rim);
    border-radius: 999px;
    background:
      linear-gradient(var(--ink-secondary), var(--ink-secondary)) center calc(50% - 6px) / 12px 2px no-repeat,
      linear-gradient(var(--ink-secondary), var(--ink-secondary)) center / 12px 2px no-repeat,
      linear-gradient(var(--ink-secondary), var(--ink-secondary)) center calc(50% + 6px) / 12px 2px no-repeat,
      var(--surface-card);
    opacity: 0.7;
    touch-action: none;
    cursor: grab;
  }
}
.hero-ask__ring[data-ring-on] li {
  position: absolute;
  top: 50%;
  left: 50%;
  white-space: nowrap;
  transition: transform 600ms var(--easing), opacity 600ms var(--easing);
}
/* The neighbours fade only as far as AA contrast allows; the ones further out fade in the edge mask. */
.hero-ask__ring[data-ring-on] li[data-ring="1"],
.hero-ask__ring[data-ring-on] li[data-ring="-1"] { opacity: 0.7; }
@media (prefers-reduced-motion: reduce) {
  .hero-ask__ring[data-ring-on] li { transition: none; }
}
.hero-ask__dots {
  display: flex;
  justify-content: center;
  gap: var(--space-1);
  margin-top: var(--space-3);
}
.hero-ask__dot {
  width: 24px;
  height: 24px;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
}
.hero-ask__dot::before {
  content: "";
  display: block;
  width: 8px;
  height: 8px;
  margin: auto;
  border-radius: 999px;
  background: var(--color-glass-rim);
  transition: background-color var(--motion-quick) var(--easing), width var(--motion-quick) var(--easing);
}
.hero-ask__dot[aria-pressed="true"]::before {
  width: 20px;
  background: var(--accent-primary);
}
.hero-ask__dot:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
  border-radius: 6px;
}
@media (prefers-reduced-motion: reduce) {
  .hero-ask__stack > * { transition: none; }
}

.hero-chat__box {
  position: relative;
  border: 1px solid var(--color-glass-rim);
  border-radius: 20px;
  background: var(--surface-card);
  box-shadow: 0 8px 28px rgb(0 0 0 / 8%);
  transition: border-color var(--motion-quick) var(--easing);
}
.hero-chat__box:focus-within { border-color: var(--accent-primary); }
.hero-chat__input {
  display: block;
  width: 100%;
  min-height: 7.5rem;
  max-height: 16rem;
  field-sizing: content;
  padding: 1.125rem 4rem 1.125rem 1.375rem;
  /* Never below 16px: iOS zooms a focused sub-16px field. */
  font: max(16px, var(--type-size-body-base)) / 1.5 var(--font-sans);
  color: var(--ink-primary);
  text-align: left;
  background: transparent;
  border: 0;
  border-radius: inherit;
  resize: none;
}
.hero-chat__input:focus { outline: none; }
.hero-chat__send {
  position: absolute;
  right: 0.625rem;
  bottom: 0.625rem;
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  color: var(--ink-secondary);
  background: transparent;
  border: 0;
  border-radius: 999px;
  opacity: 0.55;
  cursor: pointer;
  transition: opacity var(--motion-quick) var(--easing), color var(--motion-quick) var(--easing);
}
/* Quiet at rest; it wakes when there is something to send or the hand is on it. */
.hero-chat__box:focus-within .hero-chat__send,
.hero-chat__send:hover,
.hero-chat__send:focus-visible {
  color: var(--accent-ink);
  opacity: 1;
}
.hero-chat__send:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}
.hero-chat__prompts {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
}
.hero-chat__prompt {
  font: var(--type-weight-medium) var(--type-size-small-base) / 1.3 var(--font-sans);
  color: var(--ink-primary);
  background: var(--surface-card);
  border: 1px solid var(--color-glass-rim);
  border-radius: 999px;
  padding: 0.625rem 1.25rem;
  cursor: pointer;
  transition: border-color var(--motion-quick) var(--easing), background-color var(--motion-quick) var(--easing);
}
.hero-chat__prompt:hover,
.hero-chat__prompt:focus-visible,
.hero-chat__prompt--lit {
  border-color: var(--accent-primary);
  background: color-mix(in srgb, var(--accent-primary) 12%, var(--surface-card));
} /* Story 2.1 — A1 centred/cinematic hero block (landing-page.eta consumer rules) */
/* ──────────────────────────────────────────────────────────────────────────
   aspects.css — Story 2.2 D8=b+c. Section-level aspect-cascade cells.

   Story 1.3 declared only the manifesto-aspect cells (per its `tokens/
   color-tokens.css:19` comment "ONLY the (aspect × surface) cells the
   manifesto-preview required"); Story 2.2 adds the cells THIS story
   consumes:

     • [data-aspect="enterprise"] — wraps the portfolio block (above the
       manifesto-entry CTA). "Elevated accent ratio" per prd.md:543-569 +
       UX:594 ("full system on; dividers + waypoints + grid"). Implemented
       via --opacity-accent: 1 + --waypoint-fill bound to --accent-primary
       so decorative marks paint at full opacity. **--accent-primary itself
       is NOT re-bound** — text-fill consumers (portfolio-card-cta, etc.)
       inherit the page-level mode-adaptive resolution (EU blue in light,
       EU gold in dark) which is already AA-safe per Story 1.3 token-design.
       Re-binding --accent-primary at aspect-level would force a per-mode
       wrong-colour cascade and break WCAG AA contrast.

     • [data-aspect="smb"] — wraps post-portfolio scroll content (below the
       manifesto-entry CTA, above the DPP pre-footer). Modest accent ratio
       per UX:595 ("grid only; waypoints suppressed — felt-register avoids
       structural marking"); --opacity-accent: 0.15; waypoint-fill set to
       transparent to suppress structural marking.

     • [data-aspect="dpp"] — wraps the pre-footer DPP block (rendered by
       templates/partials/dpp-cta.eta). Full Stigmergy texture per UX:597
       ("full system on for the footer-zone invitation block, but no
       waypoint on the partner-link itself"); --opacity-accent: 1.

   `developer` cell DEFERRED to Story 3.4 (first consumer at
   /products/enterprise-mesh per UX:640) per Story 1.3 "declare only what's
   needed" precedent.

   Selectors are descendant-scoped (`[data-aspect="X"]`) so the aspect
   attribute lives on a SECTION root (e.g., `<section data-aspect="enterprise">`),
   NOT on `<html>`. The cascade picks up tokens from the surrounding mode
   context (light at :root, dark at :root[data-theme="dark"]) — and per
   the contrast-discipline above, aspect cells re-bind ONLY structural
   tokens (--opacity-accent, --waypoint-fill, --trail-stroke), NEVER
   --ink-* / --surface-* / --accent-primary. Substrate stays constant
   per AC6 anthracite/whitish-white rule + WCAG AA contrast.

   Type fills stay solid-on-solid per UX:601 + UX-DR1 (no pheromone
   underline / no dot-pattern on headings / no metallic finish on type).

   Lives under tokens/** so the four-prefix stylelint regex exemption
   (Story 1.1) covers ad-hoc per-aspect declarations.
   ────────────────────────────────────────────────────────────────────────── */

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Enterprise zone — full accent ratio for decorative marks.              │
   │ --accent-primary inherits the page copper (light #C2410C /             │
   │ dark #FB923C), AA on text-fill consumers.                              │
   │ Used by: <section data-aspect="enterprise"> wrapping the portfolio    │
   │ block in templates/pages/landing-page.eta (Story 2.2 Task 7).         │
   └────────────────────────────────────────────────────────────────────────┘ */
[data-aspect="enterprise"] {
  --opacity-accent: 1;
  --waypoint-fill: var(--accent-primary);
  --trail-stroke: var(--accent-primary);
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ SMB zone — modest accent ratio (felt register; structural marking     │
   │ suppressed per UX:595).                                                │
   │ Used by: <section data-aspect="smb"> wrapping post-portfolio scroll   │
   │ content (placeholder for Story 3.x value-prop content; empty at MVP-0). │
   └────────────────────────────────────────────────────────────────────────┘ */
[data-aspect="smb"] {
  --opacity-accent: 0.15;
  --waypoint-fill: transparent;
  --trail-stroke: var(--rule-quiet);
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ DPP zone — full Stigmergy texture for the invitation register.        │
   │ Used by:                                                               │
   │  • <section data-aspect="dpp"> in templates/partials/dpp-cta.eta —    │
   │    the D1 pre-footer block on the landing root (Story 2.2 Task 3).    │
   │  • <article data-aspect="dpp"> in templates/pages/partners.eta —      │
   │    the full /partners page wrapper (Story 3.6). Same token cascade    │
   │    applied at page scope, since the entire surface IS the DPP zone.   │
   │ NOTE: per UX:629 + UX:1300, NO waypoint mark renders on the /partners  │
   │ link itself — that constraint lives at the PARTIAL level (the link     │
   │ doesn't carry a waypoint marker), not in the aspect cell.              │
   │ --accent-primary stays the page copper (light and dark) —              │
   │ both AA-safe. --waypoint-fill is the accent: a mark carries a charge, │
   │ never the retired metallic finish (vision/visual-system.md).           │
   └────────────────────────────────────────────────────────────────────────┘ */
[data-aspect="dpp"] {
  --opacity-accent: 1;
  --waypoint-fill: var(--accent-primary);
  --trail-stroke: var(--accent-primary);
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Developer zone — near-zero accent ratio per UX:640. Used by           │
   │ /products/enterprise-mesh (Story 3.4 first consumer); UX spec frames the │
   │ developer register as low-ornamentation (the substrate is the        │
   │ message; the page is a capability enumeration). --accent-primary     │
   │ stays page mode-adaptive (light:blue / dark:gold) for the rare       │
   │ accent consumers (deep-link CTA, pricing line) to land at AA.        │
   │                                                                       │
   │ Waypoint + trail are NOT suppressed here — the 2026-05-14 site-wide  │
   │ stigmergy policy admits the rail on every product surface including  │
   │ enterprise-mesh. The earlier Story 3.4 "no waypoint marks, no pheromone  │
   │ trails" carve-out is superseded.                                     │
   └────────────────────────────────────────────────────────────────────────┘ */
[data-aspect="developer"] {
  --opacity-accent: 0.05;
} /* Story 2.2 — section-level aspect-cascade cells (enterprise/smb/dpp); developer cell deferred to Story 3.4 */
/* ──────────────────────────────────────────────────────────────────────────
   portfolio.css — Story 2.2 B1 5-up grid + card consumer rules.

   Composition lock: B1 cards-grid per ux-design-specification.md §Step 9
   §Implementation Approach line 1637, adapted to 5-up after the 2026-05-09
   MPA split (was 4-up MWB/MAH/MPA/MAM; now 5-up Workflow Builder /
   Agent Hub / Managed OpenClaw / Local Assistant / Enterprise Mesh).

   Breakpoints (per UX:1311-1316 + UX:1637 spec):
     • <600px       → 1-up (single column; mobile portrait)
     • 600–1024px   → 2-up (mobile landscape / small tablet portrait)
     • 1024–1280px  → 3-up (laptop / tablet landscape; 3+2 wrap accepted)
     • ≥1280px      → 5-up (desktop full)

   Card composition: 1px border at --rule-quiet; hover lifts to
   --accent-primary (cascades through containing section's data-aspect).
   Type fills solid-on-solid per UX:601 + UX-DR1 (no metallic on type).

   Reduced-motion: hover transition zeroed via tokens/motion-tokens.css
   global escape hatch (lines 48-57); local override is belt-and-braces
   only and unnecessary here — the token gate catches it.

   Token consumers use CURRENT (pre-migration) names; Story 1.3-update
   sweep targets are forward-compat with the same scheme as tokens/hero.css
   leading comment (Story 2.1 deferred-work.md:36).

   Forward-compat sweep targets that Story 1.3-update will resolve:
     • --measure              → --measure-site (72rem)
     • --type-size-h1-base    → --type-size-h1
     • --type-size-body-base / -comfortable → --type-size-body (stepped)
     • Hardcoded 32ch         → var(--measure-narrow)

   Lives under tokens/** so the four-prefix stylelint regex exemption
   (Story 1.1) covers the ad-hoc class names.
   ────────────────────────────────────────────────────────────────────────── */

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Pillars zone — "how the Workforce works." Six platform layers under   │
   │ every Autonomous Enterprise: cloud abstraction · collaboration ·      │
   │ stigmergy · integrations · traceability · AI Act readiness. Section   │
   │ envelope mirrors .value-prop-zone for continuous vertical rhythm.     │
   │ Cards are descriptive (not links) — no hover/focus affordance.        │
   └────────────────────────────────────────────────────────────────────────┘ */
/* First body section after the hero (it absorbed the old what-is band,
   SCP-2026-06-08), so it carries that band's larger top padding for the
   hero→section rhythm. */
.pillars-zone {
  padding: var(--space-6) 0 var(--space-6);
}
.pillars-zone > * {
  width: 100%;
  max-width: var(--measure);
  margin-left: auto;
  margin-right: auto;
  padding-left: var(--space-3);
  padding-right: var(--space-3);
}
@media (min-width: 768px) {
  .pillars-zone {
    padding: var(--space-7) 0 var(--space-7);
  }
  .pillars-zone > * {
    padding-left: var(--space-4);
    padding-right: var(--space-4);
  }
}

.pillars-heading {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-regular);
  font-size: var(--type-size-h1-base);
  line-height: var(--type-line-height-tight);
  letter-spacing: -0.02em;
  text-align: center;
  color: var(--ink-primary);
  margin: 0 auto var(--space-3);
}

.pillars-intro {
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-base);
  color: var(--ink-secondary);
  text-align: center;
  margin: 0 auto var(--space-5);
  max-width: 70ch;
}
@media (min-width: 768px) {
  .pillars-intro {
    font-size: var(--type-size-body-comfortable);
  }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ §3 Six cornerstones — circular layout + center-expand (Story 8.1).      │
   │                                                                          │
   │ Progressive enhancement, three states:                                   │
   │   • No JS (baseline): .pillars-circle is a readable stack — every        │
   │     cornerstone shows its name and line. Fully accessible, no JS needed. │
   │   • JS + narrow viewport: stacked ACCORDION — name buttons toggle their   │
   │     detail open/closed beneath them (AC5: not a circle on mobile).        │
   │   • JS + wide viewport: RING — six round buttons around a center; the     │
   │     selected one blooms radially inward, detail inside (AC1/AC2).         │
   │                                                                          │
   │ public/js/pillars-circle.js adds [data-enhanced] + toggles .is-open on a  │
   │ slot; ALL interactive/hidden styling is gated behind                      │
   │ .pillars-circle[data-enhanced] so the no-JS baseline stays readable.      │
   │ Fly-to-center is a transition (AC4: zeroed under reduced-motion by the    │
   │ tokens/motion-tokens.css global escape hatch). Each name is an <h3>-      │
   │ wrapped <button> (WAI disclosure + heading hierarchy AC3/AC5).            │
   └────────────────────────────────────────────────────────────────────────┘ */
.pillars-circle {
  margin: 0 auto;
  max-width: var(--measure);
}
.pillars-ring {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* ── Baseline (no JS) + accordion (JS, narrow): stacked slots ─────────────── */
.pillars-slot {
  background: var(--surface-card);
  border: 1px solid var(--rule-quiet);
  border-radius: 6px;
  overflow: hidden;
}
.pillars-slot + .pillars-slot {
  margin-top: var(--space-3);
}
.pillars-node-heading {
  margin: 0;
  font: inherit;
}
.pillars-node {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  margin: 0;
  padding: var(--space-3) var(--space-4);
  background: transparent;
  border: 0;
  border-radius: inherit;
  cursor: default;
  font: inherit;
  color: var(--ink-primary);
  text-align: center;
}
.pillars-node__name {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-semibold);
  font-size: var(--type-size-body-comfortable);
  line-height: var(--type-line-height-tight);
}
/* The title sits INSIDE the name span so it rides the same ring transform. */
.pillars-node__title {
  display: block;
  margin-bottom: var(--space-1);
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  font-weight: var(--type-weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-glass-accent);
}
.pillars-node-detail {
  padding: 0 var(--space-4) var(--space-4);
}
.pillars-node-detail__body {
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-base);
  color: var(--ink-secondary);
  margin: 0;
}
/* The why, under the what. */
.pillars-node-detail__body--reason {
  margin-top: var(--space-2);
}
/* Center cue is decorative and only used by the ring layout. */
.pillars-center {
  display: none;
}
/* Ant orbit (faint path + marching ants) is ring-only: hidden in the baseline
   stack + accordion, switched on inside the wide-viewport ring media query. */
.pillars-orbit-path,
.pillars-orbit {
  display: none;
}

/* ── Enhanced (JS present) — shared between accordion + ring ──────────────── */
.pillars-circle[data-enhanced] .pillars-node {
  /* No pointer cursor: in the ring, hover itself blooms the disc, so a "click
     me" hand reads as misleading. Click + keyboard still work (native button). */
  cursor: default;
  transition: background var(--motion-base) var(--easing),
              color var(--motion-base) var(--easing);
}
.pillars-circle[data-enhanced] .pillars-node-detail[hidden] {
  display: none;
}
.pillars-circle[data-enhanced] .pillars-node:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}
@media (hover: hover) {
  .pillars-circle[data-enhanced] .pillars-node:hover {
    color: var(--accent-primary);
  }
}
.pillars-circle[data-enhanced] .pillars-slot.is-open .pillars-node {
  background: var(--accent-primary);
  color: var(--ink-on-accent);
}

/* ── Enhanced NARROW (JS + phone/tablet) ─────────────────────────────────
   One cornerstone per screen, in glass. pillars-circle.js opens them in turn
   as the reader scrolls, so the stack reads as a deck rather than a list: the
   card in hand is lit, its neighbours are held back. Material is the page's
   own glass tokens (tokens/glass.css) — no second glass here. */
@media (max-width: 1023.98px) {
  .pillars-circle[data-enhanced] .pillars-ring {
    display: flex;
    flex-direction: column;
  }

  .pillars-circle[data-enhanced] .pillars-slot {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: 76vh;
    padding: var(--space-5) var(--space-4);
    background: var(--color-glass-fill);
    border: 1px solid var(--color-glass-rim);
    border-radius: 14px;
    box-shadow: var(--color-glass-shadow);
    backdrop-filter: var(--motion-glass-lens);
    -webkit-backdrop-filter: var(--motion-glass-lens);
    opacity: 0.3;
    transform: scale(0.94);
    transition:
      opacity var(--motion-duration-slow) var(--motion-easing-base),
      transform var(--motion-duration-slow) var(--motion-easing-base),
      box-shadow var(--motion-duration-slow) var(--motion-easing-base);
  }

  .pillars-circle[data-enhanced] .pillars-slot + .pillars-slot {
    margin-top: var(--space-4);
  }

  .pillars-circle[data-enhanced] .pillars-slot.is-open {
    opacity: 1;
    transform: none;
    border-color: color-mix(in srgb, var(--color-glass-accent) 45%, var(--color-glass-rim));
    box-shadow:
      var(--color-glass-shadow-lift),
      0 10px 40px -14px color-mix(in srgb, var(--color-glass-accent) 55%, transparent);
  }

  .pillars-circle[data-enhanced] .pillars-node-detail {
    padding: var(--space-3) var(--space-2) 0;
    text-align: center;
  }

  @media (prefers-reduced-motion: reduce) {
    .pillars-circle[data-enhanced] .pillars-slot {
      opacity: 1;
      transform: none;
      min-height: 0;
    }
  }
}

/* ── Enhanced RING (JS + wide viewport) ──────────────────────────────────── */
@media (min-width: 1024px) {
  .pillars-circle[data-enhanced] {
    position: relative;
    /* One radius drives ring placement AND the ant-orbit radius so the geometry
       can't drift apart (six centres 60deg apart sit an adjacent-distance of R).
       --pillars-node-size is the closed disc; kept < R so neighbours DON'T touch
       and the ant orbit shows through the gaps (SCP-2026-06-08).
       --pillars-bloom is the open disc. It deliberately does NOT cover the ring
       (SCP-2026-06-09): the five unselected discs slide out to
       --pillars-radius-active, shrink to --pillars-dim-scale, and dim, so the
       whole stays visible around the bloom — the section's thesis.

       RADIAL bloom (SCP-2026-06-09b): the disc inflates ALONG its radius. Its
       centre travels the full radius to the ring centre while it scales to
       k = bloom/node, so the growth is directed inward at every frame: the
       inner edge sweeps right across the centre while the outer edge retreats
       only slightly. The final bloom is CENTRED on the ring, with a radius of
       14rem — a touch inside the 15rem ant orbit, so the ants keep marching
       around the open disc. The two adjacent dimmed siblings tuck ~1.6rem
       behind the bloom's flanks (z 5 > 2), which reads as depth, not
       occlusion.

       Bloom timing is local (--pillars-bloom-dur), deliberately slower than
       the global --motion-slow: the bloom is the section's one big gesture. */
    --pillars-radius: 15rem;
    --pillars-node-size: 10.5rem;
    --pillars-bloom: 28rem;
    /* bloom / node-size as a number: Firefox has no length ÷ length in calc(). */
    --pillars-bloom-scale: calc(28 / 10.5);
    --pillars-bloom-dur: 700ms;
    --pillars-radius-active: 16.5rem;
    --pillars-dim-scale: 0.78;
    --pillars-dim-opacity: 0.5;
  }
  .pillars-circle[data-enhanced] .pillars-ring {
    position: relative;
    width: min(100%, 42rem);
    margin-inline: auto;
    aspect-ratio: 1 / 1;
  }
  /* Slot loses its card box; its children (heading button + detail) position
     against .pillars-ring (the nearest positioned ancestor). */
  .pillars-circle[data-enhanced] .pillars-slot {
    background: none;
    border: 0;
    border-radius: 0;
    overflow: visible;
    margin: 0;
    /* Angle lives on the SLOT (not the heading) so both children inherit it:
       the heading for ring placement, the detail for radial anchoring to the
       bloom centre. --pillars-i = 0..n-1 (inline), --pillars-count = n. */
    --pillars-angle: calc(var(--pillars-i) * (360deg / var(--pillars-count)));
  }
  /* Button placed around the ring via the rotate / counter-rotate sandwich so
     the card stays upright. The heading STAYS at its ring position when open
     (the radial bloom grows from here on ::before); its transform only changes
     for the siblings' make-room slide (.is-active block below). */
  .pillars-circle[data-enhanced] .pillars-node-heading {
    position: absolute;
    top: 50%;
    left: 50%;
    z-index: 2;
    transform: translate(-50%, -50%)
               rotate(var(--pillars-angle))
               translateY(calc(-1 * var(--pillars-radius)))
               rotate(calc(-1 * var(--pillars-angle)));
    /* Bloom-paced so the make-room slide + dim move with the bloom. */
    transition: transform var(--pillars-bloom-dur) var(--easing),
                opacity var(--pillars-bloom-dur) var(--easing);
  }
  /* Closed disc: smaller than the radius so the six discs keep clear gaps and
     the ant orbit shows between them (SCP-2026-06-08). The button is a fixed
     transparent box; the visible disc surface lives on ::before so the bloom
     can inflate with a composited transform: scale() instead of width/height
     layout thrash, while the name + detail keep their natural text size
     (SCP-2026-06-09). */
  .pillars-circle[data-enhanced] .pillars-node {
    position: relative;
    width: var(--pillars-node-size);
    height: var(--pillars-node-size);
    padding: var(--space-4);
    border: 0;
    border-radius: 50%;
    background: none;
  }
  .pillars-circle[data-enhanced] .pillars-node::before {
    content: "";
    position: absolute;
    inset: 0;
    /* Behind the button's in-flow name but inside the heading's stacking
       context (z 2/5), so it still paints above the ant orbit (z 0). */
    z-index: -1;
    border: 1px solid var(--rule-quiet);
    border-radius: 50%;
    background: var(--surface-card);
    /* Zeroed twin of the open state's radial-bloom list (same functions in
       the same order), so the browser interpolates per-component instead of
       falling back to matrix interpolation. */
    transform: rotate(var(--pillars-angle))
               translateY(0)
               scale(1)
               rotate(calc(-1 * var(--pillars-angle)));
    /* Colour/border/shadow stay snappy; the bloom's scale eases slowly. */
    transition: transform var(--pillars-bloom-dur) var(--easing),
                background var(--motion-base) var(--easing),
                border-color var(--motion-base) var(--easing),
                box-shadow var(--motion-base) var(--easing);
  }

  /* ── Ant orbit ─────────────────────────────────────────────────────────────
     A faint circle of radius R (== --pillars-radius) threads every cornerstone
     centre; #stigmergy-ant glyphs march clockwise around it. The orbit layer is
     a static 0x0 anchor at the ring centre. EACH ant is two nested 0x0 layers:
     the wrapper holds a STATIC even-spacing angle (--orbit-i), and the inner
     __spin layer turns on its own --orbit-dur (so every ant moves at a different
     speed, SCP-2026-06-08) — starting from the wrapper's even angle, the twelve
     drift apart over time. The glyph carries the radius offset + a rigid 180deg
     correction so it rides head-first. Reduced motion freezes the spin via the
     global motion-tokens gate (it zeroes animation-duration/-delay !important —
     no local gate needed); the wrapper's static angle then keeps the frozen ants
     spread around the ring instead of stacking at 0deg. Ants paint behind the
     discs (z 0 < node z 2) so they thread through each cornerstone and show in
     the gaps. */
  .pillars-circle[data-enhanced] .pillars-orbit-path {
    display: block;
    position: absolute;
    top: 50%;
    left: 50%;
    width: calc(2 * var(--pillars-radius));
    height: calc(2 * var(--pillars-radius));
    transform: translate(-50%, -50%);
    color: var(--ink-primary);
    opacity: 0.25;
    pointer-events: none;
    overflow: visible;
    z-index: 0;
  }
  .pillars-circle[data-enhanced] .pillars-orbit {
    display: block;
    position: absolute;
    top: 50%;
    left: 50%;
    width: 0;
    height: 0;
    color: var(--ink-primary);
    pointer-events: none;
    z-index: 0;
  }
  /* Wrapper: STATIC even-spacing angle from --orbit-i. This both sets each ant's
     start phase AND is the reduced-motion-safe base — when the gate neutralises
     the spin layer, the ants stay distributed around the ring. */
  .pillars-circle[data-enhanced] .pillars-orbit-ant {
    position: absolute;
    top: 0;
    left: 0;
    width: 0;
    height: 0;
    transform: rotate(calc(var(--orbit-i) * (360deg / var(--orbit-count))));
  }
  /* Spin layer: turns at this ant's own --orbit-dur, so the twelve drift apart
     from their even start (their differing speeds). Starts from 0 for all, so
     the wrapper's angle alone fixes the t=0 spacing. */
  .pillars-circle[data-enhanced] .pillars-orbit-ant__spin {
    position: absolute;
    top: 0;
    left: 0;
    width: 0;
    height: 0;
    animation: pillars-orbit-spin var(--orbit-dur, 12s) linear infinite;
  }
  .pillars-circle[data-enhanced] .pillars-orbit-ant__glyph {
    position: absolute;
    top: 0;
    left: 0;
    width: 18px;
    height: 6.75px;
    margin-left: -9px;
    margin-top: -3.375px;
    fill: currentcolor;
    transform: translateY(calc(-1 * var(--pillars-radius))) rotate(180deg);
  }
  @keyframes pillars-orbit-spin {
    to {
      transform: rotate(360deg);
    }
  }

  /* ── Radial bloom, whole stays visible (SCP-2026-06-09b) ───────────────────
     Driven solely by .is-open (set by pillars-circle.js on hover / click /
     keyboard activation — the JS owns the open state). Triggering off CSS
     :hover is deliberately avoided (it would oscillate with the geometry
     changing under the cursor); :focus-within is avoided too, so ARIA never
     desyncs. The disc inflates along its own radius: the rotate sandwich
     turns the inward translateY radial, the disc centre travels the full
     radius to the ring centre while scaling, and the matched closed/open
     transform lists keep the interpolation per-component — so the bloom grows
     out of the cornerstone's position and spreads across the centre. Name
     rises to the bloom apex, detail fades in at the centre, and the five
     unselected discs make room (outward + shrink + dim, block below), so the
     whole stays in view around the open part. */
  .pillars-circle[data-enhanced] .pillars-slot.is-open .pillars-node-heading {
    z-index: 5;
  }
  /* Keep the shared accordion accent fill off the round bloom: light disc with
     a structural accent ring (brand "structural-only accents" rule), why copy
     readable in dark ink. The button box stays transparent; the bloom is the
     scaled surface. Shadow is authored small so it lands right after the
     ~2.3x scale. */
  .pillars-circle[data-enhanced] .pillars-slot.is-open .pillars-node {
    background: none;
    color: var(--ink-primary);
  }
  .pillars-circle[data-enhanced] .pillars-slot.is-open .pillars-node::before {
    transform: rotate(var(--pillars-angle))
               translateY(var(--pillars-radius))
               scale(var(--pillars-bloom-scale))
               rotate(calc(-1 * var(--pillars-angle)));
    border-color: var(--accent-primary);
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.16);
  }
  /* Name rides the same radial travel to the ring centre, then rises to seat
     just under the bloom's apex. The 50% term pins the name's TOP edge (not
     its centre) at the seat, so one- and two-line names land at the same
     height. Closed state is the zeroed twin list for per-component
     interpolation. Long names (e.g. "Compound Knowledge Management") get a
     fixed 15rem column so they wrap to two centred lines instead of running
     off the round edge. */
  .pillars-circle[data-enhanced] .pillars-node__name {
    transform: rotate(var(--pillars-angle))
               translateY(0)
               rotate(calc(-1 * var(--pillars-angle)))
               translateY(0);
    transition: transform var(--pillars-bloom-dur) var(--easing);
  }
  .pillars-circle[data-enhanced] .pillars-slot.is-open .pillars-node__name {
    /* flex: none — the name is a flex item in the (still 10.5rem) button box;
       without it the 15rem column would be shrunk back to the button width. */
    flex: none;
    width: 15rem;
    transform: rotate(var(--pillars-angle))
               translateY(var(--pillars-radius))
               rotate(calc(-1 * var(--pillars-angle)))
               translateY(calc(-0.5 * var(--pillars-bloom) + var(--space-5) + 50%));
  }
  /* The five unselected discs make room: slide outward a touch, shrink, dim.
     The whole stays visible around the bloom (the section's thesis) and the
     ant orbit keeps threading between them. Reuses --pillars-angle from the
     placement rule above. */
  /* The opening slot jumps straight to full opacity — only its transform
     eases. An element with opacity < 1 is its own compositing layer, and a
     backdrop-filter descendant is NOT painted through one while that opacity
     is animating: that is why the first bloom frosted correctly (nothing was
     dimmed yet) and every later one opened clear and only picked up its glass
     once the fade had finished. */
  .pillars-circle[data-enhanced].is-active .pillars-slot.is-open .pillars-node-heading {
    opacity: 1;
    transition-property: transform;
  }

  .pillars-circle[data-enhanced].is-active .pillars-slot:not(.is-open) .pillars-node-heading {
    opacity: var(--pillars-dim-opacity);
    transform: translate(-50%, -50%)
               rotate(var(--pillars-angle))
               translateY(calc(-1 * var(--pillars-radius-active)))
               rotate(calc(-1 * var(--pillars-angle)))
               scale(var(--pillars-dim-scale));
  }

  /* Detail rides inside the bloomed disc, below the name. The
     container is the full 28rem bloom square, centred on the ring; the text
     follows the disc's round edge via two shape-outside floats (::before /
     ::after) whose polygons trace the left/right arcs, so every line gets the
     full chord width at its height. Meta type keeps the longest pair (FR
     Adaptive Collaboration, ~580 chars) inside the circle with headroom.

     COUPLED GEOMETRY: the polygon vertices below encode --pillars-bloom: 28rem
     with this 7.5rem padding-top (content box 28rem × 20.5rem, bloom centre
     at 50% / 104px ≈ 31.7% of the content height). If either value changes,
     re-derive the arc points: x = 224 + 224·cos(a), y = 104 + 224·sin(a) px,
     as % of the 224px × 328px float box. Browsers without shape-outside just
     get a straight full-width column (corners may graze the rim). */
  .pillars-circle[data-enhanced] .pillars-node-detail {
    position: absolute;
    top: 50%;
    left: 50%;
    width: var(--pillars-bloom);
    height: var(--pillars-bloom);
    /* Clears the name seated at the apex (up to two lines). */
    padding-top: 7.5rem;
    text-align: center;
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.96);
    transition: opacity var(--pillars-bloom-dur) var(--easing),
                transform var(--pillars-bloom-dur) var(--easing);
    z-index: 6;
  }
  /* Both floats must come BEFORE the text in box order (a float declared
     after content only excludes the lines below it), so the right float hangs
     on the first paragraph's ::before, not on the container's ::after. Its
     height is the container content height (28rem − 7.5rem padding) spelled
     out, because 100% of the auto-height <p> resolves to nothing. */
  .pillars-circle[data-enhanced] .pillars-node-detail::before,
  .pillars-circle[data-enhanced] .pillars-node-detail__body--why::before {
    content: "";
    width: 50%;
    height: 20.5rem;
    shape-margin: 0.5rem;
  }
  .pillars-circle[data-enhanced] .pillars-node-detail::before {
    float: left;
    shape-outside: polygon(0% 0%, 11.4% 0%, 3.4% 14%, 0% 31.7%, 3.4% 49.4%,
                           13.4% 65.9%, 29.3% 80%, 50% 90.8%, 74.1% 97.7%,
                           100% 100%, 0% 100%);
  }
  .pillars-circle[data-enhanced] .pillars-node-detail__body--why::before {
    float: right;
    shape-outside: polygon(100% 0%, 88.6% 0%, 96.6% 14%, 100% 31.7%,
                           96.6% 49.4%, 86.6% 65.9%, 70.7% 80%, 50% 90.8%,
                           25.9% 97.7%, 0% 100%, 100% 100%);
  }
  /* The disc arrives FIRST — it grows, takes its cornerstone's colour and its
     glass — and the words follow once it has. An ANIMATION, not a delayed
     transition: the detail is [hidden] while closed, and no transition runs
     across a display change, so a transition-delay was simply ignored and the
     text landed with the bloom. `both` holds the from-state through the delay. */
  @keyframes pillars-detail-in {
    from {
      opacity: 0;
      transform: translate(-50%, -50%) scale(0.96);
    }
    to {
      opacity: 1;
      transform: translate(-50%, -50%) scale(1);
    }
  }
  .pillars-circle[data-enhanced] .pillars-slot.is-open .pillars-node-detail {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
    animation: pillars-detail-in var(--motion-slow) var(--easing)
      calc(var(--pillars-bloom-dur) * 0.7) both;
  }
  @media (prefers-reduced-motion: reduce) {
    .pillars-circle[data-enhanced] .pillars-slot.is-open .pillars-node-detail {
      animation: none;
    }
  }
  /* Belt-and-braces: the open detail wins over a stale [hidden]. */
  .pillars-circle[data-enhanced] .pillars-slot.is-open .pillars-node-detail[hidden] {
    display: block;
  }

  /* Center cue — visible when nothing is open, fades out when a disc blooms. */
  .pillars-circle[data-enhanced] .pillars-center {
    display: block;
    position: absolute;
    top: 50%;
    left: 50%;
    width: min(15rem, 48%);
    margin: 0;
    text-align: center;
    pointer-events: none;
    opacity: 1;
    transform: translate(-50%, -50%);
    transition: opacity var(--motion-base) var(--easing);
    z-index: 1;
  }
  .pillars-circle[data-enhanced].is-active .pillars-center {
    opacity: 0;
  }
  .pillars-center__cue {
    font-family: var(--font-serif);
    font-style: italic;
    font-size: var(--type-size-body-base);
    line-height: var(--type-line-height-base);
    color: var(--ink-quiet);
  }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Value-prop zone — landing-root bridge between hero and portfolio.     │
   │ Outcome lede + 2-up Individuals / Businesses grid, each card a link   │
   │ to /solutions/<segment>/. Section envelope mirrors .enterprise-zone   │
   │ so vertical rhythm continues unbroken into the portfolio below.       │
   └────────────────────────────────────────────────────────────────────────┘ */
.value-prop-zone {
  padding: var(--space-5) 0 var(--space-6);
}
.value-prop-zone > * {
  width: 100%;
  max-width: var(--measure);
  margin-left: auto;
  margin-right: auto;
  padding-left: var(--space-3);
  padding-right: var(--space-3);
}
@media (min-width: 768px) {
  .value-prop-zone {
    padding: var(--space-6) 0 var(--space-7);
  }
  .value-prop-zone > * {
    padding-left: var(--space-4);
    padding-right: var(--space-4);
  }
}

.value-prop-heading {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-regular);
  font-size: var(--type-size-h1-base);
  line-height: var(--type-line-height-tight);
  letter-spacing: -0.02em;
  text-align: center;
  color: var(--ink-primary);
  margin: 0 auto var(--space-3);
}

.value-prop-intro {
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-base);
  color: var(--ink-secondary);
  text-align: center;
  margin: 0 auto var(--space-5);
  max-width: 70ch;
}
@media (min-width: 768px) {
  .value-prop-intro {
    font-size: var(--type-size-body-comfortable);
  }
}

.value-prop-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}
@media (min-width: 600px) {
  .value-prop-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-4);
  }
}

.value-prop-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  background: var(--surface-card);
  border: 1px solid var(--rule-quiet);
  border-radius: 6px;
  color: inherit;
  text-decoration: none;
  transition: border-color var(--motion-quick) var(--easing);
  min-height: 44px;
}
.value-prop-card:hover {
  border-color: var(--accent-primary);
}
.value-prop-card:focus-visible {
  border-color: var(--accent-primary);
  outline: 2px solid var(--ink-primary);
  outline-offset: 2px;
}

.value-prop-card-name {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-semibold);
  font-size: var(--type-size-body-comfortable);
  line-height: var(--type-line-height-tight);
  color: var(--ink-primary);
  margin: 0;
}

.value-prop-card-body {
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-base);
  color: var(--ink-secondary);
  margin: 0;
}

.value-prop-card-cta {
  font-family: var(--font-serif);
  font-size: var(--type-size-small-base);
  line-height: var(--type-line-height-base);
  color: var(--accent-primary);
  margin-top: auto;
}

/* Contact-us CTA at the bottom of the value-prop zone (landing). Inherits
   button shape from .cta-companion (tokens/hero.css). Centred under the grid. */
.value-prop-contact-cta {
  display: flex;
  justify-content: center;
  margin-top: var(--space-4);
}
@media (min-width: 768px) {
  .value-prop-contact-cta {
    margin-top: var(--space-5);
  }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Section + heading scaffolding                                          │
   └────────────────────────────────────────────────────────────────────────┘ */
.enterprise-zone {
  padding: var(--space-5) 0 var(--space-6);
}
.enterprise-zone > * {
  width: 100%;
  max-width: var(--measure);
  margin-left: auto;
  margin-right: auto;
  padding-left: var(--space-3);
  padding-right: var(--space-3);
}
@media (min-width: 768px) {
  .enterprise-zone {
    padding: var(--space-6) 0 var(--space-7);
  }
  .enterprise-zone > * {
    padding-left: var(--space-4);
    padding-right: var(--space-4);
  }
}

.portfolio-heading {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-regular);
  font-size: var(--type-size-h1-base);
  line-height: var(--type-line-height-tight);
  letter-spacing: -0.02em;
  text-align: center;
  color: var(--ink-primary);
  margin: 0 auto var(--space-3);
}

.portfolio-intro {
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-base);
  color: var(--ink-secondary);
  text-align: center;
  margin: 0 auto var(--space-5);
  max-width: 70ch;
}
@media (min-width: 768px) {
  .portfolio-intro {
    font-size: var(--type-size-body-comfortable);
  }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ B1 grid — single column → 2-up → 3-up → 5-up.                          │
   └────────────────────────────────────────────────────────────────────────┘ */
.portfolio-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}
@media (min-width: 600px) {
  .portfolio-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
  .portfolio-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 1280px) {
  .portfolio-grid { grid-template-columns: repeat(5, 1fr); gap: var(--space-2); }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Card composition                                                       │
   └────────────────────────────────────────────────────────────────────────┘ */
/* .portfolio-card is now an <a> — the whole card is the link to the
   per-product page. The redundant "Discover X →" inner CTA has been
   removed (was .portfolio-card-cta). Resetting link defaults (color,
   text-decoration) so the card reads as a static block; the existing
   border-colour hover/focus rules signal the affordance. */
.portfolio-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  background: var(--surface-card);
  border: 1px solid var(--rule-quiet);
  border-radius: 6px;
  color: inherit;
  text-decoration: none;
  transition: border-color var(--motion-quick) var(--easing);
  min-height: 44px; /* --target-standard touch-area floor per UX:1338 */
}
.portfolio-card:hover {
  border-color: var(--accent-primary);
}
.portfolio-card:focus-visible {
  border-color: var(--accent-primary);
  outline: 2px solid var(--ink-primary);
  outline-offset: 2px;
}

.portfolio-card-name {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-semibold);
  font-size: var(--type-size-body-comfortable);
  line-height: var(--type-line-height-tight);
  color: var(--ink-primary);
  margin: 0;
}

.portfolio-card-tagline {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-base);
  color: var(--ink-secondary);
  margin: 0;
}

.portfolio-card-body {
  font-family: var(--font-serif);
  font-size: var(--type-size-small-base);
  line-height: var(--type-line-height-base);
  color: var(--ink-secondary);
  margin: 0;
  max-width: 32ch;
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Hub-and-spoke variant (.portfolio-grid--hub) — Story 8.2.              │
   │ Mesh-centre topology (Tom-specced 2026-06-07):                         │
   │   • Workspace on top (apex, spans both cols, centered, wired).         │
   │   • TeamStore + Workforce Studio beneath it — the two feet Workspace   │
   │     stands on; surfaces inside the mesh, so NO trail.                  │
   │   • Enterprise Mesh at the centre (the wired hub).                     │
   │   • Local Assistant + Managed Hermes below (both wired).               │
   │   • Managed OpenClaw at the bottom — off-mesh by design (dashed, no    │
   │     trail; visual reinforcement of the boundary).                      │
   │                                                                       │
   │ Marching-ant trails ride between the Enterprise Mesh and Workspace /   │
   │ Local Assistant / Managed Hermes ONLY. DOM reading order is canonical  │
   │ (Workspace, TeamStore, Workforce Studio, Mesh, LA, MH, MOC); visual    │
   │ placement is [data-product]-driven so the accessible order is kept.    │
   │ Trail SVGs live as grid siblings in dedicated rows (3 + 5) so they     │
   │ never overlap card content. Narrow viewports collapse to one column in │
   │ source order and hide the trails — the wired-ness story becomes a      │
   │ stacked vertical read where horizontal geometry can't fit. Reduced     │
   │ motion freezes the ants via the tokens/motion-tokens.css global gate.  │
   └────────────────────────────────────────────────────────────────────────┘ */
.portfolio-grid--hub {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
  position: relative;
}
.portfolio-grid--hub > .portfolio-hub-trail {
  display: none;
}
.portfolio-grid--hub > .portfolio-hub-divider {
  display: none;
}

@media (min-width: 768px) {
  .portfolio-grid--hub {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-rows: auto auto var(--space-5) auto var(--space-5) auto auto auto;
    gap: var(--space-3);
    align-items: stretch;
    justify-items: stretch;
  }

  /* Row 1 — Workspace apex: spans both cols, centered + narrower, accent
     border so it reads as the lead surface sitting on the two feet. */
  .portfolio-grid--hub > [data-product="workspace"] {
    grid-column: 1 / span 2;
    grid-row: 1;
    justify-self: center;
    width: min(100%, 22rem);
    text-align: center;
    border-color: var(--accent-primary);
  }

  /* Row 2 — the two feet (surfaces inside the mesh; no trail). */
  .portfolio-grid--hub > [data-product="teamStore"]       { grid-column: 1; grid-row: 2; }
  .portfolio-grid--hub > [data-product="workforceStudio"] { grid-column: 2; grid-row: 2; }

  /* Row 3 — top trail: Workspace ↔ Mesh, single vertical line. */
  .portfolio-grid--hub > .portfolio-hub-trail--top {
    display: block;
    grid-column: 1 / span 2;
    grid-row: 3;
  }

  /* Row 4 — Enterprise Mesh centre hub: spans cols 1-2 (reads 2× wider),
     accent border + soft halo to read as "the centre". */
  .portfolio-grid--hub > [data-product="enterpriseMesh"] {
    grid-column: 1 / span 2;
    grid-row: 4;
    padding: var(--space-4);
    text-align: center;
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 1px var(--accent-primary);
  }

  /* Row 5 — bottom trail: Mesh ↔ Local Assistant + Mesh ↔ Managed Hermes. */
  .portfolio-grid--hub > .portfolio-hub-trail--bottom {
    display: block;
    grid-column: 1 / span 2;
    grid-row: 5;
  }

  /* Row 6 — LA · MH below the Mesh (both wired). */
  .portfolio-grid--hub > [data-product="localAssistant"]     { grid-column: 1; grid-row: 6; }
  .portfolio-grid--hub > [data-product="managedHermesAgent"] { grid-column: 2; grid-row: 6; }

  /* Row 7 — soft Stigmergy divider (Primitive 5) marking the off-mesh
     boundary; reads as a thought-pause rather than a hard cut. */
  .portfolio-grid--hub > .portfolio-hub-divider {
    display: block;
    grid-column: 1 / span 2;
    grid-row: 7;
    width: 100%;
    max-width: 200px;
    height: 40px;
    margin: var(--space-4) auto 0;
    color: var(--accent-primary);
  }

  /* Row 8 — Managed OpenClaw, off-mesh by design (dashed border, no trail). */
  .portfolio-grid--hub > [data-product="managedOpenclaw"] {
    grid-column: 1 / span 2;
    grid-row: 8;
    border-style: dashed;
  }
}

@media (min-width: 1024px) {
  .portfolio-grid--hub {
    gap: var(--space-4);
  }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Hub trail rendering — lines + marching ants.                          │
   │ Trail-line SVG stretches to its container with preserveAspectRatio    │
   │ ="none"; ants are rendered as separate fixed-pixel-size SVGs that    │
   │ animate via CSS keyframes on top/left percentages — that decouples    │
   │ ant glyph rendering from container-aspect distortion. Ants rotate so  │
   │ the smallest circle (head) leads in the direction of travel toward   │
   │ the Enterprise Mesh, not the abdomen (which is what rotate="auto" gets wrong on  │
   │ animateMotion since #stigmergy-ant's local +X points to the abdomen). │
   │ Reduced motion: tokens/motion-tokens.css zeroes animation-duration    │
   │ globally — ants freeze at the end of the keyframe (next to the Enterprise Mesh). │
   └────────────────────────────────────────────────────────────────────────┘ */
.portfolio-hub-trail {
  position: relative;
  width: 100%;
  height: 100%;
  color: var(--accent-primary);
}
.portfolio-hub-trail__lines {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.portfolio-hub-ant {
  position: absolute;
  width: 18px;
  height: 6.75px;
  margin-left: -9px;
  margin-top: -3.375px;
  color: var(--accent-primary);
  fill: currentcolor;
  transform-origin: center;
  pointer-events: none;
}

/* Top trail = one vertical line (Workspace ↔ Enterprise Mesh); bottom trail =
   two short diagonals (Mesh ↔ Local Assistant / Managed Hermes). The vertical
   ants travel straight down/up at left 50%; the diagonal ants travel between
   the Mesh bottom-centre (50%) and the cards at 25% / 75%. Required CSS
   rotation for the ant's natural -X facing is θ = motion_angle − 180°:

     • WS→AM (down):       -90°     AM→WS (up):          +90°
     • AM→LA (down-left):  -12°     LA→AM (up-right):   +168°
     • AM→MH (down-right): -168°    MH→AM (up-left):     +12°

   Each motion keyframe carries opacity 0→1→1→0 across 0%/15%/85%/100% so ants
   fade in at the source and out at the destination. Reverse ants carry a
   half-cycle animation-delay so the pair on each line never runs in lockstep.
   Only Workspace / Local Assistant / Managed Hermes are wired — TeamStore +
   Workforce Studio (surfaces inside the mesh) and off-mesh Managed OpenClaw
   carry no ants. */

/* WS → AM: straight down, Workspace to the Enterprise Mesh top-centre. */
.portfolio-hub-ant--ws-am {
  animation: portfolio-hub-ant-ws-am 3.5s linear infinite;
}
@keyframes portfolio-hub-ant-ws-am {
  0%       { top: 0%;   left: 50%; transform: rotate(-90deg); opacity: 0; }
  15%, 85% { opacity: 1; }
  100%     { top: 100%; left: 50%; transform: rotate(-90deg); opacity: 0; }
}

/* AM → WS: straight up. Reverse of WS→AM. */
.portfolio-hub-ant--am-ws {
  animation: portfolio-hub-ant-am-ws 3.5s linear infinite 1.75s;
}
@keyframes portfolio-hub-ant-am-ws {
  0%       { top: 100%; left: 50%; transform: rotate(90deg); opacity: 0; }
  15%, 85% { opacity: 1; }
  100%     { top: 0%;   left: 50%; transform: rotate(90deg); opacity: 0; }
}

/* AM → LA: down-left from Mesh bottom-centre to LA. */
.portfolio-hub-ant--am-la {
  animation: portfolio-hub-ant-am-la 3.5s linear infinite;
}
@keyframes portfolio-hub-ant-am-la {
  0%       { top: 0%;   left: 50%; transform: rotate(-12deg); opacity: 0; }
  15%, 85% { opacity: 1; }
  100%     { top: 100%; left: 25%; transform: rotate(-12deg); opacity: 0; }
}

/* AM → MH: down-right from Mesh bottom-centre to MH. */
.portfolio-hub-ant--am-mh {
  animation: portfolio-hub-ant-am-mh 3.5s linear infinite;
}
@keyframes portfolio-hub-ant-am-mh {
  0%       { top: 0%;   left: 50%; transform: rotate(-168deg); opacity: 0; }
  15%, 85% { opacity: 1; }
  100%     { top: 100%; left: 75%; transform: rotate(-168deg); opacity: 0; }
}

/* LA → AM: up-right along the AM↔LA trail. Reverse of AM→LA. */
.portfolio-hub-ant--la-am {
  animation: portfolio-hub-ant-la-am 3.5s linear infinite 1.75s;
}
@keyframes portfolio-hub-ant-la-am {
  0%       { top: 100%; left: 25%; transform: rotate(168deg); opacity: 0; }
  15%, 85% { opacity: 1; }
  100%     { top: 0%;   left: 50%; transform: rotate(168deg); opacity: 0; }
}

/* MH → AM: up-left along the AM↔MH trail. Reverse of AM→MH. */
.portfolio-hub-ant--mh-am {
  animation: portfolio-hub-ant-mh-am 3.5s linear infinite 1.75s;
}
@keyframes portfolio-hub-ant-mh-am {
  0%       { top: 100%; left: 75%; transform: rotate(12deg); opacity: 0; }
  15%, 85% { opacity: 1; }
  100%     { top: 0%;   left: 50%; transform: rotate(12deg); opacity: 0; }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Manifesto-entry CTA — quiet text-link (D7=a), narrative bridge.        │
   └────────────────────────────────────────────────────────────────────────┘ */
.manifesto-entry-cta {
  text-align: center;
  padding: var(--space-4) var(--space-3);
  margin: 0;
}
.manifesto-entry-cta a {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--type-size-body-base);
  color: var(--ink-secondary);
  text-decoration: underline;
  text-decoration-color: var(--rule-quiet);
  text-underline-offset: 0.25em;
  min-height: 44px; /* --target-standard */
  display: inline-block;
  padding: 0.5rem 0;
  transition:
    color var(--motion-quick) var(--easing),
    text-decoration-color var(--motion-quick) var(--easing);
}
.manifesto-entry-cta a:hover {
  color: var(--ink-primary);
  text-decoration-color: var(--accent-primary);
}
.manifesto-entry-cta a:focus-visible {
  outline: 2px solid var(--ink-primary);
  outline-offset: 2px;
  border-radius: 2px;
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ SMB zone — empty placeholder for Story 3.x value-prop content.        │
   │ At MVP-0 close-out the zone wrapper renders but holds no body content; │
   │ the data-aspect="smb" attribute lands the cascade for future content.  │
   └────────────────────────────────────────────────────────────────────────┘ */
.smb-zone {
  /* Intentionally empty at MVP-0 — Story 3.x adds value-prop scrollers. */
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ DPP pre-footer block — full Stigmergy aspect="dpp" cascade applies.   │
   │ Substrate dot-grid paints from tokens/layout-base.css :body; per-block │
   │ waypoint marks + trails + dividers land at Stigmergy primitive impl   │
   │ (defer to follow-up — Story 2.2 ships the structural shell + locked    │
   │ DPP voice copy + /partners link without the SVG primitives 2/3/4).      │
   └────────────────────────────────────────────────────────────────────────┘ */
.dpp-prefooter {
  padding: var(--space-6) var(--space-3);
  text-align: center;
  /* border-top removed 2026-05-13 — strong stigmergy divider injected by dpp-cta.eta partial */
  margin-top: var(--space-7);
}
@media (min-width: 768px) {
  .dpp-prefooter {
    padding: var(--space-7) var(--space-4);
  }
}

.dpp-heading {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-regular);
  font-size: var(--type-size-body-comfortable);
  line-height: var(--type-line-height-tight);
  color: var(--ink-primary);
  margin: 0 auto var(--space-2);
  max-width: 60ch;
}

.dpp-line {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-base);
  color: var(--ink-secondary);
  margin: 0 auto var(--space-3);
  max-width: 60ch;
}

.dpp-link {
  margin: 0;
}
.dpp-link a {
  font-family: var(--font-sans);
  font-size: var(--type-size-body-base);
  font-weight: var(--type-weight-medium);
  color: var(--accent-primary);
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 0.25em;
  min-height: 44px; /* --target-standard */
  display: inline-block;
  padding: 0.5rem 0;
  transition: text-decoration-color var(--motion-quick) var(--easing);
}
.dpp-link a:hover {
  text-decoration-color: var(--accent-primary);
}
.dpp-link a:focus-visible {
  outline: 2px solid var(--ink-primary);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Bottom CTA mirror — landing-page foot block that replaces the DPP
   pre-footer. Mirrors the hero CTA block visually (.hero-cta + .cta-companion
   own the buttons themselves); this rule only owns the section padding +
   top margin so the block sits with the same vertical rhythm the
   pre-footer used to carry. */
.landing-foot-cta {
  padding: var(--space-6) var(--space-3);
  text-align: center;
  margin-top: var(--space-7);
}
@media (min-width: 768px) {
  .landing-foot-cta {
    padding: var(--space-7) var(--space-4);
  }
}

/* ════════════════════════════════════════════════════════════════════════
   Story 2.11 — Mesh-as-core landing body (SCP-2026-06-06).
   §2+3 six cornerstones (thesis lede folded in SCP-2026-06-08, the section
   heading/intro/foundation strip; ring layout lives in the §3 block above) ·
   §5 platform / surfaces layout.
   The standalone §2 what-is band was removed when it merged into §3
   (SCP-2026-06-08); its heading/intro now reuse the .pillars-heading/.pillars-
   intro styles via the relocated landing.pillars.* locale keys. */


/* ┌── §5 Platform / surfaces — "The matrix to transform the way you work."
   │   Workspace lead card (full width) → Workforce Studio + TeamStore 2-up →
   │   wired-to-the-Mesh base → quiet adjacents row. ────────────────────────┐ */
.platform-zone {
  padding: var(--space-5) 0 var(--space-6);
}
.platform-zone > * {
  width: 100%;
  max-width: var(--measure);
  margin-left: auto;
  margin-right: auto;
  padding-left: var(--space-3);
  padding-right: var(--space-3);
}
@media (min-width: 768px) {
  .platform-zone {
    padding: var(--space-6) 0 var(--space-7);
  }
  .platform-zone > * {
    padding-left: var(--space-4);
    padding-right: var(--space-4);
  }
}
.platform-heading {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-regular);
  font-size: var(--type-size-h1-base);
  line-height: var(--type-line-height-tight);
  letter-spacing: -0.02em;
  text-align: center;
  color: var(--ink-primary);
  margin: 0 auto var(--space-3);
}
.platform-intro {
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-base);
  color: var(--ink-secondary);
  text-align: center;
  margin: 0 auto var(--space-5);
  max-width: 70ch;
}
@media (min-width: 768px) {
  .platform-intro {
    font-size: var(--type-size-body-comfortable);
  }
} /* Story 2.2 — the six-cornerstone ring geometry + dpp pre-footer (the 5-up grid went with the pages it served) */
/* ──────────────────────────────────────────────────────────────────────────
   glass.css — the liquid-glass material, landing-side.

   Same material the product shell wears (ui/components/src/styles/glass.css):
   a clear slab over the substrate — low tint so the dot-grid reads straight
   through, a crisp rim, a top specular, a defined drop. Ported rather than
   imported: the landing has no @myrmid/components dependency and never will.

   `.glass` owns the SURFACE only (fill, rim, shadow, lens). Shape, size and
   layout stay with the consumer.

   Matte is the floor, not a downgrade: prefers-reduced-transparency and
   Save-Data get an opaque card with the same geometry.
   ────────────────────────────────────────────────────────────────────────── */

:root {
  /* LIGHT — a white rim vanishes on a near-white page, so the pane edge is a
     faint dark refraction line and the drop shadow does the floating. */
  --color-glass-rim: rgb(0 0 0 / 0.13);
  --color-glass-specular: rgb(255 255 255 / 0.92);
  --color-glass-fill:
    linear-gradient(180deg, rgb(255 255 255 / 0.4) 0, rgb(255 255 255 / 0.14) 100%),
    color-mix(in srgb, var(--surface-page) 32%, transparent);
  --color-glass-shadow:
    inset 0 1px 0 var(--color-glass-specular),
    0 10px 30px -14px rgb(0 0 0 / 0.26);
  --color-glass-shadow-lift:
    inset 0 1px 0 var(--color-glass-specular),
    0 18px 42px -16px rgb(0 0 0 / 0.34);
  /* One glass on the landing: cards, chrome, popovers, Vompanion and the pillar bloom share it. */
  --glass-blur: 3.5px;
  --motion-glass-lens: blur(var(--glass-blur)) saturate(1.5);
  --motion-glass-lens-over: var(--motion-glass-lens);
  --glass-frost-lens: var(--motion-glass-lens);
  --glass-frost-fill: var(--color-glass-fill);
  --glass-frost-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.9),
    0 12px 32px -12px rgb(0 0 0 / 0.22);

  /* The accent a glass surface tints itself with. Cards override it per hue;
     everything else inherits the principal accent. */
  --color-glass-accent: var(--accent-primary);

  /* Bolder substrate dots: same field layout-base.css paints, redrawn one size
     up (the alias below raises the opacity; this raises the radius). */
  --color-grid-dot-r: 1.05px;
  --color-grid-dot-edge: 1.9px;

  /* Ink for the ants + trails drawn on the grid: the text ink, never an accent. */
  --color-ant-ink: var(--ink-primary);
}

/* The substrate dots are the text ink at a low mix: a texture behind the
   reading, not a pattern competing with it. */
:root {
  --grid-dot: color-mix(in srgb, var(--ink-primary) 16%, transparent);
}

:root[data-theme="dark"] {
  --grid-dot: color-mix(in srgb, var(--ink-primary) 14%, transparent);
  --color-glass-rim: rgb(255 255 255 / 0.16);
  --color-glass-specular: rgb(255 255 255 / 0.34);
  --color-glass-fill:
    linear-gradient(180deg, rgb(255 255 255 / 0.1) 0, rgb(255 255 255 / 0.03) 100%),
    color-mix(in srgb, var(--surface-page) 36%, transparent);
  --glass-frost-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.18),
    0 14px 36px -12px rgb(0 0 0 / 0.6);
  --color-glass-shadow:
    inset 0 1px 0 var(--color-glass-specular),
    0 10px 30px -14px rgb(0 0 0 / 0.6);
  --color-glass-shadow-lift:
    inset 0 1px 0 var(--color-glass-specular),
    0 18px 42px -16px rgb(0 0 0 / 0.7);
  --color-ant-ink: var(--ink-primary);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --grid-dot: color-mix(in srgb, var(--ink-primary) 14%, transparent);
    --color-glass-rim: rgb(255 255 255 / 0.16);
    --color-glass-specular: rgb(255 255 255 / 0.34);
    --color-glass-fill:
      linear-gradient(180deg, rgb(255 255 255 / 0.1) 0, rgb(255 255 255 / 0.03) 100%),
      color-mix(in srgb, var(--surface-page) 36%, transparent);
    --glass-frost-shadow:
      inset 0 1px 0 rgb(255 255 255 / 0.18),
      0 14px 36px -12px rgb(0 0 0 / 0.6);
    --color-glass-shadow:
      inset 0 1px 0 var(--color-glass-specular),
      0 10px 30px -14px rgb(0 0 0 / 0.6);
    --color-glass-shadow-lift:
      inset 0 1px 0 var(--color-glass-specular),
      0 18px 42px -16px rgb(0 0 0 / 0.7);
    --color-ant-ink: var(--ink-primary);
  }
}

/* A dot column lands exactly on the page centre: the printed dot sits at the
   tile CENTRE, so `50%` — which aligns the tile's midpoint with the viewport's
   — puts one on 50vw. grid-ants.js reads the same phase (`originX`). */
body {
  background-image: radial-gradient(
    circle,
    var(--grid-dot) var(--color-grid-dot-r),
    transparent var(--color-grid-dot-edge)
  );
  background-position: 50% 0;
}

.glass {
  position: relative;
  border: 1px solid var(--color-glass-rim);
  border-radius: 14px;
  background: var(--color-glass-fill);
  box-shadow: var(--color-glass-shadow);
  backdrop-filter: var(--motion-glass-lens);
  -webkit-backdrop-filter: var(--motion-glass-lens);
  isolation: isolate;
  transition:
    box-shadow var(--motion-base) var(--easing),
    border-color var(--motion-base) var(--easing),
    transform var(--motion-base) var(--easing);
}

/* content (1) < rim hairline (2) */
.glass > * {
  position: relative;
  z-index: 1;
}

/* The hairline along the top edge: ink at rest, copper on hover.
   Sits INSIDE the rim so it follows the radius. */
.glass {
  --glass-hairline: color-mix(in srgb, var(--ink-primary) 28%, transparent);
}
.glass:hover,
.glass:focus-within {
  --glass-hairline: color-mix(in srgb, var(--accent-primary) 70%, transparent);
}
.glass::after {
  content: "";
  position: absolute;
  inset: -1px -1px auto;
  height: 2px;
  border-radius: 14px 14px 0 0;
  background: linear-gradient(
    90deg,
    transparent,
    var(--glass-hairline) 22%,
    var(--glass-hairline) 78%,
    transparent
  );
  opacity: 0.85;
  z-index: 2;
  pointer-events: none;
}

/* ── One hover, everywhere ─────────────────────────────────────────────────
   Every glass card lifts a little and turns copper-framed in a copper glow,
   whatever its hue, and its title goes copper too. No tint in the fill. */
.glass:hover,
.glass:focus-within {
  --color-glass-accent: var(--accent-primary);
  border-color: var(--accent-primary);
  box-shadow:
    var(--color-glass-shadow-lift),
    0 10px 40px -14px color-mix(in srgb, var(--accent-glow) 55%, transparent);
  transform: translateY(-3px) scale(1.015);
}

@media (prefers-reduced-motion: reduce) {
  .glass:hover,
  .glass:focus-within {
    transform: none;
  }
}

/* ── Hue assignment ────────────────────────────────────────────────────────
   Every colour in the working palette is available to a surface; the
   principal accent stays the CTA / focus / brand colour and is what an
   un-hued surface inherits. A hue tints the rim line, the eyebrow and the
   card's own glyph — never body text, which stays solid ink on solid
   surface (Brand Visual System §Structural-only discipline). */
[data-hue="copper"] {
  --color-glass-accent: var(--accent-ink);
}
[data-hue="blue"] {
  --color-glass-accent: var(--accent-blue);
}
[data-hue="turquoise"] {
  --color-glass-accent: var(--accent-turquoise);
}
[data-hue="green"] {
  --color-glass-accent: var(--accent-green);
}
[data-hue="amber"] {
  --color-glass-accent: var(--accent-amber);
}
[data-hue="purple"] {
  --color-glass-accent: var(--accent-purple);
}
[data-hue="red"] {
  --color-glass-accent: var(--accent-red);
}
[data-hue="gold"] {
  --color-glass-accent: var(--accent-eu-gold);
}
[data-hue="eu"] {
  --color-glass-accent: var(--accent-eu-blue);
}

/* ── Matte floor ───────────────────────────────────────────────────────────
   No lens, opaque fill, same geometry. Forced where transparency is a
   stated accessibility preference or where bandwidth is metered. */
@media (prefers-reduced-transparency: reduce) {
  .glass {
    background: var(--surface-card);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    box-shadow: 0 6px 20px -14px rgb(0 0 0 / 0.3);
  }
}

/* Older engines with no backdrop-filter get the opaque card rather than a
   transparent pane with unreadable text over the grid. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass {
    background: var(--surface-card);
  }
} /* 2026-09 refresh — the liquid-glass material, ported from the product shell */
/* ──────────────────────────────────────────────────────────────────────────
   motion-reveal.css — sections build themselves on the way down, and the
   ants that walk the substrate grid.

   The hidden state applies ONLY under html[data-reveal-armed="on"], which the
   inline bootstrap in base-layout.eta sets when JS is running and the visitor
   has not asked for reduced motion. No JS, no bootstrap, no hiding.

   Order inside a group comes from --reveal-i on the element (0, 1, 2 …).
   ────────────────────────────────────────────────────────────────────────── */

:root {
  --motion-reveal-rise: 22px;
  --motion-reveal-step: 130ms;
  --motion-reveal-dur: 700ms;
}

html[data-reveal-armed="on"] [data-reveal] {
  opacity: 0;
  transform: translate3d(0, var(--motion-reveal-rise), 0);
  will-change: opacity, transform;
}

html[data-reveal-armed="on"] [data-reveal][data-revealed] {
  opacity: 1;
  transform: none;
  transition:
    opacity var(--motion-reveal-dur) var(--easing),
    transform var(--motion-reveal-dur) var(--easing);
  transition-delay: calc(var(--reveal-i, 0) * var(--motion-reveal-step));
  will-change: auto;
}

/* Piece by piece INSIDE a revealed block: the direct children of a revealed
   group each wait one step longer than the last, so a frame assembles
   (mark → heading → intro → lead) instead of appearing whole. Six steps is
   plenty for any block we have; beyond that they all land together, which is
   the right failure. */
html[data-reveal-armed="on"] [data-reveal] > * {
  opacity: 0;
  transform: translate3d(0, calc(var(--motion-reveal-rise) * 0.5), 0);
}
html[data-reveal-armed="on"] [data-reveal][data-revealed] > * {
  opacity: 1;
  transform: none;
  transition:
    opacity var(--motion-reveal-dur) var(--easing),
    transform var(--motion-reveal-dur) var(--easing);
}
html[data-reveal-armed="on"] [data-reveal][data-revealed] > *:nth-child(1) { transition-delay: 0ms; }
html[data-reveal-armed="on"] [data-reveal][data-revealed] > *:nth-child(2) { transition-delay: 130ms; }
html[data-reveal-armed="on"] [data-reveal][data-revealed] > *:nth-child(3) { transition-delay: 260ms; }
html[data-reveal-armed="on"] [data-reveal][data-revealed] > *:nth-child(4) { transition-delay: 390ms; }
html[data-reveal-armed="on"] [data-reveal][data-revealed] > *:nth-child(5) { transition-delay: 520ms; }
html[data-reveal-armed="on"] [data-reveal][data-revealed] > *:nth-child(n + 6) { transition-delay: 650ms; }

/* Sideways variants — used where a row reads left-to-right and a rise would
   fight the reading direction. */
html[data-reveal-armed="on"] [data-reveal="left"] {
  transform: translate3d(calc(-1 * var(--motion-reveal-rise)), 0, 0);
}
html[data-reveal-armed="on"] [data-reveal="right"] {
  transform: translate3d(var(--motion-reveal-rise), 0, 0);
}
html[data-reveal-armed="on"] [data-reveal="scale"] {
  transform: scale(0.965);
}

/* The global reduced-motion gate zeroes the duration tokens, but the armed
   attribute is never set under reduced motion anyway — this is the belt to
   that braces, for a preference flipped after load. */
@media (prefers-reduced-motion: reduce) {
  html[data-reveal-armed="on"] [data-reveal],
  html[data-reveal-armed="on"] [data-reveal] > * {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ── Ant canvas ────────────────────────────────────────────────────────────
   The colony's one canvas (public/js/grid-ants.js): fixed to the viewport,
   painted at z-index -1 so it sits above the body's substrate grid and below
   every block — a glass card's backdrop blur therefore includes the ants
   passing beneath it. The script owns everything else. */
/* A canvas is a REPLACED element: `inset: 0` does NOT stretch it — it keeps
   its intrinsic (backing-store) size, so on a dpr-2 phone the colony drew at
   twice scale and slid off the printed grid. The box is sized explicitly. */
.ant-canvas {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  pointer-events: none;
} /* 2026-09 refresh — scroll-build reveal states + the ant field */
/* ──────────────────────────────────────────────────────────────────────────
   manifesto.css — Story 2.3 Manifesto Surface consumer rules.

   Lives under tokens/** so the four-prefix stylelint regex exemption
   (Story 1.1) covers ad-hoc class names (per portfolio.css/hero.css
   precedent).

   Layout discipline (Story 2.3 ACs):
     • Measure: the frame and the chapter panes ride --page-measure like
       every other surface; the reading measure sits on the article (88ch,
       centred in its pane) and on the standalone prose (70ch, centred).
     • Native scroll preserved (no scroll-jacking — html { scroll-behavior:
       smooth } at layout-base.css is the global, no JS interception).
     • Editorial typeface (Charter, --font-serif) with --type-line-height-
       loose for generous reading rhythm.

   Stigmergy primitives (UX §Stigmergy Texture System Primitives 2–4):
     • .manifesto-waypoint — Primitive 2 left-edge mark, 7×22px envelope,
       absolutely positioned at the article's left edge. Symbol geometry +
       --waypoint-fill resolution live in templates/partials/svg-sprite.eta;
       opacity 0.85 applied here at the consumer per UX spec.
     • .manifesto-article::before — Primitive 3 pheromone trail. 1px hairline
       CSS gradient descending through the article, fading to transparent
       at 92% (linear-gradient direction + stop). Painted via ::before so
       no extra DOM, fully tokenized via --trail-stroke.
     • section boundaries use the site's own `.divider` (surfaces.css), which
       replaced the 240×38 Primitive 4 sprite row in the 2026-09 refresh
       symbol centred between sections (viewBox grew from 240×36 → 240×38
       per SCP-2026-05-13 to admit full thorax + abdomen rows). Geometry +
       colour-token resolution live in svg-sprite.eta.

   Application rules (UX-DR1 + UX §Stigmergy §Application Rules):
     • NO pheromone-trail underline on body type
     • NO dot-pattern fill on headings
     • NO metallic finish on type
     • Type fills stay solid-on-solid for AA + i18n integrity.

   Reduced-motion: nothing here animates (the surface is static per Story
   2.3 — scroll-cue ant + cursor trail belong to Stories 2.8 + 2.9 on
   section-index surfaces only). Hover transitions on the DPP CTA inherit
   --motion-quick which the global escape hatch zeroes under reduced.
   ────────────────────────────────────────────────────────────────────────── */

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Article container — outer wrapper for the manifesto document.          │
   └────────────────────────────────────────────────────────────────────────┘ */
.manifesto {
  width: 100%;
  max-width: var(--page-measure);
  margin: 0 auto;
  padding: var(--space-5) var(--space-3) var(--space-7);
  color: var(--ink-primary);
  font-family: var(--font-serif);
}
@media (min-width: 768px) {
  .manifesto {
    padding: var(--space-6) var(--space-4) var(--space-7);
  }
}
/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Page H1 — restrained editorial title; document anchor for AT.          │
   └────────────────────────────────────────────────────────────────────────┘ */
/* The 2026-09 refresh: the document opens the way every other surface does —
   a centred display line over a centred lead, on the same scale. */
.manifesto-h1 {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-regular);
  font-size: clamp(2.2rem, 6.4vw, 3.4rem);
  line-height: var(--type-line-height-tight);
  letter-spacing: -0.02em;
  color: var(--ink-primary);
  text-align: center;
  margin: 0 0 var(--space-4);
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Opening / closing / brand-mark — flowing prose, ≤70ch measure.         │
   └────────────────────────────────────────────────────────────────────────┘ */
.manifesto-opening,
.manifesto-closing,
.manifesto-brand-mark,
.manifesto-team-mark {
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-loose);
  color: var(--ink-primary);
  max-width: 70ch;
  margin: 0 auto var(--space-5);
}
@media (min-width: 768px) {
  .manifesto-opening,
  .manifesto-closing,
  .manifesto-brand-mark,
  .manifesto-team-mark {
    font-size: var(--type-size-body-comfortable);
  }
}
/* The two brand lines close the document: centred, in the accent. */
.manifesto-brand-mark,
.manifesto-team-mark {
  color: var(--accent-ink);
  text-align: center;
}
.manifesto-brand-mark strong,
.manifesto-team-mark strong {
  font-weight: var(--type-weight-semibold);
  color: inherit;
}
/* The opening line carries the document, so it gets the lead's voice. */
.manifesto-opening {
  font-size: clamp(1.22rem, 3.1vw, 1.72rem);
  line-height: 1.4;
  text-align: center;
  font-style: italic;
  color: var(--ink-secondary);
  margin-bottom: var(--space-6);
}
.manifesto-closing {
  margin-top: var(--space-6);
  text-align: center;
  margin-bottom: var(--space-3);
  font-style: italic;
}
.manifesto-brand-mark {
  /* Tight, so the team mark sits as a near sibling. */
  margin-bottom: var(--space-2);
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Section group — Three Truths / Refusals / Commitments / Purpose.       │
   └────────────────────────────────────────────────────────────────────────┘ */
/* One pane per chapter. The four movements are the document's structure, so
   they are what gets a surface; an article is a statement inside its chapter,
   not a card of its own. Written out rather than borrowed from .glass because
   the articles' ::before is the pheromone trail. */
.manifesto-group {
  position: relative;
  margin: var(--space-5) 0;
  padding: var(--space-5) var(--space-4) var(--space-4);
  background: var(--color-glass-fill);
  border: 1px solid var(--color-glass-rim);
  border-radius: 14px;
  box-shadow: var(--color-glass-shadow);
  backdrop-filter: var(--motion-glass-lens);
  -webkit-backdrop-filter: var(--motion-glass-lens);
}

/* The accent hairline along the chapter's top edge, in its own hue. */
.manifesto-group::after {
  content: "";
  position: absolute;
  inset: -1px -1px auto;
  height: 2px;
  border-radius: 14px 14px 0 0;
  background: linear-gradient(
    90deg,
    transparent,
    color-mix(in srgb, var(--color-glass-accent) 70%, transparent) 22%,
    color-mix(in srgb, var(--color-glass-accent) 70%, transparent) 78%,
    transparent
  );
  opacity: 0.85;
  pointer-events: none;
}

@media (min-width: 768px) {
  .manifesto-group {
    padding: var(--space-6) var(--space-5) var(--space-5);
  }
}

@media (prefers-reduced-transparency: reduce) {
  .manifesto-group {
    background: var(--surface-card);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}

/* A group opens like a section of the site: centred, in the accent, with air
   above it — not a small label pinned to the left margin. */
.manifesto-group-heading {
  font-family: var(--font-sans);
  font-weight: var(--type-weight-semibold);
  font-size: var(--type-size-meta-base);
  line-height: var(--type-line-height-tight);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--color-glass-accent);
  text-align: center;
  margin: 0 auto var(--space-5);
  max-width: 70ch;
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Manifesto article — heading + body block. Hosts Primitive 2 waypoint   │
   │ (absolute) + Primitive 3 pheromone trail (::before hairline gradient). │
   └────────────────────────────────────────────────────────────────────────┘ */
/* Room between articles: the document reads as a sequence of statements the
   way the rest of the site reads as a deck, rather than as a wall of prose. */
.manifesto-article {
  position: relative;
  /* The chapter pane rides the page measure; the statement inside keeps a
     reading measure and centres in it, the way the site's prose does. */
  max-width: 88ch;
  margin: 0 auto var(--space-5);
  /* Symmetric: the left padding is the waypoint column's room, and without
     the matching right padding every article's text mass sat 16px right of
     the chapter card's centre — measured across all fifteen. */
  padding-inline: var(--space-4);
  transition: opacity var(--motion-slow) var(--easing);
}

.manifesto-article:last-child {
  margin-bottom: 0;
}

/* ── A colour per group ──────────────────────────────────────────────────
   Four movements, four hues from the working palette, carried by the pane's
   rim, its hairline, its numeral and its trail. The principal accent stays
   on the group headings, which are the document's own voice. */
.manifesto-group--truths {
  --color-glass-accent: var(--accent-blue);
}
.manifesto-group--refusals {
  --color-glass-accent: var(--accent-red);
}
.manifesto-group--commitments {
  --color-glass-accent: var(--accent-green);
}
.manifesto-group--purpose {
  --color-glass-accent: var(--accent-eu-gold);
}

/* ── Arriving ────────────────────────────────────────────────────────────
   The pane rises and comes up to full as you reach it — it never disappears.
   A manifesto is a document: hiding a statement until the reader has scrolled
   to it costs them the read, and leaves the visual baseline asserting a blank
   page. So the from-state is DIM, not gone, and the words inside never stage
   at all. */
html[data-reveal-armed="on"] .manifesto-article[data-reveal] {
  opacity: 0.34;
  transform: translate3d(0, 10px, 0);
}
html[data-reveal-armed="on"] .manifesto-article[data-reveal] > * {
  opacity: 1;
  transform: none;
}
html[data-reveal-armed="on"] .manifesto-article[data-reveal][data-revealed] {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  html[data-reveal-armed="on"] .manifesto-article[data-reveal] {
    opacity: 1;
    transform: none;
  }
}

/* Primitive 3 — pheromone trail. 1px hairline gradient descending through
   the article, fading to transparent at 92% per UX spec line 1004. Painted
   via ::before, positioned at the same x as the waypoint mark so the trail
   appears to emanate from the column of dots. */
.manifesto-article::before {
  content: "";
  position: absolute;
  top: var(--space-3);
  left: 3px;
  width: 1px;
  height: calc(100% - var(--space-3));
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, var(--color-glass-accent) 70%, transparent),
    transparent 92%
  );
  pointer-events: none;
}

.manifesto-article-heading {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-semibold);
  font-size: var(--type-size-body-comfortable);
  line-height: var(--type-line-height-tight);
  color: var(--ink-primary);
  margin: 0 0 var(--space-2);
}
@media (min-width: 768px) {
  .manifesto-article-heading {
    font-size: 1.375rem;
  }
}

.manifesto-numeral {
  display: inline-block;
  margin-right: 0.4em;
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: var(--type-weight-regular);
  color: var(--color-glass-accent);
  letter-spacing: 0.02em;
}

.manifesto-article-body {
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-loose);
  color: var(--ink-primary);
  margin: 0;
}
@media (min-width: 768px) {
  .manifesto-article-body {
    font-size: var(--type-size-body-comfortable);
  }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Primitive 2 — waypoint mark. 7×22 envelope, absolutely positioned at   │
   │ the article's left edge so the column of dots aligns with the trail.   │
   └────────────────────────────────────────────────────────────────────────┘ */
.manifesto-waypoint {
  position: absolute;
  top: 0;
  left: 0;
  width: 7px;
  height: 22px;
  opacity: 0.85;
  color: var(--color-glass-accent);
  /* A body carries a charge: a hair of glow, never a bare dot. */
  filter: drop-shadow(0 0 2px color-mix(in srgb, var(--waypoint-fill) 26%, transparent));
  pointer-events: none;
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ DPP CTA — quiet footer-zone text-link (Story 2.3 AC: never a button,   │
   │ never a hero. Pattern matches portfolio.css .manifesto-entry-cta.).    │
   └────────────────────────────────────────────────────────────────────────┘ */
.manifesto-dpp-cta {
  text-align: center;
  margin: var(--space-6) 0 0;
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  font-style: italic;
  color: var(--ink-secondary);
}
.manifesto-dpp-cta a {
  color: var(--ink-secondary);
  text-decoration: underline;
  text-decoration-color: var(--rule-quiet);
  text-underline-offset: 0.25em;
  min-height: 44px; /* --target-standard touch-area floor */
  display: inline-block;
  padding: 0.5rem 0;
  transition:
    color var(--motion-quick) var(--easing),
    text-decoration-color var(--motion-quick) var(--easing);
}
.manifesto-dpp-cta a:hover {
  color: var(--ink-primary);
  text-decoration-color: var(--accent-primary);
}
.manifesto-dpp-cta a:focus-visible {
  outline: 2px solid var(--ink-primary);
  outline-offset: 2px;
  border-radius: 2px;
} /* Story 2.3 — manifesto surface (≤70ch measure, editorial rhythm, Stigmergy Primitives 2–4 consumer rules) */
/* ──────────────────────────────────────────────────────────────────────────
   nav.css — Story 2.4 Section Navigation consumer rules.

   Lives under tokens/** so the four-prefix stylelint regex exemption
   (Story 1.1) covers ad-hoc class names (per portfolio.css/hero.css/
   manifesto.css precedent).

   UX spec §Section Nav (codified 2026-05-09):
     • 5 top-level entries — Products / Solutions / Manifesto / About / Pricing
     • Products + About = standard dropdowns (<details><summary>)
     • Solutions = mega-menu (<details><summary> + grid layout)
     • Manifesto + Pricing = flat <a> links
     • Sticky header on scroll, no CLS
     • Active state via aria-current="page" (server-side via surface map)
     • Desktop ≥1024px: inline 5 entries; sub-menus open as dropdowns
     • Tablet 768–1024px: inline 5 entries (tight but holds per spec line 2146)
     • Mobile <768px: hamburger toggle reveals primary nav as a sheet;
       sub-menus expand inline within the sheet (native <details> behaviour)

   The hamburger button + sheet pattern uses pure-CSS toggle via the
   data-nav-open attribute set by JS (public/js/nav-toggle.js). When JS
   fails to load, the .nav-toggle button stays hidden on desktop (no harm)
   and the primary nav stays visible on mobile (no JS-required regression).

   Reduced-motion: dropdown open/close transitions inherit --motion-quick
   which the global escape hatch zeroes under reduced. Hamburger sheet
   slide-in is instant under reduced.
   ────────────────────────────────────────────────────────────────────────── */

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Hamburger toggle button — visible on mobile only.                      │
   └────────────────────────────────────────────────────────────────────────┘ */
.nav-toggle {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  width: 44px;
  height: 44px;
  padding: 10px;
  background: transparent;
  border: 1px solid var(--rule-quiet);
  border-radius: 4px;
  cursor: pointer;
  color: var(--ink-primary);
}
.nav-toggle:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}
.nav-toggle-bar {
  display: block;
  height: 2px;
  width: 100%;
  background: currentColor;
  border-radius: 1px;
}
@media (min-width: 768px) {
  .nav-toggle {
    display: none;
  }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Primary nav — base structure.                                          │
   ├────────────────────────────────────────────────────────────────────────┤
   │ Mobile (<768px): hidden by default; revealed when the body carries     │
   │ data-nav-open="true" (set by nav-toggle.js). Sheet-style: full-width   │
   │ panel below the header.                                                │
   │ Tablet+ (≥768px): inline horizontal row, always visible.               │
   └────────────────────────────────────────────────────────────────────────┘ */
.nav--primary {
  display: none;
}
body[data-nav-open="true"] .nav--primary {
  display: block;
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  padding: var(--space-3);
  background: var(--glass-frost-fill);
  backdrop-filter: var(--glass-frost-lens);
  -webkit-backdrop-filter: var(--glass-frost-lens);
  border-bottom: 1px solid var(--rule-quiet);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.04);
  z-index: 50;
}
@media (min-width: 768px) {
  .nav--primary {
    display: block;
    position: static;
    padding: 0;
    background: transparent;
    border: none;
    box-shadow: none;
  }
}

.nav-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
@media (min-width: 768px) {
  .nav-list {
    flex-direction: row;
    align-items: center;
    gap: var(--space-3);
  }
}

.nav-item {
  position: relative;
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Flat top-level entries (Manifesto, Pricing) + summary buttons.         │
   │ Shared visual language: editorial sans, quiet underline on hover/      │
   │ focus, accent underline when aria-current="page".                      │
   └────────────────────────────────────────────────────────────────────────┘ */
.nav-link,
.nav-summary {
  display: inline-flex;
  align-items: center;
  gap: 0.25em;
  padding: var(--space-1);
  min-height: 44px; /* --target-standard touch-area floor */
  font-family: var(--font-sans);
  font-size: var(--type-size-small-base);
  font-weight: var(--type-weight-medium);
  color: var(--ink-primary);
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 0.35em;
  cursor: pointer;
  background: transparent;
  border: none;
  list-style: none;
  transition: text-decoration-color var(--motion-quick) var(--easing);
}
.nav-link:hover,
.nav-summary:hover,
.nav-link:focus-visible,
.nav-summary:focus-visible {
  text-decoration-color: var(--accent-primary);
}
.nav-link:focus-visible,
.nav-summary:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
  border-radius: 2px;
}
.nav-link[aria-current="page"],
.nav-summary[aria-current="page"] {
  text-decoration-color: var(--accent-primary);
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ The one ACTION in the header: "Get started" wears the landing's CTA    │
   │ recipe (hero.css `.cta-companion`) at header scale — a filled accent pill  │
   │ that keeps the header's height, pushed to the row's end on desktop and │
   │ full-width at the foot of the mobile sheet. Rendered only where the    │
   │ org-request flow is on (`ORG_REQUEST_CTA_ENABLED`).                    │
   └────────────────────────────────────────────────────────────────────────┘ */
.nav-link.nav-link--cta {
  justify-content: center;
  min-height: 40px;
  padding: 0.5rem 1rem;
  background: var(--cta-face);
  color: var(--cta-label);
  border: 1px solid var(--cta-face);
  border-radius: 6px;
  text-decoration: none;
  transition:
    background-color var(--motion-quick) var(--easing),
    border-color var(--motion-quick) var(--easing);
}
.nav-link.nav-link--cta:hover,
.nav-link.nav-link--cta:focus-visible {
  background: var(--accent-primary);
  border-color: var(--accent-primary);
  color: var(--ink-on-accent);
  box-shadow: var(--hover-glow);
  text-decoration: none;
}
.nav-link.nav-link--cta:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
  border-radius: 6px;
}
.nav-link.nav-link--cta[aria-current="page"] {
  text-decoration: none;
}
.nav-item--cta {
  display: flex;
}
.nav-item--cta .nav-link--cta {
  flex: 1;
}
@media (min-width: 768px) {
  .nav-item--cta {
    margin-inline-start: var(--space-3);
  }
  .nav-item--cta .nav-link--cta {
    flex: none;
  }
}

/* Suppress the native <details><summary> disclosure triangle. */
.nav-summary::-webkit-details-marker { display: none; }
.nav-summary { list-style: none; }
.nav-summary::after {
  content: "";
  display: inline-block;
  width: 0.5em;
  height: 0.5em;
  margin-left: 0.25em;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translateY(-2px);
  transition: transform var(--motion-quick) var(--easing);
}
.nav-details[open] > .nav-summary::after {
  transform: rotate(225deg) translateY(2px);
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Standard dropdown (Products, About) sub-menu.                          │
   │ Mobile: in-flow expansion below the summary.                           │
   │ Desktop: absolute-positioned panel anchored to the parent <li>.        │
   └────────────────────────────────────────────────────────────────────────┘ */
.nav-submenu {
  list-style: none;
  margin: var(--space-1) 0 0;
  padding: var(--space-2);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
@media (min-width: 768px) {
  .nav-details:not(.nav-details--mega) > .nav-submenu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    min-width: 200px;
    background: var(--glass-frost-fill);
    backdrop-filter: var(--glass-frost-lens);
    -webkit-backdrop-filter: var(--glass-frost-lens);
    border: 1px solid var(--rule-quiet);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06);
    z-index: 40;
  }
}

/* Story 3.32: group label inside the Products dropdown (3-group FR8j structure).
   Non-interactive; reuses .nav-mega-heading typography. First group sits flush
   under the "All products" link, later groups get a little breathing room. */
.nav-submenu-group {
  padding: var(--space-1) 0.5rem 0;
}
.nav-submenu-group + .nav-submenu-group,
.nav-submenu a + .nav-submenu-group {
  margin-top: var(--space-1);
}
.nav-submenu-group .nav-mega-heading {
  margin: 0;
}

.nav-submenu a,
.nav-mega-col a {
  display: block;
  padding: 0.5rem 0.5rem;
  min-height: 44px;
  font-family: var(--font-sans);
  font-size: var(--type-size-small-base);
  color: var(--ink-secondary);
  text-decoration: none;
  border-radius: 6px;
  transition:
    color var(--motion-quick) var(--easing),
    background-color var(--motion-quick) var(--easing);
}
.nav-submenu a:hover,
.nav-mega-col a:hover {
  color: var(--ink-primary);
  background: color-mix(in srgb, var(--ink-primary) 7%, transparent);
}
.nav-submenu a:focus-visible,
.nav-mega-col a:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}
.nav-submenu a[aria-current="page"],
.nav-mega-col a[aria-current="page"] {
  color: var(--ink-primary);
  font-weight: var(--type-weight-medium);
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Mega-menu (Solutions): two-axis grouping (by-size + by-sector).        │
   │ Mobile: stacked groups in flow; tree-like inside the sheet.            │
   │ Desktop: All solutions on a full-width row; 2-col groups below.        │
   └────────────────────────────────────────────────────────────────────────┘ */
.nav-mega {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-1);
  padding: var(--space-2);
}
@media (min-width: 768px) {
  .nav-details--mega > .nav-mega {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    display: grid;
    grid-template-columns: minmax(180px, 1fr) minmax(220px, 1fr);
    gap: var(--space-3);
    min-width: 480px;
    padding: var(--space-3);
    background: var(--glass-frost-fill);
    backdrop-filter: var(--glass-frost-lens);
    -webkit-backdrop-filter: var(--glass-frost-lens);
    border: 1px solid var(--rule-quiet);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06);
    z-index: 40;
  }
  /* All solutions leads the panel on its own full-width row; the by-size
     and by-sector groups flow into the two columns directly below it. */
  .nav-details--mega > .nav-mega > .nav-mega-col {
    grid-column: 1 / -1;
  }
}

.nav-mega-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.nav-mega-heading {
  margin: 0 0 var(--space-1);
  font-family: var(--font-sans);
  font-size: var(--type-size-eyebrow-base);
  font-weight: var(--type-weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-quiet);
}
.nav-mega-col {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Body lock when mobile sheet is open — prevents background scroll.      │
   └────────────────────────────────────────────────────────────────────────┘ */
@media (max-width: 767.98px) {
  body[data-nav-open="true"] {
    overflow: hidden;
  }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Locale switcher (Story 2.5) — globe icon + current-locale dropdown.    │
   │ Sits inside .nav--utility between primary nav and mode toggle. Uses    │
   │ <details><summary> so the same nav-toggle.js outside-click + Esc       │
   │ handlers close it for free.                                            │
   └────────────────────────────────────────────────────────────────────────┘ */
.locale-switcher,
.ask-ai {
  position: relative;
}
.locale-switcher-summary,
.ask-ai-summary {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.375rem 0.625rem;
  min-height: 32px;
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  font-weight: var(--type-weight-medium);
  color: var(--ink-secondary);
  background: transparent;
  border: 1px solid var(--rule-quiet);
  border-radius: 6px;
  cursor: pointer;
  list-style: none;
  transition:
    border-color var(--motion-quick) var(--easing),
    color var(--motion-quick) var(--easing);
}
.locale-switcher-summary::-webkit-details-marker,
.ask-ai-summary::-webkit-details-marker { display: none; }
.locale-switcher-summary:hover,
.ask-ai-summary:hover {
  color: var(--accent-ink);
  border-color: var(--accent-primary);
  box-shadow: var(--hover-glow);
}
.locale-switcher-summary:focus-visible,
.ask-ai-summary:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}
.locale-switcher-icon,
.ask-ai-icon {
  flex: 0 0 16px;
  color: currentColor;
}
.locale-switcher-label,
.ask-ai-label {
  letter-spacing: 0.04em;
}

.locale-switcher-menu,
.ask-ai-menu {
  list-style: none;
  margin: 4px 0 0;
  padding: var(--space-1);
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  min-width: 160px;
  background: var(--glass-frost-fill);
  backdrop-filter: var(--glass-frost-lens);
  -webkit-backdrop-filter: var(--glass-frost-lens);
  border: 1px solid var(--rule-quiet);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06);
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.locale-switcher-menu a,
.ask-ai-menu a {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.5rem 0.625rem;
  min-height: 44px; /* --target-standard touch-area floor */
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  color: var(--ink-secondary);
  text-decoration: none;
  border-radius: 6px;
  transition:
    color var(--motion-quick) var(--easing),
    background-color var(--motion-quick) var(--easing);
}
.locale-switcher-menu a:hover,
.ask-ai-menu a:hover {
  color: var(--ink-primary);
  background: color-mix(in srgb, var(--ink-primary) 7%, transparent);
}
.locale-switcher-menu a:focus-visible,
.ask-ai-menu a:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}
.locale-switcher-menu-short,
.ask-ai-name {
  font-weight: var(--type-weight-semibold);
  letter-spacing: 0.04em;
  color: var(--ink-primary);
  min-width: 1.5rem;
}
.locale-switcher-menu-name {
  color: var(--ink-secondary);
}

/* Ask AI (MF-567) — shares the locale switcher rules above; icon-only on a phone to fit the header. */
.nav--utility {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}
.ask-ai-menu {
  min-width: 200px;
}
.ask-ai-mark {
  flex: 0 0 18px;
  width: 18px;
  height: 18px;
  color: var(--ink-primary);
}
.ask-ai-menu a {
  align-items: center;
}
/* The two button entries dress like the links: Myrmid (Vompanion, on this page) and Prompt. */
.ask-ai-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  min-height: 44px;
  padding: 0.5rem 0.625rem;
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  text-align: left;
  color: var(--ink-secondary);
  background: transparent;
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}
.ask-ai-item:hover {
  color: var(--ink-primary);
  background: color-mix(in srgb, var(--ink-primary) 7%, transparent);
}
.ask-ai-item:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}
.ask-ai-copy {
  border-top: 1px solid var(--rule-quiet);
  border-radius: 0 0 6px 6px;
}
.ask-ai-pill {
  position: fixed;
  top: 76px;
  left: 50%;
  z-index: 60;
  padding: 0.5rem 1rem;
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  font-weight: var(--type-weight-medium);
  color: var(--ink-primary);
  background: var(--glass-frost-fill);
  backdrop-filter: var(--glass-frost-lens);
  -webkit-backdrop-filter: var(--glass-frost-lens);
  border: 1px solid var(--rule-quiet);
  border-radius: 999px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -6px);
  transition:
    opacity var(--motion-quick) var(--easing),
    transform var(--motion-quick) var(--easing);
}
.ask-ai-pill--shown {
  opacity: 1;
  transform: translate(-50%, 0);
}
@media (prefers-reduced-motion: reduce) {
  .ask-ai-pill {
    transition: none;
  }
}
@media (max-width: 479.98px) {
  .ask-ai-label {
    display: none;
  }
}

/* Frosted popovers: on reduced transparency, or with no backdrop-filter, the
   opaque card again so text never sits on an unblurred page. */
@media (prefers-reduced-transparency: reduce) {
  .nav--primary,
  .nav-submenu,
  .nav-mega,
  .ask-ai-menu,
  .locale-switcher-menu,
  .ask-ai-pill {
    background: var(--surface-card) !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
  }
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nav--primary,
  .nav-submenu,
  .nav-mega,
  .ask-ai-menu,
  .locale-switcher-menu,
  .ask-ai-pill {
    background: var(--surface-card) !important;
  }
} /* Story 2.4 — section navigation (5-entry top-level + sub-menus + mega-menu + hamburger + sticky header) */
/* ──────────────────────────────────────────────────────────────────────────
   consent.css — Story 2.6 Cookie Consent Interface consumer rules.

   Lives under tokens/** so the four-prefix stylelint regex exemption
   (Story 1.1) covers ad-hoc class names (per nav.css/manifesto.css
   precedent).

   COMPOSITION:
     • Dialog: sticky bottom banner. Full-width on mobile, max-width on
       desktop (anchored bottom-right). High z-index so it sits above
       the sticky header (z-index 30) and any future modal but below
       the Vompanion widget.
     • Inner: card-style container with --surface-card background, 1px
       border, generous padding for readable copy.
     • Options list: each opt-in feature is a row with a checkbox +
       label + description. Default unchecked (opt-in is active).
     • Actions row: [Save] + [Reject all] buttons, equal weight + size
       per the no-dark-pattern AC. Save uses the accent fill to mark
       the primary affordance; Reject all uses the surface fill with
       the same border. Source order is Save → Reject all.
     • Persistent trigger: small bottom-left button visible whenever the
       dialog is hidden (i.e. visitor has made a choice). Allows
       withdrawal at any time per the AC.

   ACCESSIBILITY:
     • Reduced-motion: no entrance animation; the dialog appears/disappears
       instantly. The global motion escape hatch handles any inherited
       transition tokens.
     • Focus: consent.js sets focus to the first checkbox when the
       dialog opens. CSS provides visible focus-visible outlines on
       both the checkboxes and the action buttons.
     • Tab order: source order is checkbox(es) → privacy link → Save →
       Reject all. The user must engage with the choices before reaching
       the primary action.

   STATE TOGGLE:
     • Dialog visibility is driven by the `hidden` HTML attribute
       (consent.js writes / reads it). CSS [hidden] selector auto-hides.
     • Trigger visibility is driven by sibling-state: when the dialog is
       NOT hidden (i.e. open), the trigger hides — no double affordance.
   ────────────────────────────────────────────────────────────────────────── */

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Dialog — sticky bottom banner.                                         │
   └────────────────────────────────────────────────────────────────────────┘ */
.consent-dialog {
  position: fixed;
  left: var(--space-3);
  right: var(--space-3);
  bottom: var(--space-3);
  z-index: 60;
  display: flex;
  justify-content: center;
}
/* The HTML `hidden` attribute defaults to `display: none`, but our
   `display: flex` rule above overrides it. Re-pin `display: none` for
   the hidden state so consent.js's hidden=true actually hides the dialog. */
.consent-dialog[hidden] {
  display: none;
}
@media (min-width: 768px) {
  .consent-dialog {
    left: auto;
    right: var(--space-4);
    bottom: var(--space-4);
  }
}

.consent-dialog-inner {
  width: 100%;
  max-width: 520px;
  padding: var(--space-3);
  background: var(--surface-card);
  border: 1px solid var(--rule-quiet);
  border-radius: 6px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.08);
  font-family: var(--font-sans);
  color: var(--ink-primary);
}

.consent-dialog-title {
  margin: 0 0 var(--space-1);
  font-family: var(--font-serif);
  font-size: var(--type-size-body-comfortable);
  font-weight: var(--type-weight-semibold);
  line-height: var(--type-line-height-tight);
  color: var(--ink-primary);
}

.consent-dialog-body {
  margin: 0 0 var(--space-2);
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  line-height: var(--type-line-height-base);
  color: var(--ink-secondary);
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Options list — one row per opt-in feature. Default unchecked.          │
   │ Adding a new opt-in is: 1 new <li>, 1 new entry in consent.js FLAGS.   │
   └────────────────────────────────────────────────────────────────────────┘ */
.consent-options {
  list-style: none;
  margin: 0 0 var(--space-2);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.consent-option-row {
  margin: 0;
}
.consent-option {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-1);
  border: 1px solid var(--rule-quiet);
  border-radius: 6px;
  cursor: pointer;
  transition: border-color var(--motion-quick) var(--easing);
}
.consent-option:hover {
  border-color: var(--metal-platine);
}

/* Switch-style toggle: real <input type="checkbox" role="switch">
   visually hidden under the styled track. The input keeps the click +
   focus target (full-size, opacity 0 above the track) so AT and
   keyboard work natively without extra JS. */
.consent-toggle {
  position: relative;
  flex: 0 0 auto;
  display: inline-block;
  width: 36px;
  height: 20px;
  margin-top: 2px;
}
.consent-option-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: pointer;
  z-index: 1;
}
.consent-toggle-track {
  position: absolute;
  inset: 0;
  background: var(--rule-quiet);
  border-radius: 999px;
  transition: background-color var(--motion-quick) var(--easing);
}
.consent-toggle-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  background: var(--surface-card);
  border-radius: 50%;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
  transition: transform var(--motion-quick) var(--easing);
}
.consent-option-input:checked ~ .consent-toggle-track {
  background: var(--accent-primary);
}
.consent-option-input:checked ~ .consent-toggle-track .consent-toggle-thumb {
  transform: translateX(16px);
}
.consent-option-input:focus-visible ~ .consent-toggle-track {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .consent-toggle-track,
  .consent-toggle-thumb {
    transition: none;
  }
}

.consent-option-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  line-height: var(--type-line-height-base);
}
.consent-option-label {
  color: var(--ink-primary);
  font-weight: var(--type-weight-medium);
}
.consent-option-description {
  color: var(--ink-secondary);
}

.consent-dialog-policy-link {
  margin: 0 0 var(--space-3);
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  line-height: var(--type-line-height-base);
}
.consent-dialog-policy-link a {
  color: var(--ink-primary);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.consent-dialog-policy-link a:hover,
.consent-dialog-policy-link a:focus-visible {
  color: var(--accent-primary);
}

.consent-dialog-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
@media (min-width: 480px) {
  .consent-dialog-actions {
    flex-direction: row;
    justify-content: flex-end;
    gap: var(--space-2);
  }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Save + Reject all buttons — equal weight per no-dark-pattern AC.       │
   │ Same size, padding, border, font-weight, hover treatment. Only the    │
   │ background colour distinguishes them — Save uses --accent-primary    │
   │ (primary CTA the user reached by ticking boxes), Reject all uses      │
   │ --surface-card with the same border.                                  │
   └────────────────────────────────────────────────────────────────────────┘ */
.consent-button {
  flex: 1 1 auto;
  min-height: 44px; /* --target-standard touch-area floor */
  padding: 0.625rem 1rem;
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  font-weight: var(--type-weight-medium);
  border: 1px solid var(--rule-quiet);
  border-radius: 6px;
  cursor: pointer;
  transition:
    background-color var(--motion-quick) var(--easing),
    border-color var(--motion-quick) var(--easing),
    color var(--motion-quick) var(--easing);
}
.consent-button:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}

.consent-button--reject {
  background: var(--surface-card);
  color: var(--ink-primary);
  border-color: var(--metal-platine);
}

.consent-button--save {
  background: var(--cta-face);
  color: var(--cta-label);
  border-color: var(--cta-face);
}
/* Hover background-swap (reject) and brightness filter (save) removed —
   fills stay at their resting state. Scale + shine in cta-interaction.css
   are the sole hover signal for both variants. */

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Persistent withdrawal trigger — bottom-left, low-key, always-on.       │
   │ Visible only when the dialog is NOT shown (hidden attribute is set).  │
   │ Sibling-selector trick: when the dialog is hidden (default once       │
   │ choice made), reveal the trigger. When the dialog is open, hide it.   │
   └────────────────────────────────────────────────────────────────────────┘ */
.consent-trigger {
  position: fixed;
  left: var(--space-3);
  bottom: var(--space-3);
  z-index: 50;
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.375rem 0.625rem;
  min-height: 32px;
  background: var(--surface-card);
  border: 1px solid var(--rule-quiet);
  border-radius: 6px;
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  color: var(--ink-secondary);
  transition:
    color var(--motion-quick) var(--easing),
    border-color var(--motion-quick) var(--easing);
}
/* Hover colour/border swap removed — resting state stays on hover.
   Scale + shine in cta-interaction.css are the sole hover signal. */
.consent-trigger:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}
.consent-trigger-icon {
  flex: 0 0 14px;
  color: currentColor;
}

/* When dialog is open (NOT hidden), suppress the trigger to avoid the
   double-affordance. CSS adjacent-sibling selector reads the dialog's
   :not([hidden]) state. */
.consent-dialog:not([hidden]) ~ .consent-trigger {
  display: none;
}

/* Hide trigger label on narrow screens — icon-only is enough. */
@media (max-width: 479.98px) {
  .consent-trigger-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }
} /* Story 2.6 — cookie consent dialog + persistent withdrawal trigger */
/* ──────────────────────────────────────────────────────────────────────────
   cursor-trail.css — Stigmergy Cursor Trail (Motion Exception B), rev 3.

   Rev 3: trail is landing-page-bottom-only. cursor-trail.js gates on
   [data-cursor-trail-anchor] (mounted exclusively on the SMB-zone +
   DPP-prefooter wrapper inside templates/pages/landing-page.eta). The
   18 other hero surfaces no longer render the trail.

   Lives under tokens/** so the four-prefix stylelint regex exemption
   (Story 1.1) covers ad-hoc class names (per nav.css/manifesto.css/
   consent.css/hero.css precedent).

   COMPOSITION:
     • Fixed-position SVG overlay covers the entire viewport.
     • z-index 5 — below the sticky header (z-30), Vompanion widget (its own
       root), nav dropdowns, mobile sheet, consent dialog. Above the
       page content + Stigmergy substrate dot-grid.
     • pointer-events: none — the trail never intercepts clicks /
       hovers / focus from any underlying element.
     • Opacity is JS-driven (cursor-trail.js writes inline style); this
       file only defines the transition shape so the fade-on-CTA + fade-
       on-stop behaviours feel smooth.
     • Trail stroke + ant glyphs use currentColor → --accent-primary.
       This matches Stigmergy Primitive 4 (#stigmergy-divider) which
       paints its trail at var(--accent-primary) stroke-opacity 0.55 —
       cursor trail and static stigmergy now share the same accent.
       Mode-adaptive: EU blue on light, EU gold on dark.

   PRIVACY NOTE (mirrored from cursor-trail.js): no styling here writes
   anything observable to the page beyond the cursor visual itself. No
   data-* attributes captured into selectors that downstream telemetry
   could read.

   This file is loaded from tokens/index.css. The cursor-trail.js script
   appends the overlay element at runtime — which means the overlay
   doesn't appear in the HTML source and therefore never affects FOUC
   visual snapshots (Story 1.3) or first-paint screenshot tests.
   ────────────────────────────────────────────────────────────────────────── */

.cursor-trail {
  position: fixed;
  top: 0;
  left: 0;
  pointer-events: none;
  z-index: 5;
  color: var(--accent-primary);
  opacity: 0;
  /* Slower fade than rev 1 (was --motion-duration-quick 120ms) — the
     trail is intentionally lingering now; the visual decay is part of
     the brand metaphor (pheromone deposition + slow evaporation). */
  transition: opacity var(--motion-duration-slow) var(--motion-easing-base);
}

.cursor-trail-stroke {
  /* Trail segments paint as <line> elements with per-segment
     stroke-opacity computed from sample-age vs TRAIL_LIFETIME_MS in
     cursor-trail.js. Older end of the trail fades first; after the
     cursor stops the whole trail keeps fading until invisible. CSS
     declares only the look that doesn't depend on age — the stroke
     itself is set on the SVG attribute (currentColor + thin width). */
}

.cursor-trail-ant {
  color: var(--accent-primary);
  /* A body carries a charge — the same hair of glow as the hero cue column. */
  filter: drop-shadow(0 0 2px color-mix(in srgb, currentColor 26%, transparent));
  opacity: 0;
  transition: opacity var(--motion-duration-base) var(--motion-easing-base);
}

.cursor-trail-antennae {
  /* Antennae deploy ONLY when all 3 ants in a V-cluster are literally
     at their formation slots — never before, never while ants are
     moving. cursor-trail.js writes inline opacity 0/1 each frame; we
     deliberately omit a transition so the antennae snap cleanly off
     the instant any ant starts moving (cursor resumes / disperse). */
  opacity: 0;
} /* Story 2.9 rev 3 — Motion Exception B: cursor trail overlay (landing-page bottom anchor only, cursor surfaces only) */
/* ──────────────────────────────────────────────────────────────────────────
   products.css — Story 3.1+ per-product page consumer rules.

   Lives under tokens/** so the four-prefix stylelint regex exemption
   (Story 1.1) covers ad-hoc class names (per nav.css/manifesto.css/
   consent.css/cursor-trail.css/hero.css precedent).

   The .product-page wrapper carries data-aspect so token cascades from
   tokens/aspects.css land cleanly (enterprise variant elevates accent
   ratio for the hero / CTA block). Type stays solid-on-solid (UX-DR1
   no-metallic-on-type rule).

   Composition (Story 3.1 ACs):
     • Hero: eyebrow (product name) + h1 (outcome promise) + sub.
     • Sections (what-it-does, audiences, trust, CTA block) follow a
       consistent rhythm: h2 + body + optional grid.
     • Two equal-weight static fallback CTAs in the get-started block —
       primary (book a pilot) + secondary (create account). Equal size,
       equal padding, equal min-height; only the colour treatment
       distinguishes them per UX-DR (no dark pattern between
       enterprise/SMB CTAs — both are valid choices for different
       audiences).
   ────────────────────────────────────────────────────────────────────────── */

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Page-level wrapper.                                                    │
   └────────────────────────────────────────────────────────────────────────┘ */
.product-page {
  width: 100%;
  max-width: var(--page-measure);
  margin: 0 auto;
  padding: var(--space-5) var(--space-3) var(--space-7);
  color: var(--ink-primary);
  font-family: var(--font-serif);
}
@media (min-width: 768px) {
  .product-page {
    padding: var(--space-6) var(--space-4) var(--space-7);
  }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Hero block — eyebrow + h1 + sub.                                       │
   └────────────────────────────────────────────────────────────────────────┘ */
.product-hero {
  margin-bottom: var(--space-6);
  text-align: left;
}
.product-eyebrow {
  font-family: var(--font-sans);
  font-size: var(--type-size-eyebrow-base);
  font-weight: var(--type-weight-medium);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-quiet);
  margin: 0 0 var(--space-2);
}
.product-h1 {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-regular);
  font-size: var(--type-size-h1-base);
  line-height: var(--type-line-height-tight);
  letter-spacing: -0.02em;
  color: var(--ink-primary);
  margin: 0 0 var(--space-3);
  max-width: 18ch;
}
.product-sub {
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-loose);
  color: var(--ink-secondary);
  margin: 0;
  max-width: 60ch;
}
@media (min-width: 768px) {
  .product-sub {
    font-size: var(--type-size-body-comfortable);
  }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Sections (what-it-does, audiences, trust, CTA block).                  │
   └────────────────────────────────────────────────────────────────────────┘ */
.product-section {
  margin: var(--space-6) auto;
  max-width: var(--page-measure);
}
.product-section h2 {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-semibold);
  font-size: 1.5rem;
  line-height: var(--type-line-height-tight);
  color: var(--ink-primary);
  margin: 0 0 var(--space-3);
}
.product-section p {
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-loose);
  color: var(--ink-primary);
  margin: 0 0 var(--space-2);
}
@media (min-width: 768px) {
  .product-section p {
    font-size: var(--type-size-body-comfortable);
  }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Audiences grid — two-up at desktop, stacked on mobile.                 │
   └────────────────────────────────────────────────────────────────────────┘ */
.product-audience-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}
@media (min-width: 768px) {
  .product-audience-grid {
    grid-template-columns: 1fr 1fr;
  }
}
.product-audience {
  padding: var(--space-3);
  background: var(--surface-card);
  border: 1px solid var(--rule-quiet);
  border-radius: 6px;
}
.product-audience h3 {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-semibold);
  font-size: var(--type-size-body-comfortable);
  line-height: var(--type-line-height-tight);
  color: var(--ink-primary);
  margin: 0 0 var(--space-2);
}
.product-audience p {
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-base);
  color: var(--ink-secondary);
  margin: 0;
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Trust block — sovereignty + portability + manifesto link.              │
   └────────────────────────────────────────────────────────────────────────┘ */
.product-trust-line {
  font-style: italic;
  color: var(--ink-secondary);
}
.product-trust-link {
  margin-top: var(--space-3);
}
.product-trust-link a {
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  font-weight: var(--type-weight-medium);
  color: var(--accent-primary);
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 0.25em;
  transition: text-decoration-color var(--motion-quick) var(--easing);
}
.product-trust-link a:hover,
.product-trust-link a:focus-visible {
  text-decoration-color: var(--accent-primary);
}
.product-trust-link a:focus-visible {
  outline: 2px solid var(--ink-primary);
  outline-offset: 2px;
  border-radius: 2px;
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ CTA block — equal-weight Book a pilot + Create account.                │
   │ Visually distinct fill colour but same shape, padding, weight.         │
   └────────────────────────────────────────────────────────────────────────┘ */
.product-cta-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: var(--space-3) 0;
}
@media (min-width: 480px) {
  .product-cta-actions {
    flex-direction: row;
    flex-wrap: wrap;
  }
}
.product-cta-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px; /* --target-primary touch-area floor */
  padding: 0.75rem 1.25rem;
  font-family: var(--font-sans);
  font-size: var(--type-size-body-base);
  font-weight: var(--type-weight-medium);
  text-decoration: none;
  border: 1px solid var(--cta-face);
  border-radius: 6px;
  cursor: pointer;
  transition:
    background-color var(--motion-quick) var(--easing),
    color var(--motion-quick) var(--easing);
}
.product-cta-button:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}
.product-cta-button--primary {
  background: var(--cta-face);
  color: var(--cta-label);
}
.product-cta-button--secondary {
  background: transparent;
  color: var(--ink-primary);
}
/* Hover colour-flip removed (both variants) — fills stay at their resting
   state. Scale + shine in cta-interaction.css are the sole hover signal. */

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Built on Enterprise Mesh attribution — quiet line under CTAs.              │
   │ Same canonical string per locale (NFR-Q4 — referenced via             │
   │ common.attribution.builtOnMesh).                                       │
   └────────────────────────────────────────────────────────────────────────┘ */
.product-builton {
  margin: var(--space-3) 0 0;
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  font-style: italic;
  color: var(--ink-quiet);
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Story 3.4 — Enterprise Mesh capability + deployment lists.                 │
   │ <dl> definition lists keep capability name → description semantics    │
   │ explicit for AT (each <dt>/<dd> pair announces correctly). The        │
   │ developer-aspect cell suppresses ornamentation; type rhythm carries   │
   │ the substrate register.                                                │
   └────────────────────────────────────────────────────────────────────────┘ */
.product-capability-list,
.product-deployment-list,
.product-use-case-list {
  margin: var(--space-3) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}
@media (min-width: 768px) {
  .product-capability-list,
  .product-deployment-list {
    grid-template-columns: 1fr 1fr;
  }
}

.product-capability,
.product-deployment-state,
.product-use-case {
  margin: 0;
}
.product-capability dt,
.product-deployment-state dt,
.product-use-case dt {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-semibold);
  font-size: var(--type-size-body-comfortable);
  line-height: var(--type-line-height-tight);
  color: var(--ink-primary);
  margin: 0 0 var(--space-1);
}
.product-capability dd,
.product-deployment-state dd,
.product-use-case dd {
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-base);
  color: var(--ink-secondary);
  margin: 0;
}

/* Foundation component line — the priced line items a foundation meters,
   mirrored from pricing.yaml › publicPriceBreakdown. Reads as a quiet
   caption under the foundation body so the page stays "what it is" and
   /pricing owns "how much". */
.product-foundation__components {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--type-size-body-small, 0.875rem);
  color: var(--ink-tertiary, var(--ink-secondary));
}
.product-foundation__included {
  margin: var(--space-3) 0 0;
  font-family: var(--font-serif);
  font-size: var(--type-size-body-small, 0.875rem);
  color: var(--ink-tertiary, var(--ink-secondary));
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Built-on cross-links list — runs the three on-mesh products as       │
   │ inline links. Managed OpenClaw is intentionally absent (off-mesh).   │
   └────────────────────────────────────────────────────────────────────────┘ */
.product-builton-links {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
@media (min-width: 600px) {
  .product-builton-links {
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--space-3);
  }
}
.product-builton-links a {
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  font-weight: var(--type-weight-medium);
  color: var(--accent-primary);
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 0.25em;
  transition: text-decoration-color var(--motion-quick) var(--easing);
}
.product-builton-links a:hover,
.product-builton-links a:focus-visible {
  text-decoration-color: var(--accent-primary);
}
.product-builton-links a:focus-visible {
  outline: 2px solid var(--ink-primary);
  outline-offset: 2px;
  border-radius: 2px;
} /* Story 3.1+ — per-product page composition (hero, sections, audiences grid, equal-weight CTAs, built-on attribution) */
/* ──────────────────────────────────────────────────────────────────────────
   partners.css — /partners (Design Partner Program) surface consumer rules.

   The page wrapper composes `.product-page` for max-width / centring /
   padding / serif typography (tokens/products.css). This file only adds
   the partner-specific blocks that products.css doesn't cover.

   Post-DPP-rewrite layout (2026-05-14): four content sections under the
   hero — `.partner-how` (sprint cadence), `.partner-deal` (paired
   you-gain / we-gain blocks; stigmergy-rail anchored), `.partner-pricing`
   (PPA escalator), `.partner-apply` (mailto CTA). All compose
   `.product-section` so their outer rhythm cascades from products.css.

   Stigmergy: `.partner-how` + `.partner-deal` carry the `.stigmergy-rail`
   class (with a waypoint SVG) — see tokens/stigmergy.css for the primitive.
   ────────────────────────────────────────────────────────────────────────── */

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Hero — open + closed variants.                                          │
   └────────────────────────────────────────────────────────────────────────┘ */
.partner-hero {
  margin-bottom: var(--space-6);
  text-align: left;
}
.partner-hero--closed {
  margin-bottom: var(--space-7);
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ How it works — ordered sprint-cadence list.                             │
   │ Decimal markers in the sans/meta register sit alongside serif body     │
   │ copy; matches the institutional voice.                                  │
   └────────────────────────────────────────────────────────────────────────┘ */
.partner-how-steps {
  margin: var(--space-3) 0 0;
  padding-left: var(--space-4);
  list-style-position: outside;
  list-style-type: decimal;
}
.partner-how-steps > li {
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-loose);
  color: var(--ink-primary);
  margin: 0 0 var(--space-3);
  padding-left: var(--space-2);
}
.partner-how-steps > li:last-child {
  margin-bottom: 0;
}
.partner-how-steps > li::marker {
  font-family: var(--font-sans);
  font-weight: var(--type-weight-medium);
  color: var(--ink-quiet);
}
@media (min-width: 768px) {
  .partner-how-steps > li {
    font-size: var(--type-size-body-comfortable);
  }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ The deal — paired you-gain / we-gain blocks.                            │
   │ Stacks on narrow viewports; 2-column grid from 768px up. The two       │
   │ blocks share weight (1fr 1fr) so neither reads as primary.             │
   └────────────────────────────────────────────────────────────────────────┘ */
.partner-deal-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
  margin-top: var(--space-4);
}
@media (min-width: 768px) {
  .partner-deal-grid {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-6);
  }
}
.partner-deal-block > h3 {
  font-family: var(--font-serif);
  font-weight: var(--type-weight-semibold);
  font-size: var(--type-size-body-comfortable);
  line-height: var(--type-line-height-tight);
  color: var(--ink-primary);
  margin: 0 0 var(--space-3);
}
.partner-deal-list {
  margin: 0;
  padding-left: var(--space-4);
  list-style-position: outside;
  list-style-type: disc;
}
.partner-deal-list > li {
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-loose);
  color: var(--ink-primary);
  margin: 0 0 var(--space-3);
  padding-left: var(--space-1);
}
.partner-deal-list > li:last-child {
  margin-bottom: 0;
}
.partner-deal-list > li::marker {
  color: var(--ink-quiet);
}
@media (min-width: 768px) {
  .partner-deal-list > li {
    font-size: var(--type-size-body-comfortable);
  }
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Apply CTA wrapper — bare button after the pricing section, no section  │
   │ wrapper / no heading / no intro copy (2026-05-14 Tom redirect: the     │
   │ entire apply section reduces to its CTA).                              │
   └────────────────────────────────────────────────────────────────────────┘ */
.partner-apply-cta {
  margin: var(--space-5) 0 0;
  text-align: center;
}

/* ┌────────────────────────────────────────────────────────────────────────┐
   │ Closed-state body paragraph — quiet secondary ink.                      │
   └────────────────────────────────────────────────────────────────────────┘ */
.partner-closed-body {
  font-family: var(--font-serif);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-loose);
  color: var(--ink-secondary);
  margin: var(--space-4) 0 0;
  max-width: 60ch;
}
@media (min-width: 768px) {
  .partner-closed-body {
    font-size: var(--type-size-body-comfortable);
  }
} /* Story 3.6 — /partners DPP surface (hero spacing + cohort-terms ordered list + closed-state body); page wrapper composes .product-page */
/* ──────────────────────────────────────────────────────────────────────────
   site-footer.css — Story 3.8 site-wide footer.

   Lives under tokens/** so the four-prefix stylelint regex exemption
   (Story 1.1) covers ad-hoc class names.

   COMPOSITION:
     • Footer sits at the bottom of every page below the main content
       slot. Top border separates it from page content.
     • Inner row rides --page-measure, the same rail as the header and the
       page content, so all three chrome edges line up.
     • Two link groups (Resources · Legal), each under a quiet visible
       heading: stacked on mobile, side by side from 768px with the
       copyright line pushed to the end.
     • Quiet typography: --font-sans, --type-size-meta-base,
       --ink-quiet — the footer should not compete with main content.
   ────────────────────────────────────────────────────────────────────────── */

.site-footer {
  margin-top: var(--space-7);
  padding: var(--space-4) 0;
  border-top: 1px solid var(--rule-quiet);
  background: var(--surface-page);
}
.site-footer-inner {
  width: 100%;
  max-width: var(--page-measure);
  margin: 0 auto;
  padding: 0 var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
@media (min-width: 768px) {
  .site-footer-inner {
    flex-direction: row;
    align-items: flex-start;
    gap: var(--space-6);
    padding: 0 var(--space-4);
  }
}

.site-footer-heading {
  margin: 0 0 var(--space-1);
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  font-weight: var(--type-weight-semibold);
  color: var(--ink-primary);
}

.site-footer-links {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.site-footer-links li {
  display: flex;
}
.site-footer-links a {
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  color: var(--ink-secondary);
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 0.3em;
  transition: text-decoration-color var(--motion-quick) var(--easing);
  min-height: 32px;
  display: inline-flex;
  align-items: center;
}
.site-footer-links a:hover,
.site-footer-links a:focus-visible {
  color: var(--ink-primary);
  text-decoration-color: var(--accent-primary);
}
.site-footer-links a:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
  border-radius: 2px;
}

.site-footer-copy {
  /* --ink-secondary, not --ink-quiet: --ink-quiet on --surface-page
     measures 4.47:1, 0.03 short of WCAG 2.2 AA (4.5:1). Aligning with
     the link colour also reads as a single quiet footer band. */
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  color: var(--ink-secondary);
}

@media (min-width: 768px) {
  .site-footer-copy {
    margin-left: auto;
  }
}

/* On tablet/phone the /companion page becomes the dedicated full-viewport chat
   surface (paired with the companion-mount.eta redirect at < 1024px). Hide the
   footer there so chat chrome owns the viewport — legal links remain
   reachable from every other surface. */
@media (max-width: 1023.98px) {
  body[data-surface="companion-page"] .site-footer {
    display: none;
  }
} /* Story 3.8 — site-wide footer with legal links visible from every page */
/* pricing.css — /pricing surface, the plan + credits model (MF-746).

   The page is built on the shared landing layering (hero-block, .zone, .card
   .glass, data-reveal, data-ant-hover, data-hue, .cta-companion, foot-cta — all in
   @myrmid/brand + surfaces.css), so this file carries ONLY what the plan page
   adds on top of a .card: the price/caps/credits lines, the plan-group eyebrow
   spacing, and the Crew promo pill. No bespoke hero/section/card/button vocabulary
   lives here any more. */

/* ── Plan cards (layered over .card.glass; .card owns the flex column + gap) ── */
.plan-card__tagline {
  margin: 0;
}
.plan-card__price {
  margin: 0;
  font-family: var(--font-serif);
  font-size: clamp(1.4rem, 3vw, 1.8rem);
  font-weight: var(--type-weight-medium);
  line-height: var(--type-line-height-tight);
  color: var(--ink-primary);
}
.plan-card__per {
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  font-weight: var(--type-weight-regular);
  color: var(--ink-quiet);
}
.plan-card__caps {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--type-size-small-base);
  line-height: var(--type-line-height-base);
  color: var(--ink-secondary);
}
.plan-card__credits {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--type-size-small-base);
  line-height: var(--type-line-height-base);
  color: var(--ink-primary);
}
/* Pin the CTA to the bottom of the card and let it span the card's width. */
.plan-card__cta {
  margin-top: auto;
  align-self: stretch;
}

/* A group label over a row of cards — a .zone__eyebrow set off from what is above it. */
.pricing-plans__group {
  margin-top: var(--space-5);
  margin-bottom: 0;
}

/* The price terms, small and italic at the foot of each plans slide. */
.pricing-plans__note {
  margin-top: var(--space-4);
  font-size: var(--type-size-meta-base);
  font-style: italic;
}

/* "First month free" promo pill on the Crew card — a quiet outlined chip in the
   card's own hue (data-hue sets --color-glass-accent), so it never competes with
   the gold CTA. */
.plan-card__highlight {
  align-self: flex-start;
  margin: 0;
  padding: 0.1rem 0.6rem;
  border: 1px solid var(--color-glass-accent);
  border-radius: 999px;
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  font-weight: var(--type-weight-medium);
  letter-spacing: 0.02em;
  color: var(--color-glass-accent);
}

/* ── Monthly / yearly billing toggle (CSS-only) ─────────────────────────────
   Two hidden radios per card (.plan-card__bill--m default-checked, --y); the
   segmented control's labels drive them, and the :checked ~ rules swap which
   price (data-period) shows. Works with no JS. */
.plan-card__bill {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.plan-card__billing {
  align-self: flex-start;
  display: inline-flex;
  gap: var(--space-3);
}
.plan-card__bill-opt {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35em;
  padding-bottom: 2px;
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  color: var(--ink-quiet);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  transition: color var(--motion-quick) var(--easing);
}
.plan-card__bill-save {
  font-size: 0.9em;
  color: var(--color-glass-accent);
}
/* Active option — brighter label + a hue underline. */
.plan-card__bill--m:checked ~ .plan-card__billing .plan-card__bill-opt--m,
.plan-card__bill--y:checked ~ .plan-card__billing .plan-card__bill-opt--y {
  color: var(--ink-primary);
  font-weight: var(--type-weight-medium);
  border-bottom-color: var(--color-glass-accent);
}
/* Which price shows — monthly by default, yearly when its radio is checked. */
.plan-card__price[data-period="yearly"] {
  display: none;
}
.plan-card__bill--y:checked ~ .plan-card__price[data-period="yearly"] {
  display: block;
}
.plan-card__bill--y:checked ~ .plan-card__price[data-period="monthly"] {
  display: none;
}
/* Keyboard focus lands on the (hidden) radio — show it on the control. */
.plan-card__bill:focus-visible ~ .plan-card__billing {
  outline: 2px solid var(--ink-primary);
  outline-offset: 3px;
  border-radius: 2px;
}

/* "Public prices soon" — the quiet stand-in when PLAN_PRICES_PUBLIC is off
   (replaces the price + toggle + credit line on priced cards). */
.plan-card__price--soon {
  font-size: var(--type-size-body-base);
  font-weight: var(--type-weight-regular);
  color: var(--ink-quiet);
} /* Story 3.11 — /pricing surface (tier cards, FAQ, enterprise CTA, cross-links) */
/* ──────────────────────────────────────────────────────────────────────────
   surfaces.css — composition for the reduced page set (2026-09 refresh).

   Four surfaces share one grammar: a `.zone` section shell, glass cards over
   the substrate grid, and hue-tinted rims drawn from the working palette.
   Home, /platform, /solutions and /about all compose from what is here; the
   older per-page sheets they replaced were deleted with their templates.

   Mobile-first: every block is a single column until it has room not to be.
   ────────────────────────────────────────────────────────────────────────── */

/* ── Section shell ─────────────────────────────────────────────────────── */

/* Nothing in the page scrolls sideways; the divider's edge-to-edge margins
   overshoot by half a scrollbar and rely on this clip. */
main {
  overflow-x: clip;
}

/* ONE column width for the whole site. Every section — hero, zones, the
   cornerstone ring, the closing CTA, the cross-links — resolves its content
   box from this, so nothing sits a few pixels off its neighbour. */
:root {
  --page-measure: 72rem;
}

/* Each zone is a FRAME: the page reads as a deck of slides, so a frame gets
   a viewport's worth of room and lands centred in it rather than butting up
   against its neighbours. min-height, not fixed height — a tall frame grows
   past the fold instead of clipping. */
.zone {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 88vh;
  padding: var(--space-7) var(--space-3);
}

@media (min-width: 768px) {
  .zone {
    min-height: 92vh;
    padding: calc(var(--space-7) * 1.6) var(--space-4);
  }
}

/* A short viewport would otherwise spend its whole height on padding. */
@media (max-height: 720px) {
  .zone {
    min-height: 0;
  }
}

/* A page with a hero reads as slides: each block fills the screen below the header, centred, its
   air coming from the frame rather than padding. The glide between them is scroll-slides.js on
   wheel and trackpad; touch gets a gentle snap, since some blocks run several screens on a phone. */
body:has([data-hero]) main :is(section.hero, section.zone, section.foot-cta) {
  min-height: calc(100svh - var(--header-height, 5rem));
  scroll-snap-align: start;
}

body:has([data-hero]) main section.zone {
  padding-block: var(--space-5);
}

body:has([data-hero]) .site-footer {
  scroll-snap-align: end;
}

/* Plans: the four rungs in one row, so the rungs are one slide and the shapes the next. */
@media (min-width: 1200px) {
  .pricing-plans .zone__inner {
    width: 100%;
  }

  .pricing-plans .cards--4 {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* The divider takes no height between slides: it draws in the bottom padding of the slide above. */
body:has([data-hero]) main .divider {
  height: 0;
  margin-block: 0;
  transform: translateY(calc(var(--space-5) / -2));
}

/* The cornerstone ring is 42rem square, too tall to sit under its heading on a short screen. Wide:
   the heading beside the ring. Narrower: the ring shrinks. */
@media (min-width: 1200px) and (max-height: 1160px) {
  body:has([data-hero]) main section.pillars-zone {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 42rem;
    align-content: center;
    align-items: center;
    column-gap: var(--space-6);
    max-width: calc(var(--page-measure) + 2 * var(--space-4));
    margin-inline: auto;
  }

  .pillars-zone > * {
    max-width: none;
  }

  .pillars-zone .zone__inner > *,
  .pillars-zone .pillars-heading {
    margin-inline: 0;
    text-align: left;
  }
}
@media (min-width: 1200px) and (max-height: 828px) {
  .pillars-circle[data-enhanced] { zoom: 0.9; }
}
@media (min-width: 1200px) and (max-height: 760px) {
  .pillars-circle[data-enhanced] { zoom: 0.8; }
}
@media (min-width: 1024px) and (max-width: 1199px) and (max-height: 1160px) {
  .pillars-circle[data-enhanced] { zoom: 0.92; }
}
@media (min-width: 1024px) and (max-width: 1199px) and (max-height: 1080px) {
  .pillars-circle[data-enhanced] { zoom: 0.85; }
}
@media (min-width: 1024px) and (max-width: 1199px) and (max-height: 1000px) {
  .pillars-circle[data-enhanced] { zoom: 0.75; }
}

@media (pointer: coarse) {
  html:has([data-hero]):not([data-wheel]) {
    scroll-snap-type: y proximity;
    scroll-padding-top: var(--header-height, 5rem);
  }
}

.zone__inner {
  max-width: var(--page-measure);
  margin: 0 auto;
}

/* A section head has nothing beside it — cards and panels sit BELOW it — so it
   centres. The one exception is `.stack__text`, which does have a panel to its
   side and stays ranged left (rules further down). */
.zone__inner > * {
  text-align: center;
}

.zone__eyebrow,
.zone__h,
.zone__intro,
.zone__lead,
.zone__note {
  margin-inline: auto;
}

/* Anything that lays out its own children re-establishes normal alignment. */
.zone__inner .cards,
.zone__inner .stack,
.zone__inner .constellation,
.zone__inner .about-values,
.zone__inner .card {
  text-align: left;
}

.zone__eyebrow {
  margin: 0 auto var(--space-2);
  color: var(--color-glass-accent);
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  font-weight: var(--type-weight-semibold);
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.zone__h {
  max-width: 22ch;
  margin: 0 auto var(--space-3);
  font-family: var(--font-serif);
  font-size: clamp(1.85rem, 5.2vw, 3.15rem);
  font-weight: var(--type-weight-regular);
  line-height: var(--type-line-height-tight);
  letter-spacing: var(--type-tracking-tight);
}

.zone__h--wide {
  max-width: 30ch;
}

.zone__intro {
  max-width: 58ch;
  margin: 0 auto var(--space-4);
  color: var(--ink-secondary);
  font-size: clamp(1.02rem, 2.2vw, 1.22rem);
  line-height: var(--type-line-height-loose);
}

/* The one paragraph per section that carries the claim — larger, full ink,
   and the only place a section raises its voice. */
.zone__lead {
  max-width: 46ch;
  margin: 0 auto var(--space-5);
  font-family: var(--font-serif);
  font-size: clamp(1.22rem, 3.1vw, 1.72rem);
  line-height: 1.4;
}

.zone__note {
  max-width: 62ch;
  margin: var(--space-4) auto 0;
  color: var(--ink-quiet);
  font-family: var(--font-sans);
  font-size: var(--type-size-small-base);
  line-height: var(--type-line-height-base);
}

/* ── Section divider ───────────────────────────────────────────────────────
   Two ants facing each other across a gap, on a dotted lattice line — the
   brand's "two ants facing" thought-pause (Stigmergy Primitive 5), run the
   full column as a boundary, in the page's accent.

   The glyph's dots grow toward +x, so its HEAD is the small dot at the -x
   end: the right-hand ant is used raw (head pointing left, inward) and the
   left-hand one is mirrored, so the two face each other.

   Two HALVES, each rule + ant together, so on reveal they travel in from
   their own side of the screen and meet in the middle. Mirror images of one
   another, in step. */
.divider {
  display: flex;
  align-items: center;
  justify-content: center;
  /* The clearance between the two ants — the halves' own gap. Inside a half,
     rule and ant touch, so the dotted line runs right up to its ant. */
  gap: clamp(56px, 8vw, 88px);
  /* Edge to edge: the boundary belongs to the page, not to the column, so it
     runs from one viewport border to the other whatever wraps it (MF-355).
     `main` clips the half-scrollbar that 100vw over-counts. */
  margin: var(--space-6) calc(50% - 50vw);
  padding-inline: 0;
  /* Trail, ants and glow are all the page's glow colour. */
  --divider-core: var(--accent-glow);
  --divider-glow: color-mix(in srgb, var(--accent-glow) 55%, transparent);
  /* The halves travel in from off-screen, so only the x-axis clips: the glow
     needs the y-axis open, or it is cut flush with the ants. */
  overflow-x: clip;
  overflow-y: visible;
}

:root[data-theme="dark"] .divider {
  --divider-glow: color-mix(in srgb, var(--accent-glow) 70%, transparent);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .divider {
    --divider-glow: color-mix(in srgb, var(--accent-glow) 70%, transparent);
  }
}

.divider__half {
  display: flex;
  align-items: center;
  flex: 1;
  gap: 0;
  min-width: 0;
}

/* A rocket trail: full and bright at the ant, thinning and fading away to
   the edge. The wedge is a clip, so the glow goes on the half (below), where
   it wraps the clipped shape instead of being clipped with it. */
.divider__rule {
  flex: 1;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--divider-core));
  clip-path: polygon(0 50%, 100% 0, 100% 100%);
}
.divider__half--right .divider__rule {
  background: linear-gradient(90deg, var(--divider-core), transparent);
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}

.divider__ant {
  flex: none;
  width: 26px;
  height: 10px;
  color: var(--divider-core);
}

.divider__half {
  filter:
    drop-shadow(0 0 1px var(--divider-glow))
    drop-shadow(0 0 5px var(--divider-glow));
}

/* The left ant mirrors so the pair face each other across the centre. */
.divider__ant--flip {
  transform: scaleX(-1);
}

/* ── Open and close with the scroll ─────────────────────────────────────
   Not a reveal: --divider-open (public/js/scroll-divider.js) is the
   divider's own height in the viewport, so scrolling DOWN walks the two ants
   together and scrolling UP walks them apart — continuous and reversible,
   under the reader's hand rather than played at them once.

   No transition: the transform IS the scroll position. A transition here
   would lag the finger and read as drift. The default of 0 leaves it closed
   for no-JS and reduced motion. */
html[data-reveal-armed="on"] .divider__half--left,
.divider__half--left {
  transform: translateX(calc(var(--divider-open, 0) * -100%));
}
html[data-reveal-armed="on"] .divider__half--right,
.divider__half--right {
  transform: translateX(calc(var(--divider-open, 0) * 100%));
}

/* ── Card grids ────────────────────────────────────────────────────────── */

.cards {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: 1fr;
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 700px) {
  .cards--2 {
    grid-template-columns: repeat(2, 1fr);
  }
  .cards--3 {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (min-width: 1000px) {
  .cards--4 {
    grid-template-columns: repeat(2, 1fr);
  }
  .cards--6 {
    grid-template-columns: repeat(3, 1fr);
  }
}

.card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-3) var(--space-3);
  color: inherit;
  text-decoration: none;
}

.card__eyebrow {
  margin: 0;
  color: var(--color-glass-accent);
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  font-weight: var(--type-weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.card__name {
  margin: 0;
  font-family: var(--font-serif);
  font-size: clamp(1.2rem, 2.6vw, 1.45rem);
  font-weight: var(--type-weight-medium);
  line-height: var(--type-line-height-tight);
}

.card__body {
  margin: 0;
  color: var(--ink-secondary);
  font-size: var(--type-size-small-base);
  line-height: var(--type-line-height-loose);
}

.card__list {
  margin: var(--space-1) 0 0;
  padding: 0;
  list-style: none;
}

.card__list li {
  position: relative;
  padding-left: var(--space-3);
  color: var(--ink-secondary);
  font-family: var(--font-sans);
  font-size: var(--type-size-small-base);
  line-height: var(--type-line-height-base);
}

.card__list li + li {
  margin-top: var(--space-1);
}

/* The list marker is the brand's waypoint dot, not a bullet glyph. */
.card__list li::before {
  content: "";
  position: absolute;
  top: 0.62em;
  left: 0;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--color-glass-accent);
}

.card__cta {
  margin-top: auto;
  padding-top: var(--space-2);
  color: var(--color-glass-accent);
  font-family: var(--font-sans);
  font-size: var(--type-size-small-base);
  font-weight: var(--type-weight-medium);
}

/* ── Home §1 — the decoupling ──────────────────────────────────────────── */

.decoupling {
  padding-bottom: var(--space-6);
}

.decoupling__beats {
  margin-top: var(--space-5);
}

/* Two curves that separate: the whole argument of the section as one figure.
   Purely decorative — the beats carry the same content in words. */
.divergence {
  display: block;
  width: 100%;
  height: auto;
  margin: var(--space-5) 0 0;
  overflow: visible;
}

.divergence__axis {
  fill: none;
  stroke: var(--rule-quiet);
  stroke-width: 1;
}

.divergence__line {
  fill: none;
  stroke-width: 1;
  stroke-linecap: round;
}

/* On a phone the figure renders at ~0.6 of its viewBox; 2 units keeps the hairline above a pixel. */
@media (max-width: 767px) {
  .divergence__line {
    stroke-width: 2;
  }
}

/* NOT non-scaling-stroke on these. With it, the dash pattern is resolved in
   SCREEN space while the path is in viewBox units, so a dash sized to the
   path drew the curve twice over — one run near the origin and a second
   stray segment up by the label. Letting the stroke scale keeps dash and
   path in the same units, which is what makes the draw-on-scroll exact. */

/* Three series in one ink, told apart by weight and dash: value solid, people
   dotted, work a light dash. */
.divergence__line--work {
  stroke: var(--ink-quiet);
  stroke-dasharray: 3 4;
}

.divergence__line--people {
  stroke: var(--ink-secondary);
  stroke-dasharray: 0.5 4;
}

/* Read-off lines tie the climb to the baseline. Recessive by construction: they are the
   chart's furniture, and the eye should land on them only when it is measuring. */
.divergence__readoff path {
  fill: none;
  stroke: var(--rule-quiet);
  stroke-width: 1;
}

/* A plotted series ends in a marker, not in nothing. */
.divergence__end {
  stroke: var(--surface-page);
  stroke-width: 2;
}
.divergence__end--rise {
  fill: var(--accent-glow);
}
.divergence__end--people {
  fill: var(--ink-secondary);
}
.divergence__end--work {
  fill: var(--ink-quiet);
}

/* Value created is the one coloured series: the page's glow, lit past the mark. */
.divergence__line--rise,
.divergence__line--charge {
  stroke: var(--accent-glow);
}

/* Past the mark the value line carries the page's glow. The mask that tapers
   its tip applies after the filter, so core and halo fade together. */
.divergence__line--charge {
  filter:
    drop-shadow(0 0 1px var(--accent-glow))
    drop-shadow(0 0 5px color-mix(in srgb, var(--accent-glow) 50%, transparent));
}

/* Drawn as the slide arrives, again on every return. public/js/scroll-figure.js
   animates --figure-progress; the default of 1 means no JS, or reduced motion,
   still shows a finished chart.

   A clip wipe rather than a dash offset, so BOTH curves draw — the flat line
   carries a dash pattern of its own and cannot also be drawn by dashoffset. */
.divergence {
  --figure-progress: 1;
}

.divergence__wipe {
  transform: scaleX(var(--figure-progress));
  transform-origin: 38px 0;
  transform-box: view-box;
}

/* The adoption point. The wipe reaches x=300 at progress 0.44; the mark fades
   in there rather than being clipped, because half a logo reads as broken. */
.divergence__adopt {
  opacity: clamp(0, calc((var(--figure-progress) - 0.42) * 9), 1);
}

.divergence__adopt-guide {
  fill: none;
  stroke: var(--rule-quiet);
  stroke-width: 1;
  stroke-dasharray: 2 3;
}

/* The label arrives with the curve it names. */
.divergence__label--rise {
  opacity: var(--figure-progress);
}

.divergence__label {
  fill: var(--ink-quiet);
  font-family: var(--font-sans);
  font-size: 11px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.divergence__label--rise {
  fill: var(--accent-primary);
}

/* ── Home §2 — the six cornerstones, in glass ──────────────────────────── */
/* Geometry stays with portfolio.css (the ring maths + the accordion
   fallback). These rules only re-dress the ring in the new material. */

/* The NO-JS baseline (a readable stack of cards) is the only place the node
   itself is a surface — in the enhanced ring the button is a transparent box
   and the visible disc is its ::before, so dressing the button there would
   float a lens over nothing. */
.pillars-circle:not([data-enhanced]) .pillars-node {
  border-radius: 999px;
  background: var(--color-glass-fill);
  border: 1px solid var(--color-glass-rim);
  box-shadow: var(--color-glass-shadow);
  backdrop-filter: var(--motion-glass-lens);
  -webkit-backdrop-filter: var(--motion-glass-lens);
}

.pillars-slot[data-pillar="domainGovernance"] {
  --color-glass-accent: var(--accent-blue);
}
.pillars-slot[data-pillar="adaptiveCollaboration"] {
  --color-glass-accent: var(--accent-turquoise);
}
.pillars-slot[data-pillar="compoundKnowledge"] {
  --color-glass-accent: var(--accent-purple);
}
.pillars-slot[data-pillar="unifiedWorkforce"] {
  --color-glass-accent: var(--accent-green);
}
.pillars-slot[data-pillar="traceabilityExplainability"] {
  --color-glass-accent: var(--accent-amber);
}
.pillars-slot[data-pillar="selfImprovement"] {
  --color-glass-accent: var(--accent-eu-blue);
}

.pillars-node__name {
  color: var(--ink-primary);
}

/* The ring disc wears the SAME glass as the cards — translucent fill, lens,
   specular — ink on the rim at rest, copper under the pointer.
   Only the MATERIAL moves; the disc's geometry and its bloom stay
   portfolio.css's (that ::before IS the disc; re-declaring its box collapses
   the ring). */
.pillars-circle[data-enhanced] .pillars-slot .pillars-node::before {
  background: var(--color-glass-fill);
  backdrop-filter: var(--motion-glass-lens);
  -webkit-backdrop-filter: var(--motion-glass-lens);
  border-color: var(--rule-quiet);
  box-shadow: var(--color-glass-shadow);
}
.pillars-circle[data-enhanced] .pillars-slot .pillars-node:hover::before,
.pillars-circle[data-enhanced] .pillars-slot .pillars-node:focus-visible::before {
  border-color: var(--accent-primary);
  box-shadow:
    var(--color-glass-shadow),
    0 8px 30px -12px color-mix(in srgb, var(--accent-glow) 55%, transparent);
}

@media (prefers-reduced-transparency: reduce) {
  .pillars-circle[data-enhanced] .pillars-slot .pillars-node::before {
    background: var(--surface-card);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}

/* A cornerstone KEEPS ITS OWN ACCENT when it blooms. portfolio.css sends both
   the ring bloom and the narrow accordion to --accent-primary, so every open
   cornerstone came out the same blue and the six hues only existed while
   closed — which is exactly when they say least. */
/* The open bloom wears the same glass as everything else; the dimmed
   discs behind drop their shadow, which through the glass read as lying on top. */
.pillars-circle[data-enhanced].is-active .pillars-slot:not(.is-open) .pillars-node::before {
  box-shadow: none;
}
.pillars-circle[data-enhanced] .pillars-slot.is-open .pillars-node::before {
  /* The bloom is a scale(28/10.5) of the disc, which scales the lens with it: undo it. */
  background: var(--color-glass-fill);
  backdrop-filter: blur(calc(var(--glass-blur) * 0.375)) saturate(1.5);
  -webkit-backdrop-filter: blur(calc(var(--glass-blur) * 0.375)) saturate(1.5);
  /* The frame is a 0.375px inset ring (10.5 / 28, as for the lens), a hairline once scaled;
     a sub-pixel border would be rounded up to 1px before the scale. */
  border-color: transparent;
  box-shadow:
    inset 0 0 0 0.375px var(--color-glass-accent),
    0 4px 10px rgb(0 0 0 / 0.16),
    0 10px 44px -14px color-mix(in srgb, var(--color-glass-accent) 55%, transparent);
}

.pillars-circle[data-enhanced] .pillars-slot.is-open .pillars-node__name {
  color: var(--ink-primary);
}

/* The open card's hue is on its rim and glow; no tint band behind the name. */
.pillars-circle[data-enhanced] .pillars-slot.is-open .pillars-node {
  background: transparent;
  color: var(--ink-primary);
}

/* On narrow the cornerstone is a whole glass card (portfolio.css): the name takes the hue. */
@media (max-width: 1023.98px) {
  .pillars-circle[data-enhanced] .pillars-slot.is-open .pillars-node__name {
    color: var(--color-glass-accent);
  }
}

@media (hover: hover) {
  .pillars-circle[data-enhanced] .pillars-slot .pillars-node:hover {
    color: var(--color-glass-accent);
  }
}


/* ── Home §3 — the product constellation ───────────────────────────────── */

.constellation {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: 1fr;
  margin-top: var(--space-5);
}

/* Above 900px the five surfaces sit two over three (6 tracks: upper cards
   span 3, lower span 2). */
@media (min-width: 900px) {
  .constellation {
    grid-template-columns: repeat(6, 1fr);
    gap: var(--space-3);
  }

  .constellation__item--upper {
    grid-row: 1;
    grid-column: span 3;
  }

  .constellation__item--lower {
    grid-row: 2;
    grid-column: span 2;
  }
}

/* ── Home §4 — European ground ─────────────────────────────────────────── */

/* Five cards over two rows, both centred: a 3-column grid leaves the second
   row hanging off the left edge with a hole where the sixth card would be. */
.ground-points {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: var(--space-4);
}

.ground-points > li {
  flex: 1 1 100%;
}

@media (min-width: 640px) {
  .ground-points > li {
    flex: 0 1 calc((100% - var(--space-3)) / 2);
  }
}

@media (min-width: 960px) {
  .ground-points > li {
    flex: 0 1 calc((100% - 2 * var(--space-3)) / 3);
  }
}

/* ── Shared closing block ──────────────────────────────────────────────── */

.foot-cta {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 70vh;
  padding: var(--space-7) var(--space-3);
  text-align: center;
}

.foot-cta__inner {
  max-width: var(--page-measure);
  margin: 0 auto;
}

.foot-cta__h {
  margin: 0 0 var(--space-2);
  font-family: var(--font-serif);
  font-size: clamp(1.8rem, 5vw, 2.8rem);
  font-weight: var(--type-weight-regular);
  line-height: var(--type-line-height-tight);
}

.foot-cta__body {
  max-width: 46ch;
  margin: 0 auto var(--space-4);
  color: var(--ink-secondary);
  line-height: var(--type-line-height-loose);
}

.foot-cta .hero-cta {
  justify-content: center;
}

/* ── /platform — the alternating surface stack ─────────────────────────── */

/* No vertical padding of its own — each stack already sits in its own `.zone`,
   and doubling the two opened a hole between every surface. */
.stack {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: 1fr;
  align-items: start;
}

/* A run of consecutive stacks reads as one list, so they breathe on the
   section rhythm rather than the page rhythm. */
.zone:has(> .zone__inner > .stack) {
  padding-block: var(--space-5);
}

@media (min-width: 900px) {
  .stack {
    grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
    gap: var(--space-5);
    align-items: center;
  }
  /* Every other section leads from the right, so the page has a rhythm
     instead of a column. */
  .stack--flip .stack__text {
    order: 2;
  }
}

.stack__text {
  max-width: 46ch;
}

.stack__h {
  margin: 0 0 var(--space-2);
  font-family: var(--font-serif);
  font-size: clamp(1.6rem, 4vw, 2.35rem);
  font-weight: var(--type-weight-regular);
  line-height: var(--type-line-height-tight);
}

.stack__tagline {
  margin: 0 0 var(--space-3);
  color: var(--color-glass-accent);
  font-family: var(--font-sans);
  font-size: clamp(1rem, 2.4vw, 1.15rem);
  font-weight: var(--type-weight-medium);
}

.stack__body {
  margin: 0;
  color: var(--ink-secondary);
  line-height: var(--type-line-height-loose);
}

.stack__panel {
  padding: var(--space-4);
}

.stack__panel .card__list li {
  font-size: var(--type-size-body-base);
}

/* ── /solutions ────────────────────────────────────────────────────────── */

.sol-start {
  margin: var(--space-1) 0 0;
  padding-top: var(--space-2);
  border-top: 1px solid var(--rule-quiet);
  color: var(--ink-quiet);
  font-family: var(--font-sans);
  font-size: var(--type-size-small-base);
}

/* ── /about ────────────────────────────────────────────────────────────── */

.about-prose {
  max-width: 60ch;
}

.about-prose p {
  margin: 0 0 var(--space-3);
  line-height: var(--type-line-height-loose);
}

.about-founder__role {
  margin: 0;
  color: var(--color-glass-accent);
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  font-weight: var(--type-weight-semibold);
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.about-founder__conviction {
  margin: 0;
  padding-top: var(--space-2);
  border-top: 1px solid var(--rule-quiet);
  font-family: var(--font-serif);
  font-size: var(--type-size-small-base);
  font-style: italic;
  line-height: var(--type-line-height-base);
}

/* ── What put us in motion — six values on a turning carousel ──────────
   The stack is the floor (phones, no-JS); above 760px the cards ride a ring
   seen slightly from above, front to back. One clock (--values-turn, on the
   list) drives every card: it sits at sin/cos of its own angle plus the turn,
   so the six can never drift out of phase, and no card is ever rotated. */
.about-values {
  margin: var(--space-4) 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-3);
  grid-template-columns: 1fr;
}

.about-value {
  list-style: none;
}

/* The bubbles wear the page's glass; only the accent hairline is dropped,
   because that bar is drawn with a 14px top radius and hangs off a disc as a
   chord. The hue goes to the rim, which a circle can carry. */
.about-value__bubble {
  display: block;
  padding: var(--space-3) var(--space-4);
  text-align: center;
}

.about-value__bubble::after {
  content: none;
}

/* No coloured edge, even under the pointer, and the title keeps its own hue. */
.about-value__bubble.glass:hover,
.about-value__bubble.glass:focus-within {
  --color-glass-accent: inherit;
  border-color: var(--color-glass-rim);
}

.about-value__name {
  display: block;
  font-family: var(--font-serif);
  font-size: clamp(1.05rem, 2.4vw, 1.2rem);
  line-height: var(--type-line-height-tight);
  color: var(--ink-primary);
}

.about-value__why {
  display: block;
  margin-top: var(--space-2);
  font-family: var(--font-sans);
  font-size: var(--type-size-small-base);
  line-height: var(--type-line-height-base);
  color: var(--ink-secondary);
}

@media (min-width: 760px) {
  .about-values {
    --rx: min(19rem, 26vw); /* half the ring's width */
    --ry: 7rem; /* the back sits higher: the ring is seen from above */
    position: relative;
    display: block;
    width: 100%;
    height: 30rem;
    margin: var(--space-4) auto 0;
    /* public/js/values-carousel.js turns it; a horizontal drag is the carousel's. */
    --values-turn: 0deg;
    touch-action: pan-y;
    user-select: none;
  }

  .about-value {
    --a: calc(var(--orbit-a) + var(--values-turn));
    --front: calc((cos(var(--a)) + 1) / 2); /* 1 at the front, 0 at the back */
    position: absolute;
    /* Front over back, as it moves. */
    z-index: calc(1 + round(var(--front) * 100));
    top: 50%;
    left: 50%;
    width: min(17rem, 27vw);
    height: 11.5rem;
    cursor: grab;
    /* Flat 2D, the front card at exactly 1:1: a 3D perspective magnified it and blurred the text. */
    transform: translate(-50%, -50%)
      translate(calc(sin(var(--a)) * var(--rx)), calc(cos(var(--a)) * var(--ry)))
      scale(calc(0.84 + 0.16 * var(--front)));
  }

  .about-values[data-dragging] .about-value {
    cursor: grabbing;
  }

  /* The cards stay whole glass; only the words fade toward the back. Opacity on the
     card itself would cut its frost off from what is behind it. */
  .about-value__name,
  .about-value__why {
    opacity: calc(0.15 + 0.85 * pow(var(--front), 2));
    transition: opacity var(--motion-base) var(--easing);
  }

  .about-value:focus-visible {
    outline: none;
  }

  .about-value__bubble {
    display: flex;
    flex-direction: column;
    justify-content: center;
    height: 100%;
    padding: var(--space-3);
    transition:
      border-color var(--motion-base) var(--easing),
      box-shadow var(--motion-base) var(--easing),
      transform var(--motion-base) var(--easing);
  }

  .about-value__name {
    color: var(--color-glass-accent);
    font-size: 1.45rem;
  }

  .about-value__why {
    font-size: var(--type-size-meta-base);
    line-height: 1.45;
  }

  .about-value:hover :is(.about-value__name, .about-value__why),
  .about-value:focus-visible :is(.about-value__name, .about-value__why) {
    opacity: 1;
  }

  /* Hover lifts a card clear of the ones in front; a click brings it forward. The glow
     belongs to the front: it fades in as the card comes round toward the centre. */
  .about-value:hover .about-value__bubble,
  .about-value:focus-visible .about-value__bubble {
    box-shadow:
      var(--color-glass-shadow-lift),
      0 10px 40px -14px
        color-mix(
          in srgb,
          var(--accent-glow) calc(55% * pow(var(--front), 4)),
          transparent
        );
    transform: translateY(-1.5rem);
  }
} /* 2026-09 refresh — home / platform / solutions / about composition. Replaces products-index.css, team-store.css, solutions-index.css, solutions-sub.css and about.css, deleted with the pages they served. */
/* The enquiry form (MF-443) — /contact. A task surface: one column, plain fields, no
   flourish. The 16px floor on every control is not a style choice; below it iOS zooms a
   focused field and never zooms back. */

/* ── The page, not the form ────────────────────────────────────────────────────
   `.zone` is the HERO shell: min-height 92vh plus hero padding, sized for a short
   section head with nothing under it. Wrapped around a nine-field form it made this
   page 1829px against a 900px viewport — twice the screen before anyone typed.

   A task page earns neither. The height comes off, the padding comes in, and on a
   wide screen the ask sits BESIDE the card rather than above it: two short columns
   instead of one long one is what actually buys the fold back. */
.zone--task {
  min-height: 0;
  padding-block: var(--space-5);
  justify-content: flex-start;
}

@media (min-width: 768px) {
  .zone--task {
    min-height: 0;
    padding-block: var(--space-5);
  }
}

.enquiry-layout {
  display: grid;
  gap: var(--space-4);
  max-width: 62rem;
}

/* Ranged left, against `.zone__inner > *`'s centring: centred text beside a form
   reads as a poster with a form stuck to it, not as one thing to do. */
.enquiry-layout > * {
  text-align: start;
}

@media (min-width: 62rem) {
  .enquiry-layout {
    grid-template-columns: minmax(0, 21rem) minmax(0, 34rem);
    /* Named rows so the small print sits UNDER the ask rather than being pushed to the
       bottom of a row the card's height stretched. `auto 1fr` gives row 1 exactly the
       ask, and row 2 whatever the card leaves — which the note aligns to the top of. */
    grid-template-areas:
      "ask form"
      "note form";
    grid-template-rows: auto 1fr;
    gap: var(--space-3) var(--space-6);
    align-items: start;
  }
  .enquiry-ask {
    grid-area: ask;
  }
  /* Optically aligned with the card's first label rather than its border. */
  .enquiry-ask {
    padding-top: var(--space-3);
  }
  /* A card that opens with PROSE has no label to align to, so the ask takes the card's own
     inset and the two first lines share a baseline. */
  .enquiry-layout--prose .enquiry-ask {
    padding-top: var(--space-4);
  }
  /* And it FILLS that column, in every state. `.enquiry` carries `margin-inline: auto` for when
     it stands alone — but an auto inline margin on a grid item eats the free space instead of
     stretching, so the card shrank to its content and resized between the wait and the answer.
     The form never showed it: nine fields fill the track on their own. */
  .enquiry-layout--prose .enquiry {
    margin-inline: 0;
  }
  /* The card owns the right column for both rows; the ask and the small print stack
     down the left. On a phone this all collapses to source order, which is why the
     privacy note is last in the markup. */
  .enquiry {
    grid-area: form;
  }
  .enquiry__privacy {
    grid-area: note;
    align-self: start;
    max-width: 34ch;
  }
}





.enquiry-ask {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.enquiry-ask .zone__h {
  margin: 0;
}

.enquiry-ask .zone__intro {
  margin: 0;
  max-width: 34ch;
}

.enquiry {
  max-width: 34rem;
  /* Centred while it is alone in the column; in the two-column layout the grid places
     it and this is a no-op. */
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  /* space-2, not space-4: the fields are labelled and boxed, so the gap is doing no
     separating work the labels do not already do — and at nine fields it was costing
     ~90px, which is the difference between the send button being on the screen or
     under it. */
  gap: var(--space-2);
}

/* The card is `card glass` in the markup — the site's own material. It brings the
   rim, the radius, the fill, the one sanctioned hover bloom, and `glass::after`, which
   IS the accent hairline along the top edge. This file used to hand-roll all of that
   with a plain border and a `border-top`; it does not any more. Un-hued on purpose:
   an un-hued surface inherits the principal accent (tokens/glass.css). */

/* The select is the one control the site has no pattern for — nothing else on the
   landing uses one — so it is styled here rather than borrowed. It matches
   `.enquiry__input` exactly and swaps the OS chevron for the mark the rest of the site
   draws, because a native arrow beside our own inputs is the seam a visitor notices. */
.enquiry__control {
  position: relative;
  display: block;
}

/* The chevron is a masked pseudo on the WRAPPER, painted in `--ink-quiet`. On the
   control itself a mask erases the border and background too; as a background-image
   the stroke colour has to be hardcoded, and a light-mode grey disappears on the dark
   ground — which is exactly what it did. */
.enquiry__control::after {
  content: "";
  position: absolute;
  inset-inline-end: var(--space-3);
  top: 50%;
  translate: 0 -50%;
  width: 12px;
  height: 8px;
  background-color: var(--ink-quiet);
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")
    no-repeat center / contain;
  pointer-events: none;
}

.enquiry__select {
  appearance: none;
  padding-inline-end: var(--space-6);
}


/* Helper text under a control. `.card__body` is the card's prose voice, but this is a
   caption on a field, not body copy — meta size, secondary ink, same as a label. */
.enquiry__hint {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  color: var(--ink-secondary);
}

.enquiry__hint:empty {
  display: none;
}

.enquiry__pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}

/* The pair is a grid now, so the old flex-basis would have quietly done nothing —
   min-width: 0 is what actually matters here: a grid child defaults to min-content
   and a long placeholder would otherwise push the column past its track. */
.enquiry__pair > .enquiry__field {
  min-width: 0;
}

.enquiry__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.enquiry__label {
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  color: var(--ink-secondary);
}

.enquiry__optional {
  color: var(--ink-quiet);
}

.enquiry__input {
  width: 100%;
  min-width: 0;
  /* Never below the field token — see the note at the top. */
  font-size: var(--type-size-field);
  font-family: var(--font-sans);
  line-height: 1.4;
  color: var(--ink-primary);
  background: var(--surface-page);
  border: 1px solid var(--rule-quiet);
  border-radius: 10px;
  padding: var(--space-2) var(--space-3);
}

.enquiry__input:focus-visible {
  outline: none;
  border-color: var(--accent-primary);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent-primary) 25%, transparent);
}

.enquiry__textarea {
  resize: vertical;
  /* Four rows, not eight: the tallest single control on the page, and the one whose
     height buys the least. It still grows by drag for someone writing at length. */
  min-height: 4.5rem;
}

.enquiry__privacy,
.enquiry__error {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  color: var(--ink-secondary);
}

.enquiry__error {
  color: var(--accent-critical, #8c2f22);
}

.enquiry__actions {
  display: flex;
}

/* Both buttons are `.cta-companion` in the markup — the site's CTA (6px radius, 48px
   target, --ink-on-accent). What stays here is only the state the CTA does not carry:
   a submit that is mid-flight. The pill this file used to draw was not the CTA. */

.enquiry__again {
  color: var(--accent-primary);
  background: transparent;
}

.enquiry__done {
  max-width: 34rem;
  margin-top: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  align-items: flex-start;
}

.enquiry__done-h {
  margin: 0;
  font-family: var(--font-display, var(--font-sans));
  font-size: var(--type-size-heading-3, 1.25rem);
  color: var(--ink-primary);
}


/* `display: flex` beats the `hidden` attribute's UA `display: none`, so hiding the form or
   the thank-you needs saying twice. Without this the script sets `hidden` and nothing moves.
*/
.enquiry[hidden],
.enquiry__done[hidden],
.enquiry__error[hidden] {
  display: none;
}

/* ── Compact ───────────────────────────────────────────────────────────────────
   A phone shows the ask ABOVE the form rather than beside it, so every line up there
   is a line of form pushed under the fold. The words stay — they are what explains
   one form for three things — but they get the compact end of the type scale, and
   the eyebrow goes: it says "Contact" directly above a heading that says the same. */
@media (max-width: 61.99rem) {
  .enquiry-ask .zone__eyebrow {
    display: none;
  }
  .enquiry-ask .zone__intro {
    font-size: var(--type-size-small-base);
    /* It is three lines on a phone and every one of them pushes the Send button down,
       so it gets the tighter measure as well as the smaller step. */
    max-width: 40ch;
  }
  .zone--task {
    padding-block: var(--space-2) var(--space-4);
  }
  .enquiry-layout {
    gap: var(--space-3);
  }
  .enquiry-ask {
    gap: var(--space-1);
  }
  .enquiry-ask .zone__intro {
    line-height: 1.4;
  }
  /* `rows` wins over min-height, so lowering the floor did nothing — an explicit
     height is what actually shrinks it. Three lines on a phone, still draggable. */
  .enquiry__textarea {
    height: 4.75rem;
    min-height: 4.75rem;
  }
}

/* ── Compact AND short ─────────────────────────────────────────────────────────
   A 740px-tall phone cannot hold a heading, an intro and seven fields with every
   control at the 16px floor — and that floor is not negotiable (below it iOS zooms a
   focused field and never zooms back, which is a far worse bug than a missing line).

   So on a SHORT viewport the intro goes, and only there: a 844px phone keeps it. The
   heading and the subject line already say what the page is, which is why this is the
   line that can go rather than a field or a label. Keyed on height because height is
   the actual constraint — a short wide window has the same problem. */
@media (max-width: 61.99rem) and (max-height: 800px) {
  .enquiry-ask .zone__intro {
    display: none;
  }
  /* The subject hint goes with it. It explained an option label that used to read
     "Join the waitlist"; the options are plain nouns now, and the choice is the one
     thing on this form nobody misreads. */
  .enquiry__hint[data-subject-hint] {
    display: none;
  }
  .zone--task {
    padding-block: var(--space-1) var(--space-3);
  }
  /* Three lines of message rather than four, and the card's head tightened. Both are
     space above the Send button, which is the only space that counts here. */
  .enquiry__textarea {
    height: 3.5rem;
    min-height: 3.5rem;
  }
  /* `.card`'s own head padding, trimmed — the space above the first label is the
     cheapest thing to give back on a short screen. */
  .enquiry.card {
    padding-top: var(--space-2);
  }
}

/* A wide screen can be short: 1366x768 is still one of the most common laptops there
   is, and it missed the fold by six pixels. Height is the constraint, so the query
   keyed on it rather than on another width breakpoint. */
@media (min-width: 62rem) and (max-height: 820px) {
  .zone--task {
    padding-block: var(--space-4);
  }
}

/* The waiting line: the mark sits on the text's first line, not above it. `:not([hidden])`
   is load-bearing — an author `display` beats the hidden attribute's, so without it the
   script hides this line and it stays on screen. */
.confirm-pending:not([hidden]) {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
} /* MF-443 — the /contact enquiry form (waitlist · support · information) */
/* Consulted pages — the six footer legal pages and the four SEO articles. /contact's task zone over
   markdown prose, ranged left against `.zone__inner > *`'s centring: a long document reads down one
   edge. The body is the site's sans, not the article serif. */
.document {
  max-width: 46rem;
}

.document > * {
  text-align: start;
}

.document .zone__eyebrow,
.document .zone__h {
  margin-inline: 0;
}

.document .zone__h {
  max-width: none;
  margin-bottom: var(--space-2);
}

.document__meta {
  margin: 0 0 var(--space-5);
  color: var(--ink-quiet);
  font-size: var(--type-size-meta-base);
}

/* Markdown-rendered body: selectors target the tags, the source carries no classes. */
.document__body h2 {
  margin: var(--space-5) 0 var(--space-2);
  font-family: var(--font-serif);
  font-size: clamp(1.35rem, 2.6vw, 1.75rem);
  font-weight: var(--type-weight-regular);
  line-height: var(--type-line-height-tight);
  letter-spacing: var(--type-tracking-tight);
}

.document__body h3 {
  margin: var(--space-4) 0 var(--space-2);
  font-family: var(--font-serif);
  font-size: 1.2rem;
  font-weight: var(--type-weight-regular);
  line-height: var(--type-line-height-tight);
}

.document__body p,
.document__body ul,
.document__body ol {
  margin: 0 0 var(--space-3);
  color: var(--ink-secondary);
  font-size: var(--type-size-body-base);
  line-height: var(--type-line-height-loose);
}

.document__body ul,
.document__body ol {
  padding-left: 1.25rem;
}

.document__body li {
  margin-bottom: var(--space-1);
}

.document__body a {
  color: var(--accent-primary);
  text-decoration: underline;
  text-decoration-color: var(--rule-quiet);
  text-underline-offset: 0.2em;
  transition: text-decoration-color var(--motion-quick) var(--easing);
}

.document__body a:hover,
.document__body a:focus-visible {
  text-decoration-color: var(--accent-primary);
}

.document__body a:focus-visible {
  outline: 2px solid var(--ink-primary);
  outline-offset: 2px;
  border-radius: 2px;
}

.document__body strong {
  color: var(--ink-primary);
  font-weight: var(--type-weight-semibold);
}

.document__body code {
  padding: 0.1em 0.3em;
  border: 1px solid var(--rule-quiet);
  border-radius: 3px;
  background: var(--surface-card);
  font-family: var(--font-mono);
  font-size: 0.9em;
}

.document__body blockquote {
  margin: var(--space-3) 0;
  padding-left: var(--space-3);
  border-left: 2px solid var(--rule-quiet);
  color: var(--ink-quiet);
  font-style: italic;
} /* the footer legal pages + SEO articles — /contact's task zone over markdown prose */
/* ── The brand spinner — the one loading indicator, and it IS the brand mark ──────────
   The ants lift one by one down the left arm of the V, through the vertex and up the right,
   and the accent steps with them. Geometry and timing are `ui/components`' BrandMark `working`;
   this island cannot import that package, so the mark is reproduced here for BOTH the widget
   and the pages — one copy, two callers. Change it with the library, not on its own. */
.brand-spinner {
  display: inline-flex;
  color: var(--ink-quiet);
}

.brand-spinner__mark {
  display: block;
  flex: 0 0 auto;
  /* The palette runs on the MARK, with no per-ant delay: it is the shared clock the ants read
     as they light. On the ellipse instead, every ant would light on the same hue. */
  animation: brand-ant-palette 1.12s step-end infinite;
}

.brand-spinner__mark ellipse {
  transform-box: fill-box;
  transform-origin: center;
  /* 9 ants x 0.16s = one pass. The delay IS the ant's place in the V, so it lives with the
     geometry rather than in nine inline styles at every call site. */
  animation: brand-ant-lift 1.44s ease-in-out infinite;
}
.brand-spinner__mark ellipse:nth-of-type(1) { animation-delay: 0s; }
.brand-spinner__mark ellipse:nth-of-type(2) { animation-delay: 0.16s; }
.brand-spinner__mark ellipse:nth-of-type(3) { animation-delay: 0.32s; }
.brand-spinner__mark ellipse:nth-of-type(4) { animation-delay: 0.48s; }
.brand-spinner__mark ellipse:nth-of-type(5) { animation-delay: 0.64s; }
.brand-spinner__mark ellipse:nth-of-type(6) { animation-delay: 0.8s; }
.brand-spinner__mark ellipse:nth-of-type(7) { animation-delay: 0.96s; }
.brand-spinner__mark ellipse:nth-of-type(8) { animation-delay: 1.12s; }
.brand-spinner__mark ellipse:nth-of-type(9) { animation-delay: 1.28s; }

/* Colour and lift land together, so a pass reads as one motion travelling the V. */
@keyframes brand-ant-lift {
  0%,
  55%,
  100% {
    transform: translateY(0);
    fill: currentColor;
    opacity: 0.45;
  }
  18% {
    transform: translateY(-2.5px);
    fill: var(--brand-ant-hue, var(--accent-primary));
    opacity: 1;
  }
}

/* The accent palette in wheel order, so consecutive passes are neighbours. `--accent-eu` is the
   shell's, absent here — the var fallback keeps the sequence seven steps long either way. */
@keyframes brand-ant-palette {
  0% { --brand-ant-hue: var(--accent-blue); }
  14.28% { --brand-ant-hue: var(--accent-purple); }
  28.57% { --brand-ant-hue: var(--accent-red); }
  42.86% { --brand-ant-hue: var(--accent-amber); }
  57.14% { --brand-ant-hue: var(--accent-eu, var(--accent-primary)); }
  71.43% { --brand-ant-hue: var(--accent-green); }
  85.71% { --brand-ant-hue: var(--accent-turquoise); }
}

/* Motion off: the mark still reads as "working" from its status role and its place on the page. */
@media (prefers-reduced-motion: reduce) {
  .brand-spinner__mark,
  .brand-spinner__mark ellipse {
    animation: none;
  }
} /* the brand mark, working — widget + pages */
/* ──────────────────────────────────────────────────────────────────────────
   tokens/widget.css — Vompanion widget shell styles (Story 4.2).

   Shipped in the main tokens.css bundle (always available on every surface)
   so the CTA stub button rendered by companion-mount.eta's inline script is styled
   before the widget JS lazy-loads.  Dead rules on COMPANION_ENABLED=false pages
   (no widget elements exist in the DOM).

   Consumed by:
     • .companion-cta         — CTA stub (inline script) + React Dialog.Trigger
     • .companion-overlay     — Dialog overlay (mobile only)
     • .companion-dialog      — floating card (lower-right desktop / full-viewport mobile)
     • .companion-status-banner — Article 50 disclosure (Story 4.4 sets canonical copy)
     • .companion-chat-window — conversation log (Story 4.3 populates)
     • .companion-input-bar / .companion-input — message input (Story 4.3 enables)
     • .companion-close       — close button
   ────────────────────────────────────────────────────────────────────────── */

/* ── Nav CTA button (Story 4.6) — inline header affordance ──────────────── */
/* Quiet chrome control — mirrors .mode-toggle / .locale-switcher-summary
   pixel-for-pixel (Tom 2026-05-13): same 32×32 square, same border /
   padding / radius / colour rhythm so the three utility-nav controls
   read as a single set. The primary "open chat" affordance remains the
   floating .companion-cta. */
.nav-companion-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0.375rem;
  background: transparent;
  color: var(--ink-secondary);
  border: 1px solid var(--rule-quiet);
  border-radius: 6px;
  cursor: pointer;
  flex-shrink: 0;
  line-height: 0;
  /* Story 4.7: `.nav-companion-btn` is now an <a> (was <button>). Suppress the
     browser-default anchor underline so the icon-only chrome control stays
     visually identical to its prior button form. */
  text-decoration: none;
  transition:
    color var(--motion-quick) var(--easing),
    border-color var(--motion-quick) var(--easing);
}

.nav-companion-btn:hover {
  color: var(--accent-ink);
  border-color: var(--accent-primary);
  box-shadow: var(--hover-glow);
}

.nav-companion-btn__icon {
  display: block;
}

.nav-companion-btn:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
  border-radius: 6px;
}

@media (prefers-reduced-motion: reduce) {
  .nav-companion-btn {
    transition: none;
  }
}

/* ── CTA trigger button ──────────────────────────────────────────────────── */
/* The collapsed bubble — the shell's glass bubble, not a solid tile, so Vompanion
   on the marketing site reads as Vompanion in the product. */
.companion-cta {
  position: fixed;
  bottom: 28px;
  right: 28px;
  z-index: 990;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  padding: 0;
  background: var(--color-glass-fill);
  color: var(--ink-primary);
  border: 1px solid var(--color-glass-rim);
  border-radius: 50%;
  backdrop-filter: var(--motion-glass-lens-over);
  -webkit-backdrop-filter: var(--motion-glass-lens-over);
  cursor: pointer;
  line-height: 0;
  box-shadow: var(--color-glass-shadow);
  transition:
    box-shadow var(--motion-quick, 150ms) var(--easing, ease),
    transform var(--motion-quick, 150ms) var(--easing, ease);
}

.companion-cta__icon {
  display: block;
}

/* Hover box-shadow lift + translate removed — fill + resting shadow stay.
   Scale + shine in cta-interaction.css are the sole hover signal. */

/* The ring follows the bubble. It used to force the old square's 6px radius,
   which showed the moment focus returned here on close. */
.companion-cta:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 3px;
  border-radius: 50%;
}

@media (prefers-reduced-motion: reduce) {
  .companion-cta {
    transition: none;
  }
}

/* ── Overlay (mobile only — desktop floats without backdrop) ─────────────── */
.companion-overlay {
  display: none;
}

@media (max-width: 639px) {
  .companion-overlay {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    z-index: 9998;
    animation: companion-overlay-in var(--motion-appear, 180ms) ease;
  }

  @media (prefers-reduced-motion: reduce) {
    .companion-overlay {
      animation: none;
    }
  }
}

@keyframes companion-overlay-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ── Dialog card ─────────────────────────────────────────────────────────── */
/* overflow:visible so .companion-close can sit outside the panel (Tom 2026-05-13
   cosmetic pass: the closing cross is anchored above the dialog, not inside).
   The chat-window has its own overflow-y:auto so message history still
   scrolls independently. */
/* The open panel — a pane of the page's glass, placed and sized by the frame
   (CompanionFrame.vue writes left/top/width/height inline, and remembers them), so
   no fixed corner offsets here: they would fight the drag. */
/* ── Field size (MF-589) ─────────────────────────────────────────────────
   The widget's fields named `--type-size-field` and nobody declared it here, so `var()` fell
   back to the inherited body size: every input rendered at 17px. Defined the way the shell does
   (ui/app/src/shell.css): the small token on a precise pointer, 16px where the finger is the
   pointer — iOS zooms a focused sub-16px control and never zooms back. */
.companion-dialog {
  --type-size-field: var(--type-size-small-base);
}
@media (pointer: coarse) {
  .companion-dialog {
    --type-size-field: 16px;
  }
}

/* ── Scrollbars (MF-588) ─────────────────────────────────────────────────
   The shell's rule (ui/app/src/shell.css "Scrollbars"), verbatim: STANDARD properties only —
   never `::-webkit-scrollbar`, which turns macOS overlay scrollbars into classic ones that claim
   width. `scrollbar-width` does not inherit, so it lands on every scroller under the dialog.
   Copied, not shared: the shell consumes no brand package today, so there is no common sheet. */
.companion-dialog {
  --scrollbar-thumb: color-mix(in srgb, var(--ink-primary) 24%, transparent);
}
.companion-dialog,
.companion-dialog * {
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar-thumb) transparent;
}

.companion-dialog {
  position: fixed;
  display: flex;
  flex-direction: column;
  min-width: 320px;
  min-height: 380px;
  background: var(--color-glass-fill);
  border: 1px solid var(--color-glass-rim);
  border-radius: 14px;
  box-shadow: var(--color-glass-shadow-lift);
  backdrop-filter: var(--motion-glass-lens-over);
  -webkit-backdrop-filter: var(--motion-glass-lens-over);
  z-index: 9999;
  overflow: hidden;
  /* Slow, soft glide between stages (wide → docked, docked ↔ window). */
  --companion-glide: 900ms cubic-bezier(0.65, 0, 0.35, 1);
  transition:
    left var(--companion-glide),
    top var(--companion-glide),
    width var(--companion-glide),
    height var(--companion-glide),
    box-shadow var(--companion-glide);
}
.companion-dialog--gesture {
  transition: none;
}
/* Opening and closing: a slow fade and lift from the corner. */
.companion-pop-enter-active,
.companion-pop-leave-active {
  transition:
    opacity 650ms cubic-bezier(0.33, 1, 0.68, 1),
    transform 650ms cubic-bezier(0.33, 1, 0.68, 1),
    left var(--companion-glide),
    top var(--companion-glide),
    width var(--companion-glide),
    height var(--companion-glide),
    box-shadow var(--companion-glide);
}
.companion-pop-leave-active {
  transition-duration: 450ms;
}
.companion-pop-enter-from,
.companion-pop-leave-to {
  opacity: 0;
  transform: translateY(1rem) scale(0.97);
}

/* Drag handle + identity. The mark is Vompanion's, the same one the shell wears. */
.companion-dialog__bar {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-2, 0.5rem);
  padding: 10px 10px 10px 14px;
  border-bottom: 1px solid var(--color-glass-rim);
  cursor: grab;
  touch-action: none;
  user-select: none;
}

.companion-dialog__bar:active {
  cursor: grabbing;
}

/* Docked: pinned to the corner, so the bar is a plain header, not a grab handle. */
.companion-dialog--docked .companion-dialog__bar,
.companion-dialog--docked .companion-dialog__bar:active,
.companion-dialog--wide .companion-dialog__bar,
.companion-dialog--wide .companion-dialog__bar:active {
  cursor: default;
}

.companion-dialog__mark {
  flex: none;
  width: 28px;
  height: 28px;
  color: var(--accent-primary);
}

.companion-dialog__heading {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

/* In the bar, the Article 50 line is a quiet second line under the name —
   always on screen, which the foot-of-panel strip was not once the
   conversation grew. */
.companion-dialog__bar .companion-status-banner {
  margin: 0;
  padding: 0;
  border: none;
  background: none;
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  line-height: 1.3;
  color: var(--ink-quiet);
}

.companion-dialog__title {
  min-width: 0;
  font-family: var(--font-sans);
  font-size: var(--type-size-small-base);
  font-weight: var(--type-weight-semibold);
  color: var(--ink-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The body owns the scroll so the bar and the composer stay put. */
/* The split (MF-441): conversation on the left, the form drawer on the right.
   With no form open the conversation is simply the only child and takes it all. */
.companion-dialog__body {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: row;
}

/* The conversation half owns the log, the reopen offer and the composer, so the
   composer keeps the bottom edge of THIS column rather than the whole window. */
.companion-conversation {
  position: relative;
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* ── Resize handles ──────────────────────────────────────────────────────
   The window's own EDGES resize it, the way the shell's floating windows do.
   Each is an invisible 8px strip lying just inside the border; the four
   corners sit above the sides so a corner drag wins where they overlap. */
.companion-dialog__edge {
  position: absolute;
  touch-action: none;
  z-index: 4;
}

.companion-dialog__edge--n,
.companion-dialog__edge--s {
  right: 8px;
  left: 8px;
  height: 8px;
  cursor: ns-resize;
}

.companion-dialog__edge--n {
  top: -2px;
}

.companion-dialog__edge--s {
  bottom: -2px;
}

.companion-dialog__edge--e,
.companion-dialog__edge--w {
  top: 8px;
  bottom: 8px;
  width: 8px;
  cursor: ew-resize;
}

.companion-dialog__edge--e {
  right: -2px;
}

.companion-dialog__edge--w {
  left: -2px;
}

.companion-dialog__edge--ne,
.companion-dialog__edge--nw,
.companion-dialog__edge--se,
.companion-dialog__edge--sw {
  width: 14px;
  height: 14px;
  z-index: 5;
}

.companion-dialog__edge--nw {
  top: -2px;
  left: -2px;
  cursor: nwse-resize;
}

.companion-dialog__edge--ne {
  top: -2px;
  right: -2px;
  cursor: nesw-resize;
}

.companion-dialog__edge--sw {
  bottom: -2px;
  left: -2px;
  cursor: nesw-resize;
}

.companion-dialog__edge--se {
  right: -2px;
  bottom: -2px;
  cursor: nwse-resize;
}

/* A hint of the grab affordance in the bottom-right, as before. */
.companion-dialog__edge--se::before {
  content: "";
  position: absolute;
  right: 5px;
  bottom: 5px;
  width: 7px;
  height: 7px;
  border-right: 2px solid var(--ink-quiet);
  border-bottom: 2px solid var(--ink-quiet);
  opacity: 0.55;
}

/* The expand control sits beside the close, same size and quiet ink. */
.companion-expand {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: var(--ink-secondary);
  cursor: pointer;
}

.companion-expand svg {
  width: 16px;
  height: 16px;
}

.companion-expand:hover {
  color: var(--ink-primary);
  background: color-mix(in srgb, var(--ink-primary) 8%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .companion-dialog,
  .companion-pop-enter-active,
  .companion-pop-leave-active {
    transition: none;
  }
}

/* Mobile: full viewport via CSS-only sizing — no JS-driven layout */
@media (max-width: 639px) {
  .companion-dialog {
    inset: 0;
    width: 100%;
    max-width: 100%;
    max-height: 100%;
    height: 100%;
    border-radius: 0;
    right: 0;
    bottom: 0;
  }
}

/* ── Article 50 status banner — pinned to top of dialog ──────────────────── */
.companion-status-banner {
  padding: var(--space-1) var(--space-3);
  background: var(--surface-page);
  border-top: 1px solid var(--rule-quiet);
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  line-height: var(--type-line-height-loose);
  /* --ink-secondary, not --ink-quiet: --ink-quiet on --surface-page (this
     banner's background) measures 4.47:1, 0.03 short of WCAG 2.2 AA (4.5:1).
     Same call as .site-footer-copy. The Article 50 disclosure is a legal
     notice and must clear AA. */
  color: var(--ink-secondary);
  flex-shrink: 0;
}

/* ── Conversation log ────────────────────────────────────────────────────── */
/* The chat-window owns the only scrollbar inside the chat surface. The
   widget overflow:visible (above) lets the close button escape; this
   element keeps its own clip via border-radius on .companion-dialog. */
.companion-chat-window {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Foot room for the composer that floats over this log: its measured height (set by the
     widget from a ResizeObserver), 6.5rem before the first measurement. TWICE the gap, because
     the dock itself sits `bottom: var(--space-3)` — one gap pays that offset, the second is
     the space between the last bubble and the composer (MF-487). */
  padding: var(--space-3) var(--space-3) calc(var(--companion-dock-h, 6.5rem) + var(--space-3) * 2);
  min-height: 160px;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  border-top-left-radius: 8px;
  border-top-right-radius: 8px;
}

/* ── Chat messages (Story 4.3) ───────────────────────────────────────────── */
/* Brand-minimal transcript: user replies sit in an ink-filled bubble flush
   right; assistant replies are plain ink-on-card text flush left — reads
   like a transcript, not a chat-app. */
.companion-message {
  font-family: var(--font-sans);
  /* One step under the page body (MF-589): a transcript, not an article. */
  font-size: var(--type-size-small-base);
  line-height: var(--type-line-height-base);
  color: var(--ink-primary);
  white-space: pre-wrap;
  word-wrap: break-word;
}

.companion-message--user {
  align-self: flex-end;
  max-width: 80%;
  padding: 0.375rem var(--space-2);
  background: var(--ink-primary);
  color: var(--surface-page);
  border-radius: 0.75rem;
}

/* Vompanion speaks in a bubble too (MF-360) — the app's incoming bubble, mirrored here because the
   widget ships Vue and nothing else. Hers is the card lane, the visitor's the inked one above. */
.companion-message--assistant {
  align-self: flex-start;
  max-width: 85%;
  padding: 0.375rem var(--space-2);
  background: var(--surface-card);
  border: 1px solid var(--rule-quiet);
  border-radius: 0.75rem;
  /* Rendered markdown (MF-351): block spacing comes from the elements. */
  white-space: normal;
}
.companion-md p {
  margin: 0 0 0.5em;
  white-space: pre-wrap;
}
.companion-md ul,
.companion-md ol {
  margin: 0 0 0.5em;
  padding-left: 1.25em;
}
.companion-md li {
  margin: 0.125em 0;
}
.companion-md > :last-child {
  margin-bottom: 0;
}
.companion-md code {
  font-family: var(--font-mono, monospace);
  font-size: 0.9em;
}
.companion-md a {
  color: var(--accent-primary);
  text-decoration: underline;
  text-underline-offset: 0.15em;
  overflow-wrap: anywhere;
}

/* ── Loading dots — assistant streaming indicator ────────────────────────── */
/* Vucie thinking: the shared brand spinner, placed in the log's lane. */
.companion-loading {
  align-self: flex-start;
  padding: 0.25rem 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
/* MF-586: the words beside the mark, in the chrome's meta voice. */
.companion-loading__text {
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  color: var(--ink-secondary);
}

/* ── Error state — shown between log and input on error ──────────────────── */
/* A failed turn is something that happened to ONE message, not a page-level alert
   (MF-480): the note sits in the log, in Vompanion's lane, where her reply would have been. */
.companion-error-state {
  align-self: flex-start;
  max-width: 85%;
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  line-height: var(--type-line-height-loose);
  color: var(--ink-quiet);
}

/* ── Suggested replies (MF-599) — ghost visitor bubbles ──────────────────────
   Her offers of what the visitor might say next sit in the VISITOR's lane, shaped like their
   bubble but unsent: dashed outline, no fill. A tap sends the text as theirs; the composer below
   stays the way to say anything else. Not the app's Go chip — that is her action, this is their
   draft. */
.companion-suggestions {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.375rem;
  max-width: 80%;
  align-self: flex-end;
}
.companion-suggestion {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  /* The visitor bubble's box, hollow: same radius and padding, a dashed ink line instead of the fill. */
  min-height: 44px;
  padding: 0.375rem var(--space-2);
  border: 1px dashed color-mix(in srgb, var(--ink-primary) 55%, transparent);
  border-radius: 0.75rem;
  background: transparent;
  color: var(--ink-primary);
  font-family: var(--font-sans);
  font-size: var(--type-size-small-base);
  line-height: var(--type-line-height-base);
  text-align: left;
  cursor: pointer;
  transition:
    background-color 120ms ease,
    border-color 120ms ease;
}
.companion-suggestion:hover,
.companion-suggestion:focus-visible {
  border-style: solid;
  background: color-mix(in srgb, var(--ink-primary) 8%, transparent);
}
.companion-suggestion:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}
.companion-suggestion__glyph {
  flex: 0 0 auto;
  width: 0.875rem;
  height: 0.875rem;
  opacity: 0.6;
}

/* The way back sits on the message that asked, inside its bubble — an underlined word,
   not a second button competing with Send. */
.companion-message__retry {
  display: block;
  margin-top: 0.25rem;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--type-size-meta-base);
  color: inherit;
  opacity: 0.75;
  text-decoration: underline;
  text-underline-offset: 0.15em;
  cursor: pointer;
}
.companion-message__retry:hover,
.companion-message__retry:focus-visible {
  opacity: 1;
}

/* ── Flow drawer (MF-352, MF-441) — the RIGHT half of the window ─────────────
   A column beside the conversation, its left edge the resize grip. On a phone it is
   not a narrower drawer but the whole screen (see the mobile block below). */
.companion-flow {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  background: var(--surface-page);
  border-left: 1px solid var(--rule-quiet);
}
/* The grip lies down the left edge; it is thin to look at and 12px to hit. */
.companion-flow__grip {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 12px;
  margin-left: -6px;
  cursor: col-resize;
  touch-action: none;
  z-index: 1;
}
.companion-flow__grip::after {
  content: "";
  position: absolute;
  left: 5px;
  top: 50%;
  width: 3px;
  height: 34px;
  transform: translateY(-50%);
  border-radius: 999px;
  background: var(--rule-quiet);
}
.companion-flow__grip:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: -2px;
}
.companion-flow__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  flex: none;
  padding: var(--space-2) var(--space-3) 0;
}
.companion-flow__close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  flex: none;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--ink-secondary);
  cursor: pointer;
}
.companion-flow__close svg {
  width: 16px;
  height: 16px;
}
.companion-flow__close:hover {
  background: color-mix(in srgb, var(--ink-primary) 8%, transparent);
  color: var(--ink-primary);
}
.companion-flow__close:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}

/* The form is CENTRED in the drawer (MF-441): a measure of its own, not fields
   stretched to whatever width the split happens to have. */
.companion-flow__centre {
  width: 100%;
  max-width: 22rem;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* On a phone the form is the WHOLE screen (MF-441), dismissed by a swipe either
   way or by the cross. Must follow the base .companion-flow rules, or their
   position: relative wins and the form squeezes beside the conversation. */
@media (max-width: 639px) {
  .companion-flow {
    position: absolute;
    inset: 0;
    width: auto !important; /* the drag width is a desktop notion */
    border-left: none;
    z-index: 3;
    /* The swipe is ours to read; the browser must not claim it for a scroll. */
    touch-action: pan-y;
  }
  /* Nothing to drag when the form is the whole screen. */
  .companion-flow__grip {
    display: none;
  }
  .companion-flow__centre {
    max-width: 26rem;
  }
}

/* A form put away leaves this in the conversation — the way back to it. */
/* MF-459: the retention line under the composer — quiet, one line, never in the way. */
.companion-privacy {
  margin: var(--space-1) 0 0;
  padding: 0 var(--space-2);
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  line-height: 1.3;
  color: var(--ink-quiet);
  text-align: center;
}

.companion-privacy a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

/* The rail between the conversation and the form, for as long as a form exists: a thin
   strip on the edge the form folds into, one chevron in the middle. Replaces the MF-590
   pull over the log, which sat where nobody looked. */
.companion-flow-rail {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  border-left: 1px solid var(--rule-quiet);
  background: var(--surface-page);
}
.companion-flow-rail__toggle {
  position: relative;
  z-index: 2; /* over the sheet's grip, which overhangs the rail by 6px */
  display: inline-grid;
  place-items: center;
  width: 20px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--rule-quiet);
  border-radius: 999px;
  background: var(--surface-card);
  color: var(--ink-secondary);
  cursor: pointer;
}
.companion-flow-rail__toggle:hover {
  color: var(--ink-primary);
  border-color: var(--ink-secondary);
}
.companion-flow-rail__toggle:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}
/* A thumb needs more than 20px to land on. */
@media (pointer: coarse) {
  .companion-flow-rail {
    width: 32px;
  }
  .companion-flow-rail__toggle {
    width: 28px;
    height: 56px;
  }
}

/* The form scrolls; .companion-flow__centre inside it holds the measure. */
.companion-flow__form {
  display: flex;
  min-height: 0;
  overflow-y: auto;
  padding: var(--space-2) var(--space-3) var(--space-3);
}
/* First and last name share a line — two short fields do not deserve two rows of a short drawer. */
.companion-flow__pair {
  display: flex;
  gap: var(--space-2);
}
.companion-flow__pair > .companion-flow__field {
  flex: 1 1 0;
  min-width: 0;
}
.companion-flow__title {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--type-size-body-base);
  font-weight: 600;
  color: var(--ink-primary);
}
.companion-flow__intro,
.companion-flow__field span {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  line-height: var(--type-line-height-loose);
  color: var(--ink-secondary);
}
.companion-flow__field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.companion-flow__input {
  /* Never below the field token: iOS zooms a focused sub-16px control and never zooms back. */
  font-size: var(--type-size-field);
  font-family: var(--font-sans);
  color: var(--ink-primary);
  background: transparent;
  border: 1px solid var(--rule-quiet);
  border-radius: 8px;
  padding: 0.5rem 0.625rem;
  min-width: 0;
}
.companion-flow__input:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 1px;
}
/* The enquiry form's select (MF-489): a transparent background leaves the option list
   unreadable on the dark substrate. */
select.companion-flow__input {
  background: var(--surface-card);
  appearance: none;
}
.companion-flow__textarea {
  resize: vertical;
  min-height: 5.5rem;
  line-height: var(--type-line-height-loose);
}
.companion-flow__hint {
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  color: var(--ink-secondary);
}
/* MF-587: a field that failed says so under itself, in the ink the notice uses. */
.companion-flow__hint--error {
  color: var(--ink-primary);
}
.companion-flow__input[aria-invalid="true"] {
  border-color: var(--ink-primary);
}
.companion-flow__error {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  color: var(--ink-primary);
}
.companion-flow__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-1);
}
.companion-flow__submit,
.companion-flow__cancel {
  font-family: var(--font-sans);
  font-size: var(--type-size-body-base);
  border-radius: 8px;
  padding: 0.5rem 0.875rem;
  cursor: pointer;
}
.companion-flow__submit {
  border: 1px solid var(--cta-face);
  background: var(--cta-face);
  color: var(--cta-label);
}
.companion-flow__submit:disabled {
  opacity: 0.6;
  cursor: default;
}
.companion-flow__cancel {
  border: 1px solid transparent;
  background: transparent;
  color: var(--ink-secondary);
}

/* ── Input bar ───────────────────────────────────────────────────────────── */
/* The composer is a glass box floating OVER the conversation, the way it sits
   in the app — not a bar welded under it. The log scrolls beneath, with room
   at its foot so the last message never hides behind the composer. */
/* The dock floats over the conversation; the composer is the glass box in it
   and the disclosure sits just beneath, inside the same block. */
.companion-dock {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  position: absolute;
  right: var(--space-3);
  bottom: var(--space-3);
  left: var(--space-3);
  z-index: 2;
}

/* The app's ComposerBox shape: a column — field on top, tools beneath — in one
   rounded glass box that floats over the conversation. */
.companion-input-bar {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  background: var(--color-glass-fill);
  border: 1px solid var(--color-glass-rim);
  border-radius: 16px;
  box-shadow: var(--color-glass-shadow);
  backdrop-filter: var(--motion-glass-lens-over);
  -webkit-backdrop-filter: var(--motion-glass-lens-over);
}

.companion-input-bar:focus-within {
  border-color: color-mix(in srgb, var(--ink-primary) 30%, transparent);
}

.companion-composer__tools {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.companion-composer__spacer {
  flex: 1;
}

.companion-composer__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--ink-quiet);
  cursor: pointer;
}

.companion-composer__icon svg {
  width: 18px;
  height: 18px;
}

.companion-composer__icon:hover:not(:disabled) {
  color: var(--ink-primary);
  background: color-mix(in srgb, var(--ink-primary) 6%, transparent);
}

.companion-composer__icon:disabled {
  opacity: 0.5;
  cursor: default;
}

/* Sendable = the accent-INKED glyph, the workspace convention; never a pill. */
.companion-submit:not(:disabled) {
  color: var(--accent-primary);
}

.companion-submit:disabled {
  color: var(--ink-quiet);
}

/* ── Emoji palette ──────────────────────────────────────────────────────── */
.companion-emoji {
  position: relative;
  display: inline-flex;
}

.companion-emoji__panel {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  z-index: 3;
  display: grid;
  grid-template-columns: repeat(8, 1.75rem);
  gap: 2px;
  padding: var(--space-2);
  background: var(--color-glass-fill);
  border: 1px solid var(--color-glass-rim);
  border-radius: 12px;
  box-shadow: var(--color-glass-shadow-lift);
  backdrop-filter: var(--motion-glass-lens-over);
  -webkit-backdrop-filter: var(--motion-glass-lens-over);
}

.companion-emoji__item {
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: transparent;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}

.companion-emoji__item:hover {
  background: color-mix(in srgb, var(--ink-primary) 8%, transparent);
}

/* MF-442: font-size and background were each declared TWICE here, and the second
   pair won — an opaque box at body size inside the glass, where the app's composer
   is a transparent field at the 16px form token. */
.companion-input {
  width: 100%;
  min-width: 0;
  /* Never below the field token: iOS zooms a focused sub-16px control and
     never zooms back. */
  font-size: var(--type-size-field);
  font-family: var(--font-sans);
  line-height: 1.4;
  color: var(--ink-primary);
  resize: none;
  border: none;
  background: transparent;
  outline: none;
  padding: 6px 0;
}

.companion-input:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* The BOX carries the focus, not the field inside it — the app's composer
   brightens its own border on :focus-within, and a second rectangle drawn
   around the textarea reads as a nested input. Keyboard users still get a
   clear focus signal: the box's border lifts (see .companion-input-bar). */
.companion-input:focus-visible {
  outline: none;
}

/* The send control is the app's: an accent-INKED glyph in the tools row, not a
   filled pill. Size, radius and hover come from .companion-composer__icon; only
   the ink is its own, up with the composer rules. It carries no transition,
   so there is nothing here for reduced motion to switch off. */

/* ── Sentence-boundary live announcer (Story 4.5 AC3) ───────────────────── */
/* Visually hidden but accessible to screen readers (sr-only pattern). */
.companion-live-announcer {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── Close button ────────────────────────────────────────────────────────── */
/* In the title bar, beside the name. It used to be anchored OUTSIDE the panel
   with `top: -2.5rem`, which under the new frame put it over the sticky site
   header — and the header took the click. */
.companion-close {
  flex: none;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  background: transparent;
  cursor: pointer;
  border-radius: 6px;
  font-size: 1.125rem;
  line-height: 1;
  color: var(--ink-secondary, #555);
  transition:
    color var(--motion-quick, 150ms) var(--easing, ease),
    border-color var(--motion-quick, 150ms) var(--easing, ease);
}

.companion-close:hover {
  color: var(--ink-primary);
  border-color: var(--metal-platine, var(--ink-primary));
}

.companion-close:focus-visible {
  outline: 2px solid var(--ink-primary, #1a1a1a);
  outline-offset: 2px;
  border-radius: 6px;
}

@media (max-width: 639px) {
  /* Mobile full-viewport dialog: tuck the close back inside the top-right
     corner since there is no "outside" left to occupy. */
  .companion-close {
    top: var(--space-2, 0.5rem);
    right: var(--space-2, 0.5rem);
    background: transparent;
    border-color: transparent;
    box-shadow: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .companion-close {
    transition: none;
  }
}

/* ─────────────────────────────────────────────────────────────────────────
   Story 4.7 — dedicated /companion chat surface.

   Page-scroll lock (Tom 2026-05-13): on /companion the document does NOT
   scroll — header and site-footer are always visible, and the only
   scrollable region is the chat-window (.companion-chat-window). This is
   the conversational-app convention: history scrolls inside the panel,
   the page chrome stays put.

   Layout:
     body[data-surface="companion-page"]   100svh flex column, overflow:hidden
       └ header.site                   static (sticky is moot on a non-
                                       scrolling page; static avoids the
                                       sticky-on-flex-child quirk)
       └ main#main                     flex:1, overflow:hidden, flex column
         └ .companion-page                 flex:1, min-height:0, overflow:hidden
           ├ .companion-hero               compact, content-height
           └ .companion-page-shell         flex:1, min-height:0, overflow:hidden
             └ .companion-chat-window      flex:1, overflow-y:auto (THE scrollbar)
       └ site-footer                   static, flex-shrink:0

   Scoped via the body[data-surface] attribute so every other surface
   (landing, products, solutions, etc.) keeps its normal scrolling
   document. */
body[data-surface="companion-page"] {
  height: 100svh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
body[data-surface="companion-page"] > header.site {
  position: static;
  flex-shrink: 0;
}
body[data-surface="companion-page"] > main#main {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
body[data-surface="companion-page"] > .site-footer {
  flex-shrink: 0;
}

.companion-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  max-width: var(--measure);
  margin: 0 auto;
  padding: var(--space-3) var(--space-3);
  box-sizing: border-box;
  gap: var(--space-3);
  overflow: hidden;
}
@media (min-width: 768px) {
  .companion-page {
    padding: var(--space-3) var(--space-4) var(--space-4);
  }
}

/* Hero block — compact horizontal cluster (Tom 2026-05-13: less space).
   Logo + H1 share a baseline-aligned row; the invite line sits as a
   small caption directly below. The whole block is content-height —
   no padding bloat. */
.companion-hero {
  display: grid;
  grid-template-columns: auto auto;
  grid-template-rows: auto auto;
  column-gap: var(--space-2);
  row-gap: 0.125rem;
  align-items: center;
  justify-content: center;
  text-align: left;
  margin: 0;
  flex-shrink: 0;
}

.companion-hero__mark {
  grid-row: 1 / span 2;
  width: 40px;
  height: 40px;
  display: block;
  border-radius: 8px;
}
@media (min-width: 768px) {
  .companion-hero__mark {
    width: 48px;
    height: 48px;
  }
}

.companion-hero__title {
  margin: 0;
  font-family: var(--font-serif, var(--font-sans));
  font-size: 1.375rem;
  line-height: 1.1;
  color: var(--ink-primary);
  letter-spacing: -0.01em;
  align-self: end;
}
@media (min-width: 768px) {
  .companion-hero__title {
    font-size: 1.625rem;
  }
}

.companion-hero__invite {
  margin: 0;
  color: var(--ink-secondary);
  font-family: var(--font-sans);
  font-size: var(--type-size-meta-base);
  line-height: 1.35;
  align-self: start;
}

/* #companion-page-root is the React mount point — the bundle injects
   .companion-page-shell inside it. Make it a transparent flex pass-through
   so the flex:1 + min-height:0 chain reaches all the way down to the
   chat-window's overflow boundary. */
#companion-page-root {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  align-items: center;
}

.companion-page-shell {
  display: flex;
  flex-direction: column;
  width: 100%;
  /* Chat is centered and constrained — narrower than the page measure
     because a conversation reads better in a column. */
  max-width: 44rem;
  margin: 0 auto;
  flex: 1 1 auto;
  /* min-height:0 is the unlock that lets flex:1 actually cap height,
     so .companion-chat-window's overflow-y:auto becomes effective rather
     than the chat-window growing with content. */
  min-height: 0;
  background: var(--surface-card);
  border: 1px solid var(--rule-quiet);
  border-radius: 8px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.06);
  overflow: hidden;
}

.companion-page-shell .companion-chat-window {
  border-top-left-radius: 8px;
  border-top-right-radius: 8px;
}

@media (max-width: 639px) {
  .companion-page {
    gap: var(--space-2);
    padding: var(--space-2);
  }
  .companion-page-shell {
    border-radius: 6px;
  }
}

/* Tablet/phone (paired with the companion-mount.eta redirect + footer hide at
   < 1024px): /companion is the dedicated chat surface, so the typing zone is
   pinned to the bottom of the viewport. Fixed positioning is the bulletproof
   read of "stays at the bottom of the viewport" — survives content overflow,
   keyboard reflow, and dynamic toolbars on mobile. Bottom padding on the
   page reserves space so messages don't sit behind the bar; safe-area-inset
   covers iOS home-indicator gutters. Scoped to body[data-surface="companion-page"]
   because the same .companion-input-bar selector also styles the floating
   dialog's input, which doesn't open on tablet/phone anymore but stays
   correct on desktop. */
@media (max-width: 1023.98px) {
  body[data-surface="companion-page"] .companion-page {
    padding-bottom: calc(var(--space-6) + env(safe-area-inset-bottom, 0px));
  }
  body[data-surface="companion-page"] .companion-input-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 20;
    background: var(--surface-page);
    border-top: 1px solid var(--rule-quiet);
    padding-bottom: calc(var(--space-2) + env(safe-area-inset-bottom, 0px));
  }
}

/* Wide (the hero chat's first reply): a scrim keeps the page's large type from reading through. */
.companion-dialog--wide {
  background: var(--surface-page);
  box-shadow: 0 0 0 100vmax rgb(0 0 0 / 35%), var(--color-glass-shadow);
}

/* show_page: the section she brought the visitor to, ringed for a moment on arrival. */
.companion-arrive {
  scroll-margin-top: 128px;
  border-radius: 16px;
  animation: companion-arrive 2.4s cubic-bezier(0.33, 1, 0.68, 1);
}
@keyframes companion-arrive {
  0%   { box-shadow: 0 0 0 0 transparent; }
  20%  { box-shadow: 0 0 0 2px var(--accent-primary), 0 0 0 12px color-mix(in srgb, var(--accent-primary) 18%, transparent); }
  100% { box-shadow: 0 0 0 2px transparent, 0 0 0 24px transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .companion-arrive { animation: none; outline: 2px solid var(--accent-primary); outline-offset: 8px; }
} /* Story 4.2 — Vompanion widget shell (CTA, overlay, dialog card, status banner, input, close) */
/* ──────────────────────────────────────────────────────────────────────────
   llm.css — LLM-locale markdown viewer.

   The LLM locale wraps each surface's body content in an HTML page (so
   the header remains and the locale switcher works). The body is the
   raw Markdown that will be served at the parallel `.md` URL, displayed
   here inside <pre class="llm-markdown"> so humans can see exactly what
   an LLM ingestion fetcher receives.

   Goals: monospace, soft wrap (no horizontal scroll for long lines),
   readable measure, comfortable padding, inherits the substrate
   colour tokens from layout-base.css.
   ────────────────────────────────────────────────────────────────────────── */

pre.llm-markdown {
  width: 100%;
  max-width: 56rem;
  margin: 0 auto;
  padding: var(--space-5) var(--space-3) var(--space-7);
  color: var(--ink-primary);
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
  font-size: 0.875rem;
  line-height: 1.55;
  white-space: pre-wrap;
  word-break: break-word;
  background: transparent;
} /* LLM locale — markdown viewer (pre.llm-markdown inside the HTML wrapper served at /llm/{slug}/index.html) */

/* Cross-surface hover affordance — gentle scale + left→right shine on every
   button-shape CTA. Imported LAST (after every per-surface base style). NB:
   under the brand layer this ships in @layer myrmid.foundation, so unlayered
   per-surface CSS wins regardless of import order — that is fine because the
   per-surface hover colour-flips it used to compete with were already removed
   (the AR-6 cascade-regression gate). */
@layer myrmid.foundation {
/* ──────────────────────────────────────────────────────────────────────────
   tokens/cta-interaction.css — shared hover affordance for every button.

   Two stacked effects, applied on :hover and :focus-visible:
     1. Gentle scale (1.03) — smooth grow/shrink, --motion-base (200ms).
     2. Left→right ant trail — a band of short marks in the button's own ink
        travels the surface, looping while the pointer / keyboard focus
        stays on it. Marks, not a soft shimmer: the trail is the brand's
        stigmergy, drawn mechanically.

   The trail is currentColor, never a fixed hue. The seven CTAs below do not
   share a fill — filled accent, transparent-with-accent-text, and light card
   all appear — so any fixed colour is invisible on some of them. Their own
   text colour is by definition legible on their own surface, which is what
   keeps this ONE rule instead of seven.

   Color rule: filled CTAs STAY filled on hover. The per-surface :hover blocks
   that used to invert fill/text colours (or darken via filter) have been
   removed from their respective files. Scale + shine are now the sole hover
   signal across every button-shape CTA on the site.

   In scope (every button-shape CTA — text-link CTAs intentionally untouched
   since they have no surface for a shine to traverse):

     .cta-companion                                       (hero)
     .product-cta-button                              (products + partners)
     .pricing-tier-card__cta                          (pricing tier cards)
     .pricing-enterprise__cta.pricing-cta-button      (pricing enterprise)
     .companion-cta                                       (floating widget)
     .consent-button                                  (cookie accept / reject)
     .consent-trigger                                 (consent withdrawal)

   Out of scope: .nav-companion-btn — the header chat affordance is a quiet
   chrome control (mirrors .locale-switcher-summary), not a CTA. Its hover
   treatment is owned by widget.css (border-color + colour lift).

   Reduced motion: motion-tokens.css installs a global
   prefers-reduced-motion: reduce override that zeroes transition / animation
   durations and clamps iteration count to 1. No extra guard needed here.
   ────────────────────────────────────────────────────────────────────────── */

/* ── Base: clip the shine, declare the scale transition ─────────────────── */
.cta-companion,
.product-cta-button,
.pricing-tier-card__cta,
.pricing-enterprise__cta.pricing-cta-button,
.pricing-cta-button,
.companion-cta,
.consent-button,
.consent-trigger {
  overflow: hidden;
  transition: transform var(--motion-base) var(--easing);
}

/* Story 4.7: `.cta-companion` is now an <a> (was <button>). Suppress the
   browser-default anchor underline so the filled-button visual treatment
   carries cleanly. Same applies to `.solutions-index-notsure-btn` (the
   /solutions index "not-sure" affordance also flipped to anchor). */
.cta-companion,
.solutions-index-notsure-btn {
  text-decoration: none;
}

/* Only set position: relative on CTAs that don't already have a positioning
   context. .companion-cta and .consent-trigger are position:fixed (their own
   surface CSS owns that); leaving them alone keeps them floating. */
.cta-companion,
.product-cta-button,
.pricing-tier-card__cta,
.pricing-enterprise__cta.pricing-cta-button,
.pricing-cta-button,
.consent-button {
  position: relative;
}

/* ── Shine pseudo-element ───────────────────────────────────────────────── */
.cta-companion::before,
.product-cta-button::before,
.pricing-tier-card__cta::before,
.pricing-enterprise__cta.pricing-cta-button::before,
.pricing-cta-button::before,
.companion-cta::before,
.consent-button::before,
.consent-trigger::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* Short marks rather than one soft band — a trail that reads as stepped.
     The mask fades the band's leading and trailing edge so the trail walks
     into and out of the button instead of colliding with its rim. */
  background: repeating-linear-gradient(
    90deg,
    transparent 0 7px,
    color-mix(in srgb, currentColor 42%, transparent) 7px 11px
  );
  mask-image: linear-gradient(90deg, transparent, black 45% 55%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, black 45% 55%, transparent);
  transform: translateX(-100%);
}

/* ── Scale on hover / focus ─────────────────────────────────────────────── */
.cta-companion:hover,
.cta-companion:focus-visible,
.product-cta-button:hover,
.product-cta-button:focus-visible,
.pricing-tier-card__cta:hover,
.pricing-tier-card__cta:focus-visible,
.pricing-enterprise__cta.pricing-cta-button:hover,
.pricing-enterprise__cta.pricing-cta-button:focus-visible,
.pricing-cta-button:hover,
.pricing-cta-button:focus-visible,
.companion-cta:hover,
.companion-cta:focus-visible,
.consent-button:hover,
.consent-button:focus-visible,
.consent-trigger:hover,
.consent-trigger:focus-visible {
  transform: scale(1.03);
}

/* ── Shine sweep loops while hovered / focused ──────────────────────────── */
.cta-companion:hover::before,
.cta-companion:focus-visible::before,
.product-cta-button:hover::before,
.product-cta-button:focus-visible::before,
.pricing-tier-card__cta:hover::before,
.pricing-tier-card__cta:focus-visible::before,
.pricing-enterprise__cta.pricing-cta-button:hover::before,
.pricing-enterprise__cta.pricing-cta-button:focus-visible::before,
.pricing-cta-button:hover::before,
.pricing-cta-button:focus-visible::before,
.companion-cta:hover::before,
.companion-cta:focus-visible::before,
.consent-button:hover::before,
.consent-button:focus-visible::before,
.consent-trigger:hover::before,
.consent-trigger:focus-visible::before {
  animation: cta-shine-sweep 1100ms linear infinite;
}

@keyframes cta-shine-sweep {
  from { transform: translateX(-100%); }
  to   { transform: translateX(100%); }
}
}

/* ──────────────────────────────────────────────────────────────────────────
   tokens/anchor-links.css — spec-section-anchor-links.

   Visible "link to this section" affordance injected by
   public/js/anchor-links.js next to every section heading (h2[id], h3[id]).
   Structural-only accent per the locked visual system; the button rests
   invisible and reveals on heading hover / focus-within and on its own
   keyboard focus. The opacity transition runs on --motion-quick, which
   motion-tokens.css collapses to 0ms under prefers-reduced-motion.
   ────────────────────────────────────────────────────────────────────────── */

/* Sticky-header clearance — a directly-opened #fragment must land below the
   sticky header.site, not behind it. :where() keeps specificity at 0 so
   per-surface heading rules are never overridden. */
:where(h2[id], h3[id]) {
  scroll-margin-top: calc(var(--header-height, 5rem) + var(--space-3));
}

.anchor-link {
  margin-inline-start: var(--space-1);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4em;
  height: 1.4em;
  padding: 0;
  vertical-align: middle;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--ink-quiet);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--motion-quick) var(--motion-easing-base);
}

/* Reveal on heading hover / focus-within, and whenever the button itself
   is keyboard-focused (so it is never stranded invisible during Tab). */
h2[id]:hover .anchor-link,
h3[id]:hover .anchor-link,
h2[id]:focus-within .anchor-link,
h3[id]:focus-within .anchor-link,
.anchor-link:focus-visible,
.anchor-link--copied {
  opacity: 1;
}

.anchor-link:hover {
  color: var(--ink-primary);
}

.anchor-link:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}

.anchor-link--copied {
  color: var(--accent-primary);
}

.anchor-link__icon {
  width: 1em;
  height: 1em;
}

/* Visually-hidden live region — carries the transient "copied" announcement
   to assistive tech without occupying layout. */
.anchor-live {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
} /* spec-section-anchor-links — visible "link to this section" button on every section heading + sticky-header scroll-margin clearance. */
