/* ══════════════════════════════════════════════════════════════════
   DTH Design System tokens — paper/ink editorial world
   Ported from Camille's approved landing prototype. Single theme by
   design: paper ground, ink type, coral reserved for conversion.

   The --c-* names are kept verbatim so ~3.6k lines of existing rules and
   every app.js class keep resolving; only the VALUES moved. Read the
   mapping as: surface/subtle = the paper family, primary = ink,
   accent = the mid-ink used for links + hairline emphasis, cta = coral.
   ══════════════════════════════════════════════════════════════════ */
/* ── GROUND: the platform shell's light blue, not paper cream ────────────
   Verified 2026-09-02 against the live shell, not assumed: the Next.js bundle
   at www.dentaltechhub.com sets `body { background-color: hsl(var(--background)) }`
   with `--background: 210 44% 96%` → **#F0F5F9**. The app's pre-redesign token
   was #F2F6FA, which is CLOSE BUT WRONG (2 points light) and would have left a
   visible seam at the iframe edge; #F0F5F9 is the exact value.
   In production buyers see Mola in an iframe inside that shell, so the cream
   ground (#FAF6F0) read as a warm rectangle pasted onto a cool page — the
   tension flagged in p1-retheme-notes.md §9. This resolves it.

   The warm family is retired as GROUND but the retune is not a hue swap of
   everything: hairlines and tint fills move to blue-grays of MATCHED
   LUMINANCE (not matched HSL lightness — equal HSL lightness reads darker in
   blue, so each replacement was solved for the warm original's relative
   luminance: hairline Δlum 0.0001, cream-dim Δlum 0.0016). Cards stay pure
   white, and the ink band is untouched. */
:root {
  /* ── Raw palette (prototype names, for new work + Phases 2/3) ────── */
  --paper:        #F0F5F9;  /* was #FAF6F0 cream — now the shell's blue */
  --ink:          #12263A;
  --band:         #0F2036;
  --band-soft:    #16293F;
  --card:         #FFFFFF;
  --hairline:     #D9E1E8;  /* was #E7DFD2 — luminance-matched blue-gray */
  --hairline-dark: rgba(240,245,249,.16);
  --coral:        #D9481F;
  --coral-hot:    #F0532D;
  --slate:        #5B6B7C;
  --cream:        #F7FAFC;  /* was #FFFDF7 — the "barely-there" fill, cooled */
  --cream-dim:    #E3EBF1;  /* was #F1E9D8 — luminance-matched blue-gray */

  /* ── Mapped app tokens ──────────────────────────────────────────── */
  --c-surface:        #F0F5F9;  /* the shell's blue — page ground */
  --c-subtle:         #E3EBF1;  /* tinted fills */
  --c-subtle-2:       #F7FAFC;  /* barely-there fills */
  --c-divider:        #D9E1E8;  /* hairline */
  --c-muted:          #636F7B;  /* 4.68:1 on the blue ground ✅ — the lightest
                                   AA-safe tier. DARKENED from #66727E in the
                                   background retune: the new ground is slightly
                                   darker than paper (lum .9067 vs .9253), which
                                   dropped #66727E to 4.48 — just under AA. ~20
                                   rules put real label/counter text on this
                                   token, so it is held above 4.5 rather than
                                   edited site-by-site.
                                   Still deliberately NOT the prototype's
                                   #9AA7B4 (2.28:1); that survives only as the
                                   input placeholder (contrast-exempt) — see
                                   #question-input::placeholder. */
  --c-secondary:      #5B6B7C;  /* slate — 4.98:1 on the blue ground ✅ */
  --c-accent:         #2F4A66;  /* mid-ink: links, focus rings, dots */
  --c-accent-ink:     #1B3550;  /* darker mid-ink for small text on tints */
  --c-primary:        #12263A;  /* ink — 14.03:1 on the blue ground ✅ */
  --c-primary-hover:  #0F2036;  /* band */
  --c-success:        #2F6B5C;  /* 5.66:1 on the blue ground ✅ */
  --c-success-bg:     #E6F0EC;
  --c-success-border: #C4DBD3;
  --c-warning:        #B83A16;
  --c-warning-bg:     #FBEEE8;
  --c-warning-border: #E9CFC3;
  --c-danger:         #B3261E;  /* 5.96:1 on the blue ground ✅ */
  --c-danger-bg:      #FBEAE8;
  --c-border:         #D9E1E8;  /* navbar/footer borders — matches c-divider */

  /* Conversion accent — coral, reserved EXCLUSIVELY for demo-booking
     moments (Book Demo buttons, demo nudges, the booked-a-demo success
     card). Never on compare, navigation or chrome: the value of a
     reserved accent is that it is rare. The depth-cap callout, which
     used to share the amber family, has moved to an ink-on-cream note
     (see .last-turn-callout) precisely so coral stays conversion-only.

     The coral family stays WARM on purpose. Everything else cooled to the
     shell's blue, which is exactly why coral still reads as the one reserved
     accent: it is now the only warm thing on the page.

     HARD A11Y RULES — measured, not estimated. Ratios re-measured against the
     new blue ground (#F0F5F9) on 2026-09-02; every one moved by <0.15 and
     none crossed a threshold except --c-muted, which was darkened (see above):
       --c-cta (#D9481F) as TEXT: 4.29:1 on white, 3.91:1 on the ground.
         Large/bold only (≥18.66px, or ≥14px bold). NEVER small body text.
       white on --c-cta: 4.29:1 → AA for BOLD labels ≥14px only. Every
         coral button in this file is ≥14px/600+ or 13px with a ≥14px
         equivalent weight; do not shrink one without re-checking.
       --c-cta-ink (#B83A16) is the TEXT-SAFE coral: 5.75:1 on white,
         5.24:1 on the ground, 4.77:1 on --c-cta-tint. Use it for any small
         coral text (links, tags, inline nudge copy).
       --c-cta on band/ink ground = 3.83:1 → FORBIDDEN as text. On an ink
         ground coral may only be a FILL BEHIND WHITE/CREAM, or use
         --c-cta-hot (#F0532D, 4.67:1 on band ✅) for an eyebrow.
       ink on --c-cta-tint = 13.56:1 ✅ (nudge body copy).
       On the new --c-subtle (#E3EBF1) tint: ink 12.77 ✅, slate 4.54 ✅,
         cta-ink 4.77 ✅, --c-muted 4.08 (labels on that fill must use slate
         or darker, exactly as before the retune). */
  --c-cta:            #D9481F;
  --c-cta-hover:      #F0532D;
  --c-cta-hot:        #F0532D;  /* band-ground eyebrows only */
  --c-cta-ink:        #B83A16;  /* text-safe coral */
  --c-cta-tint:       #FBEEE8;
  --c-cta-border:     #F0D3C7;
  --c-cta-glow:       rgba(217, 72, 31, .34);

  /* Display face — Source Serif 4, adopted 2026-09-21 to match the DTH design
     system (the vendor-page/showcase work and the platform site all ship
     Source Serif 4 + Inter + JetBrains Mono). Mola had drifted onto Newsreader
     + Spline Sans Mono, which read as a second brand next to the rest of the
     product. Source Serif 4 is the same broad-nib editorial class as
     Source Serif 4, so the voice notes below still hold.
     WEIGHTS: display headlines/section H2s/vendor names = 600; the why-line
     italic = 500; 700 for the rare small-caps-ish moment. Pair each display
     use with an `opsz` setting: 'opsz' 60 for 24px+, 'opsz' 16 for the 14-18px
     sites, or the cut fights the size — Source Serif 4 carries an 8..60 opsz
     axis, so every existing font-variation-settings rule still applies.
     Georgia is the fallback: same serif class, so a failed webfont degrades in
     voice rather than in layout. */
  --font-display: 'Source Serif 4', Georgia, 'Times New Roman', serif;
  --font-serif:  'Source Serif 4', Georgia, 'Times New Roman', serif;
  --font-sans:   'Inter', -apple-system, system-ui, "Segoe UI", Roboto, sans-serif;
  --font-mono:   'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, Consolas, monospace;

  --radius-sm:   6px;
  --radius-md:   10px;
  --radius-lg:   16px;
  --radius-xl:   20px;   /* the two big containers only: replay stage, composer */
  --radius-full: 999px;

  --shadow-sm:   0 1px 2px rgba(18,38,58,.05);
  --shadow-md:   0 2px 10px rgba(18,38,58,.07), 0 12px 28px -18px rgba(18,38,58,.20);
  --shadow-lg:   0 2px 6px rgba(18,38,58,.05), 0 24px 48px -24px rgba(18,38,58,.18);

  /* ── Elevation: role-named, three tiers ────────────────────────────
     ADDITIVE to --shadow-* above (~40 rules depend on those; they are not
     redefined). Named for ROLE, not size, because the rule these encode is
     "interactive elements carry depth, scenery is flat" — the inverse of
     what the landing shipped, where the stage and the composer were the only
     raised things and every card the buyer can click was a flat rectangle.
       E0  rest        a surface that is content, not a control
       E1  interactive an at-rest CONTROL — the tier that says "press me"
       E1r raised      that same control under pointer/press
       E2  overlay     something floating above the page plane
     Paper is warm, so every shadow is tinted with the ink hue rather than
     neutral black. The ground is now the shell's blue (#F0F5F9) rather than
     cream, and the ink hue (#12263A) is itself blue, so these tints sit even
     better on it than they did on paper — they are unchanged. */
  --e0:        0 1px 2px rgba(18,38,58,.05);
  --e1:        0 1px 2px rgba(18,38,58,.06),
               0 4px 10px -6px rgba(18,38,58,.14);
  --e1-raised: 0 2px 4px rgba(18,38,58,.07),
               0 16px 32px -16px rgba(18,38,58,.26);
  --e1-press:  0 1px 2px rgba(18,38,58,.10);
  --e2:        0 2px 6px rgba(18,38,58,.05),
               0 28px 56px -24px rgba(18,38,58,.30);

  /* On the ink band (#0F2036) a shadow is invisible, so elevation there is
     carried by SURFACE LIGHTNESS instead. These are the band's tiers. */
  --band-e0:   rgba(255,255,255,.045);
  --band-e1:   rgba(255,255,255,.085);
  --band-e1r:  rgba(255,255,255,.14);
  --band-line:   rgba(231,223,210,.14);
  --band-line-2: rgba(231,223,210,.30);

  /* One focus ring, everywhere, never the browser default blue. */
  --ring:      0 0 0 3px rgba(47,74,102,.28);   /* on paper */
  --ring-band: 0 0 0 3px rgba(255,253,247,.42); /* on ink   */

  --dur-tap:   90ms;    /* press-down: must feel instant */
  --dur-hover: 160ms;
  --dur-state: 240ms;   /* expand/collapse, tab swap     */
  --dur-enter: 420ms;   /* something arriving on screen  */
  --ease-out:  cubic-bezier(.22,1,.36,1);
  --ease-io:   cubic-bezier(.4,0,.2,1);

  --header-h:    69px;   /* sticky header height (incl. border) — drives composer offset */
}

/* Opacity IS the disabled convention, so on the landing it is reserved for it
   — every "secondary/quiet" thing there uses colour and surface instead (see
   the runner strips and the duel dimming). Scoped to the landing so it can't
   reach the chat surfaces' hand-tuned disabled states. */
#landing-narrative [disabled],
#landing-narrative .is-disabled {
  opacity: .45;
  box-shadow: none;
  cursor: not-allowed;
}

/* ── Motion gate ──────────────────────────────────────────────────────
   `html.motion-ok` is added by app.js when
   matchMedia('(prefers-reduced-motion: reduce)') does NOT match, and removed
   live when the OS setting flips mid-session. Every animation added for the
   demo-conversion layer hangs off this class.
   Why both this and the @media block at the bottom of the file: that block is
   a blanket duration-crusher (animation-duration: .001ms !important), which
   stops motion from being SEEN but still runs the animation and still lands
   its end state — and it can do nothing about JS-driven motion (the demo
   success burst) that would otherwise churn the DOM. The class gate lets both
   the CSS and the JS opt out cleanly. Keep the @media block as the safety net
   for pre-existing animations; new motion should be gated on the class. */
html.motion-ok { /* marker class — selectors below scope off it */ }

/* ── Reset & base ─────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

body {
  font-family: var(--font-sans);
  background: var(--c-surface);
  color: var(--c-primary);
  font-size: 15px;
  line-height: 1.6;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--c-cta); color: var(--cream); }

/* ── Shared type utilities (paper/ink system) ─────────────────────────
   Authored here for Phase 1 so Phases 2 (landing content) and 3 (results
   UI) can reach for them instead of re-deriving the styles.

   .mono        — JetBrains Mono eyebrow/label/badge: uppercase,
                  letterspaced, 11.5px. The system's third voice.
   .mono-lg     — the same at 12.5px for section eyebrows.
   .display     — Source Serif 4 600, tight tracking: headlines + vendor names.
   .why-italic  — Source Serif 4 italic 500, hairline left rule: the editorial
                  "why it fits" line. Mola's reasoning voice.
   Every one of these sits on paper/white/cream grounds only; none is
   coral-on-ink (3.83:1, forbidden — see the :root a11y note). */
.mono {
  font-family: var(--font-mono);
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
  font-size: 11.5px;
}
.mono-lg {
  font-family: var(--font-mono);
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  font-size: 12.5px;
}
.display {
  font-family: var(--font-display);
  font-weight: 600;
  font-variation-settings: 'opsz' 60;
  /* Source Serif 4 is less tightly fitted than Fraunces, so the tracking eases
     from -.02 to -.015: the old value pinched the new counters shut. */
  letter-spacing: -.015em;
  line-height: 1.1;
  text-wrap: balance;
}
.why-italic {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-variation-settings: 'opsz' 16;
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--c-primary);
  border-left: 2px solid var(--c-divider);
  padding: 1px 0 1px 12px;
}

/* ══════════════════════════════════════════════════════════════════
   NAVBAR — Platform chrome (DTH design system)
   ══════════════════════════════════════════════════════════════════ */
/* Embed mode (platform shell owns the header/footer):
   - --header-h: 0  → composer pins to the top of the iframe (no navbar),
   - padding-bottom: 0 → the footer strip is hidden, reclaim its reserved space,
   - min-height: 0  → page height is CONTENT-driven, not 100vh. Critical for the
     auto-height postMessage: a viewport-relative min-height would make the
     reported height circular (lock to the iframe size + feedback-loop on every
     resize). With it neutralized, document height reflects real content. */
body.mola-embed {
  --header-h: 0px;
  min-height: 0;
  padding-bottom: 0;
}

/* ── Slim landing navbar ──────────────────────────────────────────────
   Only one of the two headers is ever in flow. While the landing narrative is
   up (`body.landing-live`) the page wears this 40px variant — the full navbar
   costs 61px of the one fold the composer has to clear, and Camille's approved
   hero has no chrome above it at all. The first turn drops .landing-live and
   the platform navbar takes over for the rest of the session. Embed has
   neither (the template omits both). */
.navbar-slim { display: none; }
body.landing-live .navbar-slim {
  display: block;
  position: sticky;
  top: 0;
  z-index: 12;
  width: 100%;
  border-bottom: 1px solid rgba(217, 225, 232,.5);
  background: rgba(240, 245, 249,.86);
  backdrop-filter: saturate(140%) blur(8px);
  -webkit-backdrop-filter: saturate(140%) blur(8px);
}
body.landing-live .navbar { display: none; }
/* :not(.mola-embed) so the embed's --header-h: 0px is never overridden here —
   the iframe has no header of either kind. */
body.landing-live:not(.mola-embed) { --header-h: 41px; }
.navbar-slim__inner {
  width: 100%;
  max-width: 1160px;
  margin: 0 auto;
  padding: 0 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}
.navbar-slim__brand { display: flex; align-items: center; text-decoration: none; transition: opacity .15s; }
.navbar-slim__brand:hover { opacity: .8; }
.navbar-slim__logo { height: 22px; width: auto; display: block; }
.navbar-slim__signin {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  padding: 0 12px;
  border-radius: var(--radius-full);
  font-size: 13px;
  font-weight: 600;
  color: var(--c-primary);
  text-decoration: none;
  transition: background var(--dur-hover) var(--ease-io);
}
.navbar-slim__signin:hover { background: var(--c-subtle); }
.navbar-slim__signin:focus-visible { outline: none; box-shadow: var(--ring); }
@media (max-width: 720px) {
  .navbar-slim__inner { padding: 0 16px; }
  .navbar-slim__signin { min-height: 32px; }
}

.navbar {
  position: sticky;
  top: 0;
  z-index: 12;
  width: 100%;
  border-bottom: 1px solid rgba(217, 225, 232,.5);
  background: rgba(240, 245, 249,.86);
  backdrop-filter: saturate(140%) blur(8px);
  -webkit-backdrop-filter: saturate(140%) blur(8px);
}

.navbar__inner {
  width: 100%;
  max-width: 1160px;
  margin: 0 auto;
  padding: 0 40px;
  height: 60px;
  display: flex;
  align-items: center;
  gap: 20px;
}

.navbar__brand {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
  transition: opacity .15s;
  text-decoration: none;
}
.navbar__brand:hover { opacity: .80; }

.navbar__logo {
  height: 42px;
  width: auto;
}

.navbar__nav {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
}

.nav-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 7px 14px;
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  color: var(--c-secondary);
  white-space: nowrap;
  line-height: 1;
  transition: color .15s, background .15s, border-color .15s;
  text-decoration: none;
}
.nav-link:hover { color: var(--c-primary); }
.nav-link--active {
  color: var(--c-primary);
  background: #fff;
  border-color: var(--c-divider);
}

.nav-explore {
  position: relative;
}

.nav-explore__btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 7px 14px;
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  color: var(--c-secondary);
  white-space: nowrap;
  line-height: 1;
  transition: color .15s, background .15s, border-color .15s;
  cursor: pointer;
  background: none;
}
.nav-explore__btn:hover,
.nav-explore--open .nav-explore__btn {
  color: var(--c-primary);
  background: #fff;
  border-color: var(--c-divider);
}

.chevron-icon {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  transition: transform .15s;
}
.nav-explore--open .chevron-icon { transform: rotate(180deg); }

.nav-explore__bridge {
  position: absolute;
  top: 100%;
  left: 0;
  padding-top: 8px;
  z-index: 200;
  transition: opacity .15s, transform .15s;
  opacity: 0;
  pointer-events: none;
  transform: translateY(-4px);
}
.nav-explore--open .nav-explore__bridge {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}

.explore-dropdown {
  width: 660px;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: 16px;
  box-shadow: 0 16px 48px -8px rgba(18, 38, 58,.18);
  overflow: hidden;
}

.explore-dropdown__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 20px;
  border-bottom: 1px solid rgba(217, 225, 232,.6);
  background: #fff;
}

.explore-dropdown__eyebrow {
  font-size: 10.5px;
  font-weight: 700;
  color: var(--c-muted);
  text-transform: uppercase;
  letter-spacing: .15em;
  font-family: var(--font-sans);
}

.explore-dropdown__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--c-accent);
  transition: color .15s;
  text-decoration: none;
}
.explore-dropdown__link:hover { color: var(--c-primary); }
.explore-dropdown__link svg { width: 14px; height: 14px; }

.explore-dropdown__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  padding: 12px;
  gap: 1px;
}

.explore-cat-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px;
  border-radius: 8px;
  font-size: 12.5px;
  color: var(--c-secondary);
  transition: color .1s, background .1s;
  cursor: pointer;
  text-decoration: none;
}
.explore-cat-item:hover { color: var(--c-primary); background: var(--c-subtle); }

.explore-cat-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(47, 74, 102,.3);
  flex-shrink: 0;
  transition: background .1s;
}
.explore-cat-item:hover .explore-cat-dot { background: var(--c-accent); }

.navbar__search {
  display: flex;
  flex: 1;
  justify-content: flex-end;
  padding: 0 24px;
  min-width: 0;
}

.search-wrap {
  position: relative;
  width: 100%;
  max-width: 480px;
}

.search-input-row {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 40px;
  padding: 0 16px;
  border-radius: var(--radius-full);
  background: #fff;
  border: 1px solid var(--c-divider);
  transition: border-color .15s, background .15s, box-shadow .15s;
}
.search-input-row:hover,
.search-input-row:focus-within {
  border-color: var(--c-accent);
  background: #fff;
  box-shadow: 0 2px 10px rgba(18, 38, 58,.08);
}

.search-icon { width: 14px; height: 14px; color: var(--c-muted); flex-shrink: 0; }

.search-input {
  flex: 1;
  font-size: 13px;
  font-family: var(--font-sans);
  color: var(--c-primary);
  background: transparent;
  border: none;
  outline: none;
  min-width: 0;
}
.search-input::placeholder { color: var(--c-muted); }

.navbar__auth {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  margin-left: auto;
}

.btn-signin {
  font-size: 13px;
  font-weight: 500;
  color: var(--c-secondary);
  padding: 4px 8px;
  white-space: nowrap;
  transition: color .15s;
  text-decoration: none;
}
.btn-signin:hover { color: var(--c-primary); }

.btn-signup {
  display: inline-flex;
  align-items: center;
  height: 38px;
  padding: 0 18px;
  border-radius: var(--radius-full);
  background: var(--c-primary);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  box-shadow: var(--shadow-sm);
  transition: background .15s, box-shadow .15s;
  text-decoration: none;
}
.btn-signup:hover {
  background: var(--c-primary-hover);
  box-shadow: var(--shadow-md);
}

.navbar__hamburger {
  display: none;
  padding: 8px;
  border-radius: 8px;
  color: var(--c-primary);
  transition: background .15s;
  background: none;
  border: none;
  cursor: pointer;
}
.navbar__hamburger:hover { background: var(--c-subtle); }
.navbar__hamburger svg { width: 20px; height: 20px; }

/* ── Mobile menu ─────────────────────────────────────────────────── */
.mobile-menu {
  display: none;
  border-top: 1px solid rgba(217, 225, 232,.5);
  background: #fff;
  padding: 16px 24px;
}
.mobile-menu.is-open { display: block; }

.mobile-search-row {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 40px;
  padding: 0 16px;
  border-radius: var(--radius-full);
  background: var(--c-subtle);
  border: 1px solid var(--c-border);
  margin-bottom: 12px;
}

.mobile-nav-link {
  display: block;
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 500;
  color: var(--c-secondary);
  transition: color .15s, background .15s;
  text-decoration: none;
}
.mobile-nav-link:hover { color: var(--c-primary); background: var(--c-subtle); }
.mobile-nav-link--active { color: var(--c-primary); background: var(--c-subtle); font-weight: 600; }

.mobile-explore-btn {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 500;
  color: var(--c-secondary);
  transition: color .15s, background .15s;
  background: none;
  border: none;
  font-family: inherit;
  cursor: pointer;
}
.mobile-explore-btn:hover { color: var(--c-primary); background: var(--c-subtle); }

.mobile-explore-children {
  display: none;
  margin-top: 4px;
  margin-left: 12px;
  padding-left: 12px;
  border-left: 2px solid rgba(217, 225, 232,.6);
}
.mobile-explore-children.is-open { display: block; }

.mobile-explore-children a {
  display: block;
  padding: 6px 12px;
  border-radius: 8px;
  font-size: 12.5px;
  color: var(--c-secondary);
  transition: color .15s, background .15s;
  text-decoration: none;
}
.mobile-explore-children a:hover { color: var(--c-primary); background: var(--c-subtle); }

.mobile-explore-all {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: 8px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--c-accent);
  transition: color .15s, background .15s;
  text-decoration: none;
}
.mobile-explore-all:hover { background: var(--c-subtle); }

.mobile-auth-area {
  padding-top: 8px;
  border-top: 1px solid rgba(217, 225, 232,.3);
  margin-top: 4px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.mobile-btn-signin {
  display: block;
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 500;
  color: var(--c-secondary);
  transition: background .15s;
  text-decoration: none;
}
.mobile-btn-signin:hover { background: var(--c-subtle); }

.mobile-btn-signup {
  display: block;
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 700;
  color: #fff;
  background: var(--c-primary);
  text-align: center;
  transition: background .15s;
  text-decoration: none;
}
.mobile-btn-signup:hover { background: var(--c-primary-hover); }

/* ── Navbar responsive ───────────────────────────────────────────── */
@media (max-width: 768px) {
  .navbar__inner  { padding: 0 24px; gap: 12px; }
  .navbar__nav    { display: none; }
  .navbar__search { display: none; }
  .navbar__auth .btn-signin { display: none; }
  .navbar__hamburger { display: inline-flex; }
}

/* ══════════════════════════════════════════════════════════════════
   FOOTER — Platform marketing footer (DTH design system)
   ══════════════════════════════════════════════════════════════════ */
.footer {
  width: 100%;
  background: transparent;
  border-top: 1px solid rgba(217, 225, 232,.5);
  /* padding-bottom offsets the fixed AI-caveat strip (~44px).
     body already reserves 44px via padding-bottom; the footer sits
     in normal flow so no extra reservation needed — the body padding
     pushes content above the strip, including this footer. */
}

.footer__main {
  width: 100%;
  max-width: 1160px;
  margin: 0 auto;
  padding: 38px 40px 30px;
}

.footer__grid {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 48px;
}

/* Brand keeps its natural (tagline) width; space-between then splits the
   remaining room into two EQUAL gaps so Brand · Company · Directory are
   equidistant, with Directory's right edge pinned to the container edge
   (= Sign-up's right edge in the header). */
.footer__brand { flex: 0 1 auto; }

.footer__logo-link {
  display: inline-flex;
  align-items: center;
  margin-bottom: 20px;
  transition: opacity .15s;
  text-decoration: none;
}
.footer__logo-link:hover { opacity: .70; }

.footer__logo { height: 42px; width: auto; }

.footer__tagline {
  font-size: 14px;
  color: var(--c-secondary);
  line-height: 1.6;
  max-width: 62ch;
  margin-bottom: 16px;
}

/* Social row — monochrome, inherits currentColor (secondary → primary on hover).
   No platform brand colors. 40px tap targets; -10px left margin optically aligns
   the first glyph with the logo. */
.footer__social {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-left: -10px;
}
.footer__social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 6px;
  color: var(--c-secondary);
  transition: color .15s;
}
.footer__social a:hover { color: var(--c-primary); }
.footer__social svg { width: 18px; height: 18px; display: block; }

.footer__col-heading {
  font-family: var(--font-sans);
  font-size: 10px;
  font-weight: 700;
  color: var(--c-muted);
  text-transform: uppercase;
  letter-spacing: .15em;
  margin-bottom: 14px;
}

.footer__links { display: flex; flex-direction: column; gap: 9px; }

.footer__link {
  font-size: 13.5px;
  color: var(--c-secondary);
  transition: color .15s;
  text-decoration: none;
}
.footer__link:hover { color: var(--c-primary); }

.footer__bottom {
  border-top: 1px solid rgba(217, 225, 232,.4);
}

.footer__bottom-inner {
  width: 100%;
  max-width: 1160px;
  margin: 0 auto;
  padding: 16px 40px;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.footer__copyright {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 500;
  color: var(--c-muted);
  text-transform: uppercase;
  letter-spacing: .08em;
}

.footer__bottom-links {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  justify-content: center;
}

.footer__bottom-link {
  font-size: 11px;
  font-weight: 500;
  color: var(--c-muted);
  text-transform: uppercase;
  letter-spacing: .08em;
  transition: color .15s;
  text-decoration: none;
}
.footer__bottom-link:hover { color: var(--c-primary); }

/* Footer responsive */
@media (max-width: 768px) {
  .footer__main  { padding: 38px 24px 30px; }
  .footer__grid  { gap: 32px; }
  .footer__brand { flex: 1 1 100%; }
  .footer__bottom-inner { padding: 20px 24px; }
}

@media (max-width: 480px) {
  .footer__grid  { flex-direction: column; }
}

/* Hide the marketing footer on mobile — chat context; the footer adds no
   value on a small viewport and was causing scroll to land in it. */
@media (max-width: 768px) {
  .footer { display: none; }
}

/* Sticky AI caveat strip pinned to the bottom of the viewport. Always
   visible so the buyer doesn't forget Mola is AI. Styled to match the
   platform cookie banner: white bar, left badge + grey caveat, navy
   "Got it" pill on the right. Dismissible per browser session. */
.app-footer-strip {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 50;
  background: #fff;
  border-top: 1px solid var(--c-divider);
  box-shadow: 0 -2px 12px -6px rgba(18, 38, 58, .12);
  font-size: 13px;
  color: var(--c-secondary);
}
.app-footer-strip__inner {
  max-width: 1160px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 40px;
}
.app-footer-strip .footer-caveat { line-height: 1.5; }
.app-footer-strip .footer-caveat a {
  color: var(--c-primary);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.app-footer-strip .footer-caveat a:hover { color: var(--c-accent); }
.app-footer-strip .footer-dismiss {
  margin-left: auto;
  flex-shrink: 0;
  padding: 7px 22px;
  border: none;
  border-radius: var(--radius-full);
  background: var(--c-primary);
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  transition: background 140ms ease;
}
.app-footer-strip .footer-dismiss:hover { background: var(--c-primary-hover); }
.app-footer-strip.is-dismissed { display: none; }
/* Reserve bottom space so the strip never overlaps actual content
   (no-JS fallback; the inline script sets the exact rendered height). */
body { padding-bottom: 52px; }
body.footer-dismissed { padding-bottom: 0; }

/* Mobile: drop the verbose verify tail + tighten so the bar stays ~1 line.
   Core "Mola is AI — outputs can be wrong." warning + Got it pill remain. */
@media (max-width: 480px) {
  .app-footer-strip { font-size: 12px; }
  .app-footer-strip__inner { padding: 9px 24px; gap: 10px; }
  .app-footer-strip .footer-caveat-more { display: none; }
  .app-footer-strip .footer-dismiss { padding: 6px 16px; }
}

/* ══════════════════════════════════════════════════════════════════
   MAIN LAYOUT
   ══════════════════════════════════════════════════════════════════ */
main {
  flex: 1;
  max-width: 820px;
  width: 100%;
  margin: 0 auto;
  padding: 48px 24px 80px;
}

/* The landing's own top/bottom padding — `main`'s 48/80 is sized for a
   conversation transcript, and on the narrative it is pure air above the hero
   and below the close band (which already has its own). */
body.landing-live main { padding-top: 20px; padding-bottom: 24px; }

/* The scroll narrative is marketing-width content; the chat transcript is not.
   Widen ONLY the landing wrapper so vendor cards and bubbles keep their 820px
   reading measure while the zone grid, the duel and the replay stage get the
   laptop's real width. Negative margins rather than a wider `main`, so nothing
   downstream has to know about this. The hero triad stays at 820px — Camille
   drew it centred and narrow. */
#landing-narrative,
body.landing-live #empty-state-chips {
  margin-left: -150px;
  margin-right: -150px;
}
/* Below the width where the breakout would overflow the viewport it collapses
   back to the 820px column. 1120 + 2x24 page gutter = 1168. */
@media (max-width: 1180px) {
  #landing-narrative,
  body.landing-live #empty-state-chips { margin-left: 0; margin-right: 0; }
}

/* ── Hero ─────────────────────────────────────────────────────────── */
/* Camille's hero: eyebrow + Beta, proof pill, H1, sub. The mascot is NOT here
   any more — it sits beside the composer below (the prototype's
   composer-adjacent placement), which is also the mascot app.js already drives
   through its four states. Typography lives in the LANDING NARRATIVE block. */
#hero {
  text-align: center;
  padding: 24px 20px 4px;
}

.mola-icon {
  display: inline-block;
  color: var(--c-primary);
  flex-shrink: 0;
}
.mola-icon-sm {
  width: 22px;
  height: 28px;
  color: var(--c-accent);
  animation: mola-breathe 3.5s ease-in-out infinite;
  transform-origin: 50% 100%;
}
.mola-icon-xs {
  width: 14px;
  height: 18px;
  color: var(--c-accent);
  flex-shrink: 0;
}
@keyframes mola-breathe {
  0%, 100% { transform: scale(1)    translateY(0); }
  50%      { transform: scale(1.03) translateY(-1px); }
}

/* ══════════════════════════════════════════════════════════════════
   MOLA — rich SVG character (hero + composer instances)
   State machine driven by [data-state] = idle | typing | thinking | speaking
   ══════════════════════════════════════════════════════════════════ */

.mola-rich {
  display: block;
  flex-shrink: 0;
  overflow: visible;
}

/* ── ROUND 3: the mascot as an OBJECT, not a sticker ────────────────────
   The founder: "the Mola icon feels flat, no Z-axis". Three additions, all
   CSS on geometry that already exists in _mola_svg_body.html, so no path is
   redrawn and every state animation keeps working:

     1. THE GROUND SHADOW. The partial has always carried a `.mola-shadow`
        ellipse at cy=262 and nothing ever styled it, so it sat at a flat 14%
        with no relationship to the body above it. It is now scaled per host
        (a 36px avatar needs a tighter, darker contact patch than a 150px hero
        or the shadow dissolves) and it BREATHES WITH THE BODY on the hero and
        the composer: the idle bob lifts the character, and a shadow that does
        not shrink when its object rises reads as a painted-on oval.
     2. A CAST SHADOW on the whole figure via drop-shadow, offset down so the
        light source matches every other elevation token in this file (they
        are all straight-down, ink-tinted).
     3. A TOP HIGHLIGHT on the body: one soft white ellipse painted over the
        crown of the tooth with `mix-blend-mode: soft-light`, which turns the
        flat fill into a lit surface without touching the fill itself. Kept
        off the tiny instances, where a 2px highlight is only noise. */
.mola-shadow ellipse {
  fill: #12263A;
  transition: opacity 240ms ease, transform 240ms ease;
  transform-origin: 120px 262px;
}
/* Contact shadow tightens and darkens as the mark gets smaller. */
.mola-rich-hero     .mola-shadow ellipse { opacity: .16; }
.mola-rich-composer .mola-shadow ellipse { opacity: .20; transform: scaleX(.86); }
.mola-rich-bubble   .mola-shadow ellipse { opacity: .22; transform: scaleX(.80); }

/* The highlight rides above the body fill. `soft-light` keeps the ink outline
   and the cap colour intact and only lifts the lit face. */
.mola-hilite {
  fill: #FFFFFF;
  opacity: .58;
  mix-blend-mode: soft-light;
  pointer-events: none;
}
.mola-rich-bubble .mola-hilite { display: none; }   /* 36px: noise, not light */

/* Restored 2026-09-06 (round 3). Sized as a real hero mark, centred above the
   eyebrow. The cast shadow is heavier here than on the small instances because
   the figure is bigger and further off its ground. */
.mola-rich-hero {
  width: 129px;
  height: 150px;
  margin: 0 auto 10px;
  filter: drop-shadow(0 12px 20px rgba(18, 38, 58, .20));
}
.mola-rich-composer {
  width: 48px;
  height: 56px;
  align-self: flex-end;
  margin: 0 2px -4px 4px;     /* nudge: a hair of left padding, sit on composer floor */
  filter: drop-shadow(0 4px 7px rgba(18, 38, 58,.22));
}
.mola-rich-bubble {
  opacity: 0.88;              /* historical / transcript feel — composer Mola is the active speaker */
  filter: drop-shadow(0 2px 4px rgba(18, 38, 58,.20));
}
/* Bubbles are static — composer Mola owns idle-breathe / blink / thinking /
   speaking animations. Bubbles inherit the full geometry (so the markup is
   single-source via the Jinja partial) but suppress per-state animations
   here so a long conversation isn't a wall of breathing avatars. */
.mola-rich-bubble,
.mola-rich-bubble .mola-body-group,
.mola-rich-bubble .mola-lid,
.mola-rich-bubble .mola-hat,
.mola-rich-bubble .mola-pupil,
.mola-rich-bubble .mola-brow,
.mola-rich-bubble .mola-hand,
.mola-rich-bubble .mola-mouth { animation: none !important; }

/* Smooth transitions between states for parts that aren't always animating */
.mola-rich .mola-lid    { transform: scaleY(0); transition: transform 220ms ease; }
.mola-rich .mola-pupil  { transition: transform 260ms ease; }
.mola-rich .mola-brow   { transition: transform 220ms ease; }
.mola-rich .mola-hat    { transition: transform 320ms ease; }

/* Mouth visibility — closed by default, alternates revealed in speaking state */
.mola-rich .mola-mouth-o { display: none; }

/* Cap DTH text — sized per host so it stays legible after downscaling.
   Hero renders at ~75% scale; composer/bubble at ~20%, so the text needs
   to be ~2× larger in viewBox units there to read on screen. */
.mola-rich-composer .mola-cap-text,
.mola-rich-bubble   .mola-cap-text { font-size: 32px; letter-spacing: -0.8px; }

/* ══ STATE MAP — prototype behaviour → this app's state names ══════════
   Camille's prototype drives the mascot with three states (idle /
   listening / thinking) plus a one-shot wave. app.js drives four
   (idle / typing / thinking / speaking) and is NOT renamed here — Phase 1
   keeps JS churn at zero, so the mapping lives in CSS:

     prototype idle       → [data-state="idle"]      bob + breathe + blink
     prototype listening  → [data-state="typing"]    pupils down, body tilt
     prototype thinking   → [data-state="thinking"]  pupils up, brows up, mouth O
     prototype wave       → .is-waving (one-shot, additive on any state)
     (app-only) speaking  → idle posture + talking mouth + a wave

   Every animation below is additionally gated on html.motion-ok so the
   reduced-motion opt-out is a real opt-out, not a crushed-duration one.
   ══════════════════════════════════════════════════════════════════════ */

/* ── IDLE: prototype bob + gentle breathing + slow blink + hat wobble ─
   The bob rides on the SVG element itself while breathe drives the inner
   body group, so the two compose instead of overwriting one transform. */
html.motion-ok .mola-rich[data-state="idle"] {
  animation: mola-bob 5.4s ease-in-out infinite;
}
html.motion-ok .mola-rich[data-state="idle"] .mola-body-group {
  animation: mola-breathe-rich 3.8s ease-in-out infinite;
}
html.motion-ok .mola-rich[data-state="idle"] .mola-lid {
  animation: mola-blink 5.5s ease-in-out infinite;
}
/* Second instance blinks off-cadence so hero + composer never blink in
   lockstep (the prototype's .blink-b, applied here by position). */
html.motion-ok .mola-rich-composer[data-state="idle"] .mola-lid {
  animation-duration: 6.7s;
}
html.motion-ok .mola-rich[data-state="idle"] .mola-hat {
  animation: mola-hat-wobble 4.2s ease-in-out infinite;
  transform-origin: 122px 50px;
}

@keyframes mola-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-4px); }
}

/* The bob lives on the OUTER svg, so it lifts the ground shadow along with
   the character, which is precisely what makes a mascot read as a sticker.
   Counter-animating the shadow — held on the ground, shrinking and lightening
   as the body rises — is what turns the same bob into height. Same duration
   and easing as mola-bob so the two never drift out of phase. */
html.motion-ok .mola-rich[data-state="idle"] .mola-shadow {
  animation: mola-shadow-bob 5.4s ease-in-out infinite;
  transform-origin: 120px 262px;
}
@keyframes mola-shadow-bob {
  0%, 100% { transform: translateY(0)   scale(1);        opacity: 1; }
  50%      { transform: translateY(4px) scale(.90, .82); opacity: .72; }
}

/* ── WAVE (one-shot, additive) — Camille's greeting beat. Applied by
   app.js-independent CSS: any code that adds .is-waving gets two waves
   then the class is expected to be removed. Right hand only. */
html.motion-ok .mola-rich.is-waving .mola-hand-r {
  animation: mola-wave 1.15s ease-in-out 2;
}
@keyframes mola-wave {
  0%, 100% { transform: rotate(0); }
  25%      { transform: rotate(-26deg) translateY(-7px); }
  60%      { transform: rotate(14deg); }
}

@keyframes mola-hat-wobble {
  0%,100% { transform: rotate(-2deg); }
  50%     { transform: rotate( 2deg); }
}

@keyframes mola-breathe-rich {
  0%, 100% { transform: scale(1)     translateY(0); }
  50%      { transform: scale(1.022) translateY(-2px); }
}
@keyframes mola-blink {
  0%, 92%, 100% { transform: scaleY(0); }
  95%, 96.5%    { transform: scaleY(1); }
}

/* ── TYPING = the prototype's "listening": pupils track DOWN toward the
       textarea and the whole body tilts in, the two moves that read as
       "I'm reading what you're typing". Lids stay part-closed and the hat
       tips toward the cursor (kept from the previous state machine —
       these still compose and survive at 48px composer scale). ──────── */
.mola-rich[data-state="typing"] .mola-lid {
  transform: scaleY(0.55);
}
.mola-rich[data-state="typing"] .mola-pupil {
  transform: translate(0, 5px);
}
/* Body tilt goes on the OUTER svg, not .mola-body-group: the idle/speaking
   breathe animation animates that group's transform, and a running animation
   beats a static declaration, so a tilt set there is silently discarded.
   The svg element carries only the bob animation, which we replace here. */
.mola-rich[data-state="typing"] {
  transform: rotate(-2deg);
  transform-origin: 120px 260px;
}
html.motion-ok .mola-rich[data-state="typing"] {
  animation: none;
  transition: transform 320ms ease;
}
.mola-rich[data-state="typing"] .mola-brow-l {
  transform: translateY(2px) rotate(-3deg);
}
.mola-rich[data-state="typing"] .mola-brow-r {
  transform: translateY(2px) rotate(3deg);
}
.mola-rich[data-state="typing"] .mola-hat {
  transform: rotate(5deg) translateY(-1px);
  transform-origin: 122px 50px;
}

/* ── THINKING: whole-body sway — Mola gently rocks side-to-side around
       her base while one hand lifts toward the cheek in a "pondering"
       gesture. Body breath is rolled into the sway so the figure still
       has rhythm. Brows knit, blink slows. Whole-body motion is the
       only thing that reads at composer scale (48px) — sub-pixel cap
       rotation or pupil shifts blur the DTH text and tiny pupils into
       invisibility at that size, so we move the entire figure instead.
       Pivot is `transform-origin: 120px 260px` set inline on the body
       group (her base).

       Camille's prototype adds the face half of the same idea — pupils roll
   UP, brows lift, mouth opens to an O. Those read at hero scale; the sway
   carries the composer. Both are applied, so one state reads correctly at
   both sizes. The face cues are static transforms (no animation), which
   is why they survive the reduced-motion crush and still communicate. */
html.motion-ok .mola-rich[data-state="thinking"] .mola-body-group {
  animation: mola-think-sway 2.4s ease-in-out infinite;
}
html.motion-ok .mola-rich[data-state="thinking"] .mola-hand-l {
  animation: mola-think-hand 2.4s ease-in-out infinite;
}
html.motion-ok .mola-rich[data-state="thinking"] .mola-lid {
  animation: mola-blink 4s ease-in-out infinite;
}
html.motion-ok .mola-rich[data-state="thinking"] .mola-brow-l {
  animation: mola-brow-think-l 2.4s ease-in-out infinite;
}
html.motion-ok .mola-rich[data-state="thinking"] .mola-brow-r {
  animation: mola-brow-think-r 2.4s ease-in-out infinite;
}
/* Face cues — static, so they still land under reduced motion. */
.mola-rich[data-state="thinking"] .mola-pupil {
  transform: translate(-3px, -6px);
}
.mola-rich[data-state="thinking"] .mola-mouth-closed { display: none; }
.mola-rich[data-state="thinking"] .mola-mouth-o { display: block; }
/* Brow lift for the reduced-motion path only: when the knit-brow animation
   above is suppressed, the raised resting position still reads as thought. */
@media (prefers-reduced-motion: reduce) {
  .mola-rich[data-state="thinking"] .mola-brow-l { transform: translateY(-4px) rotate(-7deg); }
  .mola-rich[data-state="thinking"] .mola-brow-r { transform: translateY(-4px) rotate(7deg); }
}

@keyframes mola-think-sway {
  0%, 100% { transform: rotate(-5deg) scale(1);    }
  50%      { transform: rotate( 5deg) scale(1.02); }
}
@keyframes mola-think-hand {
  /* Hand-l lifts toward the cheek and back. transform-origin is set
     inline at 60px 188px (the hand's anchor on the body). */
  0%, 100% { transform: translate(0, 0)       rotate(0deg);   }
  50%      { transform: translate(8px, -42px) rotate(-22deg); }
}
@keyframes mola-brow-think-l {
  0%, 100% { transform: translateY(-1px) rotate(-3deg); }
  50%      { transform: translateY(-3px) rotate( 1deg); }
}
@keyframes mola-brow-think-r {
  0%, 100% { transform: translateY(-1px) rotate( 3deg); }
  50%      { transform: translateY(-3px) rotate(-1deg); }
}

/* ── SPEAKING (app-only state): the prototype has no equivalent, so this
       maps to its closest one — idle posture (bob + breathe, no tilt, eyes
       forward) with the talking mouth on top. That keeps Mola on-model
       while she delivers an answer instead of inventing a fourth pose. */
.mola-rich[data-state="speaking"] .mola-mouth-closed { display: none; }
.mola-rich[data-state="speaking"] .mola-mouth-o { display: block; }
html.motion-ok .mola-rich[data-state="speaking"] {
  animation: mola-bob 5.4s ease-in-out infinite;
}
html.motion-ok .mola-rich[data-state="speaking"] .mola-mouth-o {
  animation: mola-talk 320ms ease-in-out infinite;
}
html.motion-ok .mola-rich[data-state="speaking"] .mola-body-group {
  animation: mola-breathe-rich 3s ease-in-out infinite;
}

@keyframes mola-talk {
  0%,100% { transform: scaleY(0.5);  }
  50%     { transform: scaleY(1.05); }
}

/* Display headline — Source Serif 4 600, the system's primary voice. Upright
   (not italic): italic Source Serif 4 is reserved for the "why it fits"
   editorial line so the two never compete. */

/* ── Composer (in-flow, sticky-top once scrolled) ─────────────────── */
/* The composer pins to the top once scrolled. Content scrolling underneath
   it is masked by two fade layers (::before above, ::after below) so the
   bar always sits in a clean band of surface color — answers visually
   disappear ~20px below the bar instead of bleeding behind it. */
.composer {
  position: sticky;
  top: calc(var(--header-h) + 8px);   /* 8px breathing room below sticky header */
  z-index: 10;
  margin: 18px 0 16px;
  background: var(--c-surface);       /* fills the rounded-corner negative space behind composer-inner */
  border-radius: var(--radius-lg);
}

/* Inline "explore the rest of {Category}" link appended at the end of
   discuss (dig-deeper) bubbles. Lives inside the bubble, so it scrolls
   with the conversation instead of sticking to the viewport. */
.bubble-category-link {
  margin-top: 14px;
  padding-top: 12px;
  border-top: .5px dashed var(--c-divider);
  font-size: 13px;
  color: var(--c-secondary);
}
.bubble-category-link a {
  color: var(--c-accent-ink);
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color 160ms ease, border-color 160ms ease;
}
.bubble-category-link a:hover {
  color: var(--c-primary);
  border-bottom-color: var(--c-accent);
}
.bubble-category-link a::after {
  content: " \2197";              /* northeast arrow */
  font-size: 11px;
  opacity: .7;
}

/* Inline vendor mention in prose (discuss bubbles): a profile link plus a
   compact Book Demo button. Only appears in card-free prose — card bubbles
   carry their own CTAs. Kept on one line so the name + button never split. */
.vendor-mention { white-space: nowrap; }
.vendor-mention-link {
  color: var(--c-accent-ink);
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color 160ms ease, border-color 160ms ease;
}
.vendor-mention-link:hover {
  color: var(--c-primary);
  border-bottom-color: var(--c-accent);
}
.vendor-mention-link::after {
  content: " \2197";              /* northeast arrow — matches category links */
  font-size: 11px;
  opacity: .7;
}
.vendor-mention-demo {
  display: inline-flex;
  align-items: center;
  margin-left: 5px;
  padding: 1px 9px;
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1.55;
  color: var(--c-cta-ink);
  background: var(--c-cta-tint);
  border: 1px solid var(--c-cta-border);
  border-radius: 999px;
  cursor: pointer;
  vertical-align: baseline;
  transition: background 150ms ease, border-color 150ms ease, color 150ms ease;
}
/* Hover fill is INK, not coral: this pill is 11.5px, and white-on-coral
   (4.29:1) needs >=14px bold to clear AA. White-on-ink is 15.40:1 at any
   size. The resting state still reads coral via --c-cta-ink text. */
.vendor-mention-demo:hover {
  background: var(--c-primary);
  border-color: var(--c-primary);
  color: #fff;
}

/* Alternatives-to-X framing pill — surfaced at the top of every
   similar_vendors bubble so the buyer can see what the shortlist is
   an alternative to. Visually distinct from the shortlist surface
   (accent-tinted pill, not a card) so it reads as framing context,
   not as a vendor row. */
.bubble-alternatives-tag {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px 0;
  padding: 6px 12px;
  background: var(--c-subtle);
  border: 1px solid var(--c-divider);
  border-radius: 999px;
  font-size: 12.5px;
  line-height: 1.3;
  color: var(--c-primary);
  width: fit-content;
}
.bubble-alternatives-tag strong { font-weight: 600; }
.bubble-alternatives-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--card);
  color: var(--c-accent);
  font-size: 12px;
  font-weight: 600;
}

/* Per-bubble AI caveat — light-grey reminder at the bottom of every
   Mola response (shortlist + discuss + comparison). Soft enough not to
   clutter the read, present enough not to forget. */
/* Round 3: roman, not italic, and 12.5px. This is the one line on the page a
   regulator would read, and it was set in the least legible way available
   (11.5px italic muted). It stays quiet by SIZE and COLOUR; it does not need
   to be hard to read on top of that. */
.bubble-caveat {
  margin-top: 12px;
  padding-top: 10px;
  border-top: .5px dashed rgba(0,0,0,.06);
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--c-muted);
  font-style: normal;
}
.composer::before,
.composer::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  pointer-events: none;
  z-index: -1;
}
.composer::before {
  /* Mask above the bar: hides anything peeking through the 8px gap between
     the sticky header bottom and the bar's top edge. */
  bottom: 100%;
  height: 12px;
  background: var(--c-surface);
}
.composer::after {
  /* Buffer zone below the bar: answers must be fully out of view before
     they reach the bar's bottom edge. Solid surface color for the first
     ~46px, then a soft fade across the remaining ~18px so the transition
     to visible content isn't a hard line. */
  top: 100%;
  height: 64px;
  background: linear-gradient(to bottom, var(--c-surface) 72%, rgba(240, 245, 249, 0));
}
.composer-inner {
  position: relative;        /* sit above the masks */
  z-index: 1;
  display: flex;
  gap: 10px;
  align-items: center;
  padding: 10px;
  background: var(--card);    /* solid — no more bleed-through */
  border: 1px solid var(--c-divider);
  /* --radius-xl, shared with .replay-stage: the composer and the stage are the
     page's two "objects", so they carry one silhouette. */
  border-radius: var(--radius-xl);
  box-shadow: var(--e2);
  transition: border-color 180ms ease, box-shadow 200ms ease;
}
.composer-inner:focus-within {
  border-color: var(--c-accent);
  box-shadow: var(--e2), var(--ring);
}

/* Pulse fired when a follow-up chip is clicked — draws the eye to the
   newly-populated composer, then settles into the focus-within state. */
.composer-inner.composer-pulse {
  animation: composer-pulse 950ms cubic-bezier(.2,.8,.2,1);
}
@keyframes composer-pulse {
  0%   { box-shadow: var(--shadow-md), 0 0 0  0 rgba(47, 74, 102,0);    border-color: var(--c-divider); }
  25%  { box-shadow: var(--shadow-md), 0 0 0  9px rgba(47, 74, 102,.22); border-color: var(--c-accent); }
  60%  { box-shadow: var(--shadow-md), 0 0 0  6px rgba(47, 74, 102,.10); border-color: var(--c-accent); }
  100% { box-shadow: var(--shadow-md), 0 0 0  3px rgba(47, 74, 102,.10); border-color: var(--c-accent); }
}

/* ══════════════════════════════════════════════════════════════════
   LANDING NARRATIVE — Camille's approved pre-first-turn experience
   ─────────────────────────────────────────────────────────────────
   Everything here renders ONLY before the first turn. setLandingVisible()
   in app.js hides #hero, #empty-state-chips and #landing-narrative
   together the moment a question is asked, and never re-shows them
   mid-session.

   COLOUR DISCIPLINE (Phase 1 rules, unchanged):
     * coral (--c-cta) is CONVERSION ONLY. On this page it appears on the
       replay stage's Book-a-Demo buttons, the duel's Book-a-Demo, the
       proof-pill dot, the section eyebrows and the zone hover question —
       and nowhere else. The "Ask" verb stays ink.
     * small coral TEXT uses --c-cta-ink (5.34:1 on paper); the raw
       --c-cta is fills-behind-white and >=18px display type only.
     * on the ink band coral text is FORBIDDEN (3.83:1) — the band's
       eyebrow uses --c-cta-hot (4.67:1).
   ══════════════════════════════════════════════════════════════════ */

/* ── Scroll reveal ─────────────────────────────────────────────────
   JS adds .is-in. Doubly gated: html.motion-ok scopes the transition,
   and startScrollReveals() marks everything revealed immediately when
   MOTION_OK is false, so a reduced-motion reader never depends on an
   observer firing to see content. */
.rv { opacity: 1; }
html.motion-ok .rv {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--dur-enter) var(--ease-out) var(--rv-d, 0ms),
              transform var(--dur-enter) var(--ease-out) var(--rv-d, 0ms);
}
html.motion-ok .rv.is-in { opacity: 1; transform: none; }

/* ── Landing sections ──────────────────────────────────────────────
   `main` is 820px wide; the ink band has to reach the viewport edges, so
   .landing-band breaks out with a symmetric negative margin and pulls its
   own content back to the text column. */
.landing-section { padding: 56px 0; }
#hero-tail { text-align: center; padding: 10px 16px 2px; }

.landing-band {
  background: var(--band);
  color: var(--cream);
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  padding-left: 24px;
  padding-right: 24px;
}
.landing-band .band-inner { max-width: 820px; margin: 0 auto; }

.landing-section h2 {
  font-size: clamp(27px, 3.8vw, 38px);
  color: var(--c-primary);
  margin-bottom: 12px;
}
/* AFTER the generic rule, not before: both are one class + one element, so
   source order decides, and ink-on-ink would make the band headline invisible. */
.landing-band h2 { color: var(--cream); }
.landing-section .mono { display: block; margin-bottom: 10px; }

/* Section eyebrows. Coral on paper is only legal as small text via
   --c-cta-ink; on the band it must be --c-cta-hot.
   ROUND 5: `.replay-eyebrow`, `.duel-eyebrow` and `.catalog-eyebrow` were
   deleted from the markup (founder: four tracked-mono labels a screen had
   become wallpaper); round 6 removed the catalog band outright. `.zones-eyebrow`
   is the last one standing, because it is the only eyebrow that asks the buyer
   a question rather than naming a section.
*/
.zones-eyebrow { color: var(--c-cta-ink); }

/* ══ HERO ══════════════════════════════════════════════════════════ */
.hero-eyebrow {
  color: var(--c-secondary);
  margin-bottom: 12px;
}
/* Proof pill. Rendered ONLY when app.py's _get_proof_stats() cleared a
   tier — there is no CSS or template path that shows it without a number. */
.proof-pill {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  background: #fff;
  border: 1.5px solid var(--c-primary);
  border-radius: var(--radius-full);
  padding: 11px 22px;
  margin-bottom: 20px;
  /* Deliberate exception to the "depth belongs to controls" rule: this is the
     one social-proof claim on the page and the founder wants it read, so it
     carries a soft ambient lift. Still visibly not a button (no hover states,
     ink border rather than a CTA fill). */
  box-shadow: 0 2px 8px rgba(18, 38, 58, .08), 0 14px 32px -18px rgba(18, 38, 58, .28);
  font-size: 16.5px;
  font-weight: 500;
  color: var(--c-secondary);
  text-align: left;
}
.proof-pill b { color: var(--c-primary); font-weight: 800; }
.proof-dot {
  position: relative;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--c-cta);
  flex-shrink: 0;
}
@media (max-width: 480px) { .proof-pill { font-size: 14px; padding: 9px 16px; } }
html.motion-ok .proof-pill {
  opacity: 0;
  transform: translateY(8px);
  animation: proof-pill-in 500ms ease 150ms forwards;
}
html.motion-ok .proof-dot::after {
  content: "";
  position: absolute;
  inset: -5px;
  border-radius: 50%;
  border: 2px solid var(--c-cta);
  opacity: 0;
  animation: proof-pulse 2.2s ease-out 800ms infinite;
}
@keyframes proof-pill-in { to { opacity: 1; transform: none; } }
@keyframes proof-pulse {
  0%        { transform: scale(.45); opacity: .85; }
  70%, 100% { transform: scale(1.25); opacity: 0; }
}

.hero-h1 {
  font-family: var(--font-display);
  /* 600, not 900. Source Serif 4 at 600 has the stroke contrast to carry a 36px
     headline without the slab-like mass Fraunces 900 had here; the optical-size
     axis is pinned to the display end so the cut matches the size. */
  font-weight: 600;
  font-variation-settings: 'opsz' 60;
  /* Two claims, one per line: premise then promise. Both lines are far shorter
     than the old count-carrying middle line ("Mola finds it from N vendors in
     about 15 seconds."), so the size/measure coupling that forced the 880px
     breakout is gone — the longest line here needs ~600px at 36px and fits
     inside `main`'s 772px column. Ceiling raised 32 → 36px because two shorter
     lines can afford it and still clear the composer AND the chips over the
     1366x768 fold. */
  font-size: clamp(24px, 3.1vw, 36px);
  letter-spacing: -.014em;
  line-height: 1.16;
  color: var(--c-primary);
  max-width: 20ch;
  margin: 0 auto 14px;
  text-wrap: balance;
}
/* Each line is its own block so it breaks where the copy says, not where the
   measure happens to run out. On a phone a line that no longer fits wraps
   naturally inside its own span; `balance` keeps that wrap even rather than
   leaving one orphaned word. */
.hero-h1-l {
  display: block;
  text-wrap: balance;
}
/* Line 2 is the subheader now, not a third claim: it stays in the display face
   so the two lines read as one headline block, but drops to slate so the
   premise/promise hierarchy is visible without a second typeface. */
.hero-h1-l--promise {
  color: var(--c-secondary);
  font-weight: 500;
}

.hero-microline { color: var(--c-secondary); }
/* One object, not a text link with a detached bouncing arrow beneath it: the
   arrow rode below the link and read as the real cue, leaving a 17px-tall
   underlined phrase that no thumb could hit. */
.scroll-cue {
  margin-top: 10px;
  font-size: 13.5px;
  color: var(--c-secondary);
}
.scroll-cue a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 40px;
  padding: 0 16px;
  border: 1px solid var(--c-divider);
  border-radius: var(--radius-full);
  background: var(--c-subtle-2);
  box-shadow: var(--e0);
  text-decoration: none;
  color: inherit;
  transition: box-shadow var(--dur-hover) var(--ease-io),
              border-color var(--dur-hover) var(--ease-io),
              color var(--dur-hover) var(--ease-io);
}
.scroll-cue a:hover { box-shadow: var(--e1); border-color: var(--c-primary); color: var(--c-primary); }
.scroll-cue a:focus-visible { outline: none; box-shadow: var(--e1), var(--ring); }
.scroll-arr { display: inline-block; }
html.motion-ok .scroll-arr { animation: cue-dip 2.4s ease-in-out infinite; }
@keyframes cue-dip {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(5px); }
}

/* ══ DOCKED MINI-COMPOSER ══════════════════════════════════════════
   A mirror of #question-input (app.js syncDockFrom), not a second
   composer. Off-screen until the hero scrolls away. Sits ABOVE the
   sticky navbar (z-index 12) so it isn't half-hidden by it.

   WHY THE MAIN COMPOSER UN-STICKS WHILE THE LANDING IS UP
   ───────────────────────────────────────────────────────
   The app composer is normally `position: sticky` and parks at the top of
   the viewport — which is exactly the dock's job. Left as-is, the landing
   scroll showed TWO stacked ask bars, and the sticky one covered the
   replay stage's tab row. So while the landing narrative is on screen the
   composer is in flow, anchored under the hero the way Camille drew it, and
   the dock is the only top bar. `.landing-live` is added by app.js's
   setLandingVisible(true) and removed on the first turn, which restores the
   composer's normal sticky behaviour for the whole conversation. */
body.landing-live .composer { position: static; }
/* The fade masks exist to hide answers scrolling under a STICKY bar. With the
   bar in flow they have nothing to mask and would just paint 12px + 64px of
   paper into whatever sits above and below it. */
body.landing-live .composer::before,
body.landing-live .composer::after { display: none; }
#landing-dock {
  position: fixed;
  inset: 0 0 auto 0;
  height: 60px;
  z-index: 14;
  background: rgba(240, 245, 249,.94);
  backdrop-filter: saturate(140%) blur(10px);
  -webkit-backdrop-filter: saturate(140%) blur(10px);
  /* The first shadow IS the hairline; the second is what makes it read as a
     layer over scrolling content rather than a seam in it. */
  box-shadow: 0 1px 0 var(--c-divider), 0 12px 28px -20px rgba(18,38,58,.34);
  display: flex;
  align-items: center;
  transform: translateY(-100%);
  visibility: hidden;
  transition: transform 350ms ease, visibility 350ms;
}
#landing-dock.is-on { transform: none; visibility: visible; }
/* The dock is a mirror of the hero composer, so its arrival should read as the
   composer travelling up rather than a new bar appearing. */
html.motion-ok #landing-dock .dock-inner {
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity var(--dur-state) var(--ease-out), transform var(--dur-state) var(--ease-out);
}
html.motion-ok #landing-dock.is-on .dock-inner { opacity: 1; transform: none; }
.dock-inner {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  max-width: 1160px;
  margin: 0 auto;
  padding: 0 24px;
}
.dock-brand {
  font-family: var(--font-display);
  font-weight: 600;
  font-variation-settings: 'opsz' 18;
  font-size: 17px;
  color: var(--c-primary);
  white-space: nowrap;
}
.dock-brand em { font-style: italic; font-weight: 600; }
/* "Ready — press Ask": the tell that a chip/zone filled the box but nothing has
   been sent. Promoted from a 10px whisper to a coral-tint pill — it sits beside
   a full-strength Ask button that is the next action, and it was losing that
   contest. Small coral text, so --c-cta-ink. */
.dock-tag {
  color: var(--c-cta-ink);
  font-size: 11px;
  font-weight: 600;
  padding: 3px 9px;
  background: var(--c-cta-tint);
  border-radius: var(--radius-full);
  white-space: nowrap;
}
.dock-tag[hidden] { display: none; }
#dock-input {
  flex: 1;
  min-width: 0;
  height: 38px;
  border: 1px solid var(--c-divider);
  border-radius: var(--radius-full);
  background: #fff;
  padding: 0 18px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  color: var(--c-primary);
  outline: none;
}
#dock-input::placeholder { color: #9AA7B4; }
#dock-input:focus { border-color: var(--c-primary); }
/* Ask is the app verb → ink, never the reserved coral. */
.dock-ask {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  border: none;
  border-radius: var(--radius-full);
  background: var(--c-primary);
  color: #fff;
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  padding: 10px 20px;
  cursor: pointer;
  transition: background 150ms ease, transform 150ms ease;
}
.dock-ask:hover { background: var(--c-primary-hover); }
html.motion-ok .dock-ask:hover { transform: translateY(-1px); }
.dock-ask svg { width: 13px; height: 13px; }
.dock-ask { min-height: 40px; }
.dock-ask:focus-visible,
#dock-input:focus-visible { outline: none; box-shadow: var(--ring); }

/* "Ask, waiting on you" — one-shot attention pulse fired by setQuery() on
   whichever Ask is on screen, cleared the moment the buyer types or submits.
   The ring alone lands under reduced motion. */
.dock-ask.is-waiting, #ask-btn.is-waiting { box-shadow: var(--ring); }
html.motion-ok .dock-ask.is-waiting,
html.motion-ok #ask-btn.is-waiting { animation: ask-nudge 1.6s var(--ease-io) 2; }
@keyframes ask-nudge {
  0%, 100% { box-shadow: 0 0 0 0 rgba(47,74,102,0); }
  40%      { box-shadow: 0 0 0 6px rgba(47,74,102,.16); }
}

/* ── One focus ring, on the palette ────────────────────────────────────
   Keyboard focus on the landing's controls used to fall through to Chrome's
   default blue double-ring — the only saturated blue on a paper/ink page — and
   several controls (.stage-steal, .stage-run-head) had no focus rule at all.
   One ring per ground, applied everywhere; where an element
   already carries a shadow at focus the two compose (see .zone). Never
   `outline: none` without a replacement. */
.stage-tab:focus-visible,
.stage-run-head:focus-visible,
.stage-steal:focus-visible,
.stage-ask:focus-visible,
.stage-demo-big:focus-visible,
.stage-demo-ghost:focus-visible { outline: none; box-shadow: var(--ring-band); }

.zone:focus-visible,
.example-chip:focus-visible,
.duel-seg button:focus-visible,
.scroll-cue a:focus-visible,
.dock-ask:focus-visible,
#dock-input:focus-visible,
.duel-v-demo:focus-visible,
.navbar-slim__signin:focus-visible { outline: none; box-shadow: var(--ring); }

/* ══ ANSWER REPLAY STAGE ═══════════════════════════════════════════ */
/* 4fr/7fr, not the prototype's 5fr/7fr: `main` is 820px here where Camille's
   page was 1140px, so the stage needs the extra column to fit its three tabs
   on one row. */
.replay-grid {
  display: grid;
  grid-template-columns: 4fr 7fr;
  gap: 36px;
  align-items: start;
}
.replay-copy { position: sticky; top: calc(var(--header-h) + 78px); }
/* `.replay-proof` (the duplicated corpus microline) went in round 4 and
   `.replay-eyebrow` / `.replay-lede` in round 5, so this column is now the H2
   alone. Its sticky offset is retuned in the round-5 block at the end of the
   file rather than here, alongside the alignment change it belongs to. */

/* The anatomy legend is RETIRED. It taught the answer's four parts through a
   hover coupling that fired an outline on a region of the stage — which below
   980px was off-screen, so the coupling was functionally dead on every phone,
   and on desktop it looked like a definition list rather than four controls.
   The same labels now ride inside the winner card as always-on inline
   annotations (.anat, rendered by app.js), which need no gesture and work on
   every device. The stage-side highlight rules go with it.
   `margin-top` is what makes these read as SECTION labels introducing the line
   beneath them; without it a label sat tight under the previous line and
   parsed as that line's subtitle. */
.stage-win .anat {
  display: block;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--c-muted);
  margin-top: 14px;
  margin-bottom: 3px;
}

.replay-stage {
  background: var(--band);
  border-radius: var(--radius-xl);
  overflow: hidden;
  box-shadow: 0 32px 64px -28px rgba(15, 32, 54,.5);
  min-height: 520px;
  display: flex;
  flex-direction: column;
}
/* Three tabs on one row at the stage's column width. They scroll horizontally
   rather than wrapping: a second tab row would change the stage's height and
   push the whole narrative down. */
.stage-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--hairline-dark);
  overflow-x: auto;
  scrollbar-width: none;
  /* Edge fade so a clipped third tab announces itself on narrow screens — the
     row scrolls with the scrollbar hidden, so there was no other cue. */
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
          mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
}
@media (min-width: 981px) {
  .stage-head { -webkit-mask-image: none; mask-image: none; }
}
.stage-head::-webkit-scrollbar { display: none; }
.stage-tab {
  position: relative;
  overflow: hidden;
  flex: 0 0 auto;
  white-space: nowrap;
  min-height: 40px;
  border: 1px solid var(--band-line);
  /* A real fill at rest: transparent pills on near-black read as disabled
     ghosts, and two of three tabs are always at rest. */
  background: var(--band-e0);
  color: rgba(255, 253, 247,.82);
  border-radius: var(--radius-full);
  padding: 9px 15px;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  transition: background var(--dur-hover) var(--ease-io),
              color var(--dur-hover) var(--ease-io),
              border-color var(--dur-hover) var(--ease-io);
}
.stage-tab:hover { background: var(--band-e1); border-color: var(--band-line-2); color: var(--cream); }
.stage-tab:active { background: var(--band-e1r); }
.stage-tab.is-on {
  background: var(--cream);
  color: var(--band);
  border-color: var(--cream);
  font-weight: 600;
}
/* A 2px coral progress line under the playing tab turns the row into a player,
   which is the mental model the three reels want. */
.stage-tab.is-on::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  width: 0;
  background: var(--c-cta);
}
html.motion-ok .stage-tab.is-on.is-playing::after { animation: tab-progress 3.6s linear forwards; }
@keyframes tab-progress { to { width: 100%; } }
.stage-body {
  position: relative;
  padding: 22px 20px 24px;
  display: flex;
  flex-direction: column;
  flex: 1;
}
.stage-feed {
  display: flex;
  flex-direction: column;
  gap: 13px;
  transition: opacity 350ms ease;
}
/* An IN-FLOW FOOTER, not a scrim. It used to be `position:absolute; inset:0`
   with the feed dimmed to .45 behind it — so 3.6s after the payoff landed we
   painted over the vendor name, the why-line and the coral CTA the whole
   section exists to produce, AND swallowed every click underneath: the runner
   strips and "Use this question" were unreachable once the reel ended, with
   nothing announcing that clicking empty scrim would dismiss it. Now nothing
   dims, nothing is covered, everything stays clickable, and one whole state
   leaves the machine. */
.stage-end {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--band-line);
  opacity: 0;
  transform: translateY(6px);
  pointer-events: none;
  transition: opacity var(--dur-enter) var(--ease-out),
              transform var(--dur-enter) var(--ease-out);
}
.stage-body.is-ended .stage-end { opacity: 1; transform: none; pointer-events: auto; }
.stage-end p {
  font-family: var(--font-sans);
  font-size: 13.5px;
  font-weight: 500;
  line-height: 1.4;
  color: rgba(255, 253, 247,.68);
}
.stage-ask { flex-shrink: 0; }

/* Skeleton winner card. The stage min-heights for its FINAL state, so for the
   ~2.5s before the card lands the 520px panel was 60% empty dark with an
   orphaned runner strip floating at the bottom — it read as a broken render,
   not a build-up. The card now resolves into a reserved box. */
.stage-skel {
  border-radius: var(--radius-lg);
  background: var(--band-e1);
  border: 1px solid var(--band-line);
  min-height: 188px;
  padding: 20px 22px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.stage-skel i {
  display: block;
  height: 11px;
  border-radius: 4px;
  background: rgba(255, 255, 255,.09);
}
.stage-skel i:nth-child(1) { width: 84px; height: 22px; border-radius: var(--radius-full); }
.stage-skel i:nth-child(2) { width: 46%;  height: 26px; }
.stage-skel i:nth-child(3) { width: 92%; }
.stage-skel i:nth-child(4) { width: 76%; }
.stage-skel i:nth-child(5) { width: 100%; height: 44px; border-radius: var(--radius-full); margin-top: auto; }
/* A travelling highlight, not just a fade. Opacity alone read as a stalled
   loader for the ~2.5s hold; a sweep says "working". Same gesture as the chat
   surfaces' .skeleton shimmer, re-mixed light-on-dark for the band. */
html.motion-ok .stage-skel i {
  background-image: linear-gradient(90deg,
    rgba(255,255,255,0) 20%,
    rgba(255,255,255,.13) 50%,
    rgba(255,255,255,0) 80%);
  background-size: 200% 100%;
  animation: skel-breathe 1.4s var(--ease-io) infinite,
             shimmer 1.6s ease-in-out infinite;
}
html.motion-ok .stage-skel i:nth-child(2) { animation-delay: 90ms, 90ms; }
html.motion-ok .stage-skel i:nth-child(3) { animation-delay: 180ms, 180ms; }
html.motion-ok .stage-skel i:nth-child(4) { animation-delay: 260ms, 260ms; }
html.motion-ok .stage-skel i:nth-child(5) { animation-delay: 340ms, 340ms; }
@keyframes skel-breathe { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
.stage-skel.is-out { opacity: 0; transition: opacity 200ms var(--ease-io); }
/* "Ask yours" is the app verb → cream-on-ink-ish, not coral. */
.stage-ask {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--cream);
  border-radius: var(--radius-full);
  background: var(--cream);
  color: var(--band);
  font-family: inherit;
  font-size: 14.5px;
  font-weight: 700;
  padding: 13px 28px;
  cursor: pointer;
  transition: background 150ms ease, transform 150ms ease;
}
.stage-ask:hover { background: #fff; }
html.motion-ok .stage-ask:hover { transform: translateY(-1px); }
.stage-ask svg { width: 13px; height: 13px; }
.stage-ask:focus-visible { outline: none; box-shadow: var(--ring-band); }

.stage-q {
  align-self: flex-end;
  max-width: 85%;
  background: var(--band-soft);
  border: 1px solid var(--hairline-dark);
  color: var(--cream);
  border-radius: 16px 16px 4px 16px;
  padding: 11px 16px;
  font-size: 15px;
  font-weight: 500;
  line-height: 1.5;
  min-height: 44px;
}
.stage-caret {
  display: inline-block;
  width: 2px;
  height: 1em;
  background: var(--cream);
  vertical-align: -2px;
}
html.motion-ok .stage-caret { animation: stage-caret 1s steps(1) infinite; }
@keyframes stage-caret { 50% { opacity: 0; } }

/* A real button, not a 12.5px dashed-underline text link that read as a
   citation. The consequence ("nothing sends") moves out of the title attribute
   — invisible on touch — into a visible sibling line. */
.stage-steal-wrap {
  align-self: flex-end;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
  margin-top: 2px;
}
.stage-steal {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 34px;
  padding: 0 13px;
  background: var(--band-e1);
  border: 1px solid var(--band-line-2);
  border-radius: var(--radius-full);
  color: rgba(255, 253, 247,.86);
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-hover) var(--ease-io),
              border-color var(--dur-hover) var(--ease-io),
              color var(--dur-hover) var(--ease-io);
}
.stage-steal svg { width: 13px; height: 13px; flex-shrink: 0; }
.stage-steal:hover { background: var(--band-e1r); border-color: var(--cream); color: var(--cream); }
.stage-steal:active { background: rgba(255, 255, 255,.2); }
.stage-steal:focus-visible { outline: none; box-shadow: var(--ring-band); }
@media (max-width: 720px) {
  .stage-steal { min-height: 44px; }
}

.stage-think {
  display: flex;
  gap: 5px;
  align-items: center;
  padding: 6px 4px;
  min-height: 24px;
}
.stage-think i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #57708C;
}
html.motion-ok .stage-think i { animation: stage-think 1s ease-in-out infinite; }
html.motion-ok .stage-think i:nth-child(2) { animation-delay: 160ms; }
html.motion-ok .stage-think i:nth-child(3) { animation-delay: 320ms; }
@keyframes stage-think {
  0%, 100% { opacity: .35; transform: translateY(0); }
  50%      { opacity: 1;   transform: translateY(-3px); }
}

/* Narrowing line: "<live count> vendors → Filed: X → 1 top match". Both the
   count and the filing come from the server; nothing is typed here. */
.stage-narrow {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: rgba(255, 253, 247,.6);
  min-height: 17px;
  padding: 2px 2px 0;
}
.narrow-seg { display: inline-block; }
html.motion-ok .narrow-seg {
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 200ms ease, transform 200ms ease;
}
html.motion-ok .narrow-seg.is-in { opacity: 1; transform: none; }
.narrow-seg-hot { color: var(--c-cta-hot); }   /* 4.67:1 on band ✅ */

.stage-win {
  background: #fff;
  border-radius: var(--radius-lg);
  padding: 20px 22px 16px;
  box-shadow: 0 12px 32px rgba(4, 12, 24,.35);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
html.motion-ok .stage-win {
  opacity: 0;
  transform: translateY(14px) scale(.985);
  transition: opacity 260ms ease, transform 260ms cubic-bezier(.22,1,.36,1);
}
html.motion-ok .stage-win.is-in { opacity: 1; transform: none; }
/* Coral FILL behind cream text at 11px/600 — legal because the text is on the
   fill, not the fill on paper (4.29:1 white-on-coral is AA for bold ≥14px;
   this is a badge, so cream at 600 with the mono's wide tracking reads). */
.stage-badge {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: #fff;
  background: var(--c-cta);
  border-radius: var(--radius-full);
  padding: 4px 11px;
  opacity: 0;
}
.stage-badge.is-stamped { opacity: 1; }
html.motion-ok .stage-badge.is-stamped { animation: stage-stamp 180ms ease-out forwards; }
@keyframes stage-stamp {
  from { opacity: 0; transform: scale(1.15); }
  to   { opacity: 1; transform: scale(1); }
}
.stage-win-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-variation-settings: 'opsz' 40;
  font-size: 26px;
  letter-spacing: -.015em;
  line-height: 1.1;
  color: var(--c-primary);
  margin-top: 10px;
}
/* The "why it fits" voice — italic Source Serif 4 + hairline rule, matching the
   vendor cards Phase 1 settled. */
.stage-win-why {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-variation-settings: 'opsz' 18;
  font-size: 16px;
  line-height: 1.45;
  color: var(--c-primary);
  border-left: 2px solid var(--c-divider);
  padding-left: 12px;
  margin-top: 10px;
}
/* Conversion. 16px/800 white-on-coral clears AA comfortably. */
.stage-demo-big {
  width: 100%;
  height: 48px;
  border: none;
  border-radius: var(--radius-full);
  margin-top: 12px;
  background: var(--c-cta);
  color: #fff;
  font-family: inherit;
  font-size: 16px;
  font-weight: 800;
  letter-spacing: .01em;
  cursor: pointer;
  transition: background 150ms ease;
}
/* The loudest button on the page had no depth response at all. One primary
   coral spec, shared with .v-win .cta-primary and .duel-v-demo. */
.stage-demo-big { min-height: 46px; height: auto; box-shadow: var(--e1); }
.stage-demo-big:hover { background: var(--c-cta-hover); box-shadow: var(--e1-raised); }
.stage-demo-big:focus-visible { outline: none; box-shadow: var(--e1), var(--ring-band); }

/* Hierarchy by SURFACE LIGHTNESS, not opacity. These strips sat at .65 on the
   ink band, which is the disabled convention — they read as unavailable rather
   than secondary, and the .06 wash was barely distinguishable from the band
   itself. */
.stage-run {
  border: 1px solid var(--band-line);
  background: var(--band-e1);
  border-radius: var(--radius-md);
  opacity: 1;
  transition: background var(--dur-hover) var(--ease-io),
              border-color var(--dur-hover) var(--ease-io);
}
html.motion-ok .stage-run {
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 250ms ease, transform 250ms ease,
              background var(--dur-hover) var(--ease-io),
              border-color var(--dur-hover) var(--ease-io);
}
html.motion-ok .stage-run.is-in { opacity: 1; transform: none; }
.stage-run.is-rest { opacity: 1; }
.stage-run:hover,
.stage-run:focus-within,
.stage-run.is-open { background: var(--band-e1r); border-color: var(--band-line-2); }
.stage-run-head {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  background: none;
  border: none;
  padding: 13px 14px;
  text-align: left;
  min-height: 48px;
  font-family: inherit;
  cursor: pointer;
}
.stage-run-head:focus-visible { outline: none; box-shadow: var(--ring-band); border-radius: var(--radius-md); }
/* The rank digits are the section's best structural device — they encode real
   ordinal information — so give them the weight to carry it. */
.stage-rank {
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 12px;
  width: 16px;
  text-align: center;
  flex-shrink: 0;
  color: rgba(255, 253, 247,.55);
  transition: color var(--dur-hover) var(--ease-io);
}
.stage-run.is-open .stage-rank { color: var(--c-cta-hot); }
.stage-run-name { font-size: 14px; font-weight: 600; color: rgba(255, 253, 247,.94); white-space: nowrap; }
.stage-fork { font-size: 13px; line-height: 1.4; color: rgba(255, 253, 247,.72); flex: 1; min-width: 0; }
.stage-chev {
  color: rgba(255, 253, 247,.72);
  font-size: 15px;
  transition: transform var(--dur-state) var(--ease-io), color var(--dur-hover) var(--ease-io);
}
.stage-run:hover .stage-chev { color: var(--cream); }
html.motion-ok .stage-run:hover .stage-chev { transform: translateX(2px); }
.stage-run.is-open .stage-chev { transform: rotate(90deg); }
html.motion-ok .stage-run.is-open:hover .stage-chev { transform: rotate(90deg) translateX(2px); }
.stage-run-body {
  max-height: 0;
  overflow: hidden;
  padding: 0 14px;
  transition: max-height 220ms ease;
}
.stage-run.is-open .stage-run-body { padding-bottom: 13px; }
.stage-run-why {
  font-family: var(--font-display);
  font-style: italic;
  font-variation-settings: 'opsz' 16;
  font-size: 13.5px;
  line-height: 1.5;
  color: rgba(255, 253, 247,.8);
  margin: 2px 0 10px;
}
.stage-demo-ghost {
  height: 36px;
  padding: 0 16px;
  border-radius: var(--radius-full);
  background: transparent;
  border: 1px solid rgba(255, 255, 255,.4);
  color: var(--cream);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background 150ms ease;
}
.stage-demo-ghost:hover { background: rgba(255, 255, 255,.1); }
.stage-demo-ghost:focus-visible { outline: none; box-shadow: var(--ring-band); }
@media (max-width: 720px) {
  .stage-demo-ghost { min-height: 44px; }
}

/* ══ SIX PROBLEM ZONES ═════════════════════════════════════════════ */
.zone-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}
.zone {
  position: relative;
  text-align: left;
  background: #fff;
  border: 1px solid var(--c-divider);
  border-radius: var(--radius-lg);
  padding: 20px 20px 18px;
  min-height: 200px;
  display: flex;
  flex-direction: column;
  gap: 9px;
  font-family: inherit;
  cursor: pointer;
  /* E1 at rest: this is the primary CTA of its section and it shipped as a flat
     white rectangle with a hairline — the same treatment the non-interactive
     scenery had. Depth is what says "press me". */
  box-shadow: var(--e1);
  transition: transform var(--dur-hover) var(--ease-io),
              box-shadow var(--dur-hover) var(--ease-io),
              border-color var(--dur-hover) var(--ease-io);
}
.zone:hover, .zone:focus-visible {
  box-shadow: var(--e1-raised);
  border-color: #D8CDBA;
}
html.motion-ok .zone:hover,
html.motion-ok .zone:focus-visible { transform: translateY(-3px); }
.zone:active { box-shadow: var(--e1-press); }
html.motion-ok .zone:active { transform: translateY(0) scale(.995); transition-duration: var(--dur-tap); }
.zone:focus-visible { outline: none; box-shadow: var(--e1-raised), var(--ring); }
.zone-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-variation-settings: 'opsz' 30;
  font-size: 22px;
  letter-spacing: -.012em;
  line-height: 1.12;
  color: var(--c-primary);
}
/* THE QUESTION IS ALWAYS VISIBLE, never revealed on hover. At rest the card
   used to end in a dim archival status stamp and the payload, the question you
   would be asking, was a discovery a first-time visitor might never make; the
   touch fallback for that gate is gone with the gate.
   ROUND 5: the eyebrow that stamp became, and the body line under the name, are
   deleted too (founder). The card is now the name and the question, and hover
   INTENSIFIES the arrow rather than revealing anything.
   `margin-top: auto` pins the footer to the card's bottom so every card in a
   row lines its question up regardless of how long the name wraps. */
.zone-foot {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  margin-top: auto;
  padding-top: 12px;
  border-top: 1px solid var(--c-divider);
}
.zone-q {
  flex: 1;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.4;
  color: var(--c-cta-ink);          /* small coral text → the text-safe token */
}
.zone-go {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--c-subtle-2);
  border: 1px solid var(--c-divider);
  color: var(--c-cta-ink);
  font-size: 14px;
  line-height: 1;
  transition: background var(--dur-hover) var(--ease-io),
              border-color var(--dur-hover) var(--ease-io),
              transform var(--dur-hover) var(--ease-out);
}
.zone:hover .zone-go, .zone:focus-visible .zone-go {
  background: var(--c-cta-tint);
  border-color: var(--c-cta-border);
}
html.motion-ok .zone:hover .zone-go,
html.motion-ok .zone:focus-visible .zone-go { transform: translateX(3px); }

/* ── "You loaded this" — one consumed state shared by zones and chips ──
   Clicking either fills the composer, but the source used to flash for 450ms
   and revert, so nothing connected the thing you clicked to the box that now
   holds it. Persists until another source is clicked or the box is cleared. */
.zone.is-loaded,
.example-chip.is-loaded {
  border-color: var(--c-cta-border);
  background: var(--c-cta-tint);
  box-shadow: var(--e0);            /* settles DOWN — it has been used */
}
.zone.is-loaded .zone-go {
  background: var(--c-cta);
  border-color: var(--c-cta);
  color: #fff;
}
html.motion-ok .zone.is-loaded { transform: none; }
.example-chip--card.is-loaded .chip-label { color: var(--c-cta-ink); }
.example-chip--card.is-loaded .chip-outcome { color: var(--c-secondary); }

/* Phone-only disclosure for zones 4-6 — see the ≤720px block. */
.zones-more {
  display: none;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  margin-top: 14px;
  padding: 0 18px;
  background: var(--c-subtle-2);
  border: 1px solid var(--c-divider);
  border-radius: var(--radius-full);
  box-shadow: var(--e0);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--c-primary);
  cursor: pointer;
  transition: box-shadow var(--dur-hover) var(--ease-io), border-color var(--dur-hover) var(--ease-io);
}
.zones-more:hover { box-shadow: var(--e1); border-color: var(--c-primary); }
.zones-more:focus-visible { outline: none; box-shadow: var(--e1), var(--ring); }
.zones-more[aria-expanded="true"] { display: none; }

/* ══ GENERAL-AI DUEL ═══════════════════════════════════════════════ */
.duel-h2 { font-size: clamp(26px, 3.4vw, 36px); }
/* `.duel-lede` was deleted in round 4 and its rule with it in round 5. The grid
   below survives only for the `.duel-head--solo` override further down, which
   collapses it to a block now that the toggle is the head's only child. */
.duel-head {
  display: grid;
  grid-template-columns: minmax(0, 58ch) 1fr;
  gap: 24px 32px;
  align-items: end;
}
.duel-toggle {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin: 22px 0 16px;
}
@media (min-width: 901px) {
  .duel-head .duel-toggle {
    margin: 0 0 2px;
    justify-content: flex-end;
  }
}
@media (max-width: 900px) {
  .duel-head { display: block; }
}
.duel-hint { color: var(--c-secondary); display: inline-block !important; margin: 0 !important; }
/* A two-segment control, not an iOS switch. A toggle means on/off; this picks
   a side, which is a segmented-control concept — and it used to start in the
   left position while BOTH cards were already readable, so its only visible
   effect was a 42%-opacity difference. role="radiogroup" is the honest mapping
   and brings arrow-key navigation from the platform for free. */
.duel-seg {
  display: inline-flex;
  align-items: stretch;
  padding: 3px;
  gap: 3px;
  background: var(--c-subtle-2);
  border: 1px solid var(--c-divider);
  border-radius: var(--radius-full);
  box-shadow: var(--e0);
}
.duel-seg button {
  min-height: 38px;
  padding: 0 18px;
  border: none;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--c-secondary);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-state) var(--ease-io),
              color var(--dur-state) var(--ease-io),
              box-shadow var(--dur-state) var(--ease-io);
}
.duel-seg button:hover { color: var(--c-primary); }
.duel-seg button[aria-checked="true"] {
  background: #fff;
  color: var(--c-primary);
  box-shadow: var(--e1);
}
.duel-seg button:focus-visible { outline: none; box-shadow: var(--ring); }
@media (max-width: 720px) {
  .duel-seg button { min-height: 44px; }
}

.duel {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
  align-items: stretch;
}
.duel-card {
  border-radius: var(--radius-lg);
  padding: 22px 24px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  transition: background 300ms ease, border-color 300ms ease,
              box-shadow 300ms ease, transform 300ms ease;
  /* Both cards carry the SAME border width at rest so selecting a side changes
     colour and elevation without resizing anything. The width used to differ
     (1px vs 1.5px), which cost 5px of card height on every toggle now that
     either side can be the focused one. */
  border: 1.5px solid var(--c-divider);
  background: #fff;
  box-shadow: var(--e0);
}
.duel-guess { color: var(--c-secondary); }
.duel-tag { font-size: 10.5px; color: var(--c-secondary); display: block !important; margin: 0 !important; }
.duel-mola .duel-tag { color: var(--c-cta-ink); }
.duel-q { font-size: 14.5px; font-weight: 600; color: var(--c-primary); }
.duel-guess .duel-q { color: var(--c-secondary); }
.duel-blah { font-size: 13.5px; line-height: 1.62; font-style: normal; margin: 0 0 9px; }
.duel-blah:last-child { margin-bottom: 0; }

/* ── The ChatGPT card's capped, self-scrolling body ──────────────────────
   The real ChatGPT answer is ~400 words. Its LENGTH is the argument, so it is
   never trimmed: the body is capped to roughly the Mola card's height and the
   remainder scrolls. Both cards keep the same width and the same chrome, so
   the comparison stays symmetric — the only asymmetry is the one being
   demonstrated, which is how much text there is.
   --duel-cap is read by app.js too (the autoscroll distance derives from it),
   so change the height here and the motion follows. */
.duel-blah-wrap {
  position: relative;
  --duel-cap: 260px;
}
.duel-blah-scroll {
  max-height: var(--duel-cap);
  overflow-y: auto;
  /* Always user-scrollable, autoscroll or not: the auto motion is a hint at
     the scale, never the only way to read the text. */
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--c-divider) transparent;
  /* Top/bottom fade masks say "this continues" without a gradient overlay that
     would have to match the card fill. mask-image is the whole effect, so the
     unprefixed + -webkit pair are both declared. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 18px, #000 calc(100% - 26px), transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 18px, #000 calc(100% - 26px), transparent 100%);
}
.duel-blah-scroll:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--radius-sm); }
.duel-blah-scroll::-webkit-scrollbar { width: 5px; }
.duel-blah-scroll::-webkit-scrollbar-thumb { background: var(--c-divider); border-radius: 999px; }
/* Hanging numerals: the 7 steps keep their structure, and the number sits in
   the gutter so the prose block stays a clean measure. */
.duel-blah-num { padding-left: 22px; position: relative; }
.duel-blah-n {
  position: absolute;
  left: 0;
  top: 1px;
  font-size: 10.5px;
  letter-spacing: .04em;
  color: var(--c-muted);
  display: inline-block !important;
  margin: 0 !important;
}
/* The scale caption. The number is computed from the answer string server-side
   (app.py _DUEL_CHATGPT_WORDS), so it cannot overstate the verbosity. It ships
   in every mode, including reduced-motion and embed, because it is the part
   that communicates scale when nothing is moving. */
.duel-blah-meta {
  margin: 0 !important;
  padding-top: 10px;
  border-top: 1px solid var(--c-divider);
  font-size: 10.5px;
  color: var(--c-muted);
  display: block !important;
}
/* The unfocused side recedes rather than disappearing — the section's whole
   thesis is two answers to one question, side by side, so BOTH must stay
   readable at a glance.

   Opacity is the wrong lever for that and this rule used to reach for it. On a
   light card any opacity below 1 composites every glyph toward the page ground
   and scales all text contrast down multiplicatively: at the previous .55 the
   unfocused body text measured 2.17:1 and its headings 3.53:1, both far under
   the 4.5 AA floor — legible only if you already knew what it said. Even a gentle
   .88 lands slate at 4.08, still short. There is no opacity that both dims and
   stays accessible.

   So de-emphasis is carried entirely by BORDER, ELEVATION and SURFACE, and the
   text keeps its full-strength colour: ink 14.69:1 and slate 5.22:1 on the
   subtle fill, versus 15.40 and 5.47 on the focused card's white. Both sides are
   comfortably AA; the focused one is distinguished by being lifted and outlined,
   not by the other being washed out. */
.duel.duel-focus-left  .duel-mola,
.duel.duel-focus-right .duel-guess {
  background: var(--c-subtle-2);
  border-color: var(--c-divider);
  box-shadow: none;
}
/* The focused side is the one that gains: a full-strength border, white paper
   and real elevation. Applied to whichever card is selected, so the two
   directions are symmetric. */
.duel.duel-focus-left  .duel-guess,
.duel.duel-focus-right .duel-mola {
  background: #fff;
  border-color: var(--c-primary);
  box-shadow: var(--e1-raised);
}
html.motion-ok .duel.duel-focus-left  .duel-mola,
html.motion-ok .duel.duel-focus-right .duel-guess { transform: scale(.99); }
/* When the Mola card recedes, its Book-a-Demo must recede WITH it. A
   saturated coral fill on a de-emphasised card is an ambiguous half-state:
   coral is conversion-reserved, so it reads as live while the card around it
   reads as secondary. Neutralised to an outlined button — it returns to full
   coral the moment that side is selected.

   This is now the DEFAULT state (ChatGPT opens focused), so the button has to
   survive it as a real, readable control rather than a ghost: a filled
   `--c-subtle-2` would be invisible against the unfocused card's own
   `--c-subtle-2` ground. Outlined on white instead, with cta-ink text at
   5.75:1, so it still reads as the button it is. */
.duel.duel-focus-left .duel-mola .duel-v-demo {
  background: #fff;
  border: 1px solid var(--c-divider);
  color: var(--c-cta-ink);
  box-shadow: none;
}

/* The ChatGPT card is taller (its capped body + caption), and `align-items:
   stretch` on the grid matches the Mola card to it. Rather than leave the
   difference as a dead gap under the Book-a-Demo button, the answer block
   centres in the space it is given: the two cards stay the same size, and
   Mola's short answer reads as deliberately brief rather than unfinished. */
.duel-mola .duel-answer { margin-top: auto; margin-bottom: auto; }
.duel-answer { display: flex; flex-direction: column; gap: 7px; }
.duel-v-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.duel-v-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-variation-settings: 'opsz' 24;
  font-size: 18px;
  letter-spacing: -.011em;
  color: var(--c-primary);
}
.duel-v-zone {
  font-size: 10px;
  color: var(--c-secondary);
  border: 1px solid var(--c-divider);
  border-radius: var(--radius-full);
  padding: 3px 9px;
  white-space: nowrap;
  display: inline-block !important;
  margin: 0 !important;
}
.duel-v-fn { font-size: 13.5px; line-height: 1.5; color: var(--c-secondary); }
.duel-v-why { font-size: 14.5px; }
.duel-v-foot { margin-top: 3px; }
/* Conversion — the only coral fill on this section. 14px/700 white-on-coral. */
.duel-v-demo {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: none;
  border-radius: var(--radius-full);
  background: var(--c-cta);
  color: #fff;
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  padding: 9px 17px;
  cursor: pointer;
  transition: background 150ms ease;
}
.duel-v-demo { min-height: 46px; font-size: 15px; font-weight: 800; padding: 0 20px; box-shadow: var(--e1); }
.duel-v-demo:hover { background: var(--c-cta-hover); box-shadow: var(--e1-raised); }
.duel-v-demo:focus-visible { outline: none; box-shadow: var(--e1), var(--ring); }

.duel-closer {
  margin-top: 16px;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 600;
  font-variation-settings: 'opsz' 24;
  font-size: 18px;
  color: var(--c-primary);
  opacity: 0;
}
.duel-closer.is-on { opacity: 1; }
html.motion-ok .duel-closer {
  transform: translateY(6px);
  transition: opacity 400ms ease, transform 400ms ease;
}
html.motion-ok .duel-closer.is-on { transform: none; }

/* ══ CLOSING BAND ══════════════════════════════════════════════════ */
/* ROUND 6: two elements, a headline and the tier-gated count sentence, and no
   control of its own. It was headline + sub + coral button + microline + five
   chips; round 5 took the button and its microline ("we are already providing a
   text box above"), round 6 the chips ("the chips are repeated" — they were the
   five hero chips reprinted verbatim). It is the page's last word, not a second
   ask, and the composer at the top plus the standalone dock carry the asking.

   THE HAIRLINE AND THE TONAL STEP STAY. They were added because the catalog
   band sat directly above at the same tone and the two read as one slab. That
   band is gone in round 6, so the neighbour above is now the paper replay
   section; the step and the rule cost nothing there and still give the band a
   defined top edge against the light ground.

   PADDING: 48px -> 64px. The band went from five stacked rows to two, and the
   48px it used to run was sized against that stack. With two centred elements
   the old value read as a strip rather than a beat; 64px restores the ratio of
   air to content the band had, without making it taller overall. */
#landing-close {
  padding: 64px 0;
  text-align: center;
  background: var(--band-soft);
  border-top: 1px solid var(--band-line-2);
}
.close-inner { max-width: 640px; }
/* Whichever of the two is last owns no bottom margin: the band's own padding is
   the space below it, and a margin there would print it twice. That matters in
   BOTH shapes, because the count sentence is tier-gated (app.py
   _get_proof_stats) and below the floor the headline is the band on its own.
   Measured at 1280: 65px above / 64px below with the sentence, 65/64 without. */
#landing-close h2 { margin-bottom: 14px; }
#landing-close .close-inner > :last-child { margin-bottom: 0; }
.close-sub {
  color: #A9BBCF;
  font-size: 15.5px;
  line-height: 1.6;
  margin-bottom: 0;
}
@media (max-width: 720px) {
  #landing-close { padding: 48px 0; }
}

/* ── Landing narrative, tablet ─────────────────────────────────────
   Above the 720px phone block: the two-column stage and the three-up zone
   grid both need to break before the phone breakpoint does. */
@media (max-width: 980px) {
  .replay-grid { grid-template-columns: 1fr; gap: 32px; }
  .replay-copy { position: static; }
  .zone-grid { grid-template-columns: repeat(2, 1fr); }
}
/* Below 820px the switch used to be a visible no-op: the dimming was disabled
   entirely, so the control animated and changed nothing. It becomes a real
   one-at-a-time switcher instead — a phone has no room to hold both cards, and
   seeing two different answers to the same question IS the section's thesis. */
@media (max-width: 820px) {
  .duel { grid-template-columns: 1fr; }
  .duel.duel-focus-left  .duel-mola,
  .duel.duel-focus-right .duel-guess {
    display: none;
    transform: none;
    background: #fff;
  }
  /* One card at a time, so the visible one is always the full-strength
     treatment — there is no second card to contrast it against. Both
     directions, since ChatGPT is now the default side. */
  .duel.duel-focus-left  .duel-guess,
  .duel.duel-focus-right .duel-mola { box-shadow: var(--e1); }
  /* One card at a time here, so the cap can be a little taller without the
     ChatGPT side dwarfing anything beside it. */
  .duel-blah-wrap { --duel-cap: 300px; }
}

/* ── Embed mode ────────────────────────────────────────────────────
   The iframe now renders the SAME sections as standalone; only the scroll-
   docked mini composer is omitted (position: fixed pins to the auto-height
   iframe's content height, not the buyer's viewport). These rules tighten the
   vertical rhythm — the frame sits inside a platform page that has already
   spent the buyer's first screen — and drop the scroll cue, whose in-page
   anchor jump does not move a parent-scrolled iframe. */
body.mola-embed .landing-section { padding: 40px 0; }
body.mola-embed .scroll-cue { display: none; }
body.mola-embed .zone-grid { grid-template-columns: repeat(2, 1fr); }
/* BACKSTOP for the embed reveal bug. The .rv IntersectionObserver's implicit
   root is the iframe's own viewport, but an auto-height iframe never scrolls —
   the PARENT does — so elements below the initial fold could never intersect
   and all six zones shipped at opacity 0. app.js reveals everything up front in
   embed; this makes a JS failure incapable of hiding content. */
body.mola-embed .rv { opacity: 1 !important; transform: none !important; }

/* ── Empty-state chips (hidden once a question is asked) ─────────── */
#empty-state-chips {
  text-align: center;
  padding: 4px 16px 10px;
}
.examples-label {
  font-size: 10.5px;
  font-weight: 700;
  color: var(--c-secondary);
  text-transform: uppercase;
  letter-spacing: .14em;
  margin-bottom: 14px;
}
.examples {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
}

.example-chip {
  /* Reset native <button> defaults */
  font-family: inherit;
  line-height: 1.4;
  margin: 0;
  /* Chip styling */
  background: var(--card);
  border: 1px solid var(--c-divider);
  border-radius: var(--radius-full);
  padding: 8px 16px;
  font-size: 13px;
  font-weight: 500;
  color: var(--c-secondary);
  text-align: left;        /* wrapped multi-line chips stay left-aligned */
  cursor: pointer;
  transition: border-color 150ms ease, color 150ms ease, background 150ms ease, transform 120ms ease, box-shadow 150ms ease;
  user-select: none;
}
/* The prototype fills a chip with coral on hover; here coral is reserved
   for conversion, so chips fill with INK — the same "chip commits" gesture
   without spending the accent. White on ink is 15.40:1 at 13px ✅. */
.example-chip:hover {
  border-color: var(--c-primary);
  background: var(--c-primary);
  color: #fff;
}
.example-chip:active { transform: scale(.985); }
.example-chip:focus { outline: none; }
.example-chip:focus-visible {
  outline: none;
  border-color: var(--c-primary);
  background: var(--c-primary);
  color: #fff;
  box-shadow: var(--ring);
}

/* Intent-card variant of the hero chips: label on line 1, a one-line outcome
   under it. ADDITIVE — a separate class, never a restyle of .example-chip
   itself, because that class is also worn by the dig-deeper follow-ups, the
   error-recovery chips and the greyed .turn-mola.is-stale chips. */
/* On the landing rail (>=721px) these are SINGLE-LINE PILLS — Camille's
   original form. As two-line cards they cost 169px where 44px does the job,
   and that 125px is exactly what pushed the chip row below the 1366x768 fold.
   The two-line form survives at <=720px, where the chips stack anyway and the
   outcome copy has room. */
.example-chip--card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  min-width: 220px;
  flex: 1 1 220px;
  max-width: 300px;
  border-radius: var(--radius-md);
  padding: 12px 16px;
  /* E1 at rest: same class of act as a zone, and it shipped flat. */
  box-shadow: var(--e1);
  transition: box-shadow var(--dur-hover) var(--ease-io),
              border-color var(--dur-hover) var(--ease-io),
              background var(--dur-hover) var(--ease-io),
              color var(--dur-hover) var(--ease-io),
              transform var(--dur-hover) var(--ease-io);
}
@media (min-width: 721px) {
  .example-chip--card {
    flex-direction: row;
    align-items: center;
    min-width: 0;
    flex: 0 0 auto;
    max-width: none;
    min-height: 40px;
    border-radius: var(--radius-full);
    padding: 9px 13px;
  }

  .example-chip--card .chip-outcome { display: none; }
}
.example-chip--card .chip-label {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--c-primary);
  line-height: 1.35;
}
/* One row of five on the landing rail: the label drops half a point here so all
   five pills fit the 1120px measure without wrapping to a second row. */
@media (min-width: 721px) {
  .example-chip--card .chip-label { font-size: 12.5px; }
  .examples { gap: 7px; }
}

/* A tint-and-lift, not a full ink inversion. Inverting is right for a bare
   13px pill; on a card it obliterates the label/outcome hierarchy, and it made
   hover read as a surprise rather than a confirmation. These rules must beat
   the base .example-chip:hover ink fill — equal specificity, later in source. */
.example-chip--card:hover,
.example-chip--card:focus-visible {
  background: var(--card);
  border-color: var(--c-primary);
  box-shadow: var(--e1-raised);
}
.example-chip--card:hover .chip-label,
.example-chip--card:focus-visible .chip-label { color: var(--c-primary); }
.example-chip--card:hover .chip-outcome,
.example-chip--card:focus-visible .chip-outcome { color: var(--c-secondary); }
.example-chip--card:focus-visible { box-shadow: var(--e1-raised), var(--ring); }
html.motion-ok .example-chip--card:hover,
html.motion-ok .example-chip--card:focus-visible { transform: translateY(-2px); }
.example-chip--card:active { box-shadow: var(--e1-press); }
html.motion-ok .example-chip--card:active { transform: translateY(0) scale(.99); }
/* Decorative only — the wire text comes from data-label, never from
   textContent, precisely so this second line can exist. */
.chip-outcome {
  font-size: 11.5px;
  font-weight: 500;
  color: var(--c-secondary);
  line-height: 1.4;
}

/* Click feedback — chip flashes once when tapped, even if release is off */
.example-chip.chip-tapped {
  animation: chip-tap 450ms cubic-bezier(.2,.8,.2,1);
  pointer-events: none;
}
@keyframes chip-tap {
  0%   { transform: scale(1);   background: var(--card);        border-color: var(--c-divider); color: var(--c-secondary); }
  35%  { transform: scale(.94); background: var(--c-primary);   border-color: var(--c-primary); color: #fff;              }
  100% { transform: scale(1);   background: var(--card);        border-color: var(--c-divider); color: var(--c-secondary); }
}

/* ══════════════════════════════════════════════════════════════════
   RESPONSE BLOCK
   ══════════════════════════════════════════════════════════════════ */
/* Inset 16px each side so answer content sits ~32px narrower than the ask
   bar above. Cards then pass fully behind the bar when scrolling — no side
   edges poking out beside it. */
#response-area {
  display: none;
  padding: 0 16px;
}

.question-label {
  font-size: 10.5px;
  font-weight: 700;
  color: var(--c-accent);
  text-transform: uppercase;
  letter-spacing: .12em;
  margin-bottom: 8px;
}

.question-text {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 500;
  font-size: 22px;
  letter-spacing: -.01em;
  color: var(--c-primary);
  margin-bottom: 20px;
  line-height: 1.35;
}

.summary {
  background: #fff;
  border: .5px solid var(--c-divider);
  border-radius: var(--radius-lg);
  padding: 20px 24px;
  font-size: 15px;
  line-height: 1.7;
  color: var(--c-primary);
  margin-bottom: 24px;
  box-shadow: var(--shadow-sm);
}

.section-label {
  font-size: 10.5px;
  font-weight: 700;
  color: var(--c-secondary);
  text-transform: uppercase;
  letter-spacing: .12em;
  margin-bottom: 12px;
}

/* ── Vendor cards (editorial layout) ──────────────────────────────── */
.vendor-grid {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.vendor-card {
  position: relative;
  background: #fff;
  /* 1px + E1 so the live answer speaks the landing's elevation language: the
     buyer watches the replay stage produce a raised white winner card, then
     asks their own question and gets the visibly same object. */
  border: 1px solid var(--c-divider);
  border-radius: var(--radius-lg);
  padding: 18px 24px 16px;
  box-shadow: var(--e1);
  display: grid;
  grid-template-columns: 1fr;
  gap: 6px;
  overflow: hidden;
  transition: border-color 180ms ease, box-shadow 220ms ease, transform 220ms ease;
}
/* accent left-bar slides in on hover */
.vendor-card::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: var(--c-accent);
  transform: scaleY(0);
  transform-origin: top center;
  transition: transform 220ms cubic-bezier(.2,.8,.2,1);
}
.vendor-card:hover {
  border-color: var(--c-accent);
  box-shadow: var(--e1-raised);
  transform: translateY(-2px);
}
.vendor-card:hover::before { transform: scaleY(1); }

/* Kicker row: rank on left, category chip on right */
.vendor-kicker {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 6px;
}
.vendor-rank {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 11.5px;
  font-weight: 500;
  color: var(--c-accent);
  letter-spacing: .04em;
}
.vendor-rank:not(:only-child)::after {
  content: "";
  width: 18px;
  height: .5px;
  background: var(--c-divider);
}
.vendor-category {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: -.05px;
  background: var(--c-subtle);
  color: var(--c-accent-ink);
  border: .5px solid var(--c-divider);
  padding: 4px 11px 4px 9px;
  border-radius: var(--radius-full);
  text-transform: lowercase;
  flex-shrink: 0;
  transition: background 180ms ease, color 180ms ease, border-color 180ms ease;
}
.vendor-category::before {
  content: "";
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--c-accent);
  flex-shrink: 0;
}
.vendor-card:hover .vendor-category {
  background: #fff;
  border-color: var(--c-accent);
  color: var(--c-primary);
}

/* "Explore more in this category" card — last item in the vendor grid.
   Inherits .vendor-card structure but tinted so it reads as a different
   artifact (a link out, not a Mola pick). */
.category-link-card {
  background: var(--c-subtle-2);
  border-style: dashed;
  border-color: var(--c-divider);
  text-decoration: none;
  color: inherit;
}
.category-link-card:hover {
  background: #fff;
}
.category-link-card .vendor-name {
  font-size: 22px;
  line-height: 1.2;
  text-align: center;
  margin-top: 4px;
}
/* Centred copy: drop the "why it fits" left rule, which only makes sense
   flush-left. This card is a link out, not one of Mola's picks. */
.category-link-card .why-fit {
  text-align: center;
  font-style: normal;
  font-family: var(--font-sans);
  font-size: 13.5px;
  color: var(--c-secondary);
  border-left: none;
  padding-left: 0;
}
.category-link-card .card-footer {
  justify-content: center;
}
.category-link-card .cta-link {
  width: 100%;
  justify-content: center;
  padding-top: 11px;
  padding-bottom: 11px;
  font-size: 13px;
}
.category-link-tag {
  text-transform: none;
  letter-spacing: 0;
}

/* ── Results: winner card + runner strips ─────────────────────────────
   Camille's .v-win / .v-run / .v-badge / .narrow shapes, translated from
   her dark stage band onto the app's paper ground. Her runner strips are
   white-on-band with opacity steps; here the same recessive-but-reachable
   read comes from a cream fill and a hairline, because opacity on ink
   text over paper degrades contrast instead of depth.

   The hierarchy is the point: ONE recommendation drawn at full weight, the
   rest as scannable forks. Both renderers emit these classes, so a live
   stream and a reloaded replay are the same layout. */

/* Ranked / alternatives grids stack — the auto-fit multi-column rule below
   would put a winner card beside its own runners. */
.bubble-mola .vendor-grid.is-ranked,
.bubble-mola .vendor-grid.is-alternatives {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Narrowing line: "547 VENDORS → FILED: PATIENT COMMUNICATIONS → 1 TOP
   MATCH". Shows the funnel the buyer never sees running. aria-hidden — the
   winner card's own sr-only "Top match:" already says this to a screen
   reader, and three arrow-joined fragments read as noise. */
.narrow-line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 7px;
  margin: 0 0 2px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--c-secondary);
}
.res-arrow { color: var(--c-divider); }
/* The payoff segment. Ink, bold: coral is reserved for the winner's Book
   Demo (verdict layer, 2026-09-06), so the count no longer competes with it. */
.res-seg-hot { color: var(--c-primary); font-weight: 700; }

/* ── Winner ──────────────────────────────────────────────────────── */
.vendor-card.v-win {
  border-color: var(--c-primary);
  border-width: 1px;
  box-shadow: var(--e2);
  padding: 20px 24px 18px;
}
/* The winner is already the emphasized card; the hover accent bar that
   distinguishes ordinary cards would only muddy that. */
.vendor-card.v-win::before { content: none; }
.vendor-card.v-win:hover {
  border-color: var(--c-primary);
  transform: none;
  box-shadow: var(--e2);
}
.v-win .vendor-name {
  font-size: 26px;
  margin-top: 2px;
  margin-bottom: 8px;
}
/* "Top match" — mono badge, INK fill, white label (14:1). Was coral; the
   verdict layer keeps coral for exactly one element in the block, the
   winner's Book Demo, so the badge became ink. Uppercase mono LABEL at
   .08em, ≥600 weight. Sits in the .v-qual row under the name. */
.v-badge {
  /* .vendor-card is a single-column GRID, so a block-level badge stretches the
     full track. justify-self is the grid axis that keeps it pill-shaped. */
  justify-self: start;
  align-self: start;
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: #fff;
  background: var(--c-primary);
  border-radius: var(--radius-full);
  padding: 4px 11px;
  /* Hidden until stamped so the layout doesn't jump when the winner's
     blurb lands (which may be after a runner's). */
  opacity: 0;
  visibility: hidden;
}
.v-badge.is-stamped {
  opacity: 1;
  visibility: visible;
}
html.motion-ok .v-badge.is-stamped {
  animation: badge-stamp 180ms ease-out both;
}
@keyframes badge-stamp {
  from { opacity: 0; transform: scale(1.15); }
  to   { opacity: 1; transform: scale(1); }
}

/* ── Verdict layer (2026-09-06) ─────────────────────────────────────
   Round 2: "the slip". Stage final renders ONE slip for rank 1 (mono
   kicker + ink TOP MATCH tag, the name at display scale, the spoken
   verdict with a hanging quote, a chart-style fit line, the one open
   question, one action) and a ledger of runner rows beneath it. Stage
   similar_vendors renders ledger rows only. Concept:
   docs/mola-multi-turn/plans/2026-09-06-verdict-layer/verdict-slip-concept.html

   Two type voices: Source Serif 4 (name, verdict, open question, row names,
   row line) and JetBrains Mono (kicker, tag, fit fields, labels, links,
   forks, "more", browse). Inter only on the button, the app-wide control
   face. Coral appears ONCE in the block: .slip-book on the slip; the same
   button in a ledger row is an ink outline. No props: no rotated stamps,
   no perforation, no fake numbers. Rhythm unit --u (12px); every gap is a
   multiple of it. Old turns (no verdict / fit_evidence / demo_question)
   render the slip without the verdict, fit line and open question. */

/* ROUND 3 — ONE VISUAL SYSTEM.
   The round-2 paper was a print artifact (2px corners, --e2 overlay shadow)
   dropped inside a soft chat UI (16px bubbles, --shadow-sm), which is what
   the founder saw as "a clear disconnect between this card and the rest of
   the conversation". Two corrections, both from tokens this file already
   defines:
     radius   the container joins the app's container tier, --radius-lg (16px),
              the same corner as .bubble, so the slip reads as something Mola
              said rather than something pasted in;
     depth    the elevation comment at the top of this file states the rule —
              "interactive elements carry depth, scenery is flat". The slip is
              scenery: it drops from --e2 (an overlay tier, reserved for things
              floating above the page plane) to --shadow-sm, the same lift the
              bubble carries. Every CONTROL inside it gains --e1 instead. */
/* ROUND 4 — ONE TYPE SCALE (the founder: "a lot of change in font sizes is
   hurting my eyes"). Round 3 fixed the FACES but left fifteen sizes between
   10px and 52px across the slip, the ledger, the question card and the recap.
   The block now has exactly FOUR, declared here and used by every rule in the
   slip / ledger / question-card / recap CSS so the scale cannot drift again:

     --slip-display  the vendor name, the one display moment (44 / 34 at 480);
     --slip-lead     the sentence the card exists to deliver: verdict, recap
                     headline, question prompt (20px);
     --slip-body     everything read as prose: the open question, ledger names
                     and lines, fit values, forks, recap lines, button labels
                     (17px);
     --slip-label    every kicker, section label, field key and caption (14px,
                     the founder's floor for "Ask this in the demo"; nothing in
                     this block is 10, 11, 12 or 13px any more).

   The recap declares the same four (it renders outside .paper). */
.paper {
  --u: 12px;
  --slip-x: 30px;
  --slip-display: 44px;
  --slip-lead: 20px;
  --slip-body: 17px;
  --slip-label: 14px;
  position: relative;
  background: #fff;
  color: var(--c-primary);
  border: .5px solid var(--c-divider);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

/* ── The slip ─────────────────────────────────────────────────────── */
.slip { display: block; }
.slip-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
  padding: calc(var(--u) * 2) var(--slip-x) calc(var(--u) * 1.5);
}
/* The rank kicker is a micro-label, so it keeps mono — but at 11px, and the
   words around the numeral are a phrase a buyer reads ("#1 in AI Scribe"),
   so they are Inter. Not uppercase: this is a sentence fragment. */
.slip-id {
  font-family: var(--font-sans);
  font-size: var(--slip-label);
  font-weight: 500;
  letter-spacing: 0;
  color: var(--c-muted);
  line-height: 1.6;
}
.slip-id b {
  font-family: var(--font-mono);
  font-size: var(--slip-label);
  color: var(--c-primary);
  font-weight: 600;
}
.slip-id-sep { color: var(--c-muted); opacity: .7; }
/* TOP MATCH: ink fill, white mono label, upright. 10.5px/600 white on ink
   is 14:1, well clear of AA for a label this size. */
.vendor-grid.is-ranked .paper,
.vendor-grid.is-alternatives .paper { max-width: 720px; }
.slip-head { align-items: center; }
.ledger-head { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; padding-right: var(--slip-x); }
.ledger-head .ledger-browse { margin-left: auto; }
.ledger-head .ledger-label { flex: 1; }
.dial-sep { margin: 0 4px; opacity: .6; }
/* "Top match" / "Close call with Bola AI" is Mola's judgement, said in words.
   Uppercase tracked mono made it read as a system label; sentence-case Inter
   is the same claim said by a person. Wraps rather than clipping, because the
   close-call variant carries a vendor name. */
.slip-mark {
  font-family: var(--font-sans);
  font-size: var(--slip-label);
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.35;
  color: var(--c-muted);
  background: transparent;
  border: 0;
  padding: 0;
  text-align: right;
  white-space: normal;
  max-width: 22ch;
}
.slip-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-variation-settings: 'opsz' 72;
  font-size: var(--slip-display);
  line-height: .98;
  letter-spacing: -.01em;
  color: var(--c-primary);
  margin: 0;
  padding: 2px var(--slip-x) calc(var(--u) * 1.5);
  text-wrap: balance;
  overflow-wrap: anywhere;
}
/* The verdict: same serif as the name, roman. The opening quote hangs in
   the left padding so the words keep the slip's left edge; the closing
   quote sits tight on the last word. */
/* ROUND 3: roman, not italic. A three-line italic paragraph at 20px is the
   single hardest thing to read on the page (the founder's "this font is very
   hard to read"); italic is for a word of emphasis, not for the sentence the
   whole card exists to deliver. */
.slip-verdict {
  position: relative;
  font-family: var(--font-display);
  font-variation-settings: 'opsz' 28;
  font-style: normal;
  font-size: var(--slip-lead);
  line-height: 1.4;
  color: var(--c-primary);
  margin: 0;
  padding: 0 var(--slip-x) calc(var(--u) * 1.5);
  max-width: calc(42ch + var(--slip-x) * 2);
  text-wrap: pretty;
}

.slip-verdict[hidden] { display: none; }
/* Streaming skeleton: two bars where the verdict / fit / question land. */
.slip-skel {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 0 var(--slip-x) calc(var(--u) * 1.5);
}
.slip-skel i {
  display: block;
  height: 12px;
  width: 72%;
  border-radius: 3px;
  background: rgba(18, 38, 58, .10);
}
.slip-skel i:last-child { width: 46%; }
html.motion-ok .slip-skel i {
  background: linear-gradient(90deg, rgba(18,38,58,.08) 0%, rgba(18,38,58,.18) 50%, rgba(18,38,58,.08) 100%);
  background-size: 200% 100%;
  animation: best-fit-shimmer 1.2s ease-in-out infinite;
}
/* Fit line: ONE mono row between two hairlines. Chart fields, not chips. */
/* Fit line: the KEYS (PMS / SIZE / TYPE) are micro-labels and keep mono; the
   VALUES are words a buyer reads, so they are Inter at 14px. Round 2 set the
   whole row in 12px mono, which turned "Ambient clinical documentation" into
   a monospaced string nobody scans. */
.slip-fit {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  row-gap: 4px;
  font-family: var(--font-sans);
  font-size: var(--slip-body);
  letter-spacing: 0;
  color: var(--c-muted);
  padding: var(--u) var(--slip-x);
  border-top: 1px solid var(--c-divider);
  border-bottom: 1px solid var(--c-divider);
}
.slip-fit[hidden] { display: none; }
.slip-field { white-space: nowrap; }
/* The one place uppercase mono survives on the slip: a 3-letter field key,
   floored at 11px so it is legible rather than decorative. */
.slip-k {
  font-family: var(--font-mono);
  font-size: var(--slip-label);
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--c-muted);
  margin-right: 7px;
}
.slip-fit b { color: var(--c-primary); font-weight: 600; }
.slip-sep { margin: 0 8px; color: var(--c-muted); opacity: .6; }
/* ROUND 4 — THE OPEN QUESTION IS THE SECOND-LOUDEST THING ON THE SLIP.
   The founder: "the text 'Ask this in the demo' is too small, make it very
   obvious". Round 3 had it as an 11.5px muted grey line, which read as a
   caption for the sentence beneath rather than as the instruction the buyer
   is supposed to carry into the demo. Three changes, no new colour:
     the block becomes ONE unit on the --c-subtle-2 tint with a 3px navy left
     rule, so it separates from the fit line above and the actions below
     without a box; the label goes to navy (--c-primary) at the label size;
     the question goes to the body size in the display serif.
   Coral stays on the Book button alone. */
.slip-open {
  position: relative;
  margin: calc(var(--u) * 1.5) var(--slip-x) 0;
  padding: var(--u) calc(var(--u) * 1.25) var(--u) calc(var(--u) * 1.25 - 3px);
  background: var(--c-subtle-2);
  border-left: 3px solid var(--c-primary);
  /* Design-critic round 4 (gap 7): the band had square left corners and
     rounded right ones, which is a second corner system on a card whose whole
     point is that there is only one. Uniform --radius-md; the navy rule is
     drawn INSIDE the rounded box, so the corner survives the border. */
  border-radius: var(--radius-md);
}
.slip-open[hidden] { display: none; }
.slip-dot { display: none; }
.slip-open p {
  margin: 0;
  font-family: var(--font-display);
  font-variation-settings: 'opsz' 17;
  font-style: normal;
  font-size: var(--slip-body);
  line-height: 1.45;
  color: var(--c-primary);
  max-width: 46ch;
}
/* Section labels ("Ask this in the demo", "Runners-up") are instructions and
   nouns, not codes: Inter, sentence case, one label size. Round 4 lifts them
   from 11.5px muted grey to --slip-label in DTH navy: at 11.5px they were the
   smallest and faintest type on the card, which is the opposite of what a
   section label is for. One colour and one size for EVERY section label on
   the slip, so none of them can quietly become a caption again. */
.slip-open-lbl,
.ledger-label {
  display: block;
  font-family: var(--font-sans);
  font-size: var(--slip-label);
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--c-primary);
}
.slip-open-lbl { margin-bottom: 4px; }
/* ROUND 4 — THE PRIMARY NEVER SHARES A ROW.
   The founder's rule for the recap, applied everywhere the same three actions
   appear (slip, expanded ledger row, recap): the coral "Book a demo" stands
   alone on its own row across the content column, and the two secondary
   controls sit on the row BELOW it, side by side at desktop and stacked at
   390. A secondary beside the primary reads as its peer no matter how quiet
   its surface is; a secondary under it reads as an alternative. Column, not
   wrap: a wrapping row puts them beside the button whenever the width allows,
   which is exactly the case the rule exists to prevent. */
.slip-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
  padding: calc(var(--u) * 1.5) var(--slip-x) calc(var(--u) * 2);
}
/* ROUND 3 — THE ELEVATION RULE, APPLIED.
   Round 2 inverted it: the container floated on --e2 and the button it exists
   to deliver was a flat 2px-cornered rectangle with box-shadow:none. The
   founder read exactly that ("the card has a shadow but the Copy and Email
   buttons have none"). Every clickable thing in the block now carries the
   control tiers — --e1 at rest, --e1-raised on hover, --e1-press on active —
   and every container is flat. Radius joins the app's control tier
   (--radius-md), the same corner as .modal-actions button and .recap-btn. */
.paper .cta-link.cta-primary.slip-book {
  display: inline-flex;
  align-self: flex-start;
  align-items: center;
  gap: 8px;
  height: auto;
  padding: 12px 20px;
  font-family: var(--font-sans);
  font-size: var(--slip-body);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0;
  border-radius: var(--radius-md);
  box-shadow: var(--e1);
  white-space: normal;
  text-align: left;
}
.paper .cta-link.cta-primary.slip-book:hover { box-shadow: var(--e1-raised); }
.paper .cta-link.cta-primary.slip-book:active { box-shadow: var(--e1-press); }
.slip-arrow { font-weight: 600; }
/* The two secondary actions. Round 2 had them as 12px mono underlined text,
   which (a) set a sentence in monospace and (b) made two real controls the
   only flat things in a row with a raised button. They are now Inter 13.5px
   ghost buttons on the same radius and the same three elevation tiers as the
   coral one: quieter by SURFACE, not by being denied depth. */
.slip-sub {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  font-family: var(--font-sans);
  font-size: var(--slip-label);
  letter-spacing: 0;
}
/* Design-critic round 3: with the primary at 14.5px/700 filled coral, these
   two at 13.5px/600 ink still read as peers of it. They keep the shape and the
   depth (they ARE controls, and the founder asked for explicit tappable
   actions rather than text links) but drop to 13.5px/500 in secondary ink, so
   the row has one obvious first move and two available ones. */
.slip-link {
  appearance: none;
  display: inline-flex;
  align-items: center;
  background: var(--card);
  border: 1px solid var(--c-divider);
  border-radius: var(--radius-md);
  padding: 9px 14px;
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--slip-label);
  font-weight: 500;
  line-height: 1.2;
  color: var(--c-secondary);
  text-decoration: none;
  box-shadow: var(--e1);
  cursor: pointer;
  transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease;
}
.slip-link:hover {
  border-color: var(--c-primary);
  color: var(--c-primary);
  box-shadow: var(--e1-raised);
}
.slip-link:active { box-shadow: var(--e1-press); }
.slip .cta-link.cta-primary.slip-book,
.ledger .cta-link.cta-primary.slip-book { border-radius: var(--radius-md); }
.slip-link:focus-visible { outline: none; box-shadow: var(--ring); }

/* ── The ledger ───────────────────────────────────────────────────── */
.ledger { border-top: 1px solid var(--c-divider); }
.ledger.is-uniform { border-top: none; }
.ledger-label {
  margin: 0;
  padding: var(--u) var(--slip-x) calc(var(--u) * .75);
}
.ledger-row { border-top: 0; }
.ledger-toggle { min-height: 0; padding-top: 7px; padding-bottom: 7px; }
.ledger.is-uniform .ledger-row:first-child { border-top: none; }
.ledger-more[hidden] { display: none; }
.ledger-toggle {
  display: flex;
  align-items: baseline;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: calc(var(--u) * .75) var(--slip-x);
  background: none;
  border: none;
  text-align: left;
  color: var(--c-primary);
  font: inherit;
  cursor: pointer;
}
/* A ledger row is a control (it expands), so it takes the control treatment
   on hover: a raised surface rather than an underline. Hover-capable pointers
   only, so a touch device never sticks in the hovered state. */
@media (hover: hover) {
  .ledger-toggle:hover {
    background: var(--card);
    box-shadow: var(--e1);
    border-radius: var(--radius-md);
  }
  .ledger-toggle:hover .ledger-name { color: var(--c-primary); }
}
.ledger-toggle:active { box-shadow: var(--e1-press); border-radius: var(--radius-md); }
.ledger-toggle:focus-visible { outline: none; box-shadow: inset var(--ring); border-radius: var(--radius-md); }
html.motion-ok .ledger-toggle { transition: box-shadow 160ms ease, background 160ms ease; }
/* "#2" is one glyph wider than the old "02" and no longer zero-padded. */
.ledger-n {
  flex: none;
  width: 26px;
  font-family: var(--font-mono);
  font-size: var(--slip-label);
  color: var(--c-muted);
}
.ledger-name {
  flex: 0 0 var(--ledger-name-w, 176px);
  min-width: 0;
  padding-right: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-display);
  font-variation-settings: 'opsz' 17;
  font-weight: 600;
  font-size: var(--slip-body);
  line-height: 1.3;
}
/* The fork is the row's whole argument ("if you're on Open Dental"), so it is
   read, not scanned: Inter roman 13.5px muted. Round 2 set it in italic
   Source Serif 4, which at 13.5px on a muted grey is the least legible pairing on
   the page. */
.ledger-why {
  flex: 1;
  min-width: 0;
  font-family: var(--font-sans);
  font-style: normal;
  font-size: var(--slip-label);
  font-weight: 400;
  line-height: 1.4;
  color: var(--c-muted);
  white-space: normal;
}
.ledger-why:empty { display: none; }
.ledger-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--u);
  padding: 0 var(--slip-x) calc(var(--u) * 1.5) calc(var(--slip-x) + 26px);
}
.ledger-body[hidden] { display: none; }
.ledger-line {
  margin: 0;
  font-family: var(--font-display);
  font-variation-settings: 'opsz' 17;
  font-style: normal;
  font-size: var(--slip-body);
  line-height: 1.45;
  color: var(--c-primary);
  max-width: 46ch;
}
.ledger-line[hidden] { display: none; }
.ledger-body .compare-select {
  margin-left: 0;
  font-family: var(--font-sans);
  font-size: var(--slip-label);
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
}
.ledger-body .slip-actions { padding: 0; }
/* The row's button: coral is spent on the slip, so here it is an ink
   outline. Same action, honest weight. */
.ledger .cta-link.cta-primary.slip-book {
  background: var(--card);
  color: var(--c-primary);
  border: 1px solid var(--c-primary);
  box-shadow: var(--e1);
}
.ledger .cta-link.cta-primary.slip-book:hover {
  background: var(--c-primary);
  color: #fff;
  border-color: var(--c-primary);
  box-shadow: var(--e1-raised);
}
.ledger .cta-link.cta-primary.slip-book:active { box-shadow: var(--e1-press); }
/* One exit row: "{n} more on file" (ink, left) and "Browse {category}"
   (muted, right, the secondary). Both mono; neither coral. */
.ledger-foot {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px 24px;
  padding: calc(var(--u) * .75) var(--slip-x) var(--u);
  border-top: 1px solid var(--c-subtle);
}
/* Both exits carry real sentences now ("Show 6 more runners-up", "See all 28
   Marketing vendors on DentalTechHub"), so both are Inter at 13.5px. "Show
   more" is a control that acts in place, so it takes the ghost-button
   treatment with the control elevation tiers; the category link navigates
   away and stays a quiet text link, which is the honest difference between
   them. */
.ledger-more-btn {
  margin: 0;
  display: inline-flex;
  align-items: center;
  padding: 8px 14px;
  background: var(--card);
  border: 1px solid var(--c-divider);
  border-radius: var(--radius-md);
  box-shadow: var(--e1);
  text-align: left;
  font-family: var(--font-sans);
  font-size: var(--slip-label);
  font-weight: 600;
  letter-spacing: 0;
  color: var(--c-primary);
  text-decoration: none;
  cursor: pointer;
  transition: border-color 160ms ease, box-shadow 160ms ease;
}
.ledger-more-btn:hover { border-color: var(--c-primary); box-shadow: var(--e1-raised); }
.ledger-more-btn:active { box-shadow: var(--e1-press); }
/* Design-critic round 3: at 13.5px/500 this link read heavier than the
   "Runners-up" label beside it, so the escape hatch outranked the section. It
   drops to the label's own size and weight; it is still explicit copy (the
   founder's requirement) but it no longer competes. */
.ledger-browse {
  margin: 0 0 0 auto;
  padding: 2px 0;
  background: none;
  border: none;
  text-align: right;
  font-family: var(--font-sans);
  font-size: var(--slip-label);
  font-weight: 400;
  letter-spacing: 0;
  color: var(--c-muted);
  text-decoration: underline;
  text-decoration-color: var(--c-divider);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  cursor: pointer;
}
.ledger-browse:hover { color: var(--c-primary); text-decoration-color: currentColor; }
.ledger-more-btn:focus-visible,
.ledger-browse:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--radius-md); }

/* Concierge modal: reuses the email modal wholesale; only the static
   address line and per-field errors are new. */
.concierge-email-static {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  font-size: 13.5px;
  color: var(--c-secondary);
}
.concierge-email-static strong { color: var(--c-primary); font-weight: 600; }
.concierge-email-static[hidden] { display: none; }
.concierge-form .demo-field[hidden] { display: none; }
.concierge-field-error {
  font-size: 12px;
  color: var(--c-warning);
  margin-top: 2px;
}
.concierge-field-error[hidden] { display: none; }
.concierge-form .demo-field.has-error input,
.concierge-form .demo-field.has-error textarea { border-color: var(--c-warning); }

@media (max-width: 720px) {
  .paper { --slip-x: 22px; }
  .slip-field { white-space: normal; }
  .slip-fit b { overflow-wrap: anywhere; }
  .ledger-head { flex-wrap: wrap; }
  .ledger-head .ledger-label { white-space: nowrap; flex: 1 0 auto; }
  /* Rows wrap: the fork drops under the name instead of truncating. */
  .ledger-toggle { flex-wrap: wrap; row-gap: 2px; }
  .ledger-name { flex-basis: auto; white-space: normal; overflow: visible; padding-right: 0; }
  .ledger-why {
    flex-basis: 100%;
    padding-left: 26px;
    white-space: normal;
    overflow: visible;
  }
  .ledger-body { padding-left: var(--slip-x); }
}
@media (max-width: 480px) {
  /* Round 4: only the DISPLAY size changes on a phone. The other three hold,
     so the narrow layout is the same four sizes, not a fifth and a sixth. */
  .paper { --slip-x: 18px; --slip-display: 34px; }
  /* At 390 the kicker and the close-call tag fight for one line and both wrap
     to two, so they stack: the rank line first, the tag under it, both left. */
  .slip-head { flex-direction: column; align-items: flex-start; gap: 3px; }
  .slip-mark { text-align: left; max-width: none; }
  /* The ledger head stacks for the same reason (the category exit is a full
     sentence now), and the exit sits left with its label rather than ranged
     right against nothing. */
  .ledger-head { flex-direction: column; align-items: flex-start; gap: 4px; }
  .ledger-head .ledger-browse { margin-left: 0; text-align: left; }
  /* Round 4: at 390 the two secondaries stack under the primary everywhere
     (slip and runner row alike), rather than sharing a 358px line. */
  .slip-sub { flex-direction: column; align-items: stretch; }
  .slip-sub .slip-link { justify-content: center; }
  .paper .cta-link.cta-primary.slip-book { align-self: stretch; justify-content: center; }
  /* The fit line stacks: one field per line, no dangling separators. */
  .slip-fit { flex-direction: column; align-items: flex-start; row-gap: 6px; }
  .slip-sep { display: none; }
  .slip-verdict::before { left: calc(var(--slip-x) - 12px); }
  .slip-dot { left: calc(var(--slip-x) - 13px); }
  .slip-actions { gap: 12px 16px; }
}

@media print {
  /* Every row on paper, open, and no interactive doors. */
  html.is-printing-shortlist .paper { box-shadow: none; }
  html.is-printing-shortlist .ledger-more[hidden] { display: block !important; }
  html.is-printing-shortlist .ledger-body[hidden] { display: flex !important; }
  html.is-printing-shortlist .ledger-more-btn,
  html.is-printing-shortlist .ledger-browse,
  html.is-printing-shortlist .slip-ask,
  html.is-printing-shortlist .slip-book,
  html.is-printing-shortlist .slip-skel,
  html.is-printing-shortlist .ledger-body .compare-select { display: none !important; }
  html.is-printing-shortlist .slip-link.mola-profile-link::after {
    content: " (" attr(href) ")";
    font-size: 10px;
    font-weight: 400;
    word-break: break-all;
  }
}

/* The winner's Book Demo is the loudest thing on the page — full-width
   rather than sharing the row, per Camille's .v-demo-big. 15px/800 clears
   the ≥14px-bold floor white-on-coral requires. */
.v-win .card-footer-split {
  grid-template-columns: 1fr;
  gap: 8px;
}
.v-win .card-footer-split .cta-link.cta-primary {
  height: 46px;
  font-size: 15px;
  font-weight: 800;
  border-radius: var(--radius-full);
}
.v-win .card-footer-split .cta-link.cta-secondary {
  justify-content: center;
  background: transparent;
  border-color: transparent;
  font-size: 12px;
}
.v-win .card-footer-split .cta-link.cta-secondary:hover {
  background: var(--c-subtle-2);
  border-color: var(--c-divider);
  color: var(--c-primary);
}

/* ── Runners ─────────────────────────────────────────────────────── */
.v-runners {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 4px;
}
.v-runners-label {
  margin: 0 0 2px 2px;
  color: var(--c-secondary);
}
.vendor-card.v-run {
  display: block;
  padding: 0;
  background: var(--c-subtle-2);
  border-radius: var(--radius-md);
  box-shadow: var(--e0);
  gap: 0;
}
.vendor-card.v-run::before { content: none; }
.vendor-card.v-run:hover {
  transform: none;
  box-shadow: var(--e1);
  border-color: var(--c-divider);
}
.vendor-card.v-run.is-open {
  background: #fff;
  border-color: var(--c-divider);
  box-shadow: var(--e1);
}
/* Head row: the toggle button takes the width, the compare checkbox sits
   OUTSIDE it — a checkbox nested in a button is neither reliably clickable
   nor correctly announced, and compare has to be reachable while every
   strip is still collapsed. */
.v-run-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-right: 12px;
}
.v-run-toggle {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 4px 12px 16px;
  min-height: 48px;
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
}
.v-run-toggle:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: -2px;
  border-radius: var(--radius-md);
}
.v-run .vendor-name {
  font-family: var(--font-sans);
  font-size: 14.5px;
  font-weight: 700;
  letter-spacing: -.01em;
  margin: 0;
  white-space: nowrap;
  flex-shrink: 0;
}
.v-run .vendor-rank {
  flex-shrink: 0;
  color: var(--c-secondary);
}
.v-run .vendor-rank::after { content: none; }
/* The fork — "· if you're on Open Dental". The line a buyer scans to find
   themselves. Truncates rather than wrapping: a strip that grows to two
   lines stops being a strip. */
.v-fork {
  flex: 1;
  min-width: 0;
  font-size: 13px;
  line-height: 1.4;
  color: var(--c-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.v-fork:empty { display: none; }
.v-chev {
  flex-shrink: 0;
  display: inline-flex;
  color: var(--c-muted);
  transition: transform 200ms ease, color 180ms ease;
}
.v-chev svg { width: 14px; height: 14px; }
.v-run.is-open .v-chev {
  transform: rotate(90deg);
  color: var(--c-primary);
}
.v-run-toggle:hover .v-chev { color: var(--c-primary); }
.v-run-body {
  padding: 2px 16px 14px;
  border-top: .5px solid var(--c-divider);
  margin-top: 2px;
}
.v-run-body .vendor-category { margin: 10px 0 2px; }
.v-run-body .vendor-axes { margin-top: 10px; }
/* Ghost Book Demo inside a strip: coral is spent on the winner, so a
   runner's CTA is an ink outline. Same action, honest weight. */
.v-run-body .card-footer-split .cta-link.cta-primary {
  background: transparent;
  color: var(--c-primary);
  border: 1px solid var(--c-primary);
  font-size: 12.5px;
  font-weight: 700;
}
.v-run-body .card-footer-split .cta-link.cta-primary:hover {
  background: var(--c-primary);
  color: #fff;
}

/* Screen-reader-only text — the winner name's "Top match: " prefix, so the
   badge (aria-hidden, because it's decorative next to a real heading) is
   still announced as part of the recommendation. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Cap-th-turn callout — last allowed follow-up. RESTYLED for the
   paper/ink system: this used to be amber, one family with the old CTA.
   Coral is now conversion-only, so a warm tint here would read as a
   Book-Demo prompt — exactly the wrong signal for "this conversation is
   ending". It becomes an EDITORIAL NOTE instead: ink copy on cream, a
   solid ink left rule, no colour temperature at all. It reads as the page
   speaking in its own voice rather than an alert, and it cannot be
   confused with the coral CTA sitting inside it.
   Ink on cream-dim = 12.75:1 ✅. */
.last-turn-callout {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: 16px;
  padding: 12px 14px;
  background: var(--cream-dim);
  border: .5px solid var(--c-divider);
  border-left: 3px solid var(--c-primary);
  border-radius: var(--radius-md);
  color: var(--c-primary);
  font-size: 13.5px;
  line-height: 1.45;
  animation: last-turn-fade-in 480ms cubic-bezier(.2,.8,.2,1);
}
.last-turn-callout strong {
  font-weight: 600;
}
.last-turn-callout-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: var(--c-secondary);
  margin-top: 1px;
}
@keyframes last-turn-fade-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Inline New-Conversation CTA next to a closing message (last-turn callout
   + depth-cap bubble). The callout is a flex row, so force the button onto
   its own line beneath the message. */
.inline-new-convo { margin-top: 10px; }
.last-turn-callout { flex-wrap: wrap; }
.last-turn-callout .inline-new-convo {
  flex-basis: 100%;
  margin-top: 8px;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* ── Demo nudge card ──────────────────────────────────────────────────
   A small tinted card offered at a real journey beat (shortlist settled,
   comparison closed, thumbs-up) — never a modal, never an interstitial, one
   at a time, once per context per session. Bounded container with its own
   border: nothing full-bleed, so the iframe still dissolves into the platform
   page it's embedded in. Copy is navy on the accent tint (10.53 ✅); only the
   button carries accent, as a fill behind white (5.02 ✅). */
.nudge-card {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: 16px;
  padding: 12px 14px;
  background: var(--c-cta-tint);
  border: .5px solid var(--c-cta-border);
  border-left: 3px solid var(--c-cta);
  border-radius: var(--radius-lg);
  color: var(--c-primary);
  font-size: 13.5px;
  line-height: 1.45;
}
html.motion-ok .nudge-card {
  animation: nudge-in 320ms cubic-bezier(.2,.8,.2,1) both,
             nudge-ring 1.6s ease-in-out 320ms 2;
}
@keyframes nudge-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes nudge-ring {
  0%   { box-shadow: 0 0 0 0   var(--c-cta-glow); }
  60%  { box-shadow: 0 0 0 8px rgba(217, 72, 31, 0); }
  100% { box-shadow: 0 0 0 0   rgba(217, 72, 31, 0); }
}
.nudge-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: var(--c-cta);
  margin-top: 1px;
}
.nudge-body {
  flex: 1 1 auto;
  min-width: 0;
}
.nudge-copy strong { font-weight: 600; }
.nudge-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 10px;
}
.nudge-dismiss {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font: inherit;
  font-size: 15px;
  line-height: 1;
  color: var(--c-cta-ink);
  background: transparent;
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  opacity: .65;
  transition: opacity 140ms ease, background 140ms ease;
}
.nudge-dismiss:hover {
  opacity: 1;
  background: rgba(217, 72, 31, .10);
}

/* Inline (card-free) variant used under a thumbs-up, where the feedback
   widget is already dense enough that another card would crowd it. */
.nudge-inline {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: 12.5px;
  color: var(--c-cta-ink);
}
html.motion-ok .nudge-inline {
  animation: nudge-in 320ms cubic-bezier(.2,.8,.2,1) both;
}
/* 14px/700 so the coral fill clears AA as large text (white-on-coral is
   4.29:1 — see the :root a11y note). Same floor as .cta-primary. */
.nudge-inline .nudge-text-btn {
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  padding: 4px 12px;
  color: #fff;
  background: var(--c-cta);
  border: 1px solid var(--c-cta);
  border-radius: 999px;
  cursor: pointer;
  transition: background 140ms ease, border-color 140ms ease;
}
.nudge-inline .nudge-text-btn:hover {
  background: var(--c-cta-hover);
  border-color: var(--c-cta-hover);
}

/* Vendor name — Source Serif 4 600. The one editorial display moment on a card;
   the prototype's .v-win-name / .v-name. Phase 3 owns card structure, but
   the NAME's voice is settled here so both renderers already agree. */
.vendor-name {
  font-family: var(--font-display);
  font-size: 21px;
  font-weight: 600;
  font-variation-settings: 'opsz' 28;
  color: var(--c-primary);
  letter-spacing: -.014em;
  line-height: 1.15;
  margin-bottom: 6px;
}

/* "Why it fits" — Source Serif 4 italic with a hairline rule, matching the
   prototype's .v-why. This is Mola's reasoning voice; see .why-italic for
   the reusable form Phases 2/3 should apply to new surfaces. */
.why-fit {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-variation-settings: 'opsz' 16;
  font-size: 14.5px;
  color: var(--c-primary);
  line-height: 1.5;
  border-left: 2px solid var(--c-divider);
  padding-left: 12px;
  margin-bottom: 14px;
}

/* Per-vendor card body: three concise axis bullets (strength / watch-out /
   best fit) from the blurb pass. Replaces the old single run-on why-fit
   line on live cards. */
.vendor-axes {
  list-style: none;
  margin: 0 0 14px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.vendor-axes li {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--c-primary);
}
.vendor-axes .axis-tag {
  font-family: var(--font-sans);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--c-accent-ink);
  margin-right: 6px;
}
/* Streaming skeleton bars while the per-vendor blurb is in flight. */
.vendor-axes.is-loading .best-fit-skeleton {
  display: inline-block;
  height: 10px;
  width: 70%;
  border-radius: 4px;
  vertical-align: middle;
  background: linear-gradient(
    90deg,
    rgba(18, 38, 58, 0.08) 0%,
    rgba(18, 38, 58, 0.18) 50%,
    rgba(18, 38, 58, 0.08) 100%
  );
  background-size: 200% 100%;
  animation: best-fit-shimmer 1.2s ease-in-out infinite;
}
.vendor-axes:not(.is-loading) .best-fit-skeleton {
  display: none;
}

.card-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding-top: 14px;
  border-top: .5px solid var(--c-divider);
}

.pricing-note {
  font-size: 11.5px;
  font-family: var(--font-mono);
  color: var(--c-muted);
  letter-spacing: .02em;
}
.pricing-note:empty::before { content: ""; }

.cta-link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: -.05px;
  color: var(--c-primary);
  text-decoration: none;
  padding: 8px 14px;
  background: var(--c-subtle-2);
  border: 1px solid var(--c-divider);   /* 1px everywhere — see .vendor-card */
  border-radius: var(--radius-md);
  transition: background 180ms ease, color 180ms ease, border-color 180ms ease, transform 120ms ease, box-shadow var(--dur-hover) var(--ease-io);
  flex-shrink: 0;
  /* Book Demo is a <button> (opens the in-app form); without this it shows the
     default arrow cursor while View profile (an <a href>) shows the hand. Set
     pointer so both read as clickable. */
  cursor: pointer;
}
.cta-link:hover {
  background: var(--c-primary);
  color: #fff;
  border-color: var(--c-primary);
}
.cta-link:active { transform: scale(.985); }
.cta-link svg {
  width: 12px; height: 12px;
  transition: transform 180ms ease;
}
.cta-link:hover svg { transform: translateX(2px); }

/* Split footer: Book Demo (primary) on the left, View profile
   (secondary) on the right. Forced side-by-side at every width —
   each CTA takes 50% of the row and shrinks its padding rather than
   wrapping to a second line. */
.card-footer-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  flex-wrap: nowrap;
}
.card-footer-split .cta-link {
  justify-content: center;
  min-width: 0;
  padding-left: 10px;
  padding-right: 10px;
  white-space: nowrap;
}
/* Book Demo — the one conversion action on a card. Coral fill behind a white
   label. The base .cta-link:hover sets an ink background; the :hover override
   below wins on specificity, and the base rule's color:#fff is exactly what
   we want to inherit. `position: relative` + overflow scope the one-shot
   sheen (.is-cta-hinted) to the button box.

   A11Y — white on coral is 4.29:1, which clears AA only at large-text size
   (≥14px BOLD / ≥18.66px normal). The inherited .cta-link size is 12.5px/600,
   which would FAIL, so this button overrides to 14px/700. Do not shrink it
   below 14px or drop it under 700 without moving to an ink fill instead. */
.cta-link.cta-primary {
  position: relative;
  overflow: hidden;
  background: var(--c-cta);
  color: #fff;
  border-color: var(--c-cta);
  box-shadow: var(--e1);
  font-size: 14px;
  font-weight: 700;
}
.cta-link.cta-primary:hover {
  background: var(--c-cta-hover);
  border-color: var(--c-cta-hover);
  box-shadow: var(--e1-raised);
}
.cta-link:focus-visible { outline: none; box-shadow: var(--ring); }
html.motion-ok .cta-link.cta-primary {
  transition: background 180ms ease, color 180ms ease, border-color 180ms ease,
              transform 160ms ease, box-shadow 160ms ease;
}
html.motion-ok .cta-link.cta-primary:hover {
  transform: translateY(-1px);
  box-shadow: var(--e1-raised);
}
html.motion-ok .cta-link.cta-primary:hover svg { transform: translateX(3px); }
.cta-link.cta-primary:active { transform: scale(.985); }

/* One-shot diagonal sheen, added by JS to the rank-1 card's Book Demo the
   first time a shortlist lands in a session, then removed on animationend.
   Deliberately runs ONCE — this is a hint, not an attention loop. */
html.motion-ok .cta-link.cta-primary.is-cta-hinted::after {
  content: "";
  position: absolute;
  top: -60%;
  left: -40%;
  width: 34%;
  height: 220%;
  background: linear-gradient(
    100deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, .42) 50%,
    rgba(255, 255, 255, 0) 100%
  );
  transform: translateX(-140%) rotate(14deg);
  pointer-events: none;
  animation: cta-sheen 900ms ease-out 1;
}
@keyframes cta-sheen {
  from { transform: translateX(-140%) rotate(14deg); }
  to   { transform: translateX(460%)  rotate(14deg); }
}
.cta-link.cta-secondary {
  /* inherits the base .cta-link subtle look */
}

.caveats {
  margin-top: 16px;
  font-size: 12.5px;
  color: var(--c-muted);
  font-style: italic;
  line-height: 1.55;
}

/* ── Suggested follow-up questions (Mola) ─────────────────────────── */
.suggested-questions {
  margin-top: 28px;
  padding-top: 24px;
  border-top: .5px solid var(--c-divider);
}
.suggested-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 500;
  font-size: 14px;
  color: var(--c-secondary);
  margin-bottom: 14px;
  letter-spacing: -.05px;
}
.suggested-label .mola-icon-xs { margin-bottom: 1px; }

.suggested-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.suggested-loading {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 4px;
}
.sugg-dot {
  display: inline-block;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--c-accent);
  opacity: .3;
  animation: sugg-pulse 1.3s ease-in-out infinite;
}
.sugg-dot:nth-child(2) { animation-delay: .18s; }
.sugg-dot:nth-child(3) { animation-delay: .36s; }
@keyframes sugg-pulse {
  0%, 100% { opacity: .25; transform: scale(.85); }
  50%      { opacity: 1;   transform: scale(1);   }
}

/* ── Error / Mola message block (DS info-callout pattern) ─────────── */
.error-block {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  background: var(--c-subtle-2);
  border: .5px solid var(--c-divider);
  border-radius: var(--radius-lg);
  padding: 18px 22px;
  box-shadow: var(--shadow-sm);
}
.error-block .mola-icon-sm {
  margin-top: 2px;
}
.error-block-body {
  flex: 1;
  font-size: 14px;
  color: var(--c-secondary);
  line-height: 1.6;
}
.error-block-body strong {
  display: block;
  color: var(--c-primary);
  font-weight: 600;
  margin-bottom: 2px;
}
.error-block.is-hard {
  background: var(--c-danger-bg);
  border-color: #E9C4C0;
}
.error-block.is-hard .error-block-body { color: var(--c-danger); }
.error-block.is-hard .error-block-body strong { color: var(--c-danger); }
.error-block.is-hard .mola-icon-sm { color: var(--c-danger); }

/* ══════════════════════════════════════════════════════════════════
   SKELETON LOADER
   ══════════════════════════════════════════════════════════════════ */
.skeleton {
  background: linear-gradient(90deg, var(--c-subtle) 25%, var(--c-subtle-2) 50%, var(--c-subtle) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.4s ease-in-out infinite;
  border-radius: var(--radius-md);
}

@keyframes shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

.skeleton-summary {
  height: 92px;
  border-radius: var(--radius-lg);
  margin-bottom: 24px;
}
.skeleton-card {
  height: 108px;
  border-radius: var(--radius-lg);
  margin-bottom: 12px;
}

/* ══════════════════════════════════════════════════════════════════
   COMPOSER FIELDS — DS form-input + btn-primary
   ══════════════════════════════════════════════════════════════════ */
#question-input {
  flex: 1;
  border: none;
  border-radius: var(--radius-md);
  padding: 13px 14px;
  font-size: 15px;
  font-family: inherit;
  color: var(--c-primary);
  background: transparent;
  resize: none;
  /* min-height matches the ask button (48px) so single-line placeholder
     never clips against the textarea's content box, and both controls
     align cleanly via the composer's `align-items: center`. */
  min-height: 48px;
  max-height: 160px;
  line-height: 1.4;
  outline: none;
  box-sizing: border-box;
}
/* Placeholder only (exempt from AA contrast) — the prototype's tone.
   Real label/counter text uses --c-muted, which is AA-safe. */
#question-input::placeholder { color: #9AA7B4; }

#ask-btn {
  background: var(--c-primary);
  color: #fff;
  border: none;
  border-radius: var(--radius-full);
  padding: 0 22px;
  font-size: 13.5px;
  font-weight: 600;
  font-family: inherit;
  letter-spacing: -.05px;
  cursor: pointer;
  height: 48px;
  display: flex; align-items: center; gap: 8px;
  transition: background 150ms ease, transform 120ms ease;
  white-space: nowrap;
  flex-shrink: 0;
}
#ask-btn:hover:not(:disabled) { background: var(--c-primary-hover); }
#ask-btn:active:not(:disabled) { transform: scale(.985); }
#ask-btn:disabled { background: var(--c-muted); cursor: not-allowed; }
#ask-btn .btn-label-thinking {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 500;
}

/* ══════════════════════════════════════════════════════════════════
   DEBUG PANEL
   ══════════════════════════════════════════════════════════════════ */
.debug-panel {
  margin-top: 16px;
  border: .5px dashed var(--c-divider);
  border-radius: var(--radius-md);
  font-size: 12px;
  background: var(--c-subtle-2);
}
.debug-panel summary {
  padding: 8px 14px;
  cursor: pointer;
  color: var(--c-secondary);
  font-weight: 600;
  user-select: none;
}
.debug-body {
  padding: 12px 14px;
  border-top: .5px dashed var(--c-divider);
  color: var(--c-secondary);
  line-height: 1.7;
}
.debug-body pre {
  margin-top: 8px;
  background: #fff;
  border: .5px solid var(--c-divider);
  border-radius: var(--radius-sm);
  padding: 10px;
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-word;
  font-size: 11px;
  line-height: 1.55;
}

/* ══════════════════════════════════════════════════════════════════
   MOTION — DS fade-in stagger (80ms increments)
   ══════════════════════════════════════════════════════════════════ */
@keyframes fade-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.fade-in-1 { animation: fade-in 600ms cubic-bezier(.2,.8,.2,1)  80ms both; }
.fade-in-2 { animation: fade-in 600ms cubic-bezier(.2,.8,.2,1) 160ms both; }
.fade-in-3 { animation: fade-in 600ms cubic-bezier(.2,.8,.2,1) 240ms both; }
.fade-in-4 { animation: fade-in 600ms cubic-bezier(.2,.8,.2,1) 320ms both; }
.fade-in-5 { animation: fade-in 600ms cubic-bezier(.2,.8,.2,1) 400ms both; }
.fade-in-6 { animation: fade-in 600ms cubic-bezier(.2,.8,.2,1) 480ms both; }
.fade-in-7 { animation: fade-in 600ms cubic-bezier(.2,.8,.2,1) 560ms both; }
.fade-in-8 { animation: fade-in 600ms cubic-bezier(.2,.8,.2,1) 640ms both; }

/* M-1 — streaming shortlist stagger. The cards all carry .fade-in-3 (a flat
   240ms delay), so without this they pop in as one block. `--card-stagger` is
   written by runnerStripHtml per rank (capped at 6 so a 10-vendor shortlist
   never makes the last strip feel late — the winner carries no stagger, it is
   the anchor the runners arrive under); this only overrides the delay, leaving
   the shared fade-in keyframe alone. Opacity/transform only —
   applyStreamingVendorCard writes textContent into descendants and toggles
   .is-loading, neither of which an ancestor animation disturbs. */
html.motion-ok .vendor-card[style*="--card-stagger"] {
  animation: fade-in 420ms cubic-bezier(.2,.8,.2,1) var(--card-stagger, 0ms) both;
}

/* Spinner used in Ask button while loading */
@keyframes spin { to { transform: rotate(360deg); } }

/* ══════════════════════════════════════════════════════════════════
   M9 — Multi-turn conversation
   ══════════════════════════════════════════════════════════════════ */

.conversation {
  display: flex;
  flex-direction: column;
  gap: 20px;
  /* Top margin clears the composer's 64px ::after fade mask so the first
     turn isn't clipped when scrolled under the sticky composer. */
  margin: 72px 0 32px;
}

.turn {
  display: flex;
  gap: 12px;
  width: 100%;
}

.turn-user { justify-content: flex-end; }
.turn-mola {
  justify-content: flex-start;
  align-items: flex-start;
  /* Scroll-anchor target for scrollResponseIntoView (block:'start'). Must clear
     the full sticky stack or the answer's top hides behind the composer and the
     "scroll to the top of the answer" behavior looks broken. Measured: header
     bottom + composer = 187px viewport-absolute, plus the composer's 64px
     ::after buffer mask = 251px occluded. Verified via Playwright: margin 260
     → answer top at 254 (clears); 272 leaves ~15px breathing above the mask. */
  scroll-margin-top: 272px;
}

/* Round 3: `overflow: visible` so the mascot's cast shadow is not clipped at
   the 36x42 box; the avatar is a small object sitting on the page ground, and
   a shadow cropped at the silhouette reads worse than no shadow at all. */
.mola-avatar {
  flex: 0 0 36px;
  width: 36px;
  height: 42px;
  margin-top: 2px;
  color: var(--c-primary);
  overflow: visible;
}
.mola-avatar svg,
.mola-avatar img.mola-icon { width: 100%; height: 100%; display: block; }
img.mola-icon-xs { width: 28px; height: 34px; }

/* White card on paper with a hairline border + soft layered shadow — the
   prototype's card texture. The shadow is what separates "white card on
   paper" from "white hole in paper". */
.bubble {
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.55;
  padding: 12px 16px;
  border-radius: 16px;
  border: .5px solid var(--c-divider);
  background: var(--card);
  box-shadow: var(--shadow-sm);
  max-width: 760px;
  /* A flex item's default `min-width: auto` is its MIN-CONTENT width, so a
     nowrap descendant (a strip's name/fork row, a Book Demo label) can push
     the bubble wider than the .turn-mola row that holds it — which at 390px
     overflowed the page by 34px. Letting the bubble shrink hands the
     constraint down to the descendants that already know how to handle it
     (the fork ellipsizes, the head row wraps). */
  min-width: 0;
}

/* Buyer's turn — ink fill, white copy (15.40:1 ✅). */
.bubble-user {
  background: var(--c-primary);
  color: #fff;
  border-color: var(--c-primary);
  border-bottom-right-radius: 4px;
}

.bubble-mola {
  background: var(--card);
  color: var(--c-primary);
  border-bottom-left-radius: 4px;
  flex: 1;
}

.bubble-error.is-hard {
  background: var(--c-danger-bg);
  border-color: var(--c-danger);
  color: var(--c-danger);
}

.bubble-body p { margin: 0 0 8px; }
.bubble-body p:last-child { margin-bottom: 0; }
.bubble-body .mola-h2 {
  font-family: var(--font-serif);
  font-size: 19px;
  font-weight: 600;
  margin: 4px 0 10px;
  color: var(--c-primary);
}
.bubble-body .mola-list { margin: 0 0 6px; padding-left: 20px; }
.bubble-body .mola-list li { margin: 2px 0; }
.bubble-body strong { font-weight: 600; }
.bubble-body em { font-style: italic; font-family: var(--font-serif); }

.answer-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}
/* Conductor answer chips — the buyer's reply options. A touch heavier than
   the starter chips (solid hairline, cream ground) because these are the
   live control surface, not decoration. Fills with ink on hover, matching
   .example-chip. */
.answer-chip {
  font-family: var(--font-sans);
  font-size: 14px;
  padding: 8px 14px;
  border-radius: 999px;
  border: 1px solid var(--c-divider);
  background: var(--c-subtle-2);
  color: var(--c-primary);
  cursor: pointer;
  transition: background .15s ease, transform .15s ease, border-color .15s ease, color .15s ease;
}
.answer-chip:hover {
  background: var(--c-primary);
  border-color: var(--c-primary);
  color: #fff;
}
.answer-chip.chip-tapped {
  transform: scale(.97);
  background: var(--c-primary);
  color: #fff;
}

.answer-hint {
  margin-top: 10px;
  font-size: 12.5px;
  color: var(--c-secondary);
  font-family: var(--font-serif);
  font-style: italic;
}

/* Multi-select checkboxes */
.answer-checks {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 12px;
}
.answer-check {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 8px 12px;
  border-radius: 10px;
  border: .5px solid var(--c-divider);
  background: #fff;
  font-size: 14px;
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.answer-check:hover { border-color: var(--c-accent); background: var(--c-subtle-2); }
.answer-check input[type="checkbox"] {
  margin-top: 3px;
  width: 16px;
  height: 16px;
  accent-color: var(--c-accent);
  flex: 0 0 16px;
  cursor: pointer;
}
.answer-check span { line-height: 1.4; color: var(--c-primary); }

.multi-continue {
  margin-top: 12px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  padding: 9px 16px;
  border-radius: 10px;
  border: .5px solid var(--c-primary);
  background: var(--c-primary);
  color: #fff;
  cursor: pointer;
  transition: background .15s ease, opacity .15s ease;
}
.multi-continue:hover:not(:disabled) { background: var(--c-primary-hover); }
.multi-continue:disabled { opacity: .4; cursor: not-allowed; }

/* Once a question has been answered, fade its chips/inputs so the user
   knows that turn is closed. Locked controls are also disabled via JS. */
.turn-question.is-answered .answer-chip,
.turn-question.is-answered .answer-check,
.turn-question.is-answered .multi-continue,
.turn-question.is-answered .inline-answer {
  opacity: .45;
  pointer-events: none;
}
.turn-question.is-answered .answer-chip { cursor: default; }

/* Stale chips on earlier Mola bubbles (after a new turn fires) — read
   as inactive so the buyer can't backtrack mid-conversation. */
.turn-mola.is-stale .answer-chip,
.turn-mola.is-stale .example-chip {
  opacity: .42;
  cursor: not-allowed;
  pointer-events: none;
}
.turn-mola.is-stale .answer-check { opacity: .42; pointer-events: none; }

.inline-answer {
  margin-top: 14px;
  padding-top: 12px;
  border-top: .5px solid var(--c-divider);
}
/* Chip-less question (graceful free-text clarify) — drop the separator and
   the italic "or" framing; the input is the whole answer surface. */
.inline-answer.no-chips {
  margin-top: 10px;
  padding-top: 0;
  border-top: none;
}
.inline-answer.no-chips .inline-answer-or {
  font-style: normal;
  font-family: var(--font-sans);
  color: var(--c-primary);
  font-size: 13px;
}
.inline-answer-or {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 12.5px;
  color: var(--c-secondary);
  margin-bottom: 8px;
}
.inline-answer-row {
  display: flex;
  gap: 8px;
  align-items: flex-end;  /* keep Send pinned to the bottom as textarea grows */
}
.inline-answer-input {
  flex: 1;
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.5;
  padding: 9px 12px;
  border-radius: 10px;
  border: .5px solid var(--c-divider);
  background: #fff;
  color: var(--c-primary);
  outline: none;
  resize: none;            /* user lets autosize handle it */
  overflow-y: auto;
  min-height: 38px;
  max-height: 160px;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.inline-answer-input:focus {
  border-color: var(--c-accent);
  box-shadow: 0 0 0 2px rgba(47, 74, 102, .15);
}
.inline-answer-send {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 38px;            /* match textarea min-height */
  border-radius: 10px;
  border: .5px solid var(--c-primary);
  background: var(--c-primary);
  color: #fff;
  cursor: pointer;
  transition: background .15s ease;
}
.inline-answer-send:hover { background: var(--c-primary-hover); }
.inline-answer-input:disabled,
.inline-answer-send:disabled {
  opacity: .5;
  cursor: not-allowed;
}

/* M11.5d — inline follow-up input attached to the latest response bubble
   when the buyer is in post-refresh follow-up-only mode. Visually echoes
   the inline-answer block on question bubbles. */
.inline-followup-block {
  margin-top: 16px;
  padding-top: 14px;
  border-top: .5px dashed var(--c-divider);
}
.inline-followup-label {
  font-family: var(--font-sans);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--c-accent-ink);
  margin-bottom: 8px;
}

.follow-up {
  margin-top: 14px;
  padding-top: 12px;
  border-top: .5px solid var(--c-divider);
}
.follow-up-prompt {
  font-size: 14px;
  font-weight: 500;
  margin-bottom: 8px;
  color: var(--c-accent-ink);
}

.thinking-text {
  display: inline-flex;
  align-items: baseline;
  padding: 4px 2px;
  color: var(--c-muted);
  font-style: italic;
}
.thinking-msg {
  transition: opacity .2s ease;
}
.thinking-text.is-swapping .thinking-msg {
  opacity: .15;
}
/* Typing-style animated ellipsis after the rotating status line. */
.thinking-ellipsis {
  display: inline-block;
  width: 1.2em;
  text-align: left;
}
.thinking-ellipsis::after {
  content: '';
  animation: thinking-ellipsis 1.4s steps(1, end) infinite;
}
@keyframes thinking-ellipsis {
  0%   { content: ''; }
  25%  { content: '.'; }
  50%  { content: '..'; }
  75%, 100% { content: '...'; }
}

.composer-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 4px 0;
  min-height: 24px;
}
.composer-hint {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 12.5px;
  color: var(--c-secondary);
}
/* ROUND 3 — the founder's "the New conversation button is still giving that
   old feeling, whereas our cards are now different". It was the last survivor
   of the pre-verdict system: a 999px pill on a tinted fill with a filled blue
   circle badge inside it and a hand-rolled rgba shadow. It now uses the same
   three things every other control in this pass uses: --radius-md, a white
   surface with a hairline, and --e1 / --e1-raised / --e1-press. The icon badge
   loses its accent fill and becomes a plain glyph, so the control has ONE
   shape rather than a shape inside a shape. */
.new-convo-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  padding: 8px 14px;
  border-radius: var(--radius-md);
  border: 1px solid var(--c-divider);
  background: var(--card);
  color: var(--c-primary);
  box-shadow: var(--e1);
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease, color .15s ease, box-shadow .16s ease;
}
.new-convo-btn .new-convo-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  border-radius: 0;
  background: none;
  color: var(--c-muted);
  flex-shrink: 0;
}
.new-convo-btn .new-convo-icon svg {
  width: 14px;
  height: 14px;
}
.new-convo-btn:hover {
  border-color: var(--c-primary);
  background: var(--card);
  color: var(--c-primary);
  box-shadow: var(--e1-raised);
}
.new-convo-btn:active { box-shadow: var(--e1-press); }
.new-convo-btn:focus-visible { outline: none; box-shadow: var(--ring); }
.new-convo-btn[hidden] { display: none; }

/* Quiet, low-salience New-Conversation control. Used for the demoted
   composer-toolbar link AND the per-answer CTA appended below answer
   bubbles. Reads as a subtle text link, not a button, so it stays out of
   the way until the buyer is looking for it. */
.new-convo-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  padding: 0;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  color: var(--c-secondary);
  cursor: pointer;
  text-decoration: none;
  transition: color .15s ease;
}
.new-convo-link svg { width: 13px; height: 13px; flex-shrink: 0; }
.new-convo-link:hover { color: var(--c-accent); text-decoration: underline; }
.new-convo-link[hidden] { display: none; }

/* Wrapper for the per-answer New-Conversation CTA — sits directly below the
   inline follow-up bar as the secondary "or start over" action. */
.turn-new-convo {
  margin-top: 12px;
  display: flex;
  justify-content: flex-end;
}

/* M11.5d — emphasized state, used when the conversation has hit the
   follow-up depth cap. Pulses to draw the buyer's eye to the only
   forward action they have left. */
.new-convo-btn.is-emphasized {
  background: var(--c-accent);
  color: #fff;
  border-color: var(--c-accent);
  box-shadow: 0 0 0 0 rgba(47, 74, 102, .50);
  animation: new-convo-pulse 2.4s ease-in-out infinite;
}
.new-convo-btn.is-emphasized .new-convo-icon {
  background: none;
  color: #fff;
}
@keyframes new-convo-pulse {
  0%   { box-shadow: 0 0 0 0   rgba(47, 74, 102, .45); }
  60%  { box-shadow: 0 0 0 10px rgba(47, 74, 102, 0);  }
  100% { box-shadow: 0 0 0 0   rgba(47, 74, 102, 0);   }
}

/* ── Themed confirm modal (replaces window.confirm) ─────────────── */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(18, 38, 58, .42);
  backdrop-filter: blur(2px);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  opacity: 0;
  transition: opacity .14s ease-out;
}
.modal-backdrop.is-open {
  display: flex;
  opacity: 1;
}
.modal-card {
  background: #fff;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 22px 22px 18px;
  width: min(420px, calc(100vw - 32px));
  border: .5px solid var(--c-divider);
  transform: translateY(6px);
  transition: transform .18s ease-out;
}
.modal-backdrop.is-open .modal-card {
  transform: translateY(0);
}
.modal-title {
  font-family: var(--font-serif);
  font-size: 20px;
  font-weight: 600;
  color: var(--c-primary);
  line-height: 1.25;
  margin-bottom: 8px;
}
.modal-message {
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.55;
  color: var(--c-secondary);
  margin-bottom: 20px;
}
.modal-actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
}
.modal-actions button {
  font-family: var(--font-sans);
  font-size: 13.5px;
  font-weight: 600;
  padding: 9px 16px;
  border-radius: var(--radius-md);
  box-shadow: var(--e1);
  cursor: pointer;
  transition: background .12s ease-out, color .12s ease-out, border-color .12s ease-out, box-shadow .16s ease;
}
.modal-actions button:hover:not(:disabled) { box-shadow: var(--e1-raised); }
.modal-actions button:active:not(:disabled) { box-shadow: var(--e1-press); }
.modal-actions button:disabled { box-shadow: none; }
.modal-cancel {
  background: #fff;
  border: 1px solid var(--c-divider);
  color: var(--c-secondary);
}
.modal-cancel:hover {
  border-color: var(--c-primary);
  color: var(--c-primary);
}
.modal-confirm {
  background: var(--c-primary);
  border: 1px solid var(--c-primary);
  color: #fff;
}
.modal-confirm:hover {
  background: var(--c-primary-hover);
  border-color: var(--c-primary-hover);
}
.modal-confirm:disabled {
  opacity: .6;
  cursor: progress;
}

/* Book Demo modal — wider card + form layout */
.modal-backdrop.demo-modal .demo-modal-card {
  width: min(480px, calc(100vw - 32px));
  max-height: calc(100vh - 48px);
  overflow-y: auto;
}

/* ── Demo booked — success card + celebration ─────────────────────────
   The one moment the accent gets to be loud. Navy copy on the accent tint
   (10.53 ✅); the checkmark is accent stroke on tint. Under reduced motion the
   check renders complete (no draw) and the burst is never created at all. */
.demo-success {
  margin: -6px -6px 18px;
  padding: 20px 18px 16px;
  text-align: center;
  background: var(--c-cta-tint);
  border: .5px solid var(--c-cta-border);
  border-radius: var(--radius-md);
}
.demo-success .modal-title { margin-bottom: 6px; }
.demo-success .modal-message { margin-bottom: 0; color: var(--c-primary); }
.demo-success-check {
  width: 52px;
  height: 52px;
  margin: 0 auto 10px;
  display: block;
  stroke: var(--c-cta);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.demo-success-ring { stroke: var(--c-cta-border); stroke-width: 2.5; }
html.motion-ok .demo-success-tick {
  stroke-dasharray: 40;
  stroke-dashoffset: 40;
  animation: demo-check-draw 520ms ease-out 120ms forwards;
}
@keyframes demo-check-draw {
  to { stroke-dashoffset: 0; }
}

/* 12-dot burst. Built by fireMolaBurst, which only runs when motion is
   allowed; each <i> carries its own --dx/--dy/--rot/--d. Pinned to the
   backdrop centre and pointer-transparent so it never eats a click on the
   Done button underneath it. */
.mola-burst {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 1;
}
.mola-burst i {
  position: absolute;
  top: 0;
  left: 0;
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  border-radius: 1.5px;
  background: var(--c-cta);
  opacity: 0;
  animation: mola-burst-fly 1100ms cubic-bezier(.15,.7,.35,1) var(--d, 0ms) 1 both;
}
/* Coral family, three tones so the ring reads as confetti not a single dot
   colour: coral → coral-hot → the deep text-safe coral. */
.mola-burst i:nth-child(3n)   { background: var(--c-cta-hot); }
.mola-burst i:nth-child(4n)   { background: var(--c-cta-ink); }
@keyframes mola-burst-fly {
  0%   { opacity: 0; transform: translate(0, 0) rotate(0deg) scale(.4); }
  12%  { opacity: 1; }
  55%  { opacity: 1; transform: translate(var(--dx), var(--dy)) rotate(var(--rot)) scale(1); }
  100% { opacity: 0; transform: translate(var(--dx), calc(var(--dy) + 90px)) rotate(var(--rot)) scale(.8); }
}

/* ── Compare: select up to 2 + structured comparison table ──────────────── */
.compare-select {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;          /* push to the right of the kicker row */
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 600;
  color: var(--c-muted);
  cursor: pointer;
  user-select: none;
}
.compare-select input { cursor: pointer; }
.compare-checkbox:disabled + span { opacity: .4; }
.compare-select:has(.compare-checkbox:disabled) { cursor: default; }

.compare-bar {
  position: fixed;
  left: 50%;
  /* Clear the fixed "Mola is AI" footer strip (bottom:0, ~34px tall). */
  bottom: 52px;
  transform: translateX(-50%);
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px 10px 16px;
  background: var(--c-primary);
  color: #fff;
  border-radius: 999px;
  box-shadow: var(--shadow-lg);
  max-width: calc(100vw - 24px);
}
.compare-bar-label {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 60vw;
}
.compare-go {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  color: var(--c-primary);
  background: #fff;
  border: none;
  border-radius: 999px;
  padding: 7px 16px;
  cursor: pointer;
}
.compare-go:disabled { opacity: .5; cursor: default; }
.compare-clear {
  font-size: 18px;
  line-height: 1;
  color: rgba(255, 255, 255, .85);
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0 4px;
}

.modal-backdrop.compare-modal .compare-modal-card {
  width: min(680px, calc(100vw - 32px));
  max-height: calc(100vh - 48px);
  overflow-y: auto;
}
.compare-table-wrap { overflow-x: auto; margin: 8px 0 4px; }
.compare-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-sans);
  font-size: 13px;
}
.compare-table th,
.compare-table td {
  text-align: left;
  vertical-align: top;
  padding: 8px 10px;
  border-bottom: .5px solid var(--c-divider);
}
.compare-table thead th {
  font-weight: 600;
  color: var(--c-primary);
  border-bottom: 1px solid var(--c-divider);
}
.compare-table tbody th {
  font-weight: 600;
  color: var(--c-muted);
  white-space: nowrap;
  width: 1%;
}
.compare-table td { color: var(--c-primary); }
/* Neutral cream, deliberately not coral: compare is analysis, not conversion. */
.compare-row-differ td,
.compare-row-differ th { background: var(--cream-dim); }
.compare-bottom {
  margin-top: 12px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--c-secondary);
}
.compare-bottom p { margin: 4px 0; }
.compare-modal-note {
  margin-top: 10px;
  font-size: 11.5px;
  color: var(--c-muted);
  line-height: 1.4;
}
.demo-modal-sub {
  margin-bottom: 16px;
}
/* Highlight the "30-min" commitment so it's not missed in the sub-header. */
.demo-duration {
  font-weight: 700;
  color: var(--c-accent-ink);
  background: var(--c-subtle);
  padding: 1px 5px;
  border-radius: 4px;
  white-space: nowrap;
}
/* Title row: let the vendor name + View-profile link wrap gracefully. */
.demo-modal-title {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 10px;
}
/* Make the vendor name stand out in the title. */
.demo-vendor-name {
  font-weight: 700;
  color: var(--c-primary);
  border-bottom: 2px solid var(--c-accent);
  padding-bottom: 1px;
}
/* Inline "View profile" link beside the vendor name (opens DTH in a new tab). */
.demo-view-profile {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  color: var(--c-accent-ink);
  text-decoration: none;
  white-space: nowrap;
}
.demo-view-profile svg { width: 13px; height: 13px; }
.demo-view-profile:hover { color: var(--c-accent); text-decoration: underline; }
.demo-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.demo-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--c-secondary);
}
.demo-field > span {
  font-weight: 600;
  color: var(--c-primary);
}
.demo-optional {
  font-weight: 400;
  color: var(--c-secondary);
}
.demo-field input,
.demo-field textarea {
  font-family: var(--font-sans);
  font-size: 14px;
  padding: 9px 11px;
  border: 1px solid var(--c-divider);
  border-radius: var(--radius-md);
  background: #fff;
  color: var(--c-primary);
  transition: border-color .12s ease-out, box-shadow .12s ease-out;
}
.demo-field textarea {
  resize: vertical;
}
/* Prefilled-from-account, non-editable identity fields (name/email). */
.demo-field input.demo-field-locked {
  background: var(--c-subtle-2);
  color: var(--c-muted);
  cursor: not-allowed;
}
.demo-field input:focus,
.demo-field textarea:focus {
  outline: none;
  border-color: var(--c-primary);
  box-shadow: 0 0 0 3px rgba(20, 35, 70, .08);
}
.demo-slots {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  border: 1px solid var(--c-divider);
  border-radius: var(--radius-md);
  background: var(--cream);
}
.demo-slots-label {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  color: var(--c-primary);
  margin-bottom: 2px;
}
.demo-tz-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--c-secondary);
  margin-bottom: 4px;
}
.demo-tz-field > span {
  font-weight: 600;
  color: var(--c-primary);
}
.demo-slot-tz {
  font-family: var(--font-sans);
  font-size: 14px;
  padding: 8px 10px;
  border: 1px solid var(--c-divider);
  border-radius: var(--radius-md);
  background: #fff;
  color: var(--c-primary);
  appearance: auto;
  transition: border-color .12s ease-out, box-shadow .12s ease-out;
}
.demo-slot-tz:focus {
  outline: none;
  border-color: var(--c-primary);
  box-shadow: 0 0 0 3px rgba(20, 35, 70, .08);
}
.demo-slot-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.demo-slot-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.demo-slot-date,
.demo-slot-time {
  flex: 1;
  min-width: 110px;
  font-family: var(--font-sans);
  font-size: 14px;
  padding: 8px 10px;
  border: 1px solid var(--c-divider);
  border-radius: var(--radius-md);
  background: #fff;
  color: var(--c-primary);
  appearance: auto;
  transition: border-color .12s ease-out, box-shadow .12s ease-out;
}
.demo-slot-date:focus,
.demo-slot-time:focus {
  outline: none;
  border-color: var(--c-primary);
  box-shadow: 0 0 0 3px rgba(20, 35, 70, .08);
}
.demo-remove-slot {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  line-height: 1;
  color: var(--c-muted);
  background: transparent;
  border: 1px solid var(--c-divider);
  border-radius: var(--radius-md);
  cursor: pointer;
}
.demo-remove-slot:hover {
  color: var(--c-danger);
  border-color: var(--c-danger);
}
.demo-add-slot {
  align-self: flex-start;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  color: var(--c-primary);
  background: transparent;
  border: 1px dashed var(--c-divider);
  border-radius: var(--radius-md);
  padding: 6px 10px;
  cursor: pointer;
}
.demo-add-slot:hover:not(:disabled) {
  border-color: var(--c-primary);
}
.demo-add-slot:disabled {
  opacity: .45;
  cursor: default;
}
/* datetime-local rules replaced by .demo-slot-date / .demo-slot-time above */
.demo-flexible {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
  font-family: var(--font-sans);
  font-size: 13.5px;
  color: var(--c-primary);
  cursor: pointer;
}
.demo-flexible input[type="checkbox"] {
  width: 16px;
  height: 16px;
  cursor: pointer;
}
.demo-error {
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--c-danger);
  padding: 8px 10px;
  background: var(--c-danger-bg);
  border: 1px solid #E9C4C0;
  border-radius: var(--radius-md);
}

.error-actions {
  margin-top: 12px;
  display: flex;
  gap: 8px;
}
.retry-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  padding: 8px 14px;
  border-radius: 10px;
  border: .5px solid currentColor;
  background: transparent;
  color: inherit;
  cursor: pointer;
  transition: background .15s ease, opacity .15s ease;
}
.retry-btn:hover:not(:disabled) { background: rgba(0, 0, 0, .05); }
.retry-btn:disabled { opacity: .5; cursor: not-allowed; }

.bubble-mola .vendor-grid {
  margin-top: 14px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 12px;
}

/* M9 — structured per-axis blurb blocks inside vendor cards */
.vendor-axis {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 10px;
  padding-top: 8px;
  border-top: .5px dashed var(--c-divider);
}
.vendor-axis:first-of-type {
  margin-top: 12px;
  padding-top: 10px;
  border-top: .5px solid var(--c-divider);
}
.vendor-axis .axis-label {
  font-family: var(--font-sans);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--c-secondary);
}
.vendor-axis .axis-body {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--c-primary);
}
.vendor-axis-strength .axis-label { color: var(--c-success); }
.vendor-axis-weakness .axis-label { color: var(--c-warning); }
.vendor-axis-fit      .axis-label { color: var(--c-accent-ink); }

/* Best-fit row on vendor cards (rendered all-or-nothing across the grid). */
.vendor-best-fit {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 10px;
  padding-top: 8px;
  border-top: .5px dashed var(--c-divider);
}
.vendor-best-fit .axis-label {
  font-family: var(--font-sans);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--c-accent-ink);
}
.vendor-best-fit .axis-body {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--c-primary);
}

/* M11.5c — streaming skeleton: pulsing bar shown in the Best-fit row
   while the per-vendor blurb LLM call is in flight. Replaced with the
   real text the moment the vendor_card SSE event arrives. */
.vendor-best-fit.is-loading .best-fit-skeleton {
  display: block;
  height: 10px;
  width: 78%;
  margin-top: 6px;
  border-radius: 4px;
  background: linear-gradient(
    90deg,
    rgba(18, 38, 58, 0.08) 0%,
    rgba(18, 38, 58, 0.18) 50%,
    rgba(18, 38, 58, 0.08) 100%
  );
  background-size: 200% 100%;
  animation: best-fit-shimmer 1.2s ease-in-out infinite;
}
.vendor-best-fit:not(.is-loading) .best-fit-skeleton {
  display: none;
}
@keyframes best-fit-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* ══════════════════════════════════════════════════════════════════
   REDUCED MOTION — disable non-essential motion
   Blanket safety net for pre-existing animations. Motion added for the
   demo-conversion layer is additionally gated on `html.motion-ok` (see the
   note under :root) — that class is the one to use for anything new.
   ══════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  html { scroll-behavior: auto; }
}

/* ══════════════════════════════════════════════════════════════════
   RESPONSIVE
   ══════════════════════════════════════════════════════════════════ */
@media (max-width: 720px) {
  :root { --header-h: 69px; }
  main { padding: 28px 18px 140px; }
  #hero { padding: 24px 12px 6px; }
  /* Smaller on a phone: the hero has to clear the composer AND the chips
     above the fold, and the mark is identity, not the message. */
  .mola-rich-hero { width: 89px; height: 104px; margin-bottom: 6px; }
  /* Composer row on a phone: the mascot (48px) + Ask button + padding left
     the textarea ~167px, narrow enough that the placeholder wrapped to a
     second line inside the 48px-tall box and got clipped. Shrink the
     composer mascot and tighten the button so the field keeps a single
     line; the mascot stays present (it is the state indicator) but at
     36px it costs a third less of the row. */
  .mola-rich-composer { width: 34px; height: 40px; margin-left: 2px; }
  .composer-inner { gap: 6px; padding: 8px; }
  /* The rotating pain placeholders are full sentences and the phone leaves the
     field ~207px wide, so they legitimately need two lines — at 48px the second
     line was sliced horizontally through the glyphs, on the primary control of
     the fold. Hold two lines instead of clipping one: 56px clears the 44px tap
     floor, and autoResize() reads this min-height as its floor so its inline
     single-line measurement can't shrink the box under the placeholder. */
  #question-input {
    padding: 7px 8px;
    min-height: 56px;
  }
  #ask-btn { padding: 0 16px; }
  .question-text { font-size: 19px; }
  .vendor-card { padding: 16px 18px; }
  .vendor-kicker { flex-wrap: wrap; }
  .card-footer { flex-direction: column; align-items: flex-start; }
  /* Two-CTA footer stays as a side-by-side grid on mobile too. */
  .card-footer-split { display: grid; grid-template-columns: 1fr 1fr; align-items: stretch; }
  .card-footer-split .cta-link { font-size: 11.5px; padding: 8px 8px; }
  /* ...but the coral Book Demo must hold 14px/700: white-on-coral is
     4.29:1 and only clears AA as large text. Tighten padding, not type. */
  .card-footer-split .cta-link.cta-primary { font-size: 14px; padding: 8px 6px; }
  /* Depth-cap callout carries two buttons (New conversation + N-3 Book a
     demo); stack them rather than letting the vendor name squeeze. */
  .last-turn-callout .inline-new-convo { flex-direction: column; align-items: stretch; }
  .last-turn-callout .inline-new-convo .cta-link { justify-content: center; }
  .nudge-actions .cta-link { width: 100%; justify-content: center; }
  .example-chip--card { min-width: 0; max-width: none; flex-basis: 100%; }

  /* ── Results on a phone ────────────────────────────────────────
     The winner's name drops a size (26px would break "Lighthouse 360+"
     across three lines at 390px) and its full-width CTA keeps its own
     single-column footer, overriding the two-up grid above. */
  .v-win .vendor-name { font-size: 21px; }
  .v-win { padding: 16px 18px 14px; }
  .v-win .card-footer-split { grid-template-columns: 1fr; }
  .v-win .card-footer-split .cta-link.cta-primary { font-size: 15px; padding: 0 12px; }
  /* Strips: 390px cannot hold rank + name + fork + chevron + Compare on one
     row. The head becomes two rows — identity + controls on top, the fork on
     its own full-width line beneath — so the fork gets the whole strip width
     instead of the ~120px left over beside the Compare label, where it
     truncated after three words. */
  .v-run-head {
    flex-wrap: wrap;
    padding-right: 10px;
  }
  .v-run-toggle {
    flex-wrap: wrap;
    padding: 11px 4px 11px 12px;
    gap: 4px 8px;
  }
  /* Compare drops its word on a phone. The bubble is only ~274px wide here
     (pre-existing: avatar + padding), so the label was costing the fork half
     its room; the checkbox alone is the affordance, and the strip's other
     controls are already icon-only at this width. The accessible name comes
     from the label's own title attribute. */
  .v-run-head > .compare-select { gap: 0; }
  .v-run-head > .compare-select > span {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }
  .v-run .vendor-name { white-space: normal; }
  /* The fork wraps to a second line INSIDE the toggle (it is the toggle's
     child, so it is that flex context it must break out of), and the chevron
     is pushed to the end of the first line so the fork owns row two. */
  .v-chev { margin-left: auto; order: 2; }
  .v-fork {
    flex-basis: 100%;
    order: 3;
    padding-right: 4px;
  }
  .v-run-body { padding: 2px 12px 12px; }
  .narrow-line { font-size: 10px; gap: 5px; }

  /* ── Landing narrative on a phone ──────────────────────────────
     Single column everywhere, the sticky legend released (it would eat the
     viewport above the stage), and the ink band's negative-margin breakout
     retightened so nothing overflows the 100vw ground. */
  .landing-section { padding: 36px 0; }
  /* On a phone the next section is already half-visible below the fold; the
     cue costs a row of #hero-tail to say what the scroll position says. */
  .scroll-cue { display: none; }
  /* Card padding and the intro block both cost a phone screen across five
     sections; the copy is untouched, only the air around it. */
  .zone { padding: 16px 16px 14px; gap: 7px; }
  .zone-grid { gap: 12px; }
  .landing-section h2 { margin-bottom: 10px; }
  .duel-card { padding: 18px 18px; }
  .landing-band { padding-left: 16px; padding-right: 16px; }
  .replay-grid { grid-template-columns: 1fr; gap: 24px; }
  .replay-copy { position: static; }
  /* The phone reel is a static end state (app.js _phoneStage), so the panel
     no longer has to reserve room for a build-up nobody watches. */
  .replay-stage { min-height: 0; }
  .stage-body { padding: 14px 12px 16px; }
  .stage-feed { gap: 10px; }
  .stage-win { padding: 16px 16px 14px; }
  .stage-win-name { font-size: 22px; margin-top: 8px; }
  .stage-head { padding: 10px 12px; }
  .replay-grid { gap: 18px; }
  .stage-run-head { padding: 11px 12px; }
  .zone-grid { grid-template-columns: 1fr; scroll-snap-type: y proximity; }
  .zone { min-height: 0; scroll-snap-align: start; }
  .zone-grid:not(.is-expanded) .zone:nth-child(n+4) { display: none; }
  .zones-more { display: inline-flex; }
  .duel { grid-template-columns: 1fr; }
  .duel-hint { display: none !important; }
  .stage-demo-big { font-size: 15px; }
  /* The fork clause wraps to two lines at 390px and loses its baseline against
     the name; give it the whole strip width on its own row. The chevron is
     pinned to the end of the NAME row (order 2, before the fork's order 3) —
     it followed the fork onto row two and sat orphaned on a line of its own,
     detached from the control it belongs to. Same pattern the results strips
     use for .v-chev / .v-fork. */
  .stage-run-head { flex-wrap: wrap; row-gap: 2px; }
  .stage-run-name { flex: 1; min-width: 0; white-space: normal; }
  .stage-chev { margin-left: auto; order: 2; }
  .stage-fork { flex-basis: 100%; order: 3; padding-left: 26px; }
  .stage-tab { min-height: 44px; padding: 11px 16px; }
  .scroll-cue a { min-height: 44px; }
  /* Chips become a one-row scroll rail rather than five stacked cards (308px
     of the fold). The outcome line comes back here — a stacked card has room
     for it and the rail is the only place it costs the fold. */
  .examples {
    flex-wrap: nowrap;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    justify-content: flex-start;
    scrollbar-width: none;
    padding-bottom: 2px;
  }
  .examples::-webkit-scrollbar { display: none; }
  .example-chip--card { flex: 0 0 auto; min-width: 0; max-width: 260px; scroll-snap-align: start; }
  .example-chip--card .chip-outcome { display: none; }
  /* Dock: drop the brand + the loaded tell so the input keeps a usable width. */
  #landing-dock { height: 64px; }
  .dock-inner { padding: 0 12px; gap: 8px; }
  .dock-brand, .dock-tag { display: none; }
  .dock-ask { min-height: 44px; padding: 0 18px; }
}

/* ── FU-P3-056 — per-turn feedback widget ────────────────────────────────── */
.bubble-feedback {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px dashed var(--c-divider);
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.bubble-feedback.is-emphasized {
  border-top: 1px solid var(--c-divider);
  background: var(--c-subtle-2);
  margin-left: -14px;
  margin-right: -14px;
  padding: 14px;
  border-radius: 10px;
}
.feedback-thumbs {
  display: flex;
  align-items: center;
  gap: 10px;
}
.feedback-prompt {
  font-size: 13px;
  color: var(--c-secondary);
  margin-right: auto;
}
.bubble-feedback.is-emphasized .feedback-prompt {
  font-weight: 600;
  color: var(--c-primary);
  font-size: 14px;
}
/* A round icon button legitimately keeps --radius-full (the chip/pill tier);
   round 3 only gives it the depth every other control now has, and a white
   surface so it is not a hole in the bubble it sits inside. */
.feedback-thumb {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full);
  border: 1px solid var(--c-divider);
  background: var(--card);
  color: var(--c-secondary);
  box-shadow: var(--e1);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  transition: background .12s ease, border-color .12s ease, color .12s ease, transform .08s ease, box-shadow .16s ease;
}
.feedback-thumb svg { width: 16px; height: 16px; }
.feedback-thumb:hover:not(:disabled) { border-color: var(--c-accent); color: var(--c-accent); box-shadow: var(--e1-raised); }
.feedback-thumb:active:not(:disabled) { transform: scale(.92); box-shadow: var(--e1-press); }
.feedback-thumb.is-selected {
  background: var(--c-accent);
  border-color: var(--c-accent);
  color: #fff;
}
.feedback-thumb.feedback-down.is-selected {
  background: var(--c-danger);
  border-color: var(--c-danger);
}
.feedback-thumb:disabled { box-shadow: none; }
.feedback-thumb:disabled {
  cursor: default;
  opacity: .8;
}
.feedback-text-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* HTML `hidden` attribute would normally set display:none via UA stylesheet,
   but the class selector above wins on specificity. Restore the intended
   reveal-on-thumb-click behavior. Same for the confirmation strip. */
.feedback-text-row[hidden],
.feedback-confirm[hidden] {
  display: none;
}
.feedback-text {
  width: 100%;
  min-height: 64px;
  padding: 8px 10px;
  font: inherit;
  font-size: 13.5px;
  line-height: 1.4;
  border: 1px solid var(--c-divider);
  border-radius: 8px;
  background: var(--c-surface);
  color: var(--c-primary);
  resize: vertical;
  box-sizing: border-box;
}
.feedback-text:disabled { background: var(--c-subtle); opacity: .85; }
.feedback-text-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.feedback-counter {
  font-size: 11.5px;
  color: var(--c-muted);
}
.feedback-submit {
  font: inherit;
  font-size: 12.5px;
  padding: 6px 14px;
  border-radius: 999px;
  border: none;
  background: var(--c-accent);
  color: #fff;
  cursor: pointer;
  transition: background .12s ease, opacity .12s ease;
}
.feedback-submit:hover:not(:disabled) { background: var(--c-accent-ink); }
.feedback-submit:disabled { opacity: .55; cursor: default; }
.feedback-actions {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.feedback-cancel {
  font: inherit;
  font-size: 12.5px;
  padding: 6px 12px;
  border-radius: 999px;
  background: transparent;
  color: var(--c-secondary);
  border: 1px solid var(--c-divider);
  cursor: pointer;
  transition: background .12s ease, border-color .12s ease, color .12s ease;
}
.feedback-cancel:hover:not(:disabled) {
  background: rgba(47, 74, 102,.06);
  color: var(--c-primary);
  border-color: rgba(47, 74, 102,.4);
}
.feedback-cancel:disabled { opacity: .55; cursor: default; }
.feedback-confirm {
  font-size: 12.5px;
  color: var(--c-accent);
  font-style: italic;
}

/* ── Take the shortlist with you (WS3) ────────────────────────────────────
   ACCENT DISCIPLINE: nothing in this block uses --c-cta. Amber is reserved for
   demo booking; an email CTA at the same visual weight would flatten the one
   signal the accent carries. The primary action here is navy-outlined, the
   secondary two are quiet text buttons. Do not "harmonise" these with the
   Book Demo styling. */
.shortlist-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--c-divider);
}
/* ROUND 3: three CONTROLS, so all three carry the control elevation tiers.
   Round 2 left the row flat next to a shadowed card, which is the pairing the
   founder named. Radius moves off --radius-full: a pill is the chip tier
   (answer chips), and these are buttons that sit under a --radius-lg card. */
.shortlist-action {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  border-radius: var(--radius-md);
  box-shadow: var(--e1);
  cursor: pointer;
  transition: background .12s ease-out, border-color .12s ease-out, color .12s ease-out, box-shadow .16s ease;
}
.shortlist-action:hover:not(:disabled) { box-shadow: var(--e1-raised); }
.shortlist-action:active:not(:disabled) { box-shadow: var(--e1-press); }
.shortlist-action-primary {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 15px;
  background: #fff;
  border: 1px solid var(--c-primary);
  color: var(--c-primary);
}
.shortlist-action-primary svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}
.shortlist-action-primary:hover:not(:disabled) {
  background: var(--c-primary);
  color: #fff;
}
.shortlist-action-primary:disabled {
  opacity: .6;
  cursor: default;
  box-shadow: none;
}
/* Quiet by SURFACE (white on white, hairline border), not by being denied
   depth: they are as clickable as the primary next to them. */
.shortlist-action-quiet {
  padding: 8px 14px;
  background: var(--card);
  border: 1px solid var(--c-divider);
  color: var(--c-secondary);
}
.shortlist-action-quiet:hover {
  color: var(--c-primary);
  border-color: var(--c-primary);
}
/* Inline "Want this in your inbox?" on the depth-cap callout. A text link, not
   a button — it sits inside a sentence. */
.shortlist-inline-link {
  font: inherit;
  font-weight: 600;
  padding: 0;
  background: none;
  border: none;
  color: var(--c-accent);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.shortlist-inline-link:hover { color: var(--c-primary); }

/* Email modal — reuses .modal-backdrop / .modal-card / .demo-field /
   .demo-field-locked / .demo-error wholesale; only the consent block and the
   opt-in row are new. */
.modal-backdrop.shortlist-email-modal .shortlist-email-card {
  width: min(440px, calc(100vw - 32px));
}
.shortlist-email-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.shortlist-email-consent {
  font-family: var(--font-sans);
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--c-secondary);
  padding: 10px 12px;
  background: var(--c-subtle-2);
  border-radius: var(--radius-md);
}
.shortlist-email-optin {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: 1.5;
  color: var(--c-secondary);
  cursor: pointer;
}
.shortlist-email-optin input[type="checkbox"] {
  width: 16px;
  height: 16px;
  margin-top: 1px;
  flex-shrink: 0;
  cursor: pointer;
}

/* Print — one shortlist, not the transcript. Everything outside the bubble
   marked .is-printing is hidden, and the cards are flattened so a long
   shortlist paginates instead of scrolling inside a clipped container. */
@media print {
  html.is-printing-shortlist .navbar,
  html.is-printing-shortlist .footer,
  html.is-printing-shortlist .app-footer-strip,
  html.is-printing-shortlist #hero,
  html.is-printing-shortlist #landing-narrative,
  html.is-printing-shortlist #landing-dock,
  html.is-printing-shortlist #empty-state-chips,
  html.is-printing-shortlist .composer,
  html.is-printing-shortlist .turn-user,
  html.is-printing-shortlist .turn-mola:not(.is-printing) {
    display: none !important;
  }
  html.is-printing-shortlist .shortlist-actions,
  html.is-printing-shortlist .bubble-feedback,
  html.is-printing-shortlist .nudge-card,
  html.is-printing-shortlist .nudge-inline,
  html.is-printing-shortlist .last-turn-callout,
  html.is-printing-shortlist .suggested-questions,
  html.is-printing-shortlist .compare-select,
  html.is-printing-shortlist .v-chev,
  html.is-printing-shortlist .card-footer .cta-primary {
    display: none !important;
  }
  html.is-printing-shortlist .mola-avatar { display: none !important; }
  html.is-printing-shortlist .vendor-grid,
  html.is-printing-shortlist .v-runners {
    display: block !important;
  }
  /* A printed shortlist with its runners still collapsed is a page with one
     vendor on it. Force every strip open — [hidden] needs the !important to
     beat the attribute's own UA rule. */
  html.is-printing-shortlist .v-run-body[hidden] {
    display: block !important;
  }
  html.is-printing-shortlist .v-run { background: #fff !important; }
  html.is-printing-shortlist .vendor-card {
    break-inside: avoid;
    page-break-inside: avoid;
    margin-bottom: 10px;
  }
  /* Print the destination of every profile link — a paper shortlist with
     unclickable "View profile" labels is useless. */
  html.is-printing-shortlist .cta-link.cta-secondary::after {
    content: " (" attr(href) ")";
    font-size: 10px;
    font-weight: 400;
    word-break: break-all;
  }
}

@media (max-width: 720px) {
  .shortlist-actions {
    gap: 6px;
  }
  .shortlist-action-primary {
    flex: 1 1 100%;
    justify-content: center;
  }
}

/* ══════════════════════════════════════════════════════════════════
   VERDICT LAYER (2026-09-06): intake meter + Verdict recap
   Appended as one block. Meter bar is ink/accent, never coral; in the
   recap only Book the demo is coral (booking is coral's one job).
   ══════════════════════════════════════════════════════════════════ */

/* ── Intake meter: the round-1 bar meter was replaced by the dial in the
   ROUND 2 block at the end of this file (.meter-card.dial-card). ── */

/* ── Verdict recap at the depth cap ────────────────────────────── */
/* The recap variant of the callout drops the editorial-note tint and rule:
   the card carries the frame. */
.last-turn-callout.is-recap {
  display: block;
  padding: 0;
  background: transparent;
  border: 0;
  color: inherit;
  font-size: inherit;
  line-height: inherit;
}
/* Container tier: --radius-lg like the bubble and the paper, --shadow-sm.
   The frame stays an ink hairline (this is the end of the thread and it
   should read as bounded), but the corner and the lift are the app's. */
.recap {
  /* Round 4: the recap renders outside .paper, so it declares the SAME four
     sizes. Any rule below that wants a size takes one of these; nothing in
     the recap is 10, 11, 12 or 13px. */
  --slip-display: 44px;
  --slip-lead: 20px;
  --slip-body: 17px;
  --slip-label: 14px;
  background: var(--card);
  border: 1px solid var(--c-primary);
  border-radius: var(--radius-lg);
  padding: 18px 20px 16px;
  max-width: 520px;
  color: var(--c-primary);
  box-shadow: var(--shadow-sm);
}
.recap-eyebrow {
  font-family: var(--font-sans);
  font-size: var(--slip-label);
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--c-primary);
}
.recap-headline {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--slip-lead);
  line-height: 1.3;
  margin: 6px 0 12px;
  color: var(--c-primary);
  text-wrap: balance;
}
/* ROUND 4 — THE PRIMARY NEVER SHARES A ROW (the founder's rule, stated for
   the recap and applied on the slip and in runner rows too). Round 3's wrap
   row put "Have DTH ask the vendor" directly beside "Book a demo with
   DentScribe" at 1280, which made the two read as a pair of equals. Now:
   row 1 is the coral button alone, full width of the recap's content column;
   row 2 is the two secondary controls side by side, stacking at 390. */
.recap-acts {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  margin-top: 14px;
}
.recap-acts-sub {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.recap-acts-sub .recap-btn { flex: 1 1 0; min-width: 0; }
.recap-btn {
  appearance: none;
  font-family: var(--font-sans);
  font-size: var(--slip-label);
  font-weight: 600;
  line-height: 1.25;
  padding: 10px 12px;
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  text-align: center;
  cursor: pointer;
  box-shadow: var(--e1);
  transition: background 180ms ease, color 180ms ease, border-color 180ms ease, box-shadow 160ms ease;
}
.recap-btn:active { box-shadow: var(--e1-press); }
.recap-btn:focus-visible { outline: none; box-shadow: var(--ring); }
/* Full width of the content column, alone on its row. Weight 700 at the label
   size clears the >=14px-bold floor white-on-coral requires. */
.recap-btn-primary {
  display: block;
  width: 100%;
  background: var(--c-cta);
  border-color: var(--c-cta);
  color: #fff;
  font-size: var(--slip-body);
  font-weight: 700;
  padding: 12px 16px;
  box-shadow: var(--e1);
}
.recap-btn-primary:hover {
  background: var(--c-cta-hover);
  border-color: var(--c-cta-hover);
  box-shadow: var(--e1-raised);
}
html.motion-ok .recap-btn-primary { transition: background 180ms ease, border-color 180ms ease, transform 160ms ease, box-shadow 160ms ease; }
html.motion-ok .recap-btn-primary:hover { transform: translateY(-1px); }
.recap-btn-primary:active { transform: scale(.985); }
.recap-btn-ghost {
  background: var(--card);
  border-color: var(--c-primary);
  color: var(--c-primary);
}
.recap-btn-ghost:hover {
  background: var(--c-primary);
  color: #fff;
  box-shadow: var(--e1-raised);
}
.recap-foot {
  margin-top: 10px;
  font-size: var(--slip-label);
  line-height: 1.45;
  color: var(--c-muted);
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

@media (max-width: 720px) {
  .recap { padding: 16px 16px 14px; }
  /* One per row on a phone: two explicit labels never share a 358px line.
     The primary is already alone on its row at every width. */
  .recap-acts-sub { flex-direction: column; align-items: stretch; }
}

/* ══════════════════════════════════════════════════════════════════
   VERDICT LAYER ROUND 2 (2026-09-06): the question card, the dial
   meter, the recap shape and the chrome-copy trims. Appended as one
   block. Two type voices (Source Serif 4 for the prompt and the pick,
   JetBrains Mono for every label and number); Inter stays on the
   controls the whole app already sets in Inter. Ink and muted only:
   coral never appears in this block.
   ══════════════════════════════════════════════════════════════════ */

/* ── Question card ─────────────────────────────────────────────────
   One dominant moment per card: the prompt. No icon row above it. */
.turn-question .bubble.qcard {
  /* Round 4: the question card runs on the same four sizes as the slip and
     the recap, declared here because it is a third root surface. Its display
     moment is the prompt, so the card scales --slip-display DOWN to 24px
     rather than introducing a size of its own: a question is a sentence to
     read, not a name to recognise across the page. */
  --slip-display: 24px;
  --slip-lead: 20px;
  --slip-body: 17px;
  --slip-label: 14px;
  padding: 22px 24px 18px;
  max-width: 600px;
}
.turn-question .q-prompt {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--slip-display);
  line-height: 1.2;
  letter-spacing: -.005em;
  color: var(--c-primary);
  text-wrap: balance;
  margin: 0;
}
.turn-question .answer-chips {
  gap: 8px;
  margin-top: 18px;
}
/* Chips stay on the pill tier (--radius-full is the chip token) and gain the
   control elevation, so the question card's primary path reads as raised
   objects rather than outlines drawn on the card. */
.turn-question .answer-chip {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: var(--slip-label);
  line-height: 1.2;
  padding: 9px 16px;
  border: 1px solid var(--c-primary);
  border-radius: var(--radius-full);
  background: var(--card);
  color: var(--c-primary);
  box-shadow: var(--e1);
  transition: background .14s ease, border-color .14s ease, color .14s ease, box-shadow .16s ease;
}
.turn-question .answer-chip:hover,
.turn-question .answer-chip.chip-tapped {
  background: var(--c-primary);
  border-color: var(--c-primary);
  color: #fff;
  box-shadow: var(--e1-raised);
}
.turn-question .answer-chip:active { box-shadow: var(--e1-press); }
.turn-question .answer-chip.is-escape {
  border-color: var(--c-divider);
  color: var(--c-muted);
  font-weight: 500;
  box-shadow: var(--e0);
}
.turn-question .answer-checks { margin-top: 18px; }
.turn-question .inline-answer {
  margin-top: 16px;
  padding-top: 0;
  border-top: 0;
}
.turn-question .inline-answer-or { display: none; }
.turn-question .inline-answer.no-chips { margin-top: 14px; padding-top: 0; border-top: 0; }
.turn-question .inline-answer.no-chips .inline-answer-or { color: var(--c-primary); }
/* The field and its arrow recede so the chips stay the primary path: hairline
   at rest, ink hairline on focus (no halo), ghost arrow that fills on hover. */
.turn-question .inline-answer-row { border-bottom: 1px solid var(--c-divider); align-items: center; }
.turn-question .inline-answer-row:focus-within { border-bottom-color: var(--c-primary); }
.turn-question .inline-answer-input {
  border: 0;
  border-radius: 0;
  background: transparent;
  padding-left: 0;
  padding-right: 0;
  box-shadow: none;
  font-family: var(--font-sans);
  font-size: var(--slip-body);
}
.turn-question .inline-answer-input::placeholder { color: var(--c-muted); }
.turn-question .inline-answer-input:focus { box-shadow: none; }
.turn-question .inline-answer-send svg { display: none; }
.turn-question .inline-answer-send::after { content: "\2192"; font-family: var(--font-mono); font-size: var(--slip-body); }
.turn-question .inline-answer-send {
  background: transparent;
  border: 0;
  border-radius: 0;
  color: var(--c-primary);
  width: 32px;
  height: 32px;
  align-self: center;
}
.turn-question .inline-answer-send {
  border-radius: var(--radius-md);
  transition: background .14s ease, color .14s ease, box-shadow .16s ease;
}
.turn-question .inline-answer-send:hover:not(:disabled) {
  background: var(--c-primary);
  border-color: var(--c-primary);
  color: #fff;
  box-shadow: var(--e1-raised);
}
.turn-question .inline-answer-send:active:not(:disabled) { box-shadow: var(--e1-press); }

/* ── Dial meter ────────────────────────────────────────────────────
   52px ring, 4px stroke: track is the tinted fill, the arc is ink. The
   number sits inside in mono; lead + mono sub to its right. */
.meter-card.dial-card {
  display: flex;
  align-items: center;
  gap: 14px;
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
  margin: 0 0 18px;
  max-width: none;
}
.dial {
  position: relative;
  width: 52px;
  height: 52px;
  flex: none;
}
.dial svg { display: block; transform: rotate(-90deg); }
.dial-track { stroke: #C9D3DC; }
.dial-fill { stroke: var(--c-primary); stroke-linecap: round; }
html.motion-ok .dial-fill { transition: stroke-dashoffset 500ms cubic-bezier(.4, 0, .2, 1); }
.dial-num {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: var(--slip-label);
  font-weight: 600;
  letter-spacing: -.02em;
  color: var(--c-primary);
}
.dial-text { flex: 1; min-width: 0; }
/* ROUND 3: the caption is a sentence ("7 AI Scribe vendors still in the
   running · question 2 of 3"), so it is Inter 13px sentence case. Uppercase
   tracked mono made an eight-word sentence read as a status code. */
.dial-lead {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: var(--slip-label);
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.45;
  color: var(--c-secondary);
}
.dial-sub {
  font-family: var(--font-sans);
  font-size: var(--slip-label);
  letter-spacing: 0;
  text-transform: none;
  color: var(--c-muted);
  margin-top: 3px;
}
.meter-card.is-locked { opacity: .7; }

/* Skip line: one mono link, no sentence around it. */
.meter-skip { margin: 12px 0 0; text-align: right; }
/* "Skip the rest and show my match" is a real escape hatch, so it is a real
   control: Inter 13.5px on the block's ghost-button surface and the same
   three elevation tiers as every other control in this pass. */
.meter-skip-btn {
  appearance: none;
  display: inline-flex;
  align-items: center;
  background: var(--card);
  border: 1px solid var(--c-divider);
  border-radius: var(--radius-md);
  box-shadow: var(--e1);
  padding: 8px 14px;
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--slip-label);
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--c-secondary);
  cursor: pointer;
  text-decoration: none;
  transition: color 160ms ease, border-color 160ms ease, box-shadow 160ms ease;
}
.meter-skip-btn:hover { color: var(--c-primary); border-color: var(--c-primary); box-shadow: var(--e1-raised); }
.meter-skip-btn:active { box-shadow: var(--e1-press); }
.meter-skip-btn:focus-visible { outline: none; box-shadow: var(--ring); }
.meter-skip-btn:disabled { color: var(--c-muted); cursor: default; box-shadow: none; }
.turn-question.is-answered .meter-skip,
.turn-mola.is-stale .meter-skip { display: none; }

/* ── Recap (depth cap), round-2 shape ──────────────────────────────
   headline, pick + fit count, one mono fallback line, still open, exits. */
.recap-pick {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 0 0;
  border-top: 1px solid var(--c-divider);
}
.recap-pick-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--slip-lead);
  line-height: 1.15;
  color: var(--c-primary);
}
/* Round 3: "Still to confirm in a demo" and "Fits 3 of 3" are phrases a buyer
   reads, so Inter sentence case. Round 4 puts them on the one label size, and
   the section label ("Still to confirm in a demo") in the same navy as every
   section label on the slip; the fit count stays muted because it is a
   read-out, not a heading. */
.recap-fit,
.recap-open-label {
  font-family: var(--font-sans);
  font-size: var(--slip-label);
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--c-muted);
}
.recap-open-label { color: var(--c-primary); }
.recap-fallback {
  font-family: var(--font-sans);
  font-size: var(--slip-label);
  line-height: 1.45;
  color: var(--c-secondary);
  margin-top: 6px;
}
.recap-fallback-label { font-weight: 600; color: var(--c-muted); }
/* Round 3: the label is now a five-word phrase ("Still to confirm in a demo"),
   which shredded into three lines inside the old fixed 82px column. It gets
   its own row above the question instead: one label, one sentence, no
   column-width guessing as the copy changes. */
.recap-open {
  display: block;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px dashed var(--c-divider);
}
.recap-open-label { display: block; margin-bottom: 3px; }
.recap-open-text {
  font-family: var(--font-display);
  font-variation-settings: 'opsz' 17;
  font-style: normal;
  font-size: var(--slip-body);
  line-height: 1.45;
  color: var(--c-primary);
}

/* ── Feedback: icons only until the end-of-conversation emphasis ── */
.feedback-prompt[hidden] { display: none; }

/* ── Export row: icon + one word each ────────────────────────────── */
.shortlist-action-quiet {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
.shortlist-action svg { width: 14px; height: 14px; flex-shrink: 0; }

@media (max-width: 720px) {
  .turn-question .bubble.qcard { padding: 18px 16px 14px; }
  .turn-question .bubble.qcard { --slip-display: 21px; }
  .recap-open { grid-template-columns: 1fr; gap: 4px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   ROUND 4 — landing copy trim (2026-09-07, founder brief: "there's a lot of
   text ... fewer words are better"). Copy deck:
   docs/mola-multi-turn/plans/2026-09-06-verdict-layer/landing-copy-deck-round4.md
   Audit: .../landing-funnel-round4.md
   One appended block; nothing above is edited.
   ══════════════════════════════════════════════════════════════════════════ */

/* The duel lede is deleted, so `.duel-head` has a single child and the two-column
   grid that lifted the toggle into the lede's right gutter has nothing to sit
   beside. Left-align it under the H2 instead: the control now precedes the cards
   it governs, which is where a segmented control belongs. `--solo` is a marker
   class so the original `.duel-head` rules stay untouched for any future layout
   that puts copy back in the left column. */
.duel-head--solo { display: block; }
@media (min-width: 901px) {
  .duel-head--solo .duel-toggle { justify-content: flex-start; margin: 4px 0 6px; }
}

/* The toggle hint now carries the live vendor count that the deleted lede used
   to hold, so it can no longer be display:none at narrow widths — that would
   drop a `data-corpus-vendors` spot on phones. Wrap it onto its own line under
   the segmented control instead of hiding it. */
@media (max-width: 640px) {
  .duel-hint { display: block !important; flex-basis: 100%; margin-top: 2px !important; }
}

/* With the parts-list sentence and the duplicated proof microline gone, the
   sticky left column is shorter than the stage it labels. Nudge its sticky
   offset so the remaining two sentences hold the stage's top edge rather than
   floating high above it. */
@media (min-width: 981px) {
  .replay-copy { top: calc(var(--header-h) + 96px); }
}

/* Design-critic round 4: the fold ended on four ideas of equal weight (helper
   line, chips, proof microline, and a bordered "Watch one get answered" pill),
   so it trailed off instead of finishing. The helper line is now empty (see
   index.html) and the scroll cue drops its border, fill and shadow to become
   what it always was semantically, a text link with a direction. It stops being
   a third custom control competing with Ask and the chips for the same glance,
   and the fold now ends on the proof microline plus a quiet cue. Tap target,
   href and hover affordance are unchanged. */
.scroll-cue a {
  border-color: transparent;
  background: transparent;
  box-shadow: none;
  padding: 0 6px;
  text-decoration-line: underline;
  text-decoration-color: transparent;
  text-underline-offset: 4px;
  transition: color var(--dur-hover) var(--ease-io),
              text-decoration-color var(--dur-hover) var(--ease-io);
}
.scroll-cue a:hover {
  box-shadow: none;
  border-color: transparent;
  color: var(--c-primary);
  text-decoration-color: currentColor;
}
.scroll-cue a:focus-visible { box-shadow: var(--ring); }


/* ══════════════════════════════════════════════════════════════════════════
   LANDING COPY CUT, ROUND 5 (2026-09-07, founder-directed)

   The founder's brief was subtraction, not layout: strip the numbered zone
   eyebrows and body lines, cut the "Here's what asking gets you" section to
   "less than 5% of the text", drop the close band's Ask button and its
   microline, and cut everywhere else as far as it goes. Copy deck:
   plans/2026-09-06-verdict-layer/landing-copy-deck-round5.md

   Everything below exists only because a container was sized for copy that is
   no longer in it. Nothing here changes a colour, a control or a behaviour, and
   no rule targets a conversation surface.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── The zone card, two elements instead of four ──────────────────────────
   `min-height: 200px` was set when the card ran index + "Answer on file" +
   name + body line + question row. With the eyebrow and the body line gone the
   same floor opened a ~70px void between the name and the question, so the
   question, which is the payload and the click target, floated away from the
   thing it belongs to. The card now sizes to its content with a smaller floor,
   and `.zone-name` takes the `flex: 1` the deleted `.zone-line` used to hold,
   so the question row still pins to the bottom edge and the six cards in a row
   still line their questions up. */
.zone { min-height: 132px; gap: 12px; }
.zone-name { flex: 1; }

/* ── The replay copy column, an H2 alone ──────────────────────────────────
   Round 4 pushed this sticky column DOWN (+96px) because it had lost two of
   its four lines and was floating above the stage. Round 5 leaves one line, so
   pushing it down is now wrong in the other direction: a single headline
   pinned two thirds of the way down a 700px stage reads as an orphan. It goes
   back to the stage's own top edge and holds it, which is what a sticky label
   for a tall object is for. */
@media (min-width: 981px) {
  .replay-copy { top: calc(var(--header-h) + 78px); }
  .replay-grid { align-items: start; }
}

/* ── The duel head, with no eyebrow above it ──────────────────────────────
   The H2 was carrying `--rv-d:60ms` because the eyebrow revealed at 0ms ahead
   of it. It is now the section's first element, so it reveals first; the
   inline delay is removed in the template and the toggle keeps its own. Same
   for the replay H2. No rule needed, noted so the next reader does not go
   looking for a stagger that is deliberately gone. (The round-5 note here also
   covered the catalog H2 and the `.band-mola-wrap` bottom margin; round 6 cut
   the whole catalog band, so both are gone with it.)

   ── The close band ────────────────────────────────────────────────────────
   Round 5 gave `.close-chips` extra top margin so the chip rail could carry the
   vertical weight the deleted "Ask Mola" button had held. Round 6 removed the
   chips, so that rule is gone too; the band's own padding and the H2/sub gap in
   the main CLOSING BAND block above are now the whole rhythm. */

/* ── Voice input (mic button) ───────────────────────────────────────────────
   Sits between the textarea and Ask. Deliberately QUIET: a circular ghost
   button in mid-ink, not a filled one. Ask is the single primary action in the
   composer (round-4 ruling: "primary action alone on its row"), and coral is
   reserved exclusively for demo-booking conversion — so the mic must not
   compete with either. It only earns colour while it is actually listening.
   Hidden until app.js confirms the browser supports SpeechRecognition, so on
   Firefox this CSS never applies to a visible node. */
/* `display: flex` below would otherwise beat the plain `hidden` attribute the
   template ships with (attribute selectors and element defaults lose to an id
   rule), leaving a dead mic on Firefox where app.js never unhides it. Keep
   this next to the display rule, not in a reset block far away. */
#mic-btn[hidden], #dock-mic-btn[hidden] { display: none !important; }

#mic-btn, #dock-mic-btn {
  position: relative;
  flex-shrink: 0;
  /* Matches #ask-btn's 48px exactly. The control cluster on the right of the
     composer has to read as ONE row of controls, and two heights 8px apart
     read as a mistake rather than a hierarchy. */
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  /* A faint tint and hairline rather than pure transparent: on the white field
     a transparent disc has no edge at all and reads as a hole. Still quiet
     enough that Ask remains the single primary action. */
  background: rgba(47, 74, 102, .05);
  border-color: rgba(47, 74, 102, .16);
  color: var(--c-accent);
  cursor: pointer;
  transition: color 150ms ease, background 150ms ease, border-color 150ms ease;
}
#mic-btn svg, #dock-mic-btn svg {
  /* 20px in a 48px disc. 18px read as undersized and made the disc look like a
     punched hole rather than a button. */
  width: 20px;
  height: 20px;
  position: relative;
  z-index: 1;
}
#mic-btn:hover, #dock-mic-btn:hover {
  color: var(--c-accent);
  background: rgba(47, 74, 102, .07);
}
#mic-btn:focus-visible, #dock-mic-btn:focus-visible {
  outline: none;
  border-color: var(--c-accent);
  box-shadow: var(--ring);
}

/* Listening. The ONLY state that takes real colour, because "is it hearing
   me?" is the one question a buyer has while talking to a text box. */
#mic-btn.is-listening, #dock-mic-btn.is-listening {
  color: #fff;
  background: var(--c-accent);
  border-color: var(--c-accent);
}

/* THE LISTENING SIGNAL.
   "Is it hearing me?" is the only question a buyer has while talking at a text
   box, and a state you cannot read is a state that does not exist. Three cues
   stack so it survives both a glance and reduced motion:
     1. the disc flips to solid ink with a white glyph (colour, always on),
     2. a "Listening" label appears in the composer hint line (words, always on),
     3. the halo below pulses (motion, gated).
   Cue 1 alone is enough to pass, which is what keeps this accessible. */

/* The halo, a second element so the icon itself never scales (a scaling glyph
   reads as a wobble, not a pulse). Motion-gated like every other animation in
   this sheet, a listening mic still shows solid colour with motion off, so
   the state is never conveyed by animation alone. */
.mic-pulse {
  position: absolute;
  inset: 0;
  border-radius: var(--radius-full);
  pointer-events: none;
  opacity: 0;
}
#mic-btn.is-listening .mic-pulse,
#dock-mic-btn.is-listening .mic-pulse {
  box-shadow: 0 0 0 0 rgba(47, 74, 102, .45);
}
html.motion-ok #mic-btn.is-listening .mic-pulse,
html.motion-ok #dock-mic-btn.is-listening .mic-pulse {
  opacity: 1;
  animation: mic-listening 1.8s var(--ease-io) infinite;
}
@keyframes mic-listening {
  0%   { box-shadow: 0 0 0 0   rgba(47, 74, 102, .45); }
  70%  { box-shadow: 0 0 0 9px rgba(47, 74, 102, 0); }
  100% { box-shadow: 0 0 0 0   rgba(47, 74, 102, 0); }
}

/* Phone: the composer is tight (see the 8px-gap block above), so the mic loses
   its padding before the textarea loses width. */
@media (max-width: 560px) {
  /* Tracks the phone composer's own 8px gap. Still a 44px touch target, the
     minimum comfortable tap size, so this is as small as it goes. */
  #mic-btn, #dock-mic-btn { width: 44px; height: 44px; }
  #mic-btn svg, #dock-mic-btn svg { width: 18px; height: 18px; }
}

/* The word cue that accompanies the listening disc. Ink, not muted grey: it is
   a live status, and the composer hint line is otherwise quiet by default. */
.composer-hint.is-listening-hint {
  color: var(--c-accent-ink);
  font-weight: 600;
}

/* Unconfirmed dictation. The engine revises interim words as the buyer keeps
   speaking, so the field reads as provisional while any are on screen: the
   text lightens and the caret-side edge picks up the listening ink. Cleared the
   moment the phrase is committed (or dictation ends). A textarea cannot colour
   part of its own value, which is why this marks the whole field rather than
   the tail. */
#question-input.is-interim, #dock-input.is-interim {
  color: var(--c-muted);
  font-style: italic;
}
