/* ============================================================
   Hero v6 &#8212; round-five decisions applied
     - conversation reopened on "Hello Julia, what do you do?" -> pitch -> work
     - Calendar receipt rebuilt as a real event card, using the project's own
       google-calendar.png from scroll-lander/assets/icons/
     - CTA moved to the DS ink primary so it stops reading as a bubble

   hs-fonts.css + hs-tokens.css inlined verbatim above. Semantic roles
   only, with two declared exceptions:
     --wash-*  the shipped hero wash, lifted from the old
               scroll-lander-v2.css before it was removed
     --ios-*   quoted iOS system colours -- see the bubble note
   ============================================================ */

.doc {
  --wash-dot: rgba(10, 10, 10, 0.045);
  --wash-base: #FCFBFA;
  --wash-foot: #F1EFE9;

  /* Height of the fixed toolbar. Everything that has to line up with the bar
     -- the page's top reserve and the hero's first-screen height -- reads this
     one value, so the two can never drift apart again. The narrow layout
     overrides it where the bar gets shorter. */
  --bar-h: 61px;

  /* Quoted, not adopted. One element only; must not spread. */
  --ios-blue: #007AFF;
  --ios-blue-hi: #2AA3FF;
  --g-blue: #4285F4;

  background: var(--hs-bg-canvas);
  color: var(--hs-fg-primary);
  font-family: var(--hs-font-body);
  font-size: var(--hs-text-base);
  line-height: var(--hs-leading-body);
  -webkit-font-smoothing: antialiased;
}

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

/* ---------- masthead ---------- */




/* ============================================================
   THE HERO
   ============================================================ */

/* Review chrome, not part of the proposal. */

/* Fill the first screen and let everything else be a scroll away. `min()`
   rather than min-height + max-height: when min-height exceeds max-height the
   minimum wins, so a tall monitor would have got a 1200px hero anyway. */
/* The first screen is all wash, at every size. `min()` rather than min-height
   + max-height: when min-height exceeds max-height the minimum wins, so a tall
   monitor would have taken the cap as a floor instead of a ceiling.
   The cap sits far above any ordinary display -- it is there so a wall panel or
   an 8K monitor does not get a 4,000px hero, not to trim a laptop. */
.hero { min-height: min(calc(100dvh - var(--bar-h)), 1500px); }
@media (min-width: 941px) {
  .hero {
    display: flex;
    align-items: center;
    min-height: min(calc(100dvh - var(--bar-h)), 1500px);
  }
  #v2-hero.lander-hero { padding-top: 0; }
}
@keyframes nudge {
  0%, 100% { transform: translateY(0); opacity: .55; }
  50% { transform: translateY(3px); opacity: 1; }
}


/* ---------- copy side ---------- */
.lockup .av {
  width: 92px; height: 92px;
  border-radius: var(--hs-radius-circle);
  flex: none;
  background: var(--hs-neutral-200) center/cover no-repeat;
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.92), var(--hs-shadow-e3);
}
/* "Kevin thinks you should meet Julia." -- server-rendered above the headline
   for referral visits only (see index.html). Sits on the same left edge as the
   headline and the button, and reads as a note from a person, not a banner. */
.hero-ref {
  /* inline-block, not flex: at phone width the line wraps, and a flex row put
     the name on a line of its own with the sentence beside it. */
  display: inline-block;
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-sm);
  font-weight: var(--hs-weight-medium);
  line-height: var(--hs-leading-snug);
  color: var(--hs-forest-700);
  background: rgba(255, 255, 255, 0.72);
  border: 1px solid var(--hs-forest-100);
  border-radius: var(--hs-radius-pill);
  padding: var(--hs-space-1) var(--hs-space-3);
  margin: 0 0 var(--hs-space-3);
}
.hero-ref__name { font-weight: var(--hs-weight-bold); color: var(--hs-forest-900); }
/* Pointer-only. A tap leaves :hover applied until you tap something else, so
   on a phone "Say hello" -- which is a .surf-try wearing .cta-hello, and so
   loses its own white fill to this rule -- stayed dark after being pressed. */
@media (hover: hover) and (pointer: fine) {
  .cta-hello:hover {
    background: var(--hs-forest-800);
    box-shadow: 0 14px 30px -12px rgba(14, 27, 17, 0.45);
    transform: translateY(-1px);
  }
}
.cta-hello:active { transform: translateY(0); }
/* The channel-card button keeps its own outline treatment even while hovered:
   it is not the page's primary action, it just borrows the shape. */
.surf-try.cta-hello:hover { background: var(--hs-bg-raised); color: var(--hs-fg-primary); }

/* ---------- the thread ---------- */

.thread {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  gap: var(--hs-space-2);
  /* Reserved, not fitted: the box must not reflow as turns arrive. Re-measured
     at 550px with the timestamp divider removed. Any edit to the bubble text
     needs this number re-checked -- it has gone stale three times now. */
  height: 596px;

  /* Bubble shadows spread sideways past the bubble edge. With the box clipping
     flush to that edge they were sliced off dead straight on the left and
     right, which is what read as "broken". Pad the box out and pull it back by
     the same amount: the content box is unchanged, the shadows get room. */
  /* Measured, not guessed. Outward shadow reach is blur/2 + spread, offset by
     the shadow's own y: the Calendar card carries --hs-glass-lift-strong
     (0 12px 34px), so it reaches 17px sideways and 29px down. At 18px/10px it
     was landing within a pixel of the clip on both axes, and a shadow that
     ends exactly at a clip edge is a drawn line. These leave real slack. */
  padding-inline: 30px;
  margin-inline: -30px;
  padding-block: 10px 40px;
  overflow: hidden;

  /* No horizontal mask. Fading the outer band did kill the line, but it also
     softened the bubble edges sitting against it, which looked worse than the
     problem. The seam had two causes and only one of them was the shadow:
     the other was the backdrop-filter's sampling boundary, and that one is
     removed at source below (see .bub--in). Shadows fit the padding with room
     to spare -- 13px of slack at the tightest measured turn. */
  /* NOT scroll-behavior: smooth. On an `overflow: hidden` container Chrome
     silently drops the programmatic scroll entirely -- scrollTop stayed 0
     while content overflowed. Instant is also the right call: the turns
     already animate in, and smooth-scrolling under them is double motion. */
}

/* Only once something has actually scrolled off, so the first turn is not
   sitting under a gradient for no reason. */
.thread.is-scrolled {
  /* Vertical only: turns riding up out of the box fade rather than being
     guillotined. Nothing horizontal -- see the note in .thread. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 28px);
  mask-image: linear-gradient(180deg, transparent 0, #000 28px);
}
/* (the .seed rule that belongs here lives after .cal -- see the note there) */

.bub--out {
  align-self: flex-end;
  background: linear-gradient(180deg, var(--ios-blue-hi), var(--ios-blue));
  color: #FFFFFF;
  border-radius: var(--hs-radius-2xl) var(--hs-radius-2xl) var(--hs-space-2) var(--hs-radius-2xl);
  box-shadow: 0 10px 24px -10px rgba(0, 60, 130, 0.45);
}
/* Incoming is grey in iOS on both transports -- it never turns blue or green. */
/* No backdrop-filter in the thread. Its backdrop root is the clipped scroll
   box, so the blur clamped at the clip and drew the straight edge that padding
   could not fix. At --hs-glass-card 0.88 alpha there was almost nothing
   showing through to blur anyway. */
.bub--in {
  align-self: flex-start;
  background: var(--hs-glass-card);
  color: var(--hs-forest-900);
  border: var(--hs-size-hairline) solid var(--hs-glass-card-border);
  box-shadow: var(--hs-glass-lift);
  border-radius: var(--hs-radius-2xl) var(--hs-radius-2xl) var(--hs-radius-2xl) var(--hs-space-2);
  transform-origin: bottom left;
}

.typing {
  align-self: flex-start;
  display: flex;
  gap: 5px;
  padding: var(--hs-space-4) var(--hs-space-5);
  background: var(--hs-glass-card);
  border: var(--hs-size-hairline) solid var(--hs-glass-card-border);
  box-shadow: var(--hs-glass-lift);
  border-radius: var(--hs-radius-2xl) var(--hs-radius-2xl) var(--hs-radius-2xl) var(--hs-space-2);
  animation: bubIn var(--hs-duration-slow) var(--hs-ease-spring) backwards;
  flex: none;
}
@keyframes typedot {
  0%, 60%, 100% { opacity: .3; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-3px); }
}
/* No scale here. Scaling a box that carries a shadow scales the shadow with
   it, so every bubble arrived with a briefly oversized, offset blur that read
   as a smear -- worse where several land in quick succession. Sliding and
   fading gives the same arrival with nothing to distort. */
@keyframes bubIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- the Calendar event card ---------- */

.cal {
  align-self: stretch;
  /* Sized to its content rather than the column. At 392px the event details
     left a wide empty gutter on the right; this sits closer to the width the
     text actually needs. Released again in the single-column layout. */
  max-width: 318px;
  background: rgba(255, 255, 255, 0.94);
  border: var(--hs-size-hairline) solid var(--hs-glass-card-border);
  /* no backdrop-filter -- same clipped backdrop root as the bubbles */
  box-shadow: var(--hs-glass-lift-strong);
  border-radius: var(--hs-radius-lg);
  overflow: hidden;
  animation: bubIn var(--hs-duration-expand) var(--hs-ease-spring) backwards;
  flex: none;
}

/* ============================================================
   THE REST OF THE LANDER
   Every section heading is the visitor's next question, in their bubble; the
   body is Julia's answer. The hero already opened with "Hello Julia, what do
   you do?", so the page is one continuous conversation rather than a set of
   feature panels that happen to sit under a chat graphic.
   ============================================================ */
/* In the top bar it is chrome and sits at control-md. Floating on mobile it is
   the page's one action, and has to match the button it hands over to in the
   closing section or the handover reads as two different buttons. */
.bar--top .cta-hello--bar { height: var(--hs-size-control-md); font-size: var(--hs-text-sm); padding-inline: var(--hs-space-5); }

/* The fixed bar covers content, so the page owes it that much room back.
   --bar-h is the bar's measured height; the reserve is one pixel less, so the
   hero wash runs a hair *under* the bar instead of stopping short of it.
   Clearance is what caused the hairline of page background below the bar on
   first paint -- it vanished on scroll, when the wash slid up to fill it. A
   pixel of overlap also absorbs subpixel rounding at fractional zoom. */
.doc { padding-top: calc(var(--bar-h) - 1px); }

/* ---------- live usage estimate ---------- */

.est {
  border: var(--hs-size-hairline) solid var(--hs-border-accent);
  background: var(--hs-accent-subtle);
  border-radius: var(--hs-radius-lg);
  padding: var(--hs-space-5) var(--hs-space-6);
  margin-top: var(--hs-space-5);
}

/* ---------- top-ups + reassurance ---------- */

/* ---------- try-demo buttons ---------- */

/* The footer row is `auto` in the card grid, so it was exactly as tall as the
   button plus uneven padding -- the button read as sitting low. Give the row a
   height and centre in it. */
.surf-cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--hs-space-2);
  min-height: 72px;
  padding: var(--hs-space-4) var(--hs-space-5);
}
.surf-try {
  display: inline-flex;
  align-items: center;
  gap: var(--hs-space-2);
  /* The card is a fixed 468px, so a body with a tall min-content steals the
     footer row and squashes the button to half a pill. Its height is not
     negotiable. */
  flex: none;
  height: var(--hs-size-control-md);
  padding-inline: var(--hs-space-4);
  border: var(--hs-size-hairline) solid var(--hs-border-strong);
  border-radius: var(--hs-radius-pill);
  background: var(--hs-bg-raised);
  color: var(--hs-fg-primary);
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-sm);
  font-weight: var(--hs-weight-bold);
  letter-spacing: var(--hs-tracking-tight);
  cursor: pointer;
  transition:
    border-color var(--hs-duration-base) var(--hs-ease-standard),
    box-shadow var(--hs-duration-base) var(--hs-ease-standard),
    transform var(--hs-duration-fast) var(--hs-ease-standard);
}
@media (hover: hover) and (pointer: fine) {
  .surf-try:hover {
    border-color: var(--hs-accent-solid);
    box-shadow: var(--hs-elevation-raised);
    transform: translateY(-1px);
  }
}



/* ---------- cost explainer, inside the estimate ----------
   It belongs with the numbers it explains rather than in a separate card
   below them, which read as an unrelated afterthought. */
.est-more { margin-top: var(--hs-space-5); border-top: var(--hs-size-hairline) solid var(--hs-accent-ring); padding-top: var(--hs-space-4); }
.est-sum {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--hs-space-3); cursor: pointer; list-style: none;
  color: var(--hs-accent-ink);
}
/* In the closing block her reply and the rotating examples under it are one
   thread, so they share every metric -- size, padding, line box, cap. It used
   to run at display size to pair with the .qa-ask pill above; that made the
   examples read as a separate, smaller thing rather than the next line of the
   same conversation. Keep this in step with .qa-eg. */
.qa--close .qa-bubble {
  /* Both are inline-block, and the pair fit on one line -- the answer ended up
     sitting beside the question instead of under it. */
  display: block;
  width: fit-content;
  font-size: var(--hs-text-lg);
  line-height: var(--hs-leading-snug);
  letter-spacing: var(--hs-tracking-tight);
  max-width: 34ch;
  padding: var(--hs-space-3) var(--hs-space-5);
}

/* one flat row of legal/contact links; the nav already covers the rest */
/* Four links, two columns, two rows: the two things you can do, then the two
   things you can read. Right-aligned, because they sit against the right edge
   of the footer -- ragged-right text on the right-hand side has nothing to
   line up with. */
.foot-cols {
  display: grid !important;
  grid-template-columns: repeat(2, minmax(0, max-content)) !important;
  grid-auto-flow: column;
  grid-template-rows: repeat(2, auto);
  gap: var(--hs-space-3) var(--hs-space-10) !important;
  align-content: start;
  justify-items: end;
  text-align: right;
}
@media (max-width: 720px) {
  /* one column below the brand, so they align with it instead */
  .foot-cols { justify-items: start; text-align: left; }
}
/* the footer's one button, dressed as its links */
.foot-link {
  padding: 0; border: 0; background: none; cursor: pointer;
  font-family: var(--hs-font-ui); font-size: var(--hs-text-sm);
  color: var(--hs-fg-secondary);
}
.foot-link:hover { color: var(--hs-fg-primary); text-decoration: underline; text-underline-offset: 3px; }
.foot-link:focus-visible { outline: var(--hs-size-focus-ring) solid var(--hs-border-focus); outline-offset: 3px; }

/* ---------- mini call bar ----------
   The component itself is in mini-call-bar.css, shared with the preview page at
   /r/:token. Only where it is pinned is the lander's business.

   Sits under the toolbar rather than over it: the header is how you get around
   and covering it to show a call would trade one problem for another. */

/* desktop: top right, clear of the 68px toolbar */
@media (min-width: 941px) {
  .minicall { top: calc(68px + var(--hs-space-4)); right: var(--hs-space-6); width: min(380px, calc(100vw - 48px)); }
}
/* mobile: a bar across the top, clear of the 48px toolbar */
@media (max-width: 940px) {
  .minicall { top: calc(48px + var(--hs-space-3)); left: var(--hs-space-4); right: var(--hs-space-4); }
}

/* ---------- contact form ----------
   A dialog, not a page: both ways in -- the enterprise line in pricing and the
   footer -- are asides from whatever the visitor was already reading. */
.cform {
  display: none; position: fixed; inset: 0; z-index: 300;
  align-items: center; justify-content: center; padding: var(--hs-space-6);
  background: rgba(14, 27, 17, .45); backdrop-filter: blur(4px);
}
.cform.is-open { display: flex; }
.cform-panel {
  position: relative; width: min(460px, 100%);
  max-height: calc(100dvh - var(--hs-space-12)); overflow-y: auto;
  padding: var(--hs-space-8) var(--hs-space-7) var(--hs-space-7);
  background: var(--hs-bg-raised); border-radius: var(--hs-radius-xl);
  box-shadow: 0 32px 80px -24px rgba(14, 27, 17, .45);
}
.cform-t {
  margin: 0; font-family: var(--hs-font-display); font-size: var(--hs-text-2xl);
  font-weight: var(--hs-weight-bold); letter-spacing: var(--hs-tracking-tighter);
  color: var(--hs-fg-primary);
}
.cform-lede {
  margin: var(--hs-space-3) 0 var(--hs-space-6);
  font-family: var(--hs-font-ui); font-size: var(--hs-text-base);
  color: var(--hs-fg-secondary); line-height: var(--hs-leading-normal);
}
.cform-field { display: block; margin-bottom: var(--hs-space-4); }
.cform-field > span {
  display: block; margin-bottom: 6px;
  font-family: var(--hs-font-ui); font-size: var(--hs-text-sm);
  font-weight: var(--hs-weight-semibold); color: var(--hs-fg-primary);
}
.cform-field input, .cform-field textarea {
  width: 100%; padding: 10px 12px;
  border: var(--hs-size-hairline) solid var(--hs-border-default);
  border-radius: var(--hs-radius-md); background: var(--hs-bg-base);
  font-family: var(--hs-font-ui); font-size: var(--hs-text-base);
  color: var(--hs-fg-primary);
}
/* `resize: vertical` draws a grabber in the bottom-right corner that sits
   proud of the rounded field and reads as a rendering artefact. The field is
   already tall enough for what people write here. */
.cform-field textarea { min-height: 120px; resize: none; }
.cform-field input:focus-visible, .cform-field textarea:focus-visible {
  outline: none; border-color: var(--hs-border-field-focus, #606963);
  box-shadow: none;
}
/* Deliberately not .cta-hello. GTM binds cta_click to that class, so sharing
   it made every contact-form submit count as a lander CTA click. Same look,
   its own name. */
.cform-send {
  display: inline-flex; align-items: center; justify-content: center;
  width: 100%; margin-top: var(--hs-space-2);
  height: var(--hs-size-control-lg); padding-inline: var(--hs-space-6);
  border: 0; border-radius: var(--hs-radius-pill); cursor: pointer;
  background: var(--hs-forest-900); color: var(--hs-neutral-25);
  font-family: var(--hs-font-ui); font-size: var(--hs-text-base);
  font-weight: var(--hs-weight-bold); letter-spacing: var(--hs-tracking-tight);
  box-shadow: 0 10px 24px -10px rgba(14, 27, 17, 0.55);
  transition: background-color var(--hs-duration-base) var(--hs-ease-standard);
}
.cform-send:hover { background: var(--hs-forest-800); }
.cform-send:disabled { opacity: .6; cursor: wait; }
.cform-send:focus-visible { outline: var(--hs-size-focus-ring) solid var(--hs-forest-700); outline-offset: 3px; }
.cform-note {
  margin: var(--hs-space-4) 0 0; min-height: 1.2em;
  font-family: var(--hs-font-ui); font-size: var(--hs-text-sm);
  color: var(--hs-fg-tertiary);
}
.cform-note.is-ok { color: var(--hs-accent-ink); }
.cform-note.is-err { color: var(--hs-danger-solid); }
.cform-x {
  position: absolute; top: var(--hs-space-4); right: var(--hs-space-4);
  width: 32px; height: 32px; border: 0; cursor: pointer;
  border-radius: var(--hs-radius-circle);
  background: var(--hs-bg-sunken); color: var(--hs-fg-secondary);
  font-size: 20px; line-height: 1;
}
/* off-screen rather than display:none, so bots still fill it in */
.cform-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.cform-lede[hidden] { display: none; }

/* Full screen on a phone. A centred dialog is fine until the keyboard opens:
   the viewport halves, a panel sized against the old height gets pushed up
   under the toolbar, and what is left shows two fields and no button. Filling
   the screen at 100dvh means the panel shrinks with the viewport and scrolls
   inside itself instead. */
@media (max-width: 640px) {
  .cform { padding: 0; align-items: stretch; justify-content: stretch; }
  .cform-panel {
    width: 100%;
    max-width: none;
    min-height: 100dvh;
    max-height: 100dvh;
    border-radius: 0;
    padding: var(--hs-space-10) var(--hs-space-6) var(--hs-space-8);
    box-shadow: none;
  }
  .cform-x { top: var(--hs-space-5); right: var(--hs-space-5); }
  /* the send button stays at the foot of the sheet rather than scrolling away
     above the keyboard */
  .cform-send { position: sticky; bottom: 0; }
}

/* ---------- waitlist form ----------
   Wears the .cform shell above, including the phone sheet and its sticky
   button. Only what the contact form has no equivalent for lives here. */
.wform-chip {
  display: inline-flex; align-items: center; gap: var(--hs-space-2);
  margin: 0 0 var(--hs-space-4);
  padding: 8px var(--hs-space-3);
  border-radius: var(--hs-radius-pill);
  background: var(--hs-bg-sunken);
  font-family: var(--hs-font-ui); font-size: var(--hs-text-sm);
  font-weight: var(--hs-weight-semibold); color: var(--hs-fg-secondary);
}
.wform-dot {
  width: 7px; height: 7px; flex: none;
  border-radius: var(--hs-radius-circle);
  background: var(--hs-warning-solid);
}
/* A hint under the field, not a second label above it. Inline-level inside the
   label so it does not break the .cform-field bottom margin. */
.wform-hint {
  display: block; margin-top: 6px;
  font-family: var(--hs-font-ui); font-size: var(--hs-text-sm);
  font-weight: var(--hs-weight-regular); color: var(--hs-fg-tertiary);
}
.wform-opt {
  font-style: normal; font-size: 12px;
  font-weight: var(--hs-weight-medium); color: var(--hs-fg-tertiary);
}
/* The label is a flex row so the "optional" tag sits on the baseline beside
   the name rather than wrapping under it. */
.wform .cform-field > span { display: flex; align-items: baseline; gap: 7px; }
.wform-fine {
  margin: var(--hs-space-4) 0 0;
  font-family: var(--hs-font-ui); font-size: 13px;
  color: var(--hs-fg-tertiary); text-align: center;
}
/* The note already reserves a line; with a note AND fine print the reserved
   empty line reads as a gap, so it only takes space once it says something. */
.wform .cform-note:empty { display: none; }
.wform-ask[hidden], .wform-ok[hidden] { display: none; }
.wform-tick {
  display: flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; margin: 0 0 var(--hs-space-5);
  border-radius: var(--hs-radius-circle);
  background: var(--hs-accent-solid); color: var(--hs-fg-on-accent);
}
.wform-close-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 100%; margin-top: var(--hs-space-2);
  height: var(--hs-size-control-lg); padding-inline: var(--hs-space-6);
  border: var(--hs-size-hairline) solid var(--hs-border-default);
  border-radius: var(--hs-radius-pill); cursor: pointer;
  background: transparent; color: var(--hs-fg-secondary);
  font-family: var(--hs-font-ui); font-size: var(--hs-text-base);
  font-weight: var(--hs-weight-bold);
}
.wform-close-btn:focus-visible { outline: var(--hs-size-focus-ring) solid var(--hs-forest-700); outline-offset: 3px; }

/* ---------- live demos in the channel cards ----------
   Try demo turns the card into the thing rather than opening a modal: it is
   already the right size, and staying put keeps the channel you picked
   visible while you use it. */
.surf-live { display: none; flex-direction: column; flex: 1; min-width: 0; min-height: 0; }
/* The card is three rows until a demo takes it over, then it is one. */
.surf.is-live { grid-template-rows: 1fr; }
/* The widget introduces itself -- "They call" over a ringing phone is a
   caption on something the visitor is already looking at. */
.surf.is-live .surf-body,
.surf.is-live .surf-head,
.surf.is-live .surf-cta { display: none; }

.live-end {
  flex: none; border: 0; cursor: pointer;
  width: 28px; height: 28px; border-radius: var(--hs-radius-circle);
  background: var(--hs-danger-solid); color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
}

/* ---------- the call screen ----------
   The card the live lander mounts for a voice call, rebuilt to its own spec:
   lander.css `.hs-callcard` and voice-call-action.css for the buttons. Every
   rule here is scoped under .surf-live--call so that when this page runs in
   the app -- where those stylesheets are already loaded -- the two cannot
   fight over the same class names.

   On the real page the call is voice-call-embed.js driving xai-voice-session.js
   over /api/lander/voice/*, mounted into #hs-lander-voice-mount below. In a
   published artifact the CSP blocks both the scripts and the API, so the same
   UI runs on a scripted transcript instead. */
.surf-live--call { padding: 0; }
/* One height for the row, live or not.
   Two things depend on this. A flex child only scrolls instead of growing when
   something above it has a definite height -- with an auto row, `flex: 1` on
   the transcript meant nothing and the card grew a line at a time. And locking
   it only while live made the card jump the moment you pressed Try a call. So
   the row is always the shipped card's height (468px, lander.css): the static
   cards get the extra room, and starting a demo changes nothing but content.
   The swiped deck is a flex row, where an auto row means nothing, so the phone
   block near the foot of this file gives the card the same height directly. */
.surf-track { grid-auto-rows: 468px; }
/* The real embed renders its own card in here and this file gets out of the
   way; the scripted card is the sibling it hides. */
.surf-live--call .call-mount { display: none; flex: 1; min-height: 0; }
.surf-live--call .call-mount:not([hidden]) { display: flex; flex-direction: column; }
.surf-live--call .call-mount:not([hidden]) > * { flex: 1; min-height: 0; }
.surf-live--call .call-mount:not([hidden]) ~ .hs-callcard { display: none; }
.surf-live--call .hs-callcard[hidden] { display: none; }
.surf-live--call .hs-callcard {
  display: flex; flex-direction: column;
  flex: 1; min-height: 0;
  padding: var(--hs-space-6) var(--hs-space-6) var(--hs-space-6);
  background: #0E1B11;
  border: 1px solid #1C2C24;
  color: #F9FAF2;
}
.surf-live--call .call-top {
  position: relative; flex: none;
  display: flex; align-items: center; justify-content: space-between; gap: var(--hs-space-3);
  font-family: var(--hs-font-ui); font-size: 13px;
}
.surf-live--call .call-tag { color: #9FB3A9; font-weight: var(--hs-weight-semibold); }
.surf-live--call .call-timer { color: #9FB3A9; font-variant-numeric: tabular-nums; font-family: var(--hs-font-mono); }
.surf-live--call .call-audio-settings-btn[hidden] { display: none; }
.surf-live--call .call-audio-settings-btn {
  flex: none; width: 30px; height: 30px; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid rgba(249, 250, 242, 0.16); border-radius: var(--hs-radius-circle);
  background: rgba(249, 250, 242, 0.08); color: #CFE0D8;
}
.surf-live--call .call-audio-settings-btn:hover { background: rgba(249, 250, 242, 0.16); }
.surf-live--call .call-audio-settings-btn:focus-visible { outline: var(--hs-size-focus-ring) solid var(--hs-border-focus); outline-offset: 2px; }
.surf-live--call .call-audio-settings-panel[hidden] { display: none; }
.surf-live--call .call-audio-settings-panel {
  position: absolute; top: calc(100% + 8px); left: 0; z-index: 20;
  width: min(280px, 100%); padding: 12px;
  border: 1px solid rgba(10, 10, 10, 0.1); border-radius: 14px;
  background: #FFFFFF; box-shadow: 0 18px 40px -24px rgba(10, 10, 10, 0.35);
}
.surf-live--call .call-audio-settings-label {
  display: block; margin-bottom: 6px;
  font-family: var(--hs-font-ui); font-size: 11px; font-weight: var(--hs-weight-semibold);
  text-transform: uppercase; letter-spacing: var(--hs-tracking-caps); color: #6B7280;
}
.surf-live--call .call-audio-settings-select {
  width: 100%; font: inherit; font-size: 13px; font-family: var(--hs-font-ui);
  border: 1px solid #E7E5E1; border-radius: 10px; padding: 8px 10px;
  background: #FCFBFA; color: #0A0A0A;
}
/* was 18/14 around a 72px avatar -- a third of the card for a name that
   does not change. The transcript takes what this gives back. */
.surf-live--call .call-id { flex: none; display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 6px 0 8px; }
/* `overflow: hidden` is what makes this a circle for the real embed, which
   puts an <img> inside rather than using the background. Without it the photo
   rendered square inside a round border. lander.css has always had it; the
   scoped copy here did not. */
.surf-live--call .call-id .ring {
  width: 56px; height: 56px; margin-bottom: 2px;
  border-radius: var(--hs-radius-circle); background: #16261C center/cover no-repeat;
  border: 2px solid rgba(67, 229, 164, 0.4);
  overflow: hidden;
}
.surf-live--call .call-id b { font-family: var(--hs-font-display); font-size: 16px; font-weight: var(--hs-weight-bold); }
.surf-live--call .call-id span { font-family: var(--hs-font-ui); font-size: 13px; color: #9FB3A9; }
.surf.is-live .surf-live--call .call-id span { color: #43E5A4; }
/* The one rule that keeps the card still: the transcript owns the slack and
   scrolls inside it. */
.surf-live--call .call-tx {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; gap: 9px;
  margin: 6px 0 8px; padding-bottom: 14px;
  overflow-y: auto;
  scrollbar-width: none;
}
.surf-live--call .call-tx::-webkit-scrollbar { width: 0; }
.surf-live--call .call-tx .b {
  max-width: 86%; padding: 10px 13px; border-radius: 13px;
  font-family: var(--hs-font-ui); font-size: 13.5px; line-height: 1.45;
  animation: bubIn var(--hs-duration-expand) var(--hs-ease-spring) backwards;
}
.surf-live--call .call-tx .b.them {
  align-self: flex-start; background: #1C2C24; color: #CFE0D8;
  border-bottom-left-radius: 4px;
}
.surf-live--call .call-tx .b.julia {
  align-self: flex-end; background: #43E5A4; color: #0E1B11;
  border-bottom-right-radius: 4px; font-weight: var(--hs-weight-semibold);
}
.surf-live--call .call-action-wrap { flex: none; margin-top: auto; padding-top: 12px; }
.surf-live--call .call-action {
  box-sizing: border-box; width: 100%; min-height: 50px;
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  padding: 15px 22px; border: 0; border-radius: var(--hs-radius-pill); cursor: pointer;
  font-family: var(--hs-font-ui); font-size: 14px; font-weight: var(--hs-weight-bold);
  transition: filter .18s ease, transform .12s ease;
}
.surf-live--call .call-action.live { background: #3A1C1C; color: #FF9B9B; }
.surf-live--call .call-action:hover { filter: brightness(1.08); }
.surf-live--call .call-action:active { transform: scale(.98); }
.surf-live--call .call-action:focus-visible { outline: var(--hs-size-focus-ring) solid var(--hs-border-focus); outline-offset: 2px; }
.surf-live--call .call-eq { display: flex; align-items: center; justify-content: center; gap: 4px; height: 22px; flex: none; }
.surf-live--call .call-eq span {
  display: block; width: 3px; height: 18px; border-radius: 2px;
  background: #43E5A4; transform: scaleY(.34);
}
.surf.is-live .surf-live--call .call-eq span { animation: eqWave 1.1s var(--hs-ease-standard) infinite; }
.surf-live--call .call-eq span:nth-child(2) { animation-delay: .12s; }
.surf-live--call .call-eq span:nth-child(3) { animation-delay: .24s; }
.surf-live--call .call-eq span:nth-child(4) { animation-delay: .36s; }
.surf-live--call .call-eq span:nth-child(5) { animation-delay: .18s; }
.surf-live--call .call-eq span:nth-child(6) { animation-delay: .30s; }
@keyframes eqWave {
  0%, 100% { transform: scaleY(.34); }
  50%      { transform: scaleY(1); }
}

/* ---------- the site chat widget ----------
   Julia as she actually ships on a customer's website, in that customer's
   colours rather than ours. Rebuilt from the old lander's Cedar & Stone
   preview markup and styles, both removed with it. */
/* The visitor's bubbles were a --cs-accent mint declared here, on both the
   resting body and the live layer -- the resting thread sits outside
   .surf-live, so scoping the accent to the live layer alone had left the
   resting bubbles white-on-nothing. They are the hero's blue now
   (.cs-thread .surf-bub--out), which needs no variable, so both layers just
   inherit the page. Keep any future accent on BOTH selectors. */
.surf-live--chat { background: #FFFFFF; }
.cs-head {
  flex: none; display: flex; align-items: center; gap: 10px;
  padding: 11px 12px;
  background: #FAFCFB; border-bottom: 1px solid #ECEDEA;
}
.cs-av {
  flex: none; width: 30px; height: 30px; border-radius: 9px;
  background: #0E241B center/cover no-repeat;
}
.cs-id { display: flex; flex-direction: column; flex: 1; min-width: 0; }
/* Wraps rather than truncating: "Cedar & Stone Custom Hom..." in a card this
   narrow reads as a bug, and it is the customer's name. */
.cs-id b {
  font-family: var(--hs-font-ui); font-size: var(--hs-text-sm);
  font-weight: var(--hs-weight-bold); color: #0A0A0A;
  line-height: 1.2;
}
.cs-id i {
  font-style: normal; font-family: var(--hs-font-ui);
  font-size: var(--hs-text-2xs); color: #6B7280;
}
.cs-thread {
  flex: 1; min-height: 0;
  display: flex; flex-direction: column; justify-content: flex-end; gap: 10px;
  padding: 14px; background: #F4F7F5; overflow: hidden;
}
.cs-thread .surf-bub {
  max-width: 88%; padding: 10px 14px; border-radius: 16px;
  font-family: var(--hs-font-ui); font-size: var(--hs-text-sm);
  font-weight: var(--hs-weight-medium); line-height: var(--hs-leading-normal);
  animation: bubIn var(--hs-duration-expand) var(--hs-ease-spring) backwards;
}
.cs-thread .surf-bub--in {
  align-self: flex-start; background: #FFFFFF;
  border: 1px solid #E7EDEA; color: #0A0A0A;
}
/* The same blue the hero thread sends in (.bub--out), not Julia's mint. A
   visitor meets the hero first: blue-on-the-right is already established as
   "the customer wrote this" by the time they reach this card, and a mint
   bubble here read as a third party rather than the same conversation. Blue
   and white also match .surf-bub in the two cards either side of it. */
.cs-thread .surf-bub--out {
  align-self: flex-end; border: 0;
  background: linear-gradient(180deg, var(--ios-blue-hi), var(--ios-blue));
  color: #FFFFFF;
}
.cs-foot { flex: none; padding: 10px 12px; background: #FFFFFF; border-top: 1px solid #E7EDEA; }
.cs-form { display: flex; align-items: center; gap: 8px; }
.cs-input {
  flex: 1; min-width: 0;
  border: 1px solid #E7EDEA; border-radius: 12px; padding: 9px 12px;
  font-family: var(--hs-font-ui); font-size: var(--hs-text-sm);
  color: #0A0A0A; background: #FFFFFF;
}
.cs-input::placeholder { color: #9CA3AF; }
.cs-input:focus,
.cs-input:focus-visible {
  outline: none;
  outline-offset: 0;
  border-radius: 12px;
  border-color: #E7EDEA;
  box-shadow: none;
}
/* inline-flex, not the button default: the label sat high in the 36px box,
   and the input beside it is taller than its own text. Centre it explicitly. */
.cs-send {
  flex: none; height: 36px; padding: 0 14px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  line-height: 1;
  border: 1px solid #E7EDEA; border-radius: 12px; background: #FFFFFF;
  font-family: var(--hs-font-ui); font-size: var(--hs-text-sm);
  font-weight: var(--hs-weight-semibold); color: #0E1B11;
}
.cs-send:hover { border-color: #D9CFC0; }
.cs-send:focus-visible { outline: var(--hs-size-focus-ring) solid var(--hs-border-focus); outline-offset: 2px; }
.surf-live--chat .live-end { width: 26px; height: 26px; background: rgba(10, 10, 10, 0.06); color: #6B7280; }
.surf-live--chat .live-end:hover { background: var(--hs-danger-solid); color: #fff; }

/* ---------- wallet deck hover: paint only, never geometry ----------
   The tool wallet and the capability wallet are stacked decks: each shut card
   carries the next one's tuck as bottom padding, and its neighbour's negative
   margin rides up onto it. A translateY here lifts a card out of a stack that
   its siblings are still measured against, so the deck opens a seam and the
   cards below shuffle -- which is why the app's own wallet
   (hs-bridge-components.css, `.hs-cw-card:not([data-open]):hover`) washes the
   background and leaves the box alone. Same rule here.

   Shut cards only. An open card is already lifted by its own shadow, and a
   wash under the cursor while the fold is being read is noise.

   Behind `hover: hover` because a stacked deck is tapped on a phone, and a
   sticky hover on a card the finger just opened stays lit until something
   else is touched. */
@media (hover: hover) {
  .tool-wrap:not(.is-open):hover,
  .cap-wrap:not(.is-open):hover {
    background: var(--hs-glass-raised);
    border-color: var(--hs-border-strong);
    /* The wash alone is two levels of grey on a card that is already almost
       white -- true to the app's wallet, and on this page too quiet to read as
       a response. The shadow is the same --hs-elevation-overlay the price,
       proof and channel cards above take on hover, so the deck answers the
       cursor as loudly as everything else here without moving. */
    box-shadow: var(--hs-elevation-overlay);
  }
  /* The chevron is the affordance, so it answers the cursor too. Only the
     right and bottom edges have a width, so setting all four is harmless. */
  .tool-wrap:not(.is-open):hover .tool-chev,
  .cap-wrap:not(.is-open):hover .cap-chev {
    border-color: var(--hs-fg-secondary);
  }
}

@media (prefers-reduced-motion: reduce) {
  .price-card:hover, .proof:hover, .surf:hover, .surf-try:hover { transform: none; }
}

/* ---------- collapsed rate card ---------- */

/* ---------- tool wallet ----------

   The same deck the workspace uses for Connectors -- see .hs-cw-card in
   design-system/css/hs-bridge-components.css. The rules are restated here
   rather than imported: the lander loads tokens and brand only, and pulling in
   the whole bridge stylesheet for one section would drop 5,000 lines of
   workspace component CSS onto the marketing page.

   What carries over from that card, and why each part is load-bearing:

     - Cards sit in normal flow and overlap by a fixed negative margin, so the
       peek is exact and opening a card simply makes it taller. Never a
       percentage margin -- those resolve against width, not height.
     - The tuck is dead space for the NEXT card to sit on, so a card only
       carries it while a card actually follows and it is shut. Without that,
       the last card in a set wears the tuck as a stray 26px of bottom padding.
     - The open animation is a 0fr -> 1fr grid track on .tool-card, not a
       height. You cannot transition to `auto`, and animating max-height either
       clips the fold or coasts through empty space after it. The track lives
       on its own element with nothing beside it -- a sibling track in the same
       grid gets robbed mid-interpolation and the head jumps. */

.tool-wrap {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: var(--hs-radius-xl);
  background: var(--hs-glass-card);
  backdrop-filter: var(--hs-glass-blur);
  -webkit-backdrop-filter: var(--hs-glass-blur);
  border: var(--hs-size-hairline) solid var(--hs-glass-card-border);
  box-shadow: var(--hs-glass-lift-strong);
  transition:
    margin-top var(--hs-duration-wallet) var(--hs-ease-spring),
    padding-bottom var(--hs-duration-wallet) var(--hs-ease-spring),
    box-shadow var(--hs-duration-base) var(--hs-ease-spring),
    background-color var(--hs-duration-fast) var(--hs-ease-standard),
    border-color var(--hs-duration-fast) var(--hs-ease-standard);
}
/* Later cards paint over earlier ones, so the overlap reads as a deck stacked
   front to back. */
.tool-wrap + .tool-wrap {
  margin-top: calc(var(--tool-tuck) * -1);
  z-index: 1;
}
.tool-wrap:not(.is-open):has(+ .tool-wrap) { padding-bottom: var(--tool-tuck); }
.tool-wrap.is-open {
  z-index: 2;
  box-shadow: var(--hs-elevation-popover);
}
/* An open card is not a shelf -- the card under it steps clear rather than
   tucking into a body it can no longer peek past. */
.tool-wrap.is-open + .tool-wrap { margin-top: var(--hs-space-2); }

/* The head IS the visible strip of a shut card, so its own padding is the
   breathing room: equal above and below, nothing centred against a phantom
   band. */
button.tool {
  display: flex;
  align-items: center;
  gap: var(--hs-space-4);
  width: 100%;
  padding: var(--hs-space-5);
  background: none;
  border: 0;
  box-shadow: none;
  cursor: pointer;
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-base);
  font-weight: var(--hs-weight-semibold);
  letter-spacing: var(--hs-tracking-tight);
  color: var(--hs-fg-primary);
  text-align: left;
  /* the grey square Android paints over a card on tap */
  -webkit-tap-highlight-color: transparent;
}
button.tool:focus-visible {
  outline: var(--hs-size-focus-ring) solid var(--hs-border-focus);
  outline-offset: -3px;
  border-radius: var(--hs-radius-xl);
}
.tool-name { min-width: 0; }
.tool-chev {
  flex: none;
  margin-left: auto;
  width: 9px; height: 9px;
  border-right: 2px solid var(--hs-fg-tertiary);
  border-bottom: 2px solid var(--hs-fg-tertiary);
  transform: rotate(45deg) translate(-2px, -2px);
  transition:
    transform var(--hs-duration-base) var(--hs-ease-standard),
    border-color var(--hs-duration-fast) var(--hs-ease-standard);
}
.tool-wrap.is-open .tool-chev { transform: rotate(-135deg) translate(-2px, -2px); }

.tool-card {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--hs-duration-wallet) var(--hs-ease-spring);
}
.tool-wrap.is-open .tool-card { grid-template-rows: 1fr; }
/* min-height:0 is what lets the 0fr track actually collapse -- a grid item's
   automatic minimum size is its content, so without this the fold stays at
   full height and the card never shuts. */
.tool-fold { min-height: 0; overflow: hidden; }
/* The bullets themselves are the shared list -- see .capd-list, which this
   card's `ul` is grouped into. Only the container is this card's own: the
   fold's indent has to line up under the tool's name, not under its icon. */
.tool-card ul {
  margin: 0;
  /* Bullets start under the name, not under the icon: 30px of logo plus the
     head's gap. */
  padding: 0 var(--hs-space-5) var(--hs-space-5) calc(30px + var(--hs-space-4) + var(--hs-space-5));
}
/* A grid of named-but-not-shipped chips is a roadmap the page has to keep
   honest. One line makes the same point and cannot go stale. */
.doc p.tool-more { margin-top: var(--hs-space-6); }
.tool-more-ask {
  padding: 0; border: 0; background: none; cursor: pointer;
  font-family: inherit; font-size: inherit;
  color: var(--hs-accent-ink); font-weight: var(--hs-weight-semibold);
  text-decoration: underline; text-underline-offset: 3px;
}
.tool-more-ask:focus-visible { outline: var(--hs-size-focus-ring) solid var(--hs-border-focus); outline-offset: 2px; }

/* ---------- social proof ---------- */

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

.foot-site {
  margin-top: var(--hs-space-20);
  padding-top: var(--hs-space-10);
  border-top: var(--hs-size-hairline) solid var(--hs-border-default);
  /* Brand on the left, links on the right, the fine print underneath both.
     It was three stacked blocks all starting at the same left edge, which
     read as more of the page rather than the end of it. */
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--hs-space-10);
  align-items: start;
}
.foot-fine { grid-column: 1 / -1; }
@media (max-width: 720px) {
  .foot-site { grid-template-columns: minmax(0, 1fr); gap: var(--hs-space-8); }
}
/* Stacked, so the brand block is two lines like the links opposite it and the
   two sides of the footer balance. Side by side they were one long line
   against a two-line block.
   The baseline nudge that used to sit here went with the change -- it existed
   to line the wordmark up with a tagline that is no longer beside it. */
.foot-top {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--hs-space-2);
  margin-bottom: 0;
}
.foot-cols {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--hs-space-6);
}

/* Fine print reads as a note ON the block above it when it sits this close.
   A full step of separation makes it a note AFTER it.
   Qualified with `.doc p` because that rule -- (0,1,1) to a bare .qa-fine's
   (0,1,0) -- was zeroing this margin outright, so the note has been sitting
   flush against the card grid above it the whole time. */
.doc p.qa-fine {
  margin: var(--hs-space-8) 0 0;
  max-width: 60ch;
  font-size: var(--hs-text-sm);
  color: var(--hs-fg-tertiary);
  line-height: var(--hs-leading-normal);
}

/* ---------- pricing ---------- */

/* the door to the custom panel */
.price-more {
  display: flex; align-items: center; justify-content: center; gap: var(--hs-space-2);
  width: 100%; margin-top: var(--hs-space-4);
  padding: var(--hs-space-4); cursor: pointer;
  border: var(--hs-size-hairline) solid var(--hs-border-default);
  border-radius: var(--hs-radius-lg);
  background: var(--hs-bg-raised); color: var(--hs-fg-secondary);
  font-family: var(--hs-font-ui); font-size: var(--hs-text-base);
  font-weight: var(--hs-weight-semibold);
}
.price-more:hover { border-color: var(--hs-accent-solid); color: var(--hs-fg-primary); }
.price-more:focus-visible { outline: var(--hs-size-focus-ring) solid var(--hs-border-focus); outline-offset: 2px; }
.price-more svg { transition: transform var(--hs-duration-base) var(--hs-ease-standard); }
.price-more[aria-expanded="true"] svg { transform: rotate(180deg); }
/* .price-card sets an explicit display, so [hidden] has to be said out loud */
.price-card[hidden] { display: none; }
#priceCustom { margin-top: var(--hs-space-4); }

/* Two options, not three: $99 or size it yourself. */
/* .price-grid--two is gone with the second card; a single plan is one column
   at a readable width rather than a full-bleed slab. */
.price-grid { grid-template-columns: minmax(0, 1fr); max-width: 30rem; }

/* On a wide screen the plan and the estimate were stacked: a narrow card
   floating over a full-width panel, with the custom door wedged between them.
   They answer the same question -- what it costs and what that buys -- so they
   sit side by side, matched height, with the door under the plan it opens. */
@media (min-width: 941px) {
  .price-pair {
    display: grid;
    grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);
    grid-template-rows: auto auto;
    gap: var(--hs-space-4) var(--hs-space-6);
    align-items: start;
  }
  /* Placed, not flowed. Auto-flow put the door in column two and pushed the
     estimate onto a second row in column one, at the plan's width. */
  .price-pair .price-grid { grid-column: 1; grid-row: 1; max-width: none; }
  .price-pair .price-more { grid-column: 1; grid-row: 2; margin-top: 0; }
  .price-pair .est { grid-column: 2; grid-row: 1 / span 2; margin-top: 0; height: 100%; }
  #priceCustom { grid-column: 1 / -1; grid-row: 3; }
}

/* Shown only at the top of the slider, in place of the rate line. An icon, a
   sentence and a link as three flex children read as three columns in a card
   this narrow -- it is one thought, so it is now one control. */
.slide-chat { margin: 0; align-self: start; }
.slide-chat-btn {
  display: block;
  width: 100%;
  padding: var(--hs-space-3) var(--hs-space-4);
  cursor: pointer;
  text-align: left;
  border: var(--hs-size-hairline) solid var(--hs-accent-ring);
  border-radius: var(--hs-radius-md);
  background: var(--hs-accent-subtle);
  color: var(--hs-accent-ink);
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-sm);
  font-weight: var(--hs-weight-medium);
  line-height: var(--hs-leading-snug);
}
.slide-chat-btn b { font-weight: var(--hs-weight-bold); text-decoration: underline; text-underline-offset: 3px; }
.slide-chat-btn:hover { background: var(--hs-accent-muted); }
.slide-chat-btn:focus-visible { outline: var(--hs-size-focus-ring) solid var(--hs-border-focus); outline-offset: 2px; }

/* ---------- what a credit buys ---------- */

/* ---------- tools ---------- */

/* The tuck leaves nothing under the last card of a set, so the gap between one
   set and the next has to carry the separation on its own. */
.tool-set { margin-bottom: var(--hs-space-8); }
/* ONE number runs the stack: how deep the next card sits on this one. A shut
   card is padding - head - padding - tuck, so the strip anyone can see is the
   head, exactly, with equal space above and below it.

   Capped well short of the 1200px shell. These cards hold an icon and one
   short name; across the full column they would read as empty bars, and the
   fold's bullets would set at a line length nothing else in the section uses. */
.tool-row {
  --tool-tuck: 26px;
  display: flex;
  flex-direction: column;
  max-width: 36rem;
}

/* ---------- the three surfaces ---------- */

.surf-track {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--hs-space-4);
}
.surf-ic--mail { background: var(--hs-accent2-subtle); color: var(--hs-accent2-ink); }

/* voice — the card at rest rings, then takes the call.
   Two phases of one card, switched by data-phase on the body: `ring` is the
   phone, `talk` is the same identity shrunk into a header row with the
   transcript under it. Nothing here is focusable and the key is aria-hidden --
   the real control is the button below, and the loop is its trailer. */
.surf-body--ring {
  align-items: center;
  justify-content: center;
  gap: var(--hs-space-4);
  text-align: center;
  overflow: hidden;
}
/* Once she is talking the height belongs to what she is saying, so the
   identity stops being centred and sits at the top of the card. */
.surf-body--ring[data-phase="talk"] {
  justify-content: flex-start;
  gap: var(--hs-space-3);
}
.ring-id {
  flex: none;
  display: flex; flex-direction: column; align-items: center;
  gap: var(--hs-space-4);
}
.surf-body--ring[data-phase="talk"] .ring-id {
  flex-direction: row;
  align-self: stretch;
  gap: var(--hs-space-3);
  text-align: left;
}
.ring-av {
  position: relative;
  width: 66px; height: 66px;
  flex: none;
  border-radius: var(--hs-radius-circle);
  background: var(--hs-neutral-200) center/cover no-repeat;
  box-shadow: 0 0 0 3px var(--hs-bg-raised), var(--hs-elevation-raised);
  transition:
    width var(--hs-duration-expand) var(--hs-ease-standard),
    height var(--hs-duration-expand) var(--hs-ease-standard);
}
.surf-body--ring[data-phase="talk"] .ring-av { width: 38px; height: 38px; }
.ring-wave {
  position: absolute;
  inset: -4px;
  border-radius: var(--hs-radius-circle);
  border: 2px solid var(--hs-accent-solid);
  opacity: 0;
  animation: ringWave 2.4s var(--hs-ease-standard) infinite;
}
.ring-wave:nth-child(2) { animation-delay: 1.2s; }
@keyframes ringWave {
  0%   { transform: scale(1);    opacity: .55; }
  70%  { transform: scale(1.75); opacity: 0; }
  100% { transform: scale(1.75); opacity: 0; }
}
.ring-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.surf-body--ring[data-phase="talk"] .ring-meta { align-items: flex-start; }
.surf-body--ring[data-phase="talk"] .ring-meta b { font-size: var(--hs-text-base); }
.ring-meta i {
  font-style: normal;
  font-family: var(--hs-font-mono);
  font-size: var(--hs-text-2xs);
  letter-spacing: var(--hs-tracking-wide);
  text-transform: uppercase;
  color: var(--hs-accent-ink);
}
.ring-meta b {
  font-family: var(--hs-font-display);
  font-size: var(--hs-text-lg);
  font-weight: var(--hs-weight-bold);
  color: var(--hs-fg-primary);
}
.ring-meta em {
  font-style: normal;
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-xs);
  color: var(--hs-fg-tertiary);
}
.ring-keys { display: flex; align-items: center; gap: var(--hs-space-5); }
.ring-key {
  width: 38px; height: 38px;
  border-radius: var(--hs-radius-circle);
  display: grid; place-items: center;
}
/* One key, not two. A decline button on a demo is an ending nobody is being
   offered, and it invites the reading that Julia is the one who might not
   pick up. */
.ring-key--yes {
  background: var(--hs-accent-solid); color: var(--hs-accent-on-solid);
  animation: ringNudge 2.4s var(--hs-ease-standard) infinite;
}
@keyframes ringNudge {
  0%, 62%, 100% { transform: translateY(0) rotate(0); }
  68% { transform: translateY(-3px) rotate(-8deg); }
  74% { transform: translateY(-3px) rotate(8deg); }
  80% { transform: translateY(0) rotate(0); }
}

/* what belongs to which phase */
.surf-body--ring[data-phase="ring"] .ring-eq,
.surf-body--ring[data-phase="ring"] .ring-tx { display: none; }
.surf-body--ring[data-phase="talk"] .ring-keys { display: none; }
/* The waves are the ring itself, so they stop when she answers. */
.surf-body--ring[data-phase="talk"] .ring-wave { animation: none; opacity: 0; }

/* the live-voice bars, beside her name rather than under it, so the header
   row reads as one line: who is on, and that she is speaking */
.ring-eq {
  display: flex; align-items: center; gap: 3px;
  margin-left: auto; height: 20px; flex: none;
}
.ring-eq span {
  display: block; width: 3px; height: 16px; border-radius: 2px;
  background: var(--hs-accent-solid); transform: scaleY(.34);
  animation: eqWave 1.1s var(--hs-ease-standard) infinite;
}
.ring-eq span:nth-child(2) { animation-delay: .12s; }
.ring-eq span:nth-child(3) { animation-delay: .24s; }
.ring-eq span:nth-child(4) { animation-delay: .36s; }
.ring-eq span:nth-child(5) { animation-delay: .18s; }

/* The transcript owns whatever height is left and clips from the top, so the
   card never grows and the newest line is always the one at the bottom. The
   mask fades the cut instead of slicing a bubble in half, which is what a
   three-column card does to the first line once the call is a few turns in. */
.ring-tx {
  flex: 1 1 auto; min-height: 0; align-self: stretch;
  display: flex; flex-direction: column; justify-content: flex-end;
  gap: var(--hs-space-2);
  overflow: hidden;
  text-align: left;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 28px);
  mask-image: linear-gradient(180deg, transparent 0, #000 28px);
}
.ring-b {
  margin: 0; max-width: 88%;
  padding: 8px var(--hs-space-4);
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-sm);
  font-weight: var(--hs-weight-medium);
  line-height: var(--hs-leading-snug);
  letter-spacing: var(--hs-tracking-tight);
  animation: bubIn var(--hs-duration-expand) var(--hs-ease-spring) backwards;
}
.ring-b--them {
  align-self: flex-start;
  background: var(--hs-bg-raised);
  color: var(--hs-fg-primary);
  border: var(--hs-size-hairline) solid var(--hs-border-default);
  border-radius: 16px 16px 16px 4px;
}
.ring-b--julia {
  align-self: flex-end;
  background: var(--hs-accent-solid);
  color: var(--hs-accent-on-solid);
  border-radius: 16px 16px 4px 16px;
}
/* A ringing phone that will not stop is an irritation, not an invitation.
   Reduced motion gets the conversation, held still: callPreview() paints the
   talk phase once and never loops. */
@media (prefers-reduced-motion: reduce) {
  .ring-wave { animation: none; opacity: .4; transform: scale(1.35); }
  .ring-key--yes { animation: none; }
  .ring-eq span { animation: none; transform: scaleY(.6); }
  .ring-b { animation: none; }
  .ring-av { transition: none; }
  /* The chat and mail cards get their finished picture instead of their loop --
     see still() on each preview. Nothing slides, so nothing here transitions. */
  .cs-typing i { animation: none; opacity: .55; }
  .cs-roll, .mail-feed, .mail-item--next, .mail-reply[data-mail-late] { transition: none; }
}

/* chat at rest ---------------------------------------------------------
   Not a mock-up of the widget: the same .cs-* chrome the live layer uses,
   with spans where the live one has a form. Clicking Try the chat swaps who
   is answering, and nothing else about the picture changes. */
.surf-body--cs {
  padding: 0;
  gap: 0;
  background: #FFFFFF;
  overflow: hidden;
}
/* No composer at rest -- it arrives with the live layer, so all three cards
   make their offer in the same place: the button under them. */
/* The resting thread reads from the top, unlike the live one below it: the
   card has to show Julia answering, so the question has to be on screen before
   the answer is, and a thread pinned to the bottom puts the opening line where
   the clip is. */
.cs-thread--rest { justify-content: flex-start; padding-bottom: 10px; }
/* Everything the thread holds rides on this one node, so a reply that outgrows
   the card slides the whole exchange up by exactly the overflow -- a thread
   scrolling, rather than four bubbles each jumping a slot. chatPreview() sets
   the transform; the height it measures is the thread's, so nothing here may
   affect layout. */
.cs-roll {
  display: flex; flex-direction: column; gap: 10px;
  transition: transform var(--hs-duration-slow) var(--hs-ease-standard);
}
/* Julia mid-answer, in the same shell her bubbles use: the reply lands in the
   space the dots were already holding instead of pushing the thread again. */
.cs-typing { display: inline-flex; align-items: center; gap: 5px; }
.cs-typing i {
  display: block; width: 6px; height: 6px; border-radius: 50%;
  background: #9CA3AF;
  animation: csDot 1.25s var(--hs-ease-standard) infinite;
}
.cs-typing i:nth-child(2) { animation-delay: .16s; }
.cs-typing i:nth-child(3) { animation-delay: .32s; }
@keyframes csDot {
  0%, 100% { opacity: .3; transform: translateY(0); }
  50%      { opacity: 1; transform: translateY(-3px); }
}

/* email ----------------------------------------------------------------
   The resting card is a lead sitting in an inbox with Julia's answer already
   under it, which is the claim; the live layer is the visitor putting their
   own question through the same path. */
/* The body is the inbox. It used to hold a bordered white .mailcard inside a
   card that was already a bordered white surface -- a window inside a window,
   which cost a frame of padding and two borders and left the mail itself
   smaller than anything else the deck shows. Now the mail fills the frame.
   min-height:0 so this body cannot push the footer row out of the card: a
   grid `1fr` row still floors at min-content, and the mail is the one body
   here long enough to hit it. The copy is written to fit; this is the guard
   if it ever is not. */
.surf-body--mail {
  padding: 0; gap: 0;
  justify-content: flex-start;
  min-height: 0; overflow: hidden;
  background: #FFFFFF;
}
/* Once the feed has moved, the thread above it is cut mid-line at the top
   edge. The fade makes that read as more mail further up rather than as a row
   that failed to render. Only while scrolled: at rest the first mail starts at
   the top edge and has nothing above it to fade. */
.surf-body--mail.is-scrolled {
  -webkit-mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0, #000 22px);
          mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0, #000 22px);
}
/* Both threads sit in here at full height from the start, the second one below
   the fold. mailPreview() only ever moves this node, so the arrival of a mail
   is a scroll and never a reflow. */
/* position:relative so that the second thread's offsetTop is measured from the
   feed rather than from the card, which under the phone deck is absolute and
   scaled -- mailPreview() scrolls by that number. */
.mail-feed {
  position: relative;
  display: flex; flex-direction: column; flex: none;
  transition: transform 900ms var(--hs-ease-standard);
}
/* Sized so one thread fills the card exactly: the resting state is a mail and
   its answer with nothing else showing, and the second thread starts flush
   under the bottom edge. Change the copy and check it still lands -- a gap of
   white under the reply is the second thread waiting with its opacity at 0. */
.mail-item {
  display: flex; flex-direction: column; gap: 8px;
  padding: var(--hs-space-4);
}
/* A whole card of room instead of a box inside it, so the mail can be read at
   the size the rest of the deck is read at rather than at inbox-preview size. */
.mail-item .mail-subj,
.mail-item .mail-copy,
.mail-item .mail-who b { font-size: var(--hs-text-sm); }
/* The reply is a band across the whole card rather than a block indented
   inside the message, so it reads as Julia answering the mail above it. */
.mail-item .mail-reply {
  margin: 4px calc(var(--hs-space-4) * -1) calc(var(--hs-space-4) * -1);
}
/* The second mail is already laid out and already the right height -- it is
   just invisible and below the edge until its turn. */
.mail-item--next {
  opacity: 0;
  transition: opacity 700ms var(--hs-ease-standard);
}
.mail-item--next.is-in { opacity: 1; }
.mail-item .mail-reply[data-mail-late] {
  opacity: 0;
  transition: opacity 600ms var(--hs-ease-standard);
}
.mail-item .mail-reply[data-mail-late].is-in { opacity: 1; }
.mail-ava--b { background: var(--hs-accent-subtle); color: var(--hs-accent-ink); }
.mail-from { display: flex; align-items: center; gap: 8px; }
.mail-ava {
  width: 26px; height: 26px; flex: none;
  border-radius: var(--hs-radius-circle);
  display: grid; place-items: center;
  background: var(--hs-accent2-subtle); color: var(--hs-accent2-ink);
  font-family: var(--hs-font-ui);
  font-size: 10px; font-weight: var(--hs-weight-bold);
}
.mail-who { display: flex; flex-direction: column; min-width: 0; }
.mail-who b {
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-xs);
  font-weight: var(--hs-weight-bold);
  color: var(--hs-fg-primary);
}
.mail-who i {
  font-style: normal; font-family: var(--hs-font-ui);
  font-size: 10px; color: var(--hs-fg-tertiary);
}
.mail-subj {
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-xs);
  font-weight: var(--hs-weight-bold);
  color: var(--hs-fg-primary);
}
.mail-copy {
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-xs);
  line-height: var(--hs-leading-normal);
  color: var(--hs-fg-secondary);
}
.mail-copy b { color: var(--hs-fg-primary); font-weight: var(--hs-weight-bold); }
.mail-reply {
  display: flex; flex-direction: column; gap: 6px;
  padding: var(--hs-space-4);
  background: var(--hs-accent-subtle);
  border-top: var(--hs-size-hairline) solid var(--hs-accent-ring);
}
.mail-tag {
  display: inline-flex; align-items: center; gap: 5px; align-self: flex-start;
  padding: 3px 8px;
  border-radius: var(--hs-radius-pill);
  background: var(--hs-accent-muted); color: var(--hs-accent-ink);
  font-family: var(--hs-font-ui);
  font-size: 10px; font-weight: var(--hs-weight-bold);
}

/* the live email layer */
/* The opacity transition is the way out: mailBack() fades this layer down
   before handing the card back to its resting loop. */
.surf-live--email {
  background: #FFFFFF;
  transition: opacity var(--hs-duration-slow) var(--hs-ease-standard);
}
.surf-live--email .cs-head { background: var(--hs-bg-sunken); border-bottom-color: var(--hs-border-default); }
.surf-live--email .cs-id b { color: var(--hs-fg-primary); }
.surf-live--email .cs-id i { color: var(--hs-fg-tertiary); }
/* A portrait, not a logo mark — round rather than the squircle a business
   avatar gets. */
.cs-av--julia { border-radius: var(--hs-radius-circle); background-color: var(--hs-neutral-200); }
/* Takes only what it needs and caps itself, so a long notice cannot squeeze the
   form off the bottom of the card -- the form keeps the slack. */
.mail-live {
  flex: none; max-height: 62%;
  display: flex; flex-direction: column;
  gap: var(--hs-space-3);
  padding: var(--hs-space-4);
  background: var(--hs-bg-sunken);
  border-bottom: var(--hs-size-hairline) solid var(--hs-border-default);
  overflow-y: auto;
}
/* Empty until there is something to say, so the form owns the card until the
   visitor has actually asked something. */
.mail-live:empty { display: none; }
.mail-note {
  margin: 0;
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-xs);
  line-height: var(--hs-leading-normal);
  color: var(--hs-fg-secondary);
  text-align: center;
}
.mail-note--bad { color: var(--hs-danger-ink); }
/* the confirmation, and the way back out ------------------------------
   Once the question is away this card has one thing to say, so it says it
   across the whole body: the form goes rather than staying under a result
   panel as a squeezed strip, and the log stops being a capped inbox strip and
   becomes the card. Three seconds later mailBack() fades the layer out and the
   resting card fades in. */
.surf-live--email.is-mail-out { opacity: 0; }
.surf-live--email.is-mail-done .mail-form { display: none; }
.surf-live--email.is-mail-done .mail-live {
  flex: 1; max-height: none;
  align-items: center; justify-content: center;
  background: #FFFFFF;
  border-bottom: 0;
}
.mail-done {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--hs-space-2);
  max-width: 30ch;
  text-align: center;
  animation: mailDoneIn var(--hs-duration-slow) var(--hs-ease-standard) both;
}
.mail-done-tick {
  width: 38px; height: 38px;
  margin-bottom: 2px;
  border-radius: var(--hs-radius-circle);
  display: grid; place-items: center;
  background: var(--hs-accent-subtle); color: var(--hs-accent-ink);
}
.mail-done-t {
  margin: 0;
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-base);
  font-weight: var(--hs-weight-bold);
  letter-spacing: var(--hs-tracking-tight);
  color: var(--hs-fg-primary);
}
.mail-done-d {
  margin: 0;
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-xs);
  line-height: var(--hs-leading-normal);
  color: var(--hs-fg-secondary);
  overflow-wrap: anywhere;
}
@keyframes mailDoneIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
/* The resting card coming back. On the children rather than on .surf, because
   the phone deck owns the card's own transform and opacity. */
.surf.is-mail-back .surf-head,
.surf.is-mail-back .surf-body,
.surf.is-mail-back .surf-cta {
  animation: mailBackIn var(--hs-duration-slow) var(--hs-ease-standard) both;
}
@keyframes mailBackIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
/* The form is the card here, not a composer pinned under a thread, so it takes
   the whole body and the action sits at the foot of it. */
.mail-form {
  flex: 1; min-height: 0;
  display: flex; flex-direction: column;
  gap: 4px;
  padding: var(--hs-space-4);
  background: #FFFFFF;
  overflow-y: auto;
}
.mail-intro {
  margin: 0 0 var(--hs-space-3);
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-xs);
  line-height: var(--hs-leading-normal);
  color: var(--hs-fg-secondary);
}
.mail-lab {
  margin-top: var(--hs-space-2);
  display: flex; align-items: baseline; gap: 6px;
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-2xs);
  font-weight: var(--hs-weight-bold);
  letter-spacing: var(--hs-tracking-wide);
  text-transform: uppercase;
  color: var(--hs-fg-tertiary);
}
.mail-lab em {
  font-style: normal;
  font-weight: var(--hs-weight-medium);
  text-transform: none;
  letter-spacing: 0;
  color: var(--hs-fg-tertiary);
  opacity: .75;
}
.mail-in {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--hs-border-default);
  border-radius: var(--hs-radius-sm);
  background: #FFFFFF;
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-sm);
  color: var(--hs-fg-primary);
}
.mail-q { resize: none; line-height: var(--hs-leading-snug); }
.mail-in::placeholder { color: #9CA3AF; }
.mail-in:focus-visible {
  outline: none;
  border-color: var(--hs-border-field-focus, #606963);
  box-shadow: none;
}
/* margin-top:auto puts the button at the foot of the card rather than tight
   under the last field; sticky keeps it there once a reply has arrived and the
   form starts scrolling -- otherwise the one action on the card scrolls out of
   sight exactly when someone wants to ask a second question. */
.mail-actions {
  margin-top: auto;
  position: sticky;
  bottom: calc(var(--hs-space-4) * -1);
  padding: var(--hs-space-4) 0 0;
  display: flex; flex-direction: column; gap: 6px;
  background: #FFFFFF;
}
/* Off-screen, not display:none -- a bot fills what it can read in the DOM,
   and a hidden field is the one thing it knows to skip. */
.mail-hp {
  position: absolute;
  left: -9999px;
  width: 1px; height: 1px;
  opacity: 0;
}
/* Ink fill, same as .cta-hello (lander-v3.css:277). The green pill was the
   only accent-solid button on the page, so it read as a different family of
   control than every other "start something" button. */
.mail-send {
  flex: none;
  height: var(--hs-size-control-md);
  border: 0; border-radius: var(--hs-radius-pill);
  background: var(--hs-forest-900); color: var(--hs-neutral-25);
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-sm);
  font-weight: var(--hs-weight-bold);
  letter-spacing: var(--hs-tracking-tight);
  cursor: pointer;
  box-shadow: 0 10px 24px -10px rgba(14, 27, 17, 0.55);
  transition:
    background-color var(--hs-duration-base) var(--hs-ease-standard),
    box-shadow var(--hs-duration-base) var(--hs-ease-standard);
}
/* Pointer-only, for the same reason .cta-hello is: a tap leaves :hover stuck
   until you tap elsewhere. */
@media (hover: hover) and (pointer: fine) {
  .mail-send:hover {
    background: var(--hs-forest-800);
    box-shadow: 0 14px 30px -12px rgba(14, 27, 17, 0.45);
  }
}
.mail-send:disabled { opacity: .55; cursor: default; box-shadow: none; }
.mail-send:disabled:hover { background: var(--hs-forest-900); }
.mail-send:focus-visible {
  outline: var(--hs-size-focus-ring) solid var(--hs-forest-700);
  outline-offset: 3px;
}
.mail-fine {
  margin: 0;
  font-family: var(--hs-font-ui);
  font-size: 10px;
  color: var(--hs-fg-tertiary);
  text-align: center;
}

/* The dots below the deck. They only mean anything once the deck is a deck,
   so they are built by surfSwipe() and hidden until the phone block turns
   them on. */
.surf-dots { display: none; }

/* ---------- the deck on a phone ----------
   Stacked down the page, the three cards are three screens of scroll and the
   second and third never get read. So on a phone they become a pile: one card
   in the middle, the other two showing under it, thrown one at a time.

   Two layers, and both have to work:

   1. Frame and track, CSS only. `.surf-grid` clips, `.surf-track` is a row
      that scroll-snaps inside it with the next card peeking. This is what a
      visitor with no JS gets, and it is already a proper swipeable deck.
   2. surfSwipe() turns that row into the pile, which needs positions no
      stylesheet can hold: every card is placed by a spring, every frame. It
      adds `.is-swipe`, and everything under that class assumes the transform
      belongs to the script.

   Layer 2 is why the cards are absolute and the track has a fixed height --
   nothing here is in flow, so the frame would otherwise collapse. */
@media (max-width: 940px) {
  .surf-grid {
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    /* overflow-y: hidden would crop the cards' own shadow, so the frame keeps
       a little room for it and takes the same amount back off the flow. */
    padding-block: 10px;
    margin-block: -10px;
  }
  .surf-grid::-webkit-scrollbar { width: 0; height: 0; }
  .surf-track {
    display: flex;
    align-items: stretch;
    grid-template-columns: none;
  }
  .surf {
    flex: 0 0 calc(100% - 60px);
    /* grid-auto-rows above is a grid property and does nothing to a flex row,
       so the card carries the row height itself -- the live transcript only
       scrolls inside a definite one. */
    height: 468px;
    scroll-snap-align: start;
  }

  /* ---- layer 2: surfSwipe() is driving, and the row becomes a pile ---- */
  .surf-grid.is-swipe {
    overflow: hidden;
    scroll-snap-type: none;
    /* Full bleed. A thrown card has to leave the screen, not the text column,
       or it stops at an invisible wall two thumbs short of the edge. The
       cards keep their own inset below, so nothing moves except the clip. */
    margin-inline: -24px;
    /* The pile's shadow is the deepest thing in the frame and `overflow:
       hidden` was cutting it off in a straight line across the page -- worst
       while a card is held, because the held shadow is the one that reaches
       furthest. The bottom card sits 40px down, tilts, and throws about 22px
       of shadow under that, which is more than the track's own slack. So the
       clip keeps 24px of room below the track and the flow takes the same
       amount back, which puts the tail of the shadow in the band the dots sit
       in -- where it belongs, and where nothing else needs the space. */
    padding-block: 10px 24px;
    margin-block: -10px -24px;
  }
  .surf-grid.is-swipe .surf-track {
    display: block;
    position: relative;
    /* Card, plus the tail of the pile showing under it. Nothing in here is in
       flow, so this height is the section's only idea of how tall the deck
       is. Keep it in step with the card height above and with the deepest
       slot in surfSwipe() -- the bottom card sits 40px down and tilts, which
       reaches about 491px, and the rest is its shadow. */
    height: 516px;
    /* Down the page is the browser's; across the pile is ours. Without this
       the first sideways move is a scroll fight nobody wins. */
    touch-action: pan-y;
  }
  .surf-grid.is-swipe .surf {
    position: absolute;
    top: 0; left: 0; right: 0;
    /* Centred by the auto margins, so the script's transform is a pure offset
       from the middle and never has to know the frame width. */
    width: min(100% - 76px, 420px);
    margin-inline: auto;
    scroll-snap-align: none;
    will-change: transform;
    /* Only box-shadow, and deliberately not transform: the shared card
       transition would fight the spring every frame. Nothing fades either --
       a card at 94% lets the one under it show through its middle, which
       during a throw reads as two cards printed on top of each other. Depth
       here is size, offset and shadow, all of which stay opaque. */
    transition: box-shadow var(--hs-duration-base) var(--hs-ease-standard);
  }
  /* Each card throws its own colour on the page under it, taken from the
     channel icon in its own header: aqua for the call, iOS blue for the
     chat, jasmine for the mail. On a desktop the three sit side by side and
     the icons carry that on their own; in a pile they overlap, and a tinted
     edge is what tells you the card under this one is a different card
     rather than a paper thickness. Kept low and wide -- it should read as
     light off the card, not as a glow around it.
     Triplets rather than the tokens themselves, because these need an alpha:
     aqua-400, --ios-blue, jasmine-500. */
  .surf-grid.is-swipe .surf:nth-child(1) { --deck-rgb: 67, 229, 164; }
  .surf-grid.is-swipe .surf:nth-child(2) { --deck-rgb: 0, 122, 255; }
  .surf-grid.is-swipe .surf:nth-child(3) { --deck-rgb: 163, 71, 234; }
  .surf-grid.is-swipe .surf {
    box-shadow:
      0 1px 3px rgba(14, 27, 17, 0.07),
      0 10px 20px -12px rgba(var(--deck-rgb), 0.5),
      0 22px 40px -20px rgba(var(--deck-rgb), 0.42);
  }
  /* Only the top card takes a touch; the pile behind it is scenery, and a tap
     landing on a corner of it should reach the card on top instead. */
  .surf-grid.is-swipe .surf:not(.is-active) { pointer-events: none; }
  .surf-grid.is-swipe.is-grabbing { user-select: none; }
  .surf-grid.is-swipe.is-grabbing .surf { transition: none; }
  /* Held, a card comes off the pile -- the same lift the desktop hover gives,
     borrowed for a thumb, and its colour deepens with it. */
  .surf-grid.is-swipe .surf.is-held {
    box-shadow:
      0 2px 6px rgba(14, 27, 17, 0.1),
      0 16px 28px -14px rgba(var(--deck-rgb), 0.6),
      0 34px 56px -22px rgba(var(--deck-rgb), 0.5);
  }

  /* ---- the wash under the deck ----
     The cards throw their colour onto the page, but the frame clips at the
     foot of the pile, so the light stopped dead in a straight line an inch
     below the bottom card. This carries it the rest of the way: the same three
     hues, wide and low, running down behind the dots and gone by the divider
     that closes the section.

     It hangs off the section rather than off the deck because the divider is
     the section's own bottom edge -- anchored there, the wash ends on the line
     whatever height the pile happens to be, with no number to keep in step.
     Only the height above it is a guess, and that only has to clear the dots.
     The -24px matches the frame's own full bleed. */
  #channels { position: relative; }
  #channels::after {
    content: "";
    position: absolute;
    left: -24px; right: -24px; bottom: 0;
    height: 190px;
    z-index: 0;
    pointer-events: none;
    background:
      radial-gradient(56% 68% at 20% 12%, rgba(67, 229, 164, 0.30), transparent 70%),
      radial-gradient(58% 72% at 52% 2%, rgba(0, 122, 255, 0.24), transparent 72%),
      radial-gradient(60% 76% at 82% 14%, rgba(163, 71, 234, 0.28), transparent 72%);
    /* Nothing at either end: it comes out from under the cards rather than
       starting, and it is spent before the line rather than cut off by it. */
    -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 32%, #000 60%, transparent 100%);
    mask-image: linear-gradient(to bottom, transparent 0%, #000 32%, #000 60%, transparent 100%);
  }

  .surf-dots {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 8px;
    margin-top: var(--hs-space-5);
    /* The frame above now reaches down into this row so its shadow is not cut
       off, and the cards inside it are positioned -- so without a z-index of
       its own this row would sit under a thrown card's shadow instead of on
       top of it. 41 clears the 40 a card in the air takes. */
    position: relative;
    z-index: 41;
  }
  .surf-dot {
    width: 8px; height: 8px; padding: 0;
    border: 0; border-radius: var(--hs-radius-circle);
    background: var(--hs-border-strong);
    cursor: pointer;
    transition:
      width var(--hs-duration-slow) var(--hs-ease-overshoot),
      background-color var(--hs-duration-base) var(--hs-ease-standard);
  }
  .surf-dot.is-on { width: 26px; border-radius: 999px; background: var(--hs-accent-solid); }
  .surf-dot:focus-visible {
    outline: var(--hs-size-focus-ring) solid var(--hs-border-focus);
    outline-offset: 3px;
  }
}

/* ---------- capabilities ----------

   The same wallet as the tool deck above: cards tucked into each other, and
   the one you tap grows open in place. Read the comment on .tool-wrap for why
   the negative margin, the conditional tuck and the 0fr -> 1fr track are each
   load-bearing -- the rules below are that card, restated for a head that
   carries a sentence instead of an icon.

   What this replaced: a 3x2 grid of tiles sharing one detail panel that was
   moved under whichever tile you tapped. Opening one shoved the two cards
   beside it down a row, so the thing you had just tapped moved. A deck only
   pushes what is below it. */

.cap-row {
  /* ONE number runs the stack: how deep the next card sits on this one, so
     the strip you can see on a shut card is its head, exactly. */
  --cap-tuck: 26px;
  display: flex;
  flex-direction: column;
  /* Short of the 1200px shell. The fold sets a two-column detail, and across
     the full width its list lines run longer than anything else on the page. */
  max-width: 52rem;
}

.cap-wrap {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: var(--hs-radius-xl);
  background: var(--hs-glass-card);
  backdrop-filter: var(--hs-glass-blur);
  -webkit-backdrop-filter: var(--hs-glass-blur);
  border: var(--hs-size-hairline) solid var(--hs-glass-card-border);
  box-shadow: var(--hs-glass-lift-strong);
  transition:
    margin-top var(--hs-duration-wallet) var(--hs-ease-spring),
    padding-bottom var(--hs-duration-wallet) var(--hs-ease-spring),
    box-shadow var(--hs-duration-base) var(--hs-ease-spring),
    background-color var(--hs-duration-fast) var(--hs-ease-standard),
    border-color var(--hs-duration-fast) var(--hs-ease-standard);
}
.cap-wrap + .cap-wrap {
  margin-top: calc(var(--cap-tuck) * -1);
  z-index: 1;
}
.cap-wrap:not(.is-open):has(+ .cap-wrap) { padding-bottom: var(--cap-tuck); }
.cap-wrap.is-open {
  z-index: 2;
  box-shadow: var(--hs-elevation-popover);
}
.cap-wrap.is-open + .cap-wrap { margin-top: var(--hs-space-2); }

/* The head IS the visible strip of a shut card, so its own padding is the
   breathing room. */
button.cap {
  display: flex;
  align-items: flex-start;
  gap: var(--hs-space-4);
  width: 100%;
  padding: var(--hs-space-5);
  background: none;
  border: 0;
  box-shadow: none;
  cursor: pointer;
  font: inherit;
  text-align: left;
  /* the grey square Android paints over a card on tap */
  -webkit-tap-highlight-color: transparent;
}
button.cap:focus-visible {
  outline: var(--hs-size-focus-ring) solid var(--hs-border-focus);
  outline-offset: -3px;
  border-radius: var(--hs-radius-xl);
}
.cap-txt { display: grid; gap: 3px; min-width: 0; }
.cap-d {
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-sm);
  color: var(--hs-fg-secondary);
  line-height: var(--hs-leading-normal);
}
.cap-chev {
  flex: none;
  margin-left: auto;
  /* sits on the title's line, not centred against a two-line head */
  margin-top: 7px;
  width: 9px; height: 9px;
  border-right: 2px solid var(--hs-fg-tertiary);
  border-bottom: 2px solid var(--hs-fg-tertiary);
  transform: rotate(45deg) translate(-2px, -2px);
  transition:
    transform var(--hs-duration-base) var(--hs-ease-standard),
    border-color var(--hs-duration-fast) var(--hs-ease-standard);
}
.cap-wrap.is-open .cap-chev { transform: rotate(-135deg) translate(-2px, -2px); }

.cap-card {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--hs-duration-wallet) var(--hs-ease-spring);
}
.cap-wrap.is-open .cap-card { grid-template-rows: 1fr; }
/* min-height:0 is what lets the 0fr track actually collapse -- a grid item's
   automatic minimum size is its content, so without this the fold stays at
   full height and the card never shuts. */
.cap-fold { min-height: 0; overflow: hidden; }
/* Left edge lines up with the head's text; nothing sits above it, so the top
   padding is the head's own gap to the fold. */
.cap-fold > .capd-grid { padding: var(--hs-space-2) var(--hs-space-5) var(--hs-space-5); }

/* ---------- capability detail ----------
   The title is on the head of the card, so the fold opens straight into the
   claim and the worked example beside it. */
/* One size down from the tile version: the card head above it is already
   setting the title in display type, and two large lines in a row read as two
   headings rather than a claim under a name. */
.capd-lede {
  margin: 0 0 var(--hs-space-5);
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-base);
  font-weight: var(--hs-weight-medium);
  line-height: var(--hs-leading-snug);
  letter-spacing: var(--hs-tracking-tight);
  color: var(--hs-fg-secondary);
}
/* One bullet list for both wallets on this page -- the capability fold here and
   the tool fold up in "What tools can you use?". They are the same object doing
   the same job, and the tool version used to be its own thing at 12px with a
   5px dot, which read as fine print next to this one. The container padding
   still belongs to each card; only the bullets are shared. */
.capd-list,
.tool-card ul {
  margin: 0;
  list-style: none;
  display: grid;
  gap: var(--hs-space-3);
}
/* Padding is the one thing that is not shared -- the tool fold indents its
   bullets under the tool's name, and sets its own above. */
.capd-list { padding: 0; }
.capd-list li,
.tool-card li {
  position: relative;
  padding-left: var(--hs-space-6);
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-base);
  color: var(--hs-fg-primary);
  line-height: var(--hs-leading-snug);
}
.capd-list li span,
.tool-card li span { display: block; color: var(--hs-fg-tertiary); font-size: var(--hs-text-sm); margin-top: 2px; }
.capd-list li::before,
.tool-card li::before {
  content: "";
  position: absolute;
  left: 0; top: 7px;
  width: 8px; height: 8px;
  border-radius: 2px;
  background: var(--hs-accent-solid);
}

/* ---------- models ---------- */

/* ---------- closing ---------- */

.qa--close {
  background-color: var(--wash-base);
  background-image:
    radial-gradient(var(--wash-dot) 1px, transparent 1.4px),
    radial-gradient(circle at 12% 18%, rgba(67, 229, 164, 0.18), transparent 42%),
    radial-gradient(circle at 88% 82%, rgba(163, 71, 234, 0.14), transparent 44%),
    linear-gradient(135deg, rgba(67, 229, 164, 0.302) 0%, rgba(163, 71, 234, 0.274) 100%),
    linear-gradient(180deg, var(--wash-base), var(--wash-foot));
  background-size: 26px 26px, 100% 100%, 100% 100%, 100% 100%, 100% 100%;
  border: var(--hs-size-hairline) solid var(--hs-border-default);
  border-radius: var(--hs-radius-2xl);
  padding: var(--hs-space-16) var(--hs-space-12);
  margin-top: var(--hs-space-16);
  color: var(--hs-forest-900);
}

/* The rotating example ask, wearing the visitor's bubble at the size a typed
   message actually is -- .qa-ask above it is display type doing a heading's
   job, and matching it would read as a second question rather than the next
   line of the thread.

   Grid, not absolute positioning: all twelve share one cell, so the block is
   as tall as the longest of them at whatever width. No measured height, and
   nothing below it moves when a two-line example comes round. */
.qa-egs {
  display: grid;
  margin: 0 0 var(--hs-space-6);
  /* Purely illustrative -- it must never eat a tap aimed at the button. */
  pointer-events: none;
}
/* Margin lives in its own qualified rule rather than in the block below.
   `.doc p` is (0,1,1) to a bare `.qa-eg`'s (0,1,0), so it wins and its bottom
   margin adds a dead 17px strip to the grid cell -- but qualifying the whole
   block would also outrank `.qa-eg.is-on` and kill the fade. */
.doc p.qa-eg { margin: 0; }

.qa-eg {
  grid-area: 1 / 1;
  justify-self: start;
  align-self: start;
  /* Wide enough that the longest of the twelve stays on one line here. The
     grid cell is sized by the tallest anyway, so a wrap costs nothing -- this
     is only about how the line reads. */
  max-width: 34ch;
  padding: var(--hs-space-3) var(--hs-space-5);
  background: linear-gradient(180deg, var(--ios-blue-hi), var(--ios-blue));
  color: #fff;
  border-radius: var(--hs-radius-2xl) var(--hs-radius-2xl) var(--hs-radius-2xl) var(--hs-space-2);
  box-shadow: 0 10px 24px -14px rgba(0, 60, 130, 0.55);
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-lg);
  font-weight: var(--hs-weight-medium);
  letter-spacing: var(--hs-tracking-tight);
  line-height: var(--hs-leading-snug);
  opacity: 0;
  transform: translateY(6px) scale(0.99);
  transition: opacity 320ms ease, transform 320ms ease;
}
/* The incoming one waits for the outgoing one to be fully gone, then holds an
   empty beat before it starts. The two bubbles are different widths and sit on
   top of each other, so crossing them at all reads as a smudge rather than a
   swap -- and the blank moment is what makes the next one land as a new
   message instead of a ticker rolling over. */
.qa-eg.is-on {
  opacity: 1;
  transform: none;
  transition-duration: 520ms;
  transition-delay: 620ms;
}
/* The heading and the answer read left, like every other section on the page.
   Only the button centres -- that is where the floating CTA hands over, and it
   is the one element that was pinned to the middle of the screen a moment
   earlier. */
/* The closing button is centred at every width -- it is the page's last word,
   and on mobile it is also where the floating one hands over. */
.qa--close .cta-hello--close {
  display: flex;
  width: fit-content;
  margin-inline: auto;
}
@media (max-width: 940px) {
  /* `visibility`, not `display`. The slot has to keep its space so the section
     does not reflow at the moment of handover, and the check that decides when
     to hand over needs a real box to measure -- `display: none` collapses it to
     zero height, which is why the swap kept firing the instant the section's
     top edge appeared instead of when the button got there. */
  .qa--close .cta-hello--close { visibility: hidden; }
  .doc.is-at-close .qa--close .cta-hello--close { visibility: visible; }
}

/* ---------- CTA behaviour demo ---------- */





@media (max-width: 940px) {
  /* Roomier on mobile: the closing card is the page's last word, and the
     "Text Hello" button needs air around it rather than sitting tight against
     the bubble above and the card edge below. */
  .qa--close { padding: var(--hs-space-16) var(--hs-space-6); }
  /* Collapses against the bubble's own 16px bottom margin, so the gap this
     buys is 40 - 16 = 24px, not 40. */
  .qa--close .cta-hello--close { margin-top: var(--hs-space-10); }
}

/* ============================================================
   THE BRAND LOCKUP
   The proportions are NOT here. They are --hs-lockup-* tokens, generated from
   design-system/tokens/primitives.json, and ds/hs-brand.css applies them to
   .brand-mark and .brand-word. This file picks a size step and nothing else.
   ============================================================ */

.brand-surface {
  border: var(--hs-size-hairline) solid var(--hs-border-default);
  border-radius: var(--hs-radius-lg);
  min-height: 168px;
  display: flex;
  align-items: center;
  padding: var(--hs-space-6);
  box-shadow: var(--hs-elevation-raised);
}
.brand-surface--nav {
  min-height: 0;
  align-items: center;
  justify-content: space-between;
  gap: var(--hs-space-4);
  padding: var(--hs-space-4) var(--hs-space-5);
  background: var(--hs-bg-raised);
  align-self: start;
}

/* Layout and gap come from ds/hs-brand.css, which adopts .brand-mark and
   .brand-word into the --hs-lockup-* tokens. This file picks the SIZE STEP and
   nothing else, so the header lockup measures the same here as it does on
   /login and anywhere else. Re-declaring 1.35em / 0.9em here — which this file
   used to do, as a copy — is how the old referral page ended up at a 1.76
   mark-to-word ratio against the design system's 1.5. */
.brand-lockup { font-size: var(--hs-lockup-size-base); }
.brand-lockup--lg { font-size: var(--hs-lockup-size-lg); }
.brand-lockup--sm { font-size: var(--hs-lockup-size-sm); }

/* ============================================================
   THE MOBILE DOCK SIMULATION
   A real scroll container at phone width. Deliberately a plain viewport
   frame, not a rendered handset -- the phone mockup was cut from the hero
   for good reason; this is a measuring instrument, not a picture of a phone.
   ============================================================ */




/* the wash, as it would be on a phone */



/* the thread, at phone scale */

/* stub sections, so there is something to scroll past */
/* the padding a real page owes the dock, or the last section hides under it */

/* a short scrim so content doesn't collide with the dock as it passes under */

/* THE DOCK.
   Absolute here because the frame is standing in for the viewport. On the
   real page this is `position: fixed` with the same bottom inset -- and it
   must be a direct child of <body>, see the note in the rail. */

/* ============================================================
   supporting sections
   ============================================================ */

.foot {
  margin-top: var(--hs-space-16);
  padding-top: var(--hs-space-5);
  border-top: var(--hs-size-hairline) solid var(--hs-border-default);
  color: var(--hs-fg-tertiary);
}

@media (max-width: 940px) {
  .hero-inner {
    grid-template-columns: minmax(0, 1fr);
    /* The copy and the thread are one thought on a single column, so they run
       close together. At space-8 the 32px gap plus the thread's own top fade
       put roughly 110px between the last line of copy and the first message
       you could actually read. */
    gap: var(--hs-space-4);
    /* Side padding matches .shell (space-6) so the hero copy sits on the same
       left edge as every section heading below it -- at space-5 it was 4px
       out, which is exactly the kind of drift you feel without seeing.
       The bottom value is NOT decorative: it is the clearance the pinned CTA
       needs, and must stay >= button height + its bottom inset or the button
       lands on the thread. A shorthand that omits it reintroduces that bug. */
    padding: var(--hs-space-10) var(--hs-space-6) var(--hs-space-20);
  }
  .bar--top .bar-nav,
  .bar--top .cta-hello--bar { display: none; }
  .bar-in { padding: var(--hs-space-3) var(--hs-space-5); }
  .bar--bottom .cta-hello--bar {
    pointer-events: auto;
    flex: 0 1 auto;
    box-shadow: 0 16px 34px -12px rgba(14, 27, 17, 0.6);
  }
  /* wash runs flush under the mobile bar, which is shorter here -- it measures
     47.4px, so 47px keeps the reserve on the tucked-under side of the fraction.
     Nothing is reserved at the foot: the wash runs under the floating button. */
  .doc { --bar-h: 47px; padding-bottom: 0; }
  .lander-hero { padding-top: 0; }
  /* The channel cards are not here -- they become a swiped deck at this same
     width, in the block above. */
  .proof-grid { grid-template-columns: minmax(0, 1fr); }
  /* The wallet is one layout at every width -- it was written from the
     workspace card, which is a phone layout first. All the narrow screen
     changes is the padding: space-5 all round eats a third of a 375px card. */
  button.tool { padding: var(--hs-space-4); }
  .tool-card ul {
    padding: 0 var(--hs-space-4) var(--hs-space-4) calc(30px + var(--hs-space-4) * 2);
  }
  /* less shadow room needed, and the hero padding here is only 20px */
  .thread { padding-inline: 12px; margin-inline: -12px; }
  .lockup .av { width: 60px; height: 60px; }
  /* ~227px of column left here, so the note wraps to two lines. Tighter type
     and padding keep it a note above the headline rather than a slab. */
  .hero-ref {
    font-size: var(--hs-text-xs);
    padding: 2px var(--hs-space-2);
    margin-bottom: var(--hs-space-2);
  }
  /* Bubbles wrap to more lines in a narrow column, so the same thread runs
     ~140px taller here. Re-measured at 688px on a 375 viewport, not guessed. */
  /* Sized to what is actually visible, not to the content. The hero ran ~980px
     on an 844px screen, so the bottom of the thread -- exactly where every new
     turn appears -- sat below the fold and nobody saw the conversation happen.
     Now it fits the window and the thread scrolls itself. */
  /* The hero fills the screen now, so the thread takes the slack rather
     than leaving a field of empty wash above the floating button.
     100dvh less the top bar, the lockup block, and the room the button
     floats in. */
  .thread { height: clamp(280px, calc(100dvh - 330px), 620px); }
}

@media (max-width: 620px) {
  /* Same deck at every width, like the tool wallet -- only the padding gives,
     because space-5 all round eats a third of a 375px card. */
  button.cap { padding: var(--hs-space-4); }
  .cap-fold > .capd-grid { padding: var(--hs-space-2) var(--hs-space-4) var(--hs-space-4); }
  .est-or { align-self: flex-start; padding-inline: 0; }
  /* the summary's negative margins have to track the reduced padding, or it
     sits wider than the card it lives in */
  .usage-sum {
    margin: calc(var(--hs-space-4) * -1) calc(var(--hs-space-5) * -1);
    padding: var(--hs-space-4) var(--hs-space-5);
  }
  .proof-grid { grid-template-columns: minmax(0, 1fr); }
  .foot-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .usage { padding: var(--hs-space-4) var(--hs-space-5) var(--hs-space-5); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001s !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001s !important;
  }
}

/* ---------- the hello modal ---------- */
.v2-modal {
  display: none;
  position: fixed; inset: 0; z-index: 200;
  align-items: center; justify-content: center;
  padding: var(--hs-space-6);
  background: rgba(14, 27, 17, 0.45);
  backdrop-filter: blur(4px);
}
.v2-modal__panel {
  position: relative;
  width: min(420px, 100%);
  padding: var(--hs-space-8) var(--hs-space-7) var(--hs-space-7);
  background: var(--hs-bg-raised);
  border-radius: var(--hs-radius-xl);
  box-shadow: var(--hs-elevation-overlay, 0 32px 80px -24px rgba(14, 27, 17, .45));
  text-align: center;
}
.v2-modal__choice-btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--hs-size-control-lg); padding-inline: var(--hs-space-6);
  border: 0; border-radius: var(--hs-radius-pill); cursor: pointer;
  font-family: var(--hs-font-ui); font-size: var(--hs-text-base);
  font-weight: var(--hs-weight-bold); text-decoration: none;
}
.v2-modal__choice-btn--secondary {
  background: transparent; color: var(--hs-fg-secondary);
  border: var(--hs-size-hairline) solid var(--hs-border-default);
}

/* ---------- dressing for the real voice embed ----------
   Production only: voice-call-embed.js renders parts the scripted card has no
   equivalent for -- the mic-permission recovery panel, the status line, the
   idle close button. Those lived in lander.css and the old
   scroll-lander-integration.css, neither of which this page loads, so the few
   that matter are restated here scoped to the widget. Everything else comes from voice-call-action.css,
   which is linked ahead of this file so these rules win where they overlap. */
.surf-live--call .call-id .ring img { width: 100%; height: 100%; object-fit: cover; object-position: center 16%; }
.surf-live--call .call-action-label { flex: 1 1 auto; min-width: 0; }
.surf-live--call .call-live { display: inline-flex; align-items: center; gap: 8px; color: #43E5A4; font-weight: var(--hs-weight-bold); }
.surf-live--call .hs-status {
  margin-top: 10px; min-height: 1.25rem; text-align: center;
  font-family: var(--hs-font-ui); font-size: 13px; color: #9FB3A9;
}
.surf-live--call .hs-status.ok { color: #43E5A4; }
.surf-live--call .hs-status.err { color: #FFB4AB; }
.surf-live--call .call-idle-close {
  flex: none; width: 28px; height: 28px; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--hs-radius-circle);
  background: rgba(249, 250, 242, 0.14); color: #FCFBFA;
}
.surf-live--call .call-idle-close svg { width: 14px; height: 14px; }
/* The browser will not re-prompt for a mic it has already refused, so this
   panel is the only way out of that state. It has no styles anywhere in the
   repo -- it was inheriting whatever the old lander's cascade gave it. */
/* The embed hides this with the `hidden` attribute, and an explicit display
   beats the UA's `[hidden] { display: none }` -- so the "Microphone is blocked"
   panel sat there permanently, on a working mic, telling everyone their mic was
   blocked. Anything a script toggles with `hidden` needs this said out loud. */
.surf-live--call .call-mic-block[hidden] { display: none; }
.surf-live--call .call-mic-block {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: var(--hs-space-2);
  padding: var(--hs-space-4); margin: 6px 0 8px;
  border-radius: var(--hs-radius-md);
  background: rgba(249, 250, 242, 0.06);
  text-align: left;
}
.surf-live--call .call-mic-block__title {
  margin: 0; font-family: var(--hs-font-ui); font-size: var(--hs-text-sm);
  font-weight: var(--hs-weight-bold); color: #FCFBFA;
}
.surf-live--call .call-mic-block__body,
.surf-live--call .call-mic-block__steps {
  margin: 0; font-family: var(--hs-font-ui); font-size: 13px;
  line-height: var(--hs-leading-normal); color: #CFE0D8;
}
.surf-live--call .call-mic-block__steps { padding-left: 1.1rem; }
.surf-live--call .call-mic-block__retry {
  align-self: flex-start; margin-top: var(--hs-space-2);
  min-height: 36px; padding: 0 14px; cursor: pointer;
  border: 0; border-radius: var(--hs-radius-pill);
  background: #43E5A4; color: #0E1B11;
  font-family: var(--hs-font-ui); font-size: 13px; font-weight: var(--hs-weight-bold);
}

/* ================= the price bar =================
   One card, one number. The price is not a figure with a slider under it --
   the price IS the handle, and it rides in the app's own segmented control
   (.sgc / .v2-pp-tabs, hs-bridge-components.css): a recessed well holding a
   raised white thumb. The two numbers that make that control work are copied
   exactly -- --pb-inset 2px, --pb-lift 3px -- so the pill stands lift - inset
   = 1px proud of the well on all four sides. The only change is that the thumb
   travels a range instead of snapping between two cells.

   This replaces .price-pair / .price-card / .slide / .est, which are left in
   place above because referrals-v3.css defines its own .price-card and .est-row
   and this file is loaded only by index.html -- nothing here is shared, but a
   sweep of dead rules is a separate, reviewable diff. */
.pb-wrap { max-width: 32rem; margin-inline: auto; display: grid; gap: var(--hs-space-3); }
.pb {
  background: var(--hs-bg-raised);
  border: var(--hs-size-hairline) solid var(--hs-border-accent);
  border-radius: var(--hs-radius-lg);
  box-shadow: var(--hs-elevation-overlay);
  padding: var(--hs-space-6);
  display: grid;
  gap: var(--hs-space-4);
}
/* The instruction and the anchor badge answer each other -- pick a size, and
   here is the size most people pick -- so they share the row. */
.pb-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--hs-space-3);
  flex-wrap: wrap;
}
.doc p.pb-lede {
  margin: 0;
  font-size: var(--hs-text-base);
  font-weight: var(--hs-weight-medium);
  color: var(--hs-fg-secondary);
  line-height: var(--hs-leading-snug);
  max-width: 30ch;
  text-wrap: balance;
}
.pb-badge {
  flex: none;
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-2xs);
  font-weight: var(--hs-weight-bold);
  color: var(--hs-fg-on-accent);
  background: var(--hs-accent-solid);
  border-radius: var(--hs-radius-pill);
  padding: 2px var(--hs-space-2);
  white-space: nowrap;
  transition: opacity var(--hs-duration-base) var(--hs-ease-standard);
}
/* Off the anchor the badge stops describing your number but keeps its space --
   removing it resized the card under the cursor mid-drag. */
.pb-badge.is-dim { opacity: 0; }

.pb-ctl { display: grid; gap: var(--hs-space-2); }
.pb-well {
  --pb-tabw: 7.75rem;
  /* px, both set by the script: --pb-x moves the pill, --pb-fill clips the
     bar. Neither is a percentage any more -- the drag has to be paint-only. */
  --pb-x: 0px;
  --pb-fill: 0px;
  --pb-inset: 2px;
  --pb-lift: 3px;
  position: relative;
  height: 3.25rem;
  padding: var(--pb-inset);
  border-radius: var(--hs-radius-pill);
  /* A well, not a pane: a dark 5% tint separates from the wash in a way that
     another layer of white cannot. No border -- a ring around the track
     competes with the raised thumb for the eye. */
  background: var(--hs-glass-recessed);
  cursor: grab;
  /* Load-bearing on touch. hs-mobile.css sets `touch-action: manipulation` on
     <html> for every coarse pointer, and effective touch-action is intersected
     down the ancestor chain, so a horizontal drag here was eligible to become a
     page pan. `pan-y` keeps vertical scrolling for the page -- you can still
     flick past the card by dragging up or down on the bar -- and hands
     horizontal movement to this control.

     The pill itself takes `none` below. `pan-y` is a bargain, not a promise:
     vertical stays the scroller's, so a drag that starts with any vertical in
     it can still be taken away mid-gesture -- pointercancel, and the price
     stops following the finger. Nobody flicks the page by starting on a 7rem
     pill, so that one is ours outright and the rest of the bar keeps the
     bargain. */
  touch-action: pan-y;
}
.pb-well.is-dragging { cursor: grabbing; }
/* Filled to the middle of the pill, not to its leading edge, or the fill
   appears to lag the number it belongs to. Aqua-100, not solid aqua: a
   two-state toggle needs no fill at all, and a continuous one only needs
   enough to say how far along the range you are. */
.pb-fill {
  position: absolute;
  inset: var(--pb-inset);
  border-radius: var(--hs-radius-pill);
  background: var(--hs-accent-muted);
  /* Clipped, not resized. A width per pointermove is layout on every frame of
     a drag; a clip is paint only. `round` keeps the pill cap, which a scaleX
     would squash. --pb-fill is px, set once per input by the script. */
  clip-path: inset(0 calc(100% - var(--pb-fill, 0px)) 0 0 round var(--hs-radius-pill));
}

/* A pure positioning frame -- transparent, exact. ::before paints the visible
   pill, inset by a negative amount on all four sides, so the lift never
   changes the box the arithmetic depends on. */
/* Hit-testable, unlike every other painted part of this control, and only so
   it can carry the `touch-action` above. pointerdown still bubbles to the
   well, which is what owns the drag -- nothing here reads the event target. */
.pb-tab {
  pointer-events: auto;
  touch-action: none;
  position: absolute;
  top: var(--pb-inset);
  bottom: var(--pb-inset);
  left: var(--pb-inset);
  /* translateX, not left. Moving the pill by `left` relaid out the well on
     every pointermove; a transform is composited and the drag tracks the
     finger. --pb-x is px, set by the script. */
  transform: translateX(var(--pb-x, 0px));
  will-change: transform;
  width: var(--pb-tabw);
  border-radius: var(--hs-radius-pill);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--hs-fg-primary);
  isolation: isolate;
}
.pb-tab::before {
  content: "";
  position: absolute;
  inset: calc(-1 * var(--pb-lift));
  z-index: 0;
  border-radius: inherit;
  /* Opaque, not the 0.88 control glass. The lift only reads if the pill is a
     solid object sitting over the well. */
  background: var(--hs-seg-thumb, var(--hs-bg-raised));
  box-shadow:
    0 0 0 0.5px rgba(13, 16, 14, 0.06),
    0 1px 2px rgba(13, 16, 14, 0.06),
    0 4px 10px -2px rgba(13, 16, 14, 0.10);
  transition:
    inset var(--hs-duration-fast) var(--hs-ease-standard),
    box-shadow var(--hs-duration-fast) var(--hs-ease-standard);
}
/* $99 and /mo share a baseline inside a box that is itself centred. Baseline-
   aligning them against the pill is what pushed the number off centre. */
.pb-tabin {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: baseline;
  gap: 1px;
}
.pb-tabin b {
  font-family: var(--hs-font-display);
  font-size: var(--hs-text-2xl);
  font-weight: var(--hs-weight-bold);
  letter-spacing: var(--hs-tracking-tighter);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.pb-tabin i {
  font-style: normal;
  font-size: var(--hs-text-sm);
  font-weight: var(--hs-weight-medium);
  color: var(--hs-fg-tertiary);
  line-height: 1;
}

/* The real input, transparent, inset to match the well so its native thumb
   travels exactly the distance the visible pill does. Keyboard, screen readers
   and click-to-jump all keep working; the grab point lands on the pill. */
.pb-range {
  position: absolute;
  inset: var(--pb-inset);
  /* Above the fill and the pill. Both are pointer-events: none or earlier
     in DOM order, but saying it here means a later addition to the well
     cannot quietly steal the drag. */
  z-index: 2;
  width: calc(100% - 2 * var(--pb-inset));
  height: calc(100% - 2 * var(--pb-inset));
  margin: 0;
  opacity: 0;
  cursor: grab;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  /* Keyboard and assistive tech only. Every pointer gesture is handled on the
     well instead, because the native range maps finger-x to a value using its
     OWN thumb geometry while the visible pill has another -- two mappings for
     one drag, which is what made it lock to a stop instead of following the
     finger. One owner, one mapping. Focus is moved here by script on
     pointerdown so the arrow keys still work after you touch the bar. */
  pointer-events: none;
}
.pb-range:active { cursor: grabbing; }
.pb-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: var(--pb-tabw);
  height: 100%;
  border-radius: var(--hs-radius-pill);
  background: transparent;
}
.pb-range::-moz-range-thumb {
  width: var(--pb-tabw);
  height: 100%;
  border: 0;
  border-radius: var(--hs-radius-pill);
  background: transparent;
}
/* The input is invisible, so its focus ring and pressed state are worn by the
   pill instead. Pressed spreads the lift -- the gesture the app's own thumb
   makes when you pick a tab. */
.pb-well:has(.pb-range:focus-visible) .pb-tab::before,
.pb-well.is-focus .pb-tab::before {
  box-shadow:
    0 0 0 var(--hs-size-focus-ring) var(--hs-border-focus),
    0 4px 10px -2px rgba(13, 16, 14, 0.10);
}
.pb-well.is-dragging .pb-tab::before {
  inset: calc(-1 * var(--pb-lift) - 1px);
  box-shadow:
    0 0 0 0.5px rgba(13, 16, 14, 0.06),
    0 2px 4px rgba(13, 16, 14, 0.08),
    0 8px 18px -4px rgba(13, 16, 14, 0.14);
}

.pb-ends {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-family: var(--hs-font-mono);
  font-size: var(--hs-text-2xs);
  letter-spacing: var(--hs-tracking-caps);
  color: var(--hs-fg-tertiary);
}
/* The left end is where most people land, so it is a way back, not a label. */
.pb-reset {
  font: inherit;
  letter-spacing: inherit;
  color: var(--hs-accent-ink);
  background: none;
  border: 0;
  padding: 2px var(--hs-space-2);
  margin-left: calc(var(--hs-space-2) * -1);
  border-radius: var(--hs-radius-pill);
  cursor: pointer;
}
.pb-reset:hover { background: var(--hs-accent-muted); }
.pb-reset:focus-visible { outline: var(--hs-size-focus-ring) solid var(--hs-border-focus); outline-offset: 2px; }
.pb-reset[disabled] { color: var(--hs-fg-tertiary); cursor: default; background: none; }

/* The balance is the card's big figure. With the price in the pill, printing it
   large again above the bar would be the repetition this card exists to end. */
.doc p.pb-cr {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: var(--hs-space-2);
  font-family: var(--hs-font-display);
  font-size: var(--hs-text-4xl);
  font-weight: var(--hs-weight-bold);
  letter-spacing: var(--hs-tracking-tighter);
  line-height: var(--hs-leading-none);
  font-variant-numeric: tabular-nums;
}
.pb-cr i {
  font-family: var(--hs-font-ui);
  font-style: normal;
  font-size: var(--hs-text-lg);
  font-weight: var(--hs-weight-semibold);
  letter-spacing: var(--hs-tracking-normal);
  color: var(--hs-accent-ink);
}

/* The rate line and the let's-talk line share one grid cell and toggle with
   visibility, so the card never resizes under the cursor at the ceiling. */
.pb-cap-slot { display: grid; min-height: 40px; }
.doc p.pb-cap, .doc p.pb-chat {
  grid-area: 1 / 1;
  margin: 0;
  align-self: start;
  transition: opacity var(--hs-duration-base) var(--hs-ease-standard);
}
.doc p.pb-cap {
  font-size: var(--hs-text-sm);
  color: var(--hs-fg-secondary);
  line-height: var(--hs-leading-normal);
}
.pb-cap b { color: var(--hs-fg-primary); font-weight: var(--hs-weight-semibold); font-variant-numeric: tabular-nums; }
.pb-cap.is-off, .pb-chat.is-off { opacity: 0; visibility: hidden; pointer-events: none; }
.pb-chat-btn {
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--hs-space-3) var(--hs-space-4);
  border: var(--hs-size-hairline) solid var(--hs-accent-ring);
  border-radius: var(--hs-radius-md);
  background: var(--hs-accent-subtle);
  color: var(--hs-accent-ink);
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-sm);
  font-weight: var(--hs-weight-medium);
  line-height: var(--hs-leading-snug);
  cursor: pointer;
}
.pb-chat-btn b { font-weight: var(--hs-weight-bold); text-decoration: underline; text-underline-offset: 3px; }
.pb-chat-btn:hover { background: var(--hs-accent-muted); }
.pb-chat-btn:focus-visible { outline: var(--hs-size-focus-ring) solid var(--hs-border-focus); outline-offset: 2px; }

/* The mix, underneath. No heading: the hairline says it -- above is what you
   pay, below is what that is. */
.pb-buys {
  border-top: var(--hs-size-hairline) solid var(--hs-accent-ring);
  padding-top: var(--hs-space-5);
}
.pb-row {
  display: grid;
  grid-template-columns: 1fr auto 1fr auto 1fr;
  align-items: baseline;
  gap: var(--hs-space-3);
}
.pb-cell {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* Left, not centred. Centred cells of unequal width put the figures at
     arbitrary intervals; a common left edge per cell lines the numbers up with
     their own labels and reads as a row you scan. */
  text-align: left;
  align-items: flex-start;
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-sm);
  color: var(--hs-fg-secondary);
  line-height: var(--hs-leading-snug);
}
.pb-cell b {
  font-family: var(--hs-font-display);
  font-size: var(--hs-text-3xl);
  font-weight: var(--hs-weight-bold);
  letter-spacing: var(--hs-tracking-tighter);
  color: var(--hs-fg-primary);
  font-variant-numeric: tabular-nums;
  line-height: var(--hs-leading-none);
}
/* The only thing stopping three figures reading as a total. */
.pb-or {
  font-family: var(--hs-font-ui);
  font-size: var(--hs-text-sm);
  color: var(--hs-fg-disabled);
  padding-inline: var(--hs-space-1);
}

/* The rate table is its own card behind a centred line: it answers a different
   question, and it is a table, not a price. */
.pb-rate { margin: 0; }
.pb-rate-sum {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--hs-space-2);
  cursor: pointer;
  color: var(--hs-fg-tertiary);
  font-family: var(--hs-font-mono);
  font-size: var(--hs-text-2xs);
  letter-spacing: var(--hs-tracking-caps);
  text-transform: uppercase;
  list-style: none;
  padding: var(--hs-space-2) 0;
  border-radius: var(--hs-radius-md);
}
.pb-rate-sum::-webkit-details-marker { display: none; }
.pb-rate-sum:hover { color: var(--hs-accent-ink); }
.pb-rate-sum:focus-visible { outline: var(--hs-size-focus-ring) solid var(--hs-border-focus); outline-offset: 2px; }
.pb-rate-sum svg { transition: transform var(--hs-duration-fast) var(--hs-ease-standard); }
.pb-rate[open] .pb-rate-sum svg { transform: rotate(180deg); }
.pb-rate-card {
  background: var(--hs-bg-raised);
  border: var(--hs-size-hairline) solid var(--hs-border-default);
  border-radius: var(--hs-radius-lg);
  box-shadow: var(--hs-elevation-raised);
  padding: var(--hs-space-6);
  margin-top: var(--hs-space-1);
}
.doc .pb-rate-card p {
  margin: 0 0 var(--hs-space-4);
  font-size: var(--hs-text-sm);
  color: var(--hs-fg-secondary);
  line-height: var(--hs-leading-normal);
}
.pb-rate-card p b { color: var(--hs-fg-primary); font-weight: var(--hs-weight-semibold); }
.doc .pb-rate-card p.pb-rate-fine {
  margin: var(--hs-space-4) 0 0;
  font-size: var(--hs-text-2xs);
  color: var(--hs-fg-tertiary);
}

@media (max-width: 560px) {
  .pb-row { grid-template-columns: minmax(0, 1fr); gap: var(--hs-space-2); }
  .pb-cell { flex-direction: row; align-items: baseline; gap: var(--hs-space-3); }
  /* A fixed box with the figure left-aligned inside it: the numbers share a
     left edge and so do the labels. Sized for the widest figure the ladder
     prints stacked (1,800), in tabular digits. */
  .pb-cell b { font-size: var(--hs-text-2xl); min-width: 5ch; }
  .pb-or { padding-inline: 0; }
  .pb-well { --pb-tabw: 7rem; }
  .pb-rate-card .usage-eq { display: none; }
}
