/* ===========
   Deck shell
   =========== */

.deck-body {
  /*
   * Chrome and slide content share these variables so the slide's left
   * edge always lines up vertically with the wordmark in the chrome.
   * --chrome-padding-x scales with viewport — wider screens get more
   * margin, narrow screens stay tight to the edge.
   */
  --chrome-padding-x: clamp(16px, 3.2vw, 36px);
  --chrome-padding-y: 24px;
  /* Width of the logo-dots block + the gap between dots and wordmark. */
  --logo-text-offset: 27px;

  background: #050507;
  color: #ededee;
  overflow: hidden;
  height: 100vh;
  height: 100dvh;
  width: 100vw;
  position: fixed;
  inset: 0;
}

.deck-body::before {
  content: "";
  position: fixed;
  inset: -10%;
  background: radial-gradient(circle at 20% 0%, rgba(254, 9, 109, 0.08), transparent 45%),
    radial-gradient(circle at 100% 90%, rgba(3, 189, 255, 0.08), transparent 45%),
    radial-gradient(circle at 50% 50%, rgba(255, 221, 4, 0.03), transparent 60%);
  pointer-events: none;
  z-index: 0;
  transition: opacity 0.5s ease;
}

/* Light theme doesn't want the ambient color wash — it sucks
   contrast off the white cards. Fade the gradients out. */
.deck-body[data-theme="light"]::before {
  opacity: 0;
}

.deck-stage {
  position: absolute;
  inset: 0;
  z-index: 1;
  transition: right 0.35s cubic-bezier(0.4, 0, 0.2, 1), bottom 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}

/*
 * Slides are left-anchored so their content lines up vertically with
 * the "ForwardSteady" wordmark in the chrome. The wordmark's x-position
 * is the chrome's left padding plus the dots block plus the gap, all of
 * which we have as variables.
 */
.deck-stage {
  --content-left: calc(var(--chrome-padding-x) + var(--logo-text-offset));
  --content-right: var(--chrome-padding-x);
}

.slide-wrapper {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  padding-left: var(--content-left);
  padding-right: var(--content-right);
  /* The enter animation is opt-in via a class set at wrapper creation
   * time so the property never changes mid-life (which would
   * re-trigger the animation). JS sets `--initial` on the very first
   * wrapper (opacity-only fade) and `--enter` on every subsequent
   * one (opacity + 24px translate cue). */
}

.slide-wrapper.slide-wrapper--initial {
  animation: slide-enter-initial 1.2s ease both;
}

.slide-wrapper.slide-wrapper--enter {
  animation: slide-enter 0.45s ease both;
}

.deck-body[data-transition="forward"] .slide-wrapper {
  animation: slide-exit-forward 0.22s ease forwards;
}

.deck-body[data-transition="back"] .slide-wrapper {
  animation: slide-exit-back 0.22s ease forwards;
}

@keyframes slide-enter {
  from {
    opacity: 0;
    transform: translateX(24px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

/* First page render uses an opacity-only enter so the mark + headline
 * just fade in instead of also translating from the right. The
 * translation is the right cue for between-slide transitions but
 * reads as a "jolt" when the page initially loads. */
@keyframes slide-enter-initial {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes slide-exit-forward {
  to {
    opacity: 0;
    transform: translateX(-24px);
  }
}

@keyframes slide-exit-back {
  to {
    opacity: 0;
    transform: translateX(24px);
  }
}

/* Beat reveals */
[data-beat] {
  transition: opacity 0.5s ease, transform 0.5s ease;
}
[data-beat][data-state="hidden"] {
  opacity: 0;
  transform: translateY(16px);
  pointer-events: none;
}
[data-beat][data-state="shown"] {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

/* ===========
   Chrome
   =========== */

.deck-chrome {
  position: fixed;
  inset: 0 0 auto 0;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: var(--chrome-padding-y) var(--chrome-padding-x);
  z-index: 10;
  pointer-events: none;
  transition: opacity 0.6s ease;
}

.deck-chrome > * {
  pointer-events: auto;
}

/*
 * Gate state — until the visitor signs in, most of the chrome (logo,
 * progress, notes toggle, hint, notes panel) is invisible and inert
 * so the intro slide reads as a clean passcode page. The theme
 * toggle is the one exception: the gate respects it so a visitor
 * can pick light vs dark before authenticating. The chrome logo has
 * its own visibility rule below because the slide-intro-mark fills
 * its role on the intro slide and the FLIP morph hands off when
 * the user advances past it.
 */
.deck-body[data-authenticated="false"] .deck-progress,
.deck-body[data-authenticated="false"] .deck-notes-toggle,
.deck-body[data-authenticated="false"] .deck-hint,
.deck-body[data-authenticated="false"] .deck-notes {
  opacity: 0;
  pointer-events: none;
}

/*
 * Initial-load intro-entry: when an already-authenticated visitor
 * lands on the intro slide, hide the navigation chrome (progress,
 * notes toggle, hint) until the slide's mark + headline have had a
 * chance to fade in. JS sets and clears `data-intro-entry-pending`
 * around the entry sequence. The theme toggle stays visible — same
 * as in the gated state — so the visitor can always change theme.
 */
.deck-body[data-intro-entry-pending="true"] .deck-progress,
.deck-body[data-intro-entry-pending="true"] .deck-notes-toggle,
.deck-body[data-intro-entry-pending="true"] .deck-hint {
  opacity: 0;
  pointer-events: none;
}

/*
 * Chrome logo: hidden while we're on the intro slide (the
 * slide-intro-mark stands in for it), and explicitly hidden again
 * during the FLIP morph so the moving clone arrives in an empty spot
 * before the static logo reveals.
 */
.deck-logo {
  transition: opacity 0.4s ease;
}
.deck-body[data-current-slide="intro"] .deck-logo,
.deck-body[data-current-slide="share"] .deck-logo,
.deck-body[data-intro-morph] .deck-logo {
  opacity: 0;
  pointer-events: none;
}

.deck-chrome > .deck-progress {
  justify-self: center;
}

.deck-chrome > .deck-chrome-actions {
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 8px;
}

.deck-logo {
  display: flex;
  align-items: center;
  gap: 8px;
  opacity: 0.85;
  /* Don't stretch in either grid axis — the FLIP morph from the intro
   * slide measures this element's bounding rect, and a stretched cell
   * would inflate both the width (causing inverse scale) and the
   * height (causing a vertical jump at the cross-fade because the
   * measured top sits above the visible content's true top). */
  justify-self: start;
  align-self: center;
}

.deck-logo .logo-dot {
  width: 8px;
  height: 8px;
}

.deck-logo .logo-dots {
  gap: 3px;
}

.deck-logo .logo-dots-row {
  gap: 3px;
}

.deck-logo-text {
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.3px;
}

.deck-logo-forward {
  color: #dddddd;
}

.deck-logo-steady {
  color: #a3a3a3;
}

.deck-progress {
  display: flex;
  gap: 6px;
  /* Fade smoothly when hidden by the gate or intro-entry rules. */
  transition: opacity 0.6s ease;
}

.deck-progress-dot {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.15);
  transition: background 0.3s ease, width 0.3s ease, border-radius 0.3s ease;
}

.deck-progress-dot[data-state="past"] {
  background: rgba(255, 255, 255, 0.45);
}

.deck-progress-dot[data-state="current"] {
  background: #03bdff;
  width: 18px;
  border-radius: 2px;
}

.deck-notes-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  background: rgba(255, 255, 255, 0.08);
  border: none;
  color: #b8b8bb;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  padding: 8px 12px;
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, opacity 0.6s ease;
}

.deck-notes-toggle:focus,
.deck-notes-toggle:focus-visible {
  outline: none;
}

.deck-notes-toggle:hover {
  background: rgba(255, 255, 255, 0.13);
  color: #ededee;
}

.deck-notes-toggle[data-state="on"] {
  background: rgba(3, 189, 255, 0.12);
  color: #03bdff;
}

.deck-notes-toggle-key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  background: rgba(255, 255, 255, 0.06);
  border-radius: 3px;
  font-size: 11px;
  font-weight: 700;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  color: inherit;
}

/* Theme toggle — icon-only sun/moon button. Sun shown in dark mode
   (clicking switches to light); moon shown in light mode. Hidden in
   present mode (mirrors the notes-toggle pattern). */
.deck-theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  background: rgba(255, 255, 255, 0.08);
  border: none;
  color: #b8b8bb;
  padding: 0;
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.deck-theme-toggle:focus,
.deck-theme-toggle:focus-visible {
  outline: none;
}
.deck-theme-toggle:hover {
  background: rgba(255, 255, 255, 0.13);
  color: #ededee;
}
.deck-theme-toggle-icon {
  width: 18px;
  height: 18px;
  display: block;
}
/* Show only the icon for the theme we'd switch *to*. */
.deck-body[data-theme="dark"] .deck-theme-toggle-icon-moon,
.deck-body[data-theme="light"] .deck-theme-toggle-icon-sun {
  display: none;
}

.deck-hint {
  position: fixed;
  /* Sit roughly halfway between the slide's centered ad copy and the
   * bottom edge of the viewport. Content centers at 50vh with ~80px
   * of height, so its bottom is near 55vh; the visual midpoint to
   * 100vh lands around 22vh from the bottom. Mobile overrides this
   * to a fixed pixel offset (small viewports don't have enough room
   * for a vh-based gap to feel right). */
  bottom: 33vh;
  left: 50%;
  transform: translateX(-50%);
  font-size: 12px;
  color: rgba(255, 255, 255, 0.55);
  letter-spacing: 0.3px;
  pointer-events: none;
  z-index: 10;
  transition: opacity 0.6s ease;
}

.deck-hint kbd {
  display: inline-block;
  padding: 1px 6px;
  margin: 0 2px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 3px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11px;
  color: #ededee;
}

.deck-hint[data-state="faded"] {
  opacity: 0;
}

/*
 * Presenter mode hides the affordances meant for self-guided viewers.
 * The notes toggle uses `visibility: hidden` (not `display: none`) so it
 * still occupies space in the chrome row — otherwise the logo and
 * progress bar would shift up when the toggle collapses out. The hint
 * sits in its own fixed position, so `display: none` is fine there.
 */
.deck-body[data-mode="present"] .deck-notes-toggle,
.deck-body[data-mode="present"] .deck-theme-toggle {
  visibility: hidden;
}
.deck-body[data-mode="present"] .deck-hint {
  display: none;
}

/*
 * The intro and share slides host a large logo + wordmark as their
 * hero element, so the small chrome logo would be redundant. Hide it
 * (visibility, not display) on these slides only — keeps the chrome
 * layout intact.
 */
.deck-body[data-current-slide="intro"] .deck-logo,
.deck-body[data-current-slide="share"] .deck-logo {
  visibility: hidden;
}

/* ===========
   Notes panel
   =========== */

/*
 * Notes popover: a small floating card anchored near the notes toggle
 * button (top-right of the chrome). Shows only the current beat's note
 * — non-current blocks and the slide title are hidden via CSS below.
 */
.deck-notes {
  position: fixed;
  top: calc(var(--chrome-padding-y) + 40px);
  right: var(--chrome-padding-x);
  width: min(520px, calc(100vw - 32px));
  max-height: min(60vh, 480px);
  /* Surface tint: the panel is noticeably lighter than the page bg
   * (#050507) so it reads as elevated. A dark-on-dark drop shadow is
   * invisible, so the "lift" cue comes from this contrast plus the
   * soft white bloom and top edge highlight in the box-shadow. */
  background: rgba(28, 30, 36, 0.96);
  backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 12px;
  box-shadow: 0 24px 56px rgba(255, 255, 255, 0.05), 0 6px 18px rgba(0, 0, 0, 0.5), inset 0 1px 0
    rgba(255, 255, 255, 0.08);
  visibility: hidden;
  opacity: 0;
  transform: translateY(-6px) scale(0.98);
  transform-origin: top right;
  transition: opacity 0.18s ease, transform 0.18s cubic-bezier(0.4, 0, 0.2, 1), visibility 0s linear
    0.18s;
  z-index: 20;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.deck-body[data-notes="open"] .deck-notes {
  visibility: visible;
  opacity: 1;
  transform: translateY(0) scale(1);
  transition: opacity 0.18s ease, transform 0.18s cubic-bezier(0.4, 0, 0.2, 1), visibility 0s linear
    0s;
}

.deck-notes-inner {
  flex: 1;
  overflow-y: auto;
  padding: 16px 20px;
  scroll-behavior: smooth;
}

/* Slide title and non-current beats are hidden — the popover only shows
   the note for the current beat. */
.deck-notes-slide-title {
  display: none;
}

.deck-notes-block {
  margin: 0;
  padding: 0;
}

.deck-notes-block:not(.current) {
  display: none;
}

.deck-notes-block-header {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: #03bdff;
  margin-bottom: 8px;
}

.deck-notes-block[data-state="pending"] .deck-notes-block-header {
  color: #6e6e73;
}

.deck-notes-block-body p {
  font-size: 14px;
  line-height: 22px;
  color: #c8c8cc;
  margin-bottom: 10px;
}

.deck-notes-block-body p:last-child {
  margin-bottom: 0;
}

/*
 * The notes panel floats over the slide content rather than reflowing
 * it — keeping the slide layout stable when notes toggle on/off, at
 * the cost of partially covering the right edge (desktop) / bottom
 * (mobile) of the slide. Backdrop blur keeps the underlying content
 * legible.
 */

/* ===========
   Slide template
   =========== */

.slide {
  width: 100%;
  max-width: 1100px;
  max-height: 88vh;
  padding: 0;
  display: flex;
  flex-direction: column;
  position: relative;
}

.slide-eyebrow {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 2.4px;
  text-transform: uppercase;
  color: #03bdff;
  margin-bottom: 18px;
}

/* ===========
   Slide 1 — Intro
   =========== */

/*
 * DEBUG: layout outlines for the intro slide. Off by default — enable
 * by adding `data-debug="layout"` to the .deck-body element (set it via
 * DevTools, or temporarily in deck.html) to color-code the boxes:
 *   - white:   .slide-intro
 *   - pink:    .slide-intro-inner
 *   - cyan:    .slide-intro-content
 *   - mint:    children of the content column
 */
.deck-body[data-debug="layout"] .slide-intro {
  outline: 1px dashed rgba(255, 255, 255, 0.4);
  outline-offset: -1px;
}
.deck-body[data-debug="layout"] .slide-intro-inner {
  outline: 1px dashed rgba(254, 9, 109, 0.55);
  outline-offset: -1px;
}
.deck-body[data-debug="layout"] .slide-intro-content {
  outline: 1px dashed rgba(3, 189, 255, 0.55);
  outline-offset: -1px;
}
.deck-body[data-debug="layout"] .slide-intro-mark,
.deck-body[data-debug="layout"] .slide-intro-headline {
  outline: 1px dashed rgba(0, 255, 209, 0.55);
  outline-offset: -1px;
}

.slide-intro {
  align-items: center;
  justify-content: center;
  max-width: none;
}

/* Symmetric padding on the intro slide so the centered content sits
 * on the true horizontal midline of the viewport. */
.slide-wrapper[data-slide-id="intro"] {
  padding-right: var(--content-left);
}

/* Slide 2 is content-heavy with absolutely-positioned quote cards that
   need to read as truly viewport-centered. Symmetrize the wrapper
   padding and center the slide block inside the wrapper so the cards
   anchor on the viewport's horizontal midline. */
.slide-wrapper[data-slide-id="why-now"],
.slide-wrapper[data-slide-id="problem"],
.slide-wrapper[data-slide-id="how-its-going"],
.slide-wrapper[data-slide-id="solution"],
.slide-wrapper[data-slide-id="wedge"],
.slide-wrapper[data-slide-id="team"],
.slide-wrapper[data-slide-id="timeline"],
.slide-wrapper[data-slide-id="ask"] {
  padding-right: var(--content-left);
  justify-content: center;
}

/*
 * Slide-1 entry animation, two stages:
 *   1. "entering"  — logo + wordmark visible. Headline is opacity 0.
 *   2. "revealed"  — headline fades in.
 */

/* Reverse navigation (e.g. retreating from slide 2) renders the intro
   slide directly in its revealed state — suppress all entry transitions
   so the final layout snaps in instantly. */
.slide-intro[data-instant],
.slide-intro[data-instant] *,
.slide-intro[data-instant] *::before,
.slide-intro[data-instant] *::after {
  transition: none !important;
  transition-delay: 0s !important;
}

.slide-intro-inner {
  display: flex;
  justify-content: center;
  width: 100%;
}

.slide-intro-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 24px;
  width: 100%;
  max-width: 760px;
  /* Anchor for the absolutely-positioned passcode below. */
  position: relative;
  /* The content column is the size context for the mark — using cqi
   * units below ensures the mark always scales relative to its
   * available width and never overflows. Width: 100% (capped at
   * max-width) keeps that container from collapsing inside the
   * centering flex parent. */
  container-type: inline-size;
}

.slide-intro-mark {
  display: flex;
  align-items: center;
  gap: clamp(5px, 2.2cqi, 15px);
  max-width: 100%;
  transition: opacity 1s ease, transform 1s ease;
}

.slide-intro-mark-dots {
  display: flex;
  flex-direction: column;
  gap: clamp(2px, 0.5cqi, 4px);
}

.slide-intro-mark-row {
  display: flex;
  gap: clamp(2px, 0.5cqi, 4px);
}

.slide-intro-mark-dot {
  width: clamp(8px, 3.5cqi, 20px);
  height: clamp(8px, 3.5cqi, 20px);
  border-radius: 9999px;
  flex-shrink: 0;
}

.slide-intro-mark-text {
  font-size: clamp(18px, 6.4cqi, 42px);
  font-weight: 700;
  letter-spacing: -1px;
  line-height: 1;
}

.slide-intro-mark-forward {
  color: #ededee;
}

.slide-intro-mark-steady {
  color: #a3a3a3;
}

.slide-intro-headline {
  font-size: clamp(20px, 1.8vw, 26px);
  font-weight: 400;
  line-height: 1.5;
  color: #ffffff;
  max-width: 760px;
  margin-top: 6px;
  transition: opacity 2s ease;
}

.slide-intro[data-stage="entering"] .slide-intro-headline {
  opacity: 0;
}

/*
 * Passcode entry — only rendered visibly when the deck is gated
 * (no active session). Reuses the existing `.passcode-group` /
 * `.passcode-row` styles from styles.css; this block is only
 * concerned with positioning the gate within the slide column and
 * sequencing it alongside the headline reveal.
 */
/* Always positioned absolutely just below the slide-intro-content
 * block so it never affects the layout of the mark + headline above
 * — they stay vertically centered in the slide regardless of whether
 * the passcode is being shown. left: 0 / right: 0 + margin-inline:
 * auto centers the element within slide-intro-content (capped at
 * max-width). */
.slide-intro-passcode {
  position: absolute;
  top: calc(100% + 40px);
  left: 0;
  right: 0;
  margin-inline: auto;
  max-width: 460px;
  display: none;
}

.deck-body[data-authenticated="false"] .slide-intro-passcode {
  display: block;
  opacity: 0;
  transition: opacity 1.2s ease 1.2s;
}

.slide-intro-passcode .passcode-group {
  gap: 4px;
}

.deck-body[data-authenticated="false"] .slide-intro[data-stage="revealed"] .slide-intro-passcode {
  opacity: 1;
}

/* ===========
   Slide: Share
   ===========

   Mirrors the intro slide's layout (mark + headline at top) but adds
   a share-specific prompt + email form positioned below, in the same
   absolute slot the passcode gate uses on the intro slide. Visually,
   advancing from the deck back into share reverse-morphs the mark
   from the corner into this slide's centered mark, then fades the
   prompt copy + form in. The form posts to the same
   `/api/auth/request-access` endpoint as the gate's request-access
   view, so the introduced person flows through the same admin
   review pipeline. */
.slide-share-prompt {
  position: absolute;
  top: calc(100% + 120px);
  left: 0;
  right: 0;
  margin-inline: auto;
  max-width: 460px;
  display: flex;
  flex-direction: column;
  gap: 32px;
  opacity: 0;
  transition: opacity 1.2s ease 1.2s;
}

.slide-share[data-stage="revealed"] .slide-share-prompt {
  opacity: 1;
}

.slide-share-form {
  display: flex;
  flex-direction: column;
  align-self: stretch;
}

/* Center-aligned label that sits one line wide above the email
   input. Smaller font than the gate's passcode label since the copy
   here is a sentence rather than a single-word heading; `nowrap`
   keeps it on a single line on desktop and lets the parent prompt's
   460px max-width contain it. Mobile drops the nowrap so the label
   wraps to multiple lines on narrow viewports instead of overflowing
   the slide. */
.slide-share-prompt .request-label {
  text-align: center;
  font-size: 13px;
  line-height: 18px;
  white-space: nowrap;
}

@media (max-width: 600px) {
  .slide-share-prompt .request-label {
    white-space: normal;
    max-width: 280px;
    margin: 0 auto;
  }
}

/* Success-state toggle: when the form submits successfully the JS
   flips `data-state="success"` on the prompt. CSS hides the form and
   shows the thank-you message. The signature stays visible either
   way as a fallback contact line. */
.slide-share-success {
  display: none;
  margin: 0;
  text-align: center;
  font-size: clamp(14px, 1.2vw, 16px);
  line-height: 1.4;
  color: #ffffff;
}

.slide-share-prompt[data-state="success"] .slide-share-form {
  display: none;
}

.slide-share-prompt[data-state="success"] .slide-share-success {
  display: block;
}

/* Signature: name + direct email below the form. Reads as a "or
   reach out directly" footnote — mailto link on the email gives an
   alternative path for anyone who'd rather skip the form. */
.slide-share-signature {
  margin: 0;
  text-align: center;
  font-size: 15px;
  line-height: 1.4;
  color: #8e8e93;
  display: flex;
  justify-content: center;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  /* Slots into the intro-content column where the tagline lives on
     the intro slide, so the mark gets centered with the signature
     filling the "subtitle" slot. Fades in with the same timing as the
     intro headline (matches the existing `.slide-intro-headline`
     stage rule). */
  transition: opacity 2s ease;
}

.slide-intro[data-stage="entering"] .slide-share-signature {
  opacity: 0;
}

.slide-share-signature-name {
  color: #c8c8cc;
  font-weight: 500;
}

.slide-share-signature-sep {
  color: #4a4a4f;
}

.slide-share-signature-email {
  color: #03bdff;
  text-decoration: none;
  transition: color 0.15s ease;
}

.slide-share-signature-email:hover {
  color: #00ffd1;
}

/* Light theme. */
.deck-body[data-theme="light"] .slide-share-success {
  color: #15171c;
}
.deck-body[data-theme="light"] .slide-share-signature {
  color: #6e6e73;
}
.deck-body[data-theme="light"] .slide-share-signature-name {
  color: #15171c;
}
.deck-body[data-theme="light"] .slide-share-signature-sep {
  color: #b8b8bb;
}
.deck-body[data-theme="light"] .slide-share-signature-email {
  color: #0387b8;
}
.deck-body[data-theme="light"] .slide-share-signature-email:hover {
  color: #009d80;
}

/* Just-signed-in fade-out. When the auth flag flips to true the
 * `[data-authenticated="false"]` rule above stops applying and the
 * default `display: none` would yank the passcode out instantly.
 * The exiting class keeps it visible (display: block) while opacity
 * transitions 1 → 0. Position / width / max-width are inherited from
 * the base rule — they don't change between gated and exiting. */
.slide-intro-passcode.slide-intro-passcode--exiting {
  display: block;
  opacity: 0 !important;
  pointer-events: none;
  transition: opacity 1s ease;
}

/* The passcode form on the legacy landing page is a horizontal row
 * with a "Learn more" button to the right of the boxes. The slide-
 * embedded gate auto-submits as soon as six digits are typed, so the
 * row collapses to a centered, single-column stack: a centered
 * "Passcode" label with the six input boxes centered below it. */
.slide-intro-passcode .passcode-row {
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0;
}

.slide-intro-passcode .passcode-field {
  align-items: center;
}

.slide-intro-passcode .passcode-label {
  width: auto;
  text-align: center;
}

/* Small breathing room between the form area and the toggle row. */
.slide-intro-passcode .passcode-toggle {
  margin-top: 12px;
}

/* Vertical stability: reserve a consistent slot for the active form
 * so the toggle below stays at the same y in both views. The
 * passcode view uses passcode-row (~72px with the 15px label) + 4px
 * gap + error-msg (20px) ≈ 96px. `flex-start` anchors the request
 * form's label + input at the top of the slot so they share the
 * exact y position as the passcode label + boxes (which sit at the
 * top of their own row). The leftover height inside the slot lands
 * below the input as breathing room before the toggle. */
.slide-intro-passcode .request-form {
  min-height: 96px;
  justify-content: flex-start;
}

/* Request-access form is a single email input with the submit button
 * embedded inside it on the right (arrow icon). The wrap is the
 * positioning anchor; the input gets right padding so the typed text
 * doesn't sit underneath the button. */
.slide-intro-passcode .request-input-wrap,
.slide-share-prompt .request-input-wrap {
  position: relative;
  width: 100%;
  max-width: 280px;
  margin: 0 auto;
}

.slide-intro-passcode .request-input,
.slide-share-prompt .request-input {
  width: 100%;
  padding-right: 44px;
}

/* Slightly larger input on the share slide — feels more inviting
   given it's the only input on the slide and the rest of the layout
   has more breathing room. */
.slide-share-prompt .request-input-wrap {
  max-width: 350px;
}

.slide-share-prompt .request-input {
  height: 50px;
  font-size: 16px;
  padding-left: 16px;
}

.slide-intro-passcode .request-submit-btn,
.slide-share-prompt .request-submit-btn {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: #00bdff;
  cursor: pointer;
  border-radius: 4px;
  padding: 0;
  transition: background 0.15s ease, color 0.15s ease;
}

.slide-intro-passcode .request-submit-btn:hover,
.slide-share-prompt .request-submit-btn:hover {
  background: rgba(0, 189, 255, 0.12);
}

.slide-intro-passcode .request-submit-btn:disabled,
.slide-share-prompt .request-submit-btn:disabled {
  opacity: 0.5;
  cursor: default;
}

.slide-intro-passcode .request-submit-btn svg,
.slide-share-prompt .request-submit-btn svg {
  width: 16px;
  height: 16px;
  display: block;
}

.deck-body[data-theme="light"] .slide-intro-passcode .request-submit-btn,
.deck-body[data-theme="light"] .slide-share-prompt .request-submit-btn {
  color: #00a8dc;
}
.deck-body[data-theme="light"] .slide-intro-passcode .request-submit-btn:hover,
.deck-body[data-theme="light"] .slide-share-prompt .request-submit-btn:hover {
  background: rgba(0, 168, 220, 0.1);
}

/* Light-mode overrides for the slide-embedded passcode form. The
 * base styles (in styles.css) are tuned for the dark landing page
 * with white text + white borders; in light mode we need dark text
 * + dark borders so the form is legible on the off-white slide bg.
 * Scoped to .slide-intro-passcode so the legacy landing page (which
 * never renders in light mode) is untouched. */
.deck-body[data-theme="light"] .slide-intro-passcode .passcode-label {
  color: #212121;
  font-weight: 500;
}
.deck-body[data-theme="light"] .slide-intro-passcode .passcode-box {
  border-color: rgba(0, 0, 0, 0.75);
  color: #212121;
}
.deck-body[data-theme="light"] .slide-intro-passcode .passcode-box:focus {
  border-color: #00a8dc;
}
.deck-body[data-theme="light"] .slide-intro-passcode .request-label,
.deck-body[data-theme="light"] .slide-share-prompt .request-label {
  color: #212121;
  font-weight: 500;
}
.deck-body[data-theme="light"] .slide-intro-passcode .request-input,
.deck-body[data-theme="light"] .slide-share-prompt .request-input {
  border-color: rgba(0, 0, 0, 0.32);
  color: #212121;
}
.deck-body[data-theme="light"] .slide-intro-passcode .request-input::placeholder,
.deck-body[data-theme="light"] .slide-share-prompt .request-input::placeholder {
  color: rgba(0, 0, 0, 0.4);
}
.deck-body[data-theme="light"] .slide-intro-passcode .request-input:focus,
.deck-body[data-theme="light"] .slide-share-prompt .request-input:focus {
  border-color: #00a8dc;
}
.deck-body[data-theme="light"] .slide-intro-passcode .passcode-toggle {
  color: #4a4a4f;
}
.deck-body[data-theme="light"] .slide-intro-passcode .passcode-toggle-btn {
  color: #00a8dc;
}
/* Success message: brand-blue and pushed down a bit so it sits in
 * the middle of the form area instead of crowding the top edge.
 * Selectors include `[data-view="success"]` so they outrank the base
 * styles.css rule (which sets color: #ffffff and margin: 0). */
.slide-intro-passcode .passcode-group[data-view="success"] .request-success {
  margin-top: 24px;
  color: #00bdff;
}

.deck-body[data-theme="light"]
  .slide-intro-passcode
  .passcode-group[data-view="success"]
  .request-success {
  color: #00a8dc;
}

/* ===========
   Slide 2 — Why now
   =========== */

.slide-why-now {
  display: flex;
  flex-direction: column;
  min-height: 70vh;
  gap: 28px;
}

/* Beat 1 → 2 transition: the header slides up from the slide's vertical
   center to its natural top-of-flow position; the quote stack fades in
   behind it. The 50% in the calc is relative to the header's own height
   (transform percentages are self-relative), so the header's visual
   midpoint lands at the slide's 35vh midline. */
.slide-why-now-header {
  transition: transform 1.1s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-wrapper[data-current-beat="1"] .slide-why-now-header {
  transform: translateY(calc(35vh - 50%));
}

.slide-why-now-headline {
  font-size: clamp(36px, 4.8vw, 64px);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -1.2px;
  color: #ffffff;
  max-width: 900px;
}

/* Headline swap: "Writing code was" → "Now the platform is" on beat 5
   (the DORA card). Both phrases occupy the same inline-grid cell so
   they overlap and the surrounding text ("the bottleneck.") doesn't
   shift. The old phrase slides out to the right while the new phrase
   slides in from the left — paired with a quick fade so each crosses
   in/out of the swap area cleanly. */
.headline-swap {
  display: inline-grid;
  overflow: hidden;
  /* Line-height padding so descenders (e.g. the "gg" in "struggle")
     aren't clipped by the overflow. */
  padding-bottom: 0.18em;
}
.headline-was,
.headline-now {
  grid-area: 1 / 1;
  transition: transform 0.55s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.4s ease;
}
.headline-was {
  transform: translateX(0);
  opacity: 1;
}
.headline-now {
  transform: translateX(-100%);
  opacity: 0;
  color: #ffdd04;
}
.slide-wrapper[data-slide-id="why-now"][data-current-beat="5"] .headline-was,
.slide-wrapper[data-slide-id="problem"][data-current-beat="3"] .headline-was,
.slide-wrapper[data-slide-id="how-its-going"][data-current-beat="4"] .headline-was {
  transform: translateX(100%);
  opacity: 0;
}
.slide-wrapper[data-slide-id="why-now"][data-current-beat="5"] .headline-now,
.slide-wrapper[data-slide-id="problem"][data-current-beat="3"] .headline-now,
.slide-wrapper[data-slide-id="how-its-going"][data-current-beat="4"] .headline-now {
  transform: translateX(0);
  opacity: 1;
}

/* Quote stack: a positioned region below the header. Each quote-card
   stacks at bottom-right; only the card matching the current beat is
   visible (cross-fade between beats). */
.quote-stack {
  position: relative;
  flex: 1;
  min-height: 280px;
  transition: opacity 0.5s ease 0.5s;
}
.slide-wrapper[data-current-beat="1"] .quote-stack {
  opacity: 0;
  pointer-events: none;
}

.quote-card {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 88%;
  max-width: 1000px;
  background: #15171c;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 14px;
  padding: 36px 44px;
  display: flex;
  flex-direction: column;
  gap: 20px;
  opacity: 0;
  transform: translate(-50%, calc(-50% + 12px));
  transition: opacity 0.35s ease, transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
  pointer-events: none;
}

.slide-wrapper[data-current-beat="2"] .quote-karpathy,
.slide-wrapper[data-current-beat="3"] .quote-ronacher,
.slide-wrapper[data-current-beat="4"] .quote-galbraith,
.slide-wrapper[data-current-beat="5"] .quote-dora,
.slide-wrapper[data-current-beat="2"] .quote-gartner,
.slide-wrapper[data-current-beat="3"] .quote-anthropic,
.slide-wrapper[data-current-beat="4"] .quote-pocketos {
  opacity: 1;
  transform: translate(-50%, -50%);
  pointer-events: auto;
}

.quote-card blockquote {
  font-size: clamp(22px, 2.1vw, 32px);
  line-height: 1.35;
  color: #ededee;
  font-weight: 400;
  margin: 0;
}

.quote-card blockquote em {
  color: #03bdff;
  font-style: normal;
  font-weight: 600;
}

.quote-karpathy blockquote em {
  color: #ffdd04;
  font-weight: 700;
}

.quote-source {
  align-self: flex-end;
  text-align: right;
}

.quote-card figcaption {
  display: flex;
  align-items: center;
  gap: 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  padding-top: 24px;
  margin-top: 8px;
}

.quote-avatar {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
}

.quote-avatar-dora,
.quote-avatar-logo {
  border-radius: 6px;
  object-fit: contain;
  background: #ffffff;
  padding: 4px;
}

.quote-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.quote-card figcaption strong {
  font-size: 19px;
  font-weight: 700;
  color: #ffffff;
  margin-bottom: 6px;
}

.quote-cred {
  font-size: 12px;
  font-weight: 400;
  color: #8e8e93;
  line-height: 1.4;
}

.quote-source {
  font-size: 12px;
  font-weight: 600;
  color: #6e6e73;
  letter-spacing: 0.3px;
  text-transform: uppercase;
}

/* Solid mint-cyan tinted dark — same vibe as the original linear
   gradient overlay, but opaque so the card doesn't show through to
   anything stacked behind it. */
.quote-dora {
  background: linear-gradient(135deg, #0e1f1d, #0a1418);
  border-color: rgba(0, 255, 209, 0.25);
}

/* PocketOS card uses the same hero-stat layout as DORA — pink-tinted
   gradient + smaller stat font (since "9 sec" is wider than "90%").
   Opaque pink-tinted dark gradient. */
.quote-pocketos {
  background: linear-gradient(135deg, #1f0c14, #15080d);
  border-color: rgba(254, 9, 109, 0.3);
}

.quote-pocketos .dora-stat-num {
  color: #fe096d;
  font-size: clamp(56px, 7vw, 88px);
  letter-spacing: -2px;
}

/* Hero stat banner: huge "90%" with its supporting line stacked
   beneath it, so the data point reads as the visual anchor of the
   closing beat. */
.dora-stat {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  padding-bottom: 8px;
}

.dora-stat-num {
  font-size: clamp(72px, 9vw, 120px);
  font-weight: 700;
  line-height: 0.95;
  color: #00ffd1;
  letter-spacing: -3px;
  flex-shrink: 0;
}

.dora-stat-label {
  font-size: clamp(14px, 1.3vw, 18px);
  font-weight: 500;
  color: #c8c8cc;
  line-height: 1.3;
  max-width: 240px;
}

.quote-dora blockquote em {
  color: #00ffd1;
  font-weight: 600;
}

/* Desktop layout for the closing-beat cards (DORA on slide 2, PocketOS
   on slide 4): stat banner + quote share the top row, with source and
   attribution stacking full-width below. Mobile inherits the default
   vertical column layout from .quote-card. */
@media (min-width: 901px) {
  .quote-dora,
  .quote-pocketos {
    /* Card fills the slide content width (vs the smaller cap on the
       other cards) so the side-by-side stat + quote layout has room.
       Explicit width: 100% is required because the card is
       position:absolute without `right` — without it the card's
       shrink-to-fit width would still be capped at the smaller cards'
       max-width. The card's left/right edges now line up with the
       slide's content edges, matching the headline above. */
    width: 100%;
    max-width: none;
    display: grid;
    grid-template-columns: minmax(220px, 280px) minmax(0, 1fr);
    grid-template-areas:
      "stat   quote"
      "source source"
      "fig    fig";
    column-gap: 36px;
    row-gap: 20px;
  }
  .quote-dora .dora-stat,
  .quote-pocketos .dora-stat {
    grid-area: stat;
    padding-bottom: 0;
    align-self: center;
  }
  .quote-dora blockquote,
  .quote-pocketos blockquote {
    grid-area: quote;
    align-self: center;
  }
  .quote-dora .quote-source,
  .quote-pocketos .quote-source {
    grid-area: source;
  }
  .quote-dora figcaption,
  .quote-pocketos figcaption {
    grid-area: fig;
  }
}

/* ===========
   Slide 3 — The problem
   =========== */

/* Mirrors slide-why-now's beat-1 → beat-2 transition: header is
   vertically centered on beat 1, then slides up to its natural
   top-of-flow position when the slide advances. Height is fixed at
   70vh (matching slide 2's nominal height) so the header lands in the
   same screen position when it settles, regardless of how much
   mosaic content sits below. The mosaic gets the remaining 1fr row
   and clips its overflow so the slide doesn't grow. */
.slide-problem {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  height: 70vh;
  gap: 28px;
}

.slide-problem-headline {
  font-size: clamp(36px, 4.8vw, 64px);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -1.2px;
  color: #ffffff;
  max-width: 900px;
}

.slide-problem-header {
  transition: transform 1.1s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-wrapper[data-current-beat="1"] .slide-problem-header {
  transform: translateY(calc(35vh - 50%));
}

/* Mosaic of category cards. On beat 2 they fade + slide in with
   per-card pseudo-random delays (set via --card-delay inline) so the
   reveal feels like tools raining onto the slide rather than a tidy
   cascade — that's the visceral "look at all this stuff" beat. The
   mosaic clips at the slide's bottom edge; cards that don't fit hint
   at "there is more" rather than pushing the slide taller. */
/* The mosaic is a fixed visible window; the track inside is the actual
   grid of cards. Splitting the two lets us scroll/animate the track
   while keeping the mosaic's clipped viewport, edge fades, and any
   absolutely positioned overlays anchored to the visible region. */
.problem-mosaic {
  position: relative;
  overflow: hidden;
  /* Soft fade at the bottom edge so the bottom row trails off rather
     than ending in a hard line — reinforces the "and there's even more"
     undertone. */
  mask-image: linear-gradient(to bottom, #000 84%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, #000 84%, transparent 100%);
}

.problem-mosaic-track {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 18px;
  align-content: start;
  position: relative;
}

/* Cloned cards exist only for the mobile vertical-marquee. On desktop
   they're hidden so the static 5-column grid only shows the originals. */
.problem-card-clone {
  display: none;
}

/* Bucket card (default state hidden). On beat 2+ it's revealed with the
   per-card pseudo-random delay set inline. The :not(.problem-card-agents)
   guard keeps the agents card from animating in alongside the bucket
   cards — it has its own beat-3 reveal below. */
.problem-card {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 10px;
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  position: relative;
  z-index: 1;
}

/* Default (beat 1) state. The transition has no delay so navigating
   *backwards* into beat 1 from beat 2/3 retracts the cards quickly,
   without the staggered delays that the forward entry uses (those
   would otherwise leave cards visible long after the headline has
   slid back down). */
.problem-card:not(.problem-card-agents) {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.25s ease, transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), border-color 0.25s
    ease;
}

/* Forward state (beat 2 / 3): apply the per-card stagger via
   transition-delay so the entry feels like cards "raining in." The
   700ms base offset on each card's delay holds the entire stagger
   until the headline (1.1s slide-up) has mostly landed — matches the
   team and timeline slides. */
.slide-wrapper[data-current-beat="2"] .problem-card:not(.problem-card-agents),
.slide-wrapper[data-current-beat="3"] .problem-card:not(.problem-card-agents) {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.55s ease calc(var(--card-delay, 0ms) + 0.7s), transform 0.55s
    cubic-bezier(0.4, 0, 0.2, 1) calc(var(--card-delay, 0ms) + 0.7s), border-color 0.9s ease
    calc(var(--card-color-delay, 0ms) + 0.7s);
}

/* Beat 3: cards' borders cascade outward from the agents cell, ring by
   ring. Each card's --card-color and --card-color-delay are set inline
   based on its grid position so the cards fan out in a wave that ends
   at the same moment the agents card itself appears below. */
.slide-wrapper[data-current-beat="3"] .problem-card:not(.problem-card-agents) {
  border-color: var(--card-color, rgba(255, 255, 255, 0.07));
}

/* AI agents card — sits in the center of the mosaic, hidden on beats
   1 and 2, revealed on beat 3 with a scale-in + soft glow. Connection
   lines fan out from its center to every bucket card. */
.problem-card-agents {
  background: linear-gradient(135deg, rgba(255, 221, 4, 0.1), rgba(3, 189, 255, 0.08));
  border-color: rgba(255, 255, 255, 0.18);
  align-items: center;
  justify-content: center;
  text-align: center;
  opacity: 0;
  transform: scale(0.92);
  transition: opacity 0.4s ease, transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
  z-index: 3;
}

.problem-card-agents .problem-card-title {
  font-size: 14px;
  color: #ffffff;
  letter-spacing: 0.8px;
}

/* Four agent dots, one in each corner of the agents card, color-coded
   to match the connection-line color in that quadrant. */
.agent-dot {
  position: absolute;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  box-shadow: 0 0 8px 0 currentColor;
}
.agent-dot-tl {
  top: 8px;
  left: 8px;
  background: #ffdd04;
  color: #ffdd04;
}
.agent-dot-tr {
  top: 8px;
  right: 8px;
  background: #fe096d;
  color: #fe096d;
}
.agent-dot-bl {
  bottom: 8px;
  left: 8px;
  background: #03bdff;
  color: #03bdff;
}
.agent-dot-br {
  bottom: 8px;
  right: 8px;
  background: #00ffd1;
  color: #00ffd1;
}

.slide-wrapper[data-current-beat="3"] .problem-card-agents {
  opacity: 1;
  transform: scale(1);
}

.problem-card-title {
  font-size: 13px;
  font-weight: 700;
  color: #ffffff;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  line-height: 1.2;
}

.problem-card-items {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.problem-card-item {
  font-size: 12px;
  font-weight: 500;
  color: #a8a8af;
  line-height: 1.35;
}

/* Mobile: 5-column circuit-board layout doesn't work on a phone (cards
   become unreadable, the agents card and dotted lines have nowhere to
   route). Drop to a denser 3-column grid, hide the agents card +
   connections, and let the cards just stack as a wall of categories —
   the headline swap on beat 3 still carries the AI message. */
@media (max-width: 900px) {
  /* Top + bottom fade so cards drift in/out of the visible window. */
  .problem-mosaic {
    mask-image: linear-gradient(to bottom, transparent 0%, #000 12%, #000 88%, transparent 100%);
    -webkit-mask-image: linear-gradient(
      to bottom,
      transparent 0%,
      #000 12%,
      #000 88%,
      transparent 100%
    );
  }
  .problem-mosaic-track {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
    /* Continuously translate the track upward. The track contains two
       copies of the cards back-to-back, so a -50% translate brings the
       second copy into the first copy's position — the snap-back to
       0% at the end of each iteration is then visually identical. */
    animation: problem-mosaic-scroll 48s linear infinite;
  }
  .problem-card-clone {
    display: flex;
  }
  /* On mobile we don't show the agents card (no space for it) and we
     drop the last bucket (and its clone) so the visible track is an
     even 36 cards = exactly 18 rows in 2 cols. The translateY(-50%)
     marquee then snaps on a clean row boundary — no mid-row flash. */
  .problem-card-agents,
  .problem-mosaic-track > .problem-card:nth-child(20),
  .problem-mosaic-track > .problem-card:last-child {
    display: none;
  }
  /* On mobile, override the desktop's "4 specific cards highlighted"
     pattern with a regular every-3rd-card rotation through the four
     brand colors. The !important is needed to win over the inline
     --card-color / --card-color-delay set on the desktop-highlighted
     cards. */
  .problem-mosaic-track > .problem-card {
    --card-color: rgba(255, 255, 255, 0.07) !important;
    --card-color-delay: 0ms !important;
  }
  .problem-mosaic-track > .problem-card:nth-child(12n + 3) {
    --card-color: #03bdff !important;
    --card-color-delay: 600ms !important;
  }
  .problem-mosaic-track > .problem-card:nth-child(12n + 6) {
    --card-color: #00ffd1 !important;
    --card-color-delay: 1200ms !important;
  }
  .problem-mosaic-track > .problem-card:nth-child(12n + 9) {
    --card-color: #ffdd04 !important;
    --card-color-delay: 1800ms !important;
  }
  .problem-mosaic-track > .problem-card:nth-child(12n + 12) {
    --card-color: #fe096d !important;
    --card-color-delay: 2400ms !important;
  }
  .problem-card {
    padding: 10px 12px;
    gap: 6px;
  }
  .problem-card-title {
    font-size: 11px;
  }
  .problem-card-item {
    font-size: 10px;
    line-height: 1.3;
  }
}

@keyframes problem-mosaic-scroll {
  from {
    transform: translateY(0);
  }
  to {
    transform: translateY(-50%);
  }
}

/* ===========
   Slide 4 — How it's going
   =========== */

.slide-how-its-going {
  display: flex;
  flex-direction: column;
  min-height: 70vh;
  gap: 28px;
}

/* Beat 1: only the header is shown, vertically centered in the slide.
   The quote-stack is faded out via the shared rule, but still occupies
   its layout space — that's deliberate so the header lands at the same
   on-screen position as slides 2 and 3 once the transform is applied. */
.slide-wrapper[data-current-beat="1"] .slide-how-its-going {
  justify-content: center;
}

.slide-how-its-going-headline {
  font-size: clamp(36px, 4.8vw, 64px);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -1.2px;
  color: #ffffff;
  max-width: 900px;
}

.slide-how-its-going-header {
  transition: transform 1.1s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-wrapper[data-current-beat="1"] .slide-how-its-going-header {
  transform: translateY(calc(35vh - 50%));
}

/* Setup paragraph that introduces the PocketOS card before its quote. */
.quote-setup {
  font-size: clamp(14px, 1.1vw, 17px);
  line-height: 1.5;
  color: #c8c8cc;
  margin: 0;
}

.quote-setup em {
  color: #fe096d;
  font-style: normal;
  font-weight: 700;
}

/* Color the inline emphasis per evidence card. */
.quote-gartner blockquote em {
  color: #03bdff;
  font-weight: 600;
}
.quote-anthropic blockquote em {
  color: #03bdff;
  font-weight: 600;
}
.quote-pocketos blockquote em {
  color: #fe096d;
  font-weight: 600;
}

/* ===========
   Slide 5 — The Solution (light mode + 3D card carousel)
   =========== */

/* Theme-aware brand color variables. Slides reference these via
   var(--brand-yellow) etc. so accent borders, dots, and glows shift
   between the bright dark-mode palette and the contrast-tuned light-
   mode palette automatically. */
.deck-body {
  --brand-yellow: #ffdd04;
  --brand-pink: #fe096d;
  --brand-cyan: #03bdff;
  --brand-mint: #00ffd1;
}

/* Light-theme overrides for the body + chrome. Triggered globally by
   the user toggling theme=light (not by which slide is shown). When
   theme=dark (the default), the existing dark-mode styles defined
   elsewhere apply unmodified. */
.deck-body[data-theme="light"] {
  background: #f3f3f0;
  color: #212121;
  --brand-yellow: #e8b400;
  --brand-pink: #ec0f6d;
  --brand-cyan: #00a8dc;
  --brand-mint: #00b98c;
}

.deck-body {
  transition: background-color 0.5s ease, color 0.5s ease;
}

.deck-logo-forward,
.deck-logo-steady,
.deck-progress-dot,
.deck-notes-toggle,
.deck-theme-toggle {
  transition: color 0.5s ease, background-color 0.5s ease, border-color 0.5s ease;
}

.deck-body[data-theme="light"] .deck-logo-forward {
  color: #212121;
}
.deck-body[data-theme="light"] .deck-logo-steady {
  color: #8e8e93;
}
.deck-body[data-theme="light"] .deck-progress-dot {
  background: rgba(0, 0, 0, 0.18);
}
.deck-body[data-theme="light"] .deck-progress-dot[data-state="past"] {
  background: rgba(0, 0, 0, 0.45);
}
.deck-body[data-theme="light"] .deck-progress-dot[data-state="current"] {
  background: #00a8dc;
}
.deck-body[data-theme="light"] .slide-eyebrow {
  color: #00a8dc;
}
.deck-body[data-theme="light"] .deck-notes-toggle,
.deck-body[data-theme="light"] .deck-theme-toggle {
  color: #6e6e73;
  background: rgba(0, 0, 0, 0.03);
}
.deck-body[data-theme="light"] .deck-notes-toggle:hover,
.deck-body[data-theme="light"] .deck-theme-toggle:hover {
  background: rgba(0, 0, 0, 0.06);
  color: #212121;
}
.deck-body[data-theme="light"] .deck-notes-toggle-key {
  background: rgba(0, 0, 0, 0.06);
}

/* Speaker notes popover — re-themed for light mode so the panel
   matches the surrounding chrome instead of staying dark. */
.deck-body[data-theme="light"] .deck-notes {
  background: rgba(255, 255, 255, 0.96);
  border-color: rgba(0, 0, 0, 0.08);
  box-shadow: 0 6px 14px rgba(0, 0, 0, 0.04);
}
.deck-body[data-theme="light"] .deck-notes-block-header {
  color: #00a8dc;
}
.deck-body[data-theme="light"] .deck-notes-block[data-state="pending"] .deck-notes-block-header {
  color: #9a9aa0;
}
.deck-body[data-theme="light"] .deck-notes-block-body p {
  color: #212121;
}

/* Logo dots are inline-styled in deck.html with the dark-mode brand
   colors. In light mode we shift them to the contrast-tuned palette
   so they read against the warm-gray bg. Applies to both the chrome
   logo (.logo-dot) and the intro-slide mark (.slide-intro-mark-dot),
   which share the same 2x2 row layout. */
.deck-body[data-theme="light"] .logo-dots-row:nth-child(1) .logo-dot:nth-child(1),
.deck-body[data-theme="light"]
  .slide-intro-mark-row:nth-child(1)
  .slide-intro-mark-dot:nth-child(1) {
  background-color: #e8b400 !important; /* yellow */
}
.deck-body[data-theme="light"] .logo-dots-row:nth-child(1) .logo-dot:nth-child(2),
.deck-body[data-theme="light"]
  .slide-intro-mark-row:nth-child(1)
  .slide-intro-mark-dot:nth-child(2) {
  background-color: #ec0f6d !important; /* pink */
}
.deck-body[data-theme="light"] .logo-dots-row:nth-child(2) .logo-dot:nth-child(1),
.deck-body[data-theme="light"]
  .slide-intro-mark-row:nth-child(2)
  .slide-intro-mark-dot:nth-child(1) {
  background-color: #00a8dc !important; /* cyan */
}
.deck-body[data-theme="light"] .logo-dots-row:nth-child(2) .logo-dot:nth-child(2),
.deck-body[data-theme="light"]
  .slide-intro-mark-row:nth-child(2)
  .slide-intro-mark-dot:nth-child(2) {
  background-color: #00b98c !important; /* mint */
}
.logo-dot,
.slide-intro-mark-dot {
  transition: background-color 0.5s ease;
}

/* Light-theme overrides for slide-specific elements that were authored
   for the dark default. Each rule flips the smallest necessary set of
   color values; visual structure and spacing stay shared. */

/* Headlines on the dark-default slides flip to dark text. */
.deck-body[data-theme="light"] .slide-why-now-headline,
.deck-body[data-theme="light"] .slide-problem-headline,
.deck-body[data-theme="light"] .slide-how-its-going-headline,
.deck-body[data-theme="light"] .slide-intro-headline,
.deck-body[data-theme="light"] .slide-solution-headline {
  color: #212121;
}

/* Headline-swap emphasis (the yellow word that flies in on the
   "now"-state headline) shifts to the light-mode yellow. */
.deck-body[data-theme="light"] .headline-now {
  color: #e8b400;
}

/* Quote cards (why-now and the other quote-driven slides) — flip
   bg/border to dark-on-light, body text to near-black, em accents to
   light-mode brand colors. */
.deck-body[data-theme="light"] .quote-card {
  background: #ebebe7;
  border-color: rgba(0, 0, 0, 0.08);
}
.deck-body[data-theme="light"] .quote-dora {
  background: linear-gradient(135deg, #e3f2ed, #dde8eb);
  border-color: rgba(0, 185, 140, 0.28);
}
.deck-body[data-theme="light"] .quote-pocketos {
  background: linear-gradient(135deg, #f0e8e9, #ebe1e3);
  border-color: rgba(140, 30, 60, 0.18);
}
.deck-body[data-theme="light"] .quote-card blockquote {
  color: #212121;
}
.deck-body[data-theme="light"] .quote-card blockquote em {
  color: #00a8dc;
}
.deck-body[data-theme="light"] .quote-karpathy blockquote em {
  color: #e8b400;
}
.deck-body[data-theme="light"] .quote-pocketos blockquote em {
  color: #ec0f6d;
}
.deck-body[data-theme="light"] .quote-pocketos .dora-stat-num {
  color: #ec0f6d;
}
.deck-body[data-theme="light"] .quote-card figcaption {
  border-top-color: rgba(0, 0, 0, 0.08);
}
.deck-body[data-theme="light"] .quote-card figcaption strong {
  color: #212121;
}
.deck-body[data-theme="light"] .quote-cred,
.deck-body[data-theme="light"] .quote-source {
  color: #6e6e73;
}
.deck-body[data-theme="light"] .quote-avatar {
  background: rgba(0, 0, 0, 0.04);
  border-color: rgba(0, 0, 0, 0.08);
}

/* Dora stat. */
.deck-body[data-theme="light"] .dora-stat-num,
.deck-body[data-theme="light"] .dora-stat-label {
  color: #212121;
}

/* Intro slide: mark text — flip to dark equivalents so the wordmark
   reads on a light bg. */
.deck-body[data-theme="light"] .slide-intro-mark-forward {
  color: #212121;
}
.deck-body[data-theme="light"] .slide-intro-mark-steady {
  color: #8e8e93;
}

/* Solution-slide pillar cards — flip the outer card from the dark-
   mode subtle-white-on-dark scheme back to the white-on-light scheme
   the cards were originally designed around. The verbs (build /
   review / run) keep their --accent colors in both themes. */
.deck-body[data-theme="light"] .solution-card {
  background: #ffffff;
  border-color: rgba(0, 0, 0, 0.06);
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.08);
}
.deck-body[data-theme="light"] .solution-card-section-title {
  color: #212121;
}
.deck-body[data-theme="light"] .solution-card-section-desc {
  color: #4a4a4f;
}

/* ============================================================
   Solution-slide demo panes — dark-mode overrides
   ============================================================
   The chat / review-PR / run-metric demos were authored for a white
   card on a light page. In dark mode the outer card is now dark,
   so flip every internal card / bubble / pane and its text to
   dark-themed equivalents. Layout / spacing / accent shapes stay
   shared. */

/* Demo pane backdrop + bottom-fade gradient. Pane is solid (not
   transparent) because the carousel stacks cards behind it; an
   rgba pane would let the back cards show through. */
.deck-body[data-theme="dark"] .solution-card-demo-pane {
  background: #1d1f25;
  border-color: rgba(255, 255, 255, 0.08);
}
.deck-body[data-theme="dark"] .solution-card-demo-pane::after {
  background: linear-gradient(to bottom, rgba(29, 31, 37, 0), #1d1f25);
}

/* ---- Build chat ---- */
.deck-body[data-theme="dark"] .chat-avatar {
  background: rgba(255, 255, 255, 0.08);
  color: #c8c8cc;
}
.deck-body[data-theme="dark"] .chat-avatar-photo {
  background: transparent;
}
.deck-body[data-theme="dark"] .chat-bubble {
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.12);
}
.deck-body[data-theme="dark"] .chat-bubble-agent {
  background: rgba(232, 180, 0, 0.1);
  border-color: rgba(232, 180, 0, 0.35);
}
.deck-body[data-theme="dark"] .chat-author {
  color: #9a9a9f;
}
.deck-body[data-theme="dark"] .chat-text {
  color: #ededee;
}
.deck-body[data-theme="dark"] .chat-text code {
  background: rgba(255, 255, 255, 0.1);
  color: #ededee;
}
.deck-body[data-theme="dark"] .service-ref-icon {
  color: #b8b8bb;
}

.deck-body[data-theme="dark"] .service-card {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.1);
}
.deck-body[data-theme="dark"] .service-name {
  color: #ededee;
}
.deck-body[data-theme="dark"] .service-stack {
  color: #9a9a9f;
}
.deck-body[data-theme="dark"] .service-endpoints {
  color: #b8b8bb;
}
.deck-body[data-theme="dark"] .service-mod {
  background: rgba(0, 163, 110, 0.18);
  color: #34d399;
}

.deck-body[data-theme="dark"] .option-card {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.1);
}
.deck-body[data-theme="dark"] .option-name {
  color: #ededee;
}
.deck-body[data-theme="dark"] .option-desc {
  color: #9a9a9f;
}

.deck-body[data-theme="dark"] .endpoint-detail {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.1);
}
.deck-body[data-theme="dark"] .endpoint-header {
  color: #ededee;
}
.deck-body[data-theme="dark"] .endpoint-response {
  color: #b8b8bb;
}

.deck-body[data-theme="dark"] .agent-action {
  background: #ffffff;
  color: #212121;
}
.deck-body[data-theme="dark"] .agent-action:hover {
  background: #d4d4d8;
}

/* ---- Review PR view ---- */
.deck-body[data-theme="dark"] .review-pr-meta,
.deck-body[data-theme="dark"] .review-pr-author {
  color: #9a9a9f;
}
.deck-body[data-theme="dark"] .review-pr-title {
  color: #ededee;
}

.deck-body[data-theme="dark"] .review-preview {
  background: rgba(0, 168, 220, 0.12);
  border-color: rgba(0, 168, 220, 0.4);
}
.deck-body[data-theme="dark"] .review-preview:hover {
  background: rgba(0, 168, 220, 0.2);
  border-color: rgba(0, 168, 220, 0.55);
}
.deck-body[data-theme="dark"] .review-preview-label {
  color: #ededee;
}
.deck-body[data-theme="dark"] .review-preview-url {
  color: #66cef0;
}

.deck-body[data-theme="dark"] .review-tile {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.1);
}
.deck-body[data-theme="dark"] .review-tile-label {
  color: #9a9a9f;
}

.deck-body[data-theme="dark"] .review-section {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.08);
}
.deck-body[data-theme="dark"] .review-section-header {
  color: #9a9a9f;
}
.deck-body[data-theme="dark"] .review-line {
  color: #ededee;
}
.deck-body[data-theme="dark"] .review-line-meta {
  color: #9a9a9f;
}
.deck-body[data-theme="dark"] .review-line-add:hover,
.deck-body[data-theme="dark"] .review-line-mod:hover,
.deck-body[data-theme="dark"] .review-line-test:hover {
  background: rgba(255, 255, 255, 0.06);
}
.deck-body[data-theme="dark"] .review-line-add::after,
.deck-body[data-theme="dark"] .review-line-mod::after,
.deck-body[data-theme="dark"] .review-line-test::after {
  color: #6e6e73;
}
.deck-body[data-theme="dark"] .review-line-add:hover::after,
.deck-body[data-theme="dark"] .review-line-mod:hover::after,
.deck-body[data-theme="dark"] .review-line-test:hover::after {
  color: #ededee;
}
.deck-body[data-theme="dark"] .review-line-expanded {
  background: rgba(255, 255, 255, 0.05);
}
.deck-body[data-theme="dark"] .review-line-expanded::after {
  color: #ededee;
}
.deck-body[data-theme="dark"] .review-line-detail {
  background: rgba(0, 0, 0, 0.25);
  border-color: rgba(255, 255, 255, 0.08);
}
.deck-body[data-theme="dark"] .review-test-spec {
  color: #ededee;
}
.deck-body[data-theme="dark"] .review-test-step code {
  background: rgba(255, 255, 255, 0.08);
}

.deck-body[data-theme="dark"] .review-detail-action {
  border-color: rgba(255, 255, 255, 0.18);
  color: #ededee;
}
.deck-body[data-theme="dark"] .review-detail-action:hover {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.3);
}

/* Performance chart inside the review. */
.deck-body[data-theme="dark"] .review-perf-chart {
  background: rgba(255, 255, 255, 0.03);
  border-color: rgba(255, 255, 255, 0.08);
}
.deck-body[data-theme="dark"] .review-perf-value {
  color: #ededee;
}
.deck-body[data-theme="dark"] .review-perf-arrow {
  color: #9a9a9f;
}
.deck-body[data-theme="dark"] .review-perf-note {
  color: #9a9a9f;
}
.deck-body[data-theme="dark"] .review-perf-legend {
  color: #9a9a9f;
}

/* Comment thread inside the review. */
.deck-body[data-theme="dark"] .review-comment {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.1);
  border-left-color: #00a8dc;
}
.deck-body[data-theme="dark"] .review-comment-author {
  color: #ededee;
}
.deck-body[data-theme="dark"] .review-comment-time {
  color: #9a9a9f;
}
.deck-body[data-theme="dark"] .review-comment-body {
  color: #ededee;
}
.deck-body[data-theme="dark"] .review-comment-message + .review-comment-message {
  border-top-color: rgba(255, 255, 255, 0.12);
}

.deck-body[data-theme="dark"] .review-secondary {
  color: #ededee;
  border-color: rgba(255, 255, 255, 0.18);
}
.deck-body[data-theme="dark"] .review-secondary:hover {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.3);
}

/* ---- Run production view ---- */
.deck-body[data-theme="dark"] .run-service-name {
  color: #9a9a9f;
}
.deck-body[data-theme="dark"] .run-env {
  color: #9a9a9f;
  border-color: rgba(255, 255, 255, 0.18);
}

/* Metric card — was warm green-on-light; in dark mode use a deeper
   green-tinted bg so the "resolved / healthy" signal still reads. */
.deck-body[data-theme="dark"] .run-metric {
  background: rgba(0, 163, 110, 0.08);
  border-color: rgba(0, 163, 110, 0.35);
}
.deck-body[data-theme="dark"] .run-metric-title {
  color: #ededee;
}
.deck-body[data-theme="dark"] .run-metric-controls {
  background: rgba(255, 255, 255, 0.06);
}
.deck-body[data-theme="dark"] .run-metric-control {
  color: #b8b8bb;
}
.deck-body[data-theme="dark"] .run-metric-control:hover {
  color: #ededee;
}
.deck-body[data-theme="dark"] .run-metric-control-active {
  background: rgba(255, 255, 255, 0.16);
  color: #ededee;
}
.deck-body[data-theme="dark"] .run-metric-chart {
  background: rgba(0, 0, 0, 0.25);
  border-color: rgba(255, 255, 255, 0.06);
}
.deck-body[data-theme="dark"] .run-metric-label {
  color: #9a9a9f;
}
.deck-body[data-theme="dark"] .run-metric-value {
  color: #ededee;
}

/* Agent comment block inside the metric. */
.deck-body[data-theme="dark"] .run-comment {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(0, 163, 110, 0.25);
}
.deck-body[data-theme="dark"] .run-comment-author {
  color: #ededee;
}
.deck-body[data-theme="dark"] .run-comment-time {
  color: #9a9a9f;
}
.deck-body[data-theme="dark"] .run-comment-body {
  color: #ededee;
}
.deck-body[data-theme="dark"] .run-comment-body code {
  background: rgba(255, 255, 255, 0.08);
}

/* Filed Change Request CTA. */
.deck-body[data-theme="dark"] .run-fix {
  background: rgba(0, 163, 110, 0.12);
  border-color: rgba(0, 163, 110, 0.4);
}
.deck-body[data-theme="dark"] .run-fix:hover {
  background: rgba(0, 163, 110, 0.2);
  border-color: rgba(0, 163, 110, 0.55);
}
.deck-body[data-theme="dark"] .run-fix-label {
  color: #ededee;
}
.deck-body[data-theme="dark"] .run-fix-meta {
  color: #6ee7b8;
}

/* Problem-slide mosaic cards — flip bg / border / text to dark-on-
   light counterparts. The agents card keeps its decorative gradient. */
.deck-body[data-theme="light"] .problem-card {
  background: rgba(0, 0, 0, 0.03);
  border-color: rgba(0, 0, 0, 0.08);
}
.deck-body[data-theme="light"] .problem-card-agents {
  border-color: rgba(0, 0, 0, 0.18);
}
.deck-body[data-theme="light"] .problem-card-title {
  color: #212121;
}
.deck-body[data-theme="light"] .problem-card-item {
  color: #4a4a4f;
}
/* Agent dots (corners of the agents card) shift to the light-mode
   brand palette to stay readable on the light card. */
.deck-body[data-theme="light"] .agent-dot-tl {
  background: #e8b400;
  color: #e8b400;
}
.deck-body[data-theme="light"] .agent-dot-tr {
  background: #ec0f6d;
  color: #ec0f6d;
}
.deck-body[data-theme="light"] .agent-dot-bl {
  background: #00a8dc;
  color: #00a8dc;
}
.deck-body[data-theme="light"] .agent-dot-br {
  background: #00b98c;
  color: #00b98c;
}

/* Hint text at the bottom of the viewport. */
.deck-body[data-theme="light"] .deck-hint {
  color: rgba(0, 0, 0, 0.55);
}
.deck-body[data-theme="light"] .deck-hint kbd {
  background: rgba(0, 0, 0, 0.05);
  border-color: rgba(0, 0, 0, 0.12);
  color: #212121;
}

/* ===========
   Slide: Wedge
   =========== */

.slide-wedge {
  display: flex;
  flex-direction: column;
  min-height: 70vh;
  gap: 56px;
}

.slide-wedge-header {
  transition: transform 1.1s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-wrapper[data-current-beat="1"] .slide-wedge-header {
  transform: translateY(calc(35vh - 50%));
}

.slide-wedge-headline {
  font-size: clamp(36px, 4.8vw, 64px);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -1.2px;
  color: #ffffff;
  max-width: 900px;
}

/* Wedge content area — a single relative-positioned stage that hosts
   three absolutely-positioned cards (Profile / Friction / Lift) and
   a pricing block.

   Cards come in one at a time, left-aligned with the headline. The
   visible group is vertically centered in the stage:
     Beat 2 — Profile alone: centered at stage midline.
     Beat 3 — Profile + Friction: gap between them centered.
     Beat 4 — all three: middle card (Friction) at midline.
     Beat 5 — same as beat 4 + pricing slides in on the right.  */
.wedge-content {
  flex: 1;
  position: relative;
  min-height: 540px;
}

.wedge-card {
  position: absolute;
  top: 50%;
  left: 0;

  width: 440px;
  max-width: 48%;
  min-height: 150px;
  padding: 16px 22px 18px;

  background: #15171c;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 12px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.3);

  display: flex;
  flex-direction: column;
  gap: 10px;
  box-sizing: border-box;

  /* Default: card vertically centered at the stage midline (its own
     midline lands on stage's top: 50%), hidden. */
  transform: translateY(-50%);
  opacity: 0;

  transition: transform 0.55s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.4s ease;
}

.wedge-card-label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1.4px;
  color: #6e6e73;
}

.wedge-card-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.wedge-card-list li {
  position: relative;
  padding-left: 14px;
  font-size: clamp(13px, 1.05vw, 15px);
  line-height: 1.45;
  color: #d8d8db;
}

.wedge-card-list li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 0.6em;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: #6e6e73;
}

/* The Friction card gets a muted darker treatment so it reads as the
   "what's wrong today" frame. */
.wedge-card-friction {
  background: #1a1c22;
}

/* === Beat-driven reveal ===
   Each beat reveals one more card, and the visible group re-centers
   vertically as a unit. The y offsets use a fixed 170px unit (card
   min-height 150 + ~20px gap), so spacing is consistent regardless
   of bullet content length. Transforms are explicit (not CSS-var
   driven) so transitions animate reliably. */

/* Beat 2 — Profile only. Shifted up 50px from the stage midline so
   the single card doesn't feel low (wedge-content's geometric center
   sits below the slide's visual midline because the header takes
   space above it). */
.slide-wrapper[data-current-beat="2"] .wedge-card-profile {
  transform: translateY(calc(-50% - 50px));
  opacity: 1;
}

/* Beat 3 — Profile + Friction, group centered with the same 50px
   upward offset as beat 2 (gap between the two cards sits 50px
   above midline). */
.slide-wrapper[data-current-beat="3"] .wedge-card-profile {
  transform: translateY(calc(-50% - 135px));
  opacity: 1;
}
.slide-wrapper[data-current-beat="3"] .wedge-card-friction {
  transform: translateY(calc(-50% + 35px));
  opacity: 1;
}

/* Beats 4 & 5 — all three visible, Friction at midline. */
.slide-wrapper[data-current-beat="4"] .wedge-card-profile,
.slide-wrapper[data-current-beat="5"] .wedge-card-profile {
  transform: translateY(calc(-50% - 170px));
  opacity: 1;
}
.slide-wrapper[data-current-beat="4"] .wedge-card-friction,
.slide-wrapper[data-current-beat="5"] .wedge-card-friction {
  transform: translateY(-50%);
  opacity: 1;
}
.slide-wrapper[data-current-beat="4"] .wedge-card-lift,
.slide-wrapper[data-current-beat="5"] .wedge-card-lift {
  transform: translateY(calc(-50% + 170px));
  opacity: 1;
}

/* Pricing block — absolutely positioned on the right of the stage,
   hidden until beat 5. Slides in from the right when the three
   cards transition into the stacked left layout. */
.wedge-pricing {
  position: absolute;
  top: 0;
  right: 0;
  width: 42%;
  display: flex;
  flex-direction: column;
  gap: 14px;
  opacity: 0;
  transform: translateX(20%);
  transition: opacity 0.45s ease, transform 0.55s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-wrapper[data-current-beat="5"] .wedge-pricing {
  opacity: 1;
  transform: translateX(0);
  transition-delay: 0.25s;
}

/* Teams + Enterprise stubs — backgrounded so they read as "future
   tiers" without competing with Pro. Two columns on desktop, stack
   on mobile. */
.wedge-future {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.wedge-future-item {
  padding: 12px 16px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid rgba(255, 255, 255, 0.05);
}
.wedge-future-label {
  font-size: 11px;
  font-weight: 700;
  color: #9a9a9f;
  text-transform: uppercase;
  letter-spacing: 1.2px;
  margin-bottom: 3px;
}
.wedge-future-meta {
  font-size: clamp(12px, 0.95vw, 13px);
  color: #6e6e73;
  line-height: 1.4;
}

/* Directional-model disclaimer — small italic line below the pricing
   block. Honest about the early-stage shape of the model. */
.wedge-disclaimer {
  margin: 4px 0 0;
  font-size: clamp(11px, 0.9vw, 12.5px);
  font-style: italic;
  color: #9a9a9f;
  line-height: 1.5;
}

.wedge-tier {
  position: relative;
  background: #15171c;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 12px;
  padding: 22px 22px 24px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.wedge-tier-pro {
  border-color: rgba(0, 168, 220, 0.5);
  box-shadow: 0 0 0 1px rgba(0, 168, 220, 0.2);
}

.wedge-tier-badge {
  position: absolute;
  top: -10px;
  left: 18px;
  padding: 3px 8px;
  background: #00a8dc;
  color: #ffffff;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  border-radius: 4px;
}

.wedge-tier-name {
  font-size: 13px;
  font-weight: 700;
  color: #6e6e73;
  text-transform: uppercase;
  letter-spacing: 1.4px;
}

.wedge-tier-price {
  display: flex;
  align-items: baseline;
  gap: 4px;
  flex-wrap: wrap;
}

.wedge-tier-price-value {
  font-size: clamp(28px, 2.6vw, 38px);
  font-weight: 700;
  color: #ffffff;
  line-height: 1;
  letter-spacing: -1px;
}

.wedge-tier-price-unit {
  font-size: clamp(12px, 1vw, 14px);
  font-weight: 500;
  color: #b8b8bb;
}

.wedge-tier-tagline {
  font-size: clamp(13px, 1vw, 14px);
  color: #d8d8db;
  line-height: 1.4;
}

.wedge-tier-features {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.wedge-tier-features li {
  position: relative;
  padding-left: 16px;
  font-size: clamp(12px, 0.95vw, 13.5px);
  line-height: 1.4;
  color: #d8d8db;
}
.wedge-tier-features li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 0.55em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #6e6e73;
}
.wedge-tier-pro .wedge-tier-features li::before {
  background: #00a8dc;
}

/* Light-theme overrides for the wedge slide. */
.deck-body[data-theme="light"] .slide-wedge-headline {
  color: #212121;
}
.deck-body[data-theme="light"] .wedge-tier {
  background: #ffffff;
  border-color: rgba(0, 0, 0, 0.06);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06);
}
.deck-body[data-theme="light"] .wedge-tier-pro {
  border-color: rgba(0, 168, 220, 0.55);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(0, 168, 220, 0.2);
}
.deck-body[data-theme="light"] .wedge-tier-price-value {
  color: #212121;
}
.deck-body[data-theme="light"] .wedge-card {
  background: #ffffff;
  border-color: rgba(0, 0, 0, 0.08);
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.08);
}
.deck-body[data-theme="light"] .wedge-card-friction {
  background: #f3f3f0;
}
.deck-body[data-theme="light"] .wedge-card-label {
  color: #6e6e73;
}
.deck-body[data-theme="light"] .wedge-card-list li {
  color: #4a4a4f;
}
.deck-body[data-theme="light"] .wedge-card-list li::before {
  background: #9a9aa0;
}
.deck-body[data-theme="light"] .wedge-tier-tagline,
.deck-body[data-theme="light"] .wedge-tier-features li,
.deck-body[data-theme="light"] .wedge-tier-price-unit {
  color: #4a4a4f;
}
.deck-body[data-theme="light"] .wedge-tier-name {
  color: #6e6e73;
}
.deck-body[data-theme="light"] .wedge-tier-features li::before {
  background: #9a9aa0;
}
.deck-body[data-theme="light"] .wedge-future-item {
  background: rgba(0, 0, 0, 0.02);
  border-color: rgba(0, 0, 0, 0.06);
}
.deck-body[data-theme="light"] .wedge-future-label {
  color: #6e6e73;
}
.deck-body[data-theme="light"] .wedge-future-meta {
  color: #9a9aa0;
}
.deck-body[data-theme="light"] .wedge-disclaimer {
  color: #6e6e73;
}

/* ===========
   Slide: Ask
   ===========

   Header sits at the same vertical position as the other content
   slides at both the centered (beat 1) and resting (beat 2+) stops,
   so the transition reads as a uniform slide-up across the deck.
   Layout is identical to the timeline slide pre-body. */
.slide-ask {
  display: flex;
  flex-direction: column;
  min-height: 70vh;
  gap: 56px;
}

.slide-ask-header {
  transition: transform 1.1s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-wrapper[data-current-beat="1"] .slide-ask-header {
  transform: translateY(calc(35vh - 50%));
}

.slide-ask-headline {
  font-size: clamp(36px, 4.8vw, 64px);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -1.2px;
  color: #ffffff;
  max-width: 900px;
}

.deck-body[data-theme="light"] .slide-ask-headline {
  color: #15171c;
}

.ask-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 48px;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.45s ease, transform 0.55s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Reveal the body only after the headline has finished sliding up
   (matches the team / timeline pattern: 1.1s header, 0.7s delay on
   the body fade so the two animations don't overlap awkwardly). */
.slide-wrapper[data-current-beat="2"] .ask-content {
  opacity: 1;
  transform: translateY(0);
  transition-delay: 0.7s;
}

/* Hero ask: the dollar range is the focal point of the slide. */
.ask-hero {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}

.ask-hero-amount {
  font-size: clamp(56px, 7vw, 96px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -2.4px;
  color: #00ffd1;
}

.ask-hero-meta {
  font-size: clamp(13px, 1.1vw, 16px);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #c8c8cc;
}

/* "What it buys" — section label + three stat tiles in a row, each
   echoing the founder card's stat treatment (large numeric on the
   left, descriptive label on the right). */
.ask-uses {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.ask-section-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #6e6e73;
}

.ask-stats {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}

.ask-stat {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 14px;
  padding: 16px 18px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 10px;
}

.ask-stat-num {
  flex-shrink: 0;
  font-size: clamp(28px, 2.8vw, 38px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -1px;
  color: #ffdd04;
}

.ask-stat-label {
  flex: 1;
  min-width: 0;
  font-size: clamp(12px, 0.95vw, 13px);
  line-height: 1.3;
  color: #c8c8cc;
}

/* Light theme. */
.deck-body[data-theme="light"] .ask-hero-amount {
  color: #009d80;
}
.deck-body[data-theme="light"] .ask-stat-num {
  color: #e8b400;
}
.deck-body[data-theme="light"] .ask-hero-meta {
  color: #4a4a4f;
}
.deck-body[data-theme="light"] .ask-section-label {
  color: #9a9aa0;
}
.deck-body[data-theme="light"] .ask-stat {
  background: rgba(0, 0, 0, 0.025);
  border-color: rgba(0, 0, 0, 0.08);
}
.deck-body[data-theme="light"] .ask-stat-label {
  color: #4a4a4f;
}

/* Mobile: stat row collapses to a single column under the hero. */
@media (max-width: 700px) {
  .ask-stats {
    grid-template-columns: 1fr;
  }
}

/* ===========
   Slide: Timeline
   ===========

   Scaffolded ahead of body content. Matches the content-slide header
   pattern: beat 1 centers the header vertically, beat 2+ snaps it back
   to the natural top position. */
.slide-timeline {
  display: flex;
  flex-direction: column;
  min-height: 70vh;
  gap: 56px;
}

.slide-timeline-header {
  transition: transform 1.1s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-wrapper[data-current-beat="1"] .slide-timeline-header {
  transform: translateY(calc(35vh - 50%));
}

.slide-timeline-headline {
  font-size: clamp(36px, 4.8vw, 64px);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -1.2px;
  color: #ffffff;
  max-width: 900px;
}

.deck-body[data-theme="light"] .slide-timeline-headline {
  color: #15171c;
}

/* "headed" suffix that slides in on the second line at beat 4.
   Mirrors the existing `.headline-swap` slide-in pattern: the
   wrapper clips overflow, and the inner suffix translates from
   `-100%` (off-screen left) to `0` while fading in. Always reserves
   the second line's vertical space (the inner span sits in layout
   even when invisible) so revealing it doesn't shift the body
   downward. */
.timeline-headline-suffix-wrap {
  /* `inline-grid` (matching the existing `.headline-swap` pattern)
     keeps the wrapper's baseline aligned with the surrounding
     headline text — `inline-block + overflow: hidden` would push the
     baseline to the wrapper's bottom edge, dropping "headed" below
     the "Where we are" line. */
  display: inline-grid;
  overflow: hidden;
  padding-bottom: 0.18em;
}

.timeline-headline-suffix {
  display: inline-block;
  transform: translateX(-100%);
  opacity: 0;
  color: #ffdd04;
  transition: transform 0.55s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.4s ease;
}

.slide-wrapper[data-slide-id="timeline"][data-current-beat="3"] .timeline-headline-suffix,
.slide-wrapper[data-slide-id="timeline"][data-current-beat="4"] .timeline-headline-suffix,
.slide-wrapper[data-slide-id="timeline"][data-current-beat="5"] .timeline-headline-suffix {
  transform: translateX(0);
  opacity: 1;
}

.deck-body[data-theme="light"] .timeline-headline-suffix {
  color: #e8b400;
}

.timeline-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.45s ease, transform 0.55s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Reveal the timeline only after the headline has finished sliding
   up to its resting position. Header transition is 1.1s long; the
   0.7s delay mirrors the team slide's founder-section reveal. The
   delay only applies on the way in — retreating to beat 1 clears
   the rule so the body snaps out immediately. */
.slide-wrapper[data-current-beat="2"] .timeline-content,
.slide-wrapper[data-current-beat="3"] .timeline-content,
.slide-wrapper[data-current-beat="4"] .timeline-content,
.slide-wrapper[data-current-beat="5"] .timeline-content {
  opacity: 1;
  transform: translateY(0);
  transition-delay: 0.7s;
}

/* Timeline visualization. Single HTML structure positioned via CSS
   variables (`--pos` on each tick, `--from` / `--to` on each bar),
   so swapping orientation on mobile means swapping which axis the
   variables target (horizontal `left`/`width` on desktop becomes
   vertical `top`/`height` on mobile).

   Tick intervals are intentionally vague: only the milestone ticks
   (May 2026, Jan 2027, 2027+) carry labels. The unlabeled ticks
   suggest the passage of time without committing to specific dates,
   so the bars can drift if plans change. */
.timeline {
  display: flex;
  flex-direction: column;
  gap: 32px;
  padding: 0 24px 0 0;
  margin-left: -32px;
}

/* === Lanes ===
   Four horizontal bands (raise / hire / build / target) plus a final
   "lane" that hosts the tick axis. Each lane is a 2-column grid
   sharing the same template, so the labels stack vertically and the
   bar tracks align across rows. The axis row reuses the grid so its
   tick positions sit under the same x-coords as the bars above. */
.timeline-lanes {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.timeline-lane {
  display: grid;
  grid-template-columns: 80px 1fr;
  gap: 18px;
  align-items: center;
}

.timeline-lane-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #c8c8cc;
  text-align: right;
}

.timeline-lane-track {
  position: relative;
  height: 96px;
}

.deck-body[data-theme="light"] .timeline-lane-label {
  color: #4a4a4f;
}

/* === Axis ===
   The axis itself has no visible line — the ticks alone communicate
   the timeline, with the labeled milestone ticks anchoring May 2026
   and Jan 2027. Sits as the last lane so its ticks line up with the
   bar positions above. */
.timeline-lane-axis {
  margin-top: 8px;
}

.timeline-axis {
  list-style: none;
  margin: 0;
  padding: 0;
  position: relative;
  height: 1px;
}

.timeline-tick {
  position: absolute;
  left: var(--pos);
  top: 50%;
  width: 1px;
  height: 8px;
  background: rgba(255, 255, 255, 0.3);
  transform: translate(-50%, -50%);
}

/* Labeled milestone ticks (May 2026, Jan 2027) just stand a bit
   taller than the surrounding ruler ticks so the labels below them
   feel anchored. They inherit the same neutral background as their
   unlabeled siblings — the height alone is enough to read as a
   milestone. */
.timeline-tick[data-labeled="true"] {
  height: 16px;
}

.timeline-tick-label {
  position: absolute;
  top: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #c8c8cc;
  white-space: nowrap;
}

/* === Activity bars ===
   Bars use a neutral card-style background by default (matches the
   rest of the deck's card chrome). Each lane carries its own
   `--lane-border` CSS variable on the parent .timeline-lane via the
   [data-lane] attribute; that border color only applies to bars
   whose `data-beat` matches the slide's current beat — see the
   beat-driven rule below. */
.timeline-bar {
  position: absolute;
  left: var(--from);
  width: calc(var(--to) - var(--from));
  top: 0;
  bottom: 0;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.12);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 4px;
  padding: 10px 14px;
  transition: border-color 0.35s ease, box-shadow 0.35s ease;
}

/* Pre-seed and PoC keep their labels right-aligned. Both fade into
   the past on their left, so the label wants to sit near the right
   edge where the bar is solid. In-network testers is the mirror —
   fades into the future on the right, so its label sits at the
   left edge. */
.timeline-bar[data-align="right"] {
  align-items: flex-end;
}

.timeline-bar[data-align="left"] {
  align-items: stretch;
}

/* True left-anchored variant: label sits flush against the bar's
   left edge with `text-align: left` so single-word labels don't
   appear to float toward center. Used on end-of-timeline bars
   (Product launch, Customers) where the label should clearly read
   as the start of an ongoing activity, not as a centered title. */
.timeline-bar[data-align="left-edge"] {
  align-items: flex-start;
}

/* "Extends into the past" bars: the whole bar (background + border)
   softly fades to transparent on the left, suggesting the activity
   started before the timeline begins. Used on Pre-seed and PoC
   since those are already underway when the deck is being shown. */
.timeline-bar[data-extends="past"] {
  mask-image: linear-gradient(to right, transparent, black 50%);
  -webkit-mask-image: linear-gradient(to right, transparent, black 50%);
}

/* "Extends into the future" bars: mirror image — fades on the right
   so the activity reads as ongoing indefinitely past the bar's
   nominal end. Used on In-network testers since that feedback loop
   doesn't have a hard cutoff date. */
.timeline-bar[data-extends="future"] {
  mask-image: linear-gradient(to left, transparent, black 50%);
  -webkit-mask-image: linear-gradient(to left, transparent, black 50%);
}

/* Active-beat highlight: colored 1px border plus an INSET 1px
   shadow just inside it, giving the active state a 2px-thick "this
   is the focus" feel. Inset shadow (vs outer `box-shadow`) sits
   inside the border-box, so it lives inside `mask-image`'s region
   and fades along with the border on the pre-seed / PoC bars —
   keeping the outline thickness uniform across all active bars
   without the blur/halo artifacts that `filter: drop-shadow` caused. */
.slide-wrapper[data-current-beat="2"] .timeline-bar[data-beat="2"],
.slide-wrapper[data-current-beat="3"] .timeline-bar[data-beat="3"],
.slide-wrapper[data-current-beat="4"] .timeline-bar[data-beat="4"],
.slide-wrapper[data-current-beat="5"] .timeline-bar[data-beat="5"] {
  background: var(--lane-fill);
  border-color: var(--lane-border);
  box-shadow: inset 0 0 0 1px var(--lane-border);
}

.deck-body[data-theme="light"] .timeline-bar {
  background: #ffffff;
  border-color: rgba(0, 0, 0, 0.08);
}

.timeline-bar-label {
  color: #ededee;
  font-size: clamp(13px, 1.1vw, 16px);
  font-weight: 500;
  line-height: 1.25;
  overflow: hidden;
  text-align: center;
}

.timeline-bar[data-align="right"] .timeline-bar-label {
  text-align: right;
  transform: translateX(-16px);
}

/* Text stays centered, but the whole label is nudged left so the
   centered text lands inside the bar's solid zone instead of in
   the right-edge fade. Paired with `data-extends="future"`. Per-bar
   tuning via `--label-shift` (e.g. `style="--label-shift: -8%"`)
   for cases where the default needs adjustment. */
.timeline-bar[data-align="left"] .timeline-bar-label {
  transform: translateX(var(--label-shift, -18%));
}

.timeline-bar[data-align="left-edge"] .timeline-bar-label {
  text-align: left;
  transform: translateX(var(--label-shift, 0));
}

/* Per-lane brand colors. Dark theme uses the bright brand colors at
   moderate alpha so the bars read as vivid against the dark slide
   bg; light theme uses deeper / desaturated variants so they sit
   more quietly against the off-white slide bg without screaming.
   Slightly different alphas per color since yellow and mint visually
   read brighter at the same opacity than blue and pink. */
.timeline-lane[data-lane="raise"] {
  --lane-fill: rgba(255, 221, 4, 0.18);
  --lane-border: rgba(255, 221, 4, 0.75);
  --lane-label: #ffdd04;
}

.timeline-lane[data-lane="hire"] {
  --lane-fill: rgba(254, 9, 109, 0.2);
  --lane-border: rgba(254, 9, 109, 0.85);
  --lane-label: #fe5e95;
}

.timeline-lane[data-lane="build"] {
  --lane-fill: rgba(3, 189, 255, 0.19);
  --lane-border: rgba(3, 189, 255, 0.8);
  --lane-label: #03bdff;
}

.timeline-lane[data-lane="feedback"] {
  --lane-fill: rgba(0, 255, 209, 0.14);
  --lane-border: rgba(0, 255, 209, 0.7);
  --lane-label: #00ffd1;
}

/* Light-theme variants: shift each lane to a deeper / muted version
   of its brand color so the bars don't over-saturate against the
   light bg. Same per-color alpha tuning as above. */
.deck-body[data-theme="light"] .timeline-lane[data-lane="raise"] {
  --lane-fill: rgba(184, 134, 11, 0.1);
  --lane-border: rgba(184, 134, 11, 0.5);
  --lane-label: #b8860b;
}

.deck-body[data-theme="light"] .timeline-lane[data-lane="hire"] {
  --lane-fill: rgba(165, 25, 80, 0.09);
  --lane-border: rgba(165, 25, 80, 0.5);
  --lane-label: #a51950;
}

.deck-body[data-theme="light"] .timeline-lane[data-lane="build"] {
  --lane-fill: rgba(3, 135, 184, 0.1);
  --lane-border: rgba(3, 135, 184, 0.55);
  --lane-label: #0387b8;
}

.deck-body[data-theme="light"] .timeline-lane[data-lane="feedback"] {
  --lane-fill: rgba(0, 130, 105, 0.09);
  --lane-border: rgba(0, 130, 105, 0.5);
  --lane-label: #008269;
}

/* Color-tint the lane label so the row's identity reads at a glance.
   Falls back to the default uppercase gray when no lane color is set
   (e.g. the tick-axis row). */
.timeline-lane[data-lane] .timeline-lane-label {
  color: var(--lane-label, #c8c8cc);
}

/* === Light theme === */
.deck-body[data-theme="light"] .timeline-tick {
  background: rgba(0, 0, 0, 0.3);
}
.deck-body[data-theme="light"] .timeline-tick-label {
  color: #4a4a4f;
}
.deck-body[data-theme="light"] .timeline-bar-label {
  color: #15171c;
}

/* === Mobile: the timeline extends past the right edge of the
   viewport so bars have legible width. With each beat the bar
   tracks (and the tick axis) slide left to bring the active range
   into view, while the lane labels stay anchored on the left. */
@media (max-width: 700px) {
  /* Reset the desktop -32px margin-left that pulled the timeline
     leftward to align with the slide headline — on mobile the slide
     padding is much tighter, so the negative margin pushes the lane
     labels off the left edge of the viewport. */
  .timeline {
    margin-left: 0;
  }

  .timeline-lane {
    grid-template-columns: 60px 1fr;
    gap: 12px;
  }

  .timeline-lane-label {
    font-size: 10px;
    /* Sit on top of the solid strip / fade pseudo-elements painted on
       `.timeline-lanes` so the lane text is readable above them. The
       label itself no longer carries a bg — the strip behind it does. */
    position: relative;
    z-index: 2;
  }

  /* Solid black strip and fade sit on the lanes container so they
     run continuously from the top of the first lane to the bottom of
     the last — no per-lane gaps. The strip covers the 60px label
     column; the fade extends another 40px into the track so bars
     dissolve in instead of snapping past the strip's hard edge. */
  .timeline-lanes {
    position: relative;
  }

  .timeline-lanes::before,
  .timeline-lanes::after {
    content: "";
    position: absolute;
    top: 0;
    /* Stop at the bottom of the Feedback lane so the strip + fade
       don't extend over the tick-axis row below — otherwise the
       leftmost tick label (May 2026) gets clipped. Height = 4 bar
       lanes × 96px + 3 gaps × 12px. */
    height: calc(4 * 96px + 3 * 12px);
    z-index: 1;
    pointer-events: none;
  }

  .timeline-lanes::before {
    /* Extend leftward past the slide's content padding all the way
       to (and beyond) the viewport's left edge so bars translating
       under the label column can't peek out in the gap between the
       viewport edge and the strip's start. */
    left: -100px;
    width: 160px;
    background: #050507;
  }

  .timeline-lanes::after {
    left: 60px;
    width: 40px;
    background: linear-gradient(to right, #050507, transparent);
  }

  .deck-body[data-theme="light"] .timeline-lanes::before {
    background: #f3f3f0;
  }

  .deck-body[data-theme="light"] .timeline-lanes::after {
    background: linear-gradient(to right, #f3f3f0, transparent);
  }

  .timeline-bar-label {
    font-size: 12px;
  }

  .timeline-bar {
    padding: 8px 10px;
  }

  /* Bar tracks (and the tick axis) get a fixed wider minimum so the
     bars space out across ~700px and the right side of the timeline
     extends past the viewport edge. The deck body's `overflow:
     hidden` clips whatever falls past the right edge. */
  .timeline-lane-track,
  .timeline-axis {
    min-width: 700px;
    transition: transform 0.55s cubic-bezier(0.4, 0, 0.2, 1);
  }

  /* Per-beat horizontal shifts. Each beat slides the tracks left
     just enough to bring the active bars into the visible window.
     Beats 1 and 2 stay at the default 0px (left edge of the
     timeline visible). */
  .slide-wrapper[data-slide-id="timeline"][data-current-beat="3"] .timeline-lane-track,
  .slide-wrapper[data-slide-id="timeline"][data-current-beat="3"] .timeline-axis {
    transform: translateX(-100px);
  }

  .slide-wrapper[data-slide-id="timeline"][data-current-beat="4"] .timeline-lane-track,
  .slide-wrapper[data-slide-id="timeline"][data-current-beat="4"] .timeline-axis {
    transform: translateX(-300px);
  }

  .slide-wrapper[data-slide-id="timeline"][data-current-beat="5"] .timeline-lane-track,
  .slide-wrapper[data-slide-id="timeline"][data-current-beat="5"] .timeline-axis {
    transform: translateX(-450px);
  }
}

/* ===========
   Slide: Team
   ===========

   Three beats:
     1 — Header centered on the slide.
     2 — Header moves up; founder card fades in below it.
     3 — Advisors row (label + three cards) fades in below the founder. */
.slide-team {
  display: flex;
  flex-direction: column;
  min-height: 70vh;
  gap: 56px;
}

.slide-team-header {
  transition: transform 1.1s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-wrapper[data-current-beat="1"] .slide-team-header {
  transform: translateY(calc(35vh - 50%));
}

.slide-team-headline {
  font-size: clamp(36px, 4.8vw, 64px);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -1.2px;
  color: #ffffff;
  max-width: 900px;
}

.team-content {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 36px;
  align-items: stretch;
}

.team-card {
  background: #15171c;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 12px;
  padding: 18px 24px 20px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.3);
  display: flex;
  flex-direction: column;
  gap: 10px;
  box-sizing: border-box;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.45s ease, transform 0.55s cubic-bezier(0.4, 0, 0.2, 1);
}

.team-section-label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1.4px;
  color: #6e6e73;
}

.team-card-name {
  font-size: clamp(20px, 1.9vw, 26px);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.4px;
  color: #ffffff;
}

.team-card-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.team-card-list li {
  position: relative;
  padding-left: 14px;
  font-size: clamp(13px, 1.05vw, 15px);
  line-height: 1.45;
  color: #d8d8db;
}

.team-card-list li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 0.6em;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: #6e6e73;
}

.team-card-list li strong {
  color: #ededee;
  font-weight: 600;
}

/* Section label + role bullets, grouped so the label sits tightly
   above its list (the founder card's 20px flex gap would otherwise
   space the label too far from the bullets it belongs to). */
.team-roles {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Inside the founder card, add extra space above the "Most Recently"
   block so the stats and the role bullets aren't crowded together.
   Scoped to the founder card so advisor cards (which reuse
   `.team-roles`) keep their natural spacing. */
.team-card-founder .team-roles {
  margin-top: 8px;
}

/* Side-by-side stat tiles inside the founder card. Each tile is a
   muted inset block with a large numeric and a short label, sized to
   read as a stat without competing with the hero "dora-stat" treatment
   on slides 2 and 4. */
.team-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  margin-top: 8px;
}

.team-stat {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 8px;
}

.team-stat-label {
  flex: 1;
  min-width: 0;
  font-size: clamp(12px, 0.95vw, 13px);
  line-height: 1.3;
  color: #c8c8cc;
}

.team-stat-num {
  flex-shrink: 0;
  font-size: clamp(32px, 3.4vw, 44px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -1.2px;
  color: #03bdff;
}

.deck-body[data-theme="light"] .team-stat {
  background: rgba(0, 0, 0, 0.025);
  border-color: rgba(0, 0, 0, 0.08);
}
.deck-body[data-theme="light"] .team-stat-num {
  color: #0387b8;
}
.deck-body[data-theme="light"] .team-stat-label {
  color: #4a4a4f;
}

/* Multi-line bullet: primary role on top, a small "stat · meta" line
   left-aligned underneath. */
.team-bullet-stacked {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-bottom: 2px;
}

.team-bullet-primary {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  color: #d8d8db;
}

.team-bullet-role {
  flex: 1;
  min-width: 0;
}

.team-bullet-stat {
  flex-shrink: 0;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-weight: 600;
  letter-spacing: -0.2px;
  color: #c8c8cc;
}

.team-bullet-meta {
  flex-shrink: 0;
  color: #6e6e73;
  font-weight: 500;
}

/* Secondary line under a role bullet: pithy description on the left,
   tenure on the right. Sits flush under the primary line so the
   description aligns with the role text above it. */
.team-bullet-subs {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.team-bullet-sub {
  font-size: clamp(11px, 0.85vw, 12px);
  line-height: 1.4;
  color: #6e6e73;
  font-style: italic;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
}

.deck-body[data-theme="light"] .team-bullet-primary {
  color: #15171c;
}
.deck-body[data-theme="light"] .team-bullet-stat {
  color: #4a4a4f;
}
.deck-body[data-theme="light"] .team-bullet-sub {
  color: #4a4a4f;
}
.deck-body[data-theme="light"] .team-bullet-meta {
  color: #6e6e73;
}

.team-card-founder {
  /* Founder card is split into a top section (avatar + name) and a
     bottom section (bullets), separated by a thin divider. The card
     itself stays a vertical flex container — its default direction.
     Grows to fill the founder column so its bottom edge aligns with
     the bottom of the stacked advisor cards in the other column. */
  flex: 1;
  gap: 20px;
}

.team-card-founder-top {
  display: flex;
  align-items: center;
  gap: 16px;
  /* Add enough top space so the avatar is vertically centered between
     the card's top edge and the dividing line below. Card padding-top
     is 18px; the space below the top section is the 20px flex gap +
     the divider's 8px margin-top = 28px. Adding 10px here balances
     them so the avatar's vertical midpoint lands exactly between the
     card top and the divider. `align-items: center` already keeps the
     name vertically centered with the avatar. */
  padding-top: 10px;
}

.team-card-divider {
  border: 0;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  margin: 8px 0 0;
  width: 100%;
}

.deck-body[data-theme="light"] .team-card-divider {
  border-top-color: rgba(0, 0, 0, 0.1);
}

.team-card-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
}

.team-card-avatar {
  display: block;
  border-radius: 50%;
  object-fit: cover;
  background: #0d0e12;
  flex-shrink: 0;
}

.team-card-avatar-founder {
  width: 100px;
  height: 100px;
  background: #03bdff;
  object-position: right center;
}

.deck-body[data-theme="light"] .team-card-avatar-founder {
  background: #0387b8;
}

.team-card-advisor .team-card-avatar {
  width: 56px;
  height: 56px;
}

.team-founder,
.team-advisors {
  display: flex;
  flex-direction: column;
  gap: 14px;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.45s ease, transform 0.55s cubic-bezier(0.4, 0, 0.2, 1);
}

/* The founder card has its own per-card opacity rule too; clear that
   here so the wrapper drives the reveal cleanly. */
.team-card-founder {
  opacity: 1;
  transform: none;
  transition: none;
}

.team-advisors-row {
  display: flex;
  flex-direction: column;
  gap: 20px;
  /* Fill the wrapper so the last card's bottom edge lands at the
     bottom of the column — same y as the founder card's bottom.
     `justify-content: space-between` keeps the cards at their natural
     sizes and lets the extra height accumulate as bigger gaps between
     them (the 20px gap above is the minimum). */
  flex: 1;
  justify-content: space-between;
}

.team-card-advisor {
  /* Advisor cards reveal as a group with their parent .team-advisors
     section, so their own opacity/transform stays at the visible state
     to avoid double-staggering. Two-column layout: avatar + name on
     the left, vertical divider, bullets on the right. */
  opacity: 1;
  transform: none;
  padding: 12px 16px 14px;
  flex-direction: row;
  align-items: stretch;
  gap: 0;
}

/* Left column on each advisor card: avatar on the left, name (first
   name / last name on two lines) to its right. Locked width so the
   vertical divider sits at exactly the same x across all four cards
   regardless of name length. */
.team-card-advisor-id {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 12px;
  flex: 0 0 160px;
  padding-right: 14px;
}

.team-card-advisor .team-card-name {
  flex: 1;
  font-size: clamp(15px, 1.2vw, 17px);
  line-height: 1.15;
}

/* Right column on each advisor card: section label ("Currently" /
   "Previously") above the bullet list. The vertical divider runs on
   the wrapper's left edge so it sits between the avatar+name and the
   whole label-plus-bullets block. */
.team-card-advisor .team-roles {
  flex: 1;
  min-width: 0;
  padding-left: 14px;
  border-left: 1px solid rgba(255, 255, 255, 0.08);
}

.deck-body[data-theme="light"] .team-card-advisor .team-roles {
  border-left-color: rgba(0, 0, 0, 0.1);
}

/* "Footnote" for accolades attached to a bullet — drops onto its own
   line directly under the bullet text, smaller and muted so it reads
   as supporting context rather than a second point. Used on Stephen's
   "Founder & CEO of Paper" line. */
.team-bullet-footnote {
  display: block;
  margin-top: 2px;
  font-size: 11px;
  color: #8e8e93;
  font-weight: 400;
  font-style: italic;
  line-height: 1.3;
}

.deck-body[data-theme="light"] .team-bullet-footnote {
  color: #6e6e73;
}

/* === Beat-driven reveal ===
   Header transition is 1.1s long (see `.slide-team-header`). Delay the
   founder section so it doesn't fade in over a still-moving headline —
   only on the way in. The default no-delay transition on the wrapper
   rule applies when retreating, so the card snaps back out without
   feeling sluggish. */
.slide-wrapper[data-current-beat="2"] .team-founder,
.slide-wrapper[data-current-beat="3"] .team-founder {
  opacity: 1;
  transform: translateY(0);
  transition-delay: 0.7s;
}

.slide-wrapper[data-current-beat="3"] .team-advisors {
  opacity: 1;
  transform: translateY(0);
}

/* Light theme overrides. */
.deck-body[data-theme="light"] .slide-team-headline {
  color: #15171c;
}
.deck-body[data-theme="light"] .team-card {
  background: #ffffff;
  border-color: rgba(0, 0, 0, 0.08);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.08);
}
.deck-body[data-theme="light"] .team-card-name {
  color: #15171c;
}
.deck-body[data-theme="light"] .team-card-list li {
  color: #4a4a4f;
}
.deck-body[data-theme="light"] .team-card-list li strong {
  color: #15171c;
}
.deck-body[data-theme="light"] .team-card-list li::before {
  background: #9a9aa0;
}
.deck-body[data-theme="light"] .team-section-label {
  color: #9a9aa0;
}

/* Narrower viewports: the founder + advisors two-column split gets
   cramped. Collapse to a single grid cell where both sections
   overlap — beat 2 shows the founder, beat 3 hides the founder and
   reveals the advisors in the same space, so advancing reads as a
   replacement rather than a scroll. */
@media (max-width: 900px) {
  .team-content {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr;
    align-items: start;
  }

  .team-founder,
  .team-advisors {
    grid-row: 1;
    grid-column: 1;
  }

  /* Each section sizes to its own content on mobile — no need to
     match heights since they're never visible together. */
  .team-card-founder {
    flex: 0 0 auto;
  }

  /* Stack the stat number on top of the label on narrow viewports.
     The horizontal "big number + label to the right" layout makes
     the two stat cards uneven when their labels are different
     lengths; stacking centers both pieces and the cards line up. */
  .team-stat {
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 6px;
  }

  .team-stat-label {
    flex: 0 0 auto;
  }

  /* Beat 3: founder slides up and out as the advisors slide in. The
     existing per-section transitions handle the timing. */
  .slide-wrapper[data-current-beat="3"] .team-founder {
    opacity: 0;
    transform: translateY(-12px);
    pointer-events: none;
    transition-delay: 0s;
  }
}

/* ===========
   Slide: Solution
   =========== */

/* Slide layout (mirrors the slide-2/3/4 pattern) */
.slide-solution {
  display: flex;
  flex-direction: column;
  min-height: 70vh;
  gap: 28px;
}

.slide-solution-header {
  transition: transform 1.1s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-wrapper[data-current-beat="1"] .slide-solution {
  justify-content: center;
}
.slide-wrapper[data-current-beat="1"] .slide-solution-header {
  transform: translateY(calc(35vh - 50%));
}

.slide-solution-headline {
  font-size: clamp(36px, 4.8vw, 64px);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -1.2px;
  color: #ffffff;
  max-width: 900px;
}

/* Each card has its pillar verb (build / review / run) prominently at
   the top of the text pane, colored to its --accent. The verb replaces
   the previous decorative accent bar. */
.solution-card-verb {
  font-size: clamp(28px, 3.4vw, 44px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -1px;
  color: var(--accent, #212121);
  text-transform: capitalize;
}

/* 3D card carousel stage. Cards are positioned absolutely at the
   center of the stage; per-beat selectors set CSS variables that drive
   the transform — front, back-left, back-right. On beat advance the
   variables animate, producing the rotation. */
.solution-stage {
  position: relative;
  flex: 1;
  perspective: 1500px;
  perspective-origin: center;
  min-height: 420px;
  transition: opacity 0.5s ease 0.5s;
}
.slide-wrapper[data-current-beat="1"] .solution-stage {
  opacity: 0;
  pointer-events: none;
}

.solution-card {
  --card-x: 0%;
  --card-z: 0px;
  --card-rot: 0deg;
  --card-scale: 1;
  --card-opacity: 0;

  position: absolute;
  top: 50%;
  left: 50%;
  width: 96%;
  max-width: 980px;
  height: 88%;
  background: #15171c;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 16px;
  padding: 28px;
  display: grid;
  grid-template-columns: 2fr 3fr;
  gap: 28px;
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.4);

  transform-origin: center;
  transform: translate(calc(-50% + var(--card-x)), -50%) translateZ(var(--card-z))
    rotateY(var(--card-rot)) scale(var(--card-scale));
  opacity: var(--card-opacity);
  transition: transform 0.85s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.65s ease;
}

/* Position assignments per beat. Cycle: BR → front → BL → BR.
   Beat 2: front=Build, BL=Run,    BR=Review
   Beat 3: front=Review, BL=Build,  BR=Run
   Beat 4: front=Run,    BL=Review, BR=Build */

.slide-wrapper[data-current-beat="2"] .solution-card-build,
.slide-wrapper[data-current-beat="3"] .solution-card-review,
.slide-wrapper[data-current-beat="4"] .solution-card-run {
  --card-opacity: 1;
  --card-x: 0%;
  --card-z: 0px;
  --card-rot: 0deg;
  --card-scale: 1;
  z-index: 3;
}

.slide-wrapper[data-current-beat="2"] .solution-card-run,
.slide-wrapper[data-current-beat="3"] .solution-card-build,
.slide-wrapper[data-current-beat="4"] .solution-card-review {
  --card-opacity: 0.45;
  --card-x: -32%;
  --card-z: -220px;
  --card-rot: 22deg;
  --card-scale: 0.78;
  z-index: 1;
  pointer-events: none;
}

.slide-wrapper[data-current-beat="2"] .solution-card-review,
.slide-wrapper[data-current-beat="3"] .solution-card-run,
.slide-wrapper[data-current-beat="4"] .solution-card-build {
  --card-opacity: 0.45;
  --card-x: 32%;
  --card-z: -220px;
  --card-rot: -22deg;
  --card-scale: 0.78;
  z-index: 1;
  pointer-events: none;
}

/* Card content — left text column, right demo pane. */
.solution-card-text {
  display: flex;
  flex-direction: column;
  gap: 22px;
  padding-top: 4px;
  min-width: 0;
}

/* Extra space below the verb so the big label has clear separation
   from the section content underneath it. */
.solution-card-verb + .solution-card-section,
.solution-card-verb + .solution-card-title {
  margin-top: 20px;
}

.solution-card-title {
  font-size: clamp(18px, 1.8vw, 24px);
  font-weight: 700;
  line-height: 1.25;
  color: #212121;
  margin: 0;
}

/* Highlighted phrases inside a card title — a subtle medium gray that
   differentiates them tonally from the dark connector words around
   them, without pulling the eye like a brand color would. */
.solution-card-title-accent {
  color: #6e6e73;
}

.solution-card-desc {
  font-size: clamp(13px, 1.05vw, 16px);
  line-height: 1.5;
  color: #4a4a4f;
  margin: 0;
}

/* Sub-section pattern — used on the Build card to break the text column
   into two short titled blocks ("Comprehensive Infra", "Opinionated
   Architecture") instead of one big title + paragraph. */
.solution-card-section {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.solution-card-section-title {
  font-size: clamp(16px, 1.5vw, 20px);
  font-weight: 700;
  line-height: 1.25;
  color: #ffffff;
  margin: 0;
}

.solution-card-section-desc {
  font-size: clamp(13px, 1.05vw, 16px);
  line-height: 1.5;
  color: #b8b8bb;
  margin: 0;
}

.solution-card-demo-pane {
  background: #fafaf8;
  border: 1px solid rgba(0, 0, 0, 0.05);
  border-radius: 10px;
  padding: 16px;
  overflow: hidden;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* Pillar accent colors — light-mode contrast-tuned palette so the
   accents read on the white card. (Brand palette is preserved for
   the dark-mode slides.) */
.solution-card-build {
  --accent: #e8b400;
}
.solution-card-review {
  --accent: #00a8dc;
}
.solution-card-run {
  --accent: #00b98c;
}

/* ---- Build pillar demo (chat + service proposal) ---- */

.solution-demo-build {
  display: flex;
  flex-direction: column;
  gap: 10px;
  font-size: 12px;
}

.chat-message {
  display: flex;
  gap: 10px;
  align-items: flex-start;
}

.chat-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: #e1e1de;
  flex-shrink: 0;
  font-size: 10px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #555;
  /* Optical nudge so the avatar aligns with the bubble's first line of
     text rather than the bubble's top edge. */
  margin-top: 5px;
}

.chat-avatar-agent {
  background: #e8b400;
  color: #212121;
}

/* When the avatar holds a photo (vs. initials), strip the bg fill and
   let the image fill the circle. */
.chat-avatar-photo {
  background: transparent;
  overflow: hidden;
}
.chat-avatar-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.chat-bubble {
  flex: 1;
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 10px;
  padding: 10px 12px;
  min-width: 0;
}

.chat-bubble-agent {
  background: #fff8de;
  border-color: rgba(232, 180, 0, 0.4);
}

.chat-author {
  font-size: 9px;
  font-weight: 700;
  color: #6e6e73;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  margin-bottom: 4px;
}

.chat-text {
  font-size: 12px;
  line-height: 1.45;
  color: #212121;
}

.service-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: 10px;
}

.service-card {
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: 6px;
  padding: 8px 10px;
}

.service-card-active {
  border-color: rgba(232, 180, 0, 0.7);
  box-shadow: 0 0 0 1px rgba(232, 180, 0, 0.55);
}

.service-name {
  font-size: 11px;
  font-weight: 700;
  color: #212121;
  margin-bottom: 2px;
}

.service-stack {
  font-size: 9px;
  color: #6e6e73;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  margin-bottom: 6px;
}

.service-endpoints {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 10px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  line-height: 1.65;
  color: #4a4a4f;
}

.endpoint-active {
  color: #b88500;
  font-weight: 700;
}
/* Keep small-text accent darker than the punchy decorative variant —
   #e8b400 on white is decorative-only and would be hard to read at
   10–11px monospace. */

.endpoint-detail {
  margin-top: 8px;
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 6px;
  padding: 8px 10px;
}

.endpoint-header {
  font-size: 10px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-weight: 700;
  color: #212121;
  margin-bottom: 6px;
}

.endpoint-response {
  font-size: 10px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  color: #4a4a4f;
  line-height: 1.5;
  margin: 0;
  padding: 0;
  white-space: pre;
  overflow-x: auto;
}

/* Inline code references inside agent chat copy — slightly lighter
   monospace tint so service / file references read as code without
   pulling away from the conversational tone. */
.chat-text code {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.92em;
  background: rgba(0, 0, 0, 0.05);
  padding: 1px 4px;
  border-radius: 3px;
  color: #212121;
}

/* A clickable service reference — wraps an inline <code> with an
   "open" arrow so the viewer can tell the agent is linking out to
   the service's full details. Static in the demo, but reads as an
   affordance. */
.service-ref {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  cursor: pointer;
  transition: opacity 0.15s ease;
}
.service-ref:hover {
  opacity: 0.7;
}
.service-ref-icon {
  font-size: 0.78em;
  color: #6e6e73;
  line-height: 1;
  margin-left: 1px;
}

/* Followup question after a list of options — adds a touch of breathing
   room above so the question reads as the agent's "over to you" beat. */
.chat-text-followup {
  margin-top: 8px;
}

/* Option cards inside an agent bubble — surfaces architectural
   tradeoffs visually. The recommended option is tinted with the card's
   accent and tagged. */
.option-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: 10px;
}

.option-card {
  position: relative;
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: 6px;
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.option-card-recommended {
  border-color: rgba(232, 180, 0, 0.7);
  box-shadow: 0 0 0 1px rgba(232, 180, 0, 0.45);
  background: #fffaee;
}

.option-tag {
  display: inline-block;
  align-self: flex-start;
  background: #e8b400;
  color: #212121;
  font-size: 8px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  padding: 2px 6px;
  border-radius: 3px;
  margin-bottom: 2px;
}

.option-name {
  font-size: 11px;
  font-weight: 700;
  color: #212121;
}

.option-desc {
  font-size: 10px;
  color: #6e6e73;
  line-height: 1.4;
}

/* Service card header — name + a small "modified" pill so a glance
   makes clear which services are being touched. */
.service-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  margin-bottom: 2px;
}

.service-card-header .service-name {
  margin-bottom: 0;
}

.service-mod {
  font-size: 8px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: #00875a;
  background: rgba(0, 135, 90, 0.1);
  padding: 1px 5px;
  border-radius: 3px;
}

/* New endpoints get a "+" marker and a green tint so additions read at
   a glance — diff semantics without a heavy diff view. */
.endpoint-add {
  color: #00875a;
  font-weight: 600;
}
.endpoint-add::before {
  content: "+ ";
  color: #00a36e;
  font-weight: 700;
}

/* CTA button inside an agent bubble — drives "drill in for full plan."
   Static in this demo, but reads as a clickable affordance. */
.agent-action {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
  padding: 6px 10px;
  background: #212121;
  color: #ffffff;
  border: none;
  border-radius: 6px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.2px;
  cursor: pointer;
  transition: transform 0.15s ease, background 0.15s ease;
}
.agent-action:hover {
  background: #2c2c2c;
  transform: translateY(-1px);
}
.agent-action-arrow {
  font-size: 12px;
  line-height: 1;
}

/* Soft fade at the bottom of any demo pane — when content exceeds the
   visible area this signals "more below" instead of an abrupt clip.
   Lives on the pane itself (not the demo) so it stays anchored to the
   pane when an inner demo is scrolled or animated. */
.solution-card-demo-pane {
  position: relative;
}
.solution-card-demo-pane::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 36px;
  background: linear-gradient(to bottom, rgba(250, 250, 248, 0), #fafaf8);
  pointer-events: none;
  z-index: 1;
}

/* ---- Run pillar demo (production observability + agent diagnosis) ---- */

.solution-demo-run {
  display: flex;
  flex-direction: column;
  gap: 10px;
  font-size: 12px;
}

/* Status header — service name + env, plus the active alert with a
   pulsing red dot to communicate "live incident". */
.run-status {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.run-status-meta {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 10px;
}
.run-service-name {
  font-weight: 400;
  color: #6e6e73;
}
.run-env {
  color: #6e6e73;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  font-size: 9px;
  padding: 1px 5px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: 3px;
}
.run-alert {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  font-weight: 600;
  color: #006a47;
}
.run-alert-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #00a36e;
  flex-shrink: 0;
}

/* Resolved variant — already covered by the green defaults above, but
   left as a hook for future "active alert" vs "resolved" states. */
.run-alert-resolved .run-alert-dot {
  background: #00a36e;
}

/* Metric block — chart + peak/current/duration stats. Green-tinted
   background communicates "we are back below threshold" — the agent
   already resolved the spike, this is a post-mortem view. */
.run-metric {
  background: #edfaf3;
  border: 1px solid rgba(0, 163, 110, 0.22);
  border-radius: 6px;
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.run-metric-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 10px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  color: #212121;
  font-weight: 600;
}
.run-metric-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Time-range segmented control in the chart header — common
   monitoring-dashboard pattern. The active range gets a white pill
   inside the gray track to read as selected. */
.run-metric-controls {
  display: flex;
  gap: 1px;
  padding: 1px;
  background: rgba(0, 0, 0, 0.06);
  border-radius: 4px;
  flex-shrink: 0;
}
.run-metric-control {
  background: transparent;
  border: none;
  padding: 2px 6px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 9px;
  font-weight: 600;
  color: #6e6e73;
  cursor: pointer;
  border-radius: 3px;
  transition: background 0.12s ease, color 0.12s ease;
}
.run-metric-control:hover {
  color: #212121;
}
.run-metric-control-active {
  background: #ffffff;
  color: #212121;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}
.run-metric-chart {
  background: #fafaf8;
  border: 1px solid rgba(0, 0, 0, 0.05);
  border-radius: 4px;
  padding: 4px 6px;
}
.run-metric-chart svg {
  display: block;
  width: 100%;
  height: 150px;
}
.run-metric-stats {
  display: flex;
  gap: 16px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
}
.run-metric-stat {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.run-metric-label {
  font-size: 9px;
  color: #6e6e73;
  text-transform: uppercase;
  letter-spacing: 0.4px;
}
.run-metric-value {
  font-size: 12px;
  font-weight: 700;
  color: #212121;
}

/* "Filed Change Request" CTA — single-row callout that links into the
   filed CR. Mirrors the layout of .review-preview (icon + label/meta
   + arrow) but in green to signal "resolved / a fix has been filed". */
.run-fix {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  background: rgba(0, 163, 110, 0.08);
  border: 1px solid rgba(0, 163, 110, 0.35);
  border-radius: 8px;
  cursor: pointer;
  text-align: left;
  text-decoration: none;
  color: inherit;
  margin-top: 4px;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
.run-fix:hover {
  background: rgba(0, 163, 110, 0.14);
  border-color: rgba(0, 163, 110, 0.55);
  transform: translateY(-1px);
}
.run-fix-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #00a36e;
  color: #ffffff;
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  flex-shrink: 0;
  padding-bottom: 1px;
}
.run-fix-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1;
  min-width: 0;
}
.run-fix-label {
  font-size: 11px;
  font-weight: 700;
  color: #212121;
}
.run-fix-meta {
  font-size: 9px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  color: #006a47;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.run-fix-arrow {
  font-size: 14px;
  color: #00a36e;
  line-height: 1;
  flex-shrink: 0;
}

/* Agent comment inside the green metric card — reads as a structured
   diagnostic note rather than a chat message. White bg keeps it
   visually distinct from the green card it sits inside; yellow avatar
   pill stays consistent with the agent identity used in build/review. */
.run-comment {
  background: #ffffff;
  border: 1px solid rgba(0, 163, 110, 0.18);
  border-radius: 6px;
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 4px;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
.run-comment-header {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 10px;
}
.run-comment-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #e8b400;
  color: #212121;
  font-size: 8px;
  font-weight: 700;
  line-height: 1;
  padding-bottom: 1px;
  flex-shrink: 0;
}
.run-comment-author {
  font-weight: 700;
  color: #212121;
}
.run-comment-time {
  color: #6e6e73;
}
.run-comment-body {
  font-size: 11px;
  line-height: 1.45;
  color: #212121;
}
.run-comment-body code {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.92em;
  background: rgba(0, 0, 0, 0.05);
  padding: 0 3px;
  border-radius: 2px;
}

/* ---- Review pillar demo (contract-level PR view) ---- */

.solution-demo-review {
  display: flex;
  flex-direction: column;
  font-size: 12px;
}

.review-pr {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.review-pr-meta {
  font-size: 10px;
  color: #6e6e73;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  text-transform: none;
  letter-spacing: 0.2px;
}

.review-pr-title {
  font-size: 14px;
  font-weight: 700;
  color: #212121;
  line-height: 1.3;
}

.review-pr-author {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 10px;
  color: #6e6e73;
}
.review-author-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #00a8dc;
  color: #ffffff;
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0;
}

/* Summary tiles — quick-scan counts of what changed, plus the
   automated p50 perf delta. */
.review-tiles {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
  margin-top: 2px;
}
.review-tile {
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 6px;
  padding: 6px 8px;
  text-align: center;
}
.review-tile-num {
  font-size: 14px;
  font-weight: 700;
  color: #00a8dc;
  line-height: 1.1;
}
.review-tile-label {
  font-size: 9px;
  color: #6e6e73;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  margin-top: 2px;
}

/* Section blocks — one per contract concern (entities, endpoints,
   tests, performance). Header reads as a small label, body holds the
   diff lines. */
.review-section {
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.06);
  border-radius: 6px;
  padding: 6px 10px 8px;
}
.review-section-header {
  font-size: 9px;
  font-weight: 700;
  color: #6e6e73;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  margin-bottom: 4px;
}
.review-section-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Diff lines — monospace + a leading marker. Add lines get a green
   "+" prefix; mod lines get a yellow "~"; test lines get a green "✓". */
.review-line {
  position: relative;
  font-size: 10px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  line-height: 1.55;
  color: #212121;
}

/* All entity / endpoint / test lines are expandable — chevron on the
   right + hover state makes the affordance clear. */
.review-line-add,
.review-line-mod,
.review-line-test {
  cursor: pointer;
  padding-right: 14px;
  border-radius: 3px;
  margin: 0 -4px;
  padding-left: 4px;
  transition: background 0.12s ease;
}
.review-line-add:hover,
.review-line-mod:hover,
.review-line-test:hover {
  background: rgba(0, 0, 0, 0.04);
}
.review-line-add::after,
.review-line-mod::after,
.review-line-test::after {
  content: "›";
  position: absolute;
  right: 4px;
  top: 0;
  height: 16px;
  width: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #9a9aa0;
  font-size: 14px;
  font-weight: 600;
  line-height: 1;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  transition: color 0.15s ease;
}
.review-line-add:hover::after,
.review-line-mod:hover::after,
.review-line-test:hover::after {
  color: #212121;
}

/* Expanded state — chevron rotates 90° (so "›" becomes "v") and a
   detail block appears below the summary with element-specific
   actions (e.g. test spec + "Run in playground"). */
.review-line-expanded::after {
  transform: rotate(90deg);
  color: #212121;
}
.review-line-expanded {
  background: rgba(0, 0, 0, 0.03);
}

/* For expanded lines, suppress the line's own prefix ::before (which
   would land on its own row above a block-level summary div) and
   re-attach the prefix to the summary so it stays inline with the
   text. */
.review-line-expanded::before {
  display: none;
}
.review-line-add.review-line-expanded .review-line-summary::before {
  content: "+ ";
  color: #00a36e;
  font-weight: 700;
}
.review-line-mod.review-line-expanded .review-line-summary::before {
  content: "~ ";
  color: #b88500;
  font-weight: 700;
}
.review-line-test.review-line-expanded .review-line-summary::before {
  content: "✓ ";
  color: #00a36e;
  font-weight: 700;
}

.review-line-detail {
  margin-top: 6px;
  padding: 6px 8px 8px;
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.06);
  border-radius: 4px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  cursor: default;
}

/* Inline review comment thread — appears under the line it's
   commenting on so it reads as anchored to a specific endpoint /
   entity / test. Indented + cyan accent border so it's visually
   subordinate to the diff lines but clearly distinct. The thread
   contains one or more .review-comment-message blocks. */
.review-comment {
  margin: 4px 0 4px 14px;
  padding: 7px 9px 8px;
  background: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-left: 2px solid #00a8dc;
  border-radius: 0 4px 4px 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  cursor: default;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
.review-comment-message {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.review-comment-message + .review-comment-message {
  padding-top: 8px;
  border-top: 1px dashed rgba(0, 0, 0, 0.08);
}
.review-comment-header {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 10px;
}
.review-comment-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  overflow: hidden;
  flex-shrink: 0;
}
.review-comment-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Agent avatar — yellow pill matching the agent identity used in the
   build chat. Small "AI" text scaled down to fit the 14px circle.
   line-height: 1 + a 1px bottom padding nudges the glyphs upward to
   compensate for the font's descender space sitting empty below the
   baseline. */
.review-comment-avatar-agent {
  background: #e8b400;
  color: #212121;
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0;
  line-height: 1;
  padding-bottom: 1px;
}
.review-comment-author {
  font-weight: 700;
  color: #212121;
}
.review-comment-time {
  color: #6e6e73;
}
.review-comment-body {
  font-size: 11px;
  line-height: 1.45;
  color: #212121;
}
.review-comment-actions {
  display: flex;
  gap: 8px;
}
.review-comment-action {
  background: transparent;
  border: none;
  padding: 0;
  font-size: 10px;
  font-weight: 600;
  color: #00a8dc;
  cursor: pointer;
  letter-spacing: 0.2px;
  font-family: inherit;
  transition: color 0.12s ease;
}
.review-comment-action:hover {
  color: #006a93;
  text-decoration: underline;
}

/* BDD-style test spec inside the expanded test view. Keyword
   ("given"/"when"/"then") gets the cyan accent so the spec reads
   semantically at a glance. */
.review-test-spec {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 10px;
  line-height: 1.55;
  color: #212121;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
}
.review-test-step code {
  font-size: 0.95em;
  background: rgba(0, 0, 0, 0.05);
  padding: 0 3px;
  border-radius: 2px;
}
.review-test-keyword {
  color: #00a8dc;
  font-weight: 700;
  margin-right: 4px;
  text-transform: lowercase;
}

/* Per-element action row inside the expanded detail block —
   one primary affordance ("Run in playground") and one secondary
   ("view code"). Sits below the spec so it reads as the next step
   the viewer can take. */
.review-line-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.review-detail-action {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 8px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.2px;
  border-radius: 4px;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
  background: transparent;
  color: #212121;
  border: 1px solid rgba(0, 0, 0, 0.18);
  font-family: inherit;
}
.review-detail-action:hover {
  background: rgba(0, 0, 0, 0.04);
  border-color: rgba(0, 0, 0, 0.28);
  color: #212121;
}
.review-detail-primary {
  background: #00a8dc;
  color: #ffffff;
  border-color: #00a8dc;
}
.review-detail-primary:hover {
  background: #0090c0;
  border-color: #0090c0;
  color: #ffffff;
}
.review-line-add::before {
  content: "+ ";
  color: #00a36e;
  font-weight: 700;
}
.review-line-test::before {
  content: "✓ ";
  color: #00a36e;
  font-weight: 700;
}
.review-line-mod {
  color: #212121;
}
.review-line-mod::before {
  content: "~ ";
  color: #b88500;
  font-weight: 700;
}
.review-line-meta {
  color: #6e6e73;
  font-weight: 400;
}

/* Preview-environment callout — the primary interactive affordance.
   Reads as a single clickable row: play icon, label + isolated env URL,
   arrow on the right. Cyan-tinted to align with the slide's brand
   accent and signal the action. */
.review-preview {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 12px;
  background: rgba(0, 168, 220, 0.08);
  border: 1px solid rgba(0, 168, 220, 0.35);
  border-radius: 8px;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
.review-preview:hover {
  background: rgba(0, 168, 220, 0.14);
  border-color: rgba(0, 168, 220, 0.55);
  transform: translateY(-1px);
}
.review-preview-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #00a8dc;
  color: #ffffff;
  font-size: 8px;
  flex-shrink: 0;
  padding-left: 1px;
}
.review-preview-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1;
  min-width: 0;
}
.review-preview-label {
  font-size: 11px;
  font-weight: 700;
  color: #212121;
}
.review-preview-url {
  font-size: 9px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  color: #006a93;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.review-preview-arrow {
  font-size: 14px;
  color: #00a8dc;
  line-height: 1;
  flex-shrink: 0;
}

/* Inline load-test chart — small SVG sparkline with two series:
   dashed gray baseline, solid cyan after-change line. preserveAspect
   stretches the chart to fill the section width. */
.review-perf-chart {
  background: #fafaf8;
  border: 1px solid rgba(0, 0, 0, 0.06);
  border-radius: 4px;
  padding: 6px 8px;
  margin-bottom: 6px;
}
.review-perf-chart svg {
  display: block;
  width: 100%;
  height: 50px;
}

.review-perf-rows {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.review-perf-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 10px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
}
.review-perf-label {
  color: #6e6e73;
  font-weight: 700;
  width: 28px;
  flex-shrink: 0;
}
.review-perf-value {
  color: #212121;
  flex: 1;
}

.review-perf-legend {
  display: flex;
  gap: 12px;
  margin-top: 6px;
  font-size: 9px;
  color: #6e6e73;
  text-transform: uppercase;
  letter-spacing: 0.4px;
}
.review-perf-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.review-perf-legend-baseline,
.review-perf-legend-new {
  display: inline-block;
  width: 14px;
  height: 2px;
}
.review-perf-legend-baseline {
  background: repeating-linear-gradient(to right, #9a9aa0 0 3px, transparent 3px 5px);
}
.review-perf-legend-new {
  background: #00a8dc;
}

/* Performance row — endpoint name on the left, before/after timings
   in the middle, delta on the right. */
.review-perf-line {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  font-size: 10px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
}
.review-perf-endpoint {
  color: #212121;
  font-weight: 600;
}
.review-perf-arrow {
  color: #6e6e73;
}
.review-perf-delta {
  color: #b88500;
  font-weight: 700;
  margin-left: auto;
}
.review-perf-note {
  font-size: 10px;
  color: #6e6e73;
  margin-top: 4px;
}

/* Action row — Approve is the primary cyan-filled action; Reject is a
   quieter outlined alternate. The "view full diff" link sits at the
   right edge as a subtle escape hatch for going deeper into code. */
.review-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
}

.review-code-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
  font-size: 10px;
  color: #6e6e73;
  text-decoration: none;
  transition: color 0.15s ease;
}
.review-code-link:hover {
  color: #212121;
  text-decoration: underline;
}
.review-approve,
.review-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 6px 12px;
  border-radius: 6px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.2px;
  cursor: pointer;
  transition: transform 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}
.review-approve {
  background: #00a36e;
  color: #ffffff;
  border: none;
}
.review-approve:hover {
  background: #008b5d;
  transform: translateY(-1px);
}
.review-secondary {
  background: transparent;
  color: #212121;
  border: 1px solid rgba(0, 0, 0, 0.18);
}
.review-secondary:hover {
  background: rgba(0, 0, 0, 0.04);
  border-color: rgba(0, 0, 0, 0.28);
}

/* ===========
   Hint variants (keyboard vs touch)
   =========== */

.deck-hint-keyboard,
.deck-hint-touch {
  display: none;
}

@media (hover: hover) and (pointer: fine) {
  .deck-hint-keyboard {
    display: inline;
  }
}

@media (hover: none), (pointer: coarse) {
  .deck-hint-touch {
    display: inline;
  }
}

/* Width-based override: on narrow viewports always show the touch
 * variant (and force-hide the keyboard one) so devices that report
 * ambiguous hover/pointer don't flash the desktop hint on first
 * paint. Width queries evaluate instantly and consistently across
 * real devices and dev-tool emulators. */
@media (max-width: 600px) {
  .deck-hint-keyboard {
    display: none;
  }
  .deck-hint-touch {
    display: inline;
  }
}

/* ===========
   Tablet — 900px
   =========== */

@media (max-width: 900px) {
  /* On mobile the slide-wrapper's asymmetric padding (left = chrome
     padding + logo-text-offset, right = chrome padding) eats too much
     horizontal space and shifts everything to the right. Make the
     wrapper padding symmetric on narrow widths so slide content is
     truly centered. */
  .slide-wrapper {
    padding-left: var(--chrome-padding-x);
    padding-right: var(--chrome-padding-x);
  }

  /* The intro / why-now / problem / how-its-going / solution / wedge
     slides override their own padding-right to var(--content-left) on
     desktop (to visually re-balance the chrome). That same rule applies
     on mobile and produces a wider right margin than left. Override
     here so all of these slides get symmetric chrome-padding-x on
     mobile. */
  .slide-wrapper[data-slide-id="intro"],
  .slide-wrapper[data-slide-id="why-now"],
  .slide-wrapper[data-slide-id="problem"],
  .slide-wrapper[data-slide-id="how-its-going"],
  .slide-wrapper[data-slide-id="solution"],
  .slide-wrapper[data-slide-id="wedge"],
  .slide-wrapper[data-slide-id="team"],
  .slide-wrapper[data-slide-id="timeline"],
  .slide-wrapper[data-slide-id="ask"] {
    padding-right: var(--chrome-padding-x);
  }

  /* Quote card on mobile: fill the full content width (vs the
     min(820px, 92%) cap on desktop). Centering is inherited from the
     base rule. */
  .quote-card {
    width: 100%;
    max-width: 100%;
  }
  .dora-stat {
    gap: 12px;
  }
  /* Progress bar relocates to the bottom of the viewport so the chrome
     bar isn't crowded by the logo + indicator + toggle on narrow widths. */
  .deck-chrome {
    grid-template-columns: 1fr auto;
  }
  .deck-progress {
    position: fixed;
    left: 50%;
    bottom: 14px;
    transform: translateX(-50%);
    z-index: 15;
    transition: opacity 0.18s ease;
  }
  .deck-body[data-notes="open"] .deck-progress {
    opacity: 0;
    pointer-events: none;
  }

  /* On narrow viewports the notes popover anchors to the bottom-center
     of the viewport (closer to the thumb) instead of the top-right, and
     stretches nearly edge-to-edge so it has room for longer notes. */
  .deck-notes {
    top: auto;
    right: auto;
    left: 50%;
    bottom: 16px;
    width: calc(100vw - 32px);
    max-width: none;
    transform: translate(-50%, 6px) scale(0.98);
    transform-origin: bottom center;
  }
  .deck-body[data-notes="open"] .deck-notes {
    transform: translate(-50%, 0) scale(1);
  }
}

/* ===========
   Phone — 600px
   =========== */

@media (max-width: 600px) {
  /* Chrome — tighten padding, shrink logo, bigger touch target on toggle */
  .deck-body {
    --chrome-padding-y: 14px;
    /* Mobile logo: 7px dots × 2 + 3px gap = 17px wide; logo gap = 6px */
    --logo-text-offset: 23px;
  }
  .deck-logo {
    gap: 6px;
  }
  .deck-logo-text {
    font-size: 14px;
  }
  .deck-logo .logo-dot {
    width: 7px;
    height: 7px;
  }
  .deck-progress {
    gap: 4px;
  }
  .deck-progress-dot {
    width: 4px;
    height: 4px;
  }
  .deck-progress-dot[data-state="current"] {
    width: 14px;
  }
  .deck-notes-toggle {
    /* Match the theme toggle's 36×36 footprint on mobile — the label
     * is hidden, so the button collapses to a centered "N" key
     * indicator at the same size as the sun/moon icon button. */
    width: 36px;
    height: 36px;
    min-height: 0;
    padding: 0;
    justify-content: center;
    gap: 0;
  }
  .deck-notes-toggle-label {
    display: none;
  }

  /* Hint — mirror the desktop's mid-lower-screen positioning so the
     instructions sit between the headline and the bottom of the
     viewport on mobile too. The progress bar sits at the very bottom
     (its own mobile rule), so there's still room. */
  .deck-hint {
    bottom: 29vh;
    font-size: 11px;
    padding: 0 24px;
    text-align: center;
  }

  /* Slide template — reduce padding, max width to viewport */
  .slide {
    width: 100vw;
    padding: 0 20px;
    max-height: none;
  }
  .slide-eyebrow {
    font-size: 10px;
    letter-spacing: 1.8px;
    margin-bottom: 12px;
  }

  /* Intro slide */
  .slide-intro-content {
    gap: 18px;
  }
  .slide-intro-mark {
    gap: 14px;
  }
  .slide-intro-mark-dot {
    width: 18px;
    height: 18px;
  }
  .slide-intro-mark-text {
    font-size: clamp(32px, 9vw, 44px);
  }
  .slide-intro-headline {
    font-size: 18px;
    line-height: 1.5;
  }

  /* Why-now / problem / how-its-going slides — share the same headline
     scaling on mobile so all four story-arc slides line up at the same
     size. Capped at 26px on mobile. */
  .slide-why-now {
    gap: 20px;
  }
  .slide-why-now-headline,
  .slide-problem-headline,
  .slide-how-its-going-headline,
  .slide-wedge-headline {
    font-size: 26px;
    line-height: 1.15;
    letter-spacing: -0.5px;
  }

  /* Unify the slide-up start position across all four story-arc slides.
     The desktop's calc(35vh - 50%) uses the header's own height for the
     50% term — since headers wrap to different line counts on mobile,
     headers end at slightly different y positions. Replace the
     self-relative offset with a fixed 30px so every header translates
     by the same amount and lands at the same on-screen position
     (top-left aligned). */
  .slide-wrapper[data-current-beat="1"] .slide-why-now-header,
  .slide-wrapper[data-current-beat="1"] .slide-problem-header,
  .slide-wrapper[data-current-beat="1"] .slide-how-its-going-header,
  .slide-wrapper[data-current-beat="1"] .slide-solution-header,
  .slide-wrapper[data-current-beat="1"] .slide-wedge-header,
  .slide-wrapper[data-current-beat="1"] .slide-team-header,
  .slide-wrapper[data-current-beat="1"] .slide-timeline-header,
  .slide-wrapper[data-current-beat="1"] .slide-ask-header {
    transform: translateY(calc(35vh - 30px));
  }
  .quote-card {
    padding: 14px 16px;
    gap: 10px;
  }
  .quote-card blockquote {
    font-size: 17px;
    line-height: 1.4;
  }
  .quote-card figcaption {
    padding-top: 8px;
  }
  .quote-card figcaption strong {
    font-size: 12px;
  }
  .quote-cred,
  .quote-source {
    font-size: 10px;
  }
  .dora-stat-num {
    font-size: 64px;
  }
  .dora-stat-label {
    font-size: 13px;
  }

  .deck-notes-block-body p {
    font-size: 13px;
    line-height: 20px;
  }

  /* Wedge slide — drop the desktop carousel/stage entirely on mobile.
     All three cards stack vertically (each one fades in on its beat),
     and the pricing block stacks below the cards on beat 5. */
  .slide-wedge {
    gap: 20px;
  }
  .wedge-content {
    perspective: none;
    min-height: auto;
    display: flex;
    flex-direction: column;
    gap: 14px;
  }
  .wedge-card {
    position: relative;
    top: auto;
    left: auto;
    width: 100% !important;
    max-width: none !important;
    min-height: 0 !important;
    padding: 18px 20px !important;
    gap: 10px !important;
    transform: none !important;
    opacity: 0;
  }
  .wedge-card-list {
    gap: 4px !important;
  }
  .wedge-card-list li {
    font-size: 13px !important;
    padding-left: 14px !important;
    line-height: 1.45 !important;
  }
  .wedge-card-list li::before {
    width: 4px !important;
    height: 4px !important;
  }
  .wedge-card-label {
    font-size: 10px !important;
    letter-spacing: 1.2px !important;
  }
  /* Reveal cards progressively on their beats. Profile from beat 2,
     Friction from beat 3, Lift from beat 4, all stay visible on 5. */
  .slide-wrapper[data-current-beat="2"] .wedge-card-profile,
  .slide-wrapper[data-current-beat="3"] .wedge-card-profile,
  .slide-wrapper[data-current-beat="3"] .wedge-card-friction,
  .slide-wrapper[data-current-beat="4"] .wedge-card-profile,
  .slide-wrapper[data-current-beat="4"] .wedge-card-friction,
  .slide-wrapper[data-current-beat="4"] .wedge-card-lift,
  .slide-wrapper[data-current-beat="5"] .wedge-card {
    opacity: 1;
  }
  /* Pricing block stacks below the cards on mobile beat 5. */
  .wedge-pricing {
    position: relative !important;
    top: auto;
    right: auto;
    width: 100% !important;
    transform: none;
    gap: 14px;
  }
  .slide-wrapper[data-current-beat="5"] .wedge-pricing {
    transform: none;
  }
  .wedge-tier {
    padding: 20px 22px;
    gap: 14px;
  }
  .wedge-tier-name {
    font-size: 10px;
    letter-spacing: 1.2px;
  }
  .wedge-tier-tagline,
  .wedge-tier-features li {
    font-size: 13px;
  }
  .wedge-future {
    grid-template-columns: 1fr;
    gap: 10px;
  }
  .wedge-future-item {
    padding: 10px 14px;
  }
  .wedge-future-meta {
    font-size: 12px;
  }
  .wedge-disclaimer {
    font-size: 11px;
  }
  .wedge-tier-price-value {
    font-size: 26px;
  }
  .wedge-tier-price-unit {
    font-size: 12px;
  }

  /* Solution slide — flatten the 3D carousel into a single-card-per-
     beat view. The story still progresses Build → Review → Run by
     advancing beats; only the active card renders. The card itself is
     wider than the viewport so the text column reads at full width and
     a slice of the demo pane peeks in at the right edge — clipped, not
     scrollable.

     Layout matches the why-now / problem / how-its-going pattern:
     wrapper stays flex-centered, slide is flex-column with the header
     up top, and the desktop translateY animation handles the beat-1 →
     beat-2 slide-up motion (so the header lands at the same on-screen
     position as the other slides' headlines). */

  .slide-solution {
    gap: 16px;
    /* Anchor the slide to the same top position as the other story-arc
       slides (15vh — what a 70vh slide naturally lands at when centered
       in a 100vh wrapper). align-self: flex-start exempts this slide
       from the wrapper's align-items: center. Then grow downward so
       the card has more room: slide bottom lands ~40px above the
       viewport bottom — clearing the progress bar with breathing room. */
    align-self: flex-start;
    margin-top: 15vh;
    min-height: calc(85vh - 40px);
  }
  .slide-solution-headline {
    font-size: 26px;
    line-height: 1.15;
    letter-spacing: -0.5px;
  }

  /* Stage — drop the 3D perspective. Cards are absolutely positioned
     inside (matches the why-now quote-stack pattern), so they don't
     contribute to slide height — the slide stays at min-height: 70vh
     and the slide-up header animation lands at the same on-screen
     position as the other slides. */
  .solution-stage {
    perspective: none;
    min-height: auto;
    display: block;
    width: 100%;
  }

  /* Cards — absolutely positioned to fill the stage, single column
     flex layout. Text content sits at the top; the demo pane fills
     the remaining space with overflow: hidden so the demo content
     (which is intrinsically wider/taller than what the mobile card
     can show) gets clipped on the right and bottom — giving a hint
     of the demo without revealing the full thing. */
  .solution-card {
    position: absolute;
    inset: 0;
    width: auto;
    max-width: none;
    height: auto;
    padding: 18px;
    flex-direction: column;
    gap: 14px;
    transform: none !important;
    opacity: 1 !important;
    display: none;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
    overflow: hidden;
  }
  .slide-wrapper[data-current-beat="2"] .solution-card-build,
  .slide-wrapper[data-current-beat="3"] .solution-card-review,
  .slide-wrapper[data-current-beat="4"] .solution-card-run {
    display: flex;
  }

  /* Demo pane fills the remaining card height; overflow: hidden clips
     content past the bottom edge. The demo content keeps its desktop
     proportions (forced wider via min-width on the inner wrapper),
     so anything past the right edge is clipped too. */
  .solution-card-demo-pane {
    flex: 1;
    min-height: 0;
    overflow: hidden;
  }
  .solution-demo-build,
  .solution-demo-review,
  .solution-demo-run {
    min-width: 480px;
  }

  /* Card text spacing tightened for the narrower mobile column. */
  .solution-card-text {
    gap: 14px;
  }
  .solution-card-verb {
    font-size: 26px;
    letter-spacing: -0.8px;
  }
  .solution-card-verb + .solution-card-section,
  .solution-card-verb + .solution-card-title {
    margin-top: 6px;
  }
  .solution-card-section-title {
    font-size: 15px;
  }
  .solution-card-section-desc {
    font-size: 13px;
  }
  .solution-card-demo-pane {
    padding: 12px;
  }
}

/* Short viewports — keep slide content from clipping vertically. */
@media (max-height: 600px) {
  .slide {
    max-height: 92vh;
  }
  .slide-intro-mark-text {
    font-size: clamp(32px, 6vw, 52px);
  }
  .slide-intro-mark-dot {
    width: clamp(18px, 2.4vw, 26px);
    height: clamp(18px, 2.4vw, 26px);
  }
  .slide-why-now-headline {
    font-size: clamp(24px, 5vw, 40px);
  }
}

/* -------------------------
   Intro slide — passcode gate + request-access form
   ------------------------- */

/* --------
   Passcode
   -------- */

.passcode-group {
  display: flex;
  flex-direction: column;
  align-self: stretch;
  gap: 20px;
}

.passcode-row {
  display: flex;
  align-items: end;
  align-self: stretch;
  justify-content: end;
  gap: 18px;
}

.passcode-field {
  display: flex;
  flex-direction: column;
  align-items: start;
  justify-content: center;
  gap: 10px;
}

.passcode-label {
  color: #ffffff;
  font-size: 15px;
  font-weight: 300;
  line-height: 20px;
  width: 70px;
}

.request-field {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
}

.request-label {
  color: #ffffff;
  font-size: 15px;
  font-weight: 300;
  line-height: 20px;
}

.passcode-boxes {
  display: flex;
  gap: 10px;
  height: 42px;
}

.passcode-box {
  width: 28px;
  align-self: stretch;
  background: transparent;
  border: 1px solid #fdfdfd;
  border-radius: 3px;
  color: #ffffff;
  font-family: "Open Sans", system-ui, sans-serif;
  font-size: 18px;
  font-weight: 600;
  text-align: center;
  outline: none;
  padding: 0;
  transition: border-color 0.15s;
}

.passcode-box:focus {
  border-color: #fdfdfd;
}

.learn-more-btn {
  flex: 1;
  height: 44px;
  background: #00bdff;
  border: none;
  border-radius: 5px;
  color: #000000;
  font-family: "Open Sans", system-ui, sans-serif;
  font-size: 16px;
  font-weight: 600;
  line-height: 20px;
  cursor: pointer;
  transition: opacity 0.15s;
}

.learn-more-btn:hover {
  opacity: 0.85;
}

.error-msg {
  font-size: 14px;
  color: #fe096d;
  min-height: 20px;
  align-self: stretch;
  text-align: center;
}

/* The landing page toggles between the passcode form and the
   request-access form via [data-view] on .passcode-group. */
.request-form,
.request-success {
  display: none;
}

.passcode-group[data-view="request"] .passcode-row,
.passcode-group[data-view="request"] .error-msg,
.passcode-group[data-view="success"] .passcode-row,
.passcode-group[data-view="success"] .error-msg,
.passcode-group[data-view="success"] .request-form,
.passcode-group[data-view="success"] .passcode-toggle {
  display: none;
}

.passcode-group[data-view="request"] .request-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
  align-self: stretch;
}

.passcode-group[data-view="success"] .request-success {
  display: block;
  font-size: 16px;
  color: #ffffff;
  text-align: center;
  align-self: stretch;
  margin: 0;
  line-height: 24px;
}

.request-fields {
  display: flex;
  gap: 10px;
  align-self: stretch;
}

.request-input {
  flex: 1;
  min-width: 0;
  height: 42px;
  padding: 0 14px;
  background: transparent;
  border: 1px solid #fdfdfd;
  border-radius: 3px;
  color: #ffffff;
  font-family: "Open Sans", system-ui, sans-serif;
  font-size: 15px;
  outline: none;
  transition: border-color 0.15s;
}

.request-input::placeholder {
  color: rgba(255, 255, 255, 0.5);
}

.request-input:focus {
  border-color: #00bdff;
}

/* Validation state: pink border when the JS regex check fails. Wins
   over `:focus` (more specific) so the red sticks even while the
   input is focused. Cleared by the JS `input` listener as soon as
   the visitor edits the field. */
.request-input.is-invalid,
.request-input.is-invalid:focus {
  border-color: #fe096d;
}

.passcode-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 14px;
  color: rgba(255, 255, 255, 0.7);
}

.passcode-toggle-prompt {
  display: none;
}

.passcode-group[data-view="passcode"] .passcode-toggle-prompt[data-prompt-for="passcode"],
.passcode-group[data-view="request"] .passcode-toggle-prompt[data-prompt-for="request"] {
  display: inline;
}

.passcode-toggle-btn {
  background: transparent;
  border: none;
  padding: 0;
  color: #00bdff;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.passcode-toggle-btn:hover {
  opacity: 0.85;
}

.passcode-toggle-btn span {
  display: none;
}

.passcode-group[data-view="passcode"] .passcode-toggle-btn span[data-label-for="passcode"],
.passcode-group[data-view="request"] .passcode-toggle-btn span[data-label-for="request"] {
  display: inline;
}


@media (max-width: 600px) {
  .passcode-group {
    align-items: center;
  }

  .passcode-row {
    flex-direction: column;
    align-items: center;
    gap: 14px;
  }

  .passcode-field {
    align-items: center;
  }

  .request-fields {
    flex-direction: column;
  }
}
