/* Otto landing — desktop. Spec: design_handoff_otto_hero_to_dfy/README.md
   (hero → "Done for you"; supersedes the earlier bundles) + design_handoff_otto_hero/README.md
   for the hero's XL fluid scaling (1440×880 → 2560×1290). */

* { box-sizing: border-box; }

[hidden] { display: none !important; }

body {
  margin: 0;
  min-width: 1180px;
  background: #FBF7EF;
  color: #2A2620;
  font-family: 'Figtree', sans-serif;
}

/* ---------- Global units + shared chat palette ---------- */

:root {
  /* Fluid scale unit per the XL "1a" hero spec: s = 1 at ≤1728px wide → 1.42 at
     2560px, height-guarded. --px = one 1440-spec pixel. Sections below the fold
     pin --px to 1px (fixed sizes per the hero→testimonial handoff). */
  --px: calc(clamp(10px, min(0.5048vw + 1.277px, 1.0244vh + 0.985px), 14.2px) / 10);
  /* background-anchored math unit: the meadow images draw width-fit */
  --W: max(100vw, 1180px);
  /* The storefront art's cover scale, and the height of its 457 below-fold
     extension rows. The DFY section is sized to CONTAIN both its framing (941
     rows = 100vh) and that extension, so the meadow crosses the fold inside
     ONE section, one image, with plain overflow:hidden — no overflow:visible
     tricks (engine-dependent), no second copy, no zoom boundary in the art. */
  --dfy-scale: max(calc(var(--W) / 1672), calc(100vh / 941));
  --dfy-ext: calc(457 * var(--dfy-scale));
  /* Strip composition push-down on wide-short windows (her call 2026-08-05:
     the WHOLE section slides down — copy included — so sky opens at the top
     and it stops reading bottom-heavy). One aspect shortfall S = 0.28W + 22px
     − 47.5vh (how far the width-anchored scene has ridden up vs a 10.5vh
     comfort line; 0 at the 1440×880 artboard) drives both levers:
     · --scene-drop = 1.1·S — painting + bubbles sink together (pinned to each
       other); the meadow band's layers shift identically so the fold seam
       stays pixel-continuous (the drop cancels in the seam equation).
     · --copy-down = 0.4·S — the copy follows down via the head/scene split
       (see .strip-scene), opening ~S·0.4 of extra sky above the headline.
     Net copy↔bubble gap ≈ natural + 0.7·S (~87px at 2000×1058).
     (Was 1.5·S / 0.75·S — her "up just a tad, 24–48px" pass settled it here.) */
  --scene-drop: clamp(0px, calc((0.28 * var(--W) + 22px - 47.5vh) * 1.1), 128px);
  --copy-down: clamp(0px, calc((0.28 * var(--W) + 22px - 47.5vh) * 0.4), 46px);

  /* Day-mode chat/scene values. The hero overrides these at night (.hero.night);
     the S3 demo card reuses the same chat components and always reads these. */
  --toolbar-bg: rgba(15, 21, 38, .38);
  --card-bg: rgba(252, 249, 241, .58);
  --card-bf: blur(22px) saturate(1.25);
  --chip: #877F6E;
  --title: #2A2620;
  --booked-sub: #7E776A;
  --bubble-otto: rgba(247, 239, 224, .75);
  --booked-bg: rgba(251, 246, 234, .6);
}

/* ---------- Hero frame ---------- */

.hero {
  --night: 0;
  position: relative;
  overflow: hidden;
}

.hero.night {
  --night: 1;
  --toolbar-bg: rgba(255, 244, 228, .16);
  --card-bg: rgba(252, 249, 241, .6);
  --card-bf: blur(26px) saturate(1.3) brightness(1.5);
  --chip: #544D3C;
  --title: #1B1813;
  --booked-sub: #5C5443;
  --bubble-otto: rgba(247, 239, 224, .85);
  --booked-bg: rgba(251, 246, 234, .72);
}

/* ---------- Background stack (back → front) ---------- */

/* Explicitly full-bleed: the wrapper is also the layers' containing block when
   an entrance animation puts a transform on it (Morning haze's zoom-settle) —
   without its own size the layers would collapse with it. */
.hero-bg {
  position: absolute;
  inset: 0;
}

.hero-bg img,
.hero-bg video,
.bg-scrim {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.hero-bg img,
.hero-bg video {
  object-fit: cover;
  object-position: center;
}

/* 30-inch hero: plain `object-fit: cover`, NO transform — same treatment as the
   16-inch. The 21B README's `translateY(70px) scale(1.38)` is tuned for the
   reference's own still (uploads/hero_3_flipped.png), whose scene is ~1.5x wider
   than the hero video's. Applying that push-in to the video compounded it: the desk
   rendered at 35% of viewport width against the mockup still's 23%, which read as
   badly zoomed-in. With the push-in off, the video lands at ~25% — within ~9% of the
   mockup, and the widest field of view this footage can give (cover already fills
   the width, so there is no room to pull back further). Measured at 2560x1338. */

/* Day still frame shows until the loop is ready (and as the reduced-motion fallback) */
.bg-video {
  opacity: 0;
  transition: opacity .5s ease;
}

.bg-video.is-playing { opacity: 1; }

.bg-night {
  opacity: var(--night);
  transition: opacity .9s ease;
}

/* Headline legibility over the grass — day only */
.bg-scrim {
  background: radial-gradient(90% 70% at 16% 86%, rgba(10, 16, 20, .34), rgba(10, 16, 20, .12) 45%, transparent 66%);
  opacity: calc(1 - var(--night));
  transition: opacity .9s ease;
}

/* ---------- Toolbar ---------- */

.toolbar {
  position: relative;
  z-index: 3;
  display: flex;
  justify-content: center;
  padding: calc(24 * var(--px)) calc(48 * var(--px));
}

.toolbar-pill {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(32 * var(--px));
  width: 100%;
  /* v2 reference nav geometry (2026-08-05): fixed 1160 below the 1790 wide-mode
     threshold, then 1160 × (vw/1790) — 1296 @2000, 1659 @2560. The toolbar's own
     48 × --px side padding is the small-viewport clamp (1084 @1180, as designed). */
  max-width: max(1160px, 64.804vw);
  padding: calc(10 * var(--px)) calc(12 * var(--px)) calc(10 * var(--px)) calc(26 * var(--px));
  border-radius: 999px;
  background: var(--toolbar-bg);
  border: 1px solid #1919191C; /* #191919 @ 11% alpha — lightened ~50% from the prior 22% (#19191938) */
  box-shadow: 0 calc(18 * var(--px)) calc(50 * var(--px)) rgba(10, 16, 34, .2);
  backdrop-filter: blur(16px) saturate(1.1);
  -webkit-backdrop-filter: blur(16px) saturate(1.1);
  transition: background-color .9s ease;
}

.logo img { height: calc(26 * var(--px)); display: block; }

.nav {
  display: flex;
  align-items: center;
  gap: calc(28 * var(--px));
}

.nav a {
  font-size: calc(13.5 * var(--px));
  font-weight: 600;
  color: rgba(255, 243, 226, .92);
  text-decoration: none;
  transition: opacity .18s ease;
}

.nav a:hover { opacity: .65; }

/* ---------- Buttons ---------- */

.btn {
  border-radius: 999px;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}

.btn-toolbar {
  background: #FFF3E2;
  color: #1E2433;
  font-size: calc(13.5 * var(--px));
  padding: calc(10 * var(--px)) calc(20 * var(--px));
  transition: transform .18s ease;
}

.btn-toolbar:hover { transform: translateY(-1px); }

.btn-primary {
  background: #FFF3E2;
  color: #1E2433;
  font-size: calc(14.5 * var(--px));
  padding: calc(13 * var(--px)) calc(26 * var(--px));
  box-shadow: 0 calc(12 * var(--px)) calc(32 * var(--px)) rgba(20, 28, 46, .22);
  transition: transform .18s ease;
}

.btn-primary:hover { transform: translateY(-1px); }

.btn-glass {
  background: rgba(255, 255, 255, .16);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, .45);
  color: #FFFDF8;
  font-size: calc(14.5 * var(--px));
  padding: calc(12 * var(--px)) calc(24 * var(--px));
  transition: opacity .18s ease;
}

.btn-glass:hover { opacity: .75; }

/* ---------- Hero bottom rail (copy left · chat card right) ---------- */

.hero-rail {
  position: absolute;
  z-index: 4;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  justify-content: center;
}

.hero-rail-inner {
  width: 100%;
  /* Matches the v2 reference's hero framing (2026-08-05, measured from the dc at
     1180/1440/1600/1850/2000): a 1240 box at the 1440 artboard (kept exact), and
     the reference's ≥1790 wide mode where the content inset is 112.8px × (vw/1790)
     — h1 left edge / card right edge land 126px from the viewport at 2000, 161px
     at 2560. The reference itself keeps the fixed 1240 box until 1790 then SNAPS
     wide (inset 292 → 113 at the flip — a canvas-mode artifact); we interpolate
     the 1440→1790 stretch instead (inset 148 → 112.8, linear) so nothing jumps
     and the 16″ sizes already get the mockup's edge-hugging look. The inner
     max(...) is that target inset T(vw); adding back 96 × --px cancels our own
     side padding, so the CONTENT inset equals T exactly at any --px. */
  max-width: max(calc(100vw + 96 * var(--px) - 2 * max(292.86px - 10.06vw, 6.3016vw)), 1240px);
  padding: 0 calc(48 * var(--px)) calc(72 * var(--px));
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: calc(40 * var(--px));
}

.hero-copy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  max-width: calc(700 * var(--px));
}

.hero-copy h1 {
  margin: 0;
  font-family: 'Instrument Sans', sans-serif;
  font-weight: 600;
  font-size: calc(63 * var(--px));
  line-height: 1.05;
  letter-spacing: calc(-1.5 * var(--px));
  color: #FFFDF8;
}

.hero-copy h1 .dim {
  font-weight: 500;
  color: rgba(255, 244, 226, .75);
}

.hero-copy h1 em {
  font-family: 'Newsreader', serif;
  font-style: italic;
  font-weight: 500;
  font-size: 1.05em;
  letter-spacing: calc(-.5 * var(--px));
  line-height: 1;
}

.cta-row {
  display: flex;
  align-items: center;
  gap: calc(12 * var(--px));
  margin-top: calc(34 * var(--px));
}

/* ---------- Hero chat card (scripted sample conversation) ---------- */

.hero-card-col {
  flex: none;
  width: calc(350 * var(--px));
  display: flex;
  flex-direction: column;
  gap: calc(18 * var(--px));
}

.chat-card {
  border-radius: calc(22 * var(--px));
  background: var(--card-bg);
  border: 1px solid rgba(255, 255, 255, .55);
  box-shadow: 0 calc(30 * var(--px)) calc(70 * var(--px)) rgba(15, 22, 40, .25);
  backdrop-filter: var(--card-bf);
  -webkit-backdrop-filter: var(--card-bf);
  transition: background-color .9s ease, backdrop-filter .9s ease, -webkit-backdrop-filter .9s ease;
  animation: float 7s ease-in-out infinite;
}

.chat-header {
  display: flex;
  align-items: center;
  gap: calc(10 * var(--px));
  padding: calc(14 * var(--px)) calc(16 * var(--px));
  border-bottom: 1px solid rgba(27, 26, 23, .08);
}

.chat-avatar {
  width: calc(34 * var(--px));
  height: calc(34 * var(--px));
  border-radius: 50%;
  background: rgba(243, 234, 218, .8);
  display: flex;
  align-items: center;
  justify-content: center;
}

.chat-avatar img { height: calc(16 * var(--px)); }

.chat-id {
  display: flex;
  flex-direction: column;
  gap: calc(2 * var(--px));
}

.chat-title {
  font-size: calc(13.5 * var(--px));
  font-weight: 600;
  color: var(--title);
  transition: color .9s ease;
}

.chat-status {
  display: flex;
  align-items: center;
  gap: calc(5 * var(--px));
}

.dot {
  width: calc(6 * var(--px));
  height: calc(6 * var(--px));
  border-radius: 50%;
  background: #34A863;
  animation: ping 2.4s infinite;
}

.online-now {
  font-size: calc(10.5 * var(--px));
  font-weight: 500;
  letter-spacing: .01em;
  color: #2E9558;
}

.chip {
  position: relative;
  margin: calc(12 * var(--px)) calc(16 * var(--px)) 0;
  height: calc(13 * var(--px));
  text-align: center;
  font-family: 'Spline Sans Mono', monospace;
  font-size: calc(8 * var(--px));
  letter-spacing: .16em;
  color: var(--chip);
  transition: color .9s ease;
}

/* conversation viewport — fixed height, bottom-anchored, top fade once it overflows */
.convo {
  position: relative;
  height: calc(242 * var(--px));
  margin: calc(9 * var(--px)) calc(14 * var(--px)) calc(14 * var(--px));
  overflow: hidden;
}

.convo.over {
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 calc(36 * var(--px)));
  mask-image: linear-gradient(to bottom, transparent, #000 calc(36 * var(--px)));
}

.convo-flow {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  gap: calc(10 * var(--px));
}

.bubble {
  max-width: 82%;
  font-size: calc(13 * var(--px));
  line-height: 1.5;
  padding: calc(9 * var(--px)) calc(13 * var(--px));
}

.bubble.visitor {
  align-self: flex-end;
  min-width: calc(20 * var(--px));
  background: rgba(43, 39, 31, .85);
  color: #FBF6EA;
  border-radius: calc(14 * var(--px)) calc(14 * var(--px)) calc(4 * var(--px)) calc(14 * var(--px));
  animation: ottoIn .45s cubic-bezier(.22, .9, .3, 1) both;
}

.bubble.otto {
  align-self: flex-start;
  background: var(--bubble-otto);
  color: #2A2620;
  border-radius: calc(14 * var(--px)) calc(14 * var(--px)) calc(14 * var(--px)) calc(4 * var(--px));
  transition: background-color .9s ease;
  animation: ottoIn .5s cubic-bezier(.22, .9, .3, 1) both;
}

.cv-cursor {
  display: inline-block;
  width: calc(7 * var(--px));
  height: calc(13 * var(--px));
  margin-left: calc(3 * var(--px));
  vertical-align: calc(-2 * var(--px));
  background: rgba(251, 246, 234, .85);
  animation: ottoBlink 1.05s infinite;
}

.cv-dots {
  display: flex;
  gap: calc(4 * var(--px));
  padding: calc(13 * var(--px)) calc(14 * var(--px));
  animation: ottoIn .35s ease both;
}

.cv-dots .tdot {
  width: calc(6 * var(--px));
  height: calc(6 * var(--px));
  border-radius: 50%;
  background: #8F887A;
  animation: ottoDot 1.2s ease-in-out infinite;
}

.cv-dots .tdot:nth-child(2) { animation-delay: .15s; }
.cv-dots .tdot:nth-child(3) { animation-delay: .3s; }

.qr-row {
  display: flex;
  flex-wrap: wrap;
  gap: calc(8 * var(--px));
  align-self: flex-start;
  margin-left: calc(2 * var(--px));
  animation: ottoIn .45s ease both;
}

.qr {
  font-size: calc(12.5 * var(--px));
  font-weight: 600;
  padding: calc(8 * var(--px)) calc(16 * var(--px));
  border-radius: 999px;
  cursor: default;
}

.qr-book {
  border: 1px solid rgba(43, 39, 31, .28);
  background: rgba(255, 255, 255, .62);
  color: #2A2620;
  transition: background-color .25s ease, color .25s ease, transform .25s ease;
}

.qr-book.pressed {
  background: rgba(43, 39, 31, .92);
  color: #FBF6EA;
  transform: scale(.95);
}

.qr-other {
  border: 1px solid rgba(43, 39, 31, .16);
  background: rgba(255, 255, 255, .32);
  color: #5C5443;
}

.booked {
  display: flex;
  align-items: center;
  gap: calc(11 * var(--px));
  padding: calc(12 * var(--px)) calc(15 * var(--px));
  border-radius: calc(14 * var(--px));
  border: 1px solid rgba(234, 223, 201, .7);
  background: var(--booked-bg);
  box-shadow: 0 calc(10 * var(--px)) calc(26 * var(--px)) rgba(15, 22, 40, .1);
  transition: background-color .9s ease;
  animation: ottoPop .55s cubic-bezier(.22, .9, .3, 1) both;
}

.badge {
  flex: none;
  width: calc(30 * var(--px));
  height: calc(30 * var(--px));
  border-radius: 50%;
  background: rgba(228, 240, 228, .85);
  color: #3E7E52;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: calc(14 * var(--px));
  font-weight: 700;
  animation: ottoCheck .55s cubic-bezier(.3, 1.4, .45, 1) .15s both;
}

.booked-text {
  display: flex;
  flex-direction: column;
  gap: calc(2 * var(--px));
  animation: ottoIn .45s ease .5s both;
}

.booked-title {
  font-size: calc(13 * var(--px));
  font-weight: 700;
  color: #2A2620;
}

.booked-sub {
  font-size: calc(11.5 * var(--px));
  color: var(--booked-sub);
  transition: color .9s ease;
}

.cv-replay {
  display: flex;
  justify-content: flex-end;
  padding: calc(2 * var(--px)) calc(2 * var(--px)) 0;
  animation: ottoIn .5s ease both;
}

.replay-btn {
  display: flex;
  align-items: center;
  gap: calc(4 * var(--px));
  border: 0;
  background: none;
  padding: 0;
  font-family: 'Spline Sans Mono', monospace;
  font-size: calc(9 * var(--px));
  letter-spacing: .13em;
  color: var(--chip);
  cursor: pointer;
  transition: opacity .18s ease, color .9s ease;
}

.replay-btn:hover { opacity: .65; }

.replay-icon { font-size: calc(12 * var(--px)); line-height: 1; }

/* Italic + dimmed per the reference frame */
.hero-desc {
  margin: 0;
  padding: 0 calc(2 * var(--px));
  text-align: right;
  font-style: italic;
  font-size: calc(15 * var(--px));
  line-height: 1.4;
  font-weight: 400;
  color: #FFFCF5CF;
  text-shadow: 0 1px calc(2 * var(--px)) rgba(10, 16, 22, .5), 0 calc(3 * var(--px)) calc(16 * var(--px)) rgba(10, 16, 22, .4);
}

/* ---------- Audience strip ---------- */

.strip {
  display: flex;
  flex-direction: column;
  background: #FAF3E4;
  /* no bottom border: the meadow band below continues the scene's bottom row
     pixel-identically — any hairline would break the fold seam */
}

/* B2 background: full-bleed illustrated scene, bottom-anchored so the storefronts
   never crop (sky crops from the top instead). Carries the scene entrance. */
.strip-bg {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

/* soft cream mask behind the text only — sky stays visible at the edges */
.strip-mask {
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse 46% 36% at 50% 30%, rgba(250, 243, 228, .92), rgba(250, 243, 228, .55) 55%, rgba(250, 243, 228, 0) 78%);
}

.strip-head {
  position: relative;
  flex: 1;
  min-height: 220px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: 20px;
  text-align: center;
  padding: 56px 48px 16px;
}

/* locked eyebrow system (hero→testimonial handoff): the mono eyebrow is retired */
.eyebrow {
  font-family: 'Instrument Sans', sans-serif;
  font-weight: 600;
  font-size: 11.5px;
  letter-spacing: .18em;
  color: #A08F76;
  margin-bottom: 14px; /* + the head's 20px gap = the spec's ~34px to the heading */
}

.strip-head h2 {
  margin: 0;
  font-family: 'Instrument Sans', sans-serif;
  font-weight: 600;
  font-size: 54px;
  line-height: 1.14;
  letter-spacing: -1.4px;
  color: #2A2620;
  text-wrap: balance;
}

.strip-head h2 em {
  font-family: 'Newsreader', serif;
  font-style: italic;
  font-weight: 500;
  letter-spacing: -.5px;
}

.strip-sub {
  margin: 0;
  max-width: 600px;
  font-size: 17px;
  line-height: 1.45;
  color: #6E6759;
  text-wrap: pretty;
}

/* bubble zone — in-flow 58% spacer; its bottom edge == section bottom, so the
   bubbles' bottom calc() anchors are unchanged from the previous background.
   No overflow clip: anchors scale with viewport WIDTH (pinned to the painting)
   while this spacer scales with HEIGHT, so on wide-short viewports (16" MBP)
   the tallest bubble rides above the spacer's top — it must stay visible. */
.strip-scene {
  position: relative;
  flex: none;
  /* shrinking the spacer grows the head above it, so the bottom-aligned copy
     sits --copy-down lower on wide-short windows. The scene's BOTTOM edge (the
     bubbles' anchor line and the painting's reference) never moves. */
  height: calc(58% - var(--copy-down));
}

.strip-photo {
  position: absolute;
  left: 0;
  right: 0;
  /* Width-anchored so the painting always draws at width/1586 scale. The source
     (1586×1413) extends 309 rows of meadow below the original scene for the future
     transition into the next section: 309/1586 = 19.483vw. Anchoring the image
     that far below the section (plus the 20px scene down-shift she chose) keeps
     the in-viewport crop pixel-identical — the extra meadow stays clipped under
     the fold until the next section exists to reveal it. */
  bottom: calc(-20px - 19.483vw - var(--scene-drop));
  width: 100%;
  height: auto;
  /* guard for unusually tall/narrow desktop windows: fall back to cover-cropping
     rather than ever showing a gap above the painting */
  min-height: calc(100% + 20px + var(--scene-drop));
  object-fit: cover;
  object-position: 50% 100%;
}

/* floating question bubbles — positions track people in the photo */
.qb {
  position: absolute;
  transform: translateX(-50%);
}

/* Anchors are painting-pinned and DELIBERATELY tight to the buildings — canvas-
   measured sky above each bubble's own roofline: qb-3 ~12px, qb-2 ~29px at
   2000w. Never shift these down (any shared drop lands a bubble on a building —
   tried 2026-08-05, reverted); breathing room vs the copy comes from
   --head-lift on .strip instead. */
.qb-1 { left: 9.5%; bottom: calc(0.268 * var(--W) - var(--scene-drop)); animation: bob 5.4s ease-in-out infinite; }
.qb-2 { left: 29%;  bottom: calc(0.28 * var(--W) - var(--scene-drop));  animation: bob 6.1s ease-in-out 0.8s infinite; }
.qb-3 { left: 48%;  bottom: calc(0.253 * var(--W) - var(--scene-drop)); animation: bob 5.7s ease-in-out 1.6s infinite; }
.qb-4 { left: 66%;  bottom: calc(0.236 * var(--W) - var(--scene-drop)); animation: bob 6.4s ease-in-out 2.3s infinite; }
.qb-5 { left: 84%;  bottom: calc(0.244 * var(--W) - var(--scene-drop)); animation: bob 5.9s ease-in-out 3s infinite; }

.qbubble {
  background: rgba(43, 39, 31, .92);
  padding: 11px 16px;
  font-size: 13.5px;
  font-weight: 500;
  font-style: italic;
  color: #FBF6EA;
  box-shadow: 0 14px 34px rgba(43, 39, 31, .28);
  white-space: nowrap;
}

/* tail corners alternate; the Bloom entrance grows each bubble from its tail */
.qb-1 .qbubble { border-radius: 16px 16px 5px 16px; transform-origin: 88% 100%; }
.qb-2 .qbubble { border-radius: 16px 16px 16px 5px; transform-origin: 12% 100%; }
.qb-3 .qbubble { border-radius: 16px 16px 5px 16px; transform-origin: 88% 100%; }
.qb-4 .qbubble { border-radius: 16px 16px 16px 5px; transform-origin: 12% 100%; }
.qb-5 .qbubble { border-radius: 16px 16px 5px 16px; transform-origin: 88% 100%; }

.qb-typing {
  display: flex;
  gap: 5px;
  padding: 15px 16px;
}

.qb-typing .tdot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #CFC8B8;
  animation: ottoDot 1.2s ease-in-out infinite;
}

.qb-typing .tdot:nth-child(2) { animation-delay: .15s; }
.qb-typing .tdot:nth-child(3) { animation-delay: .3s; }

@media (max-width: 1360px) {
  .qbubble { font-size: 12px; padding: 9px 13px; }
  .qb-typing { padding: 13px 14px; }
}

/* ---------- Hero ⇄ strip pinned dissolve — LOCKED ----------
   Both sections render full-viewport inside a sticky stage; scrolling the wrap's
   runway crossfades between them (scroll-driven --p, wheel = 1s sine tween).
   Below the strip's resting point the page scrolls natively — no snap anywhere. */

.stage-wrap {
  position: relative;
  height: calc(100vh + max(100vh, 780px));
  height: calc(100svh + max(100svh, 780px));
}

.stage {
  --p: 0;
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  min-height: 780px;
  overflow: hidden;
}

.stage .hero {
  position: absolute;
  inset: 0;
  z-index: 1;
}

.strip {
  position: absolute;
  inset: 0;
  z-index: 2;
  opacity: var(--p);
  pointer-events: none;
}

.strip.interactive { pointer-events: auto; }

/* ---------- Fixed chat launcher ---------- */

.otto-launcher {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 50;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  border: 0;
  background: rgba(22, 20, 16, .6);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 16px 40px rgba(10, 16, 22, .35);
  transition: transform .18s ease;
}

.otto-launcher:hover { transform: translateY(-2px); }

.otto-launcher img { height: 20px; display: block; }

/* ---------- "Done for you" — dusk storefront + journey rail (handoff §7, 12a) ---------- */

.dfy {
  --px: 1px;
  position: relative;
  /* framing (100vh) + the art's own below-fold extension — the fold now lives
     INSIDE this section, so nothing has to escape it */
  min-height: calc(100vh + var(--dfy-ext));
  display: flex;
  align-items: center;
  overflow: hidden; /* plain + universally safe; the art's top crop relies on it */
  /* the extra bottom padding keeps the copy centred in the framing, not in the
     taller section */
  padding: 120px 48px calc(90px + var(--dfy-ext));
  /* flat page cream, so the hand-off to the sky wrapper is one colour meeting
     the identical colour with no gradient slope-change to read as an edge
     (.dfy-blend supplies the testimonial's warmer cream at the top) */
  background: #FBF7EF;
}

.dfy-bg {
  /* Hannah's continuous tall render (1672×1398): rows 0–941 are this section's
     framing; the extra 457 rows hang below the fold (overflow:hidden) and
     reappear in the pricing band, which renders THE SAME FILE with this same
     sizing recipe offset by 100vh — identical scale + shared content origin ⇒
     the DFY→band seam is pixel-identical at every viewport (was a visible
     splice while this section still used the old 941-row 17B render). */
  position: absolute;
  left: 50%;
  /* THE seam contract, final form: the whole art (1398 rows at the cover
     scale) sits BOTTOM-ANCHORED in a section that is exactly framing + its
     extension tall. Row 941 therefore lands on the fold and row 1398 on the
     section's bottom edge automatically — no branches, no percentages of the
     section, no second copy to disagree with. Any excess is sky above the
     frame, clipped by the section's plain overflow:hidden.
     The bottom fade dissolves the extension into the cream that the sky
     wrapper starts from (the last 32.7% of the box IS the extension). */
  bottom: 0;
  top: auto;
  transform: translateX(-50%);
  height: var(--dfy-art-h, calc(1398 * var(--dfy-scale)));
  width: auto;
  min-width: 100%;
  max-width: none;
  object-fit: cover;
  object-position: 50% 0;
  /* fully dissolved by 90% — a healthy margin above the section's bottom edge,
     so even a small geometry difference between engines can't clip the art
     while it is still visible (that produced the hard line at the boundary) */
  -webkit-mask-image: linear-gradient(to bottom, #000 66%, rgba(0, 0, 0, .45) 79%, transparent 90%);
  mask-image: linear-gradient(to bottom, #000 66%, rgba(0, 0, 0, .45) 79%, transparent 90%);
}

.dfy-blend { /* continues the testimonial's cream — quote → this section reads as one surface */
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 34vh; /* 34% of the FRAMING (the section is now taller than a viewport) */
  background: linear-gradient(to bottom, #F3EDE0, rgba(243, 237, 224, .55) 46%, rgba(243, 237, 224, 0) 100%);
}

.dfy-wrap {
  position: relative;
  width: 100%;
  /* proportion-true: fixed 1120 made side margins swallow all extra window
     width (content started 22% from the left at 2000w vs the mockup's ~11%).
     77.8vw keeps the 1440 artboard's exact proportions at any width. */
  max-width: max(1120px, 77.8vw);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

.dfy-copy {
  width: 620px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.dfy-h {
  margin: 0;
  font-family: 'Instrument Sans', sans-serif;
  font-weight: 600;
  font-size: 46px;
  line-height: 1.08;
  letter-spacing: -1.2px;
  color: #2A2620;
}

.dfy-h em {
  font-family: 'Newsreader', serif;
  font-weight: 500;
}

.dfy-sub {
  margin: 16px 0 0;
  max-width: 460px;
  font-size: 15.5px;
  line-height: 1.55;
  color: #6E6759;
  text-wrap: pretty;
}

.dfy-cta-row {
  margin-top: 34px;
  display: flex;
  align-items: center;
  gap: 18px;
}

.dfy-cta {
  background: #26221B;
  color: #FBF6EA;
  font-size: 14.5px;
  font-weight: 600;
  text-decoration: none;
  padding: 13px 26px;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 14px 34px rgba(30, 24, 14, .3);
  transition: transform .18s ease;
}

.dfy-cta:hover { transform: translateY(-1px); }

/* 2×2 preview-step grid (17B) — fills the 620px content column, storefront clear on the right */
.dfy-grid {
  margin-top: 34px;
  width: 620px;
  max-width: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}

.dfy-step {
  display: flex;
  flex-direction: column;
  gap: 8px;
  border-radius: 20px;
  padding: 17px 19px 18px;
  background: rgba(252, 249, 241, .8);
  backdrop-filter: blur(22px) saturate(1.25);
  -webkit-backdrop-filter: blur(22px) saturate(1.25);
  border: 1px solid rgba(255, 255, 255, .55);
  box-shadow: 0 16px 36px rgba(43, 39, 31, .12);
}

.dfy-step-n {
  font-family: 'Spline Sans Mono', monospace;
  font-size: 9px;
  letter-spacing: .14em;
  color: #8F8265;
}

.dfy-step-t {
  font-size: 15.5px;
  font-weight: 700;
  letter-spacing: -.2px;
  color: #2A2620;
}

.dfy-step-b {
  font-size: 13.5px;
  line-height: 1.5;
  color: #6E6759;
}

.dfy-bar {
  margin-top: 34px;
  width: 520px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 10px;
  border-radius: 20px;
  padding: 19px 24px;
  background: rgba(38, 34, 27, .88);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, .1);
  box-shadow: 0 22px 54px rgba(15, 12, 7, .28);
}

.dfy-bar-t {
  font-family: 'Instrument Sans', sans-serif;
  font-weight: 600;
  font-size: 17px;
  line-height: 1.3;
  letter-spacing: -.2px;
  color: #FBF6EA;
}

.dfy-checks {
  display: flex;
  align-items: baseline;
  gap: 16px;
  flex-wrap: wrap;
}

.dfy-check {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.dfy-check > span:first-child {
  color: #8FCB9F;
  font-weight: 700;
  font-size: 12px;
}

.dfy-check > span:last-child {
  font-size: 12.5px;
  font-weight: 600;
  color: rgba(255, 253, 246, .85);
}

/* entrance pre-states — main.js drives these on scroll (hidden until it runs) */
.dfy-h, .dfy-sub, .dfy-cta-row, .dfy-step, .dfy-bar { opacity: 0; }

/* ═══════════ Pricing → FAQ — continuous periwinkle-sky wrapper (handoff v2) ═══════════ */

/* zero-height carrier for the cloud bank, unzoomed and painted between the DFY
   artwork (z auto) and the sky wrapper (z 4) — see the note in index.html */
.fold-clouds { position: relative; height: 0; z-index: 3; }

.skywrap {
  position: relative;
  z-index: 4;
  /* The v3 design scales ALL fixed sizes by --pz = max(1, vw/1790) on wide
     viewports (its "16-inch beside 30-inch" continuum). Production port for
     the below-DFY zone: real CSS zoom (main.js sets --pz), with every vh/--W
     formula inside divided by --pz so artwork geometry and section heights
     render viewport-true (zoom × x/pz = x) — only design-px sizes (type,
     cards, containers, paddings) grow. pz = 1 at ≤1790, so the 1440 artboard
     is bit-identical. The DFY fold seam is unaffected (its formulas cancel). */
  zoom: var(--pz, 1);
  /* Geometry inside this zoomed subtree uses ONLY px-valued custom properties
     set by main.js (--svh = one zoom-corrected vh; --wz = --W/pz; --vwz =
     100vw/pz). Never use raw vh/vw/--W units in here: Chrome versions disagree
     on whether viewport units scale under CSS zoom, and the mismatch rendered
     ~11% off on Hannah's browser (the DFY seam cut) while measuring correct
     on the preview engine. px vars scale identically on every engine. */
}

/* the sky's static fade-in window: transparent over the meadow continuation at
   the skywrap's top, solid before the pricing content arrives. The DISSOLVE
   lives here (sky fades IN over the one continuous dfy-bg image) — replacing
   the old .pb-meadow fade-OUT copy, whose separate render kept splicing at the
   fold on Hannah's machine. Mask on the wrapper so the img parallax inside
   never moves the fade line. px vars only (engine-proof under the zoom). */
.sky-mask {
  position: absolute;
  inset: 0;
  z-index: 0;
  /* The meadow has already dissolved to cream by this wrapper's top edge, so
     the sky materialises from here — but it must EASE in. A linear ramp
     starting at the edge reads as a line on flat cream (the eye catches the
     slope change); these stops hold flat, then curve up gently. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, transparent calc(3 * var(--svh, 1vh)), rgba(0, 0, 0, .06) calc(9 * var(--svh, 1vh)), rgba(0, 0, 0, .22) calc(18 * var(--svh, 1vh)), rgba(0, 0, 0, .5) calc(30 * var(--svh, 1vh)), #000 calc(48 * var(--svh, 1vh)));
  mask-image: linear-gradient(to bottom, transparent 0, transparent calc(3 * var(--svh, 1vh)), rgba(0, 0, 0, .06) calc(9 * var(--svh, 1vh)), rgba(0, 0, 0, .22) calc(18 * var(--svh, 1vh)), rgba(0, 0, 0, .5) calc(30 * var(--svh, 1vh)), #000 calc(48 * var(--svh, 1vh)));
}

.sky-bg { /* periwinkle sky behind the whole band; slight scroll parallax via main.js */
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 0;
  will-change: transform;
}

.sky-veil { /* continues the DFY cream into the sky so there's no hard seam */
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(251, 247, 239, 0) 0%, rgba(251, 247, 239, 0) 25%, rgba(251, 247, 239, .1) 52%, rgba(251, 247, 239, .46));
}

/* transition band — the DFY dusk meadow continues past the fold, masked into the sky */
.pricing-band {
  position: relative;
  z-index: 1;
  /* the meadow extension now lives inside the DFY section, so this is just the
     remaining slack of the v3 band (56vh total − the ~48.6vh extension) —
     the page rhythm below the fold is unchanged */
  height: calc(7.43 * var(--svh, 1vh));
  overflow: hidden;
}

/* (.pb-meadow is gone — the DFY background itself continues past the fold as
   one image; see .dfy's overflow note and .sky-mask. Don't re-add a second
   meadow copy: two renders of the fold can and did seam across engines.) */

/* cloud bank straddling the DFY→band seam (covers the boundary); rise+fade via main.js */
.pb-clouds {
  position: absolute;
  left: 0;
  top: calc(-1 * var(--dfy-ext) - 0.10 * var(--W)); /* straddles the transition; unzoomed vars — this now sits outside .skywrap */
  z-index: 2;
  width: 100%;
  height: auto;
  opacity: 0;
  pointer-events: none;
  will-change: transform, opacity;
  -webkit-mask-image: linear-gradient(to bottom, transparent 10%, #000 37%, #000 80%, transparent 99%);
  mask-image: linear-gradient(to bottom, transparent 10%, #000 37%, #000 80%, transparent 99%);
}

/* ---------- Pricing (single Essential split panel) ---------- */
.pricing {
  position: relative;
  z-index: 2;
  box-sizing: border-box;
  min-height: calc(90 * var(--svh, 1vh));
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 48px 96px;
  margin-top: calc(-12 * var(--svh, 1vh)); /* pulls the header up into the transition band */
}

.pr-wrap {
  position: relative;
  width: 100%;
  /* proportion-true (see .dfy-wrap); zoom-corrected px var — 11.1% side
     margins at any width, exact at the artboard */
  max-width: max(1120px, calc(0.778 * var(--vwz, 100vw)));
  display: flex;
  flex-direction: column;
  gap: 44px;
}

.pr-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 16px;
}

.pr-eyebrow {
  font-family: 'Instrument Sans', sans-serif;
  font-weight: 600;
  font-size: 11.5px;
  letter-spacing: .18em;
  color: #A08F76;
  margin-bottom: 18px;
}

.pr-h {
  margin: 0;
  font-family: 'Instrument Sans', sans-serif;
  font-weight: 600;
  font-size: 46px;
  line-height: 1.08;
  letter-spacing: -1.2px;
  color: #2A2620;
  text-wrap: balance;
}
.pr-h em { font-family: 'Newsreader', serif; font-weight: 500; font-style: italic; }

.pr-sub {
  margin: 0;
  max-width: 520px;
  font-size: 15.5px;
  line-height: 1.55;
  color: #6E6759;
  text-wrap: pretty;
}

.pr-card-wrap { display: flex; justify-content: center; }

.pr-card {
  width: 100%;
  max-width: 860px;
  display: grid;
  grid-template-columns: 1fr 1px 1fr;
  background: rgba(255, 253, 248, .94);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(43, 39, 31, .08);
  border-radius: 26px;
  box-shadow: 0 30px 72px rgba(43, 39, 31, .13);
  overflow: hidden;
}

.pr-left { padding: 42px 40px; display: flex; flex-direction: column; gap: 18px; }

.pr-tier {
  font-family: 'Instrument Sans', sans-serif;
  font-weight: 600;
  font-size: 10.5px;
  letter-spacing: .18em;
  color: #A08F76;
}

.pr-price { display: flex; align-items: baseline; gap: 7px; }
.pr-amt { font-family: 'Instrument Sans', sans-serif; font-weight: 600; font-size: 60px; line-height: 1; letter-spacing: -2px; color: #2A2620; }
.pr-per { font-size: 15px; color: #8F887A; }

.pr-desc { margin: 0; max-width: 270px; font-size: 14.5px; line-height: 1.55; color: #6E6759; }

.pr-cta {
  margin-top: auto;
  align-self: flex-start;
  text-align: center;
  background: #26221B;
  color: #FBF6EA;
  font-size: 14.5px;
  font-weight: 600;
  text-decoration: none;
  padding: 14px 26px;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 14px 34px rgba(38, 34, 27, .24);
  transition: background .18s ease, transform .18s ease;
}
.pr-cta:hover { background: #3A342A; transform: translateY(-1px); }

.pr-seam { background: linear-gradient(to bottom, rgba(43, 39, 31, 0), rgba(43, 39, 31, .12) 22%, rgba(43, 39, 31, .12) 78%, rgba(43, 39, 31, 0)); }

.pr-right {
  padding: 42px 40px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 13px;
  background: rgba(247, 242, 231, .5);
  font-size: 14px;
  color: #4A4438;
}

.pr-incl {
  font-family: 'Instrument Sans', sans-serif;
  font-weight: 600;
  font-size: 9.5px;
  letter-spacing: .18em;
  color: #A08F76;
  margin-bottom: 2px;
}

.pr-feat { display: flex; gap: 9px; }
.pr-check { color: #3E7E52; font-weight: 700; }

.pr-note { align-self: center; font-style: italic; font-size: 13px; color: #A6A08F; text-align: center; }

/* ---------- Cord divider (pricing → FAQ) ----------
   The cord draws itself across the page on scroll: .cord-reveal is a masked
   window (220px feather past its right edge) that update() slides right while
   counter-sliding the img — equal + opposite transforms, so the art holds
   still and only the window moves (composited; the mask itself never repaints).
   Pre-state hides the art until main.js takes over. */
.cord-divider { position: relative; z-index: 2; margin: -28px 0 14px; pointer-events: none; overflow: hidden; }
.cord-reveal {
  /* The draw is a mask sliding across static art (main.js animates
     mask-position), so the feather sits ON the leading edge instead of off the
     element's far side — that's what makes the edge soft rather than cut.
     --cord-feather (px, from the tuner's SOFT dial) is its length; the extra
     stops curve the falloff so it dissolves rather than ramps. */
  --f: var(--cord-feather, 260px);
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - var(--f)), rgba(0, 0, 0, .55) calc(100% - var(--f) * .55), rgba(0, 0, 0, .18) calc(100% - var(--f) * .26), transparent 100%);
  mask-image: linear-gradient(to right, #000 calc(100% - var(--f)), rgba(0, 0, 0, .55) calc(100% - var(--f) * .55), rgba(0, 0, 0, .18) calc(100% - var(--f) * .26), transparent 100%);
  -webkit-mask-size: calc(100% + var(--f)) 100%;
  mask-size: calc(100% + var(--f)) 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  /* pre-JS: parked far enough left to be fully hidden (px, not %, because
     mask-position percentages resolve against area−image, not the area) */
  -webkit-mask-position: calc(-1 * var(--vwz, 100vw) - var(--f)) 0;
  mask-position: calc(-1 * var(--vwz, 100vw) - var(--f)) 0;
  will-change: mask-position;
}
/* the art itself never moves now — only the mask does; this is just the v3
   render's resting tilt, pivoted at the handset */
.cord-divider img { display: block; width: 100%; height: auto; margin-left: -60px; opacity: .97; transform-origin: 100% 50%; transform: rotate(0.25deg); }

/* ---------- FAQ ---------- */
.faq {
  position: relative;
  z-index: 2;
  box-sizing: border-box;
  min-height: calc(100 * var(--svh, 1vh));
  display: flex;
  justify-content: center;
  padding: 96px 48px 240px; /* bottom pad gives the twilight room to begin (v3: 240, 400 on wide) */
}

.faq-wrap { width: 100%; max-width: max(1120px, calc(0.778 * var(--vwz, 100vw))); display: flex; align-items: flex-start; gap: 72px; } /* proportion-true, see .dfy-wrap */

.faq-rail {
  flex: none;
  width: 340px;
  position: sticky;
  top: 96px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.faq-eyebrow {
  font-family: 'Instrument Sans', sans-serif;
  font-weight: 600;
  font-size: 11.5px;
  letter-spacing: .18em;
  color: #A08F76;
  margin-bottom: 18px;
}

.faq-h {
  margin: 0;
  font-family: 'Instrument Sans', sans-serif;
  font-weight: 600;
  font-size: 46px;
  line-height: 1.08;
  letter-spacing: -1.2px;
  color: #2A2620;
  text-wrap: balance;
}
.faq-h em { font-family: 'Newsreader', serif; font-weight: 500; font-style: italic; }

.faq-sub { margin: 0; font-size: 15px; line-height: 1.6; color: #6E6759; text-wrap: pretty; }
.faq-sub a { color: #7A6A4F; text-decoration: none; }
.faq-sub a:hover { color: #2A2620; }

.faq-scene { position: relative; height: 400px; margin-top: 14px; }
.faq-scene img {
  position: absolute;
  top: -42px;
  left: -290px;
  width: 730px;
  max-width: none;
  height: auto;
  pointer-events: none;
  user-select: none;
  opacity: .97;
  transform: rotate(-1.5deg);
  -webkit-mask-image: radial-gradient(ellipse 70% 60% at 52% 42%, #000 46%, transparent 92%);
  mask-image: radial-gradient(ellipse 70% 60% at 52% 42%, #000 46%, transparent 92%);
}

.faq-list { flex: 1; display: flex; flex-direction: column; }
/* the top hairline lives on the first row (not the list) so it fades in with it */
.faq-item { border-bottom: 1px solid rgba(43, 39, 31, .12); }
.faq-item:first-child { border-top: 1px solid rgba(43, 39, 31, .12); }

.faq-q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 21px 6px;
  cursor: pointer;
  transition: opacity .18s ease;
}
.faq-q:hover { opacity: .7; }
.faq-qt { font-size: 16.5px; font-weight: 600; letter-spacing: -.2px; color: #2A2620; }

.faq-plus {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid rgba(43, 39, 31, .25);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  font-weight: 500;
  color: #6E6759;
  transition: transform .3s ease;
}
.faq-item.open .faq-plus { transform: rotate(45deg); }

.faq-a {
  overflow: hidden;
  max-height: 0;
  padding: 0 6px;
  max-width: 600px;
  font-size: 15px;
  line-height: 1.6;
  color: #6E6759;
  text-wrap: pretty;
  transition: max-height .35s ease, padding .35s ease;
}
.faq-item.open .faq-a { max-height: 400px; padding: 0 6px 22px; }

/* ---------- Closing — "After hours" night CTA + footer (handoff v3 §11, FINAL) ---------- */
.closing {
  position: relative;
  z-index: 2;
  /* 112vh per spec (zoom-corrected) + a wide-short compromise. The width-scaled
     art overshoots the 112vh section on wide-short windows; no single move
     preserves all three of (FAQ bleed 253px / one-viewport section / footer on
     the art's dead-cobble bottom rows) at aspect ≳1.8, so the overshoot SPLITS:
     ~55% grows the section (a modest scroll, not the earlier cobble desert)
     and ~45% slides the art down past the page end (--cs-slide on .cs-env img).
     Result: bleed stays 253px, the footer lands ~80% on the dead cobbles, and
     the section runs ~1.3 viewports at 2000-wide. Both terms are 0 at the
     1440×900 artboard. The 0.55/0.45 split is the tuning knob. */
  --cs-overshoot: calc(0.876 * var(--wz, var(--W)) - 112 * var(--svh, 1vh) - 253px);
  min-height: calc(112 * var(--svh, 1vh) + clamp(0px, 0.55 * var(--cs-overshoot), 330px));
  --cs-slide: clamp(0px, calc(0.45 * var(--cs-overshoot)), 270px);
  display: flex;
  flex-direction: column;
}

/* twilight base — begins 650px above the section, painting over the FAQ's tail
   (transparent top, so the periwinkle sky warms through peach into starry navy) */
.cs-twilight {
  position: absolute;
  left: 0;
  right: 0;
  top: -650px;
  bottom: 0;
  overflow: hidden;
  pointer-events: none;
}
.cs-twilight img { display: block; width: 100%; height: 100%; object-fit: fill; }

/* storybook environment — glowing shop + cobble plaza, bottom-anchored; the
   800px headroom keeps the art's faded top from hard-clipping on tall viewports */
.cs-env {
  position: absolute;
  left: 0;
  right: 0;
  top: -800px;
  bottom: 0;
  overflow: hidden;
  pointer-events: none;
}
.cs-env img { position: absolute; left: 0; bottom: calc(-1 * var(--cs-slide, 0px)); width: 100%; height: auto; }

/* ---------- 22B wisps (handoff: design_handoff_otto_closing_wisps) ----------
   Tiny chat bubbles rising out of the shop's window glow. Anchors are
   fractions of the effective page width (the art is width-scaled), so they
   track the window as it resizes; --wz is the zoom-corrected width because
   this lives inside .skywrap. Ambient only — no scroll or pointer input. */
.cs-wisps { position: absolute; inset: 0; z-index: 3; pointer-events: none; }

.wisp {
  position: absolute;
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 8px 10px;
  border-radius: 11px;
  animation: cbRise 9s ease-out infinite both;
}

/* The anchors are fractions of the page width measured from the ART's bottom
   edge, so they must also absorb --cs-slide: on wide-short windows the closing
   art is pushed down to keep the moon in frame, and without this the wisps stay
   put and drift out of the window glow (they read ~60px high at 2000x1200). */
.wisp-1 {
  right: 21.5%;
  bottom: calc(0.381 * var(--wz, var(--W)) - var(--cs-slide, 0px));
  background: rgba(38, 34, 27, .88);
  border-radius: 11px 11px 4px 11px; /* tail lower-right */
  box-shadow: 0 8px 22px rgba(4, 8, 20, .35);
}

.wisp-2 {
  right: 24%;
  bottom: calc(0.377 * var(--wz, var(--W)) - var(--cs-slide, 0px));
  flex-direction: row;
  align-items: center;
  gap: 4px;
  background: rgba(252, 246, 232, .93);
  border-radius: 11px 11px 11px 4px; /* tail lower-left */
  box-shadow: 0 8px 22px rgba(4, 8, 20, .3);
  animation-delay: 3s;
}

.wisp-3 {
  right: 20%;
  bottom: calc(0.372 * var(--wz, var(--W)) - var(--cs-slide, 0px));
  background: rgba(252, 246, 232, .93);
  border-radius: 11px 11px 11px 4px;
  box-shadow: 0 8px 22px rgba(4, 8, 20, .3);
  animation-delay: 6s;
}

.wisp-bar { display: block; height: 4.5px; border-radius: 3px; }
.wisp-1 .wisp-bar:first-child { width: 40px; background: rgba(251, 246, 234, .8); }
.wisp-1 .wisp-bar:last-child { width: 26px; background: rgba(251, 246, 234, .5); }
.wisp-3 .wisp-bar:first-child { width: 34px; background: rgba(66, 58, 46, .7); }
.wisp-3 .wisp-bar:last-child { width: 46px; background: rgba(66, 58, 46, .42); }

.wisp-dot {
  width: 4.5px;
  height: 4.5px;
  border-radius: 50%;
  background: #57503F;
  animation: ottoDot 1.2s ease-in-out infinite;
}
.wisp-dot:nth-child(2) { animation-delay: .15s; }
.wisp-dot:nth-child(3) { animation-delay: .3s; }

/* quick pop-in by the window, then a slow drift up-and-left, growing + fading */
@keyframes cbRise {
  0% { opacity: 0; transform: translate(0, 0) scale(.72); }
  12% { opacity: .95; transform: translate(-3px, -18px) scale(.88); }
  55% { opacity: .75; }
  100% { opacity: 0; transform: translate(-16px, -120px) scale(1); }
}

.cs-main {
  position: relative;
  z-index: 2;
  flex: 1;
  display: flex;
  align-items: center;
}

.cs-wrap { width: 100%; max-width: max(1120px, calc(0.778 * var(--vwz, 100vw))); margin: 0 auto; } /* proportion-true; flush with the footer's content line (the dc puts the CTA at the container edge — the extra 48px inset was ours, and read as shifted-right) */

.cs-copy { max-width: 560px; display: flex; flex-direction: column; align-items: flex-start; gap: 22px; }

/* page-wide eyebrow treatment (Instrument Sans, not mono — v3 rule for this section) */
.cs-eyebrow {
  font-family: 'Instrument Sans', sans-serif;
  font-weight: 600;
  font-size: 10.5px;
  letter-spacing: .18em;
  color: rgba(255, 244, 220, .75);
}

.cs-h {
  margin: 0;
  font-family: 'Instrument Sans', sans-serif;
  font-weight: 600;
  font-size: 58px;
  line-height: 1.08;
  letter-spacing: -1.4px;
  color: #FFFDF6;
  text-shadow: 0 2px 24px rgba(4, 8, 20, .55);
  text-wrap: balance;
}
.cs-h em { font-family: 'Newsreader', serif; font-weight: 500; font-style: italic; letter-spacing: -.5px; }

.cs-sub {
  margin: 0;
  max-width: 400px;
  font-size: 16px;
  line-height: 1.55;
  color: rgba(255, 250, 240, .88);
  text-wrap: pretty;
}

.cs-cta-row { margin-top: 6px; }
.cs-cta {
  display: inline-block;
  background: #FFF3E2;
  color: #1E2433;
  font-size: 14.5px;
  font-weight: 600;
  text-decoration: none;
  padding: 14px 26px;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 22px 54px rgba(15, 12, 7, .28);
  transition: transform .18s ease, background .18s ease;
}
.cs-cta:hover { transform: translateY(-1px); background: #FFF8ED; }

/* footer — sits on the open cobblestones at the bottom of the storybook art */
.cs-footer { position: relative; z-index: 2; padding: 0 48px 40px; }
.cs-footer-wrap { max-width: max(1120px, calc(0.778 * var(--vwz, 100vw))); margin: 0 auto; } /* proportion-true, matches the CTA block's margins */
.cs-footer-cols { display: flex; justify-content: space-between; align-items: flex-start; gap: 48px; padding-bottom: 30px; }

.cs-wordmark { height: 24px; display: block; }
.cs-tagline { margin-top: 12px; font-size: 13.5px; color: rgba(255, 250, 240, .6); }

.cs-links { display: flex; gap: 72px; }
.cs-col { display: flex; flex-direction: column; gap: 11px; }
.cs-col-t {
  font-family: 'Instrument Sans', sans-serif;
  font-weight: 600;
  font-size: 9.5px;
  letter-spacing: .15em;
  color: rgba(255, 244, 220, .5);
  margin-bottom: 3px;
}
.cs-col a { font-size: 14px; color: rgba(255, 250, 240, .85); text-decoration: none; transition: opacity .18s ease; }
.cs-col a:hover { opacity: .65; }

.cs-legal {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-top: 1px solid rgba(251, 246, 234, .16);
  padding-top: 18px;
  font-size: 12.5px;
  color: rgba(255, 250, 240, .55);
}
.cs-domain { font-family: 'Instrument Sans', sans-serif; font-weight: 600; font-size: 10px; letter-spacing: .14em; }

/* ---------- Wide viewport (locked 30-inch layout, handoff v3) — below-pricing scope ---------- */
/* ═══════════ 21B — the wide (30-inch) layout ═══════════
   Turned on by main.js as html.wide at >= 2200px, where it also moves the page
   zoom to the root (see applyLayout). Class-driven rather than a media query so
   the trigger and the zoom can't disagree: under root zoom, media queries and
   layout no longer measure the same width. Everything here is expressed in the
   zoomed CSS space — at 2560 the root scales it all by ~1.43.
   Reference: design_handoff_otto_30in_21b. Below the gate none of this applies
   and the page is pixel-identical to the locked 16-inch build. */

/* Viewport units do NOT rescale under root zoom — 100vh stays the real screen
   height and then gets multiplied by z, so a "full-screen" band would come out
   1.43 screens tall. Every vw/vh-derived value therefore switches to the
   JS-fed, already-z-corrected px vars in this mode. (The spec calls this out:
   "any 100vh-based section heights are divided by z".) */
html.wide {
  --W: var(--wz);
  --px: 1px; /* the root zoom replaces the hero's XL fluid scale — otherwise both compound */
  --scene-drop: clamp(0px, calc((0.28 * var(--wz) + 22px - 47.5 * var(--svh)) * 1.1), 128px);
  --copy-down: clamp(0px, calc((0.28 * var(--wz) + 22px - 47.5 * var(--svh)) * 0.4), 46px);
}

html.wide .stage-wrap { height: calc(100 * var(--svh) + max(100 * var(--svh), 780px)); }
html.wide .stage { height: calc(100 * var(--svh)); }
html.wide .toolbar-pill { max-width: max(1160px, calc(0.64804 * var(--vwz))); }
html.wide .strip-photo { bottom: calc(-20px - 0.19483 * var(--vwz) - var(--scene-drop)); }
html.wide .dfy { min-height: calc(100 * var(--svh) + var(--dfy-ext)); }
html.wide .dfy-blend { height: calc(34 * var(--svh)); }

/* hero: rail opens up. The art is deliberately NOT transformed here.
   The 21B README §2 asks for `translateY(70px) scale(1.38)` on the hero bg and
   the reference really does apply it — but to a DIFFERENT ASSET. The reference's
   hero is `uploads/hero_3_flipped.png` (2305×1203, aspect 1.916): a wide, distant
   framing where the desk is small and the sun sits on the horizon. Ours is
   `hero-day.jpg` (3536×2104, aspect 1.681), the Veo frame matching hero-loop.mp4 —
   a much tighter crop of the same scene, roughly 1.65× closer in. The design's
   1.38 push-in is tuned for the wide render; applied to our already-tight one it
   compounds (measured 2560×1350: ours would render the subject ~64% larger than
   the mockup; without it, ~19% larger). So it stays off — closest achievable match
   until the asset question is settled. Closing that last 19% needs a wider hero
   render, not CSS: cover already fills the width exactly, so there is no room to
   scale down without letterboxing. */
html.wide .hero-rail-inner { max-width: 1660px; }

/* S3 — the band trims and the copy lift deepens so the headline still sits at
   ~mid-viewport on the art, which the wider aspect would otherwise push down */
html.wide .op-wrap { max-width: 1340px; gap: 90px; }
html.wide .op-right { width: 520px; margin-top: calc(-0.155 * var(--W) + 148px); }
html.wide .op-left { margin-top: calc(-0.155 * var(--W)); }
html.wide .meadow-band { height: calc(0.40 * var(--W)); }

/* testimonial — the quote sets three lines instead of four */
html.wide .tm-rise { max-width: 1240px; }

/* done for you — copy shifts left, storefront art breathes on the right */
html.wide .dfy-wrap { max-width: 1440px; }
html.wide .dfy-copy { width: 660px; }
html.wide .dfy-bar { margin-top: 34px; }

/* FAQ — rail and accordion both widen; more runway into the twilight */
html.wide .faq { padding-bottom: 400px; }
html.wide .faq-wrap { max-width: 1400px; gap: 110px; }
html.wide .faq-rail { width: 400px; }

/* closing + footer */
html.wide .cs-wrap, html.wide .cs-footer-wrap { max-width: 1400px; }

/* entrance pre-states for pricing/FAQ/closing — update() in main.js scrubs
   these on scroll (word sweeps + gated rises, like DFY); shown for reduced motion */
.pricing [data-an], .faq [data-an], .closing [data-an], .faq-item { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .pricing [data-an], .faq [data-an], .closing [data-an], .faq-item { opacity: 1; }
  .cord-reveal { -webkit-mask-position: 0 0; mask-position: 0 0; }
}

/* ---------- Scroll hint ----------
   Fades in at bottom-center after 2s of rest on the strip; scrolling dismisses
   it. A single thin vertical line (per her site) — a faint rail with a brighter
   runner sliding down it. Shown/hidden by main.js via .on. */

.scroll-hint {
  position: fixed;
  left: 50%;
  bottom: 44px; /* nudged up from 24 — sat too low to notice on the 16" */
  transform: translateX(-50%);
  z-index: 40;
  width: 2px;
  height: 24px; /* her call: the 48px line ran long for the space */
  overflow: hidden;
  border-radius: 2px;
  pointer-events: none;
  opacity: 0;
  transition: opacity .45s ease;
  filter: drop-shadow(0 0 5px rgba(38, 34, 27, .3)); /* soft dark ground against the grass */
}

.scroll-hint.on {
  opacity: 1;
  transition: opacity .9s ease;
}

/* rail removed — just the white runner slides (her call: drop the faint track behind it) */

.scroll-hint::after { /* runner */
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  width: 2px;
  height: 10px;
  border-radius: 2px;
  background: rgba(255, 252, 245, .95);
  transform: translateY(-10px);
}

.scroll-hint.on::after { animation: hint-run 2.4s ease-in-out infinite; }

@keyframes hint-run {
  0% { transform: translateY(-10px); }
  100% { transform: translateY(24px); }
}

/* ---------- Hero entrance animation ----------
   LIVE: 2 · Slow drift (default, set by the head script via data-anim on <html>).
   BACKUP: 3 · Morning haze — kept in case the choice is revisited; preview with
   ?anim=3. Any other ?anim value disables the entrance. */

.hero {
  --ease-out-soft: cubic-bezier(0.16, 1, 0.3, 1);
}

@keyframes riseIn {
  from { opacity: 0; transform: translateY(var(--rise, 20px)); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes dropIn {
  from { opacity: 0; transform: translateY(var(--drop, -12px)); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes bgSettle {
  from { transform: scale(1.07); }
  to { transform: scale(1); }
}

@keyframes hazeClear {
  from { opacity: 1; }
  to { opacity: 0; }
}

/* --- 2 · Slow drift — the page surfaces as one long breath --- */
html[data-anim="2"] .toolbar-pill { --drop: calc(-8 * var(--px)); animation: dropIn 2.9s var(--ease-out-soft) .25s both; }
html[data-anim="2"] .hero-copy h1 { --rise: calc(10 * var(--px)); animation: riseIn 3.2s var(--ease-out-soft) .45s both; }
html[data-anim="2"] .cta-row { --rise: calc(10 * var(--px)); animation: riseIn 3.2s var(--ease-out-soft) .6s both; }
html[data-anim="2"] .chat-card { --rise: calc(12 * var(--px)); animation: riseIn 3.4s var(--ease-out-soft) .55s both, float 7s ease-in-out 3.95s infinite; }
html[data-anim="2"] .hero-desc { --rise: calc(8 * var(--px)); animation: riseIn 3.2s var(--ease-out-soft) .8s both; }

/* --- 3 · Morning haze — background settles from a slight zoom while a warm
       haze lifts; elements fade in place --- */
html[data-anim="3"] .hero-bg { animation: bgSettle 3s var(--ease-out-soft) both; transform-origin: 50% 60%; }
html[data-anim="3"] .hero::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(rgba(255, 246, 230, .55), rgba(255, 242, 224, .4));
  animation: hazeClear 2.2s ease .3s both;
  pointer-events: none;
}
html[data-anim="3"] .toolbar-pill { animation: fadeIn 1.6s ease .45s both; }
html[data-anim="3"] .hero-copy h1 { animation: fadeIn 1.6s ease .7s both; }
html[data-anim="3"] .cta-row { animation: fadeIn 1.6s ease 1s both; }
html[data-anim="3"] .chat-card { animation: fadeIn 1.6s ease .9s both, float 7s ease-in-out 2.5s infinite; }
html[data-anim="3"] .hero-desc { animation: fadeIn 1.6s ease 1.25s both; }

/* ---------- Ambient + conversation keyframes ---------- */

@keyframes float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(calc(-6 * var(--px))); }
}

@keyframes ping {
  0%, 100% { opacity: 1; }
  50% { opacity: .35; }
}

@keyframes bob {
  0%, 100% { transform: translate(-50%, 0); }
  50% { transform: translate(-50%, -7px); }
}

@keyframes ottoIn {
  from { opacity: 0; transform: translateY(9px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes ottoPop {
  from { opacity: 0; transform: scale(.94) translateY(6px); }
  to { opacity: 1; transform: scale(1) translateY(0); }
}

@keyframes ottoBlink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}

@keyframes ottoDot {
  0%, 60%, 100% { opacity: .3; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-3px); }
}

@keyframes ottoCheck {
  0% { opacity: 0; transform: scale(.25) rotate(-14deg); }
  60% { opacity: 1; transform: scale(1.14) rotate(2deg); }
  100% { opacity: 1; transform: scale(1) rotate(0deg); }
}

/* ---------- Meadow reveal band (strip → S3 transition) ----------
   Continues the strip's staged meadow at identical width-fit scale/alignment,
   then dissolves it through mist/haze into S3's cream. Scroll-linked values
   (mist rise, haze trail, fog release) are driven per-frame from main.js. */

.meadow-band {
  position: relative;
  height: calc(0.46 * var(--W));
  overflow: hidden;
  background: #F3EDE0;
}

.mw-grass,
.mw-scene {
  position: absolute;
  left: 0;
  width: 100%;
  height: auto;
}

/* deeper-grass re-render behind the original: fills the sliver where the mist
   edge arrives below the original's feather */
.mw-grass {
  /* + --scene-drop: rides the strip's scene push-down so the fold seam stays
     pixel-continuous (the drop cancels in the seam equation) */
  top: calc(20px - 0.59722 * var(--W) + var(--scene-drop));
  -webkit-mask-image: linear-gradient(to bottom, #000 86%, transparent 99%);
  mask-image: linear-gradient(to bottom, #000 86%, transparent 99%);
}

/* the strip's own image — band top row continues the fold pixel-identically */
.mw-scene {
  top: calc(20px - 0.69609 * var(--W) + var(--scene-drop));
  -webkit-mask-image: linear-gradient(to bottom, #000 90%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 90%, transparent 100%);
}

.mw-haze {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: calc(-0.25 * var(--W));
  transform: translateY(calc(0.09 * var(--W))); /* ez=0 rest state; JS scrubs */
  opacity: .55;
  will-change: transform, opacity;
}

.mw-haze-grad {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: calc(0.5 * var(--W));
  background: linear-gradient(to bottom, rgba(246, 240, 228, 0) 8%, rgba(246, 240, 228, .45) 30%, rgba(246, 240, 228, .85) 45%, #F6F0E4 58%, #F6F0E4 100%);
}

.mw-haze-solid {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(0.498 * var(--W));
  bottom: 0;
  background: #F6F0E4;
}

/* mist/tree-line overlay — FLIPPED horizontally (locked "flipped sky"): its
   high edge sits top-left, S3's copy staggers up into the clearing */
.mw-mist {
  position: absolute;
  left: 0;
  top: calc(-0.19 * var(--W));
  width: 100%;
  height: calc(1.441 * var(--W)); /* ≈18% vertical trim vs native — intentional */
  transform: scaleX(-1) translateY(calc(0.2 * var(--W))); /* ez=0; JS scrubs */
  opacity: .1;
  will-change: transform, opacity;
}

.mw-fog {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  top: calc(0.2 * var(--W));
  background: linear-gradient(to bottom, rgba(246, 240, 228, 0), rgba(246, 240, 228, .78) 30%, #F6F0E4 58%);
  opacity: 1;
  pointer-events: none;
  will-change: opacity;
}

.mw-base {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 45%;
  background: linear-gradient(to bottom, rgba(243, 237, 224, 0), #F3EDE0 90%);
}

/* ---------- Section 3 — "See what Otto can take off your plate" ----------
   Fixed sizes (--px pinned to 1px). Locked "flipped sky + stagger" geometry:
   the copy column lifts into the mist's top-left clearing, the demo card rests
   lower right. Entrances are scroll-linked from main.js (reversible). */

.offplate {
  --px: 1px;
  position: relative;
  display: flex;
  justify-content: center;
  padding: 0 48px 104px;
  background: #F3EDE0;
  /* clip-x only: cloud 2 overhangs the right edge (right: -420px) and would
     otherwise widen the page — trackpad side-swipes panned into blank space.
     clip (not hidden) keeps overflow-y truly visible for the clouds' downward
     bleed into the testimonial, and never creates a scroll container. */
  overflow-x: clip;
}

.op-cloud {
  position: absolute;
  bottom: -290px;
  left: -280px;
  width: 960px;
  opacity: 0;
  pointer-events: none;
  z-index: 0;
  will-change: transform, opacity;
}

.op-cloud2 { /* right edge, settled lower — decorates the space between demo and quote */
  position: absolute;
  bottom: -420px;
  right: -420px;
  width: 780px;
  opacity: 0;
  pointer-events: none;
  z-index: 0;
  will-change: transform, opacity;
}

.op-wrap {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 1120px;
  display: flex;
  align-items: flex-start;
  gap: 64px;
}

.op-left {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  position: relative;
  z-index: 5;
  margin-top: calc(-0.13 * var(--W));
}

.op-h {
  margin: 0;
  font-family: 'Instrument Sans', sans-serif;
  font-weight: 600;
  font-size: 46px;
  line-height: 1.08;
  letter-spacing: -1.2px;
  color: #2A2620;
  will-change: transform, opacity;
}

.op-h em {
  font-family: 'Newsreader', serif;
  font-style: italic;
  font-weight: 500;
}

.op-sub {
  margin: 16px 0 0;
  max-width: 460px;
  font-size: 15.5px;
  line-height: 1.55;
  color: #6E6759;
  text-wrap: pretty;
  will-change: transform, opacity;
}

.op-cards {
  margin-top: 34px;
  align-self: stretch;
  display: flex;
  gap: 14px;
}

.op-card-w {
  flex: 1;
  display: flex;
  will-change: transform, opacity;
}

.op-card {
  appearance: none;
  font: inherit;
  text-align: left;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 10px;
  border-radius: 20px;
  padding: 15px 16px 17px;
  cursor: pointer;
  background: rgba(255, 253, 248, .55);
  border: 1px solid rgba(43, 39, 31, .12);
  box-shadow: 0 0 0 rgba(43, 39, 31, 0);
  transition: background-color .3s ease, border-color .3s ease, box-shadow .35s ease, transform .35s cubic-bezier(.22, .9, .3, 1);
}

.op-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 22px 44px rgba(43, 39, 31, .14);
}

.op-card.active {
  background: rgba(255, 251, 241, .95);
  border-color: rgba(199, 154, 62, .6);
  box-shadow: 0 16px 36px rgba(43, 39, 31, .1);
}

.op-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.op-num {
  font-family: 'Spline Sans Mono', monospace;
  font-size: 9px;
  letter-spacing: .14em;
  color: #B4A98F;
}

.op-playing {
  display: flex;
  align-items: center;
  gap: 4px;
  font-family: 'Instrument Sans', sans-serif;
  font-weight: 600;
  font-size: 8px;
  letter-spacing: .14em;
  padding: 4px 8px;
  border-radius: 999px;
  background: rgba(38, 34, 27, .9);
  color: #F2C24E;
  opacity: 0;
  transition: opacity .3s ease;
}

/* 7px play triangle before the word */
.op-play-tri {
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 3.5px 0 3.5px 6px;
  border-color: transparent transparent transparent currentColor;
}

.op-card.active .op-playing { opacity: 1; }

.op-illo {
  align-self: center;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 128px;
  height: 108px;
  background: radial-gradient(ellipse 62% 55% at 50% 52%, rgba(164, 183, 209, .32), rgba(190, 201, 216, .14) 58%, rgba(190, 201, 216, 0) 78%);
}

/* The artwork is a TRANSPARENT PNG (uploads/*-objects-only.png) — no blend mode.
   It used to be a white-background JPEG knocked out by `mix-blend-mode: multiply`,
   which left a hard white square on the cards. Two compounding reasons, both fixed
   by going transparent: (1) a blend only reaches as far as its isolation group, and
   the float animation on .op-illo created a stacking context, so the image
   multiplied against .op-illo's own gradient instead of the card; (2) even with
   that corrected, `.op-card` backgrounds are TRANSLUCENT (.55 inactive / .95
   active), and multiply can't fully resolve against a partly transparent backdrop —
   which is exactly why the active card looked clean while the other two kept a
   visible box. Alpha sidesteps the whole class of problem. Don't reintroduce
   mix-blend-mode here. The float stays on the img (harmless now, but it also keeps
   .op-illo free of a stacking context). */
.op-illo img {
  object-fit: contain;
  animation: bobS3 5.8s ease-in-out infinite;
}

.op-card-w:nth-child(2) .op-illo img { animation-delay: -1.9s; }
.op-card-w:nth-child(3) .op-illo img { animation-delay: -3.8s; }

/* per-artwork heights equalize the drawn size across the set */
.op-card[data-scenario="0"] .op-illo img { height: 112px; }
.op-card[data-scenario="1"] .op-illo img { height: 90px; }
.op-card[data-scenario="2"] .op-illo img { height: 114px; }

.op-card-text {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.op-card-title {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -.2px;
  color: #2A2620;
}

.op-card-tag {
  font-size: 12.5px;
  font-style: italic;
  line-height: 1.45;
  color: #8F887A;
}

.op-right {
  flex: none;
  width: 460px;
  position: relative;
  z-index: 5;
  margin-top: calc(-0.13 * var(--W) + 148px); /* card top ≈ sub-copy top */
  will-change: transform, opacity;
}

/* scroll-fx pre-states (JS drives per frame; reduced-motion sets final) */
.op-h, .op-sub, .op-right, .op-card-w { opacity: 0; }

/* ---------- S3 demo chat card (fixed sizes; message classes shared via --px: 1px) ---------- */

.demo-card {
  border-radius: 22px;
  background: rgba(252, 249, 241, .58);
  backdrop-filter: blur(22px) saturate(1.25);
  -webkit-backdrop-filter: blur(22px) saturate(1.25);
  border: 1px solid rgba(255, 255, 255, .55);
  box-shadow: 0 34px 80px rgba(43, 39, 31, .12);
}

.dc-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 16px;
  border-bottom: 1px solid rgba(27, 26, 23, .08);
}

.dc-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: rgba(243, 234, 218, .8);
  display: flex;
  align-items: center;
  justify-content: center;
}

.dc-avatar img { height: 16px; }

.dc-id {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.dc-biz {
  font-size: 13.5px;
  font-weight: 600;
  color: #2A2620;
}

.dc-status {
  display: flex;
  align-items: center;
  gap: 5px;
}

.dc-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #34A863;
  animation: ping 2.4s infinite;
}

.dc-online {
  font-size: 10.5px;
  font-weight: 500;
  color: #2E9558;
}

.dc-chip {
  margin: 12px 16px 0;
  height: 13px;
  text-align: center;
  font-family: 'Spline Sans Mono', monospace;
  font-size: 8px;
  letter-spacing: .16em;
  color: #9C9484;
}

.demo-window {
  position: relative;
  height: 306px;
  margin: 10px 16px 6px;
  overflow: hidden;
}

.demo-window.over {
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 40px);
  mask-image: linear-gradient(to bottom, transparent, #000 40px);
}

.demo-flow {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  gap: 10px;
  padding-bottom: 14px;
}

/* an Otto bubble can hand off to the business's own booking / service page:
   a bold underlined link line, then a follow-on paragraph (handoff: S3 demo
   update — availability + after-hours now link out instead of booking here) */
.dm-link {
  display: block;
  margin-top: 7px;
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.dm-p2 { display: block; margin-top: 7px; }

/* the demo's capture card gets the tight in-window shadow */
.demo-flow .booked {
  margin-right: 2px;
  box-shadow: 0 4px 9px -4px rgba(43, 39, 31, .16);
}

.dm-dots {
  align-self: flex-start;
  display: flex;
  gap: 4px;
  background: var(--bubble-otto);
  padding: 13px 14px;
  border-radius: 14px 14px 14px 4px;
  animation: ottoIn .35s ease both;
}

.dm-dots .tdot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #8F887A;
  animation: ottoDot 1.2s ease-in-out infinite;
}

.dm-dots .tdot:nth-child(2) { animation-delay: .15s; }
.dm-dots .tdot:nth-child(3) { animation-delay: .3s; }

.dc-input {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 16px 16px;
  background: rgba(255, 255, 255, .42);
  border: 1px solid rgba(43, 39, 31, .08);
  border-radius: 999px;
  padding: 9px 9px 9px 16px;
}

.dc-ph {
  flex: 1;
  font-size: 12.5px;
  color: #8F887A;
}

.dc-send {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: #322E26;
  color: #FBF6EA;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 600;
}

/* ---------- Testimonial ---------- */

.testimonial {
  --px: 1px;
  display: flex;
  justify-content: center;
  padding: 310px 48px 170px; /* big top pad gives the S3 cloud room — never clip it */
  background: #F3EDE0;
}

.tm-rise {
  max-width: 960px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 34px;
  text-align: center;
  opacity: .02; /* pre-state; scroll rise drives to 1 */
  will-change: transform, opacity;
}

.tm-keyline {
  width: 26px;
  height: 2px;
  border-radius: 1px;
  background: #C79A3E;
}

.tm-quote {
  margin: 0;
  font-family: 'Newsreader', serif;
  font-style: italic;
  font-weight: 400;
  font-size: 31px;
  line-height: 1.55;
  letter-spacing: -.2px;
  color: #57503F;
  text-wrap: pretty;
}

.tm-quote strong {
  font-weight: 600;
  color: #26221B;
}

.tm-attr {
  display: flex;
  align-items: center;
  gap: 10px;
}

.tm-name {
  font-size: 14.5px;
  font-weight: 700;
  color: #2A2620;
}

.tm-sep {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: #B4A98F;
}

.tm-co {
  font-size: 14.5px;
  color: #8F887A;
}

@keyframes bobS3 {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-6px); }
}

/* ---------- TEMPORARY: S3 entrance tuner ---------- */

.s3-tuner {
  position: fixed;
  left: 16px;
  bottom: 16px;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 6px 10px 6px 4px;
  border-radius: 18px;
  background: rgba(15, 21, 38, .6);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, .18);
}

.st-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.st-tabs {
  display: flex;
  gap: 4px;
  padding: 2px 0 3px 8px;
}

.st-tab {
  font: 500 8.5px 'Spline Sans Mono', monospace;
  letter-spacing: .14em;
  color: rgba(255, 243, 226, .6);
  background: transparent;
  border: 1px solid rgba(255, 243, 226, .28);
  border-radius: 999px;
  padding: 4px 10px;
  cursor: pointer;
}

.st-tab:hover { color: rgba(255, 243, 226, .95); }

.st-tab.active {
  color: #1E1B15;
  background: #FFF3E2;
  border-color: #FFF3E2;
}

.st-copy { margin-left: auto; border-style: dashed; }
.st-replay { margin-left: auto; border-style: dashed; }

/* Which of the two value sets is live (16-inch vs 30-inch). A status badge, not a
   control — it follows the viewport, so it's filled amber to read as state rather
   than as a selectable tab (the active tab is cream). */
.st-mode {
  font: 500 8.5px 'Spline Sans Mono', monospace;
  letter-spacing: .14em;
  align-self: center;
  white-space: nowrap;
  padding: 4px 10px;
  border: 1px solid #D8A657;
  border-radius: 999px;
  color: #2A2109;
  background: #D8A657;
  cursor: help;
}

/* effect toggles: dashed + dimmed when off, filled (like the active tab) when on */
.st-toggle { border-style: dashed; opacity: .6; }
.st-toggle:hover { opacity: .85; }
.st-toggle.on {
  opacity: 1;
  color: #1E1B15;
  background: #FFF3E2;
  border-color: #FFF3E2;
  border-style: solid;
}

.st-row {
  display: flex;
  align-items: center;
  gap: 4px;
}

/* only the active tab's rows show */
.s3-tuner[data-sec="s3"] .st-row:not([data-sec="s3"]),
.s3-tuner[data-sec="quote"] .st-row:not([data-sec="quote"]),
.s3-tuner[data-sec="dfy"] .st-row:not([data-sec="dfy"]),
.s3-tuner[data-sec="pricing"] .st-row:not([data-sec="pricing"]),
.s3-tuner[data-sec="faq"] .st-row:not([data-sec="faq"]),
.s3-tuner[data-sec="closing"] .st-row:not([data-sec="closing"]) { display: none; }

.st-grip {
  font-size: 12px;
  line-height: 1;
  color: rgba(255, 243, 226, .5);
  padding: 6px 2px 6px 8px;
  cursor: grab;
  user-select: none;
  touch-action: none;
}

.st-label {
  font: 500 9px 'Spline Sans Mono', monospace;
  letter-spacing: .12em;
  color: rgba(255, 243, 226, .6);
  padding: 0 2px 0 8px;
  white-space: nowrap;
}

.s3-tuner input[type="range"] {
  width: 70px;
  accent-color: #FFF3E2;
  cursor: pointer;
}

.st-readout {
  font: 500 10px 'Spline Sans Mono', monospace;
  color: rgba(255, 243, 226, .92);
  min-width: 32px;
  text-align: right;
}

.st-min {
  font: 500 12px 'Spline Sans Mono', monospace;
  color: rgba(255, 243, 226, .92);
  background: transparent;
  border: 0;
  border-radius: 999px;
  padding: 4px 8px;
  cursor: pointer;
  min-width: 24px;
}

.st-min:hover { background: rgba(255, 255, 255, .12); }

.st-caption {
  font: 500 9px 'Spline Sans Mono', monospace;
  letter-spacing: .08em;
  color: rgba(255, 243, 226, .55);
  padding: 2px 2px 1px 8px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 880px;
}

.s3-tuner.min .st-body { display: none; }

/* ---------- Motion preferences ---------- */

@media (prefers-reduced-motion: reduce) {
  .chat-card { animation: none; }
  .dot { animation: none; }
  .bg-video { display: none; } /* day still frame shows instead */
  .qb, .tdot, .cv-cursor { animation: none !important; }
  .convo-flow > * { animation: none !important; }
  /* hero entrance off — elements simply appear in place */
  .toolbar-pill, .hero-copy h1, .cta-row, .hero-desc, .hero-bg { animation: none !important; }
  html[data-anim="2"] .chat-card, html[data-anim="3"] .chat-card { animation: none !important; }
  html[data-anim="3"] .hero::after { display: none; }
  /* ambient loops below the fold */
  .op-illo img, .dc-dot, .dm-dots, .demo-flow > * { animation: none !important; }
  .cs-wisps { display: none; } /* the closing wisps are purely decorative */
  .scroll-hint::after { animation: none !important; } /* main.js never shows it, either */
}
