/* Connexus marketing site - standalone dark stylesheet.
 *
 * Deliberately a COPY of the product's design tokens rather than an import of
 * public/sandbox/sandbox.css: this page is served from S3 by CloudFront, not by the Fastify
 * static mount, so it cannot reach /sandbox/sandbox.css without adding a cross-behavior
 * dependency (and coupling a marketing publish to an app deploy). The precedent is
 * sandbox.css itself, which is standalone and does not load /assets/theme.css.
 *
 * Keep the tokens below IDENTICAL to sandbox.css's :root block - the click from here into
 * /sandbox/ must not shift color.
 *
 * =============================================================================================
 * SHARED CSS CONTRACT WITH THE PRODUCT - READ BEFORE RENAMING ANYTHING
 * =============================================================================================
 * The "See it in action" section embeds a scripted call replay (site/marketing/replay.js, ported
 * from public/sandbox/demo.js) rendered in the PRODUCT's real call-stage markup. The class and id
 * names in the "embedded call replay" block near the bottom of this file are copied VERBATIM from
 * public/sandbox/sandbox.css so the two surfaces cannot visually drift:
 *
 *   #call-stage (+ .ended)   .call-topbar   .call-state (+ .dot/.timer/.ended)
 *   .call-summary  .summary-stat  .summary-label  .summary-value
 *   .stage-grid  .stage-panel (+ .interpreter/.speaking)  .avatar  .name-tag  .interp-mark
 *   .dir-pill  .latency-note
 *   .transcript-panel  .live-head  .live-dot  .transcript-lines (+ .scrolled-away)
 *   .t-cols  .t-col-label  .t-turn (+ .current)  .t-cell (+ .you-col/.other-col)  .t-sep
 *   .t-said  .t-said-tag  .t-text  .t-heard  .t-xlation-tag  .t-heard-text  .t-lang  .sr-only
 *
 * RENAMING ANY OF THOSE NOW BREAKS THREE PAGES: this one, /sandbox/ (the live call screen) and
 * /sandbox/demo.html (the animated replay). There is a matching note at the head of the transcript
 * block in public/sandbox/sandbox.css. If you restyle the product's transcript, mirror it here in
 * the same change.
 *
 * RECONCILED against main (docs/marketing-site.md records when), and the reconciliation is NOT "all
 * identical" - measure before you claim it is. Of the shared stage/transcript rules, 51 top-level
 * rules are byte-identical to public/sandbox/sandbox.css, EIGHT differ, and ONE is absent here
 * entirely. None of that is drift; each is listed below. Re-measured after the status strip moved onto
 * the chip's line and after the placeholder removal - if you change anything in this block, RE-RUN the
 * count rather than editing these numbers by hand (they have been stale twice):
 *   - `.call-topbar` - sandbox is `position: sticky` with its own background and top padding. This
 *     page is a SECTION, not a full-viewport call screen, so there is nothing to stick to.
 *   - `.transcript-panel` - a smaller height clamp here (360/46vh/480 vs 420/60vh/620), same reason.
 *   - `.transcript-panel .live-head` - same rendered result, different rule text. Sandbox's copy is
 *     an `h3` picking up margin/weight/uppercase from its own base type and `flex: none` from
 *     `.transcript-panel > h3`; this file has no such base type, so it folds them into one rule.
 *   - `.t-text` and `.t-heard-text` - sandbox carries a `border-radius: 0` reset left from its old
 *     bubble styling. Under `background: none; border: 0` it is a visual no-op, so it is not copied.
 *   - `.call-summary` + `.summary-stat:first-child` - this file drops the strip's card treatment in
 *     the base rule (the strip is a topbar child here), while sandbox.css keeps the card and overrides
 *     it through `.call-topbar .call-summary`; and the first cell's leading hairline is suppressed here
 *     in the base rule where sandbox.css does it at a breakpoint. Same rendered result, different route.
 *   - `.sr-only` - see the note on that rule further down.
 *   - `.t-empty` - NOT COPIED AT ALL here, the only rule in the shared block that is absent rather
 *     than merely different. This page has no empty-state placeholder (removed from all three
 *     surfaces), and unlike the live SPA it has no ended-with-nothing-captured line sharing that node
 *     - verified, 0 occurrences - so the rule had no user left. sandbox.css KEEPS it, because the live
 *     SPA still builds a `.t-empty` for exactly that ended state. Re-add it here if a placeholder ever
 *     returns.
 * `.stage-panel` differed once too (sandbox gained `position: relative` as the containing block for
 * the language cue). That one WAS drift, and it is now mirrored here.
 *
 * The product has also grown stage features this page DELIBERATELY DOES NOT PORT, so their CSS is
 * absent here on purpose:
 *   - the speaking-language cue overlay (.lang-cue*) - a live-call affordance; this page's replay
 *     needs no per-turn language banner (the transcript row tags already carry the language pills);
 *   - the connecting-beat language marquee (.connect-langs/.lang-chip/.connect-count) - dropped with
 *     the whole connecting beat, whose lines duplicated this page's own sections;
 *   - the demo's opt-in SOUND control - a marketing page must not ship autoplay-adjacent audio, and
 *     the audio clips would blow the payload budget and the "self-contained" invariant;
 *   - the demo's RUM telemetry - this page makes ZERO network calls by design, which is why its CSP
 *     needs no `connect-src`.
 * If you port any of them, port its CSS from sandbox.css rather than re-authoring it.
 *
 * DIVERGENCE, DELIBERATE - the mobile interpreter-tile collapse. Both files hide
 * `.stage-panel.interpreter` outright below 900px so You and the Caller sit side by side, and both
 * run `.stage-grid` at `1fr 1fr` there. Two differences, neither of which is drift:
 *   1. SCOPE. sandbox.css must scope its rules to `body[data-page="demo"]`, because that file also
 *      styles the LIVE in-call screen at /sandbox/, which must not change. This file has no such
 *      consumer, so its rules are unscoped.
 *   2. NEIGHBOURING RULES. sandbox.css additionally has to out-specify its own `<= 640px` block
 *      (which restores a 3-column stage, and switches the speaking-language cue from the in-tile
 *      overlay to a reserved full-width band - see that block). This page ports neither, so it needs
 *      neither override.
 * An earlier revision of both used `display: contents` plus selectively-hidden children to keep the
 * in-tile timing caption alive as a full-width row. That is gone, and so is the caption itself at
 * EVERY width - see the desktop 3-up rule below. The plain `display: none` is simpler and honest;
 * do not reintroduce `display: contents` here.
 * (The justification for keeping the caption alive used to be that it was the page's only timing
 * qualifier. It was not - and now there is no on-page timing claim at all, so there is nothing left
 * to preserve. Do NOT re-derive a justification from copy elsewhere on the page without grepping for
 * it first; every such pointer in this file has gone stale at least once.)
 * =============================================================================================
 *
 * SYSTEM FONT STACK ONLY. sandbox.css records why: a strict CSP blocks webfont CDNs, so a
 * leading "Inter" silently fell back anyway - and self-hosting a font here would add binary
 * assets to a page whose whole point is being small and self-contained.
 */

:root {
  --bg: #0f1615; /* PG brand ink #1f3131 darkened; subtly teal, not neutral black */
  --panel: #182221;
  --panel-2: #212d2b;
  --line: #2c3a38;
  --text: #eef2f1;
  --muted: #93a5a1; /* AA on --bg */
  --accent: #2ec4a6; /* bright teal for accents/structure on dark */
  --accent-dim: rgba(46, 196, 166, 0.13);
  --accent-line: rgba(46, 196, 166, 0.42);
  --teal: #006155; /* brand teal - borders/fills */
  --teal-text: #35b39a; /* readable teal for labels/links on dark */
  --lime: #98c441; /* THE action color */
  --lime-hover: #88b338;
  --lime-ink: #1f3131; /* text on lime (AA); never white on lime */
  --radius: 16px;
  --radius-sm: 10px;
  /* LAYOUT MEASURES. The page used to be a single ~640px column, which left large dead margins at
     1440px. Now: a WIDE outer container for structure + visuals, and a NARROW measure applied only
     where prose needs a readable line length. */
  --maxw: 1160px;
  --maxw-narrow: 720px;
  --measure: 66ch; /* prose line length inside a wide container */
  --sticky-h: 68px; /* header height, for scroll-margin-top on anchor targets */
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: auto;
}

/* Anchor-nav smoothness, but only for visitors who have not asked for less motion. */
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  /* One atmospheric layer, same as the product: a faint teal glow from the top. */
  background-image: radial-gradient(90% 55% at 50% -8%, rgba(0, 97, 85, 0.12), transparent 60%);
  background-repeat: no-repeat;
  background-attachment: fixed;
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 15px;
  line-height: 1.5;
}

/* The hidden attribute must win over any class rule that sets `display` (same guard as
   sandbox.css and assets/theme.css). */
[hidden] {
  display: none !important;
}

:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: 2px;
}

img,
svg {
  max-width: 100%;
}

/* Screen-reader-only text (the per-row speaker prefix in the replay transcript). */
/* INTENTIONAL DIVERGENCE from the product's copy of this rule: `clip: rect(0,0,0,0)` is deprecated,
   so this uses the modern `clip-path: inset(50%)`. Visually and behaviourally identical for
   screen-reader-only text. public/sandbox/sandbox.css deliberately keeps the legacy property - it
   works today and it is main's file, so it is not ours to churn. This is one of six knowing
   differences in the shared block, all enumerated at the top of this file - it is NOT the only one,
   so do not "reconcile" the others away on the assumption that they are drift. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- shared containers ----
   Sections are full-bleed; `.wrap` is the inner measure. That is what kills the dead margins:
   backgrounds run edge to edge while content stays measured. */

.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 24px;
}

.wrap-narrow {
  max-width: var(--maxw-narrow);
}

/* ---- header ---- */

header.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  /* Explicit now that `.topbar-nav` is absolutely positioned: the nav's `margin: 0 auto` used to be
     what pushed `.topbar-actions` to the right edge, and taking the nav out of flow removed that
     side effect. Without this the actions collapse leftward against the brand. */
  justify-content: space-between;
  gap: 18px;
  padding: 14px 24px;
  background: rgba(15, 22, 21, 0.92);
  /* Brand accent bar (teal ramp), matching the product topbar. Teal-only: lime stays reserved for
     the action, so the bar never competes with the CTA. */
  border-bottom: 2px solid var(--accent);
  border-image: linear-gradient(90deg, var(--teal), var(--accent)) 1;
}

@supports (backdrop-filter: blur(8px)) {
  header.topbar {
    backdrop-filter: blur(8px);
  }
}

.topbar .brand {
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  color: inherit;
  /* SHRINKABLE ON PURPOSE (was `flex: none`). The brand and the CTA pair were BOTH `flex: none` with
     `white-space: nowrap`, so nothing in this header could give: it fit only when the text happened
     to measure narrower than the viewport, which is font-stack- and OS-dependent. That is the
     structural reason this header kept producing 1-2px overflows at whatever breakpoint was tuned
     last (one label measured 129.297px on macOS vs 130.453px on CI Linux).

     SCOPE OF THE GUARANTEE - it is narrower than it looks, so do not over-trust it: the BRAND can no
     longer CAUSE an overflow at any width or in any font, because it shrinks and ellipsizes instead
     of pushing. A **CTA label** change still can, because `.topbar-actions` stays `flex: none` on
     purpose - the buttons are the page's only actions above the closing band and must never
     compress to unreadability. Verified by mutation: growing the button labels to ~190px each still
     overflows 320-430px. So the rule below stands unchanged - a CTA label change means re-running
     tests/browser/marketing.spec.ts, not eyeballing it.

     The `max-width` rules further down still perform the DESIGNED degradation (`.sub`, then
     `.by-pg`, then the wordmark itself) first, so this shrink only engages if those are not enough;
     with the current labels the wordmark never actually truncates at any width. */
  flex: 0 1 auto;
  min-width: 0;
}

.topbar .brand-mark {
  width: 34px;
  height: 34px;
  border-radius: 8px;
  flex: none;
  display: block;
}

.topbar .brand-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  /* Part of the shrink path above: a flex item will not shrink below its content unless min-width is
     cleared, and the ellipsis needs an overflow context to form in. */
  min-width: 0;
  overflow: hidden;
}

/* The product renders this as its <h1>; here the <h1> belongs to the hero headline, so the
   wordmark is a span styled to look identical. */
.wordmark-line {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
  /* Truncate rather than push the header wide - see the shrink note on `.brand`. This is the LAST
     resort: `.sub`, `.by-pg` and finally the whole wordmark are dropped at their own breakpoints
     first, so in practice an ellipsis appears only if a label grows past what those buy back. */
  overflow: hidden;
  text-overflow: ellipsis;
}

.wordmark-line .wordmark {
  color: var(--accent);
}

.wordmark-line .sub {
  color: var(--muted);
  font-weight: 400;
}

.topbar .by-pg {
  color: #aebbb7;
  font-size: 11px;
  letter-spacing: 0.04em;
}

/* Anchor nav: plain links, no JS, no scroll hijacking. */
.topbar-nav {
  display: flex;
  align-items: center;
  gap: 22px;
  /* TRUE page-centre, matching `.sandbox-nav` in public/sandbox/sandbox.css - and the two MUST
     agree, because the brand + nav are the one piece of chrome shared across the navigation from
     this page into /sandbox/. With `margin: 0 auto` here and absolute centring there, the nav
     visibly JUMPED 10px right on that click (measured: -10px here, 0px there; reported).
     Auto margins centre within the space left over by the siblings, so the offset is a function of
     how wide the brand and the CTA pair happen to be - i.e. it changes whenever a label changes.
     `header.topbar` is `position: sticky`, which is already a containing block, so no
     `position: relative` is needed.

     OUT OF FLOW MEANS NO COLLISION AVOIDANCE - what makes that safe HERE is measured, not the
     breakpoint. Minimum clearance across 1001-1440px is 21px, at the tightest width; below 1000px
     the nav hides. It holds because this header's right-hand cluster is two fixed-width buttons
     that are identical in every state. The sandbox header is NOT like that - its right side gains
     an email plus two or three buttons once signed in, and the same absolute centring painted the
     nav straight through it (see public/sandbox/sandbox.css, which returns the nav to flow when
     `#user-box` is visible). RE-MEASURE if a CTA label grows: 21px is not much, and a longer label
     eats it directly. */
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
}

.topbar-nav a {
  color: var(--muted);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  padding: 4px 0;
  border-bottom: 1px solid transparent;
}

.topbar-nav a:hover {
  color: var(--text);
  border-bottom-color: var(--accent-line);
}

.topbar-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
}

/* CONTACT US reads as a real secondary action, not a ghost.
   The base `.btn` is transparent with a `--line` hairline, which next to the lime primary
   disappeared - and for a page whose ONLY path for an uninvited visitor is this mailto, that is the
   wrong button to make invisible. It now carries the teal accent family: a visible border plus a
   faint wash, so it reads as deliberate and clickable.
   LIME IS STILL THE ONE PRIMARY ACTION COLOR - teal is structure/secondary throughout this page, so
   lifting this button does not create two competing primaries.
   Scoped to the header's non-primary so the closing band's "Watch a demo call" is untouched (that one
   sits beside a lime primary on a quiet band and does not need the lift).
   NOTE: the weight bump changes the label's natural WIDTH, which is a re-measure trigger per the
   note below - swept 320/360/390/420/421/480/481 after this change. */
.topbar-actions .btn:not(.btn-primary) {
  border-color: var(--accent-line);
  background: var(--accent-dim);
  font-weight: 600;
}

.topbar-actions .btn:not(.btn-primary):hover {
  border-color: var(--accent);
  background: rgba(46, 196, 166, 0.2);
}

/* Hold the two header CTAs to ONE width so they read as a matched pair rather than two competing
   sizes.

   EQUAL BY CONSTRUCTION, NOT BY A MEASURED PIXEL VALUE. `grid-auto-columns: 1fr` on a `fit-content`
   track makes both columns the width of the WIDER label, whatever that label is and however the font
   renders it. The earlier approach - `min-width` set just above the wider label's measured width -
   is not portable: the same label measured 129.297px on macOS and 130.453px on CI's Linux, so a
   floor tuned on one platform left the pair 0.45px apart on the other. Chasing that number is
   whack-a-mole against every font stack and every OS font update. Do NOT reintroduce a magic width.

   Applied from 481px UP only. On phones the two must stay at their NATURAL widths: equal widths there
   would size both to the wider label, and 2x130 plus the gap does not fit a 320px header alongside
   the brand - the priority below 481px is that both buttons remain reachable, not that they match. */
@media (min-width: 481px) {
  .topbar-actions {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    width: fit-content;
  }
}

/* Anchor targets sit below the sticky header, and are focusable (tabindex=-1 in the markup) so a
   keyboard user actually lands on them rather than under the header. */
.replay-section,
.split,
.sheet,
[id]:target {
  scroll-margin-top: calc(var(--sticky-h) + 12px);
}

section[tabindex="-1"]:focus {
  outline: none;
}

section[tabindex="-1"]:focus-visible {
  outline: 2px solid var(--lime);
  outline-offset: -4px;
}

/* ---- buttons (same system as sandbox.css) ---- */

.btn {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  height: 48px;
  padding: 0 22px;
  font-size: 15px;
  font-weight: 500;
  font-family: inherit;
  white-space: nowrap;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition:
    border-color 0.12s,
    background 0.12s,
    transform 0.12s,
    box-shadow 0.12s;
}

.btn:hover {
  border-color: var(--teal-text);
}

.btn-primary {
  background: var(--lime);
  border-color: var(--lime);
  color: var(--lime-ink);
  border-radius: 12px;
  font-weight: 600;
}

.btn-primary:hover {
  background: var(--lime-hover);
  border-color: var(--lime-hover);
  transform: translateY(-1px);
  box-shadow: 0 8px 22px rgba(152, 196, 65, 0.28);
}

.btn-primary:active {
  transform: translateY(0);
  box-shadow: none;
}

/* Header- and control-sized variant (the product's .user-box .btn rhythm). */
.btn-sm {
  height: 36px;
  padding: 0 16px;
  font-size: 14px;
}

@media (prefers-reduced-motion: reduce) {
  /* The beat only plays on the animated path (renderStatic never enters .connecting), but kill the
     chip glide too in case that ever changes. */
  .lang-chip {
    transition: none;
  }

  .btn {
    transition: none;
  }

  .btn-primary:hover {
    transform: none;
    box-shadow: none;
  }
}

/* ---- shared type ---- */

/* The uppercase micro-label register (matches the product's .hero-eyebrow). */
.eyebrow,
.hero-eyebrow {
  margin: 0 0 10px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--teal-text);
}

/* Section headings. NOTE: the accent full stop (.dot) is used exactly ONCE on this page - on the
   hero headline. It used to repeat on six section headings, which read as a tic. */
h2 {
  margin: 0;
  font-size: clamp(26px, 3.6vw, 34px);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: #fff;
  text-wrap: balance;
}

/* The accent full stop. Deliberately NOT called `.dot`: that name belongs to the shared product
   contract below (`.call-state .dot` is the pulsing call-status indicator on the embedded replay),
   and reusing it here would collide two unrelated meanings in one stylesheet. */
.accent-stop {
  color: var(--accent);
}

/* Gradient brand accent for one headline phrase (same rule as sandbox.css .hero-accent). */
.hero-accent {
  background: linear-gradient(90deg, var(--accent), var(--lime));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

/* ---- S1 hero: full-bleed, centered ---- */

.hero {
  text-align: center;
  padding: 72px 0 58px;
  position: relative;
  overflow: hidden;
  /* Interior teal glow, same construction as the product's #auth-panel.hero. */
  background: radial-gradient(90% 100% at 50% -25%, rgba(46, 196, 166, 0.11), transparent 62%);
}

.hero h1 {
  font-size: clamp(34px, 6vw, 58px);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.08;
  margin: 0 0 16px;
  text-wrap: balance;
}

.hero-sub {
  margin: 0 auto;
  max-width: 44ch;
  color: var(--muted);
  font-size: clamp(16px, 1.8vw, 18px);
  line-height: 1.6;
}

/* Three proof chips: true product facts only. */
.hero-points {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin: 28px 0 0;
  padding: 0;
}

/* Icon-over-label chips, matching the sandbox's signed-out hero (public/sandbox/sandbox.css
   `.hero-points li` / `.hero-points svg`) so the two front doors look like one product. `flex: 1`
   rather than a fixed basis, so the three share the row in equal thirds as they do there; the
   inherited `flex-wrap` + `justify-content: center` on the container stay, which is what keeps the
   row from overflowing before the phone breakpoint takes over. */
.hero-points li {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 14px 10px 12px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  line-height: 1.35;
}

.hero-points svg {
  flex: none;
  width: 20px;
  height: 20px;
  stroke: var(--accent);
  fill: none;
}

/* The hero's one action: an in-page anchor to the replay (#see-it), NOT a door. Deliberately its
   own class - `.hero .hero-actions` (the removed CTA ROW with the /sandbox/ + demo doors) is
   pinned to count 0 in tests/browser/marketing.spec.ts, and this must never match that guard.
   Outline `.btn`, not lime: the primary color stays reserved for the Sandbox Login door. */
.hero-cue {
  margin-top: 26px;
}

/* The closing band's contact path - see the markup note. A BUTTON, nothing else: an uninvited
   visitor cannot use either CTA above it, so their route out needs the same affordance those have.
   The lead-in sentence and the visible address that used to sit around it were removed by CTO
   direction 2026-08-01 (tradeoff recorded in the markup comment). */
.closing-contact-btn {
  /* Owns its own top margin now that the removed <p> above it is no longer providing one. */
  margin-top: 20px;
  border-color: var(--accent-line);
  background: var(--accent-dim);
  font-weight: 600;
}

.closing-contact-btn:hover:not(:disabled) {
  border-color: var(--accent);
  background: rgba(46, 196, 166, 0.2);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
  margin-top: 28px;
}

/* The closing band's two CTAs, same rule and same reasoning as the header pair above: equal BY
   CONSTRUCTION via `grid-auto-columns: 1fr`, never a measured pixel floor (see that note - the same
   label differs ~1.2px between macOS and CI Linux).
   Applied from 721px UP only: at `<= 720px` the buttons already take `flex: 1 1 100%` and stack
   full-width, so they are equal there by a different mechanism. */
@media (min-width: 721px) {
  .hero-actions {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    width: fit-content;
    margin-inline: auto;
  }
}


@media (prefers-reduced-motion: no-preference) {
  @keyframes hero-rise {
    from {
      opacity: 0;
      transform: translateY(12px);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }

  .hero .wrap {
    animation: hero-rise 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
  }
}

/* ---- connecting beat: the language marquee ----
   PORTED FROM public/sandbox/demo.css. It occupies the transcript pane's own slot while
   `#call-stage.connecting` is set (replay.js adds it), and stands the pane's normal children down -
   so the pane keeps its clamped height throughout and the hand-off to the replay is a swap INSIDE a
   fixed box, not a resize. That is what keeps the answer-time shift at 0px, which is the boundary
   this page has been bitten at repeatedly. */

#connect-teaser {
  display: none;
}

#call-stage.connecting #connect-teaser {
  display: flex;
  flex: 1;
  min-height: 0;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 18px;
  text-align: center;
}

/* HIDE the languages strip during the beat.
   The beat cycles the interpreter pill through EN<->ZH, EN<->AR, ... while the topbar strip reads
   "LANGUAGES English <-> Spanish" - two elements on screen disagreeing about what the call is, which
   a reviewer caught in a mid-beat screenshot (pill "EN <-> EL" next to strip "English <-> Spanish").
   `visibility` rather than `display`, so the strip keeps its space and the topbar does not reflow at
   the answer - this page has been bitten by layout shifts at exactly that boundary. The pair then
   APPEARS as the call answers, which is a better beat than it being there all along. */
#call-stage.connecting .call-summary {
  visibility: hidden;
}

#call-stage.connecting .t-cols,
#call-stage.connecting #transcript-lines {
  display: none;
}

.connect-eyebrow {
  margin: 0;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--lime);
}

.connect-langs {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 16px;
  justify-content: center;
  max-width: 42ch;
}

/* Dim by default; the .on pair (moved across the row by replay.js) is full-strength. The transition
   makes the highlight GLIDE from pair to pair rather than snap - the "scroll" feel. */
.lang-chip {
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--muted);
  opacity: 0.4;
  transition:
    color 0.3s ease,
    opacity 0.3s ease;
}

.lang-chip.on {
  color: var(--text);
  opacity: 1;
}

.connect-count {
  margin: 4px 0 0;
  font-size: 14px;
  color: var(--muted);
}

/* ---- S3 the replay section ---- */

.replay-section {
  padding: 64px 0 58px;
  /* The section separator. It belongs on THIS rule because the tinted credibility strip that used
     to sit between the hero and this section carried `border-top`/`border-bottom`, and its bottom
     edge was what delimited the hero from this section - so deleting it left hero -> replay as the ONLY adjacent pair on the page with no
     hairline between them, while `.split`, `.statement`, `.sheet` and `.closing` all declare one.
     This restores the pattern rather than inventing a treatment. `.hero` still has none, correctly:
     it is the first section and sits under the header's own bottom edge. */
  border-top: 1px solid var(--line);
}

.replay-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px 28px;
  flex-wrap: wrap;
  margin-bottom: 22px;
}

/* The simulated-conversation caption. REQUIRED while the conversation is on screen (honesty
   contract) - never hide it, fade it, or shrink it out of legibility. */
.replay-caption {
  margin: 0;
  color: var(--muted);
  font-size: 13px;
  max-width: 42ch;
}

/* ---- S4 / S6 the two-column split (visual | copy, and its mirror) ---- */

.split {
  padding: 62px 0;
  border-top: 1px solid var(--line);
}

.split-inner {
  display: grid;
  /* Asymmetric on purpose: the visual gets the smaller share, the copy the readable one. */
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  align-items: center;
  gap: 44px;
}

/* The MIRROR variant (copy left, spec rows right) top-aligns instead: against a six-row list,
   vertically centering the short copy block leaves it adrift in the middle of the section. */
.split-mirror .split-inner {
  align-items: start;
}

.split-copy h2 {
  margin-bottom: 10px;
}

.split-lead {
  margin: 0;
  color: var(--muted);
  font-size: 15.5px;
  line-height: 1.65;
  max-width: 40ch;
  /* WIDOW CONTROL. This lead is capped at 40ch, so its ~60 characters MUST wrap - the goal is a
     balanced two-line block, not one line. `balance` evens the lines; `pretty` (which only backs
     words off the LAST line) measurably did not clear it - it moved "need it." to "you need it."
     and left a two-word last line at 1440, 1280 and 1100.
     Use `balance` for SHORT blocks that will wrap anyway, and `pretty` for long prose - see
     .statement-body. Both are PROGRESSIVE ENHANCEMENTS: an engine without support ignores the
     declaration and wraps exactly as it does today, so there is no fallback to write, and this file
     already relies on `balance` for its headings.
     Deliberately NOT applied to `.hero h1`: that headline is a hand-authored two-line construction
     with an explicit <br /> and the gradient on line 2 - it must not be re-wrapped. (It already
     carries `balance` from before this pass; that is left exactly as it was.) */
  text-wrap: balance;
}

/* The inline call-flow diagram. Hand-authored SVG, no raster, no external asset. */
.split-visual {
  display: flex;
  justify-content: center;
}

.flow {
  width: 100%;
  max-width: 380px;
  height: auto;
}

.flow-node {
  fill: var(--panel-2);
  stroke: var(--line);
  stroke-width: 1.5;
}

.flow-core {
  fill: #131a19;
  stroke: var(--accent-line);
  stroke-width: 1.5;
}

.flow-arc {
  fill: none;
  stroke: var(--accent-line);
  stroke-width: 1.6;
  stroke-dasharray: 4 5;
}

.flow-tip {
  fill: var(--accent);
}

.flow-label {
  fill: var(--teal-text);
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
}

.flow-node-label {
  fill: var(--text);
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
}

.flow-core-label {
  fill: var(--accent);
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
}

/* Steps: a numbered list with real presence (the old version was three cramped small cards). */
.steps {
  list-style: none;
  counter-reset: step;
  margin: 26px 0 0;
  padding: 0;
}

.steps li {
  counter-increment: step;
  position: relative;
  padding: 18px 0 18px 52px;
  border-top: 1px solid var(--line);
}

.steps li:first-child {
  border-top: 0;
  padding-top: 4px;
}

.steps li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 9px;
  background: var(--accent-dim);
  border: 1px solid var(--accent-line);
  color: var(--accent);
  font-size: 14px;
  font-weight: 600;
}

.steps li:first-child::before {
  top: 2px;
}

.steps h3 {
  margin: 0 0 5px;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
}

.steps p {
  margin: 0;
  color: var(--muted);
  font-size: 15px;
  line-height: 1.6;
  max-width: 46ch;
}

/* ---- S5 the human off-ramp statement: full-width, centered, glowing ---- */

.statement {
  text-align: center;
  padding: 68px 0;
  border-top: 1px solid var(--line);
  background: radial-gradient(70% 130% at 50% 0%, rgba(46, 196, 166, 0.14), transparent 64%);
}

.statement-lead {
  margin: 0 auto 16px;
  max-width: 24ch;
  font-size: clamp(27px, 4.4vw, 40px);
  font-weight: 600;
  line-height: 1.18;
  letter-spacing: -0.025em;
  color: #fff;
  text-wrap: balance;
}

.statement-body {
  margin: 0 auto;
  max-width: var(--measure);
  color: var(--muted);
  font-size: clamp(15px, 1.7vw, 17.5px);
  line-height: 1.7;
  /* Long prose: `pretty` (avoid a short last line), NOT `balance` - balancing a long paragraph
     shortens every line. See the note on .split-lead. */
  text-wrap: pretty;
}

/* ---- spec-sheet rows (term left, description right; hairline rules, no tiles) ---- */

.spec-rows {
  margin: 0;
  padding: 0;
  list-style: none;
}

.spec-row {
  display: grid;
  grid-template-columns: 150px 1fr;
  gap: 20px;
  padding: 13px 2px;
  border-top: 1px solid var(--line);
}

.spec-row:first-child {
  border-top: 0;
  padding-top: 0;
}

.spec-row dt {
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.01em;
}

.spec-row dd {
  margin: 0;
  color: var(--muted);
  font-size: 14.5px;
  line-height: 1.6;
  /* WIDOW CONTROL, `balance` not `pretty` - see the note on .split-lead for why. These row texts are
     short enough that when they wrap it is to two lines, which is the case `balance` is for. Covers
     BOTH labelled-row sections (Integrations + Security), so a new row is protected the day it ships
     instead of needing its copy tuned - which is the point of fixing the class, not the string.
     This is what cleared the reported "language." and "need it." orphans WITHOUT editing any
     grounded copy - which matters, because several of these lines are grounded claims and a
     typography pass must not reword them. */
  text-wrap: balance;
}

/* ---- S7 security: the same LABELED rows as S6, but laid out THREE-UP - one row of three - so
        the section's composition differs from its neighbour's single right-hand column. It was
        2-up while the section held four rows; with three, a 2-up grid left the third sitting
        alone under an uneven first row. If the section ever grows a (grounded) fourth row,
        2-up becomes the right shape again. ---- */

.sheet {
  padding: 62px 0;
  border-top: 1px solid var(--line);
}

.sheet h2 {
  margin-bottom: 26px;
}

.spec-rows-3up {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: 36px;
}

/* In a 3-up grid, `:first-child` alone would leave the other columns' top row with a rule the
   first column's does not have, so the borderless treatment covers the whole top row. */
.spec-rows-3up .spec-row:nth-child(-n + 3) {
  border-top: 0;
  padding-top: 0;
}

/* ---- S8 closing CTA ---- */

.closing {
  text-align: center;
  padding: 64px 0 70px;
  border-top: 1px solid var(--line);
}

.closing p {
  margin: 14px auto 0;
  max-width: 50ch;
  color: var(--muted);
  font-size: 15.5px;
  /* See the widow note on .split-lead. */
  text-wrap: pretty;
}

/* ---- footer ---- */

.site-footer {
  border-top: 1px solid var(--line);
  padding: 30px 0 40px;
  text-align: center;
}

.footer-line {
  margin: 0 0 10px;
  color: var(--muted);
  font-size: 13px;
}

.footer-legal {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 8px 18px;
  margin: 0;
  color: var(--muted);
  font-size: 13px;
}

.footer-legal a {
  color: var(--teal-text);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}

.footer-legal a:hover {
  color: var(--accent);
  border-bottom-color: var(--accent-line);
}

/* =============================================================================================
   EMBEDDED CALL REPLAY - ported VERBATIM from public/sandbox/sandbox.css (see the shared-contract
   note at the top of this file). Only two deliberate deviations, both because this is a SECTION on
   a landing page rather than a full-viewport call screen:
     1. .call-topbar is NOT sticky here (the page has its own sticky header; a second sticky bar
        inside a section would pin over the page chrome);
     2. .transcript-panel height is a smaller clamp, so the stage fits inside a scrollable section.
   Everything else - the lane grid, the tags, the pills, the active-speaker highlight, the ended
   power-down - is the product's own CSS.
   ============================================================================================= */

#call-stage {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.call-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 0 0 8px;
  border-bottom: 1px solid var(--line);
}

/* The single source of call state on the stage: pulsing dot + label + timer. */
.call-state {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  font-weight: 600;
  white-space: nowrap;
  padding: 6px 16px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 999px;
}

.call-state .dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent);
  animation: pulse 1.6s ease-in-out infinite;
}

.call-state.ended {
  color: var(--muted);
}

.call-state.ended .dot {
  background: var(--muted);
  animation: none;
}

.call-state .timer {
  color: var(--muted);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}

@keyframes pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.35;
  }
}

@keyframes pulse-ring {
  0% {
    box-shadow: 0 0 0 0 rgba(46, 196, 166, 0.45);
  }
  70% {
    box-shadow: 0 0 0 7px rgba(46, 196, 166, 0);
  }
  100% {
    box-shadow: 0 0 0 0 rgba(46, 196, 166, 0);
  }
}

.call-topbar .actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* Live status strip: turns | languages, ON THE STATE CHIP'S LINE. It used to be a bordered card on
   its own row below; Duration went with the move because the chip's timer already shows it (see the
   markup comment in index.html).
   It is now a flex CHILD of `.call-topbar`, so it drops the card treatment entirely - a bordered
   panel nested inside the topbar row read as a second surface crammed onto the line. What remains is
   the hairline dividers between cells, which is what makes it scan as
   `chip | TURNS 8 | LANGUAGES English <-> Spanish`. `min-width: 0` lets it shrink before the Replay
   button does; `flex-wrap` is what handles phone width (see the <= 720px block). */
.call-summary {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  min-width: 0;
  /* Aligns the cell text with the chip's label rather than sitting proud of it. */
  padding: 2px 0;
  justify-content: center;
  flex: 0 0 auto;
}

/* TRUE CENTRING for the three-part bar: chip left, strip centred, action right.
   GRID, not flex, and `:has()`-gated - both parts are load-bearing.
     - Grid because with `1fr auto 1fr` the middle column's centre IS the row's centre, whatever the
       flanks contain. The strip therefore does NOT move as the chip's timer widens (00:07 -> 12:47)
       or as its label changes (Connecting -> Call in progress -> Call ended). Flex cannot do this:
       `justify-content` only distributes leftover space, so the middle child slides as the left
       flank grows - measured drift, not theory. Giving the FLANKS `flex: 1` does centre it, but it
       also stretches `.call-state`, which IS the visible pill, into a wide empty rounded box (seen
       on the first render of this change).
     - `:has(> .call-summary)` because grid columns are POSITIONAL. A `.call-topbar` that has not
       moved its strip in has only two children, and under a three-column template its action row
       would land in the middle column. Gating on the strip actually being a direct child means such
       a surface keeps today's flex layout untouched, so this rule cannot break a topbar it was not
       written for. Where `:has()` is unsupported the whole rule is dropped and the bar stays flex -
       today's behaviour - so the fallback is the status quo, not a broken layout.
   The phone breakpoint deliberately reverts to flex so the strip can take a row of its own. */
.call-topbar:has(> .call-summary) {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
}

.call-topbar > .call-state {
  justify-self: start;
}

.call-topbar > .call-summary {
  justify-self: center;
}

.call-topbar > .actions {
  justify-self: end;
}

.summary-stat {
  display: flex;
  align-items: baseline;
  gap: 7px;
  padding: 2px 16px;
  border-left: 1px solid var(--line);
}

/* On the chip's line the FIRST cell keeps its divider - it is what separates the stats from the
   chip. (It used to be suppressed because the strip was a standalone card whose first cell had
   nothing to its left.) */
.summary-stat:first-child {
  /* The left hairline is a SEPARATOR, so the first cell must not have one - otherwise it reads as a
     stray rule floating before the first label. That was masked while the strip was a bordered card
     with several cells; on the chip's line, with the strip down to one or two cells, it is obvious.
     (A phone rule further down already did this; doing it here makes it true at every width.) */
  border-left: 0;
  padding-left: 14px;
}

.summary-stat:has(> .summary-value:empty) {
  display: none;
}

.summary-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.summary-value {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* Three participants: You | AI Interpreter (hero) | Caller. */
.stage-grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  align-items: stretch;
  gap: 12px;
}

.stage-panel {
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  /* MIRRORED from the product: containing block for the in-tile
     speaking-language cue (.lang-cue-tile), an absolutely positioned bottom overlay. This page does
     not render that cue (see the omissions list in the contract note above), but the rule is copied
     verbatim so the two stylesheets stay diff-clean and a future port of the cue works unchanged.
     Deliberately NO overflow:hidden - it would clip the active-speaker avatar's pulse-ring shadow. */
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Top-aligned in the BASE rule, byte-identical to sandbox.css. The desktop 3-up row centres
     instead - see the `min-width: 901px` rule below - and it is expressed as a desktop-only override
     in both files so the collapsed (<=900px) layout is provably untouched. */
  justify-content: flex-start;
  gap: 8px;
  padding: 12px 14px;
  min-height: 104px;
  /* So the active-speaker glow travels turn-by-turn instead of snapping between panels. */
  transition:
    border-color 0.25s ease,
    box-shadow 0.25s ease,
    background-color 0.25s ease;
}

/* DESKTOP 3-UP ROW: centre all three tiles and let the row be shorter.
   Desktop-only (`min-width: 901px`) on purpose - below 900px the interpreter tile is hidden and the
   remaining two lay out as a ROW, where `justify-content` would control HORIZONTAL alignment and
   centring would shove the avatar off the left edge. Scoping it here means the collapsed layout is
   untouched by construction rather than by relying on a later media query to reset it.
   `min-height` drops with it: the row's height is content-driven (the interpreter hero was the
   tallest tile and lost its `.latency-note`), so without this the old floor would hold the shorter
   content at the old height. Centring is what removes the ~52px of dead space that used to sit
   under You / Them when the taller interpreter tile stretched the grid.
   The matching rule in public/sandbox/sandbox.css is scoped `body[data-page="demo"]` as well as to
   this breakpoint, because THAT file also styles the live call screen, which keeps its taller
   top-aligned tiles and its latency line. */
@media (min-width: 901px) {
  .stage-panel {
    justify-content: center;
    min-height: 84px;
  }
}

.stage-panel .avatar {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  border: 1px solid var(--accent-line);
  background: radial-gradient(circle at 50% 36%, rgba(46, 196, 166, 0.22), var(--panel) 70%);
  display: flex;
  align-items: center;
  justify-content: center;
}

.stage-panel .avatar svg {
  width: 24px;
  height: 24px;
  fill: none;
  stroke: #cdeee6;
  stroke-width: 2;
  stroke-linecap: round;
}

.name-tag {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: 0;
  text-align: center;
}

/* The interpreter is the product - a teal-lit hero card between the two participants. */
.stage-panel.interpreter {
  justify-content: center;
  background:
    radial-gradient(120% 80% at 50% -20%, rgba(46, 196, 166, 0.12), transparent 60%),
    linear-gradient(180deg, #18211f, #131a19);
  border-color: var(--accent-line);
  box-shadow: 0 0 50px rgba(0, 97, 85, 0.18);
  gap: 8px;
}

.interp-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 13px;
  background: var(--accent-dim);
  border: 1px solid var(--accent-line);
}

.interp-mark svg {
  width: 24px;
  height: 24px;
  stroke: var(--accent);
  fill: none;
  stroke-width: 2.3;
}

.stage-panel.interpreter h3 {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
  text-transform: none;
  letter-spacing: 0;
}

/* Static language-direction label (NOT interactive). */
.dir-pill {
  padding: 2px 0;
  color: var(--teal-text);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.05em;
  font-variant-numeric: tabular-nums;
}

/* The product's own latency line. This page NEVER overwrites it with a measured number. */
.latency-note {
  color: var(--muted);
  font-size: 12.5px;
  text-align: center;
}

/* Active-speaker highlight. Every rule is scoped `:not(.interpreter)` so the middle AI Interpreter
   panel NEVER lights - the contract is enforced here in CSS, not only in the script. */
.stage-panel:not(.interpreter).speaking {
  border-color: var(--accent);
  background-color: color-mix(in srgb, var(--panel-2) 80%, var(--accent));
  box-shadow:
    0 0 0 2px var(--accent),
    0 0 56px rgba(46, 196, 166, 0.45);
}

.stage-panel:not(.interpreter).speaking .avatar {
  border-color: var(--accent);
  animation: pulse-ring 1.8s ease-out infinite;
}

.stage-panel:not(.interpreter).speaking .name-tag {
  color: var(--text);
}

/* ---- transcript ---- */

.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.live-head {
  display: flex;
  align-items: center;
  gap: 8px;
}

.transcript-panel .live-head {
  margin: 0;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--teal-text);
  flex: none;
}

.live-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  animation: pulse 1.6s ease-in-out infinite;
}

.transcript-panel {
  position: relative;
  overflow: hidden;
  /* DEVIATION from the product's clamp(420px, 62vh, 640px): this is a section on a scrolling page,
     not a full-height call screen.
     Shortened again 2026-08-01 (was 360/46vh/480). MEASURE ROWS, NOT PIXELS, before going lower -
     the same rule the product's clamp carries. Measured with the real replay, 46px rows:
       360/46vh/480 (old) -> 4 fully-visible rows at 1440x900 AND 1280x800
       300/38vh/400 (now) -> 3 rows at both
       280/34vh/360       -> 3 at 1440x900 but only 2 at 1280x800
     So this is the smallest clamp that still shows THREE turns on both common desktop sizes; the
     next step down starts costing a row on the smaller one. The pane scrolls as the replay advances,
     so fewer rows means more scrolling, never lost content. */
  height: clamp(300px, 38vh, 400px);
  padding: 20px 22px;
  display: flex;
  flex-direction: column;
  background:
    radial-gradient(120% 55% at 50% -20%, rgba(46, 196, 166, 0.07), transparent 55%), var(--panel);
}

/* The transcript card's brand ramp (same top edge as the product's). */
.transcript-panel::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--teal), var(--accent) 55%, var(--lime));
}

.transcript-lines {
  display: flex;
  flex-direction: column;
  gap: 12px;
  overflow-y: auto;
  flex: 1;
  min-height: 0;
  padding-top: 18px;
  padding-right: 14px;
  mask-image: linear-gradient(to bottom, transparent 0, #000 16px);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 16px);
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

/* Rows exiting through the pane's TOP edge fade rather than being sliced mid-line. Toggled by the
   pane's own scroll handler only while scrollTop > 0-ish, so the resting first row never fades. */
.transcript-lines.scrolled-away {
  mask-image: linear-gradient(180deg, transparent 0, #000 26px);
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 26px);
}

.transcript-lines::-webkit-scrollbar {
  width: 8px;
}

.transcript-lines::-webkit-scrollbar-thumb {
  background: var(--line);
  border-radius: 4px;
}

.transcript-lines::-webkit-scrollbar-track {
  background: transparent;
}

/* Two-lane headers, mirroring the row grid so each label sits over its lane start. Decorative -
   the per-row sr-only speaker prefix carries the a11y attribution. */
.t-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  margin-top: 12px;
  padding: 0 0 8px;
  border-bottom: 1px solid var(--line);
}

.t-col-label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.t-col-label.other {
  color: var(--teal-text);
  padding-left: 16px;
}

/* One transcript TURN = one full-width ROW split down the middle: the speaker's column carries what
   they SAID and the listener's carries what they HEARD, on the SAME row. Cells are placed by column
   class (not DOM order), so screen readers still get said before heard. */
.t-turn {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 0;
  align-self: stretch;
  min-width: 0;
}

.t-cell {
  grid-row: 1;
  min-width: 0;
}

.t-cell.you-col {
  grid-column: 1;
  padding-right: 16px;
}

/* The other party's column carries the center divider, so every row reads as two aligned lanes. */
.t-cell.other-col {
  grid-column: 2;
  border-left: 1px solid var(--line);
  padding-left: 16px;
}

/* Faint full-width rule between turns (never before the first). Decorative + aria-hidden. */
.t-sep {
  align-self: stretch;
  flex: none;
  height: 1px;
  background: var(--line);
}

.t-said,
.t-heard {
  display: flex;
  flex-direction: column;
  gap: 3px;
  align-items: flex-start;
}

.t-said-tag,
.t-xlation-tag {
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--muted);
}

/* The language name as a scannable pill: neutral in a "said" tag, accent-tinted in a "heard" tag so
   the translation direction reads at a glance. */
.t-lang {
  display: inline-block;
  vertical-align: baseline;
  margin-left: 2px;
  padding: 1px 8px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--muted);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
}

.t-xlation-tag {
  color: var(--teal-text);
}

.t-xlation-tag .t-lang {
  border-color: var(--accent-line);
  background: var(--accent-dim);
  color: var(--teal-text);
}

/* The arrow points FROM the speaker's column INTO the listener's: a caller row flows left -> right,
   a partner row right -> left. */
.t-xlation-tag::before {
  content: "\2192  ";
}

.t-turn.partner .t-xlation-tag::before {
  content: "\2190  ";
}

/* The said (native-language) line is the PRIMARY read: unboxed, brightest, boldest. */
.t-text {
  background: none;
  border: 0;
  padding: 2px 0 0;
  font-size: 0.9rem;
  line-height: 1.45;
  color: #fff;
  font-weight: 500;
  display: inline-block;
  overflow-wrap: anywhere;
}

/* The heard (translated) line is the subordinate gloss: unboxed, muted, slightly smaller. */
.t-heard-text {
  background: none;
  border: 0;
  padding: 2px 0 0;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--muted);
  display: inline-block;
  overflow-wrap: anywhere;
}

/* The current (in-flight) turn while the call is live: a soft accent glow on its said line. */
#call-stage:not(.ended) .t-turn.current .t-text {
  text-shadow: 0 0 14px rgba(46, 196, 166, 0.3);
}

/* Ended: the console powers down - gray the live dot, dim the participants + badge - so a finished
   call never looks live (a truth cue on a trust surface). */
#call-stage.ended .live-dot {
  background: var(--muted);
  animation: none;
}

#call-stage.ended .avatar,
#call-stage.ended .interp-mark,
#call-stage.ended .dir-pill {
  opacity: 0.5;
}

#call-stage.ended .transcript-panel::before {
  opacity: 0.45;
}

/* Respect reduced-motion for all the stage's live animation (the script also renders the whole
   conversation statically on that path - this is belt-and-braces). */
@media (prefers-reduced-motion: reduce) {
  .call-state .dot,
  .live-dot {
    animation: none;
  }

  .stage-panel {
    transition: none;
  }

  .stage-panel:not(.interpreter).speaking .avatar {
    animation: none;
    box-shadow: 0 0 0 3px rgba(46, 196, 166, 0.4);
  }
}

/* ---- responsive ---- */

@media (max-width: 1000px) {
  .topbar-nav {
    display: none;
  }

  .topbar-actions {
    margin-left: auto;
  }
}

@media (max-width: 900px) {
  .split-inner {
    grid-template-columns: 1fr;
    gap: 30px;
  }

  /* Single column: THE WORDS LEAD on a narrow screen and everything else follows.
     Written as a SAFE DEFAULT, not an enumeration, and that is the point. It used to name the
     two known columns (`.split-visual: 2` / `.split-copy: 1`), which silently broke the moment a
     section shipped a right column that was neither: `#integrations` uses
     `.spec-rows.split-rows`, so it matched neither rule, kept the initial `order: 0`, and sorted
     AHEAD of the heading - a phone visitor read six labelled integration rows BEFORE the
     "Works with what you have" heading that explains them. Desktop was fine (2-col grid), so it
     only showed up stacked. Fixed by inverting the rule: the wildcard catches EVERY column and
     the more specific selector lifts the copy column above it, so any column type added later
     lands after its heading automatically instead of jumping the queue.
     `#how` (`.split-visual`) is byte-identical under this: it was 1-then-2, it is still 1-then-2. */
  .split .split-inner > * {
    order: 2;
  }

  .split .split-inner > .split-copy {
    order: 1;
  }

  .spec-rows-3up {
    grid-template-columns: 1fr;
    column-gap: 0;
  }

  .spec-rows-3up .spec-row:nth-child(-n + 3) {
    border-top: 1px solid var(--line);
    padding-top: 13px;
  }

  .spec-rows-3up .spec-row:first-child {
    border-top: 0;
    padding-top: 0;
  }

  /* MOBILE STAGE: You and Caller SIT SIDE BY SIDE, and the AI Interpreter tile is
     GONE - not restyled, hidden outright.
     Below the 3-column desktop stage this used to be a single column, so the tile sat as a full-width
     block BETWEEN the two people and pushed them apart: a lot of vertical space for a card that
     carries nothing unique at this width:
       - "EN <-> ES" duplicates the status strip's "Languages English <-> Spanish" directly above it;
       - the timing caption is gone from this page at EVERY width now (and so is the strip's "0.8s"
         readout), so hiding the tile here drops nothing that any other width still shows. What keeps
         that honest is that the page states NO latency figure anywhere - not that the claim is
         repeated somewhere else. An earlier revision preserved the caption as a full-width row via
         `display: contents` + selectively-hidden children; that is gone with the caption.
     VERIFIED the strip is neither hidden nor truncated at these widths (`.call-summary` wraps; at
     <= 720 it left-aligns and tightens padding) - the redundancy claim rests on that check.
     Contracts, all still intact: the never-lights rule `.stage-panel:not(.interpreter).speaking`
     is unaffected (this element is simply not rendered, so it can never be lit); the base
     `.stage-panel { position: relative }` containing block for the product's `.lang-cue-tile` is
     untouched, and that cue is only ever mounted in the You/Caller panels anyway.
     NOTE `demo.html` CANNOT use `display: none` here - see the divergence note at the top of this
     file. Its `#dir-pill` is a live marquee hub, so it collapses with `display: contents` instead.
     Applied at the SAME 900px breakpoint where the 3-column stage collapses, not at the narrower
     720/560 phone breakpoints: the tile only starts costing vertical space once the grid stops being
     3-up, so fixing it at 720 would leave 721-900px with exactly the reported layout. Desktop
     (> 900px) is untouched - the tile and its caption render there as before. */
  .stage-grid {
    grid-template-columns: 1fr 1fr;
  }

  .stage-panel {
    min-height: 0;
    flex-direction: row;
    justify-content: flex-start;
    gap: 12px;
  }

  .stage-panel.interpreter {
    display: none;
  }
}

@media (max-width: 720px) {
  .wrap {
    padding: 0 16px;
  }

  .hero {
    padding: 48px 0 42px;
  }

  .replay-section,
  .split,
  .sheet,
  .closing {
    padding: 44px 0;
  }

  .statement {
    padding: 48px 0;
  }

  /* Spec rows stack: term over description. */
  .spec-row {
    grid-template-columns: 1fr;
    gap: 3px;
  }

  .hero-actions .btn {
    flex: 1 1 100%;
  }

  /* PHONE CHIPS: one wrapping row of compact inline pills - icon BESIDE the label - instead of
     three stacked full-width cards. Stacked, the chips read as three tappable buttons (they are
     not interactive) and pushed the replay a full viewport down; as pills they stay proof points.
     A DELIBERATE phone-width divergence from the sandbox signed-out hero, which still stacks its
     chips - the desktop treatment stays identical (see the note on `.hero-points li` above). */
  .hero-points li {
    flex: 0 1 auto;
    flex-direction: row;
    gap: 7px;
    padding: 8px 13px;
    border-radius: 999px;
    font-size: 12.5px;
  }

  .hero-points svg {
    width: 15px;
    height: 15px;
  }

  /* Marquee tightening, mirroring demo.css at its own phone breakpoint. The long scripts
     (Tieng Viet, Ellinika) are the wrapping risk here, so the row wraps rather than overflowing. */
  #call-stage.connecting #connect-teaser {
    gap: 12px;
  }

  .connect-langs {
    gap: 8px 12px;
  }

  .lang-chip {
    font-size: 17px;
  }

  /* Keep the two transcript lanes (they ARE the proof) but tighten the divider padding. */
  .t-cell.you-col {
    padding-right: 10px;
  }

  .t-cell.other-col,
  .t-col-label.other {
    padding-left: 10px;
  }

  /* PHONE: TWO deliberate rows, not a cramped one. The single line is the intent for the normal
     case, not a mandate - at 390px the chip + two label/value pairs + Replay cannot share a row, and
     letting them wrap freely put each on its own line (measured: 3 rows, 134px tall, WORSE than the
     two blocks it replaced). So the strip takes a full-width row of its own BELOW the chip/Replay
     row: `order: 1` sends it after both (which are order 0) and `flex-basis: 100%` gives it the row.
     Visually that is the older two-row layout minus the card treatment - deliberate, and shorter
     than either that version or a free wrap. */
  /* PHONE: back to FLEX so the strip can take a row of its own. The desktop rule above is a GRID
     (for true centring), and under grid `flex-wrap` / `flex-basis` are inert - so without this
     revert the three-column template stays on and squeezes all three parts onto one cramped row.
     Measured: it also made the strip drift as the chip's label changed (Connecting / Call in
     progress / Call ended), because a too-wide middle column shrinks the two `1fr` flanks
     unevenly. Reverting restores the wrap, and the rule below gives the strip the full row. */
  .call-topbar:has(> .call-summary) {
    display: flex;
    flex-wrap: wrap;
  }

  .call-summary {
    order: 1;
    flex-basis: 100%;
    justify-content: flex-start;
  }

  .summary-stat {
    padding: 2px 10px;
  }

  /* On its own row there is nothing to the left of the first cell, so the divider that separates it
     from the chip at wider widths would read as a stray mark. The cell keeps the block's normal 10px
     inset (rather than going flush) so its label does not sit hard against the stage's outer edge -
     every other block on the row below has internal padding, and flush text read as misaligned. */
  .summary-stat:first-child {
    border-left: none;
  }
}

@media (max-width: 560px) {
  /* THE HEADER CTAs SURVIVE ON A PHONE - do not "reclaim" the space by hiding them. An earlier
     revision did hide them here, justified by "nothing is lost - the hero's own primary CTA is
     immediately below the fold". That justification died with the hero CTA row: both actions now
     live in this header, so hiding them would leave a phone visitor with NO action anywhere above
     the closing band at the very bottom of the page.
     They fit because both labels are short and the nav is already hidden at this width.
     tests/browser/marketing.spec.ts ASSERTS the fit - a label change alone is enough to break it, so
     re-run it rather than eyeballing. What gives way instead is the wordmark's sub-label (`.sub`),
     which is decorative next to two live actions. */
  /* Gap only. Deliberately NOT re-declaring `display: flex` here: this block and the
     `min-width: 481px` grid rule both match from 481-560px, and being later in the file this one
     would win and silently drop the equal-column behaviour in that band (it did - 80 widths were
     unequal). The base rule is already flex, so the declaration was redundant anyway. */
  .topbar-actions {
    gap: 8px;
  }

  .topbar .sub {
    display: none;
  }

}

/* "by Piedmont Global" gets its OWN breakpoint, wider than `.sub`'s and wider than the CTA rules'.
   It is 110px, and the header is a single row of brand + two CTAs: once "Sandbox Login" (129px)
   replaced the shorter primary label, brand(260) + actions(268) + padding(48) = 576 no longer fit a
   561px viewport. Dropping this line takes the brand to 150px, which fits from 421px up with slack.
   640px rather than 600px because 600 measured EXACTLY at the edge, and an exact fit is what keeps
   turning into a 1px overflow here. The brand degrades
   "Connexus AI Interpretation by Piedmont Global" -> "Connexus AI Interpretation" -> "Connexus"
   -> mark only, as the width drops. */
@media (max-width: 640px) {
  .topbar .sub,
  .topbar .by-pg {
    display: none;
  }
}

/* The 421-480px band: the wordmark is still SHOWN here (its clip starts at 420px) while the CTAs were
   still at full desktop padding, which made this the tightest fit on the page - brand(150) +
   actions(268) + padding(48) = 466 against a 421px viewport. It "passed" only on macOS metrics and
   overflowed CI's Linux fonts by 2px (423 > 421).
   Compacting the BUTTONS is what buys the band back, so the brand text survives on the 421-430px
   phones that are extremely common (iPhone Pro Max is 430px) instead of being clipped to the mark.
   Split from the <=420px block below rather than widening it: that block ALSO clips the wordmark and
   tightens the header padding, neither of which should start happening at 480px.
   Ordered after the 560px gap rule so it wins in the overlap, and before the 420px block so that one
   still wins at its own widths. 480px is the exact complement of the `min-width: 481px` grid rule, so
   the equal-width pair and this compact pair never both apply. */
@media (max-width: 480px) {
  .topbar-actions {
    gap: 8px;
  }

  .topbar-actions .btn {
    padding: 0 11px;
    font-size: 13px;
  }
}

@media (max-width: 420px) {
  header.topbar {
    padding: 12px 14px;
  }

  /* The wordmark TEXT gives way on the narrowest phones - the mark alone carries the brand there.
     "Sandbox Login" is 129px against "Sign In"'s 63px, and at 320px the header needed 363px for a
     320px viewport; the 81px wordmark is what buys that back.
     CLIPPED, NOT `display: none`: the brand's only other content is `<img alt="">` (decorative by
     design), so hiding this text outright would leave a LINK WITH NO ACCESSIBLE NAME. These are the
     `.sr-only` properties applied in place - the name stays in the accessibility tree while the
     glyphs leave the layout. Do not "simplify" this to display:none. */
  .topbar .wordmark {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  /* Both header CTAs must survive on the narrowest phones - they are the page's ONLY actions above
     the closing band now - so the BUTTONS give way instead of one of them being hidden. At their
     desktop padding the pair measured 185px and overflowed a 320px viewport by 23px; tightened they
     fit with room to spare (verified at 320px in the sweep). Applied at this existing breakpoint
     rather than a new one; 360-414px did not overflow either way, so this only makes them slightly
     more compact there. */
  .topbar-actions {
    gap: 6px;
  }

  .topbar-actions .btn {
    /* 9px, not 10: the current labels need that extra pixel each to keep 320px clear of horizontal
       overflow. This is the tightest width on the page - re-measure it if a label changes. */
    padding: 0 9px;
    font-size: 13px;
  }
}
