/* ==========================================================================
   Tally — marketing site stylesheet
   Brand: Lufga (display + body), Caveat (handwritten accents)
   Palette is defined once in :root and used everywhere; do not hardcode hexes.
   ========================================================================== */

/* ---------- FONTS (self-hosted, see README for licensing note) ---------- */
@font-face{font-family:'Lufga'; src:url('../fonts/lufga-400.woff2') format('woff2');
  font-weight:400; font-style:normal; font-display:swap;}
@font-face{font-family:'Lufga'; src:url('../fonts/lufga-500.woff2') format('woff2');
  font-weight:500; font-style:normal; font-display:swap;}
@font-face{font-family:'Lufga'; src:url('../fonts/lufga-600.woff2') format('woff2');
  font-weight:600; font-style:normal; font-display:swap;}
@font-face{font-family:'Lufga'; src:url('../fonts/lufga-700.woff2') format('woff2');
  font-weight:700; font-style:normal; font-display:swap;}
@font-face{font-family:'Lufga'; src:url('../fonts/lufga-800.woff2') format('woff2');
  font-weight:800; font-style:normal; font-display:swap;}
@font-face{font-family:'Caveat'; src:url('../fonts/caveat-700.woff2') format('woff2');
  font-weight:700; font-style:normal; font-display:swap;}

:root{
  --blue:#006dff; --mint:#00fada; --mint-ink:#00a884; --orange:#ff6400; --navy:#070e1a; --sky:#ebf0f7;
  --ink:#1a2b45; --grey:#c3ccdb; --ease:cubic-bezier(.22,.9,.28,1);
  --text-muted:#6b7488; --text-secondary:#c7d0e6;
  /* Four near-identical darks and three tints had been hardcoded across the
     stylesheet, the inline panel styles and main.js. Named here so the house
     rule ("the palette lives in :root") actually holds and so the dark
     sections stop drifting a few points apart from each other. */
  --navy-soft:#0c1220;   /* footer — one step up from --navy so the CTA above it still separates */
  --navy-panel:#101a30;  /* raised surfaces on a dark ground */
  --blue-soft:#4d8dff;   /* doodles and arrows on dark */
  --orange-soft:#ff8a3d; /* the lead accent on dark, where --orange vibrates */
  --slate:#8b93a8;       /* muted label text on dark */
  /* #eef1f6 is the literal this token was created to replace — the commit that
     introduced it (1a35254) wrote `var(--hairline)` as its own value, which is
     a cycle, so the property resolved to nothing and all 18 uses fell back to
     currentColor or transparent: the nav lost its bottom rule, the compare rows
     and the demo step connector went invisible, and .demo-card drew a navy
     border where a hairline was intended. */
  --hairline:#eef1f6;    /* every light-ground divider */
  --page-bg:#e4e9f2;
  --font-display:'Lufga','Outfit','Poppins',system-ui,-apple-system,'Segoe UI',sans-serif;
  --font-hand:'Caveat','Segoe Script',cursive;
  --radius-card:32px;
  /* Two section-heading sizes, and only two. Ten headings used to carry eight
     different clamp() triplets — three minimums, four vw slopes, six maximums
     — with no rule saying which got which, so nothing in the type told a
     reader which sections were the main event.
     --h-display  the pinned narrative sections that carry the story, plus the
                  closing CTA.
     --h-section  every other section heading.
     The companion rule is alignment, and it is about axes, not taste: a
     heading centres when the block beneath it is itself horizontally centred
     (a `margin:0 auto` grid of equal items — features, benefits, pricing,
     integrations, compare, the demo stage, the FAQ list), and aligns left when
     the block beneath it starts at the section's content edge (business, cta,
     you-talk). A left heading over a centred grid is the "some are in between"
     mismatch this rule exists to kill. Do not add a third size — if a heading
     to feel bigger than its neighbours, it wants the display tier or it wants
     a different section treatment. */
  --h-display:clamp(32px,4.6vw,60px);
  --h-section:clamp(28px,4.2vw,46px);
  /* The companion pair to the two sizes above: one box for every centred
     section title, one for the line underneath it. The six title boxes used to
     be 600, 640, 640, 720, 720 and 820px wide with nothing behind the numbers,
     so where a headline broke had nothing to do with how long it was —
     "Questions people actually ask." dropped "ask." onto its own line in a
     600px box while a longer headline sat on one line in a 720px one.
     940px clears the longest headline on the page ("You communicate, Tally
     handles the rest.", 892px at the 46px cap) with room for a font swap, and
     --h-section's clamp shrinks the type in step with the viewport below that,
     so every one of them holds a single line down to ~660px wide.
     A heading that breaks on purpose carries a <br> and says why (#features).
     Do not reintroduce a per-section max-width on top of these. */
  --head-max:940px;
  --head-sub:640px;
  --focus:#ff6400;
  /* Height of the sticky nav. It sits in normal flow, so a 100svh hero below it
     overflows the viewport by exactly this much unless it is subtracted. Driven
     by the CTA button (38px) plus 14px padding either side plus the 1px border;
     keep it in step if that button's padding changes. */
  --nav-h:67px;
}

*{box-sizing:border-box; margin:0; padding:0;}
html{scroll-behavior:smooth; -webkit-text-size-adjust:100%;}
body{font-family:var(--font-display); color:var(--navy); background:var(--page-bg); overflow-x:hidden;
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;}
img,svg{max-width:100%;}
h1,h2,h3{font-weight:800; line-height:1.1;}
a{text-decoration:none; color:inherit;}
button{font:inherit;}
/* No explicit width: sections are block-level and fill their container. Adding
   width:100% here would fight the 16px side margins on .card-section and push
   a horizontal scrollbar onto the document. */
section{position:relative;}

/* Visible keyboard focus everywhere — the original had none. */
:focus-visible{outline:3px solid var(--focus); outline-offset:3px; border-radius:4px;}
.skip-link{position:absolute; left:-9999px; top:0; z-index:999; background:var(--navy); color:#fff;
  padding:12px 20px; border-radius:0 0 12px 0; font-weight:700;}
.skip-link:focus{left:0;}
.sr-only{position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden;
  clip:rect(0,0,0,0); white-space:nowrap; border:0;}

.card-section{border-radius:var(--radius-card); margin:0 16px 16px; overflow:hidden;}

/* ---------- SECTION TIERS ----------
   The page runs in three tiers, and the whole point is that a visitor can tell
   which is which without reading a word. Before this, six white card sections
   ran back to back with four different vertical paddings (110/100, 100/96,
   90/84, 100/96) and identical everything else, so #features looked exactly as
   important as the demo.

   PRIMARY — the narrative, in the order it is told: #you-talk (what it feels
     like), #who-its-for (who it is for), #how-it-works (how), #try-it (watch
     it happen). The first three are full-height pinned scroll tracks; #try-it
     keeps the generous 110px/100px card padding. Headline sizing for the two
     that carry an HTML heading is --h-display.
   SUPPORTING — #features, #integrations, #compare, #benefits. White ground,
     --h-section heading, and the tighter 84px/78px rhythm set below, shared by
     all four so they read as one block of detail rather than four more
     main events.
   COMMERCIAL — #pricing, #business, #faq, #cta keep the generous padding;
     they are destinations, not scenery.

   Two things this tiering does NOT do, both flagged to the owner rather than
   guessed at: #who-its-for keeps its blue full-bleed pinned treatment (the
   blue IS the section — it is a 230vh scroll-driven SVG, not a card that can
   be repainted white), and #try-it keeps its white ground (its three demo
   panels are built on #fff and --sky, so a coloured ground means rebuilding
   them, not restyling a wrapper). */

/* The tall scroll tracks need the SAME inset rounded-card treatment as
   .card-section, or the page alternates between edge-to-edge sections and
   floating cards and reads as two different layouts stitched together.
   They cannot just use .card-section: `overflow:hidden` on an ancestor kills
   `position:sticky` for the pinned panel inside. So the inset and the radius
   live here, and the clipping stays on the sticky child, which already has
   `overflow:hidden` of its own and is not an ancestor of itself. */
.track-section{margin:0 16px 16px; border-radius:var(--radius-card);}
.track-section > .fly-sticky,
.track-section > .hiw-sticky{border-radius:var(--radius-card);}
.kicker{font-size:13px; font-weight:700; letter-spacing:2px; text-transform:uppercase; color:var(--grey);}

/* The eyebrow over a centred section head. One rule for the four sections that
   carry one, because it was the same six declarations copied three times over
   and about to be copied a fourth for #features. Distinct from .kicker above,
   which is the pinned tracks' own eyebrow and sits on --grey. */
.demo-kicker,.integrations-kicker,.benefits-kicker,.feat-kicker{
  font-size:13px; font-weight:700; letter-spacing:2px; text-transform:uppercase;
  color:var(--text-muted); margin-bottom:10px;}

/* Every centred section headline, on the one box set by --head-max above.
   text-wrap:balance is the safety net under it: at the widths where a headline
   genuinely has to break — narrow phones, mostly — it splits the line evenly
   instead of dropping a last word on its own. Browsers that don't know the
   property ignore it and get the ordinary greedy break, which is what the page
   did before, so nothing regresses. */
.demo-head h2,.feat-head h2,.integrations-head h2,.benefits-head h2,
.pricing-head h2,.faq-head h2,.compare h2{text-wrap:balance;}
/* Same treatment for the line under the headline and the footnotes that follow
   the same centred axis. These run two lines often enough that a greedy break
   was leaving one word centred under a full line — the same orphan the boxes
   above were creating, one type size down. */
.demo-sub,.integrations-sub,.benefits-sub,.pricing-head p,.faq-head p,
.integrations-note,.pricing-note{text-wrap:balance;}

/* And the headings inside the cards, which have the same problem one tier
   down. Every card in a grid is already exactly as wide as its siblings, so
   there was nothing to unify there — the orphans were purely the greedy
   break: at the two- and three-column widths "LinkedIn fills in the rest"
   dropped "rest" onto its own line, "It asks for what's missing" dropped
   "missing", and "Your fields, mapped to your CRM" dropped "CRM". */
.feat-card h3,.feat-mini h4,.dep-label,.plan-name,.biz-form h3{text-wrap:balance;}


/* ---------- BUTTONS ---------- */
.btn-primary{display:inline-block; background:var(--orange); color:#fff; font-weight:700; font-size:15px;
  padding:14px 30px; border-radius:30px; border:none; cursor:pointer; text-align:center;
  transition:transform .15s ease, box-shadow .15s ease, background .15s ease;}
.btn-primary:hover{transform:translateY(-2px); box-shadow:0 10px 24px rgba(255,100,0,.35);}
.btn-primary:disabled{opacity:.55; cursor:not-allowed; transform:none; box-shadow:none;}
.btn-outline{display:inline-block; background:transparent; color:var(--navy); font-weight:700; font-size:15px;
  padding:13px 28px; border-radius:30px; border:1.5px solid var(--navy); cursor:pointer; text-align:center;
  transition:background .15s ease, color .15s ease;}
.btn-outline:hover{background:var(--navy); color:#fff;}

/* ---------- NAV ---------- */
nav{position:sticky; top:0; z-index:200; display:flex; align-items:center; justify-content:center; gap:40px;
  background:rgba(255,255,255,.9); backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  padding:14px 32px; border-bottom:1px solid var(--hairline);}
.logo{display:flex; align-items:center; margin-right:8px; flex-shrink:0;}
.logo img{height:26px; width:auto; display:block;}
.navlinks{display:flex; gap:30px; font-size:14px; font-weight:600; color:var(--ink);}
.navlinks a{transition:color .2s ease;}
.navlinks a:hover{color:var(--blue);}
nav .btn-primary{padding:10px 22px; font-size:14px; white-space:nowrap;}

/* "Log in" — the way back into the console for someone who already has an
   account. A soft --sky pill rather than a second bright button: it has to be
   findable at a glance without competing with the orange CTA beside it.
   line-height:1 is load-bearing, not tidiness. The nav's height is not free:
   --nav-h is derived from the CTA button (14px text + 10px padding either
   side) and .hero, .hiw-sticky and .wif-sticky all subtract it. With the
   inherited line-height this pill computes ~1px taller than the button, which
   grows the nav while --nav-h stays put and pushes the hero past the
   viewport. Pinned to 1 and padded to 12px, it is exactly 38px — the button's
   height, never more. Change the font size here and re-check the padding. */
.btn-login{display:inline-flex; align-items:center; background:var(--sky); color:var(--navy);
  font-weight:700; font-size:14px; line-height:1; padding:12px 20px; border-radius:30px;
  white-space:nowrap; flex-shrink:0; transition:background .15s ease, color .15s ease;}
.btn-login:hover{background:var(--navy); color:#fff;}

.nav-toggle{display:none; background:none; border:none; cursor:pointer; padding:8px; color:var(--navy);}
.nav-toggle svg{display:block;}

/* ---------- SHARED SVG WRAPPERS ---------- */
/* Fills its sticky parent's content box rather than the whole viewport, so the
   nav-height padding on .wif-sticky actually takes height away from the SVG
   instead of being overrun by a hardcoded 100vh. */
.svg-wrap{width:100%; height:100%; display:block;}
/* Do NOT give these SVGs a background — the section behind them already carries
   the colour, and a full-bleed background on the SVG paints its own square
   corners straight over the rounded ones of .track-section. That is exactly
   what made the "Who it's for" and "You talk" sections look like square blocks
   butting into rounded cards. The SVG must stay transparent so the section's
   own rounded background shows through. */
.svg-wrap svg{width:100%; height:100%; display:block; background:transparent;}

/* ---------- WORD-DOODLE HOVER (bold -> handwritten) ---------- */
.tw{position:relative; display:inline-block; white-space:pre;}
.tw .tw-b{display:inline-block; transition:opacity .22s ease, transform .22s ease;}
.tw .tw-d{position:absolute; left:50%; top:50%; transform:translate(-50%,-50%) scale(.7) rotate(-3deg); opacity:0;
  font-family:var(--font-hand); font-weight:700; white-space:nowrap; pointer-events:none; color:var(--mint);
  transition:opacity .22s ease, transform .22s ease;}
.tw:hover .tw-b{opacity:0; transform:scale(.85);}
.tw:hover .tw-d{opacity:1; transform:translate(-50%,-50%) scale(1) rotate(-2deg);}
.hero .tw .tw-d, .wif .tw .tw-d, .benefits .tw .tw-d, footer .tw .tw-d{color:var(--mint);}
.compare .tw .tw-d, .faq .tw .tw-d,
.pricing .tw .tw-d, .biz .tw .tw-d{color:var(--blue);}
/* The how-it-works steps are the one place a fixed doodle colour cannot work:
   the panel cycles through six backgrounds under the same word, so any single
   accent collides with at least one of them. Blue was the accent and blue is
   the "Extract" background, which made that word vanish into the panel on
   hover; the mint-ink and orange steps were legible but sat near 1.6:1. Each
   step picks its own accent beside its background below — this rule only
   reads whichever one is currently live. */
.hiw-panel .tw .tw-d{color:var(--doodle, var(--blue));}

/* ---------- HERO ---------- */
/* Column layout, not a centred flex box: the copy sits in the upper area and
   the conveyor is pushed to the bottom by margin-top:auto. Reserving the stage's
   space with padding instead made the hero's height content-driven, so it
   overshot the viewport whenever the copy wrapped to an extra line. */
.hero{min-height:calc(100svh - var(--nav-h)); background:var(--blue); display:flex; flex-direction:column;
  align-items:stretch; position:relative; overflow:hidden; padding:clamp(72px,10vh,124px) 6vw 0;
  text-align:center;}
/* Auto margins on all four sides: horizontally it centres the 880px column,
   vertically it takes whatever space the conveyor has not claimed and splits it
   evenly, so the copy sits centred above the stage rather than pinned under the
   nav with a growing gap beneath it on tall screens. */
.hero-inner{position:relative; z-index:2; max-width:880px; margin:auto;}
.hero .rise{opacity:0; transform:translateY(22px); animation:riseIn .8s var(--ease) forwards;}
@keyframes riseIn{to{opacity:1; transform:translateY(0);}}
/* Telegram-as-interface cue, above the headline: the first thing read, so the
   "it lives in Telegram" story lands before the pitch does. Not a CTA — sign-up
   is on the web; this only names the surface you capture in. */
/* Orange-tinted, not another white-on-blue whisper. At 13.5px in a 12%-white
   pill it disappeared into the hero — the one line naming the surface you
   actually capture in was the least visible thing on the screen. It stays a
   translucent tint rather than a solid orange fill on purpose: .btn-primary
   directly below it IS solid orange, and two solid orange pills stacked make
   the eyebrow read as the call to action. */
.hero-eyebrow{
  display:inline-flex; align-items:center; gap:9px; margin-bottom:24px;
  padding:9px 18px 9px 14px; border-radius:100px;
  background:rgba(255,100,0,.22); border:1.5px solid var(--orange-soft);
  color:#fff; font-size:15px; font-weight:600; letter-spacing:.2px;
  box-shadow:0 6px 20px rgba(255,100,0,.22);
}
.hero-eyebrow svg{width:17px; height:17px; flex:none; color:var(--orange-soft);}
.hero-eyebrow b{color:var(--orange-soft); font-weight:800;}
.hero-head{color:#fff; font-size:clamp(32px,5.4vw,58px); font-weight:800; line-height:1.12; margin-bottom:22px;}
.hero-head span.tw-line{display:block;}
.hero-sub{color:#eaf1ff; font-size:clamp(16px,1.8vw,20px); font-weight:500; max-width:640px;
  margin:0 auto 34px; line-height:1.5;}
.hero-btns{display:flex; gap:16px; justify-content:center; flex-wrap:wrap;}
.hero-btns .btn-outline{border-color:#fff; color:#fff;}
.hero-btns .btn-outline:hover{background:#fff; color:var(--blue);}
.hero-note{margin-top:20px; font-size:13.5px; color:#bcd6ff; font-weight:500;}

/* ==========================================================================
   HERO CONVEYOR — skeleton capture in, structured lead out
   --------------------------------------------------------------------------
   One shared cycle (--cv-dur) drives everything. Per lane, the skeleton card
   owns the first half of the timeline (drift in, dissolve at the beam) and the
   lead card owns the second (emerge from the beam, drift out). Because both
   read the same --delay, they are two halves of one journey and can never
   drift apart — there is no script keeping them in step.

   Three lanes — one per input type — staggered by exactly a third of the cycle,
   so something reaches the gate every --cv-dur/3, which is also the gate pulse's
   period. Three rather than four because a card is ~120px tall and the stage is
   ~340px: a fourth lane could not sit anywhere without its top or bottom being
   eaten by the edge mask.
   ========================================================================== */
.conveyor{
  /* 16s originally, and it read as slow — a lane sat empty long enough that
     the stage looked stalled between cards. Everything else in this block is
     a percentage of this value or --cv-dur/3, so this one number is the only
     speed control; do not retime the keyframes to go faster.

     The floor is the finished lead card's readable window, which is 58%-88%
     of the cycle. This is now at the floor that window can take: 56%-82% of
     8s is ~2.1s of the finished card fully legible, down from 4.8s at the
     original 16s. Raul asked for faster twice, and for less pause with it, so
     the trade is deliberate — but do not shorten this further without also
     widening the readable stops in cvOut, or the four fields stop being
     readable at a glance and the stage loses the thing it exists to show. */
  --cv-dur:8s;
  --cv-skel-w:158px;
  --cv-lead-w:216px;
  /* Full-bleed inside the hero's 6vw padding, because the card keyframes
     measure in vw and assume 50vw is the beam. */
  position:relative; margin:0 -6vw 0; width:100vw; z-index:1;
  height:clamp(250px,38vh,380px);
  pointer-events:none;
  /* Soft top edge so cards fade under the copy instead of colliding with it,
     and a soft bottom edge into the section boundary. */
  -webkit-mask-image:linear-gradient(to bottom,transparent 0%,#000 5%,#000 93%,transparent 100%);
  mask-image:linear-gradient(to bottom,transparent 0%,#000 5%,#000 93%,transparent 100%);
  /* mask-repeat defaults to `repeat`: without this the gradient tiles past the
     box and a card that has travelled beyond the stage is painted solid again
     by the next tile instead of being clipped. */
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
}

.cv-lane{position:absolute; left:0; top:var(--lane); width:100%; height:0;}
/* A dashed rail per lane, echoing the dashed flight path in the section below.
   It gives the cards something to travel along and stops the stage reading as
   empty at the moments when a lane happens to be between cards. */
.cv-lane::before{
  content:''; position:absolute; left:0; right:0; top:0; height:1px;
  background:repeating-linear-gradient(to right,
    rgba(255,255,255,.30) 0 7px, transparent 7px 15px);
  -webkit-mask-image:linear-gradient(to right,transparent,#000 12%,#000 88%,transparent);
  mask-image:linear-gradient(to right,transparent,#000 12%,#000 88%,transparent);
}

/* ---------- the two travelling cards ---------- */
.cv-skel,.cv-lead{
  position:absolute; left:0; top:0; opacity:0;
  transform:translate(-34vw,-50%);
  animation-duration:var(--cv-dur);
  animation-timing-function:linear;
  animation-iteration-count:infinite;
  animation-delay:var(--delay);
  will-change:transform,opacity;
}
.cv-skel{width:var(--cv-skel-w); animation-name:cvIn;}
.cv-lead{width:var(--cv-lead-w); animation-name:cvOut;}

/* translateX is in vw because the hero is full-bleed, so 50vw is exactly the
   beam; the -50% pulls the card back by half its own width to centre it there. */
@keyframes cvIn{
  /* Whoosh in and DECELERATE to a readable "presented" pose at 30% (easeOut),
     then ACCELERATE into the beam (easeIn). The two segments meet at ~zero
     velocity, so there is no hitch — the card glides to a stop and gets sucked
     in, instead of sliding past at a flat, mechanical belt speed.

     THE EXIT EASE IS cubic-bezier(.4,0,.7,.25), NOT THE .5,0,.75,0 THIS USED
     TO CARRY. That curve pins its output near zero for over half its segment,
     so a settled card genuinely stood still for a beat before moving — which
     is what "too slow, less pause" was about, and it is a separate lever from
     --cv-dur. Shortening the cycle alone scales the standstill down with
     everything else and keeps the dead feel; this starts the card moving
     almost as soon as it has been read. Both cvIn and cvOut use it, and so do
     their portrait counterparts, so the whole stage shares one exit
     character. */
  0%   {transform:translate(-34vw,calc(-50% - 6px)); opacity:0; animation-timing-function:cubic-bezier(.33,1,.68,1);}
  5%   {opacity:1;}
  30%  {transform:translate(22vw,calc(-50% + 5px)); opacity:1; animation-timing-function:cubic-bezier(.4,0,.7,.25);}
  44%  {opacity:.85;}
  /* Dissolves INTO the beam rather than beside it: it shrinks and desaturates
     over the last stretch so the gate reads as consuming it. */
  50%  {transform:translate(calc(50vw - 50%),-50%) scale(.86); opacity:0;}
  100% {transform:translate(calc(50vw - 50%),-50%) scale(.86); opacity:0;}
}
/* Born centred on the beam but invisible, then clear of the badge by the time
   it is readable — offsetting by the badge's own half-width plus a margin, so
   the card never sits on top of the mark it is supposed to have come out of. */
@keyframes cvOut{
  /* Mirror of cvIn: BURST out of the beam and decelerate to a readable pose
     (easeOut), then ACCELERATE away under the edge mask (easeIn). Emerging
     fast and settling reads as "produced", where a flat belt speed made the
     finished card look like it was just sliding by.

     56%-82% is the whole readable life of the finished card, ~2.1s at 8s, and
     it is the tightest constraint on the stage — see the note on --cv-dur.
     The "linger a beat" this comment used to describe is gone on purpose. */
  0%   {transform:translate(calc(50vw - 50%),-50%) scale(.9); opacity:0;}
  50%  {transform:translate(calc(50vw - 50%),-50%) scale(.9); opacity:0; animation-timing-function:cubic-bezier(.33,1,.68,1);}
  56%  {transform:translate(calc(50vw + 34px),calc(-50% - 3px)) scale(1); opacity:1; animation-timing-function:cubic-bezier(.4,0,.7,.25);}
  82%  {opacity:1;}
  100% {transform:translate(106vw,calc(-50% + 4px)) scale(1); opacity:0;}
}

/* Portrait counterparts. translateX(-50%) centres the card on the single
   column; the Y target is the stage's own midpoint, where the gate sits. */
/* PORTRAIT TIMING IS NOT THE LANDSCAPE TIMING SCALED DOWN — read this before
   touching the percentages.

   Landscape gives each lane its own row, so a lane may occupy half the cycle
   (skeleton 0-50%, lead 50-100%) and simply pass its neighbours side by side.
   Portrait collapses all three lanes onto ONE centre column, and the lanes are
   staggered by only a third of the cycle — so a half-cycle window means lane
   A's outgoing lead card is still in the column when lane B's skeleton arrives,
   and they overlap into an unreadable pile.

   So here each lane's whole journey is squeezed into its OWN third: skeleton
   0-16.5%, lead 16.5-33%, then parked and invisible for the remaining 67% while
   the other two lanes take their turns. Exactly one card is ever in the column.
   --cv-dur stays LONGER than landscape's to compensate — 10s against 8s —
   which leaves ~1.7s of travel for the skeleton and ~1.7s for the lead.

   That is why portrait was not cut in the same proportion as landscape: a
   lane owns a third of the cycle here against landscape's half, so the same
   percentage cut costs it more real time, and at 10s that beat is already
   the shortest thing on the stage.

   The invariant: the lead must pick up at exactly the percentage the skeleton
   dissolves at (16.5%), and nothing may still be visible at 33%. */
@keyframes cvInV{
  /* Starts just above the stage, not far above it: the mask clips anything
     outside the box, so a bigger head start is time the card spends invisible
     rather than visibly travelling.

     EVERY Y HERE IS MEASURED OFF THE CARD'S OWN BOTTOM EDGE (the -100%/-50%
     terms are percentages of the element's own height), NOT off the stage.
     That is deliberate, and it is what the previous version got wrong: it held
     its readable pose at `--cv-h * .30`, a stage fraction, and then targeted
     `--cv-gy - 50%`, a card fraction. At 375x812 those both land on ~80px, so
     the last leg had zero travel and the card sat motionless on top of the
     badge for about a second while it faded. It read as the skeleton stopping
     in the middle of the stage instead of being taken by the gate.

     So: the card presents itself with its bottom edge 26px CLEAR of the gate
     line, which keeps it off the 26px badge, then keeps closing that gap right
     through the dissolve. Travel stays monotonic for every card height in the
     stack (42px, 54px and 55px at the time of writing). If you change
     --cv-gy, the badge size, or the card padding, re-check that 9% is still
     above 16.5% for the SHORTEST card, which is the worst case (its final leg
     is the shortest, being half its own height).

     THE EASING MATTERS AS MUCH AS THE POSITIONS. An easeOut that decelerates
     onto its target and then waits reads as a dead stop, which is the second
     thing to get complained about here. So the pose at 9% is a waypoint, not a
     destination: it is followed by a LINEAR creep to 12% and only then the
     accelerate-into-the-beam curve. The card is never not moving.

     Handoff to cvOutV is a CROSSFADE, not a baton pass. Fading to 0 here and
     only then starting the lead leaves a hole where the stage is empty, which
     is exactly what "there is a gap, it is not seamless" meant. The lead now
     starts lifting at 12.5%, while this card is still at ~55% opacity, so the
     two overlap either side of the beam. Change one of these numbers and you
     have to change the other. */
  0%    {transform:translate(-50%,-56px); opacity:0; animation-timing-function:cubic-bezier(.33,1,.68,1);}
  2.5%  {opacity:1;}
  9%    {transform:translate(-50%,calc(var(--cv-gy) - 100% - 26px)); opacity:1; animation-timing-function:linear;}
  12%   {transform:translate(-50%,calc(var(--cv-gy) - 100% - 8px)); opacity:.6; animation-timing-function:cubic-bezier(.4,0,.7,.25);}
  15.5% {transform:translate(-50%,calc(var(--cv-gy) - 55%)) scale(.9); opacity:0;}
  16.5% {transform:translate(-50%,calc(var(--cv-gy) - 50%)) scale(.86); opacity:0;}
  100%  {transform:translate(-50%,calc(var(--cv-gy) - 50%)) scale(.86); opacity:0;}
}
/* The lead is born just BELOW the gate rather than centred on it. Centred, a
   ~75px card sits squarely on top of the 30px badge for the whole time it is
   readable, which is what made the old version look like a card parked behind
   the logo. Emerging below keeps the badge visible as the thing doing the work
   and reads as output coming out of it. */
/* Picks up at 12.5%, NOT at the 16.5% the skeleton finishes on. The two
   deliberately overlap: for those four points the skeleton is still dissolving
   above the beam while this card is already lifting below it, which is what
   makes the exchange read as one continuous event instead of one card leaving
   and another arriving after a beat of empty stage.
   Still finishes inside its own third. Lane B's skeleton becomes visible at
   this lane's 35.8% (its own 2.5%, one third of a cycle later), and this is
   gone by 32%, so exactly one token is ever in the column. */
@keyframes cvOutV{
  0%    {transform:translate(-50%,calc(var(--cv-gy) + 8px)) scale(.92); opacity:0;}
  12.5% {transform:translate(-50%,calc(var(--cv-gy) + 8px)) scale(.92); opacity:0; animation-timing-function:cubic-bezier(.33,1,.68,1);}
  18%   {transform:translate(-50%,calc(var(--cv-gy) + 18px)) scale(1); opacity:1; animation-timing-function:cubic-bezier(.4,0,.7,.25);}
  28%   {opacity:1;}
  32%   {transform:translate(-50%,calc(var(--cv-h) + 26px)) scale(1); opacity:0;}
  100%  {transform:translate(-50%,calc(var(--cv-h) + 26px)) scale(1); opacity:0;}
}

/* ---------- skeleton card: unprocessed, ghostly, still loading ---------- */
.cv-skel{
  background:rgba(255,255,255,.09);
  border:1.5px dashed rgba(255,255,255,.28);
  border-radius:14px; padding:11px 12px 13px;
  /* No backdrop-filter: blurring the backdrop every frame while the card is
     translating is the priciest thing the compositor could be asked to do here
     and the likeliest source of dropped frames. The card reads fine on the dark
     hero from its own translucent fill; the blur bought almost nothing. */
  overflow:hidden;
}
/* One sweep per card rather than one per bar — a translating pseudo-element is
   compositor-friendly, where animating background-position on a dozen bars
   would repaint each of them every frame. */
.cv-skel::after{
  content:''; position:absolute; inset:0;
  background:linear-gradient(105deg,transparent 30%,rgba(255,255,255,.22) 50%,transparent 70%);
  transform:translateX(-100%);
  animation:cvShimmer 1.9s linear infinite;
}
@keyframes cvShimmer{to{transform:translateX(100%);}}

.cv-skel-head{display:flex; align-items:center; justify-content:space-between; gap:8px; margin-bottom:9px;}
.cv-chip{
  font-size:9.5px; font-weight:700; letter-spacing:.6px; text-transform:uppercase;
  color:rgba(255,255,255,.72); border:1px solid rgba(255,255,255,.24);
  border-radius:20px; padding:2px 8px; white-space:nowrap;
}
.cv-dur{font-size:9.5px; font-weight:600; color:rgba(255,255,255,.5); font-variant-numeric:tabular-nums;}

.cv-bar{height:7px; border-radius:4px; background:rgba(255,255,255,.2); width:var(--w); margin-top:7px;}

.cv-wave{display:flex; align-items:center; gap:3px; height:30px;}
.cv-wave i{flex:1; height:var(--h); min-height:3px; border-radius:2px; background:rgba(255,255,255,.26);}

.cv-img{
  height:52px; border-radius:9px; background:rgba(255,255,255,.14);
  display:flex; align-items:center; justify-content:center; color:rgba(255,255,255,.42);
}

/* ---------- lead card: solid, finished, legible ---------- */
.cv-lead{
  background:#fff; color:var(--navy);
  border-radius:14px; padding:11px 13px 12px;
  box-shadow:0 18px 40px rgba(3,20,55,.28);
}
.cv-lead-top{
  display:flex; align-items:center; gap:6px;
  font-size:10px; font-weight:800; letter-spacing:.7px; text-transform:uppercase;
  color:var(--mint-ink); margin-bottom:9px;
}
.cv-fields{display:flex; flex-direction:column; gap:5px;}
.cv-fields div{display:flex; align-items:baseline; gap:8px;}
.cv-fields dt{
  flex:0 0 62px; font-size:9.5px; font-weight:700; letter-spacing:.4px;
  text-transform:uppercase; color:var(--text-muted);
}
.cv-fields dd{font-size:12px; font-weight:700; line-height:1.25; color:var(--navy);}
.cv-next{
  margin-top:9px; padding-top:8px; border-top:1px solid var(--hairline);
  font-size:10.5px; font-weight:700; color:var(--orange);
}

/* ---------- the gate ---------- */
.cv-gate{position:absolute; left:50%; top:0; bottom:0; width:0;}
.cv-beam{
  position:absolute; left:50%; top:0; bottom:0; width:2px; transform:translateX(-50%);
  background:linear-gradient(to bottom,
    transparent 0%, rgba(255,255,255,.5) 14%, #fff 50%, rgba(255,255,255,.5) 86%, transparent 100%);
  box-shadow:0 0 34px 7px rgba(0,250,218,.34), 0 0 90px 24px rgba(0,250,218,.14);
}
/* Widening halo that swells as each capture is absorbed. */
.cv-pulse{
  position:absolute; left:50%; top:50%; width:132px; height:132px;
  transform:translate(-50%,-50%) scale(.6);
  border-radius:50%;
  background:radial-gradient(circle,rgba(0,250,218,.32) 0%,rgba(0,250,218,.10) 42%,transparent 70%);
  animation:cvPulse calc(var(--cv-dur)/3) ease-out infinite;
}
@keyframes cvPulse{
  0%   {transform:translate(-50%,-50%) scale(.55); opacity:0;}
  38%  {opacity:1;}
  100% {transform:translate(-50%,-50%) scale(1.9); opacity:0;}
}
/* White tile carrying the canonical blue glyph, rather than the blue app icon
   this used to hold. On the blue hero ground a blue tile read as blue-on-blue
   and only the glyph inside it showed; white makes the gate a solid node and
   separates it from the white beam running through it. The padding replaces
   the bleed the app icon had built into its own artwork — t-glyph.svg fills
   its viewBox, so without it the glyph would touch the tile edges. */
.cv-badge{
  position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);
  width:46px; height:46px; border-radius:14px; overflow:hidden;
  background:#fff; padding:8px;
  box-shadow:0 10px 30px rgba(0,0,0,.32);
}
.cv-badge img{width:100%; height:100%; display:block;}

/* ---------- edge captions ---------- */
.cv-caption{
  position:absolute; left:0; right:0; bottom:2px;
  display:flex; justify-content:space-between; padding:0 6vw;
  font-family:var(--font-hand); font-size:21px; font-weight:700; color:rgba(255,255,255,.5);
}

.hero-inner{padding-bottom:12px;}

@media (max-width:900px){
  .conveyor{--cv-skel-w:132px; --cv-lead-w:202px; height:clamp(222px,32vh,300px);}
  .cv-fields dt{flex-basis:52px;}
  /* From here down the card is too narrow for a value to wrap without doubling
     the card's height, which the shorter stage has no room for. Clip instead —
     the widths above are set so nothing actually reaches the ellipsis. */
  .cv-fields dd{white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
  .cv-caption{font-size:17px; padding:0 22px;}
}

@media (max-width:768px){
  /* ---- PORTRAIT: the conveyor turns ninety degrees, and the payload stops
     being a card ----

     Landscape has room for a full skeleton card and a full lead record to pass
     each other in separate rows. Portrait does not, and simply shrinking them
     failed for a reason worth recording: a readable card is ~170px tall (chip
     + waveform + bars, or a tick + three field rows) against a ~250px stage, so
     the card was two thirds of its own runway. It could not travel anywhere. It
     read as a card parked behind the logo rather than as a journey.

     So on a phone the payload is a TOKEN, not a card: identity only, ~34px
     tall, which makes the runway about five times the height of the thing
     crossing it. The detail those cards carried — Company / Contact / Role and
     the next step — is precisely what the "See in action" section now shows at
     a properly readable size, so nothing is lost by not repeating it at 9px
     inside a moving card.

     The three lanes still collapse onto one centre column, and each lane's
     whole journey still fits inside its own third of the cycle (see the cvInV /
     cvOutV note above), so exactly one token is ever in flight. */
  /* Height scales with the viewport instead of being a fixed 236px: the runway
     is whatever is left after the card's own height, so on a taller phone the
     extra pixels go straight into visible travel. Capped at 300px so the hero
     copy above it is never pushed off-screen. */
  /* The gate sits at 40% of the stage, not at 50%. The hero has ~2px of slack
     at 375x812, so the stage cannot grow — the only way to give the lead card
     more runway is to take it from the half that needs less. The skeleton is
     the smaller card and it only has to arrive; the lead is the payoff and has
     to be read while it travels, so it gets the bigger share.
     --cv-gy is the gate's Y and is consumed by BOTH .cv-gate and the
     cvInV/cvOutV keyframes; change it here and everything follows. */
  .conveyor{--cv-skel-w:132px; --cv-lead-w:170px; --cv-dur:10s;
    --cv-h:clamp(238px,33svh,300px); --cv-gy:calc(var(--cv-h) * .40);
    height:var(--cv-h); margin-left:-24px; margin-right:-24px;}

  .cv-lane{left:50%; top:0; width:0; height:100%;}
  /* All three lanes now share one column, so their rails would draw on top of
     each other and read three times as bright as intended. Only the first
     paints it. */
  .cv-lane:not(:first-child)::before{display:none;}
  .cv-lane::before{
    left:0; right:auto; top:0; bottom:0; width:1px; height:auto;
    background:repeating-linear-gradient(to bottom,
      rgba(255,255,255,.30) 0 7px, transparent 7px 15px);
    -webkit-mask-image:linear-gradient(to bottom,transparent,#000 12%,#000 88%,transparent);
    mask-image:linear-gradient(to bottom,transparent,#000 12%,#000 88%,transparent);
  }

  .cv-skel{animation-name:cvInV;}
  .cv-lead{animation-name:cvOutV;}

  /* ---- the two cards, compacted ----
     These stay CARDS, not badges or pills: the whole point of the right-hand
     one is that it shows Company / Contact / Role, which is the benefit being
     claimed. A token reading just "Northstar Gaming" says nothing. So the fix
     is to shrink the chrome — padding, type, row gaps, the decorative extras —
     rather than to throw away the fields. Only the "next step" line goes; the
     three fields carry the story on their own. */
  .cv-skel{padding:6px 8px 7px; border-radius:10px; border-width:1.3px;}
  .cv-skel-head{margin-bottom:5px;}
  .cv-chip{font-size:8px; padding:1px 5px; letter-spacing:.4px;}
  .cv-dur{display:none;}
  .cv-wave{height:14px; gap:2px;}
  .cv-img{height:22px;}
  .cv-bar{height:4px; margin-top:4px;}
  /* The voice lane carries a waveform AND two filler bars. On a stage this
     short the bars are what to lose — the waveform is the part that says
     "this was a voice note". */
  .cv-wave ~ .cv-bar{display:none;}

  .cv-lead{padding:7px 9px 8px; border-radius:10px; box-shadow:0 8px 20px rgba(3,20,55,.3);}
  .cv-lead-top{font-size:8px; margin-bottom:5px; letter-spacing:.4px;}
  .cv-lead-top svg{width:9px; height:9px;}
  .cv-fields{gap:2px;}
  .cv-fields dt{flex-basis:36px; font-size:7.5px; letter-spacing:.2px;}
  .cv-fields dd{font-size:10px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
  .cv-next{display:none;}

  /* ---- the gate ---- */
  .cv-gate{left:0; right:0; top:var(--cv-gy); bottom:auto; width:auto; height:0;}
  .cv-beam{
    left:0; right:0; top:0; bottom:auto; width:auto; height:2px; transform:translateY(-50%);
    background:linear-gradient(to right,
      transparent 0%, rgba(255,255,255,.5) 14%, #fff 50%, rgba(255,255,255,.5) 86%, transparent 100%);
  }
  .cv-pulse{width:84px; height:84px; top:0;}
  /* Deliberately smaller than the token crossing it, so the token reads as
     being consumed by the gate instead of disappearing behind it. */
  .cv-badge{top:0; width:26px; height:26px; border-radius:9px; padding:5px;
    box-shadow:0 8px 20px rgba(0,0,0,.3);}

  /* Captions move to the ends of the new axis, and to opposite sides of it —
     centred they would sit underneath the column the tokens travel down. The
     padding clears the stage's -24px bleed so neither runs off the screen. */
  .cv-caption{inset:0; flex-direction:column; align-items:stretch;
    justify-content:space-between; padding:0 26px; font-size:15px;}
  .cv-caption span:first-child{text-align:left;}
  .cv-caption span:last-child{text-align:right;}
}

@media (max-width:400px){
  .conveyor{--cv-skel-w:124px; --cv-lead-w:160px;}
  .cv-fields dt{flex-basis:38px;}
  .cv-caption{font-size:13.5px; padding:0 20px;}
}


/* Reduced motion: hold the story as one still frame — a capture on the left,
   the gate, the finished record on the right. The point survives without any
   of the movement. */
@media (prefers-reduced-motion:reduce){
  .cv-skel,.cv-lead{animation:none; opacity:1;}
  .cv-skel::after,.cv-pulse{animation:none;}
  .cv-pulse{opacity:.85; transform:translate(-50%,-50%) scale(1);}
  /* Only the first lane is kept. Frozen, all three would rest at the same two
     x positions and overlap into an unreadable stack — it is the horizontal
     stagger, not the lane spacing, that keeps them apart while animating. */
  .cv-lane:not(:first-child){display:none;}
  .cv-skel{transform:translate(13vw,-50%);}
  .cv-lead{transform:translate(62vw,-50%);}
  .cv-lane{top:50%;}
}

/* Reduced motion in portrait needs its own resting positions: the rule above
   parks cards at fixed vw offsets, which is meaningless once all three lanes
   share one centre column. Here the two spare lanes are dropped entirely and
   the remaining pair sits either side of the gate. */
@media (prefers-reduced-motion:reduce) and (max-width:768px){
  .cv-lane:not(:first-child){display:none;}
  .cv-skel{transform:translate(-50%,6px); opacity:1;}
  .cv-lead{transform:translate(-50%,calc(var(--cv-h) / 2 + 22px)); opacity:1;}
}

/* ---------- YOU TALK. TALLY FILES. ---------- */
/* The section is a phone on a pinned track: scroll advances a Telegram
   thread from a voice note to a filed CRM record. It replaced a dashed SVG
   route with the four beats hung off it on measured leader lines — that
   version needed ~90 lines of path geometry in main.js to work out where a
   flexbox bubble sat relative to an SVG curve, because CSS cannot know. The
   phone carries the same "this happens inside Telegram" claim with no
   measurement at all, so main.js now only toggles a class.

   Track height unchanged at 420vh desktop / 260vh mobile: five beats now
   rather than four (the typing indicator is its own beat), but the
   indicator overlaps the gap between two others rather than needing its
   own stretch of scroll. */
.fly-section{min-height:420vh; background:#fff;}

/* Top padding is nav-height-aware, not a flat number: .fly-head-start below
   sits at top:0 of .fly-inner, which otherwise lands the "HOW IT WORKS"
   kicker directly under the sticky nav band and gets clipped the instant
   this section pins. calc(var(--nav-h) + 28px) clears the nav with room to
   spare and stays correct if --nav-h ever changes. */
.fly-sticky{position:sticky; top:0; height:100vh; height:100svh; overflow:hidden;
  display:flex; align-items:center; justify-content:center;
  padding:calc(var(--nav-h) + 28px) 32px 56px;}
/* Centres the phone. The old track was height:100% and filled this box, so
   it never needed the centring and this rule never had it — the phone is
   shorter than the box on most viewports and top-aligned against the "HOW
   IT WORKS" kicker, which is a collision, not a layout. The two headlines
   are position:absolute and out of flow, so flex here only moves the
   phone. */
.fly-inner{position:relative; width:100%; max-width:1180px; height:100%; margin:0 auto;
  display:flex; align-items:center; justify-content:center;}

/* Headlines pinned to opposite corners of the sticky viewport — the
   established "You Talk" / "Tally files" motif this section is named after.
   Positioned against .fly-inner's own box, so they stay put and legible at
   any width without per-breakpoint markup. The phone is centred between
   them and is far narrower than the old track, so the collision this
   section has shipped with before has more clearance now, not less. */
.fly-head-start{position:absolute; top:0; left:0; z-index:2; max-width:56%;}
.fly-head-start .kicker{display:block; margin-bottom:16px;}
.fly-word{font-size:var(--h-display); font-weight:800; line-height:1; margin:0;}
.fly-word-start{color:var(--navy);}
.fly-word-end{position:absolute; bottom:0; right:0; z-index:2; color:var(--orange); text-align:right; max-width:56%;}

/* ---- the phone ----
   Height-first, width from aspect-ratio: the binding constraint is always
   .fly-sticky's height (min() against 100% keeps it inside on a short
   laptop window), and letting the width follow means the frame stays
   phone-shaped at every viewport instead of going squat. Do not swap this
   for a fixed width — that is what makes it letterbox on short screens. */
.fly-phone{position:relative; z-index:1; height:min(600px,100%); aspect-ratio:9/18.5;
  margin:0 auto; display:flex;}
.fly-phone-frame{position:relative; flex:1; display:flex; flex-direction:column;
  background:var(--navy); border-radius:38px; padding:9px; overflow:hidden;
  box-shadow:0 26px 60px rgba(7,14,26,.28), 0 0 0 1px rgba(7,14,26,.06);}
/* Dynamic island. Absolute and on top of the status bar rather than laid out
   with it, so the status row can keep its own simple flex spacing. */
.fly-phone-island{position:absolute; top:16px; left:50%; transform:translateX(-50%);
  width:64px; height:18px; border-radius:10px; background:var(--navy); z-index:3;}

/* Status bar and header are white against the chat's tinted ground, the way
   a real client separates chrome from thread. Same colour for both and the
   header stops reading as chrome at all — which matters here because
   messages scroll UP past it and need something to disappear behind. */
.fly-phone-status{display:flex; align-items:center; justify-content:space-between;
  padding:6px 14px 8px; color:var(--navy); font-size:11px; font-weight:800;
  background:#fff; border-radius:30px 30px 0 0;}
.fly-status-icons{display:flex; align-items:center; gap:5px;}

.fly-phone-head{display:flex; align-items:center; gap:9px; padding:8px 14px 10px;
  background:#fff; border-bottom:1px solid rgba(7,14,26,.09); z-index:2;}
.fly-phone-avatar{display:flex; align-items:center; justify-content:center; flex-shrink:0;
  width:28px; height:28px; border-radius:50%; background:var(--blue);}
.fly-phone-who{display:flex; flex-direction:column; line-height:1.25; min-width:0;}
.fly-phone-who strong{font-size:13px; font-weight:800; color:var(--navy);}
.fly-phone-who span{font-size:10.5px; font-weight:600; color:var(--text-muted);}

/* justify-content:flex-end is the whole trick. Messages grow in at the
   bottom and push the earlier ones up and out through overflow:hidden, so
   this behaves like a real thread AND the screen never has to be tall
   enough to hold all five beats at once — which is what let the record card
   keep its full five rows. */
.fly-screen{position:relative; flex:1; min-height:0; overflow:hidden; background:var(--sky);
  display:flex; flex-direction:column; justify-content:flex-end;
  padding:12px 12px 8px; gap:0;}

/* Softens the cut where a message leaves the top of the thread — without it
   the oldest bubble is sliced flat against the header and reads as a
   clipping bug rather than as scrolling.

   This is an overlay, NOT mask-image, and that is the whole point: a mask
   fades the element's own background along with its content, so the first
   version of this punched a hole straight through .fly-screen's sky and
   showed the navy phone frame underneath. What looked like a harsh dark
   gradient was the bezel, not the fade. A gradient in the chat's own colour
   has nothing behind it to expose. position:absolute keeps it out of the
   flex column, and pointer-events:none keeps it off the text. */
.fly-screen::before{content:''; position:absolute; top:0; left:0; right:0; height:30px;
  z-index:1; pointer-events:none;
  background:linear-gradient(180deg, var(--sky) 18%, transparent 100%);}

/* Reveal is a height animation, not a fade-in-place: grid-template-rows
   0fr→1fr expands to the content's own height without anything having to
   measure it, and margin-top animating alongside means the gap between
   messages arrives with the message rather than being reserved in advance.
   The child needs overflow:hidden for the 0fr row to actually clip.
   Browsers without 0fr interpolation snap instead of sliding — the beat
   still appears, which is the part that matters. */
/* flex-shrink:0 is belt-and-braces, and measured as such rather than
   assumed: these are flex items in a column that deliberately overflows,
   but a flex item's automatic min-height already stops it shrinking below
   its content, so removing this changes nothing today. It is here because
   the failure it guards against is silent and ugly — a squeezed message
   plus .fly-msg-in's overflow:hidden clips the record card along its
   BOTTOM edge, losing the payoff beat's last lines — and it would be one
   `min-height:0` away from happening. */
.fly-msg{display:grid; grid-template-rows:0fr; opacity:0; margin-top:0; flex-shrink:0;
  transition:grid-template-rows .5s var(--ease), opacity .38s var(--ease), margin-top .5s var(--ease);}
.fly-msg-in{overflow:hidden; display:flex;}
.fly-msg.on{grid-template-rows:1fr; opacity:1; margin-top:9px;}
.fly-msg.out .fly-msg-in{justify-content:flex-end;}
.fly-msg.in .fly-msg-in{justify-content:flex-start;}
/* First message carries no top margin — it sits against the screen's own
   padding, and 9px on top of that reads as a misaligned first bubble. */
.fly-msg:first-child.on{margin-top:0;}

/* Outgoing is blue and right, incoming is white and left — the same pairing
   every time, which is what makes the alternation legible as a conversation.
   max-width is a percentage of the screen, not a flat px: the phone scales
   with viewport height, so a fixed cap would leave a wide gutter on a tall
   screen and overflow on a short one. */
.fly-bubble{max-width:86%; border-radius:15px; padding:9px 12px; font-size:12.5px;
  line-height:1.45; font-weight:600;}
.fly-msg.out .fly-bubble{background:var(--blue); color:#fff; border-bottom-right-radius:4px;}
.fly-msg.in .fly-bubble{background:#fff; color:var(--ink); border:1px solid var(--hairline);
  border-bottom-left-radius:4px;}

.fly-voice{display:flex; align-items:center; gap:8px;}
.fly-voice-icon{display:flex; flex-shrink:0;}
.fly-waveform{display:flex; align-items:center; gap:2.5px; height:18px; flex-shrink:0;}
.fly-waveform span{display:block; width:2.5px; border-radius:2px; background:#fff; opacity:.9;}
.fly-voice-dur{font-size:11px; font-weight:700; opacity:.9; flex-shrink:0;}

/* The bot is composing. Three dots on one keyframe set with staggered
   delays, so the animation is declared once rather than three times. */
.fly-typing{display:flex; align-items:center; gap:4px; padding:11px 13px;}
.fly-typing i{display:block; width:5px; height:5px; border-radius:50%;
  background:var(--slate); animation:fly-blink 1.25s var(--ease) infinite;}
.fly-typing i:nth-child(2){animation-delay:.18s;}
.fly-typing i:nth-child(3){animation-delay:.36s;}
@keyframes fly-blink{0%,60%,100%{opacity:.28; transform:translateY(0);} 30%{opacity:1; transform:translateY(-3px);}}

.fly-ask p{margin:0;}
.fly-ask-q{color:var(--ink); font-weight:800; margin-top:7px !important;}

.fly-record{display:flex; flex-direction:column; gap:6px; width:100%; max-width:100%;}
.fly-record-row{display:flex; justify-content:space-between; align-items:baseline; gap:12px; font-size:11.5px;}
.fly-record-row span{color:var(--text-muted); font-weight:600; flex-shrink:0;}
.fly-record-row strong{color:var(--ink); font-weight:700; text-align:right;}
.fly-record-note{font-size:10.5px; font-weight:700; color:var(--mint-ink); margin:0;}
.fly-record-note:first-of-type{margin-top:5px; padding-top:7px; border-top:1px dashed var(--hairline);}
.fly-record-crm{color:var(--blue);}

/* Composer. Decorative — it is what tells you this is Telegram and not a
   generic chat illustration, and the mic is accented because the voice note
   is the thing the section is selling. */
.fly-phone-bar{display:flex; align-items:center; gap:8px; padding:9px 12px 12px;
  background:var(--sky); border-top:1px solid rgba(7,14,26,.08); border-radius:0 0 30px 30px;}
.fly-phone-input{flex:1; background:#fff; border:1px solid var(--hairline); border-radius:999px;
  padding:7px 13px; font-size:11.5px; font-weight:600; color:var(--text-muted);}
.fly-phone-mic{display:flex; align-items:center; justify-content:center; flex-shrink:0;
  width:30px; height:30px; border-radius:50%; background:var(--blue); color:#fff;}

/* ---------- WHO IT'S FOR ---------- */
/* 260vh. This has been retuned twice: 230vh opening the callouts at 72% of the
   track, then 200vh opening them at 48%. The second was still too tight in
   practice — half of a 100vh track is only ~50vh of scroll, so a small wheel
   movement took the finished composition off screen before it could be read.
   260vh gives 160vh of scroll, and the 0.18 / 0.34 thresholds in main.js spend
   it 29vh / 25vh / 106vh, so the state with every callout open holds for more
   than a full screen of scrolling. Height and thresholds are one setting in
   two files — change them together. */
.wif{min-height:260vh; background:var(--blue);}
.wif-mobile{display:none;}
.wif-m-kicker{font-size:12px; font-weight:700; letter-spacing:2px; text-transform:uppercase;
  color:#cfe6ff; text-align:center; margin-bottom:14px;}
.wif-m-head{color:#fff; font-size:clamp(28px,8vw,38px); text-align:center; margin-bottom:44px; line-height:1.15;}
.wif-m-list{list-style:none; display:flex; flex-direction:column; gap:34px;}
.wif-m-list li{display:flex; flex-direction:column; align-items:center; text-align:center; gap:16px;}
.wif-m-circle{width:168px; height:168px; border-radius:50%; border:2px solid #fff;
  display:flex; align-items:center; justify-content:center; color:#fff; font-weight:800; font-size:21px;
  line-height:1.15; flex-shrink:0;
  /* Second offset ring, echoing the hand-drawn double circles on desktop. */
  box-shadow:0 0 0 1px rgba(255,255,255,.35), 3px 4px 0 1px rgba(255,255,255,.35);}
.wif-m-list p{color:#e8f0fe; font-size:15.5px; line-height:1.55; max-width:330px; font-weight:500;}
/* padding-top clears the sticky nav. The SVG is height-constrained
   (`meet` on a 1300x1120 viewBox in a box that is always wider than it is
   tall), so its "WHO IT'S FOR" label at y=45 was landing at viewport y~29 —
   45px *behind* the nav — and in the split state the "Built for" headline was
   clipped too. Padding is the only lever that stays correct at every viewport
   height: a viewBox offset would be a fixed number of user units and so shrink
   to nothing on short viewports, exactly where the nav eats the most.
   The 120 units trimmed off the viewBox's dead bottom (content ends at
   y~1031) pay the padding back — the diagram renders ~1% larger than it did
   before this fix, not smaller. */
.wif-sticky{position:sticky; top:0; height:100vh; height:100svh;
  padding-top:var(--nav-h); display:flex; align-items:center; justify-content:center;}
/* The split: one circle becomes three, then the callouts arrive.
   Everything is staggered off a single --d set per group in the markup, so the
   three circles do not all arrive on the same frame (which read as one
   mechanical snap) and each circle's own label follows its own circle. */
.wif-headline{transition:transform 1.15s var(--ease); transform-box:view-box; transform-origin:650px 663px;}
.wif-circ-g{transition:transform 1.15s var(--ease); transition-delay:var(--d,0s);}
.wif-name{opacity:0; transform:translateY(6px); transform-box:fill-box; transform-origin:center;
  transition:opacity .45s ease, transform .45s var(--ease);}
.wif-single{opacity:1; transition:opacity .55s ease;}
.wif-sticky.split .wif-single{opacity:0;}
.wif-sticky.split .wif-headline{transform:translate(0,-540px) scale(.6);}
.wif-sticky.split .wif-circ-g{transform:translate(0,0) scale(1) !important;}
.wif-sticky.split .wif-name{opacity:1; transform:none;
  transition-delay:calc(var(--d,0s) + .5s);}

/* Callouts: the line draws itself from the dot outward, the dot pops, then the
   words fade in — rather than the whole group cross-fading at once, which is
   what made them look pasted on. --len is each path's length, set in the
   markup; it has to be >= the real length or the line never fully hides. */
.wif-conn{opacity:0; transition:opacity .4s ease var(--d,0s);}
.wif-conn path{stroke-dasharray:var(--len,80); stroke-dashoffset:var(--len,80);
  transition:stroke-dashoffset .7s var(--ease) calc(var(--d,0s) + .1s);}
.wif-conn circle{transform:scale(0); transform-box:fill-box; transform-origin:center;
  transition:transform .38s var(--ease) var(--d,0s);}
.wif-conn text{opacity:0; transition:opacity .5s ease calc(var(--d,0s) + .42s);}
.wif-sticky.reveal .wif-conn{opacity:1;}
.wif-sticky.reveal .wif-conn path{stroke-dashoffset:0;}
.wif-sticky.reveal .wif-conn circle{transform:scale(1);}
.wif-sticky.reveal .wif-conn text{opacity:1;}
/* No hover state and no pointer cursor: nothing here is clickable, so the
   white wash and the hand cursor were both promising an interaction that does
   not exist.
   .wif-circ-hit stays in the markup even though nothing hovers it now — it is
   the widest shape in each .wif-circ-shape, so it defines the fill-box that
   .wif-circ-g's `transform-origin:center` is measured against. Delete the
   circle and every circle in the split animation moves. */
.wif-circ-hit{fill:none;}

/* ---------- BEFORE / AFTER TALLY ---------- */
.compare{padding:84px 8vw 78px; background:#fff;}
/* Centred, because .compare-grid below it is centred (max-width:1300px;
   margin:0 auto). It used to be left-aligned over that centred grid — one of
   the "in between" headings. */
.compare h2{font-size:var(--h-section); max-width:var(--head-max); margin:0 auto 60px; text-align:center;}
.compare h2 .hl{color:var(--blue);}
.compare-grid{display:grid; grid-template-columns:1fr 1fr; gap:6vw; max-width:1300px; margin:0 auto;}
.compare-pill{display:inline-flex; align-items:center; gap:10px; padding:11px 24px; border-radius:30px;
  font-weight:700; font-size:15px; margin-bottom:30px;}
.compare-pill .pbadge{width:19px; height:19px; border-radius:6px; display:inline-flex; align-items:center;
  justify-content:center; padding:3px;}
.compare-pill .pbadge img{width:100%; height:100%; display:block;}
/* No badge on the "before" pill — that column is life without Tally, so
   putting the Tally mark on it was branding the problem. Only .after carries
   the glyph, which is also what makes the mark mean something when it shows up. */
.compare-pill.before{background:var(--navy); color:#fff;}
.compare-pill.after{background:var(--mint); color:var(--navy);}
.compare-pill.after .pbadge{background:var(--navy); color:var(--mint);}
.compare-row{display:flex; gap:16px; padding:20px 0; border-bottom:1px solid var(--hairline); align-items:flex-start;
  opacity:0; transform:translateY(14px); transition:opacity .6s var(--ease), transform .6s var(--ease);}
.compare.reveal .compare-row{opacity:1; transform:translateY(0);}
.compare-row:nth-child(2){transition-delay:.05s;} .compare-row:nth-child(3){transition-delay:.15s;}
.compare-row:nth-child(4){transition-delay:.25s;} .compare-row:nth-child(5){transition-delay:.35s;}
.compare-col .compare-row:last-child{border-bottom:none;}
.compare-icon{width:24px; height:24px; border-radius:50%; display:flex; align-items:center; justify-content:center;
  flex-shrink:0; margin-top:2px; border:1.5px solid currentColor;}
.compare-col.before-col{color:var(--navy);}
.compare-col.after-col{color:var(--mint-ink);}
.compare-row p{font-size:clamp(15px,1.6vw,18px); font-weight:500; line-height:1.45;}

/* ---------- CAPTURE / READ / EXTRACT / STORE / SYNC / RETURN ---------- */
/* 6 steps * 75vh ≈ one screen and change per step, not a full 100vh each —
   was 500vh/5 panels (100vh/step), which read as "forced" for one word and
   one short line. main.js divides this by hiwPanels.length automatically,
   so the maths stays even as long as the panel count and this height agree. */
.hiw-track{height:450vh;}
/* Pins BELOW the nav, not behind it. This is the only pinned card on the page
   that carries its own background (see the hiw-pN classes below; .fly-section
   and .wif paint theirs on the section itself), so it is the only one where
   pinning at top:0 was visible: the coloured block ran up under the sticky nav
   band, which put its two rounded top corners out of sight and left the card
   reading as square on top and round on the bottom. Offsetting the pin by
   --nav-h and taking the same amount off the height puts the whole card in the
   visible area — all four corners on screen, and the panel's own centring now
   centres against what the visitor can actually see, which is the same bug
   from the other end.

   Both height declarations are load-bearing: svh is the stable small-viewport
   height (it does not move when mobile browser chrome collapses on scroll) and
   the vh line above it is the fallback for browsers that don't know svh.

   trackProgress() in main.js is unaffected — it measures the SECTION against
   window.innerHeight, and the card still unpins at exactly progress 1 because
   nav-h + (100svh - nav-h) is still the bottom of the viewport. */
.hiw-sticky{position:sticky; top:var(--nav-h);
  height:calc(100vh - var(--nav-h)); height:calc(100svh - var(--nav-h)); overflow:hidden;
  transition:background-color .6s var(--ease), color .6s var(--ease);}
/* The background used to live on each full-bleed .hiw-panel and cross-fade
   against the next one — two opaque colours overlapping mid-transition
   blended into a muddy midtone. It now lives here, on the shared sticky
   container, so it changes as a single continuous wash with nothing to blend
   against. main.js swaps which hiw-pN class is present; the transition above
   animates the change.

   --doodle is the handwritten hover layer's colour for that step (see the
   .hiw-panel .tw .tw-d rule up in the word-doodle block). It is chosen to
   clear both the step's background and its own bold layer's colour, so the
   crossfade always reads as a colour change and never as a disappearance. */
.hiw-p0{background:var(--sky);     color:var(--blue); --doodle:var(--navy);}
.hiw-p1{background:var(--mint);    color:var(--navy); --doodle:var(--blue);}
.hiw-p2{background:var(--blue);    color:var(--navy); --doodle:var(--mint);}
.hiw-p3{background:var(--navy);    color:var(--mint); --doodle:var(--blue-soft);}
.hiw-p4{background:var(--mint-ink);color:#fff;        --doodle:var(--navy);}
.hiw-p5{background:var(--orange);  color:#fff;        --doodle:var(--navy);}

/* Panels are transparent now (background moved to .hiw-sticky above) and only
   position content. Every panel uses the identical .hiw-inner layout at the
   identical spot, so nothing moves except which panel is visible — that's
   the fix for "title and texts are randomly positioned".

   `inset:0` is centring against the visible area, not just against the box,
   and only because .hiw-sticky pins below the nav rather than behind it —
   see the note there. While it pinned at top:0 this same rule put the step
   --nav-h/2 (~34px) above the middle of the screen, because the top --nav-h
   of the box it was centring in was underneath the nav band. Do not give
   this panel a compensating offset if that ever regresses; fix where the
   card pins, or the offset and the card fight each other. */
.hiw-panel{position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  padding:6vw; pointer-events:none;}
.hiw-panel.on{pointer-events:auto; z-index:2;}
/* One column, not two. The old layout put the number, icon and word in a left
   column and the description in a right one with a 6vw gutter between them, so
   at 1280px the sentence explaining the step floated ~400px away from the word
   it explained, level with the number rather than the heading — the eye had no
   reading order to follow and the panel read as two unrelated halves with a
   hole in the middle. Everything is one left-aligned stack now, capped at
   860px and centred in the panel, so the order is literally top to bottom:
   which step this is, its mark, its name, what it does. */
.hiw-inner{display:block; max-width:860px; width:100%;}
/* Icon and step counter share a line — a 56px mark stranded between a 14px
   number and a 82px word made the vertical rhythm lurch tiny/medium/huge. */
.hiw-left{display:grid; grid-template-columns:auto 1fr;
  grid-template-areas:"icon index" "word word"; gap:0 16px; align-items:center;}
.hiw-icon{grid-area:icon;}
.hiw-icon svg{width:46px; height:46px; display:block;}
.hiw-index{grid-area:index; justify-self:start; font-size:15px; font-weight:800; letter-spacing:2.4px;}
.hiw-word{grid-area:word; font-size:clamp(42px,6.4vw,82px); font-weight:800; margin-top:18px;}
.hiw-right{margin-top:20px;}
/* min-height reserves room for the longest description (the Sync step wraps to
   3 lines where most others are 1) so the block's height is constant across
   steps. Without it the panel's vertical centring re-centres a shorter step
   and the heading appears to jump between them. */
.hiw-desc{font-size:clamp(18px,1.9vw,25px); font-weight:500; max-width:640px; line-height:1.5;
  min-height:3.8em;}

/* Content swap: outgoing fades/slides down to rest, incoming rises in and
   fades up, staggered per element (index -> icon -> word -> desc) so the step
   reads as composed rather than one block swapping at once. Only content
   animates — the background wash above is the only thing that "cross-fades". */
.hiw-index,.hiw-icon,.hiw-word,.hiw-desc{opacity:0; transform:translateY(14px);
  transition:opacity .5s var(--ease), transform .5s var(--ease);}
.hiw-panel.on .hiw-index{opacity:1; transform:none; transition-delay:.02s;}
.hiw-panel.on .hiw-icon{opacity:1; transform:none; transition-delay:.08s;}
.hiw-panel.on .hiw-word{opacity:1; transform:none; transition-delay:.14s;}
.hiw-panel.on .hiw-desc{opacity:1; transform:none; transition-delay:.2s;}

/* Vertical rail, matching the vertical scroll axis (the old .hiw-dots row was
   horizontal, fighting the direction you actually scroll in). Colour comes
   from currentColor, inherited straight from .hiw-sticky's own `color` (set
   by the hiw-pN classes above) — no JS re-tint needed since the rail is a
   descendant of the element the colour actually lives on now. A fixed label
   width keeps every tick in one straight vertical line regardless of how long
   each step's name is. */
.hiw-rail{position:absolute; right:40px; top:50%; transform:translateY(-50%);
  display:flex; flex-direction:column; gap:22px; z-index:5; color:inherit; --p:0;}
/* An actual bar, not six loose dots. Six 7px ticks at 0.4 opacity with 12px
   labels told you nothing at a glance — you could not see how far through the
   section you were, which is the one job a progress indicator has. ::before is
   the full track, ::after is the filled part, and both are drawn behind the
   ticks (which carry z-index above). Right offset is set so the 3px line
   centres on the 11px tick column: tick centre sits 5.5px from the rail's
   right edge, the line's centre is `right + 1.5`, hence right:4px. The 18px
   subtracted from the fill height is the 9px inset at each end, which is where
   the first and last tick centres fall. --p is set by main.js as
   index / (steps - 1), so the fill reaches exactly the active tick. */
.hiw-rail::before,.hiw-rail::after{content:''; position:absolute; right:4px; width:3px;
  border-radius:2px; background:currentColor;}
.hiw-rail::before{top:9px; bottom:9px; opacity:.22;}
.hiw-rail::after{top:9px; height:calc((100% - 18px) * var(--p)); opacity:.95;
  transition:height .5s var(--ease);}
.hiw-rail-item{position:relative; z-index:1; display:flex; align-items:center; gap:12px;}
.hiw-rail-label{width:74px; text-align:right; font-size:13px; font-weight:700; letter-spacing:.3px;
  white-space:nowrap; opacity:.5; transition:opacity .3s ease;}
.hiw-rail-tick{width:11px; height:11px; border-radius:50%; background:currentColor; flex-shrink:0;
  opacity:.3; transition:opacity .3s ease, transform .3s var(--ease);}
.hiw-rail-item.on .hiw-rail-label{opacity:1;}
.hiw-rail-item.on .hiw-rail-tick{opacity:1; transform:scale(1.35);}

/* ---------- DEPLOYMENT (two tiles: individual vs team) ---------- */
/* Light ground for the whole section — a full-bleed navy block here used to
   fight the white sections either side of it. The two tiles carry the
   contrast instead: a light "light" tile for the self-serve individual and
   a dark "dark" tile for the team rollout, so which tile is which reads
   instantly without reading the copy first. */
.benefits{background:#fff; padding:84px 8vw 78px;}
.benefits-head{max-width:var(--head-max); margin:0 auto 48px; text-align:center;}
.benefits-head h2{font-size:var(--h-section); margin-bottom:14px;}
.benefits-sub{font-size:17px; color:var(--ink); line-height:1.55;
  max-width:var(--head-sub); margin:0 auto;}
.benefits-grid{display:grid; grid-template-columns:1fr 1fr; gap:24px; max-width:1100px; margin:0 auto;}
.dep-tile{position:relative; border-radius:22px; padding:40px 34px; display:flex; flex-direction:column;
  opacity:0; transform:translateY(16px); transition:opacity .6s var(--ease), transform .6s var(--ease);}
.benefits.reveal .dep-tile{opacity:1; transform:translateY(0);}
.dep-tile:nth-child(2){transition-delay:.1s;}
.dep-tile.light{background:var(--sky); border:1.5px solid var(--hairline); color:var(--navy);}
.dep-tile.dark{background:var(--navy); color:#fff;}
/* The chip is gone. It was a 40px filled square — solid --blue on the light
   tile, a white wash on the dark — holding a clock and a two-person glyph, and
   with the label now at 27px it was a second heavy element competing for the
   same first read in the same corner. What's left is line only: no background,
   no radius, 1.6px strokes at 26px in a colour that sits under the label
   rather than beside it. The marks are circles because circles are already
   this site's shorthand for people — #who-its-for is built from them — so one
   ring reads as one rep and three read as the floor team without either
   needing a caption. */
.dep-icon{width:26px; height:26px; margin-bottom:18px; display:block;}
.dep-icon svg{display:block;}
.dep-tile.light .dep-icon{color:var(--blue);}
.dep-tile.dark .dep-icon{color:rgba(255,255,255,.55);}
/* Three ranks, in the order the owner asked for them: the audience the tile is
   addressed to is primary, what that audience does with Tally is secondary,
   the detail is tertiary. It used to run the other way — "FOR INDIVIDUALS" was
   12.5px uppercase muted, the quietest thing in the tile, under a 23px h3 — so
   a reader scanning the two tiles got "Run your own pipeline" / "Roll it out to
   the whole floor" and had to read the small print to find out which one was
   theirs. The label carries the tile's <h3> now, not just its largest type;
   uppercase is dropped because at this size letter-spaced caps still read as a
   kicker no matter how big they are. */
.dep-label{font-size:27px; font-weight:800; line-height:1.15; letter-spacing:-.2px; margin-bottom:10px;}
.dep-tile.light .dep-label{color:var(--navy);}
.dep-tile.dark .dep-label{color:#fff;}
.dep-tile p{font-size:15px; line-height:1.6; margin:0 0 24px; max-width:360px;}
.dep-tile.light p{color:var(--text-muted);}
.dep-tile.dark p{color:var(--slate);}
/* The secondary line is a <p> too, so these have to out-specify `.dep-tile p`
   and `.dep-tile.light p` above — hence `p.dep-line` rather than `.dep-line`. */
.dep-tile p.dep-line{font-size:17px; font-weight:700; line-height:1.35; margin-bottom:12px; max-width:360px;}
.dep-tile.light p.dep-line{color:var(--ink);}
.dep-tile.dark p.dep-line{color:var(--text-secondary);}
.dep-link{margin-top:auto; display:inline-flex; align-items:center; gap:8px; font-weight:700; font-size:14.5px;
  transition:gap .2s ease;}
.dep-tile.light .dep-link{color:var(--blue);}
.dep-tile.dark .dep-link{color:#fff;}
.dep-link:hover{gap:13px;}

/* ---------- SEE IN ACTION ---------- */
/* ==========================================================================
   SEE IN ACTION — one stage on screen at a time
   --------------------------------------------------------------------------
   Earlier version put a two-column stage (raw message / rail / structured
   record) and a five-CRM strip on screen simultaneously and just lit parts
   of it up as the sequence played — everything was visible from the first
   frame, so there was nothing to actually watch unfold. This version shows
   exactly one of three panels at a time (raw capture, structured, in your
   CRM), with a compact step indicator above the stage saying which one and
   what's still ahead.

   The three panels share one CSS Grid cell (`.demo-panel{grid-area:1/1}`),
   so the browser sizes `.demo-stage` to the tallest panel's content and that
   height never changes as steps swap — nothing below the demo jumps mid-run,
   and nothing needs a JS height measurement to make that true. Only the
   `.active` panel is opaque and interactive; a panel already shown carries
   `.prev` (faded out, shifted left) and a panel not yet reached sits in its
   default resting state (faded out, shifted right), so advancing always
   reads as "this leaves toward the past, that arrives from what's next"
   rather than a cross-fade through a muddy half-opaque middle. Nothing here
   is absolutely positioned, so the identical markup holds from 320px to
   1600px — the step indicator wraps and the stage narrows, but the "one
   thing on screen" rule never depends on width.
   ========================================================================== */
/* 8vw sides, matching every other centred section. It was 6vw, and above the
   --head-max cap that was invisible, but below ~1120px the section padding is
   what sets the title box — so this head and #integrations' came out 4vw wider
   than the five around them at exactly the widths where the difference shows.
   Nothing in here is measured against the old 6vw: .demo-stage caps at 520px
   and .demo-steps well under that, so the extra inset only bites below ~830px,
   where the 520px mobile rules take over anyway. */
.see-action{background:#fff; padding:110px 8vw 100px;}
.demo-head{max-width:var(--head-max); margin:0 auto 54px; text-align:center;}
.demo-head h2{font-size:var(--h-section); margin-bottom:14px;}
.demo-sub{font-size:17px; color:var(--ink); line-height:1.55;
  max-width:var(--head-sub); margin:0 auto 28px;}
.demo-play{display:inline-flex; align-items:center; gap:9px;}
.demo-play svg{flex:none;}

/* ---------- step indicator ---------- */
/* The step name is the only thing on screen that says what the animation is
   currently doing, so it is sized as the section's second-level type — not as
   a caption. It used to render at 11.5px, smaller than every other word in
   the section including the CRM tag pills, which left the reader watching a
   card change with no dominant label telling them why. */
/* The indicator sits in its own tinted track. On a white card in a white
   section, three dots and three words floating in space read as a caption
   above the demo rather than as the control telling you where you are; giving
   it a container makes it one component with edges, and everything inside it
   gains contrast against --sky instead of against #fff. */
.demo-steps{list-style:none; display:flex; justify-content:center; gap:0;
  max-width:640px; margin:0 auto 40px; padding:22px 26px;
  background:var(--sky); border-radius:22px;}
.demo-step{position:relative; flex:1; display:flex; flex-direction:column; align-items:center;
  text-align:center; min-width:0;}
/* Connector line between dots, filled in behind the dots (z-index below) so
   it reads as one continuous track rather than three separate pills. The mint
   fill wipes left-to-right on `.done` instead of switching colour on one
   frame — it is the only part of the indicator that can show a step *being*
   completed rather than already complete.
   `top` is half the dot height and the insets are half the dot plus 7px of
   breathing room — all three must move if .demo-step-dot's size changes. */
.demo-step:not(:last-child)::before{content:''; position:absolute; top:21px; left:calc(50% + 28px);
  right:calc(-50% + 28px); height:3px; border-radius:2px; background:#d7e0ee; z-index:0;}
.demo-step:not(:last-child)::after{content:''; position:absolute; top:21px; left:calc(50% + 28px);
  right:calc(-50% + 28px); height:3px; border-radius:2px; background:var(--mint-ink); z-index:0;
  transform:scaleX(0); transform-origin:left center; transition:transform .45s var(--ease);}
.demo-step.done:not(:last-child)::after{transform:scaleX(1);}
.demo-step-dot{position:relative; z-index:1; width:42px; height:42px; border-radius:50%;
  background:#fff; border:2px solid #d7e0ee; display:flex; align-items:center; justify-content:center;
  margin-bottom:13px; color:var(--text-muted);
  transition:border-color .3s ease, background .3s ease, color .3s ease, transform .3s var(--ease), box-shadow .3s ease;}
.demo-step-num{font-size:16px; font-weight:800;}
.demo-step-check{display:none;}
/* The active step is the loudest thing in the section on purpose: filled
   brand blue, white numeral, scaled up and haloed. Before this it was a 30px
   outline dot in --sky, near-identical to the two steps either side of it, so
   nothing on screen told you which stage you were watching. */
.demo-step.active .demo-step-dot{border-color:var(--blue); background:var(--blue); color:#fff;
  transform:scale(1.14); box-shadow:0 8px 22px rgba(0,109,255,.4), 0 0 0 6px rgba(0,109,255,.14);}
.demo-step.done .demo-step-dot{border-color:var(--mint-ink); background:var(--mint-ink); color:#fff;}
.demo-step.done .demo-step-num{display:none;}
.demo-step.done .demo-step-check{display:block;}
.demo-step-name{position:relative; font-size:17px; font-weight:700; color:var(--text-muted); max-width:150px;
  line-height:1.3; padding-bottom:7px; transition:color .3s ease;}
/* An underline bar under the active name, so the step reads as current even
   at a glance where the dot's colour alone might not register. */
.demo-step-name::after{content:''; position:absolute; left:50%; bottom:0; height:3px; width:0;
  border-radius:2px; background:var(--blue); transform:translateX(-50%);
  transition:width .35s var(--ease);}
.demo-step.active .demo-step-name::after{width:100%;}
.demo-step.active .demo-step-name{color:var(--navy); font-weight:800;}
.demo-step.done .demo-step-name{color:var(--ink);}

/* ---------- the stage: one panel visible, height reserved for the tallest ---------- */
/* The clip is horizontal only. `overflow:hidden` on both axes cut the top 2px
   off the Telegram card the moment it went `.live` — that state lifts it
   translateY(-2px) and paints a 34px blue glow, and both were being sliced
   flat against the stage's top edge, which is the "first card gets cut at the
   top" report. `overflow-x:clip` keeps the sliding panels out of frame without
   making the y axis a scroll container the way `overflow-x:hidden` would.
   The `overflow:hidden` above it is the pre-2022-browser fallback and must
   stay first. */
.demo-stage{display:grid; max-width:520px; margin:0 auto;
  overflow:hidden; overflow-x:clip; overflow-y:visible;}
.demo-panel{grid-area:1/1; opacity:0; transform:translateX(14px);
  transition:opacity .28s var(--ease), transform .28s var(--ease); pointer-events:none;}
.demo-panel.prev{transform:translateX(-14px);}
.demo-panel.active{opacity:1; transform:none; pointer-events:auto;}

/* ---------- panel 1: the raw message ---------- */
.demo-msg{background:var(--sky); border:1.5px solid transparent; border-radius:18px; padding:18px 20px 20px;
  transition:border-color .4s ease, box-shadow .4s ease, transform .4s var(--ease);}
.demo-msg.live{border-color:var(--blue); box-shadow:0 14px 34px rgba(0,109,255,.16); transform:translateY(-2px);}
.demo-msg-head{display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:10px;}
.demo-msg-app{display:inline-flex; align-items:center; gap:6px; font-size:11.5px; font-weight:800;
  letter-spacing:.8px; text-transform:uppercase; color:var(--blue);}
.demo-msg-time{font-size:11.5px; font-weight:600; color:var(--text-muted); font-variant-numeric:tabular-nums;}

/* The three input types, on the card. The active one is the format this run
   plays; the other two are stated rather than animated, because restaging the
   whole sequence three times would bury the one thing the demo is for. */
.demo-inputs{display:flex; flex-wrap:wrap; gap:6px; margin-bottom:11px;}
.demo-chip{display:inline-flex; align-items:center; gap:5px; padding:5px 10px; border-radius:100px;
  font-size:11px; font-weight:700; letter-spacing:.2px; white-space:nowrap;
  background:#fff; border:1px solid #d7e0ee; color:var(--text-muted);}
.demo-chip svg{flex:none;}
.demo-chip.on{background:var(--blue); border-color:var(--blue); color:#fff;}

/* The voice note the message actually arrives as. It stays visible for the
   whole beat — the transcript appears underneath it rather than replacing it,
   because the point is that the audio is the input, not that it vanishes. */
.demo-voice{display:flex; align-items:center; gap:9px; padding:9px 11px; border-radius:12px;
  background:#fff; border:1px solid #dde4ef; transition:border-color .4s ease;}
.demo-msg.live .demo-voice{border-color:rgba(0,109,255,.35);}
.demo-voice-play{flex:none; width:19px; height:19px; border-radius:50%; background:var(--blue); color:#fff;
  display:flex; align-items:center; justify-content:center; padding-left:1px;}
.demo-wave{flex:1; min-width:0; display:flex; align-items:center; gap:2px; height:20px;}
.demo-wave i{flex:1; height:var(--h); min-height:3px; border-radius:2px; background:#c2d0e4;
  transition:background .5s ease;}
.demo-msg.live .demo-wave i{background:var(--blue);}
.demo-voice-dur{flex:none; font-size:10.5px; font-weight:700; color:var(--text-muted); font-variant-numeric:tabular-nums;}

/* Reserved from the first frame, revealed on the transcribe beat: animating
   height instead would grow the card and, even inside a height-reserved
   stage, shove its own contents around while the panel is on screen. */
.demo-msg-body{margin-top:11px; font-size:14px; line-height:1.55; color:var(--ink); font-weight:500;
  opacity:0; transform:translateY(3px); transition:opacity .5s ease, transform .5s var(--ease);}
.demo-msg.transcribed .demo-msg-body{opacity:1; transform:none;}

/* ---------- panel 2: the record that fills in ---------- */
.demo-card{background:#fff; border:1.5px solid var(--hairline); border-radius:18px; padding:18px 20px 20px;
  box-shadow:0 10px 30px rgba(3,20,55,.07); transition:border-color .4s ease, box-shadow .4s ease;}
.demo-card.done{border-color:rgba(0,168,132,.4); box-shadow:0 16px 40px rgba(3,20,55,.12);}
.demo-card-top{display:flex; align-items:center; gap:7px; margin-bottom:14px;
  font-size:11.5px; font-weight:800; letter-spacing:.8px; text-transform:uppercase;
  color:var(--mint-ink); opacity:0; transition:opacity .45s ease;}
.demo-card.done .demo-card-top{opacity:1;}
.demo-fields{display:flex; flex-direction:column; gap:9px;}
.demo-row{position:relative; display:flex; align-items:baseline; gap:12px; min-height:19px;}
.demo-row dt{flex:0 0 76px; font-size:10.5px; font-weight:700; letter-spacing:.5px; text-transform:uppercase;
  color:var(--text-muted);}
/* Each value starts as a grey placeholder bar and is swapped for real text when
   its row lands, so the card has its final height from the very first frame and
   nothing below it jumps as the rows fill. */
.demo-row dd{flex:1; min-width:0; font-size:14px; font-weight:700; color:var(--navy); line-height:1.3;
  opacity:0; transform:translateY(4px); transition:opacity .4s ease, transform .4s var(--ease);}
.demo-row.in dd{opacity:1; transform:none;}
/* Overlaid on the value, not a flex sibling of it — as a sibling the bar and
   the text each claimed half the row and the value rendered alongside its own
   placeholder instead of replacing it. `left` tracks the dt column width plus
   the row gap, so it must move with .demo-row dt's flex-basis. */
.demo-row-ph{position:absolute; left:88px; right:0; top:8px; height:9px; border-radius:5px;
  background:var(--sky); transition:opacity .3s ease;}
.demo-row.in .demo-row-ph{opacity:0;}
.demo-next{margin-top:14px; padding-top:12px; border-top:1px solid var(--hairline);
  font-size:12.5px; font-weight:700; color:var(--orange); opacity:0; transition:opacity .45s ease;}
.demo-card.done .demo-next{opacity:1;}

/* ---------- panel 3: the success screen ---------- */
/* This is the end of the story, so it is allowed to look like an outcome
   rather than a third card in a row: a mint check that lands with the record,
   "Synced to your CRM" at heading weight, and the whole frame going green.
   The previous version said "success" only through an 11px uppercase badge
   tucked into a fake window title bar, next to three grey traffic-light dots. */
.demo-crm-window{background:#fff; border:1.5px solid var(--hairline); border-radius:18px; overflow:hidden;
  box-shadow:0 10px 30px rgba(3,20,55,.07); transition:border-color .45s ease, box-shadow .45s ease, background .45s ease;}
.demo-crm-window.arrived{border-width:2px; border-color:var(--mint-ink);
  box-shadow:0 20px 50px rgba(0,168,132,.26), 0 0 0 6px rgba(0,250,218,.18);}

.demo-done-head{display:flex; align-items:center; gap:15px; padding:19px 20px;
  background:var(--sky); border-bottom:1px solid var(--hairline);
  transition:background .45s ease, border-color .45s ease;}
.demo-crm-window.arrived .demo-done-head{background:rgba(0,250,218,.26); border-bottom-color:rgba(0,168,132,.35);}
/* The mark is the single frame the whole 9 seconds builds to, so it gets the
   one spring in the sequence — everything else here fades and slides. */
.demo-done-mark{flex:none; width:46px; height:46px; border-radius:50%; background:var(--grey); color:#fff;
  display:flex; align-items:center; justify-content:center; transform:scale(.6); opacity:0;
  transition:transform .5s cubic-bezier(.34,1.56,.64,1), opacity .35s ease, background .45s ease;}
.demo-crm-window.arrived .demo-done-mark{background:var(--mint-ink); transform:scale(1); opacity:1;
  box-shadow:0 0 0 7px rgba(0,250,218,.28);}
.demo-done-text{display:flex; flex-direction:column; gap:2px; min-width:0;}
.demo-done-title{font-size:20px; font-weight:800; color:var(--navy); line-height:1.2;
  opacity:0; transform:translateY(4px); transition:opacity .4s ease .1s, transform .4s var(--ease) .1s;}
.demo-done-sub{font-size:13px; font-weight:600; color:var(--mint-ink); line-height:1.3;
  opacity:0; transition:opacity .4s ease .18s;}
.demo-crm-window.arrived .demo-done-title{opacity:1; transform:none;}
.demo-crm-window.arrived .demo-done-sub{opacity:1;}

.demo-crm-body{padding:18px 20px 20px; opacity:0; transform:translateY(4px);
  transition:opacity .4s ease .12s, transform .4s var(--ease) .12s;}
.demo-crm-window.arrived .demo-crm-body{opacity:1; transform:none;}
.demo-crm-name{font-size:16px; font-weight:800; color:var(--navy); margin-bottom:4px;}
.demo-crm-sub{font-size:13px; color:var(--ink); margin-bottom:12px;}
.demo-crm-tags{display:flex; flex-wrap:wrap; gap:7px;}
.demo-crm-tags span{font-size:11.5px; font-weight:700; color:var(--navy); background:var(--sky);
  border-radius:100px; padding:5px 11px;}

/* Secondary to the step indicator now, not a competing caption — the steps say
   which stage you are on, this says what is happening inside it, and it is the
   aria-live region a screen reader hears. */
.demo-status{max-width:600px; margin:24px auto 0; text-align:center; font-size:13.5px;
  color:var(--text-muted); min-height:20px; line-height:1.5;}

@media (max-width:520px){
  /* Vertical rhythm only — the 768px block later in the file sets the sides to
     a flat 24px, same as every other section. See the note on .integrations. */
  .see-action{padding:80px 5vw 74px;}
  .demo-head{margin-bottom:38px;}
  .demo-steps{max-width:380px; margin-bottom:32px;}
  .demo-step-name{font-size:14px; max-width:104px;}
  /* Dot drops 38 -> 32px here, so the connector's `top` and insets follow it:
     top = half the dot, insets = half the dot + 7. */
  .demo-step-dot{width:36px; height:36px; margin-bottom:11px;}
  .demo-step-num{font-size:14.5px;}
  .demo-step:not(:last-child)::before,
  .demo-step:not(:last-child)::after{top:18px; left:calc(50% + 25px); right:calc(-50% + 25px);}
  .demo-done-head{padding:16px 16px; gap:12px;}
  .demo-done-mark{width:40px; height:40px;}
  .demo-done-title{font-size:18px;}
  .demo-steps{padding:18px 12px;}
  .demo-chip{font-size:10.5px; padding:4px 8px;}
  .demo-row dt{flex-basis:66px;}
  .demo-row-ph{left:78px;} /* 66px dt + 12px gap — keep in step with the dt above */
  .demo-msg-body{font-size:13.5px;}
}

/* ---------- INTEGRATIONS ---------- */
/* Sits right after the demo, which already ends by pushing the record into a
   CRM — this section is that payoff spelled out. Same white ground and head
   layout as .see-action so the two read as one continuous beat. */
/* 8vw sides — same reason as .see-action above. The logo grid keeps its own
   1060px cap and only starts sharing the extra inset below ~1300px, which the
   five marks absorb by scaling. */
.integrations{background:#fff; padding:84px 8vw 78px;}
.integrations-head{max-width:var(--head-max); margin:0 auto 44px; text-align:center;}
.integrations-head h2{font-size:var(--h-section); margin-bottom:14px;}
.integrations-sub{font-size:17px; color:var(--ink); line-height:1.55;
  max-width:var(--head-sub); margin:0 auto;}

.integrations-grid{list-style:none; display:grid; grid-template-columns:repeat(5,1fr); gap:28px 20px;
  max-width:1060px; margin:0 auto; padding:0;}
/* No card around each logo: the marks sit straight on the section's white
   ground. Each cell is a two-row grid — a fixed 46px band for the logo, then
   the name — so that five marks of five different shapes (three wordmarks up
   to 4.65:1, two square-ish icons) all centre on one line and every name
   starts on the same baseline underneath. Without the fixed band the row
   ragged itself against each mark's own height. */
.integration-tile{display:grid; grid-template-rows:46px auto; align-items:center;
  justify-items:center; gap:12px; text-align:center;}
/* --logo-h is set per tile in the markup — a 4.65:1 wordmark and a 1:1 icon
   set to the same height do not read at the same weight, so each one is tuned
   to sit on the same optical line. Width is whatever the aspect ratio makes
   it, capped so a wide mark cannot burst its cell. */
.integration-logo{height:var(--logo-h,28px); width:auto; max-width:100%;
  object-fit:contain; display:block;}
/* The logos are decorative here — their <img alt> is empty on purpose, because
   this label is the accessible name and a screen reader would otherwise read
   every vendor twice. */
.integration-name{font-size:14px; font-weight:600; color:var(--text-muted); line-height:1.35;}
/* --head-sub, not a 560px of its own: this footnote sits on the same centred
   axis as the sub-line above the logos, and at 560 it broke onto a second line
   at every desktop width — the sentence was never long enough to deserve one,
   the box was just narrower than the other six numbers on this axis. */
.integrations-note{max-width:var(--head-sub); margin:32px auto 0; text-align:center; font-size:14px;
  color:var(--text-muted); line-height:1.55;}

@media (max-width:900px){
  .integrations-grid{grid-template-columns:repeat(3,1fr);}
}
@media (max-width:600px){
  /* Sides only look like 5vw here. The 768px block near the bottom of the file
     is later and sets 24px, so this shorthand now contributes the vertical
     rhythm and nothing else. Left as a shorthand because the two numbers that
     matter are still the ones written here. */
  .integrations{padding:74px 5vw 64px;}
  /* Three across, not two: with the cards gone these are just marks with a
     caption, so they need far less width than a padded tile did. */
  .integrations-grid{grid-template-columns:repeat(3,1fr); gap:26px 12px;}
  /* 42px band, not 38: Salesforce and Dynamics are 38px tall, so a 38px row
     leaves them touching both edges. The 4px side padding is what stops
     Pipedrive — the widest mark — from running edge to edge in the 94px cell
     at 375px, since its --logo-h is inline and cannot be overridden from
     here. (94px, not the 98px this note used to quote: the section gutter
     went from 5vw to a flat 24px when the title boxes were unified.) */
  .integration-tile{grid-template-rows:42px auto; gap:9px; padding:0 4px;}
  .integration-name{font-size:12.5px;}
}

/* ---------- FEATURES ---------- */
/* Two tiers sharing one three-column grid — see the section comment in
   index.html for why. .feat-grid carries the six that sell; .feat-more
   carries six one-liners under a rule, styled deliberately quieter (no filled
   icon chip, smaller heading) so the tier above keeps the eye. Same head
   layout as .see-action/.integrations so it reads as one family of sections. */
.feat{background:#fff; padding:84px 8vw 78px;}
.feat-head{max-width:var(--head-max); margin:0 auto 56px; text-align:center;}
.feat-head h2{font-size:var(--h-section);}
.feat-grid{display:grid; grid-template-columns:repeat(3,1fr); gap:24px; max-width:1160px; margin:0 auto;}
.feat-card{padding:34px 30px; border-radius:20px; background:var(--sky); border:1.5px solid var(--hairline);
  opacity:0; transform:translateY(16px); transition:opacity .6s var(--ease), transform .6s var(--ease);}
.feat.reveal .feat-card{opacity:1; transform:translateY(0);}
/* Stagger runs across all six, one row after the other. */
.feat-card:nth-child(2){transition-delay:.07s;}
.feat-card:nth-child(3){transition-delay:.14s;}
.feat-card:nth-child(4){transition-delay:.21s;}
.feat-card:nth-child(5){transition-delay:.28s;}
.feat-card:nth-child(6){transition-delay:.35s;}
.feat-card-icon{width:44px; height:44px; border-radius:12px; display:flex; align-items:center;
  justify-content:center; color:#fff; margin-bottom:20px;}
.feat-card h3{font-size:19px; margin-bottom:10px;}
.feat-card p{font-size:14.5px; line-height:1.6; color:var(--text-muted);}

/* Tier two. A <ul> so a screen reader announces it as the list of extras it
   is, with the marker off and the rule above it doing the separating. */
.feat-more{list-style:none; display:grid; grid-template-columns:repeat(3,1fr);
  gap:30px 34px; max-width:1160px; margin:52px auto 0; padding-top:46px;
  border-top:1.5px solid var(--hairline);}
.feat-mini{display:flex; gap:13px; align-items:flex-start;
  opacity:0; transform:translateY(12px); transition:opacity .6s var(--ease), transform .6s var(--ease);}
.feat.reveal .feat-mini{opacity:1; transform:translateY(0);}
.feat-mini:nth-child(2){transition-delay:.06s;}
.feat-mini:nth-child(3){transition-delay:.12s;}
.feat-mini:nth-child(4){transition-delay:.18s;}
.feat-mini:nth-child(5){transition-delay:.24s;}
.feat-mini:nth-child(6){transition-delay:.30s;}
/* flex:0 0 auto stops the icon being squeezed by a long heading beside it. */
.feat-mini-icon{flex:0 0 auto; display:flex; color:var(--blue); margin-top:1px;}
.feat-mini h4{font-size:15.5px; line-height:1.35; margin-bottom:6px;}
.feat-mini p{font-size:14px; line-height:1.55; color:var(--text-muted);}

/* Two across before collapsing to one. This block has to sit AFTER the base
   .feat-grid/.feat-more rules above, not with the other 900px rules further up
   the file — at equal specificity the later declaration wins, so a media query
   placed before them would simply be overridden. */
@media (max-width:900px){
  .feat-grid,.feat-more{grid-template-columns:repeat(2,1fr);}
}

/* ---------- PRICING ---------- */
.pricing{background:var(--sky); padding:110px 8vw 100px;}
.pricing-head{max-width:var(--head-max); margin:0 auto 14px; text-align:center;}
.pricing-head h2{font-size:var(--h-section); margin-bottom:14px;}
.pricing-head p{font-size:17px; color:var(--ink); line-height:1.55;
  max-width:var(--head-sub); margin:0 auto;}
.pricing-note{max-width:var(--head-sub); margin:0 auto 44px; text-align:center; font-size:13px; color:var(--text-muted);}
.plan-grid{display:grid; grid-template-columns:repeat(3,1fr); gap:20px; max-width:1150px; margin:0 auto; align-items:stretch;}
.plan{position:relative; background:#fff; border-radius:24px; padding:36px 32px 32px; display:flex; flex-direction:column;
  border:1.5px solid transparent;
  opacity:0; transform:translateY(16px); transition:opacity .6s var(--ease), transform .6s var(--ease);}
.pricing.reveal .plan{opacity:1; transform:translateY(0);}
.plan:nth-child(2){transition-delay:.1s;} .plan:nth-child(3){transition-delay:.2s;}
.plan.featured{background:var(--navy); color:#fff; border-color:var(--blue);
  box-shadow:0 24px 60px rgba(7,14,26,.18);}
.plan-flag{position:absolute; top:-13px; left:50%; transform:translateX(-50%); background:var(--orange); color:#fff;
  font-size:11.5px; font-weight:700; letter-spacing:1px; text-transform:uppercase; padding:6px 16px; border-radius:20px;
  white-space:nowrap;}
.plan-name{font-size:19px; font-weight:800; margin-bottom:6px;}
.plan-for{font-size:13.5px; color:var(--text-muted); margin-bottom:22px; min-height:38px; line-height:1.4;}
.plan.featured .plan-for{color:#9fb0d0;}
.plan-price{display:flex; align-items:baseline; gap:6px; margin-bottom:4px; flex-wrap:wrap;}
.plan-price .amount{font-size:42px; font-weight:800; line-height:1;}
/* A word carries far more visual weight than a short numeral at the same
   size, so the 'Custom' tier is set down to sit level with the figures. */
.plan-price .amount.sm{font-size:32px;}
.plan-price .period{font-size:14px; font-weight:600; color:var(--text-muted);}
.plan.featured .plan-price .period{color:#9fb0d0;}
.plan-billing{font-size:12.5px; color:var(--text-muted); margin-bottom:24px; min-height:18px;}
.plan.featured .plan-billing{color:#9fb0d0;}
.plan-cta{width:100%; margin-bottom:26px;}
/* The :hover here is not optional decoration — without it the Free and
   Business buttons turn navy-on-navy and the label disappears.
   `.plan .btn-outline` and `.btn-outline:hover` have the SAME specificity
   (0-2-0), and this rule sits later in the file, so it wins `color` even
   while the button is hovered: the base hover paints a navy background and
   this paints navy text on top of it. Restating the hover at 0-3-0 is what
   settles it.
   The rule generalises: anything that recolours .btn-outline for a section
   must ship its own :hover pair. .hero-btns, .plan.featured and .cta all
   already do — this was the one that did not. */
.plan .btn-outline{border-color:var(--navy); color:var(--navy);}
.plan .btn-outline:hover{background:var(--navy); color:#fff;}
.plan.featured .btn-outline{border-color:#fff; color:#fff;}
.plan.featured .btn-outline:hover{background:#fff; color:var(--navy);}
.plan-features{list-style:none; display:flex; flex-direction:column; gap:12px; font-size:14.5px; line-height:1.45;
  margin-top:auto;}
.plan-features li{display:flex; gap:10px; align-items:flex-start;}
.plan-features svg{flex-shrink:0; margin-top:3px; color:var(--mint-ink);}
.plan.featured .plan-features svg{color:var(--mint);}
.plan-features li.off{color:var(--text-muted);}
.plan-features li.off svg{color:var(--grey);}
.plan-foot{max-width:1150px; margin:34px auto 0; text-align:center; font-size:14px; color:var(--ink);}
.plan-foot a{color:var(--blue); font-weight:700; text-decoration:underline; text-underline-offset:3px;}

/* ---------- BUSINESS / LEAD GEN FORM ---------- */
.biz{background:#fff; padding:110px 8vw 100px;}
.biz-inner{display:grid; grid-template-columns:1fr 1.05fr; gap:6vw; max-width:1200px; margin:0 auto; align-items:start;}
.biz-copy h2{font-size:var(--h-section); margin-bottom:18px;}
.biz-copy > p{font-size:17px; color:var(--ink); line-height:1.6; max-width:440px; margin-bottom:30px;}
/* A plain dotted list, not four icon chips. The 34px tinted squares were the
   heaviest thing in the column and pulled the eye left, away from the form —
   the whole point of this pass was raising the form's attention ratio, and a
   supporting list does not need its own iconography to do its job. */
.biz-list{list-style:none; display:flex; flex-direction:column; gap:18px; max-width:420px;}
.biz-list li{position:relative; padding-left:17px; font-size:14.5px; color:var(--text-muted); line-height:1.5;}
.biz-list li::before{content:''; position:absolute; left:0; top:.52em; width:6px; height:6px;
  border-radius:50%; background:var(--blue);}
.biz-list strong{display:block; font-size:15.5px; font-weight:700; color:var(--navy); margin-bottom:3px;}

/* The panel is a raised white card on the section's white ground, not the flat
   --sky slab it was. That inverts where the tint sits: the card lifts off the
   page with a hairline and a soft shadow, and --sky moves onto the inputs
   themselves, which is what makes eight fields read as eight distinct targets
   instead of white boxes on a pale panel a shade away from them. */
.biz-form{background:#fff; border:1.5px solid var(--hairline); border-radius:24px; padding:34px 32px;
  box-shadow:0 18px 50px rgba(3,20,55,.07);}
.biz-form h3{font-size:23px; margin-bottom:6px; font-weight:800;}
.biz-form .form-sub{font-size:14.5px; color:var(--text-muted); margin-bottom:6px; line-height:1.5;}

.form-group{margin-top:26px;}
.form-group-label{font-size:11.5px; font-weight:800; letter-spacing:1.3px; text-transform:uppercase;
  color:var(--text-muted); margin-bottom:14px; padding-bottom:10px; border-bottom:1px solid var(--hairline);}

.field{margin-bottom:18px;}
.field-row{display:grid; grid-template-columns:1fr 1fr; gap:16px;}
.field label{display:block; font-size:13.5px; font-weight:700; margin-bottom:8px; color:var(--navy);}
.field label .req{color:var(--orange);}
/* Filled inputs, borderless at rest. The border is transparent rather than
   absent so nothing reflows by 1.5px when focus or an error paints one in. */
.field input,.field select,.field textarea{width:100%; background:var(--sky); border:1.5px solid transparent;
  border-radius:12px; padding:13px 15px; font-family:inherit; font-size:15px; color:var(--navy);
  transition:background .15s ease, border-color .15s ease, box-shadow .15s ease;}
.field input::placeholder,.field textarea::placeholder{color:var(--text-muted); opacity:.85;}
.field input:hover,.field select:hover,.field textarea:hover{background:#e4eaf3;}
.field input:focus,.field select:focus,.field textarea:focus{background:#fff; border-color:var(--blue);
  box-shadow:0 0 0 4px rgba(0,109,255,.13); outline:none;}
.field input:focus-visible,.field select:focus-visible,.field textarea:focus-visible{outline:3px solid var(--focus); outline-offset:1px;}
.field textarea{resize:vertical; min-height:92px;}
.field select{appearance:none; background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='9' viewBox='0 0 14 9' fill='none'%3E%3Cpath d='M1 1l6 6 6-6' stroke='%236b7488' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 15px center; padding-right:40px;}
.field-error{display:none; font-size:12.5px; color:#d63a1e; margin-top:7px; font-weight:600;}
.field.invalid input,.field.invalid select,.field.invalid textarea{background:#fdf2f0; border-color:#d63a1e;}
.field.invalid .field-error{display:block;}
.hp-field{position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden;}

/* The consent tick is a step, not fine print — it is the one control that
   silently blocks submission — so it sits in its own tinted well at readable
   size rather than as 12.8px grey text under the last input. */
.consent{display:flex; gap:12px; align-items:flex-start; margin:24px 0 6px; padding:14px 16px;
  background:var(--sky); border-radius:14px; font-size:13.5px; color:var(--ink); line-height:1.55;
  cursor:pointer; transition:background .15s ease;}
.consent:hover{background:#e4eaf3;}
.consent input{width:18px; height:18px; flex-shrink:0; margin-top:1px; accent-color:var(--blue); cursor:pointer;}
.consent a{color:var(--blue); text-decoration:underline; text-underline-offset:2px;}
#consentError{margin:8px 0 0;}
.form-note{margin-top:12px; text-align:center; font-size:12.5px; color:var(--text-muted); line-height:1.5;}
.form-status{margin-top:14px; font-size:14px; font-weight:600; line-height:1.5; display:none;}
.form-status.ok{display:block; color:var(--mint-ink);}
.form-status.err{display:block; color:#d63a1e;}
.biz-form .btn-primary{width:100%; margin-top:20px; padding:16px 30px; font-size:15.5px;}

/* ---------- FAQ ---------- */
.faq{background:var(--sky); padding:110px 8vw 100px;}
/* Centred, matching .faq-list (max-width:820px; margin:0 auto) below it. */
.faq-head{max-width:var(--head-max); margin:0 auto 44px; text-align:center;}
.faq h2{font-size:var(--h-section); margin-bottom:14px;}
.faq-head p{font-size:16px; color:var(--ink); max-width:var(--head-sub); margin:0 auto;}
.faq-list{max-width:820px; margin:0 auto;}
.faq-item{background:#fff; border-radius:16px; margin-bottom:14px; overflow:hidden;}
.faq-item summary{list-style:none; cursor:pointer; padding:22px 26px; font-weight:700; font-size:16.5px;
  display:flex; align-items:center; justify-content:space-between; gap:16px;}
.faq-item summary::-webkit-details-marker{display:none;}
.faq-item summary .plus{flex-shrink:0; width:22px; height:22px; position:relative;}
.faq-item summary .plus::before,.faq-item summary .plus::after{content:''; position:absolute; background:var(--navy);
  top:50%; left:50%; transform:translate(-50%,-50%);}
.faq-item summary .plus::before{width:14px; height:2px;}
.faq-item summary .plus::after{width:2px; height:14px; transition:transform .25s ease;}
.faq-item[open] summary .plus::after{transform:translate(-50%,-50%) rotate(90deg);}
.faq-item p{padding:0 26px 22px; font-size:15px; color:var(--ink); line-height:1.6; max-width:680px;}

/* ---------- CTA ---------- */
.cta{min-height:80vh; background:var(--navy); color:#fff; display:flex; align-items:center;
  padding:100px 6vw; position:relative; overflow:hidden;}
.cta-inner{display:flex; align-items:center; justify-content:space-between; gap:60px; max-width:1300px;
  width:100%; margin:0 auto; flex-wrap:wrap;}
.cta-left{flex:1; min-width:280px; text-align:left;}
.cta .badge{width:56px; height:56px; border-radius:16px; margin-bottom:26px; overflow:hidden;
  opacity:0; transform:scale(.6); transition:opacity .6s var(--ease), transform .6s var(--ease);}
.cta .badge img{width:100%; height:100%; display:block;}
.cta h2{font-size:var(--h-display); margin-bottom:24px; opacity:0; transform:translateY(20px);
  transition:opacity .7s var(--ease) .1s, transform .7s var(--ease) .1s;}
.cta-btns{display:flex; gap:14px; flex-wrap:wrap; opacity:0; transform:translateY(20px);
  transition:opacity .7s var(--ease) .25s, transform .7s var(--ease) .25s;}
.cta .btn-primary{padding:16px 34px; font-size:16.5px;}
.cta .btn-outline{padding:15px 32px; font-size:16.5px; border-color:#fff; color:#fff;}
.cta .btn-outline:hover{background:#fff; color:var(--navy);}
.cta-micro{margin-top:16px; font-size:14px; color:#8a93a8; opacity:0; transition:opacity .7s ease .4s;}
.cta.reveal .badge{opacity:1; transform:scale(1);}
.cta.reveal h2{opacity:1; transform:translateY(0);}
.cta.reveal .cta-btns{opacity:1; transform:translateY(0);}
.cta.reveal .cta-micro{opacity:1;}
.cta-right{flex:1; min-width:280px; position:relative; display:flex; justify-content:center; align-items:center;
  min-height:520px;}
.cta-doodle{position:absolute; font-family:var(--font-hand); font-weight:700; font-size:24px; opacity:0;
  transition:opacity .6s ease, transform .6s ease; z-index:3;}
.cta.reveal .cta-doodle{opacity:.9;}
.phone{width:230px; height:470px; background:#12182b; border-radius:42px; padding:8px; position:relative; z-index:2;
  box-shadow:0 40px 80px rgba(0,0,0,.5); opacity:0; transform:translateY(24px) scale(.94);
  transition:opacity .7s var(--ease) .15s, transform .7s var(--ease) .15s;}
.cta.reveal .phone{opacity:1; transform:translateY(0) scale(1);}
.phone-screen{width:100%; height:100%; background:#0b0f1a; border-radius:34px; overflow:hidden; position:relative;
  display:flex; flex-direction:column; padding:34px 12px 14px;}
.phone-island{position:absolute; top:10px; left:50%; transform:translateX(-50%); width:70px; height:16px;
  background:#12182b; border-radius:10px;}
.phone-head{display:flex; align-items:center; gap:8px; padding:0 4px 14px;
  border-bottom:1px solid rgba(255,255,255,.08); margin-bottom:14px;}
.phone-head .t{width:26px; height:26px; border-radius:8px; overflow:hidden; flex-shrink:0;}
.phone-head .t img{width:100%; height:100%; display:block;}
.phone-head span{font-size:13px; font-weight:700; color:#fff;}
.phone-bubble{border-radius:14px; padding:10px 12px; font-size:11px; line-height:1.4; margin-bottom:10px; max-width:88%;}
.phone-bubble.out{background:#101a30; border:1.5px solid var(--blue); color:#dbe6ff; align-self:flex-end; margin-left:auto;}
.phone-bubble.in{background:rgba(0,250,218,.1); border:1.5px solid var(--mint); color:#c8fff2; align-self:flex-start;}

/* Ambient "Tally in action" loop: the out-bubble lands, a typing indicator
   shows work happening, then the structured confirmation arrives — on repeat.
   All three elements stay in normal flex flow at all times (only opacity/
   transform change, nothing is display:none'd or added/removed), so the
   .phone-screen column height they sit in never changes and the frame around
   them never jumps — see the note on the markup in index.html for why that
   matters. Same var(--ease) curve as the rest of the site's reveals, just
   looped: each animation runs the full cycle, holds the finished state for a
   readable beat (80%–88%), then goes blank for a beat (88%–100%) before the
   next lap — that gap is the "clear pause" between rounds.  */
/* One fixed-height slot holding all three inputs, stacked. Height is set to
   the tallest of them (the badge photo); the text and voice bubbles are much
   shorter, and letting any of them size the column is what would make the
   phone frame lurch every 9 seconds. */
.phone-slot-out{position:relative; height:126px; margin-bottom:10px; flex:none;}
.phone-slot-out .phone-bubble.out{position:absolute; top:0; right:0; margin:0; max-width:88%;}

/* The out-bubbles run on a 13.5s cycle — three 4.5s laps, one per input type —
   while .phone-typing and .phone-bubble.in stay on 4.5s and simply repeat three
   times per cycle. 13.5 divides by 4.5 exactly, which is the only reason the
   reply lands in the right place on every lap; do not change one duration
   without the other. Each lap's window is 33.333% of the cycle, and the
   percentages below are the per-lap keyframes (0 / 4 / 80 / 88 / 100) mapped
   into it.

   Halved from 27s/9s at Raul's request (2026-08-15) — the loop is now exactly
   twice as fast. The time came out of the dead beats, NOT the reading:
   .phone-bubble.in still holds its confirmation for a full 1.8s, because that
   sentence names a company, a person, a role and a CRM and is the one thing in
   the loop a visitor has to actually read. What shrank is the wait before the
   typing pill (2.2s -> 0.5s), the pill's own hold (1.6s -> 0.6s, it is three
   dots) and the blank gap between laps (1.1s -> 0.4s). Same trade the hero
   conveyor made when it went 16s -> 8s: if this is ever sped up again, take it
   from the gaps and widen the 44%-84% stop, or the reply stops being legible
   and the loop loses the only thing it exists to show. */
.phone-bubble.out{opacity:0; transform:translateY(10px);
  animation-duration:13.5s; animation-timing-function:var(--ease); animation-iteration-count:infinite;}
.phone-bubble.out.o1{animation-name:phoneOut1;}
.phone-bubble.out.o2{animation-name:phoneOut2;}
.phone-bubble.out.o3{animation-name:phoneOut3;}
.phone-bubble.in{opacity:0; transform:translateY(10px);
  animation:phoneInIn 4.5s var(--ease) infinite;}
@keyframes phoneOut1{
  0%{opacity:0; transform:translateY(10px);}
  2.3%,26.7%{opacity:1; transform:translateY(0);}
  29.3%,100%{opacity:0; transform:translateY(-8px);}
}
@keyframes phoneOut2{
  0%,33.3%{opacity:0; transform:translateY(10px);}
  35.7%,60%{opacity:1; transform:translateY(0);}
  62.7%,100%{opacity:0; transform:translateY(-8px);}
}
@keyframes phoneOut3{
  0%,66.7%{opacity:0; transform:translateY(10px);}
  69%,93.3%{opacity:1; transform:translateY(0);}
  96%,100%{opacity:0; transform:translateY(-8px);}
}

/* ---- the voice-note input ---- */
.ph-voice{display:flex; align-items:center; gap:6px;}
.ph-play{flex:none; width:15px; height:15px; border-radius:50%; background:var(--blue); color:#fff;
  display:flex; align-items:center; justify-content:center; padding-left:1px;}
/* Fixed bar widths, not flex:1. The bubble is absolutely positioned and so
   shrink-to-fit, which gave the flexible wave track no width to divide up and
   collapsed all ten bars to nothing — the voice note rendered as a play button
   and a duration with a gap between them. */
.ph-wave{flex:none; display:flex; align-items:center; gap:2px; height:16px;}
.ph-wave i{flex:none; width:3px; height:var(--h); min-height:3px; border-radius:2px; background:var(--blue-soft);}
.ph-dur{flex:none; font-size:9.5px; font-weight:700; color:#8a95b3; font-variant-numeric:tabular-nums;}

/* ---- the badge-photo input ---- */
/* Drawn, not photographed: a real badge would mean a real name on a real
   person's pass. The bars stand in for the printed lines the OCR reads. */
.ph-badge{position:relative; display:block; width:104px; height:70px; border-radius:8px;
  background:linear-gradient(150deg,#e9eef8,#cfd9ec); padding:16px 10px 0;}
.ph-badge-clip{position:absolute; top:6px; left:50%; transform:translateX(-50%);
  width:26px; height:5px; border-radius:3px; background:#8a95b3;}
.ph-badge-l{display:block; height:5px; border-radius:3px; background:#7d8aa8; margin-bottom:6px;}
.ph-badge-l.w1{width:70%; height:7px; background:#3d4a66;}
.ph-badge-l.w2{width:52%;}
.ph-badge-l.w3{width:38%; background:#9aa6c0;}
.ph-cap{display:block; margin-top:8px; font-size:9.5px; font-weight:700; letter-spacing:.4px;
  text-transform:uppercase; color:#8a95b3;}
/* 44%-84% is the readable stop — 1.8s at the 4.5s duration, unchanged from
   what it was at 9s. Everything either side of it moved in; this did not. */
@keyframes phoneInIn{
  0%,36%{opacity:0; transform:translateY(10px);}
  44%,84%{opacity:1; transform:translateY(0);}
  92%,100%{opacity:0; transform:translateY(-8px);}
}
/* The "processing" beat between the two bubbles: a small pill of three dots,
   reserved in the flex column at all times (fixed height) so it never causes
   the layout shift a conditionally-rendered element would. */
.phone-typing{display:flex; align-items:center; gap:4px; width:40px; height:16px;
  padding:9px 12px; margin-bottom:10px; border-radius:14px; box-sizing:content-box;
  background:#101a30; border:1.5px solid rgba(255,255,255,.14);
  opacity:0; animation:phoneTypingIn 4.5s var(--ease) infinite;}
/* The dot bounce is the one duration here that is NOT tied to the lap — it is
   the pill's own idle, and it has to complete a visible bounce inside the
   0.6s the pill is now up. 1.1s left it barely past one dot. */
.phone-typing i{width:5px; height:5px; border-radius:50%; background:#8a95b3; display:block;
  animation:phoneDot .8s ease-in-out infinite;}
.phone-typing i:nth-child(2){animation-delay:.11s;}
.phone-typing i:nth-child(3){animation-delay:.22s;}
@keyframes phoneTypingIn{
  0%,12%{opacity:0;}
  18%,32%{opacity:1;}
  38%,100%{opacity:0;}
}
@keyframes phoneDot{0%,60%,100%{opacity:.3; transform:translateY(0);} 30%{opacity:1; transform:translateY(-3px);}}

/* ---------- FOOTER ---------- */
footer{background:var(--navy-soft); border:1px solid rgba(255,255,255,.08); border-radius:var(--radius-card);
  margin:0 16px 16px; overflow:hidden;}
.foot-top{display:flex; justify-content:space-between; gap:48px; flex-wrap:wrap; padding:52px 44px 40px;
  border-bottom:1px solid rgba(255,255,255,.08);}
.foot-brand{flex:1.3; min-width:230px;}
.foot-brand .foot-logo{margin-bottom:16px;}
.foot-brand .foot-logo img{height:24px; width:auto; display:block;}
.foot-brand p{font-size:14.5px; color:var(--slate); line-height:1.6; max-width:270px; margin-bottom:22px;}
.foot-social{display:flex; gap:10px;}
.foot-social a{width:34px; height:34px; border-radius:50%; border:1px solid rgba(255,255,255,.14);
  display:flex; align-items:center; justify-content:center; color:var(--text-secondary);
  transition:border-color .2s ease, color .2s ease;}
.foot-social a:hover{border-color:#fff; color:#fff;}
/* Grid rather than wrapping flex: with flex, the third column dropped to its own
   row at mid widths and left a hole beside the other two. auto-fit keeps the
   columns evenly distributed however many fit. */
.foot-cols{display:grid; grid-template-columns:repeat(auto-fit,minmax(132px,1fr)); gap:24px 40px;
  flex:2.1; min-width:320px;}
.foot-col h5{font-size:11px; letter-spacing:1.5px; text-transform:uppercase; color:#5c6684; font-weight:700;
  margin-bottom:18px;}
.foot-col a{display:block; font-size:14.5px; font-weight:600; color:#fff; opacity:.85; margin-bottom:13px;
  transition:opacity .2s ease;}
.foot-col a:hover{opacity:1;}
.foot-cta-block{flex:1.1; min-width:220px;}
.foot-cta-row{display:block; padding:14px 0; border-bottom:1px solid rgba(255,255,255,.08);}
.foot-cta-row:first-child{padding-top:0;}
.foot-cta-row:last-child{border-bottom:none;}
.foot-cta-row .fc-title{display:inline-flex; align-items:center; gap:7px; color:var(--orange); font-weight:700;
  font-size:15.5px;}
.foot-cta-row .fc-title .fc-arrow{width:22px; height:22px; border-radius:50%; background:rgba(255,100,0,.15);
  display:inline-flex; align-items:center; justify-content:center; transition:background .2s ease, transform .2s ease;}
.foot-cta-row:hover .fc-title .fc-arrow{background:var(--orange); transform:translate(1px,-1px);}
.foot-cta-row .fc-title .fc-arrow svg{width:11px; height:11px; color:var(--orange); transition:color .2s ease;}
.foot-cta-row:hover .fc-title .fc-arrow svg{color:#fff;}
.foot-cta-row .fc-sub{display:block; font-size:13px; color:#7a8296; margin-top:5px;}
.foot-wm-wrap{position:relative; padding:18px 44px 4px; overflow:hidden;}
.foot-wordmark{height:clamp(52px,10vw,132px); opacity:.06;}
.foot-wordmark img{height:100%; width:auto; display:block;}
.foot-wm-dot{position:absolute; width:16px; height:16px; border-radius:50%; background:var(--orange);
  top:26%; right:16%; opacity:.85;}
.foot-bottom{padding:20px 44px; display:flex; justify-content:space-between; flex-wrap:wrap; gap:10px;
  font-size:12.5px; color:#5c6684;}
.foot-bottom .fb-right{display:flex; gap:22px; flex-wrap:wrap;}
.foot-bottom a{color:#5c6684; transition:color .2s ease;}
.foot-bottom a:hover{color:var(--text-secondary);}

/* ==========================================================================
   RESPONSIVE
   The pinned scroll-tracks are the fragile part: their heights are multiples
   of the viewport, so on a short phone screen they need shorter tracks and
   stacked panel layouts, otherwise a user scrolls several screens per step.
   ========================================================================== */

@media (max-width:1080px){
  .navlinks{gap:22px; font-size:13.5px;}
  nav{gap:20px; padding:14px 22px;}
  .btn-login{padding:12px 16px;}
  .plan-grid{grid-template-columns:1fr; max-width:520px;}
  .plan.featured{order:-1;}
  .biz-inner{grid-template-columns:1fr; gap:48px;}
  .biz-copy > p,.biz-list{max-width:none;}
}

@media (max-width:900px){
  /* The nav is 2px taller here than the 67px --nav-h is derived from, because
     the hamburger (24px icon + 8px padding either side = 40px) is taller than
     the 38px CTA button the variable was measured off. Two pixels sounds
     ignorable and is not: --nav-h is what .hiw-sticky pins below, and the
     card's rounded top corners are drawn in exactly those first pixels, so
     being 2px short hides them behind the nav and the card reads as square on
     top. Correct the variable rather than the hamburger's tap target. */
  :root{--nav-h:69px;}
  nav{justify-content:space-between; gap:10px; flex-wrap:wrap;}
  .nav-toggle{display:block; order:3;}
  /* margin-left:auto moves from the CTA to the login pill, because the login
     pill is now the first of the right-hand group. Leave it on the CTA as well
     and the two separate instead of sitting together. */
  .btn-login{order:1; margin-left:auto; padding:12px 16px;}
  nav .btn-primary{order:2;}
  .navlinks{order:4; flex-basis:100%; flex-direction:column; gap:0; display:none;
    border-top:1px solid var(--hairline); margin-top:12px; padding-top:6px;}
  nav.open .navlinks{display:flex;}
  .navlinks a{padding:13px 2px; font-size:15px; border-bottom:1px solid #f4f6fa;}
  .navlinks a:last-child{border-bottom:none;}

  .compare-grid{grid-template-columns:1fr; gap:44px;}
  .benefits-grid{grid-template-columns:1fr;}
  .cta-inner{flex-direction:column; gap:40px;}
  .cta-left{text-align:center;}
  .cta .badge{margin-left:auto; margin-right:auto;}
  .cta-btns{justify-content:center;}
  .cta-right{min-height:440px; width:100%;}
  .foot-top{padding:44px 28px 34px; gap:36px;}
  .foot-wm-wrap,.foot-bottom{padding-left:28px; padding-right:28px;}
}

/* ---- Narrow phones: four things on one nav row, and they have to stay on it.
   Logo + "Log in" + "Start free" + hamburger measures ~349px at the 900px
   block's spacing, so anything under ~350px wraps the hamburger onto a second
   line. That is not a cosmetic wrap: nav is the element --nav-h describes, and
   .hero, .hiw-sticky and .wif-sticky all size themselves by subtracting it, so
   a 117px nav claiming to be 69px overflows the hero by 48px and pins the
   how-it-works card behind the band. Trimming the side padding, the gap and
   both pills' horizontal padding brings it to ~304px, which clears 320px with
   room. Only horizontal values change — the pills stay 38px tall, so --nav-h
   still holds. Re-measure the row before adding a fifth item to it. ---- */
@media (max-width:400px){
  nav{padding:14px 14px; gap:7px;}
  .btn-login{font-size:13.5px; padding:12px 12px;}
  nav .btn-primary{padding:10px 16px;}
}

@media (max-width:768px){
  .card-section{border-radius:24px; margin:0 10px 10px;}
  footer{border-radius:24px; margin:0 10px 10px;}

  .hero{padding:84px 24px 0;}
  .hero-btns{flex-direction:column; align-items:stretch;}
  .hero-btns a{width:100%;}

  /* Shorter pinned tracks so each step is roughly one thumb-scroll. */
  .fly-section{min-height:260vh;}
  .hiw-track{height:420vh;}

  /* Nothing about the composition forks here any more. The old two-sided
     layout could not survive a ~303px track (bubbles either side of a centre
     curve had to shrink to ~110px to keep every leader line positive), so
     mobile got a left-hand spine and its own row ordering. The phone has no
     such problem: it is already a narrow single column, so the same markup
     and the same rules simply scale. Top padding still has to clear the
     sticky nav band, and the mobile nav is the same height, so the same
     calc() applies with tighter side and bottom padding. */
  .fly-sticky{padding:calc(var(--nav-h) + 18px) 20px 28px;}
  .fly-head-start,.fly-word-end{max-width:62%;}
  /* The headlines own the top-left and bottom-right corners, so on a narrow
     screen the phone has to sit clear of both diagonally rather than filling
     the height the way it does on desktop. 74% leaves the two corners
     readable at 375px.

     The 480px cap is doing the real work on a modern phone and is the fix
     for "the mockup resizes when Chrome opens and closes its bottom bar".
     Mobile browser chrome collapsing on scroll changes the layout viewport
     by ~15px, and everything downstream of that — svh, vh, and this rule's
     own percentage — moves with it. On any viewport taller than ~760px the
     cap binds instead of the percentage, so the phone is a flat 480px and
     the toolbar can do what it likes. Below that the percentage takes over
     again, but it crosses over CONTINUOUSLY (480px is 74% of a ~649px inner
     box, so the two terms meet at the same number), which is the other half
     of the fix: the aspect-ratio fork that used to live in a
     `(max-height:760px)` query jumped the phone 17% wider the moment the
     toolbar crossed that line, and an iPhone 16 Pro Max in Chrome sits
     within a few pixels of it. One ratio, one continuous height, no jump.

     9/17 rather than the base 9/18.5 because that fork existed for a real
     reason: with the height capped by corner clearance, the taller ratio
     drags the width down to ~180px at 375px wide, too narrow to read a
     record card in. 9/17 splits the difference and still reads as a phone. */
  .fly-phone{height:min(480px,74%); aspect-ratio:9/17;}
  /* Was --h-display's flat 32px on every phone plus a 22px override inside
     the deleted short-viewport query. One clamp covers both: it only reaches
     the old 32px around 500px wide, and shrinks to 24px at 375px, where the
     kicker-plus-word block has to fit in the ~57px of clearance above the
     phone. */
  .fly-word{font-size:clamp(24px,6.4vw,32px);}
  /* 16px down to 10px buys 6px of clearance between the headline block and
     the top of the phone, which is the whole margin there is at 375x553 (an
     SE with browser chrome): the inner box is 440px tall, the phone takes
     74% of it, and the 17px kicker plus a 24px word plus the old gap came to
     57px against 57.2px of room. It measured as touching. */
  .fly-head-start .kicker{margin-bottom:10px;}
  .fly-phone-frame{border-radius:32px; padding:7px;}
  .fly-phone-island{top:13px; width:54px; height:15px;}
  .fly-screen{padding:10px 10px 6px;}
  .fly-bubble{font-size:12px; padding:8px 11px;}
  .fly-record-row{font-size:11px; gap:10px;}
  .wif{min-height:0;}
  .wif-sticky{display:none;}
  .wif-mobile{display:block; padding:80px 24px 78px;}

  /* .hiw-inner is display:block now, so the old flex centring here did nothing
     and `height:100%` pinned the stack to the top of the panel — which put the
     icon and step counter behind the sticky nav and left the bottom half of
     the screen empty. Height:auto lets .hiw-panel's own align-items:center do
     the centring, the same way it does on desktop. The right padding clears
     the rail. */
  .hiw-panel{padding:26px 46px 26px 24px;}
  .hiw-inner{max-width:none; height:auto;}
  .hiw-icon svg{width:38px; height:38px;}
  .hiw-index{font-size:13px; letter-spacing:2px;}
  .hiw-word{font-size:clamp(42px,13vw,64px); margin-top:14px;}
  .hiw-right{margin-top:16px;}
  .hiw-desc{font-size:17px; max-width:none; min-height:3.6em;}

  /* Rail collapses to unlabelled dots on phones — still vertical (per
     instruction), just small enough not to compete with the 24px content
     padding it now sits inside of. */
  /* Labels go, ticks and the filled track stay — the bar is the part that has
     to survive here. Tick drops to 9px, so the line's right offset becomes
     (9/2) - (3/2) = 3px and the end inset half a tick, 4.5px -> 5px. */
  .hiw-rail{right:14px; gap:16px;}
  .hiw-rail-label{display:none;}
  .hiw-rail-tick{width:9px; height:9px;}
  .hiw-rail::before,.hiw-rail::after{right:3px;}
  .hiw-rail::before{top:5px; bottom:5px;}
  .hiw-rail::after{top:5px; height:calc((100% - 10px) * var(--p));}

  .feat{padding:74px 24px 64px;}
  .feat-grid,.feat-more{grid-template-columns:1fr;}
  .feat-more{margin-top:44px; padding-top:38px; gap:26px;}

  /* #integrations and #try-it join the 24px gutter here rather than keeping
     the 5vw their own smaller breakpoints set. Those two rules sit earlier in
     the file, so this one wins the sides at equal specificity and leaves their
     top/bottom values alone — which is the point: the vertical rhythm below
     600/520px was tuned per section, the side gutter never was, and a section
     whose title box starts 20px inside its neighbour's is exactly the
     mismatch --head-max fixes further up the page. */
  .compare,.faq,.pricing,.biz,.integrations,.see-action{padding-left:24px; padding-right:24px;}
  .benefits{padding:80px 24px 70px;}
  .compare,.faq,.pricing{padding-top:80px; padding-bottom:70px;}
  .biz{padding-top:80px; padding-bottom:70px;}
  .biz-form{padding:28px 22px;}
  .field-row{grid-template-columns:1fr; gap:0;}
  .cta{padding:80px 24px; min-height:0;}
  .dep-tile{padding:32px 26px;}
  .dep-tile p{max-width:none;}
  .faq-item summary{padding:18px 20px; font-size:15.5px;}
  .faq-item p{padding:0 20px 18px;}
  .phone{width:206px; height:420px;}
  .cta-doodle{font-size:19px;}
  .foot-bottom{flex-direction:column;}
}

@media (max-width:480px){
  .compare h2{margin-bottom:40px;}
}

/* ---------- YOU TALK / TALLY FILES: short-viewport compaction ---------- */
/* Nothing lives here any more, and NOTHING SHOULD — this is a note about a
   query that was deleted, not a placeholder for a new one.

   The old composition was four beats stacked down a fixed-height track,
   which overflowed a short .fly-sticky no matter how hard padding, gaps and
   font sizes were trimmed, so it needed its own compact fork. The phone
   replaced that: its height is min() against its container, so it simply
   gets smaller, and .fly-screen's justify-content:flex-end means a thread
   taller than the screen clips its OLDEST message off the top rather than
   pushing the newest one out of the frame. The payoff beat is safe at every
   height by construction.

   What was left, a `(max-width:768px) and (max-height:760px)` query swapping
   the phone to 9/16, then became a bug in its own right. Mobile browsers
   change the layout viewport by ~15px when their toolbar collapses on
   scroll, an iPhone 16 Pro Max in Chrome sits within a few pixels of 760,
   and so the phone jumped 17% wider and back every time the bar moved. Its
   job — keeping the phone wide enough to read on a small screen — is done by
   the single 9/17 ratio in the 768px block above.

   The lesson generalises: on a page whose every full-height section is
   `100svh`, a viewport-HEIGHT breakpoint is a breakpoint the browser chrome
   can cross on its own, without the visitor doing anything. Width is stable;
   height is not. Solve short-viewport problems with a continuous min() or
   clamp() that degrades smoothly, never with a height query that snaps. */

/* Touch devices: hover-only affordances never fire, so make the handwritten
   overlay a no-op rather than leaving words that never reveal. */
@media (hover:none){
  .tw .tw-d{display:none;}
  .tw:hover .tw-b{opacity:1; transform:none;}
}

/* Respect the OS "reduce motion" setting: kill transforms and long transitions,
   and let every scroll-driven element rest in its finished state. */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto;}
  *,*::before,*::after{animation-duration:.01ms !important; animation-iteration-count:1 !important;
    transition-duration:.01ms !important; scroll-behavior:auto !important;}
  .hero .rise{opacity:1; transform:none;}
  .compare-row,.dep-tile,.feat-card,.feat-mini,.plan{opacity:1 !important; transform:none !important;}
  .cta .badge,.cta h2,.cta .cta-btns,.cta-micro,.cta .phone,.cta-doodle{opacity:1 !important; transform:none !important;}
  /* Show the finished conversation as a still frame instead of looping it —
     both bubbles stay put, the "processing" dots (mid-cycle only, so they'd
     otherwise freeze half-visible) are hidden outright. */
  .phone-bubble.out,.phone-bubble.in{animation:none !important; opacity:1 !important; transform:none !important;}
  .phone-typing{animation:none !important; opacity:0 !important;}
  .phone-typing i{animation:none !important;}
  /* The callout children each hold their own hidden state now, so resetting
     the group alone would leave the lines undrawn and the words invisible. */
  .wif-name,.wif-conn,.wif-conn text{opacity:1;}
  /* Progressive reveal off: every beat is simply visible and readable at
     once, and the progress line shows fully drawn instead of assembling as
     trackProgress advances. */
  /* Every message is simply present, with the thread resting at its end
     state. The margin has to be forced alongside the height: it normally
     arrives with .on, which JS only adds as the section scrolls, so without
     this the messages show but sit flush against each other. The typing
     indicator is the one beat that makes no sense held open — it stands in
     for a wait that is not happening here. */
  .fly-msg{opacity:1 !important; grid-template-rows:1fr !important; margin-top:9px !important;}
  .fly-msg:first-child{margin-top:0 !important;}
  .fly-msg-typing{display:none !important;}
  .wif-name{transform:none;}
  .wif-conn path{stroke-dashoffset:0;}
  .wif-conn circle{transform:scale(1);}
  /* The demo's own reduced-motion path is in main.js: every beat still fires,
     the gaps between them just collapse. Nothing to disable here. */
}

/* ---------- LEGAL / CONTENT PAGES ---------- */
.doc{background:#fff; max-width:820px; margin:0 auto; padding:60px 32px 90px;}
.doc h1{font-size:clamp(30px,4.4vw,44px); margin-bottom:10px;}
.doc .doc-meta{font-size:13.5px; color:var(--text-muted); margin-bottom:40px;}
.doc h2{font-size:21px; margin:38px 0 12px;}
.doc h3{font-size:17px; margin:26px 0 8px;}
.doc p,.doc li{font-size:15.5px; line-height:1.7; color:var(--ink);}
.doc p{margin-bottom:14px;}
.doc ul,.doc ol{margin:0 0 16px 22px;}
.doc li{margin-bottom:8px;}
.doc a{color:var(--blue); text-decoration:underline; text-underline-offset:3px;}
.doc table{width:100%; border-collapse:collapse; margin:0 0 20px; font-size:14.5px;}
.doc th,.doc td{text-align:left; padding:11px 12px; border-bottom:1px solid var(--hairline); vertical-align:top;}
.doc th{font-weight:700; background:var(--sky);}
.doc-table-wrap{overflow-x:auto; margin-bottom:20px;}
.doc-back{display:inline-flex; align-items:center; gap:8px; font-weight:700; font-size:14px; color:var(--blue);
  margin-bottom:32px;}

/* ---------- CONSENT BANNER ----------
   Injected by assets/js/analytics.js rather than authored into all five HTML
   files, so the home page and the four secondary pages cannot drift apart.
   It only ever exists when a GTM container ID is set in config.js AND the
   visitor has not already chosen — with no ID, nothing loads, nothing is
   stored and this block styles nothing.

   Bottom-anchored rather than a centre-screen modal on purpose: the page's
   whole argument is above the fold on a phone, and an overlay that has to be
   dismissed before the hero can be read is the pattern this product's
   audience closes the tab on. No backdrop, no focus trap, no scroll lock. */
.tally-consent{position:fixed; left:16px; right:16px; bottom:16px; z-index:200;
  background:#fff; border:1px solid var(--hairline); border-radius:20px;
  box-shadow:0 18px 50px rgba(7,14,26,.22); animation:consent-in .32s var(--ease) both;}
.tally-consent-body{display:flex; align-items:center; gap:22px; flex-wrap:wrap;
  padding:18px 44px 18px 22px; max-width:1120px; margin:0 auto;}
.tally-consent-text{flex:1 1 340px; font-size:14px; line-height:1.6; color:var(--ink); margin:0;}
.tally-consent-text a{color:var(--blue); text-decoration:underline; text-underline-offset:3px;}
.tally-consent-btns{display:flex; gap:10px; flex-shrink:0;}
.tally-consent-btns .btn-primary,.tally-consent-btns .btn-outline{font-size:14px; padding:11px 20px;}
.tally-consent-close{position:absolute; top:8px; right:10px; width:28px; height:28px;
  display:grid; place-items:center; padding:0; border:0; border-radius:50%;
  background:transparent; color:var(--text-muted); cursor:pointer; line-height:0;
  transition:background .18s var(--ease), color .18s var(--ease);}
.tally-consent-close:hover{background:var(--surface-2, rgba(7,14,26,.06)); color:var(--ink);}
.tally-consent-close:focus-visible{outline:2px solid var(--blue); outline-offset:2px;}
@keyframes consent-in{from{opacity:0; transform:translateY(14px);} to{opacity:1; transform:none;}}

@media (max-width:600px){
  .tally-consent{left:10px; right:10px; bottom:10px;}
  .tally-consent-body{padding:16px 40px 16px 16px; gap:14px;}
  .tally-consent-btns{width:100%;}
  .tally-consent-btns .btn-primary,.tally-consent-btns .btn-outline{flex:1; text-align:center;}
}

@media (prefers-reduced-motion:reduce){
  .tally-consent{animation:none;}
}

/* ==========================================================================
   LARGE DISPLAYS — 1600px and up
   --------------------------------------------------------------------------
   Everything above this block tops out between 1095px and 1440px: --h-section
   hits its 46px cap at 1095, --h-display its 60px cap at 1304, and the rest is
   fixed pixels. Past that the page stops growing while the window keeps going.
   On a real 3840x2160 at DPR 1 that left the site as a ~1200px column of small
   type adrift in the middle of the screen, and #you-talk showing a 600px phone
   in a 2009px-tall pane.

   THE RULE FOR EVERY VALUE IN HERE: the vw slope is `value-at-1600 / 1600`.
   That makes each clamp compute to exactly what the rule already had when the
   viewport is 1600px, so the tier switches on with no visible step and nothing
   at or below 1599px is affected by any of it. Read
   `clamp(46px,2.875vw,82px)` as "was 46, still 46 at the breakpoint, grows to
   82 by ~2850px". The caps are set to land around 2700-2900px rather than at
   3840, because past that the type is large enough and a 3840 window should
   get margin, not more scale. **If you add a rule here, derive its slope the
   same way or you put a step back in at 1600.**

   An earlier version of this block skipped the three pinned tracks on the
   theory that they size off viewport height and were already fine at 4K. They
   are not: `.fly-phone` is `min(600px,100%)`, `.hiw-inner` is a fixed 860px,
   and `.conveyor` caps at 380px, so all three froze with everything else. They
   are in here now.
   ========================================================================== */
@media (min-width:1600px){
  :root{
    --h-display:clamp(60px,3.75vw,106px);
    --h-section:clamp(46px,2.875vw,82px);
    --head-max:clamp(940px,58.75vw,1700px);
    --head-sub:clamp(640px,40vw,1080px);
  }

  /* ---- nav. The logo is capped at 36px and the CTA button is not scaled at
     all, both for the same reason: --nav-h (67px) is the button's 38px plus
     padding, and `.hero`'s min-height subtracts it. Let either one exceed 38px
     and the nav grows while --nav-h does not, so the hero overflows the
     viewport by the difference. `nav .btn-primary` (0-1-1) already outranks
     the base `.btn-primary`, which is what keeps the button pinned while the
     hero's own buttons below grow. ---- */
  .logo img{height:clamp(26px,1.625vw,36px);}
  .navlinks{font-size:clamp(14px,.875vw,22px); gap:clamp(30px,1.875vw,48px);}

  /* ---- hero copy ---- */
  .hero-inner{max-width:clamp(880px,55vw,1560px);}
  .hero-eyebrow{font-size:clamp(15px,.9375vw,26px); gap:clamp(9px,.5625vw,15px);
    padding:clamp(9px,.5625vw,15px) clamp(18px,1.125vw,30px) clamp(9px,.5625vw,15px) clamp(14px,.875vw,24px);}
  .hero-eyebrow svg{width:clamp(17px,1.0625vw,29px); height:clamp(17px,1.0625vw,29px);}
  .hero-head{font-size:clamp(58px,3.625vw,104px);}
  .hero-sub{font-size:clamp(20px,1.25vw,34px); max-width:clamp(640px,40vw,1100px);}
  .hero-btns{gap:clamp(16px,1vw,28px);}
  .hero-btns .btn-primary,.hero-btns .btn-outline{font-size:clamp(15px,.9375vw,24px);
    padding:clamp(14px,.875vw,22px) clamp(30px,1.875vw,48px); border-radius:100px;}
  .hero-note{font-size:clamp(13.5px,.84375vw,23px);}

  /* ---- hero conveyor. The three lanes are set in percentages (--lane:22/50/74)
     so the stage's height can grow without re-tuning them, and the card
     keyframes measure horizontal travel in vw, which scales on its own. The two
     card widths grow with their own type — widening them alone just makes a
     sparse card. ---- */
  /* The stage height is the one value in this block that is NOT width-driven:
     the base rule is `clamp(250px,38vh,380px)`, so on a 1600x900 window it is
     already 342px, not the 380px cap. Replacing the whole expression with a vw
     slope put a 38px step at the breakpoint. So the 38vh slope stays and only
     the ceiling moves — and the ceiling itself ramps with width, which is what
     keeps it continuous in both directions: at 1600 wide the inner clamp is
     380, reproducing the base exactly; at 3840 it is 660, which is what a
     2160px-tall window actually needs. */
  .conveyor{--cv-skel-w:clamp(158px,9.875vw,280px); --cv-lead-w:clamp(216px,13.5vw,384px);
    height:clamp(250px,38vh,clamp(380px,23.75vw,660px));}
  .cv-caption{font-size:clamp(21px,1.3125vw,36px);}

  /* ---- #you-talk. The phone is height-first with its width from
     aspect-ratio, so raising the height cap is the only lever it needs. The
     screen's own type has to come with it or a 1080px phone shows 12.5px
     bubbles. .fly-inner widens so the two corner headings move out to the
     edges instead of crowding the phone. ---- */
  .fly-inner{max-width:clamp(1180px,73.75vw,2100px);}
  .fly-phone{height:min(clamp(600px,37.5vw,1080px),100%);}
  .fly-bubble{font-size:clamp(12.5px,.78125vw,22px); border-radius:clamp(15px,.9375vw,26px);
    padding:clamp(9px,.5625vw,16px) clamp(12px,.75vw,21px);}
  .fly-record-row{font-size:clamp(11.5px,.71875vw,20px);}

  /* ---- #how-it-works ---- */
  .hiw-inner{max-width:clamp(860px,53.75vw,1520px);}
  .hiw-icon svg{width:clamp(46px,2.875vw,82px); height:clamp(46px,2.875vw,82px);}
  .hiw-index{font-size:clamp(15px,.9375vw,26px);}
  .hiw-word{font-size:clamp(82px,5.125vw,146px);}
  .hiw-desc{font-size:clamp(25px,1.5625vw,44px); max-width:clamp(640px,40vw,1140px);}

  /* ---- the centred section ledes, all four on the same step ---- */
  .demo-sub,.integrations-sub,.benefits-sub,
  .pricing-head p{font-size:clamp(17px,1.0625vw,30px);}
  .faq-head p{font-size:clamp(16px,1vw,28px);}
  .integrations-note{font-size:clamp(14px,.875vw,23px);}
  .pricing-note{font-size:clamp(13px,.8125vw,22px);}

  /* ---- demo ---- */
  .demo-steps{max-width:clamp(640px,40vw,1140px);}
  .demo-step-name{font-size:clamp(17px,1.0625vw,30px); max-width:clamp(150px,9.375vw,270px);}
  .demo-stage{max-width:clamp(520px,32.5vw,920px);}
  .demo-status{font-size:clamp(13.5px,.84375vw,23px); max-width:clamp(600px,37.5vw,1060px);}

  /* ---- integrations ---- */
  .integrations-grid{max-width:clamp(1060px,66.25vw,1900px);}
  .integration-name{font-size:clamp(14px,.875vw,24px);}

  /* ---- features, both tiers ---- */
  .feat-grid,.feat-more{max-width:clamp(1160px,72.5vw,2100px);}
  .feat-card h3{font-size:clamp(19px,1.1875vw,33px);}
  .feat-card p{font-size:clamp(14.5px,.90625vw,25px);}
  .feat-mini h4{font-size:clamp(15.5px,.96875vw,27px);}
  .feat-mini p{font-size:clamp(14px,.875vw,24px);}

  /* ---- before / after ---- */
  .compare-grid{max-width:clamp(1300px,81.25vw,2320px);}
  .compare-row p{font-size:clamp(18px,1.125vw,32px);}

  /* ---- deployment ---- */
  .benefits-grid{max-width:clamp(1100px,68.75vw,1960px);}
  .dep-label{font-size:clamp(27px,1.6875vw,48px);}
  .dep-tile p{font-size:clamp(15px,.9375vw,26px); max-width:clamp(360px,22.5vw,640px);}
  .dep-tile p.dep-line{font-size:clamp(17px,1.0625vw,30px);}

  /* ---- pricing. .amount.sm is the 'Custom' tier, set down a step from the
     figures on purpose — it has to grow with them or that relationship quietly
     inverts. ---- */
  .plan-grid,.plan-foot{max-width:clamp(1150px,71.875vw,2050px);}
  .plan-name{font-size:clamp(19px,1.1875vw,33px);}
  .plan-for{font-size:clamp(13.5px,.84375vw,23px);}
  .plan-price .amount{font-size:clamp(42px,2.625vw,74px);}
  .plan-price .amount.sm{font-size:clamp(32px,2vw,56px);}
  .plan-features{font-size:clamp(14.5px,.90625vw,25px);}

  /* ---- team demo ---- */
  .biz-inner{max-width:clamp(1200px,75vw,2140px);}
  .biz-copy > p{font-size:clamp(17px,1.0625vw,30px); max-width:clamp(440px,27.5vw,780px);}
  .biz-list{max-width:clamp(420px,26.25vw,750px);}

  /* ---- faq ---- */
  .faq-list{max-width:clamp(820px,51.25vw,1460px);}
  .faq-item summary{font-size:clamp(16.5px,1.03125vw,29px);}
  .faq-item p{font-size:clamp(15px,.9375vw,26px); max-width:clamp(680px,42.5vw,1220px);}

  /* ---- closing cta ---- */
  .cta-inner{max-width:clamp(1300px,81.25vw,2320px);}
}

/* ==========================================================================
   LARGE DISPLAYS, PART 2 — spacing, chrome and component internals
   --------------------------------------------------------------------------
   Part 1 scaled type and container widths, which left the page reading right
   but sitting wrong: at 2160px tall you could see three sections at once,
   because every vertical padding, gap, card inset and control on the page is
   a fixed pixel value that part 1 never touched. A section whose type doubled
   but whose 84px/78px padding did not is a section that got shorter relative
   to the screen, not taller.

   Same slope rule as part 1 — vw slope is `value-at-1600 / 1600`, so every
   declaration in here computes to exactly its current value at 1600px and
   nothing at or below 1599px changes. Caps land around 2880px.

   Kept as a separate block from part 1 purely for reading order: part 1 is
   "how big is the type and how wide is the column", this is "how much air is
   around it and how big are the parts". They are the same breakpoint and could
   be merged; do not merge them without a reason.
   ========================================================================== */
@media (min-width:1600px){
  /* The card radius is a token, so scaling it here reaches .card-section,
     .track-section and the consent bar in one go. A 32px radius on a 2100px
     card reads as a square corner. */
  :root{--radius-card:clamp(32px,2vw,58px);}

  /* ---- SECTION RHYTHM. This is the fix for "three sections in one
     viewport". The two families are 84/78 (the supporting sections) and
     110/100 (the commercial ones); both keep that relationship as they grow.
     The horizontal 8vw is already fluid and is restated only because these are
     shorthand properties. ---- */
  .compare,.benefits,.integrations,.feat{
    padding-top:clamp(84px,5.25vw,170px); padding-bottom:clamp(78px,4.875vw,158px);}
  .see-action,.pricing,.faq,.biz{
    padding-top:clamp(110px,6.875vw,220px); padding-bottom:clamp(100px,6.25vw,200px);}
  .hero{padding-top:clamp(72px,10vh,clamp(124px,7.75vw,250px));}

  /* Head-to-body gaps, all on the same slope as the paddings above. */
  .feat-head{margin-bottom:clamp(56px,3.5vw,112px);}
  .demo-head{margin-bottom:clamp(54px,3.375vw,108px);}
  .benefits-head{margin-bottom:clamp(48px,3vw,96px);}
  .integrations-head,.faq-head{margin-bottom:clamp(44px,2.75vw,88px);}
  .pricing-head{margin-bottom:clamp(14px,.875vw,28px);}
  .pricing-note{margin-bottom:clamp(44px,2.75vw,88px);}
  .compare h2{margin-bottom:clamp(60px,3.75vw,120px);}
  .integrations-note{margin-top:clamp(32px,2vw,64px);}
  .demo-status{margin-top:clamp(24px,1.5vw,48px);}
  .plan-foot{margin-top:clamp(34px,2.125vw,68px);}
  .feat-more{margin-top:clamp(52px,3.25vw,104px); padding-top:clamp(46px,2.875vw,92px);
    gap:clamp(30px,1.875vw,60px) clamp(34px,2.125vw,68px);}

  /* Grid gutters. .compare-grid and .biz-inner already gap in vw. */
  .feat-grid,.benefits-grid{gap:clamp(24px,1.5vw,48px);}
  .plan-grid{gap:clamp(20px,1.25vw,40px);}
  .integrations-grid{gap:clamp(28px,1.75vw,56px) clamp(20px,1.25vw,40px);}

  /* Card insets and their radii. */
  .feat-card{padding:clamp(34px,2.125vw,68px) clamp(30px,1.875vw,60px);
    border-radius:clamp(20px,1.25vw,36px);}
  .plan{padding:clamp(36px,2.25vw,72px) clamp(32px,2vw,64px) clamp(32px,2vw,64px);
    border-radius:clamp(24px,1.5vw,44px);}
  .dep-tile{padding:clamp(40px,2.5vw,80px) clamp(34px,2.125vw,68px);
    border-radius:clamp(22px,1.375vw,40px);}
  .faq-item{border-radius:clamp(16px,1vw,28px); margin-bottom:clamp(14px,.875vw,25px);}
  .faq-item summary{padding:clamp(22px,1.375vw,40px) clamp(26px,1.625vw,46px);
    gap:clamp(16px,1vw,28px);}
  .faq-item p{padding:0 clamp(26px,1.625vw,46px) clamp(22px,1.375vw,40px);}

  /* ---- NAV. --nav-h is not a magic number, it is the CTA button's height
     plus the bar's padding plus the border — exactly what the comment at the
     token says. Restating it as that arithmetic here lets the bar grow without
     the hero's `min-height:calc(100svh - var(--nav-h))` drifting out of step
     with it, which would leave the hero over- or under-shooting the viewport
     by the difference.

     `line-height:1` on the button is what makes the arithmetic exact rather
     than estimated: with it the button is exactly font-size + 2 * padding, so
     --nav-h can be computed instead of measured. At line-height:normal the box
     depends on the font's own metrics and the derived token came out ~1px
     short at 3840. The padding compensates — 12px against normal's 10px
     reproduces the same 38px button at 1600, so nothing moves at the
     breakpoint. Verified by asserting the nav's measured height against
     --nav-h; see HANDOFF.md. ---- */
  :root{
    --nav-pad-y:clamp(14px,.875vw,25px);
    --nav-btn-fs:clamp(14px,.875vw,25px);
    --nav-btn-pad-y:clamp(12px,.75vw,21px);
    --nav-h:calc(var(--nav-btn-fs) + var(--nav-btn-pad-y) * 2 + var(--nav-pad-y) * 2 + 1px);
  }
  nav{padding:var(--nav-pad-y) clamp(32px,2vw,58px); gap:clamp(40px,2.5vw,72px);}
  nav .btn-primary{font-size:var(--nav-btn-fs); line-height:1;
    padding:var(--nav-btn-pad-y) clamp(22px,1.375vw,40px);}
  /* Deliberately the same three tokens as the CTA above, so the login pill
     scales with it and stays exactly as tall — which is what keeps the
     measured nav height equal to the --nav-h derived from those tokens. Give
     it a size of its own and the nav outgrows the variable. */
  nav .btn-login{font-size:var(--nav-btn-fs);
    padding:var(--nav-btn-pad-y) clamp(20px,1.25vw,36px);}
  .logo img{height:clamp(26px,1.625vw,46px);}
  .logo{margin-right:clamp(8px,.5vw,14px);}

  /* ---- Buttons everywhere else: the demo's play button, the plan CTAs, the
     form's submit. .hero-btns (part 1), nav and .biz-form all carry their own
     two-class rules and override this, which is why the form's is restated. -- */
  .btn-primary,.btn-outline{font-size:clamp(15px,.9375vw,26px);
    padding:clamp(14px,.875vw,24px) clamp(30px,1.875vw,52px);}
  .btn-outline{padding:clamp(13px,.8125vw,22px) clamp(28px,1.75vw,48px);}
  .demo-play{gap:clamp(9px,.5625vw,16px);}
  .demo-play svg{width:clamp(15px,.9375vw,26px); height:clamp(15px,.9375vw,26px);}
  .plan-cta{margin-bottom:clamp(26px,1.625vw,46px);}

  /* ---- EYEBROWS. Five of them, three different rules, all 13px at rest.
     The letter-spacing has to grow with the size or a 22px word set at 2px
     tracking reads tighter than the 13px one did. ---- */
  .kicker,.demo-kicker,.integrations-kicker,.benefits-kicker,.feat-kicker{
    font-size:clamp(13px,.8125vw,22px); letter-spacing:clamp(2px,.125vw,3.4px);}
  .demo-kicker,.integrations-kicker,.benefits-kicker,.feat-kicker{
    margin-bottom:clamp(10px,.625vw,18px);}
  .plan-flag{font-size:clamp(11.5px,.71875vw,20px); letter-spacing:clamp(1px,.0625vw,1.8px);
    padding:clamp(6px,.375vw,11px) clamp(16px,1vw,28px); top:clamp(-13px,-.8125vw,-22px);}
  .form-group-label{font-size:clamp(11.5px,.71875vw,20px); letter-spacing:clamp(1.3px,.08125vw,2.3px);
    margin-bottom:clamp(14px,.875vw,25px); padding-bottom:clamp(10px,.625vw,18px);}

  /* ---- SEE IN ACTION: the step indicator. --dot drives the connector's
     `top` and its two insets, which the base rule warns must move together
     with the dot's size — expressing them off one variable is what keeps that
     true at every width instead of at two. ---- */
  .demo-steps{--dot:clamp(42px,2.625vw,74px);
    padding:clamp(22px,1.375vw,40px) clamp(26px,1.625vw,46px);
    margin-bottom:clamp(40px,2.5vw,72px); border-radius:clamp(22px,1.375vw,40px);}
  .demo-step-dot{width:var(--dot); height:var(--dot);
    margin-bottom:clamp(13px,.8125vw,23px); border-width:clamp(2px,.125vw,3.5px);}
  .demo-step-num{font-size:clamp(16px,1vw,28px);}
  .demo-step-check{width:clamp(11px,.6875vw,20px); height:clamp(11px,.6875vw,20px);}
  .demo-step:not(:last-child)::before,
  .demo-step:not(:last-child)::after{
    top:calc(var(--dot) / 2); left:calc(50% + var(--dot) / 2 + 7px);
    right:calc(-50% + var(--dot) / 2 + 7px); height:clamp(3px,.1875vw,5px);}

  /* ---- SEE IN ACTION: the three panels ---- */
  .demo-card,.demo-crm-window{border-radius:clamp(18px,1.125vw,32px);}
  .demo-card{padding:clamp(18px,1.125vw,32px) clamp(20px,1.25vw,36px) clamp(20px,1.25vw,36px);}
  .demo-chip{font-size:clamp(11px,.6875vw,19px); gap:clamp(5px,.3125vw,9px);
    padding:clamp(5px,.3125vw,9px) clamp(10px,.625vw,18px);}
  .demo-msg-body{font-size:clamp(14px,.875vw,25px); margin-top:clamp(11px,.6875vw,20px);}
  .demo-row dt{flex-basis:clamp(76px,4.75vw,135px); font-size:clamp(10.5px,.65625vw,18px);}
  .demo-row dd{font-size:clamp(14px,.875vw,25px);}
  /* left tracks the dt column plus the row gap — see the base rule. */
  .demo-row-ph{left:calc(clamp(76px,4.75vw,135px) + 12px); height:clamp(9px,.5625vw,16px);}
  .demo-next{font-size:clamp(12.5px,.78125vw,22px); margin-top:clamp(14px,.875vw,25px);
    padding-top:clamp(12px,.75vw,21px);}
  .demo-done-head{padding:clamp(19px,1.1875vw,34px) clamp(20px,1.25vw,36px);
    gap:clamp(15px,.9375vw,26px);}
  .demo-done-mark{width:clamp(46px,2.875vw,82px); height:clamp(46px,2.875vw,82px);}
  .demo-done-title{font-size:clamp(20px,1.25vw,35px);}
  .demo-done-sub{font-size:clamp(13px,.8125vw,23px);}
  .demo-crm-body{padding:clamp(18px,1.125vw,32px) clamp(20px,1.25vw,36px) clamp(20px,1.25vw,36px);}
  .demo-crm-name{font-size:clamp(16px,1vw,28px);}
  .demo-crm-sub{font-size:clamp(13px,.8125vw,23px);}
  .demo-crm-tags span{font-size:clamp(11.5px,.71875vw,20px);
    padding:clamp(5px,.3125vw,9px) clamp(11px,.6875vw,19px);}

  /* ---- #you-talk phone chrome. Part 1 grew the shell and the bubbles; the
     header, the frame and the island are the rest of it. ---- */
  .fly-phone-frame{border-radius:clamp(38px,2.375vw,68px); padding:clamp(9px,.5625vw,16px);}
  .fly-phone-island{top:clamp(16px,1vw,28px); width:clamp(64px,4vw,114px); height:clamp(18px,1.125vw,32px);}
  .fly-phone-head{gap:clamp(9px,.5625vw,16px);
    padding:clamp(8px,.5vw,14px) clamp(14px,.875vw,25px) clamp(10px,.625vw,18px);}
  .fly-phone-avatar{width:clamp(28px,1.75vw,50px); height:clamp(28px,1.75vw,50px);}
  .fly-phone-who strong{font-size:clamp(13px,.8125vw,23px);}
  .fly-phone-who span{font-size:clamp(10.5px,.65625vw,18px);}
  .fly-screen{padding:clamp(12px,.75vw,21px) clamp(12px,.75vw,21px) clamp(8px,.5vw,14px);}

  /* ---- INTEGRATIONS logo band. Each tile's --logo-h is an inline style, and
     an inline declaration beats any stylesheet rule for the same property, so
     the marks cannot be scaled by redefining --logo-h from here. They are
     reached through the data-integration attribute the markup already carries
     instead, each on its own slope from its own tuned base (34/38/38/26/24) —
     which is the point of those five different numbers: they are optical
     matches, not one size, so they have to scale independently to stay
     matched. The 46px band grows with them or a 61px mark is clipped by it. --*/
  .integration-tile{grid-template-rows:clamp(46px,2.875vw,82px) auto;
    gap:clamp(12px,.75vw,21px);}
  .integration-tile[data-integration="Zoho"] .integration-logo{height:clamp(34px,2.125vw,61px);}
  .integration-tile[data-integration="Salesforce"] .integration-logo{height:clamp(38px,2.375vw,68px);}
  .integration-tile[data-integration="Microsoft Dynamics"] .integration-logo{height:clamp(38px,2.375vw,68px);}
  .integration-tile[data-integration="HubSpot"] .integration-logo{height:clamp(26px,1.625vw,47px);}
  .integration-tile[data-integration="Pipedrive"] .integration-logo{height:clamp(24px,1.5vw,43px);}

  /* ---- before / after pills and rows ---- */
  .compare-pill{font-size:clamp(15px,.9375vw,26px); gap:clamp(10px,.625vw,18px);
    padding:clamp(11px,.6875vw,19px) clamp(24px,1.5vw,42px);
    margin-bottom:clamp(30px,1.875vw,54px);}
  .compare-pill .pbadge{width:clamp(19px,1.1875vw,33px); height:clamp(19px,1.1875vw,33px);}
  .compare-row{gap:clamp(16px,1vw,28px); padding:clamp(20px,1.25vw,36px) 0;}
  .compare-icon{width:clamp(24px,1.5vw,42px); height:clamp(24px,1.5vw,42px);}

  /* ---- pricing plan internals ---- */
  .plan-billing{font-size:clamp(12.5px,.78125vw,22px); margin-bottom:clamp(24px,1.5vw,42px);}
  .plan-features{gap:clamp(12px,.75vw,21px);}

  /* ---- TEAM DEMO FORM ---- */
  .biz-form{padding:clamp(34px,2.125vw,68px) clamp(32px,2vw,64px);
    border-radius:clamp(24px,1.5vw,44px);}
  .biz-form h3{font-size:clamp(23px,1.4375vw,40px);}
  .biz-form .form-sub{font-size:clamp(14.5px,.90625vw,25px);}
  .form-group{margin-top:clamp(26px,1.625vw,46px);}
  .field{margin-bottom:clamp(18px,1.125vw,32px);}
  .field-row{gap:clamp(16px,1vw,28px);}
  .field label{font-size:clamp(13.5px,.84375vw,23px); margin-bottom:clamp(8px,.5vw,14px);}
  .field input,.field select,.field textarea{font-size:clamp(15px,.9375vw,26px);
    padding:clamp(13px,.8125vw,23px) clamp(15px,.9375vw,26px);
    border-radius:clamp(12px,.75vw,21px);}
  .field select{padding-right:clamp(40px,2.5vw,70px);}
  .field textarea{min-height:clamp(92px,5.75vw,164px);}
  .field-error{font-size:clamp(12.5px,.78125vw,22px);}
  .consent{font-size:clamp(13.5px,.84375vw,23px); gap:clamp(12px,.75vw,21px);
    padding:clamp(14px,.875vw,25px) clamp(16px,1vw,28px);
    margin-top:clamp(24px,1.5vw,42px); border-radius:clamp(14px,.875vw,25px);}
  .consent input{width:clamp(18px,1.125vw,32px); height:clamp(18px,1.125vw,32px);}
  .form-note{font-size:clamp(12.5px,.78125vw,22px);}
  .form-status{font-size:clamp(14px,.875vw,25px);}
  .biz-form .btn-primary{font-size:clamp(15.5px,.96875vw,27px);
    padding:clamp(16px,1vw,28px) clamp(30px,1.875vw,52px); margin-top:clamp(20px,1.25vw,36px);}

  /* ---- closing CTA and its phone ---- */
  .cta .badge{width:clamp(56px,3.5vw,100px); height:clamp(56px,3.5vw,100px);
    border-radius:clamp(16px,1vw,28px); margin-bottom:clamp(26px,1.625vw,46px);}
  .cta-btns{gap:clamp(14px,.875vw,25px);}
  .cta-micro{font-size:clamp(14px,.875vw,25px); margin-top:clamp(16px,1vw,28px);}
  .cta-doodle{font-size:clamp(24px,1.5vw,42px);}
  .cta-inner{gap:clamp(60px,3.75vw,108px);}
  .phone{width:clamp(230px,14.375vw,410px); height:clamp(470px,29.375vw,840px);
    border-radius:clamp(42px,2.625vw,75px); padding:clamp(8px,.5vw,14px);}
  .phone-head{padding:0 clamp(4px,.25vw,7px) clamp(14px,.875vw,25px); gap:clamp(8px,.5vw,14px);}
  .phone-bubble{font-size:clamp(11px,.6875vw,19px); border-radius:clamp(14px,.875vw,25px);
    padding:clamp(10px,.625vw,18px) clamp(12px,.75vw,21px); margin-bottom:clamp(10px,.625vw,18px);}

  /* ---- footer ---- */
  .foot-top{padding:clamp(52px,3.25vw,94px) clamp(44px,2.75vw,79px) clamp(40px,2.5vw,72px);
    gap:clamp(48px,3vw,86px);}
  .foot-brand .foot-logo img{height:clamp(24px,1.5vw,42px);}
  .foot-brand p{font-size:clamp(14.5px,.90625vw,25px); max-width:clamp(270px,16.875vw,480px);}
  .foot-social a{width:clamp(34px,2.125vw,60px); height:clamp(34px,2.125vw,60px);}
  .foot-cols{gap:clamp(24px,1.5vw,42px) clamp(40px,2.5vw,72px);}
  .foot-col h5{font-size:clamp(11px,.6875vw,19px); letter-spacing:clamp(1.5px,.09375vw,2.6px);
    margin-bottom:clamp(18px,1.125vw,32px);}
  .foot-col a{font-size:clamp(14.5px,.90625vw,25px); margin-bottom:clamp(13px,.8125vw,23px);}
  .foot-cta-row{padding:clamp(14px,.875vw,25px) 0;}
  .foot-cta-row .fc-title{font-size:clamp(15.5px,.96875vw,27px); gap:clamp(7px,.4375vw,12px);}
  .foot-cta-row .fc-title .fc-arrow{width:clamp(22px,1.375vw,39px); height:clamp(22px,1.375vw,39px);}
  .foot-cta-row .fc-sub{font-size:clamp(13px,.8125vw,23px);}
  .foot-wm-wrap{padding:clamp(18px,1.125vw,32px) clamp(44px,2.75vw,79px) clamp(4px,.25vw,7px);}
}
