/* Animated demo page (/sandbox/demo.html) - a scripted replay of an interpreted call rendered in
   the product's OWN call stage. sandbox.css supplies everything on the stage (panels, transcript
   lanes, the status strip, the ended power-down); this file adds only what the demo page itself
   introduces: the marketing intro block above the stage, the Replay control, and the features view
   the page settles on when the call ends. Tokens only - no new colors, no external assets
   (script-src/style-src are 'self'). */

/* ---- intro block (above the stage) ---- */

.demo-intro {
  width: 100%;
  max-width: 640px;
  text-align: center;
  /* Breathing room under the topbar without pushing the stage below the fold. */
  margin: clamp(4px, 2vh, 20px) auto 2px;
}

/* Matches the signed-out hero headline register (.hero h2 is scoped to .hero, so restate it). */
.demo-intro h2 {
  margin: 0 0 12px;
  font-size: 32px;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  text-wrap: balance;
}

/* The honesty caption: this is a replay, not a live call. Quiet but always present - it is a
   truth cue on a sales surface, so it must never be styled away. */
.demo-caption {
  margin: 10px 0 0;
  font-size: 12.5px;
  letter-spacing: 0.02em;
  color: var(--muted);
  opacity: 0.85;
}

/* ---- replay control ---- */

/* Sits where End call sits on the real stage, so the call topbar keeps its shape. */
#replay-btn {
  height: 36px;
  padding: 0 16px;
}

/* Reduced motion renders the finished conversation statically, so a Replay would restart nothing;
   demo.js hides it via the hidden attribute (the [hidden] guard in sandbox.css wins). */

/* ---- sound toggle ----
   Sits left of Replay in the same .actions row (.btn is already inline-flex + centered, so this
   only adds the icon gap and the size match). The two speaker glyphs are inline in demo.html and
   swapped purely off aria-pressed - demo.js flips that attribute and nothing else, so the pressed
   state a screen reader announces and the icon a sighted visitor sees can never disagree. Ships
   [hidden] in demo.html and is REVEALED by demo.js only when a clip manifest is committed, so it
   stays away with the placeholder manifest and on the reduced-motion static render - and never
   flashes a control that is about to be taken back. Tokens only. */

/* The sound control is the one thing on this page a visitor must actively DO, so it is styled as
   an action (filled accent + a verb label + the callout below), not as a settings switch. Lime
   stays reserved for the conversion CTA ("Sandbox Login"), which must not have to compete. */
.sound-wrap {
  position: relative;
  display: inline-flex;
}

#sound-btn {
  height: 38px;
  padding: 0 16px;
  gap: 8px;
  font-weight: 600;
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg);
}

#sound-btn:hover {
  background: var(--accent);
  border-color: var(--accent);
  filter: brightness(1.06);
}

/* Exactly one label at a time, mirroring the glyph swap below. */
#sound-btn .label-on,
#sound-btn[aria-pressed="true"] .label-off {
  display: none;
}

#sound-btn[aria-pressed="true"] .label-on {
  display: inline;
}

/* The nudge that explains WHY to press: a pill above the control with a caret into it. Absolute,
   so it never widens the topbar row or reflows the actions. */
.sound-callout {
  display: none;
  position: absolute;
  bottom: calc(100% + 9px);
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  padding: 5px 11px;
  border-radius: 999px;
  background: var(--panel-2);
  border: 1px solid rgba(46, 196, 166, 0.45);
  color: var(--text);
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.01em;
  pointer-events: none;
}

.sound-callout::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: rgba(46, 196, 166, 0.45);
}

/* Shown only while the control is actually on screen (it ships [hidden] and stays hidden with a
   placeholder manifest / on the static render) AND sound is still off. */
#sound-btn:not([hidden])[aria-pressed="false"] + .sound-callout {
  display: block;
}

/* Retired for good on the first enable (demo.js adds .used): once the visitor knows the control is
   there, a nudge that came back on every toggle-off would just be nagging. */
#sound-wrap.used #sound-btn + .sound-callout {
  display: none;
}

#sound-btn .sound-icon {
  width: 16px;
  height: 16px;
  flex: none;
  fill: none;
  stroke: currentcolor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Exactly one glyph at a time: muted by default, waves once sound is on. */
#sound-btn .icon-on,
#sound-btn[aria-pressed="true"] .icon-off {
  display: none;
}

#sound-btn[aria-pressed="true"] .icon-on {
  display: inline;
}

/* On = job done: the fill drops away to a quiet accent outline, so the control stops competing
   for attention the moment it has been pressed. */
#sound-btn[aria-pressed="true"],
#sound-btn[aria-pressed="true"]:hover {
  background: transparent;
  color: var(--accent);
  border-color: var(--accent);
  filter: none;
}

/* ---- connecting beat: language marquee ----
   Before the scripted call answers, the stage sits in the product's own pre-answer state
   (#call-stage.connecting, added by demo.js) and the transcript pane's slot carries a static row of
   language pairs instead of the empty-state placeholder; demo.js scrolls the .on highlight across
   them, landing on EN<->ES, and mirrors the hub #dir-pill. The teaser takes over the pane's OWN
   flex:1 child slot (the .transcript-panel is a flex column in sandbox.css - see .transcript-panel
   / .transcript-lines there), so the panel's clamp(420px, 62vh, 640px) height is untouched and
   nothing shifts when the call answers and the pane comes back.

   The "Live transcript" head and its live-dot stay up through the beat; only the lane headers and
   the lines themselves stand down. Scroll TIMING lives in demo.js (CONNECT_STEP_MS / CONNECT_ANSWER_MS)
   - this file only describes the dim/on states each chip can be in. */

#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;
}

#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 demo.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);
}

/* ---- transcript top-edge fade ----
   Once the pane scrolls (it pins to the newest turn), earlier rows exit through its TOP edge;
   unmasked they are sliced mid-line, which reads as a rendering bug on a sales page. demo.js
   toggles .scrolled-away only while scrollTop > 0-ish, so the resting first row is never faded. */
#transcript-lines.scrolled-away {
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 26px);
  mask-image: linear-gradient(180deg, transparent 0, #000 26px);
}

/* ---- the scene swap: stage (and intro) out, features in ----
   The call plays ONCE. After the ended console holds, demo.js adds .closing to the stage AND the
   intro block (the "See it in action" head + simulated-conversation caption belong to the replayed
   call, so they leave with it - the features head is the only header in the end state), waits out
   this transition, hides both, then unhides #feature-view and adds .revealed. Both durations are
   mirrored by STAGE_FADE_MS in demo.js - keep them in step. */

#call-stage,
.demo-intro {
  transition:
    opacity 0.4s ease,
    transform 0.4s ease;
}

#call-stage.closing,
.demo-intro.closing {
  opacity: 0;
  transform: translateY(6px);
}

/* ---- features view (the end state) ---- */

/* Deliberately NO `display` rule here: [hidden] in sandbox.css is display:none !important, and
   the section is shown purely by dropping the attribute. Width matches the stage's full column. */
#feature-view {
  width: 100%;
  opacity: 0;
  transition: opacity 0.45s ease;
}

#feature-view.revealed {
  opacity: 1;
}

.feature-head {
  max-width: 940px;
  margin: 0 auto 6px;
  text-align: center;
}

.feature-head h2 {
  margin: 0;
  font-size: 54px;
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.08;
  text-wrap: balance;
}

/* The brand ramp, reduced to a short accent stroke under the headline (the full-width version
   crowded the header - it was the escalation card's top edge before the cards went borderless). */
.feature-head h2::after {
  content: "";
  display: block;
  width: 96px;
  height: 4px;
  margin: 20px auto 0;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--teal), var(--accent) 55%, var(--lime));
}

/* Marketing statement layout (CTO pick 2026-07-28, the "v1 statement" mockup): the three sections
   STACK full width - a centered borderless escalation statement, then the integration tile grid,
   then the security bullet band - instead of the earlier side-by-side card columns. */
.feature-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 22px;
}

/* Sections are borderless bands; the visual chrome lives on the escalation glow and the connect
   tiles, not on card outlines. */
.feature-card {
  position: relative;
}

.feature-card-wide {
  grid-column: 1 / -1;
}

/* The human off-ramp LEADS: a centered hero statement under the headline over a faint interior
   teal glow. No top edge - the brand ramp lives on the headline's accent stroke now. */
#feat-escalation {
  overflow: hidden;
  border-radius: 18px;
  text-align: center;
  padding: 40px 64px 30px;
  background: radial-gradient(85% 140% at 50% 0%, rgba(46, 196, 166, 0.15), transparent 62%);
}

/* Section title, marketing register (the "a3 editorial" mockup): a short confident headline with
   an accent full stop, over spec-sheet rows. No icons, no uppercase micro-labels. */
.feature-title {
  margin: 0 0 22px;
  text-align: center;
  font-size: 30px;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: #fff;
}

.feature-title-dot {
  color: var(--accent);
}

/* The escalation statement: one bright line, then its supporting paragraph, both centered (the
   section carries no micro-label - the statement IS the header). */
.feature-lead {
  margin: 0 0 16px;
  font-size: 31px;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: #fff;
  text-wrap: balance;
}

/* Sized so the escalation statement's support line breaks as TWO lines at desktop widths. */
.feature-body {
  margin: 0 auto;
  max-width: 86ch;
  color: var(--muted);
  font-size: 17px;
  line-height: 1.7;
}

/* Integration points as spec-sheet rows (Apple tech-specs register): term left, description
   right, hairline rules between rows, no tiles. Kept tight vertically. */
.feature-rows {
  margin: 0 auto;
  max-width: 860px;
}

/* Evens the section rhythm: the gap ABOVE the next section title measures the same ~52px the
   escalation band leaves above this one (last row 11px + this 30px + the 22px grid gap ~= the
   escalation's 30px padding-bottom + 22px gap). */
#feat-connect {
  padding-bottom: 30px;
}

.feature-row {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 24px;
  padding: 11px 2px;
  border-top: 1px solid var(--line);
}

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

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

.feature-row dd {
  margin: 0;
  color: var(--muted);
  font-size: 14.5px;
  line-height: 1.6;
}

/* Security points continue the same spec-sheet rhythm: one hairline-separated row per point, no
   bullet markers. */
.feature-list {
  margin: 0 auto;
  padding: 0;
  list-style: none;
  max-width: 860px;
}

.feature-list li {
  padding: 11px 2px;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 15.5px;
  line-height: 1.6;
}

.feature-list li:first-child {
  border-top: 0;
  padding-top: 0;
}

.feature-actions {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 14px;
  margin-top: 40px;
}

.feature-actions .btn {
  height: 52px;
  padding: 0 32px;
  font-size: 16.5px;
  border-radius: 14px;
}

/* ---- reveal choreography ----
   Cards start low and transparent and settle in sequence once .revealed lands. Delays are on the
   cards only; the section's own opacity carries the head and the actions. */
.feature-card {
  opacity: 0;
  transform: translateY(10px);
  transition:
    opacity 0.45s ease,
    transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}

#feature-view.revealed .feature-card {
  opacity: 1;
  transform: translateY(0);
}

#feature-view.revealed .feature-card:nth-child(1) {
  transition-delay: 0ms;
}

#feature-view.revealed .feature-card:nth-child(2) {
  transition-delay: 90ms;
}

#feature-view.revealed .feature-card:nth-child(3) {
  transition-delay: 180ms;
}

@media (max-width: 760px) {
  #feat-escalation {
    padding: 26px 24px 24px;
  }
}

@media (max-width: 640px) {
  /* Intro block: tighten the vertical rhythm so the stage stays near the top of the fold. */
  .demo-intro {
    margin: 6px auto 2px;
  }

  .demo-intro h2 {
    font-size: 26px;
    margin-bottom: 8px;
  }

  .demo-caption {
    margin-top: 6px;
  }

  /* Connecting beat: tighten the language marquee. The compound selector matches the base rule's
     specificity so the gap override actually lands. */
  #call-stage.connecting #connect-teaser {
    gap: 12px;
  }

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

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

  /* Features view (end state): tighten the heads, section padding, and the stacked spec rows. */
  .feature-head h2 {
    font-size: 30px;
  }

  .feature-head h2::after {
    margin-top: 12px;
  }

  .feature-grid {
    gap: 14px;
  }

  #feat-escalation {
    padding: 20px 16px 16px;
  }

  .feature-lead {
    font-size: 25px;
  }

  .feature-body {
    font-size: 15px;
  }

  .feature-title {
    font-size: 24px;
    margin-bottom: 12px;
  }

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

  .feature-list li {
    padding: 8px 2px;
  }

  .feature-actions {
    flex-direction: column;
    align-items: stretch;
    margin-top: 24px;
    gap: 10px;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* The stage's own live animation (pulsing dot, speaking glow, avatar ring) is already handled by
     sandbox.css. On this path demo.js renders the conversation AND the features view statically on
     first paint, so neither the stage fade nor the card stagger may run - the cards must be fully
     placed from the start rather than transitioning in from an invisible offset. */
  .demo-intro {
    animation: none;
  }

  #call-stage,
  #feature-view,
  .feature-card {
    transition: none;
  }

  /* The connecting beat only plays on the animated path (the static render never enters
     .connecting), but kill the chip glide too in case that ever changes. */
  .lang-chip {
    transition: none;
  }

  .feature-card {
    opacity: 1;
    transform: none;
  }
}

/* ---------------------------------------------------------------------------
   Floating page header (demo page only)
   ---------------------------------------------------------------------------
   The demo page is a LONG scroll - the call stage, then the features view - and its single action
   ("Sandbox Login", #try-link) lives in the header. Pinning the header keeps that door reachable
   from anywhere on the page instead of only at the top.

   Scoped to body[data-page="demo"] on purpose: the authenticated SPA (data-page="sandbox") shares
   this header markup but has no long scroll, and a floating header there would hang over the in-call
   stage. This is a marketing-surface change, not a product-layout one.

   Only ONE bar floats. `.call-topbar` is `position: sticky; top: 0` at all widths (sandbox.css), so
   with the header pinned it would either collide at the same offset or need a hard-coded top offset -
   and the header's height is NOT a constant (it is `flex-wrap: wrap` under 640px, so the CTA can drop
   to a second line). Rather than encode a brittle number, the demo page un-sticks the call topbar and
   lets the header win; the SPA keeps its sticky call topbar, where End call must stay reachable
   mid-call. Two stacked sticky bars would also eat most of a phone viewport. */
body[data-page="demo"] .topbar {
  position: sticky;
  top: 0;
  /* Above .call-topbar (z-index 5) and the stage panels (z-index 1). */
  z-index: 20;
  /* MUST be opaque - page content scrolls underneath. `var(--bg)` is the body fill; the body's
     atmospheric glow is `background-attachment: fixed`, so a flat fill here reads as continuous
     rather than as a seam. Same approach the sticky .call-topbar already uses. */
  background: var(--bg);
}

body[data-page="demo"] .call-topbar {
  position: static;
}

@media (max-width: 640px) {
  /* ONE LINE, not two. At 390px the bar has ~358px but its content wants ~426px
     (.call-state 171 + gap + .actions 243), so it wrapped the buttons onto a second row - and the
     absolutely-positioned "Hear the call" nudge then landed on top of the state chip.

     The 68px is bought back from the sound button's LABEL rather than from the state chip: the
     chip's text is load-bearing choreography on this page (Connecting -> Call in progress -> Call
     ended), while the speaker glyph carries the button on its own. The label is moved to
     screen-reader-only rather than removed, so the accessible NAME survives untouched - including
     the deliberate verb/state swap ("Turn on sound" while off, "Sound on" once pressed), which is
     still driven by the same aria-pressed CSS. Icon-only is visual, never semantic.

     nowrap makes a regression loud: if this ever stops fitting it will overflow visibly instead of
     silently folding into two rows again. */
  body[data-page="demo"] .call-topbar {
    flex-wrap: nowrap;
    gap: 8px;
  }

  /* ...EXCEPT once the status strip moved INTO this bar. Then one row is arithmetically impossible -
     at 390px the bar has ~358px and chip + strip + actions want ~587px - so `nowrap` stops being a
     regression signal and becomes a permanent 245px overflow (measured). The strip is MEANT to take
     a second row here; what must stay on one row is the chip and the actions, and that is guaranteed
     by them FITTING (the assertion in sandbox-demo-mobile.spec.ts), not by `nowrap`.
     So: allow wrapping only while the strip is a child of the bar. A bar without it keeps `nowrap`
     and the rule above is untouched, which is why this is an ADDITION rather than an edit.
     The overflow signal `nowrap` was protecting is not lost - it moved into the spec, which now
     includes the strip in its fits-check and so fails on an overflowing bar rather than passing
     while only chip and actions are measured. That is how this overflow got through green tests. */
  body[data-page="demo"] .call-topbar:has(> .call-summary) {
    flex-wrap: wrap;
  }

  /* Same declarations as the shared .sr-only helper (sandbox.css) - inlined because this hides an
     EXISTING span at a breakpoint rather than marking up a new one. */
  body[data-page="demo"] #sound-btn .sound-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    /* `clip` is what the shared .sr-only helper uses and is kept for parity/legacy; `clip-path` is
       its non-deprecated equivalent and is what modern engines honor. Both, so this neither
       diverges from the helper nor relies on a deprecated property alone. */
    clip: rect(0, 0, 0, 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  /* NOTE: `display` is deliberately NOT touched here. The base rules swap the two labels with
     `display: none` off aria-pressed, so only the ACTIVE label is in the accessible name - the
     inactive one stays display:none and out of it. Overriding display here would put BOTH
     ("Turn on sound" AND "Sound on") into the button's accessible name at once. */

  /* The nudge is decorative (aria-hidden) and was overlapping the state chip at this width, so it
     goes rather than being repositioned into the equally tight rows above/below. Cost: the
     icon-only button loses its "Hear the call" hint on mobile - worth revisiting if sound
     engagement on phones matters. */
  body[data-page="demo"] .sound-callout {
    display: none;
  }
}
