/* ============================================================================
   DeadlineFox — landing page design system
   Deliberately INDEPENDENT of the app's token set. The only value shared with
   the app is the brand orange, copied as a literal (never imported).
   App source of truth: src/app/globals.css  →  --primary: #F2662A
   ========================================================================== */
:root{
  /* --- brand (the one shared value) --- */
  --brand:          #F2662A;   /* accents, highlights, icons, large display text */
  /* Button fill is the app's own primary, so the site and the product match exactly.
     White on it is 3.13:1, below AA — the same trade-off the app already ships with
     on its own primary buttons. Chosen deliberately for brand fidelity.
     --brand-deep is the AA-safe variant (4.53:1), used for the small white-on-orange
     chips where the text is 10-13px and legibility actually suffers. */
  --brand-cta:      #F2662A;   /* = app --primary  */
  --brand-cta-hov:  #E55B20;   /* = app --primary-hover */
  --brand-deep:     #CB4E14;   /* small white-on-orange chips (10–11px) — 4.53:1 */
  --brand-tint:     #FFE9DE;
  --brand-wash:     #FFF6F1;
  --teal:           #00B5A9;   /* app's success/evergreen — used for the revenue line */

  /* --- feature accents ---------------------------------------------------------
     Light fill, dark text. These name a feature without competing with the brand
     orange, which has to stay the colour that means "do the thing". The use-case
     tags were already using this shape with hardcoded hexes; these are the same
     values, promoted so a fourth accent doesn't become a fourth one-off.
     Every pair is AA on its own fill: 5.38 / 4.89 / 5.52 / 4.82. */
  --acc-amber-bg:  #FFF3D6;  --acc-amber-fg:  #8A5A00;   /* Flash Pass */
  --acc-teal-bg:   #E4F7F5;  --acc-teal-fg:   #00776F;   /* evergreen */
  --acc-violet-bg: #EEEBFB;  --acc-violet-fg: #5B4BC4;   /* weekly */
  --acc-brand-bg:  #FFE9DE;  --acc-brand-fg:  #B34310;   /* fixed date */
  /* Deeper than the badge green on the lift arrow: that one sits on a white hero and
     only has to clear 3:1 as a graphic, this one is body-weight text at 5.02:1. */
  --acc-green-bg:  #E6F4EA;  --acc-green-fg:  #15803D;   /* gains / "up" */

  /* --- warm neutral ramp (landing-only; the app uses pure grays) --- */
  --ink:      #1B1613;
  --ink-800:  #2E2521;
  --ink-700:  #4A3C35;
  --ink-600:  #6E5C52;
  --ink-500:  #7D6B61;
  /* Halfway between --ink-500 and --ink-300, which had no step between them: they jump
     ~95 per channel where the ramp's other neighbours step 15-20. Picked as the OKLab
     midpoint rather than an sRGB average, so it is halfway to the EYE: its L lands on
     0.7013 against a true midpoint of 0.7011. The naive average came out #AA9D95, one
     unit per channel away, which is close only because these two ends share a hue.
     ⚠️ 2.63:1 on --paper. Fine for rules, markers, dividers and disabled states, but it
     clears neither the 4.5:1 for body text nor the 3:1 for UI components. Use --ink-500
     if it has to be read. */
  --ink-400:  #A99C93;
  --ink-300:  #D8CFC9;
  --ink-200:  #EAE3DE;
  --ink-100:  #F4EFEB;
  --ink-50:   #F8F6F4;   /* sits between --ink-100 and --paper-alt */
  --paper:    #FFFFFF;
  --paper-alt:#FAF7F5;

  /* --- type scale (steep; landing, not app UI) --- */
  --fs-hero:  clamp(2.6rem, 4.8vw, 3.9rem);
  --fs-h2:    clamp(1.95rem, 3.6vw, 2.9rem);
  /* 17px. Was 1.3125rem, which only ever applied to ONE heading on the page: every
     other h3 sat in a section that overrode it to exactly this value. The token now
     states the real default and those four overrides are gone. Every remaining h3 is a
     card title -- the one heading that wanted a larger size became an <h2> instead. */
  --fs-h3:    1.0625rem;
  --fs-lead:  clamp(1.0625rem, 1.4vw, 1.1875rem);
  --fs-body:  1.0625rem;
  --fs-sm:    0.9375rem;
  --fs-xs:    0.8125rem;
  /* 13px. Now numerically equal to --fs-xs but kept as its own token: eyebrows are a
     distinct role and have already been sized independently of body small text. */
  --fs-eyebrow:0.8125rem;

  /* --- radii (generous; the app uses 3px) --- */
  --r-sm: 8px; --r-md: 14px; --r-lg: 20px; --r-xl: 28px; --r-pill: 999px;

  /* --- elevation --- */
  --sh-sm: 0 1px 2px rgba(27,22,19,.04), 0 2px 6px rgba(27,22,19,.05);
  --sh-md: 0 2px 4px rgba(27,22,19,.04), 0 10px 24px -6px rgba(27,22,19,.09);
  --sh-lg: 0 4px 8px rgba(27,22,19,.04), 0 24px 48px -12px rgba(27,22,19,.14);
  --sh-brand: 0 6px 22px -6px rgba(242,102,42,.45);

  --wrap: 1140px;
  /* Ceiling lowered 7.5rem -> 6rem; floor and slope untouched. Desktop tightens from
     120px to 96px while phones stay at the 72px they already had, which is the point:
     a flat value can't do both, since mobile was already sitting on the 4.5rem floor
     and any single number big enough for desktop would have loosened it.
     Crossover: 72px below 800px wide, scaling to 96px at 1066px and flat above. */
  --sect: clamp(4.5rem, 9vw, 6rem);
}

*,*::before,*::after{box-sizing:border-box;}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;}
body{
  margin:0;
  font-family:'Inter','Inter var',-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;
  font-size:var(--fs-body);
  line-height:1.65;
  color:var(--ink-600);
  background:var(--paper);
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  font-feature-settings:'cv11','ss01';
}
/* one tracking value across every headline level */
h1,h2,h3,h4{color:var(--ink);margin:0;letter-spacing:-.02em;line-height:1.12;font-weight:700;}
h1{font-size:var(--fs-hero);line-height:1.04;font-weight:800;}
h2{font-size:var(--fs-h2);}
h3{font-size:var(--fs-h3);font-weight:650;}
p{margin:0;}
a{color:inherit;text-decoration:none;}
img,svg{display:block;max-width:100%;}
ul{margin:0;padding:0;list-style:none;}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;}
:focus-visible{outline:2.5px solid var(--brand);outline-offset:3px;border-radius:4px;}

.wrap{max-width:var(--wrap);margin-inline:auto;padding-inline:clamp(1.25rem,4vw,2rem);}
/* header is static, so anchors only need a little breathing room */
section[id]{scroll-margin-top:24px;}
.section{padding-block:var(--sect);}
.lead{font-size:var(--fs-lead);color:var(--ink-600);line-height:1.6;}
.eyebrow{
  font-size:var(--fs-eyebrow);font-weight:700;letter-spacing:.14em;
  color:var(--brand);margin-bottom:.9rem;
}
/* The one eyebrow carrying a mark (Flash Pass). inline-flex + align-items rather than
   vertical-align on the svg: centring by construction, with no tuned pixel offset to
   go wrong at a font-size the icon does not follow. The icon inherits currentColor,
   so it tracks the eyebrow on both the light and dark surfaces without its own rule. */
.eyebrow-icon{display:inline-flex;align-items:center;gap:.4375rem;}
.eyebrow-mark{width:13px;height:13px;flex:none;}
.center{text-align:center;}
.measure{max-width:44ch;}
.measure-wide{max-width:56ch;}
/* For a line that should hold on one row at desktop. Still a max-width, not nowrap —
   it wraps normally once the viewport is narrower than the text. */
.measure-xl{max-width:66ch;}
.mx-auto{margin-inline:auto;}

/* ---------- buttons ---------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:.5rem;
  font-size:var(--fs-sm);font-weight:600;letter-spacing:-.01em;
  padding:.8125rem 1.375rem;border-radius:var(--r-pill);
  transition:background-color .18s ease, color .18s ease, box-shadow .22s ease, transform .18s ease;
  white-space:nowrap;
}
.btn:active{transform:translateY(1px);}
.btn-primary{background:var(--brand-cta);color:#fff;box-shadow:var(--sh-brand);}
.btn-primary:hover{background:var(--brand-cta-hov);box-shadow:0 10px 28px -6px rgba(242,102,42,.55);}
.btn-ghost{background:var(--paper);color:var(--ink-800);box-shadow:inset 0 0 0 1px var(--ink-200), var(--sh-sm);}
.btn-ghost:hover{box-shadow:inset 0 0 0 1px var(--ink-300), var(--sh-md);}
/* Used by one button, the Pro card's. Outlined white as of 2026-08-17; it was a filled
   #9B928C before, under the name .btn-dark, which described the old fill and was left
   describing the lightest button on the page. Renamed to .btn-outline in the same pass.
   ⚠️ Not to be confused with .btn-ghost above, which is a different light button:
   --paper fill, --ink-800 text, a fainter --ink-200 edge drawn as an inset box-shadow,
   and the inherited 600 weight. This one is heavier-edged and lighter-weighted. Worth
   knowing that .btn-ghost currently has NO call sites -- the hero's was removed in
   e186624 -- so if these two ever want consolidating, that is the one to fold in.
   The border is a real border rather than .btn-ghost's inset box-shadow, because that is
   what was specified. ⚠️ box-sizing:border-box does NOT absorb it: these buttons set no
   width or height, so they size to content and the border ADDS 2px to each axis. Left
   alone it stood 1px proud above and below the Lifetime button beside it, which sits on
   the same row. The padding therefore takes the border back off, 13/22 -> 12/21, so the
   outer box still matches. Any future border change here has to move the padding with it.
   (.btn-ghost avoids the whole problem with an inset box-shadow, which draws no box.)
   ✅ This fixes an accessibility exception rather than adding one: white on #9B928C was
   3.05:1, under AA. --ink-700 on white is 10.56:1.
   The 1px border itself is 2.67:1, under the 3:1 for a UI boundary, but the button is
   also carried by its text and its shape, so the outline is not the only signal. */
.btn-outline{background:#ffffff;color:var(--ink-700);border:1px solid var(--ink-400);font-weight:500;
          padding:calc(.8125rem - 1px) calc(1.375rem - 1px);}
/* Hover darkens the fill a step and strengthens the outline with it. --ink-50 is the
   token for exactly this, one step off white, so the shift stays slight; --ink-100 is the
   next step up if it reads too subtle on a real screen. The old rule swapped the
   background to #8A817B, which on a white button would have flipped it to a dark fill.
   .btn already transitions background-color, so this eases rather than snaps. */
.btn-outline:hover{background:var(--ink-50);border-color:var(--ink-500);}
.btn-lg{padding:1rem 1.75rem;font-size:1.0625rem;}
.btn-sm{padding:.625rem 1.125rem;font-size:.875rem;}
.btn .ico{width:17px;height:17px;transition:transform .22s ease;}
.btn-primary:hover .ico{transform:translateX(3px);}
/* The Pro button's "Purchase Lifetime first" arrow ships in both directions and CSS picks
   one; see landing.js. Left is the default because that is the desktop layout, where the
   Lifetime card sits beside it. The stacked case is switched in the 680px query, the same
   breakpoint .price-grid drops to one column at. Hidden means display:none, so the hidden
   arrow is not a flex item and .btn's .5rem gap does not reserve space for it. */
.pro-arrow-up{display:none;}

/* ---------- header ---------- */
/* static — scrolls away with the page, no sticky/blur behaviour */
.site-header{background:var(--paper);}
.hdr{display:flex;align-items:center;gap:2.25rem;height:76px;}
.logo{display:flex;align-items:center;}
/* object-fit is a safety net, not styling. The img carries an explicit height and picks
   up max-width:100% from the reset, so any container narrower than the lockup clamps the
   WIDTH while the height stays pinned, which squashes the mark horizontally rather than
   scaling it down. `contain` makes that impossible: it scales to fit and keeps the ratio.
   This bit on 2026-08-17 when the lockup went from 4.79:1 to 5.95:1 and suddenly wanted
   202px at 34px tall -- the phone header rendered it at 4.98 and the footer at 4.62. */
/* 30px tall, which at the lockup's 5.95:1 is 178.6px wide. The files are cropped to the
   artwork's exact bounds, so box and artwork are the same thing here and this number
   needs no compensating. (4 units of viewBox bleed were tried on 2026-08-17 and reverted:
   measured against the unbled file at matched scale, the antialiased edge was identical,
   so it bought nothing and made every size 113/105 of what it looked like.) */
.logo img{height:30px;width:auto;flex:none;object-fit:contain;object-position:left center;}
.hdr-actions{margin-left:auto;display:flex;align-items:center;gap:1.125rem;}
.hdr-actions .signin{font-size:var(--fs-sm);font-weight:550;color:var(--ink-700);}
.hdr-actions .signin:hover{color:var(--ink);}

/* ---------- hero ---------- */
.hero{position:relative;overflow:hidden;padding-top:clamp(3.5rem,6vw,5.5rem);padding-bottom:clamp(4rem,7vw,6rem);}
/* ============================================================================
   HERO BACKGROUND — Rings: concentric ripples out from behind the timer, echoing
   the pulse dot on the capsule itself. Paints inside one full-bleed layer plus a
   pseudo-element; .hero's own overflow:hidden clips anything past the edges.
   Was one of two data-bg options; the alternative (Glow, soft warm blobs) and the
   switcher that toggled them were removed 2026-08-14, so these are unconditional
   now and .hero carries no data-bg attribute.
   ========================================================================== */
.hero-bg{
  position:absolute;inset:0;pointer-events:none;overflow:hidden;
  background:radial-gradient(34% 44% at 72% 44%, rgba(242,102,42,.17), transparent 70%);
}
.hero-bg::before,.hero-bg::after{
  content:'';position:absolute;inset:0;pointer-events:none;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-size:100% 100%;mask-size:100% 100%;
}
.hero-bg::before{
  inset:auto;left:72%;top:44%;width:1500px;height:1500px;transform:translate(-50%,-50%);
  background:repeating-radial-gradient(circle, rgba(242,102,42,.16) 0 1.5px, transparent 1.5px 68px);
  -webkit-mask-image:radial-gradient(circle, #000 6%, transparent 52%);
  mask-image:radial-gradient(circle, #000 6%, transparent 52%);
}
/* On phones the rings are centred off to one side of a now full-width stage and read as
   stray arcs rather than a halo. Hidden — the .hero-bg wash underneath still shows. */
@media (max-width:700px){
  .hero-bg::before{display:none;}
}

.hero .wrap{position:relative;}
/* balanced two-column — the timer is at true product size now, so it no longer
   needs the edge-bleed that the oversized version did */
.hero-grid{display:grid;grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);gap:clamp(2rem,5vw,4.5rem);align-items:center;}
h1 .hl{color:var(--brand);}
.hero p.lead{margin-top:1.375rem;max-width:34rem;}
.cta-row{display:flex;flex-wrap:wrap;gap:.75rem;margin-top:2.125rem;}
/* No pill: the wash fill, tint border, padding and radius were all dropped 2026-08-17.
   Losing the padding is the point of losing the fill, so the text now starts on the
   column's left edge, in line with the h1, the lead and the CTA row above it.
   line-height is stated here rather than inherited so the icon nudge below can be
   derived from it and the two cannot drift apart. */
.guarantee{
  display:inline-flex;align-items:flex-start;gap:.5rem;margin-top:2rem;
  font-size:15px;font-weight:500;line-height:1.65;color:var(--ink-800);
}
/* flex-start, not center: if this wraps on a narrow phone, centring would hang the
   shield in the gutter between the two lines instead of beside the first one. The margin
   optically centres the icon in the first line box, which is 1.65em = 24.75px at 15px.
   ⚠️ The three numbers here are one calculation: font-size, line-height and the icon's
   own size. Change any one and re-derive this margin, or the shield drifts off the line.
   At 20px in a 24.75px box the slack is only 2.4px a side, so it is easy to see. */
.guarantee svg{
  width:20px;height:20px;color:var(--brand);flex:none;
  margin-top:calc((1.65em - 20px) / 2);
}

/* ---------- hero timer stage ---------- */
.timer-stage{
  position:relative;display:flex;flex-direction:column;
  justify-content:center;align-items:center;gap:1.125rem;min-height:230px;
}
/* Plain container now — the entrance moved onto .capsule-enter so the browser frame
   in variant B can stay stationary while only the capsule travels. */
.timer-slide{width:100%;}   /* so the variants' % widths resolve against the column */
.timer-halo{
  position:absolute;width:min(430px,90%);aspect-ratio:1;border-radius:50%;
  background:radial-gradient(circle, rgba(242,102,42,.24), transparent 68%);
  filter:blur(28px);opacity:0;transition:opacity 1s ease .25s;
}
.timer-slide.in ~ .timer-halo, .timer-halo.in{opacity:1;}
/* No pill — the marks sit straight on the hero background. */
.lift-badge{
  position:relative;z-index:3;
  /* One row: arrow, figure, then the caption to its right on two lines. .3rem is the
     arrow-to-figure gap; flex `gap` applies between every child, so the caption adds its
     own margin-left to keep the figure-to-caption spacing wider than that. */
  display:flex;align-items:center;gap:.3rem;
  /* Offsets from the browser above. margin-top adds to .timer-stage's 18px flex gap and
     genuinely reserves the space; `left` only shifts the paint, which is what we want
     horizontally — the badge is centred by align-items and nudging it with a margin
     would move it half the value, not the whole. NOT transform: the entrance animation
     below owns that property and resets it to none, which would undo any offset. */
  margin-top:10px;left:-15px;
  opacity:0;transform:translateY(10px) scale(.9);
  /* delayed past the timer's own 1s wait + .95s travel so it arrives after it settles */
  transition:opacity .5s cubic-bezier(.16,1,.3,1) .55s, transform .5s cubic-bezier(.16,1,.3,1) .55s;
}
.lift-badge.in{opacity:1;transform:none;}
/* A true green rather than the palette's teal: an up-triangle reading as growth is the
   whole job here, and teal doesn't carry that the way green does. The one colour on the
   page from outside the token set — change this line to var(--teal) to bring it back in. */
/* 20px, set against what was a 1.4rem figure. The figure is 1.8rem now, so the arrow no
   longer tracks its cap height -- left as-is because it was not asked for, but this is
   the line to change if the triangle starts reading small beside the number. */
.lift-arrow{width:20px;height:20px;flex:none;color:#3CB44B;}
.lift-pct{font-size:1.8rem;font-weight:700;color:var(--ink-700);letter-spacing:-.03em;line-height:1;}
/* margin-left is back with the row layout: it separates the caption from the figure by
   more than the flex gap, which applies equally between every child. */
.lift-text{font-size:11px;font-weight:400;color:var(--ink-500);line-height:1.3;margin-left:.5rem;}

/* ============================================================================
   CAPSULE TIMER — faithful copy of the app's `capsule` inline preset.
   Geometry copied from src/app/t/[token]/route.ts (production embed CSS):
     pill      background + 1px border + 999px radius + .55em .95em .55em .7em
     .dot      .5em square, .6em right margin, layered .6em/1.4em glow,
               opacity cross-faded .3s — flashed once per real second BY JS,
               deliberately not a CSS keyframe (keeps multiple instances and a
               late page load in phase with the true second).
     .prefix   1em / 500 / .5em right margin
     .n        1em / 700 / -.01em tracking, tabular figures
     .days     .4em right margin;  .dsuf  .03em left margin;  .sep  0 .04em
   Markup order matches buildCapsuleHTML(): icon → prefix → digits.
   Size is the app's real "Large": baseFontPx 16 × TIMER_SIZE_SCALE.large 1.2 = 19.2px.
   (ecSizeOverrides is empty, so Capsule has no per-style override of that scale.)
   No responsive scaling — this is exactly what a customer's Large Capsule renders at.
   ========================================================================== */
.ec-it[data-style="capsule"]{
  --base:16px;                       /* preset baseFontPx */
  --k:1.2;                           /* TIMER_SIZE_SCALE.large */
  position:relative;z-index:2;
  font-size:calc(var(--base) * var(--k));
  font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;
  display:inline-flex;align-items:center;line-height:1;
  font-variant-numeric:tabular-nums;
  background:#1B1613;
  border:1px solid #3D2E25;
  border-radius:999px;
  padding:.55em .95em .55em .7em;
  box-shadow:0 18px 40px -12px rgba(27,22,19,.5), 0 2px 6px rgba(27,22,19,.18);
}
.ec-it .dot{
  position:relative;width:.5em;height:.5em;border-radius:50%;
  background:#F2662A;margin-right:.6em;flex-shrink:0;
  box-shadow:0 0 .6em rgba(242,102,42,1), 0 0 1.4em rgba(242,102,42,.6);
  transition:opacity .3s ease;
}
.ec-it .prefix{font-size:1em;font-weight:500;color:#F2EDE9;margin-right:.5em;white-space:nowrap;}
.ec-it .n{font-size:1em;font-weight:700;color:#F2662A;letter-spacing:-.01em;}
.ec-it .days{display:inline-flex;align-items:baseline;margin-right:.4em;}
.ec-it .dsuf{font-size:1em;font-weight:700;color:#F2662A;margin-left:.03em;}
.ec-it .sep{font-size:1em;font-weight:700;color:#F2662A;margin:0 .04em;}
/* ---------- floating dock (mimics the product's own floating widget) ----------
   A second Capsule, fixed bottom-left, that slides in once the hero timer has
   scrolled out of view — the same thing a customer's floating timer does on their
   page. Both instances are driven by the one countdown tick, so they never drift. */
.timer-dock{
  position:fixed;left:24px;bottom:24px;z-index:70;
  /* travels in from, and back out to, the left edge of the window */
  opacity:0;transform:translateX(calc(-100% - 32px));pointer-events:none;
  transition:opacity .38s ease, transform .52s cubic-bezier(.16,1,.3,1);
}
.timer-dock.in{opacity:1;transform:none;pointer-events:auto;}
@media (max-width:620px){
  .timer-dock{left:12px;right:12px;bottom:12px;display:flex;justify-content:center;}
}

/* ============================================================================
   HERO VARIANTS — A/B comparison only. Once one is chosen the other and the
   toggle come out; nothing else on the page depends on them.
   ========================================================================== */

/* shared mock chrome. No outer border: the shadow alone carries the frame, so the
   browser reads as a surface rather than an outlined box. */
.mock{background:var(--paper);border-radius:10px;box-shadow:var(--sh-lg);overflow:hidden;}
/* Fixed 36px rather than padding-derived, so the bar's height is stated once and does
   not move if the dots or their gap ever change. The address pill was removed, which is
   why the left inset is explicit now — the dots were previously spaced by the bar's own
   horizontal padding. */
.mock-bar{display:flex;align-items:center;gap:.3125rem;height:36px;padding:0 .5625rem 0 12px;background:var(--ink-100);border-bottom:1px solid var(--ink-200);}
.mock-bar i{width:7px;height:7px;border-radius:50%;background:var(--ink-300);flex:none;}
.mock-body{padding:1rem 1.125rem;display:flex;flex-direction:column;gap:.5rem;}
/* every skeleton part sits on --ink-50 */
.ln{height:8px;border-radius:4px;background:var(--ink-50);}
.ln.hd{height:12px;width:58%;background:var(--ink-50);}
.sk-hero{height:64px;border-radius:8px;background:var(--ink-50);}
.sk-row{display:grid;grid-template-columns:repeat(3,1fr);gap:.6875rem;}
.sk-card{height:56px;border-radius:8px;background:var(--ink-50);}
.ln.w88{width:88%;} .ln.w70{width:70%;} .ln.w80{width:80%;}

/* ---------------- the real floating widget (layout_style:'floating', Large) -------------
   Geometry from ecWidgetSizeRules() + the `sizes.large` table in route.ts:
   icon slot 38 (dotSlot), dot 10, cta 19, time 19, gPulse 4, gCta 10,
   btnH 38 / btnPx 16 / btnFont 14 / gBtn 14, and padding that becomes a uniform
   py (8px) on all four edges while the button is showing. */
.ec-widget{
  display:inline-flex;align-items:center;
  background:#0E1116;border-radius:999px;padding:8px;
  font-family:'Inter',-apple-system,BlinkMacSystemFont,sans-serif;
  line-height:1;font-variant-numeric:tabular-nums;white-space:nowrap;
  box-shadow:0 14px 34px -10px rgba(0,0,0,.55);
}
.ecw-icon{width:38px;height:38px;display:grid;place-items:center;flex:none;}
.ecw-dot{width:10px;height:10px;border-radius:50%;background:#F2662A;box-shadow:0 0 12px #F2662A,0 0 28px #F2662A99;transition:opacity .3s ease;}
.ecw-cta{font-size:19px;color:#fff;margin-left:4px;font-weight:500;}
.ecw-time{font-size:19px;color:#fff;margin-left:10px;font-weight:600;}
.ecw-btn{
  height:38px;padding:0 16px;font-size:14px;margin-left:14px;
  background:#F2662A;color:#fff;border-radius:999px;font-weight:650;
  display:inline-flex;align-items:center;flex:none;
}

/* ---------------- variant A — the Capsule on its own ---------------- */

/* ---------------- variant B — the widget on a real page ----------------
   The frame bleeds toward the viewport edge (bounded by the real gutter, so it can
   never cause horizontal scroll) and the widget sits bottom-left inside it, exactly
   where a customer's floating widget lands on their own page. */
.showcase{margin-right:calc(-1 * max(0px, min(130px, (100vw - 1220px) / 2)));}
.showcase .mock{position:relative;}
.showcase .mock-body{padding:1.625rem 1.75rem;gap:.6875rem;min-height:340px;}
.showcase .ln{height:9px;}
.showcase .ln.hd{height:15px;}
.showcase .widget-slot{position:absolute;left:18px;bottom:18px;}
.showcase .ec-widget{transform:scale(.86);transform-origin:left bottom;}

/* The entrance lives on this wrapper, never on .ec-widget itself — the widget already
   carries a scale() in variant B, and composing the two would mean rewriting both
   together on every frame. The browser frame around it stays put. */
.capsule-enter{opacity:0;will-change:transform,opacity;}

@media (max-width:1020px){
  .showcase{margin-right:0;}
}
/* Below ~700px the widget steps down to the app's own `small` size table rather than
   being transform-scaled — a transform leaves the layout box at full width, which is
   what pushed the hero column past the viewport. Values from sizes.small in route.ts. */
@media (max-width:700px){
  /* (the phone entrance is a short vertical rise instead of the edge slide — that
     branch lives in runHeroEntrance, not here, so it isn't restated as dead CSS) */
  .showcase .ec-widget{transform:none;}
  /* The card row is dropped on phones, not just shrunk. min-height alone could not get
     this near 225px: the skeletons plus the 80px bottom reserve came to 297px of real
     content, so the old 220px min-height was never binding. Removing the row (56px plus
     its 11px gap) lands the body at ~230px. */
  .showcase .sk-row{display:none;}
  .showcase .mock-body{min-height:225px;padding:1.25rem 1.25rem 5rem;}
  .showcase .widget-slot{left:12px;bottom:12px;}
  .ec-widget{padding:6px;}
  .ecw-icon{width:28px;height:28px;}
  .ecw-dot{width:8px;height:8px;box-shadow:0 0 9.6px #F2662A,0 0 22.4px #F2662A99;}
  .ecw-cta{font-size:14px;margin-left:4px;}
  .ecw-time{font-size:14px;margin-left:6px;}
  .ecw-btn{height:28px;padding:0 12px;font-size:12px;margin-left:10px;}
}
@media (max-width:430px){
  .ecw-cta{display:none;}          /* the label is the first thing to go, then the button */
  .ecw-time{margin-left:4px;}
}

/* ---------- ESP strip ---------- */
/* sits between the dark proof section and the (paper-alt) steps section, so it stays
   white — otherwise it and .steps read as one undivided block */
/* A full section, so .section supplies padding-block -- the old 2.5rem strip value was
   dropped rather than left, because .esp is declared after .section and would win.
   OFF-WHITE is load-bearing, not decorative: the chips carry no border, so their white
   fill against this surface is the only thing that makes them visible. Do not switch to
   --paper without giving the chips an edge back.
   border-top because #analytics above is off-white too and nothing else separates them;
   #features below is white and needs none. */
.esp{background:var(--paper-alt);border-top:1px solid var(--ink-200);}
/* An h3 now, so it takes the page's heading size, weight and ink from the base h3 rule.
   The old uppercase-label tracking and muted colour are dropped rather than overridden:
   the element carries them from h1-h4 already, and leaving letter-spacing:.1em on a
   sentence-case heading would space it like a label. margin-bottom is kept because the
   base rule zeroes heading margins. */
.esp-row{max-width:880px;margin:clamp(2.5rem,5vw,3.25rem) auto 0;display:flex;flex-wrap:wrap;justify-content:center;gap:.625rem;}
/* An edge and a fill, so each name reads as an object rather than a word in a sentence.
   White on the section's off-white, which is what separates the chip from the page --
   the same pair the .uc cards use one section up. */
/* No border: the white fill on the section's off-white is what defines the chip, with
   --sh-sm giving it just enough lift to read as a surface rather than a patch. */
.esp-chip{
  display:inline-flex;align-items:center;padding:.5rem .875rem;
  border-radius:var(--r-pill);background:var(--paper);
  font-size:var(--fs-sm);font-weight:500;letter-spacing:-.01em;color:var(--ink-500);
  box-shadow:var(--sh-sm);
  transition:box-shadow .2s ease, transform .2s ease, color .2s ease;
}
/* Hover works the colour and the lift, since there is no longer a border to tint. */
.esp-chip:hover{color:var(--ink-700);box-shadow:var(--sh-md);transform:translateY(-1px);}


/* ---------- dark proof section ---------- */
.proof{
  position:relative;background:var(--ink);color:#C9BEB6;overflow:hidden;
}
.proof::before{
  content:'';position:absolute;inset:0;pointer-events:none;
  background:
    radial-gradient(52% 44% at 50% 0%, rgba(242,102,42,.17), transparent 68%),
    radial-gradient(40% 40% at 88% 88%, rgba(0,181,169,.10), transparent 70%);
}
.proof .wrap{position:relative;}
.proof h2{color:#fff;}
.proof h2 .hl{color:var(--brand);}
.proof .lead{color:#A6988E;}
.proof .eyebrow{color:var(--brand);}
/* the hero's own background, protruding down into the dark section — reads as the
   hero pointing into what's below, not as a shape belonging to the dark section */
.notch{
  position:absolute;top:-1px;left:50%;transform:translateX(-50%);
  width:44px;height:22px;color:var(--paper);
}
.chart-card{
  margin-top:clamp(2.5rem,5vw,3.5rem);
  background:var(--paper);border-radius:var(--r-xl);
  box-shadow:0 30px 70px -20px rgba(0,0,0,.55);
  padding:clamp(1.25rem,3vw,2rem);
}
.chart-head{display:flex;flex-wrap:wrap;gap:.75rem 1.5rem;align-items:baseline;justify-content:space-between;margin-bottom:.5rem;padding-inline:.25rem;}
.chart-head p{font-size:var(--fs-xs);color:var(--ink-500);}
.chart-legend{display:flex;gap:1.125rem;font-size:var(--fs-xs);font-weight:550;color:var(--ink-500);}
.chart-legend i{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:.4375rem;vertical-align:middle;}
.chart svg{width:100%;height:auto;}
.chart .grid-line{stroke:var(--ink-200);stroke-width:1;stroke-dasharray:3 4;}
.chart .axis-t{font-size:11px;font-weight:550;fill:var(--ink-500);font-family:inherit;}
.chart .rev-line{fill:none;stroke:var(--teal);stroke-width:2.75;stroke-linecap:round;stroke-linejoin:round;}
.chart .rev-area{fill:url(#revfill);opacity:0;transition:opacity .8s ease .9s;}
.chart .dot-pt{fill:#fff;stroke:var(--teal);stroke-width:2.5;opacity:0;}
.chart .ring{fill:none;stroke:var(--brand);stroke-width:2.5;opacity:0;}
.chart .anno{opacity:0;}
.chart .anno-t{font-size:15px;font-weight:650;fill:var(--ink-800);font-family:inherit;letter-spacing:-.01em;}
.chart .lift-t{font-size:12px;font-weight:700;fill:#fff;font-family:inherit;letter-spacing:-.01em;}
.chart .surge{fill:none;stroke:var(--brand);stroke-width:2.75;stroke-dasharray:7 6;stroke-linecap:round;}
/* Connector and arrowhead both track the label's own --ink-800 rather than the fainter
   --ink-500 they used to carry, so the annotation reads as one object. The head is a
   <marker>, which sits outside the path in the DOM, so it needs its own rule -- its
   fill was a hardcoded #7D6B61 and would not have followed. */
.chart .connector{fill:none;stroke:var(--ink-800);stroke-width:1.75;stroke-linecap:round;}
.chart .arrow-head{fill:var(--ink-800);}
.chart-foot{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem;margin-top:1.5rem;padding-top:1.375rem;border-top:1px solid var(--ink-200);}
.chart-foot div{text-align:center;}
.chart-foot b{display:block;font-size:clamp(1.25rem,2.4vw,1.625rem);font-weight:750;color:var(--ink);letter-spacing:-.03em;line-height:1.2;}
.chart-foot b.up{color:var(--teal);}
.chart-foot span{font-size:var(--fs-xs);color:var(--ink-500);font-weight:500;}

/* ---------- steps ---------- */
.steps{background:var(--paper);}
/* The off-white, so it separates from the now-white .steps above and .ext below. The
   section previously had no background rule at all and was showing the body's white
   through. Class, not #usecases — every other section here is styled by class. */
.usecases{background:var(--paper-alt);}
.steps-grid{display:grid;grid-template-columns:.92fr 1.08fr;gap:clamp(2rem,5vw,4rem);align-items:center;}
/* --list-gap is referenced by the rail below, which has to reach ACROSS this gap to the
   next marker, so the two must move together. */
.step-list{--list-gap:2rem;display:flex;flex-direction:column;gap:var(--list-gap);}

/* Each step is a numbered marker on a connecting rail, with its text beside it — so the
   sequence reads as 1 → 2 → 3 before you get to any of the words. The numbers ARE the
   icons here: the icon tiles and the surrounding cards were both removed, leaving the
   text sitting straight on the section background. */
.step{display:flex;gap:1.25rem;align-items:stretch;}
.step-rail{position:relative;flex:none;width:38px;}
.step-n{
  position:relative;z-index:1;
  width:38px;height:38px;border-radius:50%;
  background:var(--brand-deep);color:#fff;
  font-size:1.0625rem;font-weight:750;letter-spacing:-.02em;line-height:1;
  display:grid;place-items:center;
  /* Ring breaks the rail cleanly around the marker, so it must be the SECTION's colour.
     It was --paper-alt against this section's --paper, which the cards used to hide. */
  box-shadow:0 0 0 5px var(--paper);
}
/* Rail from just under this marker down to the next one. bottom is the negative list
   gap, so it crosses the gap and meets the following marker's ring rather than stopping
   at the row edge — .step-rail stretches to the row height via .step's align-items. */
.step:not(:last-child) .step-rail::after{
  content:'';position:absolute;left:50%;transform:translateX(-50%);
  top:44px;bottom:calc(-1 * var(--list-gap));width:2px;
  background:repeating-linear-gradient(180deg,var(--ink-300) 0 4px,transparent 4px 10px);
}
/* 9px centres the title's line box on the 38px marker: the h3 measures 20px, so
   (38 - 20) / 2. Measured, not assumed — 7px from the nominal type size left it 2px out.
   Without this the title sits level with the marker's TOP and the pair reads misaligned. */
.step-body{flex:1;min-width:0;padding-top:9px;}
.step h3{margin-bottom:.25rem;}
.step p{font-size:var(--fs-sm);color:var(--ink-500);line-height:1.55;}

/* ---------- use cases ---------- */
.tag{
  display:inline-block;font-size:.625rem;font-weight:700;letter-spacing:.09em;
  padding:.25rem .5rem;border-radius:5px;margin-bottom:.625rem;
}
/* same colours as before, now coming from the accent tokens instead of literals */
.tag-fixed{background:var(--acc-brand-bg);color:var(--acc-brand-fg);}
.tag-ever{background:var(--acc-teal-bg);color:var(--acc-teal-fg);}
.tag-hybrid{background:var(--acc-violet-bg);color:var(--acc-violet-fg);}

/* Card with a header row: icon and tag share one line, so title and body start higher.
   Chosen over the original stacked layout 2026-08-14; the .ucd- prefix it carried while
   the two were being compared is gone with it. */
.uc-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1.125rem;margin-top:clamp(2.5rem,5vw,3.25rem);}
.uc{
  background:var(--paper);border:1px solid var(--ink-200);border-radius:var(--r-lg);
  padding:1.25rem 1.375rem;
  transition:box-shadow .28s ease, transform .28s ease, border-color .28s ease;
}
.uc:hover{box-shadow:var(--sh-md);transform:translateY(-3px);border-color:var(--ink-300);}
/* The tag's own margin-bottom would misalign it against the icon on this shared row. */
.uc-top{display:flex;align-items:center;justify-content:space-between;gap:.75rem;margin-bottom:.875rem;}
.uc-top .tag{margin-bottom:0;}
/* Outlined, not filled: the --brand-wash fill is gone and the 1px brand border stays,
   so the tile reads as an outline. 38px box, 18px glyph. */
.uc-ico{
  width:38px;height:38px;flex:none;border-radius:9px;display:grid;place-items:center;
  color:var(--brand);border:1px solid var(--brand);
}
.uc-ico svg{width:18px;height:18px;}
/* Pill badges. The scoping dates from when two card layouts were being compared and
   .tag had to stay neutral between them; only this one survives, so .tag and .uc .tag
   now always apply together. */
.uc .tag{border-radius:var(--r-pill);padding-left:.6875rem;padding-right:.6875rem;}
.uc h3{margin-bottom:.375rem;}
.uc p{font-size:var(--fs-sm);color:var(--ink-500);line-height:1.55;}

/* "+Plus many more" as a line under the grid, not a tenth cell. It used to be a dashed
   ghost card holding the ninth slot; the win-back use case took that slot, and three
   full rows plus a closing line reads better than a ghost breaking the run.
   An h3, so it takes the page's card-title size and weight. The colour is stepped back
   from the h1-h4 default (--ink) so it reads as a closing note rather than a tenth card
   title; the class beats the element selector, so no !important is needed. */
.uc-note{
  margin-top:50px;text-align:center;
  color:var(--ink-600);
}

/* ---------- advanced analytics ----------
   A REBUILD of the dashboard's stats modal in this page's language, not a port of the
   app's markup — warm neutrals and 20px radii, where the app uses greys and 3px. The
   information is the app's; none of the CSS is.

   Sits BELOW Flash Pass (moved 2026-08-13), so --paper-alt is what separates it from
   the dark section above and the white pricing below. It also now reads after the two
   features its attribution chart names, rather than before them. */
.an{background:var(--paper-alt);}
/* Reversed: visual in column 1, copy in column 2 — same construction as .ext-grid. The
   DOM stays copy-then-visual so the stacked layout still reads copy first, and the swap
   is done with `order`, which MUST be undone at the stacking breakpoint or the card
   lands above the headline. */
.an-grid{display:grid;grid-template-columns:1.05fr 1fr;gap:clamp(2rem,5vw,4rem);align-items:center;}
.an-visual{order:-1;}

/* --- icon-tile variant (comparison scaffolding, 2026-08-16) ---
   Same construction as .ef-list in the evergreen section — 40px outlined tile, 19px
   solid glyph, name over text. Its own prefix rather than reusing .ef-*, because
   sections here own their classes and these two sit on opposite surfaces: the moment
   one is restyled the other would follow it silently.

   These ARE the light values. .ef-* reads dark only because .eg-dark overrides it, so
   "recolour for a light background" mostly meant not inheriting those overrides:
   --brand-wash fill and a brand border on the tile, #CC4E14 on the name (4.50:1 on
   white, where --brand is 3.13:1 and fails), --ink-600 on the body. */
.anf-list{margin-top:1.625rem;display:flex;flex-direction:column;gap:1.5rem;list-style:none;padding:0;}
.anf-item{display:flex;gap:1rem;align-items:flex-start;}
.anf-ico{
  width:40px;height:40px;border-radius:11px;flex:none;
  display:grid;place-items:center;
  background:var(--brand-wash);color:var(--brand);border:1px solid var(--brand);
}
.anf-mark{width:19px;height:19px;}
.anf-body{display:flex;flex-direction:column;gap:.3125rem;min-width:0;}
.anf-name{font-size:var(--fs-body);font-weight:650;color:#CC4E14;}
.anf-text{font-size:var(--fs-sm);color:var(--ink-600);line-height:1.55;}

.an-card{
  background:var(--paper);border:1px solid var(--ink-200);
  border-radius:var(--r-lg);box-shadow:var(--sh-lg);
  padding:clamp(1.5rem,3vw,2rem);
}
.an-stats{
  display:grid;grid-template-columns:repeat(3,1fr);gap:1rem;
  padding-bottom:1.25rem;border-bottom:1px solid var(--ink-200);
}
.an-stat{display:flex;flex-direction:column;gap:.3125rem;}
/* tabular-nums everywhere a figure sits in a column, so the three stats and the legend
   rates line up on their digits rather than drifting with glyph widths */
.an-num{
  font-size:1.75rem;font-weight:700;line-height:1.1;letter-spacing:-.02em;
  color:var(--ink);font-variant-numeric:tabular-nums;
}
.an-stat-hl .an-num{color:var(--brand);}
.an-cap{font-size:var(--fs-xs);color:var(--ink-500);line-height:1.35;}

.an-panel{padding-top:1.25rem;}
/* Sentence case as of 2026-08-16, so the tracking came down with it: .09em is set for
   uppercase, where it opens up letterforms that would otherwise crowd. On sentence case
   it just pulls the word apart. */
.an-label{font-size:var(--fs-xs);font-weight:700;letter-spacing:.01em;color:var(--ink-700);margin-bottom:.875rem;}
.an-bar{display:flex;height:10px;border-radius:var(--r-pill);overflow:hidden;background:var(--ink-100);}
.an-seg{display:block;height:100%;}

/* One fill class drives both the bar segment and its legend dot, so the two can never
   disagree about which colour means which bucket. Hues follow the app's own choices:
   primary / evergreen / the violet the Flash Pass badge used. */
.an-fill-main{background:var(--brand);}
.an-fill-ext{background:var(--teal);}
.an-fill-flash{background:var(--acc-violet-fg);}

/* --- histogram panel (variant scaffolding, 2026-08-16) ---
   Conversions by how much clock was left. Separated from the attribution panel by a
   hairline, because they answer different questions off the same 312 conversions:
   that one is WHICH deadline, this one is WHEN.

   Bars are neutral except the last two, which carry the point — the spike is at the
   end. Colouring all eight brand orange would make it a decorative bar chart; colouring
   only the spike makes it an argument. Neutral is --ink-300 rather than --ink-200,
   which disappears against the card. */
.an-hist-panel{margin-top:1.25rem;padding-top:1.25rem;border-top:1px solid var(--ink-200);}
.an-hist{display:flex;align-items:flex-end;gap:7px;height:92px;}
.an-hb{
  flex:1;min-width:0;min-height:3px;
  background:var(--ink-300);border-radius:3px 3px 0 0;
}
.an-hb-hl{background:var(--brand);}
/* Five labels, space-between, against eight even bins — so they land on the bin edges
   at 0/25/50/75/100% rather than near them. Uneven bins would break that. */
.an-hax{
  display:flex;justify-content:space-between;margin-top:.5rem;
  font-size:12px;color:var(--ink-500);font-variant-numeric:tabular-nums;
}
.an-hnote{margin-top:1.2rem;font-size:12px;color:var(--ink-600);line-height:1.5;}
.an-hnote b{color:var(--ink-700);font-weight:650;}

.an-legend{margin-top:1.0625rem;display:flex;flex-direction:column;gap:.6875rem;}
.an-row{display:flex;align-items:center;gap:.625rem;}
.an-dot{width:9px;height:9px;border-radius:50%;flex:none;}
.an-key{font-size:var(--fs-sm);color:var(--ink-600);flex:1;}
.an-val{font-size:var(--fs-sm);font-weight:650;color:var(--ink);font-variant-numeric:tabular-nums;}
.an-rate{font-size:var(--fs-xs);color:var(--ink-500);width:3.25rem;text-align:right;font-variant-numeric:tabular-nums;}

/* ---------- the stats row sets a hard floor on the card's width ----------
   .an-stats is a three-track 1fr grid, and a 1fr track's minimum is `auto`, i.e.
   min-content — so the row cannot shrink below the widest unbreakable thing in each
   column. Measured at 320px: 77.0 for "1,284", 76.7 for "Conversions" and 85.6 for
   "24.3%", plus 32px of gaps = 271.3, which is exactly the card's content width. That
   floor held .an-card at 321.2px inside a 280px column and pushed 21px of the whole page
   off the right edge.

   ⚠️ The middle column is held up by its CAPTION, not its figure ("Conversions" is one
   word and cannot wrap; its number "312" only needs 52.5). Shrinking .an-num alone
   leaves that 76.7 in place and lands 0.6px OVER budget — which is why the caption, the
   gap and the card's own padding all come down too. Together they buy about 15px of
   headroom at 320px instead of none.

   Stops at 370px on purpose. The overflow only exists below ~361px, where the column
   finally drops under 321.2, so every common phone width from 375 up keeps the
   full-size stats and is not touched by this. */
@media (max-width:370px){
  .an-card{padding:1.25rem;}
  .an-stats{gap:.75rem;}
  .an-num{font-size:1.375rem;}
  .an-cap{font-size:12px;}
}

/* ---------- countdown timers: placement wireframes ----------
   Same visual language as the app's own timer picker — grey skeleton page, timer
   highlighted in brand orange — so someone who has seen the product recognises it.
   These are diagrams, not previews: nothing here ticks and the digits read "00", which
   is what the picker draws too.

   --paper, alternating off the --paper-alt use-cases section above. It still sits
   against the white evergreen section below; that seam is unmarked until the evergreen
   variants are down to one. */
/* Off-white. Swapped with .ed-sec below when evergreen moved up between usecases and
   this section, so the run alternates: usecases alt / evergreen white / timers alt /
   editor white. */
.tm{background:var(--paper-alt);}
.pl-grid{
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  gap:clamp(1rem,2vw,1.375rem);
  margin-top:clamp(2.5rem,5vw,3.75rem);
}
/* The four supplied SVGs, capped at 230px and not restretched.
   max-width rather than a fixed width so a narrower grid cell shrinks them instead of
   overflowing; the sticky-bar file is 252x172 where the rest are 250x170, which is a
   0.4% ratio difference and needs no normalising. They are vector, so 230px is a clean
   downscale of the 250px artwork with nothing resampled.

   drop-shadow, not box-shadow: it traces the SVG's own rendered silhouette, so the
   shadow follows the artwork's 6px corners without this rule having to restate that
   radius and keep it in sync with four external assets. Two layers, matching the shape
   of --sh-md, which is a box-shadow token and so cannot be reused here directly. */
.pl-shot{
  display:block;width:100%;max-width:230px;height:auto;margin-inline:auto;
  filter:drop-shadow(0 1px 2px rgba(27,22,19,.07)) drop-shadow(0 8px 18px rgba(27,22,19,.10));
}
/* Centred under its diagram, which is itself centred in the cell via margin-inline:auto
   on .pl-shot — so the pair stays aligned at any column width, including the 2-up
   breakpoint where the cell is much wider than the 230px image. */
.pl-name{
  margin-top:.875rem;text-align:center;
  font-size:var(--fs-sm);font-weight:500;color:var(--ink);
}

/* ========== BEGIN timer editor — self-contained, safe to delete as one unit ==========
   Everything for the #editor section lives between this marker and the matching END
   marker below, INCLUDING the .ed-zoom scale media queries at the very bottom of the
   block (easy to orphan, since they sit ~160 lines down and read like generic
   breakpoints). To remove the section entirely: delete <section id="editor"> from
   landing.html and delete this whole block. Nothing else references .ed-*, and
   landing.js contains no editor code at all, so there is nothing else to unpick.
   The dependency runs one way only — these rules consume page tokens (--sh-lg,
   --paper-alt); no page rule consumes anything defined here.

   The app's inline-timer customizer, rebuilt rather than screenshotted. Built at a
   native 1040px and scaled as one unit, so the proportions stay the product's rather
   than being re-guessed at each width.

   ⚠️ This block deliberately uses the APP's palette, not the page's — #F2662A accents,
   neutral greys, the product's own control colours. It is the one place the product's
   own colours appear on this page, and the values are literals rather than tokens for
   exactly that reason: they belong to the app, and promoting them would invite reuse. */
.ed-sec{background:var(--paper);}
.ed-stage{margin-top:clamp(2.5rem,5vw,3.75rem);display:flex;justify-content:center;}
/* zoom, not transform:scale — scale leaves the layout box at 1040px and blows out the
   wrap while looking correctly sized. Same trap as the Flash Pass card. */
.ed-zoom{zoom:var(--ed-scale,1);}
.ed{
  width:1040px;background:#fff;
  border-radius:14px;overflow:hidden;box-shadow:var(--sh-lg);
  font-family:'Inter',-apple-system,BlinkMacSystemFont,sans-serif;
}

/* --- header --- */
.ed-top{display:flex;align-items:center;gap:12px;padding:13px 16px;}
.ed-mark{width:30px;height:30px;border-radius:8px;flex:none;display:grid;place-items:center;background:#FDECE4;}
.ed-mark svg{width:15px;height:15px;color:#F2662A;}
.ed-title{font-size:15px;font-weight:600;color:#3D3D3D;}

/* --- body --- */
.ed-body{display:flex;height:625px;}
.ed-side{width:300px;flex:none;border-right:1px solid #E8E8E8;display:flex;flex-direction:column;}
.ed-tabs{display:flex;flex:none;}
.ed-tab{
  flex:1;text-align:center;padding:12px 0;font-size:13px;font-weight:500;
  color:#8A8A8A;background:#F3F3F3;
}
.ed-tab-on{color:#F2662A;background:#fff;box-shadow:inset 0 2px 0 #F2662A;}
/* gap is the label-to-field distance; the extra separation ABOVE a label comes from
   .ed-row's own margin-top, so the two together read as grouped pairs. */
.ed-pane{padding:16px 15px;display:flex;flex-direction:column;gap:6px;}

/* --- template card --- */
.ed-tpl{
  display:flex;align-items:center;gap:10px;
  border:1px solid #F2662A;border-radius:9px;padding:9px 10px;margin-bottom:5px;
}
.ed-tpl-meta{display:flex;flex-direction:column;gap:1px;flex:none;width:52px;}
.ed-tpl-k{font-size:9.5px;color:#8A8A8A;}
.ed-tpl-v{font-size:11.5px;font-weight:700;color:#3D3D3D;}

/* --- controls --- */
.ed-row{display:flex;align-items:center;justify-content:space-between;margin-top:8px;}
/* Bottom rule only. The matching border-top was removed on request: it sat directly
   under the Glass finish segment and read as a second, competing divider. */
.ed-row-rule{border-bottom:1px solid #EFEFEF;padding-bottom:11px;margin-top:9px;}
.ed-lab{font-size:11.5px;font-weight:600;letter-spacing:.03em;color:#6E6E6E;}
.ed-val{font-size:11.5px;color:#8A8A8A;}
/* Mirrors the app's .ec-range (globals.css): 6px track, 16px thumb carrying
   --shadow (0 1px 2px rgba(0,0,0,.2)), and a two-tone track — gray-300 #C4C4C4 for
   the filled portion left of the thumb, gray-200 #E0E0E0 for the rest. The app drives
   that split with an inline --range-fill percentage, so --fill here does the same job
   and positions the thumb from the same number, keeping the two in sync by
   construction. NOTE the app's greys come from globals.css (#C4C4C4/#E0E0E0), which
   supersedes the older values in the app CLAUDE.md's token table. */
.ed-slider{
  position:relative;display:block;height:6px;border-radius:999px;margin:3px 0 4px;
  background:linear-gradient(to right,#C4C4C4 0 var(--fill,0%),#E0E0E0 var(--fill,0%));
}
.ed-slider i{
  position:absolute;top:50%;left:var(--fill,0%);
  width:16px;height:16px;margin-top:-8px;margin-left:-8px;
  border-radius:50%;background:#F2662A;box-shadow:0 1px 2px rgba(0,0,0,.2);
}
.ed-seg{display:flex;gap:0;}
/* 30px tall, matching the app exactly: text-xs (12px/16px line box) + py-1.5 (6px)
   + 1px borders. The previous 11.5px font inheriting the page's 1.65 line-height
   came out at 35px. line-height is pinned here because this page's body value would
   otherwise decide the height. */
.ed-sg{
  flex:1;text-align:center;padding:6px 0;font-size:12px;line-height:16px;font-weight:500;color:#7A7A7A;
  border:1px solid #E0E0E0;border-right-width:0;background:#fff;
}
.ed-sg:first-child{border-radius:6px 0 0 6px;}
.ed-sg:last-child{border-radius:0 6px 6px 0;border-right-width:1px;}
.ed-sg-on{color:#F2662A;border-color:#F2662A;background:#FEF6F2;border-right-width:1px;}
/* The app's shared Toggle.tsx, not a generic pill: the knob is TALLER than the track
   (16px vs 8px) and overlaps it rather than sitting inset. Shown OFF, which is the
   state in the supplied screenshot — knob #9597A6 (--toggle-knob-off) sitting left,
   lighter #CFCFCF track (--toggle-track) visible to its right. ON would move the knob
   to left:14px and turn it #F2662A. */
.ed-toggle{position:relative;width:30px;height:16px;flex:none;}
.ed-toggle::before{
  content:'';position:absolute;left:0;top:50%;margin-top:-4px;
  width:30px;height:8px;border-radius:999px;background:#CFCFCF;
}
.ed-toggle i{
  position:absolute;left:0;top:50%;margin-top:-8px;
  width:16px;height:16px;border-radius:50%;background:#9597A6;
  box-shadow:0 1px 2px 0 rgba(0,0,0,.2);
}

/* --- preview pane --- */
.ed-prev{flex:1;min-width:0;background:#EFEFEF;padding:20px;display:flex;}
.ed-brow{flex:1;min-width:0;background:#fff;border-radius:9px;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 1px 3px rgba(0,0,0,.06);}
.ed-brow-bar{display:flex;align-items:center;gap:5px;padding:9px 12px;background:#FAFAFA;border-bottom:1px solid #EFEFEF;}
.ed-brow-bar i{width:7px;height:7px;border-radius:50%;background:#D6D6D6;flex:none;}
.ed-url{margin:0 auto;background:#fff;border:1px solid #E8E8E8;border-radius:999px;padding:3px 22px;font-size:10.5px;color:#9A9A9A;}
.ed-page{flex:1;padding:18px;display:flex;flex-direction:column;gap:10px;}
.ed-pg-top{display:flex;align-items:center;justify-content:space-between;}
.ed-nav{display:flex;gap:8px;}
.ed-nav i{display:block;width:52px;height:9px;border-radius:4px;background:#EDEDED;}
.ed-blk{display:block;border-radius:5px;background:#EDEDED;}
.ed-blk-lg{width:112px;height:22px;}
.ed-blk-hero{height:30px;margin:6px 32px 0;}
.ed-ln{display:block;height:7px;border-radius:4px;background:#F0F0F0;}
.ed-ln-a{margin:0 22px;} .ed-ln-b{margin:0 44px;}
/* The Glass template, reproducing the real one's geometry rather than approximating it.
   Every metric below is the app's own, from the style='glass' branch in the embed
   script (src/app/t/[token]/route.ts) — which notes that Glass is Cards' box metrics
   with a translucent pane swapped in for the solid card.
     container  inline-flex, gap .42em, line-height 1, tabular-nums
     unit       min-width 2.2em, padding .37em, radius .36em, 1px rgba(255,255,255,.6)
     digits     1em, weight 800, letter-spacing -.03em, #333556
     label      margin-top .42em, weight 600, letter-spacing .06em, uppercase, #333556b3

   ONE rule set drives both copies of the timer — the live preview band and the
   template card's thumbnail — because they are the same design at two sizes. The only
   thing that differs is --ed-tbase: everything else is em-derived off it, so the two
   cannot drift apart and a new size needs exactly one number changed.
   The units were previously flex:1, which stretched them across the full width — the
   real timer is inline-flex and content-sized, so it centres instead. */
.ed-band,.ed-tpl-timer{
  display:flex;justify-content:center;gap:.42em;
  font-size:var(--ed-tbase);line-height:1;font-variant-numeric:tabular-nums;
  font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
  background:linear-gradient(100deg,#7A5AE0 0%,#E56FAE 58%,#F6A87C 100%);
}
/* --ed-k is the app's own size multiplier (its --k), the thing the sidebar's "Size"
   row reports. At .9 the timer renders at 39.6px, which is exactly what the product
   would draw for the 0.9× the mock already claims, so the panel is now internally
   consistent. Change --ed-k alone to resize; --ed-tbase follows from it. */
.ed-band{--ed-k:.9;--ed-tbase:calc(44px * var(--ed-k));padding:17px;border-radius:9px;margin:8px 0;}
/* The label's real size is NOT proportional to the digits: the app clamps it, so a 10%
   smaller timer keeps a near-identical label (12px -> 11.88px). This is that clamp
   verbatim from the embed script — clamp(8px, --lblnat, --lblmax) where --lblnat is
   13.2px*k and --lblmax is min(15px, max(12px, 12px*k/1.1)). Overrides the shared
   .2727em ratio below, which stays in force for the template thumbnail, where a
   proportional miniature is the right behaviour. */
.ed-band .ed-gc i{
  font-size:clamp(8px, calc(13.2px * var(--ed-k)), min(15px, max(12px, calc(12px * var(--ed-k) / 1.1))));
}
/* 17px is the largest base whose 4 units + gaps still fit the sidebar card. */
.ed-tpl-timer{--ed-tbase:17px;flex:1;min-width:0;padding:6px;border-radius:7px;}
.ed-gc,.ed-tc{
  display:flex;flex-direction:column;align-items:center;box-sizing:border-box;
  min-width:2.2em;padding:.37em;border-radius:.36em;
  background:rgba(255,255,255,.45);border:1px solid rgba(255,255,255,.6);
  box-shadow:0 .05em .23em rgba(14,28,47,.12);
  -webkit-backdrop-filter:blur(20px) saturate(200%);backdrop-filter:blur(20px) saturate(200%);
}
.ed-gc b,.ed-tc b{font-size:1em;font-weight:800;letter-spacing:-.03em;color:#333556;line-height:1;}
/* .2727em resolves to exactly 12px at the live band's 44px base, which is what the
   app's clamp(8px, --lblnat, --lblmax) evaluates to at scale 1 (--lblnat 13.2px,
   --lblmax min(15,max(12,10.9)) = 12). Expressed as a ratio rather than that fixed
   12px so the thumbnail scales as a true miniature of the same design. */
.ed-gc i,.ed-tc i{
  margin-top:.42em;font-size:.2727em;font-weight:600;font-style:normal;
  letter-spacing:.06em;text-transform:uppercase;color:#333556b3;line-height:1;
}
/* flex:1 so the mock page reaches the bottom of its browser rather than stopping short
   and leaving a dead white band, capped so the cards stay content-sized rather than
   swelling to fill — uncapped they came out 245px against the timer band's 91px and
   pulled the eye off the timer, which is the thing the preview exists to show. */
.ed-cards{display:flex;gap:11px;flex:1;min-height:0;max-height:180px;}
.ed-card{flex:1;border-radius:6px;background:#EDEDED;}
.ed-cards-sm{flex:none;}
.ed-cards-sm .ed-card{height:9px;border-radius:4px;}

/* Scale steps. The modal is a fixed 1040px, and the wrap's content is
   W - 2*clamp(20px, 4vw, 32px) — so it clears unscaled only above ~1140px. Each step
   below is the largest that still fits its breakpoint's narrowest content width.
   Kept as their own media queries rather than a clamp() because zoom takes a number or
   a percentage and cannot mix vw with % inside calc().

   NOTE: a stray comment-close used to sit at the end of the line above, ending this
   comment 17 lines early and turning everything from there to the intended close into
   bare prose in the stylesheet. CSS error recovery then discarded tokens until it could
   resync, and what it swallowed was the FIRST rule below, the .90 step. That step never
   applied from the day this section was built (f6e6f0a) until 2026-08-18, so the editor
   rendered UNSCALED at 1040px across the whole 1001-1104 band and pushed the page
   sideways there.

   Nothing reports this. The file looks right, the bytes are clean, the braces balance and
   every other rule loads. It shows up only by counting @media in the source against
   document.styleSheets, or by reading the computed --ed-scale and finding it unset.
   ⚠️ Never write a comment-close sequence inside CSS comment prose, backticked or not:
   CSS comments do not nest, so the first one ends the comment. Describe it in words.

   🚨 THE RULE, and it was inverted here until 2026-08-17. A `max-width` step applies all
   the way DOWN to the next step, so its scale has to fit the NARROWEST viewport in its
   range, not the widest. Every original value was sized against its own breakpoint —
   .44 fits the 520px it is named after and nothing below it — so all four steps
   overflowed across most of their range and pushed the page sideways:

       .93 -> 967px  scrolled 901-967      .78 -> 811px  scrolled 701-811
       .60 -> 624px  scrolled 521-624      .44 -> 458px  scrolled 401-457

   That last band is every large iPhone (Plus 414, Pro Max 430): the editor rendered
   458px wide inside a 390px column, shearing its own sidebar off the left edge.

   Each value below is floor(content(narrowest viewport in range) / 1040, 2dp), where
   content = min(vw, --wrap 1140) - 2*clamp(20px, 4vw, 32px). Steps run to 320px, which
   is an iPhone SE and also what ANY iPhone reports with Display Zoom turned on.
   ⚠️ Adding a breakpoint means re-deriving its neighbour: widening a range lowers its
   narrowest viewport, and the scale above it silently stops fitting. */
@media (max-width:1104px){ .ed-zoom{--ed-scale:.90;} }  /*  936px, fits >=1001 */
@media (max-width:1000px){ .ed-zoom{--ed-scale:.80;} }  /*  832px, fits >= 901 */
@media (max-width:900px){  .ed-zoom{--ed-scale:.70;} }  /*  728px, fits >= 801 */
@media (max-width:800px){  .ed-zoom{--ed-scale:.61;} }  /*  634px, fits >= 701 */
@media (max-width:700px){  .ed-zoom{--ed-scale:.53;} }  /*  551px, fits >= 601 */
@media (max-width:600px){  .ed-zoom{--ed-scale:.46;} }  /*  478px, fits >= 521 */
@media (max-width:520px){  .ed-zoom{--ed-scale:.40;} }  /*  416px, fits >= 461 */
@media (max-width:460px){  .ed-zoom{--ed-scale:.34;} }  /*  354px, fits >= 401 */
@media (max-width:400px){  .ed-zoom{--ed-scale:.32;} }  /*  333px, fits >= 373 */
@media (max-width:372px){  .ed-zoom{--ed-scale:.29;} }  /*  302px, fits >= 343 */
@media (max-width:342px){  .ed-zoom{--ed-scale:.27;} }  /*  281px, fits >= 321 */
@media (max-width:320px){  .ed-zoom{--ed-scale:.23;} }  /*  239px, fits >= 280 */
/* ========== END timer editor ========== */

/* ---------- evergreen deadlines ----------
   The surviving section (was variant D). .eg is just the section background; the
   panel rules that used to sit here belonged to variant A and went with it. */
.eg{background:var(--paper);}


/* ---------- evergreen features split ----------
   Centred header over a features/image split. The header uses the page's existing .center / .measure-wide / .mx-auto
   helpers, so only the split below needs new rules. */
.ef-grid{
  display:grid;grid-template-columns:1fr 1.15fr;
  gap:clamp(2rem,5vw,4rem);align-items:center;
  margin-top:clamp(2.5rem,5vw,3.75rem);
}
.ef-list{display:flex;flex-direction:column;gap:1.75rem;list-style:none;margin:0;padding:0;}
/* .cta-row already brings margin-top:2.125rem, which is the spacing every other CTA on
   the page sits on, so this only has to exist to scope anything section-specific. */
.ef-copy .cta-row{margin-top:clamp(1.75rem,3.5vw,2.5rem);}
.ef-item{display:flex;gap:1rem;align-items:flex-start;}
/* Same 40 / 19 / 11 tile as .uc-ico in the use-cases section, deliberately — it is the
   page's established icon-tile shape. Kept as its own class rather than reusing that
   one, since every section here owns its classes and .uc-ico also carries a
   margin-bottom that only makes sense stacked above a heading. */
.ef-ico{
  width:40px;height:40px;border-radius:11px;flex:none;
  display:grid;place-items:center;
  background:var(--brand-wash);color:var(--brand);border:1px solid var(--brand);
}
.ef-mark{width:19px;height:19px;}
.ef-body{display:flex;flex-direction:column;gap:.3125rem;min-width:0;}
/* #CC4E14 is the light-surface value. It is never used as written — .eg-dark overrides
   it below, and this section is only ever dark — but it is kept so the rule still
   states a colour that works on white if the section is ever moved. 4.50:1 there. */
.ef-name{font-size:var(--fs-body);font-weight:650;color:#CC4E14;}
.ef-text{font-size:var(--fs-sm);color:var(--ink-600);line-height:1.55;}

/* ---------- evergreen, dark surface ----------
   The surface, the glow and the muted text tone are lifted from .flash-dark so the
   page's two dark sections read as one treatment rather than two attempts at one.

   The section's colours were originally picked against white and none of them survived
   the move. The one worth keeping in mind: #CC4E14 (.ef-name) is 4.00:1 on --ink and
   fails AA at these sizes, so it goes to --brand #F2662A, which is 5.77:1 here. That is
   the colour that fails on WHITE (3.13:1) — the same pair, swapped, which is why a
   light version of this section cannot simply reuse these values.

   Left alone deliberately: the .eyebrow stays brand orange (5.77:1, and .flash-dark
   sets no eyebrow colour either), and the ActiveCampaign glyph keeps AC blue. A
   third-party mark is not ours to recolour for a background. */
.eg-dark{
  position:relative;overflow:hidden;
  background:var(--ink);
  border-top:1px solid rgba(255,255,255,.08);
}
.eg-dark::before{
  content:'';position:absolute;inset:0;pointer-events:none;
  background:
    radial-gradient(52% 44% at 50% 0%, rgba(242,102,42,.17), transparent 68%),
    radial-gradient(40% 40% at 88% 88%, rgba(91,75,196,.14), transparent 70%);
}
.eg-dark > .wrap{position:relative;}
.eg-dark h2{color:#fff;}
.eg-dark .lead{color:#A6988E;}

/* features side. No tile fill here: the brand border and glyph carry the shape, and any
   fill on this background read as a lit panel behind the icon. */
.eg-dark .ef-ico{background:none;}
.eg-dark .ef-name{color:var(--brand);}
.eg-dark .ef-text{color:#A6988E;}


/* ---------- evergreen, contact-stream illustration ----------
   A funnel with contacts flowing down it, rather than one contact's history.

   ⚠️ THESE ARE DARK VALUES, written straight in rather than split into light defaults
   plus dark overrides, because this component only ever renders inside .eg-dark.
   Writing a light half that nothing renders would be untested code that is wrong the
   day it is first used. If this section ever moves to a light surface it needs a real
   colour pass, not a find-and-replace.

   Geometry is driven by --cs-lane, and three things depend on it: the third grid
   column, the lane box the contacts travel in, and the spine's offset from the right
   edge. Change it in one place only. */
/* --cs-pad is the card's padding AND the gap under the automation row, so the space
   above that row and the space below it to the divider are the same number by
   construction rather than by two values that happen to match today. */
.cs-card{
  --cs-pad:clamp(1.375rem,2.75vw,1.75rem);
  background:rgba(255,255,255,.045);border:1px solid rgba(255,255,255,.10);
  border-radius:var(--r-lg);
  padding:var(--cs-pad);
}
.cs-head{
  display:flex;align-items:center;gap:.6875rem;
  padding-bottom:var(--cs-pad);border-bottom:1px solid rgba(255,255,255,.10);
}
/* 40px, not 38: the glyph file carries 4 units of antialiasing
   bleed per side, so 40 renders the visible circle at the 38.6px it was drawn for. */
.cs-brand{width:40px;height:40px;flex:none;}
.cs-who{display:flex;flex-direction:column;gap:.125rem;min-width:0;}
.cs-title{font-size:var(--fs-sm);font-weight:650;color:#fff;}
.cs-meta{font-size:var(--fs-xs);color:#A6988E;}

/* The lane got wider to hold a name and a contact id beside each avatar. Nothing else
   had to move: the label column is 1fr, so widening the lane is what pushes the labels
   and the spine left. */
/* margin-right, NOT padding-right, to shift the contents left. An absolutely positioned
   child resolves right:0 against the padding box, so padding would move the grid's third
   column left while leaving .cs-lane where it was and the two would come apart. */
/* What a contact actually measures: 28 avatar + 8 + 74 name + 8 + 129 chip = 247, plus
   slack. Sized to the content rather than guessed, because every px beyond it is dead
   space that pushes the dots left for nothing — but NOT to the pixel either: the chip
   is text, and a fallback font before Inter loads renders it wider. Re-measure this if
   the chip's font-size moves again, and re-derive the zoom tiers below with it. */
.cs-stage{--cs-lane:258px;position:relative;margin-top:1.4rem;margin-right:40px;}
.cs-steps{position:relative;list-style:none;margin:0;padding:0;}
/* Right-anchored, not left: the dot column is the third-from-right, so its position is
   known from the right edge and unknown from the left (the label column is 1fr).
   lane + column gap + 4 puts the 2px spine on the dot's centre line. */
.cs-steps::before{
  content:'';position:absolute;top:18px;bottom:18px;
  right:calc(var(--cs-lane) + .625rem + 4px);
  width:2px;background:rgba(255,255,255,.14);
}
.cs-step{
  display:grid;grid-template-columns:1fr 10px var(--cs-lane);
  align-items:center;gap:.625rem;padding:.5rem 0;
}
/* Labels do NOT react to the highlight. With contacts, clocks, dots and a ring all
   moving, a label changing colour as well was one signal too many, and it also broke
   the rule the colour is carrying: grey is the ESP's step, orange is ours. A label that
   brightens on arrival makes the colour mean "current" for a moment instead. */
.cs-label{text-align:right;font-size:var(--fs-sm);color:#A6988E;}
/* MUST stay below .cs-label. Both are single classes, so they tie on specificity and
   source order decides the winner. */
.cs-label-key{color:var(--brand);}
.cs-dot{
  width:10px;height:10px;border-radius:50%;position:relative;
  background:#625F5D;box-shadow:0 0 0 3px #25211E;
}
/* Brand orange still means "a step DeadlineFox owns", exactly as on the other card. */
.cs-dot-key{background:var(--brand);}
/* The arrival highlight is a RING, not a recolour. Turning the dot orange on arrival
   would have given the orange a third meaning on a page where it already means both
   "act" and "ours"; a pulse is motion, which carries no meaning to collide with.

   Scoped to .cs-dot-key, so only "Deadline starts" and "Conversion tracked" pulse. The
   ESP's own steps still take .is-hit and simply draw nothing for it: the beacon marks
   the two moments DeadlineFox owns rather than every step the contact passes. JS still
   flags every arrival, which keeps the mechanism general if a third row ever earns
   one. */
.cs-step.is-hit .cs-dot-key::after{
  content:'';position:absolute;inset:-4px;border-radius:50%;
  border:2px solid var(--brand);animation:csPing .9s ease-out;
}
@keyframes csPing{
  from{transform:scale(.55);opacity:.9;}
  to{transform:scale(2);opacity:0;}
}
/* The travel lane. Overlays the third grid column exactly, so a contact's Y is the only
   thing JS has to compute. */
.cs-lane{position:absolute;top:0;right:0;bottom:0;width:var(--cs-lane);}
.cs-contact{
  position:absolute;left:0;top:0;
  display:flex;align-items:center;gap:.5rem;
  opacity:0;
  /* ⚠️ 1.8s MUST equal CS_TICK in landing.js, and linear is not a style choice.
     A move that finishes before the next one starts leaves the contact resting at each
     row, which reads as stop-start; matching the duration to the tick means every move
     begins exactly as the last ends, so the column descends at one steady speed. Any
     easing would then re-introduce the stalling at each row that the timing removed.
     CS_TRAVEL sits just under this: it is when a contact counts as arrived. */
  transition:transform 1.8s linear, opacity .5s ease;
}
.cs-contact.is-on{opacity:1;}
.cs-ini{
  width:28px;height:28px;border-radius:50%;flex:none;
  display:grid;place-items:center;
  font-size:.625rem;font-weight:700;color:#fff;
  background:rgba(255,255,255,.10);border:1px solid rgba(255,255,255,.18);
}
/* Fixed width, not shrink-to-fit: names differ in length, and letting this size to its
   content would step every clock chip left and right as the stream cycles. The width is
   set by the LONGEST name in CS_PEOPLE, so that list is kept short and even — one long
   name would push every clock right and leave the short ones trailing a gap. */
.cs-person{
  width:74px;flex:none;min-width:0;
  display:flex;flex-direction:column;gap:.0625rem;
}
.cs-name,
.cs-id{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.cs-name{font-size:.75rem;font-weight:650;color:#fff;line-height:1.25;}
.cs-id{font-size:.6875rem;color:#8D8078;line-height:1.25;}
/* Hidden until the contact reaches the deadline step, then it travels with them: the
   deadline belongs to the person, which is the section's whole claim. */
.cs-clock{
  font-size:.75rem;font-weight:650;font-variant-numeric:tabular-nums;
  /* Transparent while the deadline is running, filled once it converts — so the green
     chip is the only one carrying a fill and reads as the outcome rather than as one
     more state. The border still holds the pill's shape. */
  color:var(--brand);background:rgba(242,102,42,0);
  border:1px solid rgba(242,102,42,.5);
  padding:.1875rem .4375rem;border-radius:var(--r-pill);white-space:nowrap;
  opacity:0;transform:translateX(-10px) scale(.94);
  /* opacity and transform are deliberately NOT here: the arrival animation below owns
     them, and a transition on the same properties fights it on the frame the class
     lands. The colour properties stay, for the swap to green at conversion. */
  transition:color .3s ease, background-color .3s ease, border-color .3s ease;
}
/* The chip overshoots its resting position and settles back, so a deadline arrives with
   some weight instead of sliding quietly to a stop.

   Keyframes rather than a back-out cubic-bezier: a bezier can overshoot only ONCE, and
   a single overshoot reads as a bounce. Settling through a second, smaller swing is
   what reads as elastic. */
@keyframes csClockIn{
  0%   {opacity:0;transform:translateX(-10px) scale(.94);}
  50%  {opacity:1;transform:translateX(6px)   scale(1.05);}
  72%  {opacity:1;transform:translateX(-3px)  scale(.985);}
  88%  {opacity:1;transform:translateX(1.5px) scale(1.005);}
  100% {opacity:1;transform:none;}
}
/* Lighter and slightly held back, so the chip still reads as a number first and the
   word only names it. Same colour, not a second one: two tones inside a chip this small
   would fight. */
.cs-clock-k{font-weight:600;opacity:.72;margin-right:.375rem;}
.cs-contact.has-clock .cs-clock{
  opacity:1;transform:none;
  animation:csClockIn .62s cubic-bezier(.3,.7,.4,1) both;
}
/* Same green .flash-dark uses; --acc-green-fg is near-black on this surface. */
.cs-contact.is-won .cs-clock{
  color:#3CB44B;background:rgba(60,180,75,.14);border-color:rgba(60,180,75,.32);
}

/* ---------- expired pages ----------
   Off-white, which also breaks the editor -> extensions white-on-white run that the
   section now sits inside. Copy left / visual right, mirroring .ext-grid's reversed
   split below it. */
/* White, so it separates from the off-white .an above and .esp below rather than
   running into both — this used to be the middle of an off-white run. */
.xp{background:var(--paper);}
/* White bar instead of .mock-bar's --ink-100. Scoped to this section's frame via a
   modifier: .mock-bar is shared with the hero, and recolouring it directly would
   silently restyle that browser too. */
.xp-mock .mock-bar{background:var(--paper);}
.xp-grid{display:grid;grid-template-columns:1fr 1.05fr;gap:clamp(2rem,5vw,4rem);align-items:center;}

/* The page behind the overlay. No radius or shadow of its own — it sits inside .mock,
   which owns the frame and clips this to the browser's corners via its overflow:hidden.

   CRIMSON, from src/lib/overlayThemes.ts, id 'crimson': the 0% and 100% stops are its
   defaults.pageColor and page.darkStop2, at the 120% 96% at 50% 0% geometry the theme
   itself specifies.

   ⚠️ The MIDDLE stop is not the theme's. Crimson's page.darkStop1 is #310A13; #43101c
   is lighter and redder, which keeps the card readable against it rather than letting
   the middle of the gradient fall away to near-black. One more place this mock is a
   customised Crimson rather than the shipped defaults — see the eyebrow note below. */
.xp-stage{
  /* Two-value shorthand, so top and bottom cannot drift apart: they were separate
     clamps and the card sat 20px higher than centre. */
  padding:clamp(2.5rem,5vw,4rem) clamp(1.25rem,3vw,2rem);
  background:radial-gradient(120% 96% at 50% 0%, #5E1424 0%, #43101c 55%, #170509 100%);
}
/* ⚠️ Crimson's card is the one that breaks the pattern: cardColor is #00000033, BLACK
   at 20%, where every other theme uses a near-invisible white wash. Its border is the
   shared white highlight at .17, not a matching black. Both straight from the theme. */
/* Scaled to .9 by TWO properties, and both are needed.
   zoom alone does not narrow this card: it is an auto-width block, so it re-lays-out at
   parentWidth/0.9 and scales back to exactly fill the parent. The box stays put and only
   the padding and type shrink, which reads as a card stretched horizontally. Measured:
   CSS width 504.8px rendering at 454px, the full stage content width.
   width:90% supplies the horizontal 0.9. The percentage resolves against the inflated
   coordinate space (454/0.9 = 504.8), so 90% of that scaled back down lands at 409px —
   one 0.9 overall, not 0.81. Percentage rather than a px max-width so it holds at every
   width instead of quietly ceasing to bind as the column narrows. */
.xp-card{
  zoom:.9;width:70%;margin-inline:auto;
  background:rgba(0,0,0,.20);border:1px solid rgba(255,255,255,.17);
  border-radius:16px;padding:clamp(1.5rem,3.5vw,2.25rem);text-align:center;
  font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
}
/* --- copy-column icon list (variant scaffolding, 2026-08-16) ---
   Same construction as .anf-* in the analytics section. Own prefix because sections
   here own their classes; identical values, so if one is ever restyled the other has
   to be moved with it deliberately rather than by accident.
   ⚠️ .xp-ico below is a different thing entirely — the lock inside the mock. These are
   the tiles in the copy column. */
.xpf-list{margin-top:1.625rem;display:flex;flex-direction:column;gap:1.5rem;list-style:none;padding:0;}
.xpf-item{display:flex;gap:1rem;align-items:flex-start;}
.xpf-ico{
  width:40px;height:40px;border-radius:11px;flex:none;
  display:grid;place-items:center;
  background:var(--brand-wash);color:var(--brand);border:1px solid var(--brand);
}
.xpf-mark{width:19px;height:19px;}
.xpf-body{display:flex;flex-direction:column;gap:.3125rem;min-width:0;}
/* #CC4E14, not --brand: 4.50:1 on white where --brand is 3.13:1 and fails. */
.xpf-name{font-size:var(--fs-body);font-weight:650;color:#CC4E14;}
.xpf-text{font-size:var(--fs-sm);color:var(--ink-600);line-height:1.55;}

.xp-ico{width:44px;height:44px;margin:0 auto .875rem;color:#FF6B7E;}
/* Matched to the icon rather than Crimson's own eyebrowColor (#B97E8A). That makes this
   a CUSTOMISED Crimson, not the theme default — which is on-message, since the section
   copy is about picking your own colours, but it does mean the card no longer renders
   exactly what a customer sees before they touch anything. */
.xp-eyebrow{font-size:11px;font-weight:500;letter-spacing:.14em;text-transform:uppercase;color:#FF6B7E;}
/* Crimson's headlineColor, which is NOT its iconColor — Ember happened to use one value
   for both, so copying the icon colour up here would have looked right and been wrong. */
.xp-head{font-size:clamp(1.375rem,2.6vw,1.75rem);font-weight:600;line-height:1.15;letter-spacing:-.02em;color:#FFE1E6;margin:.5rem 0 1.2rem;}
/* Skeleton in place of the body copy, borrowing .fp-skl's rgba(255,255,255,.11) from the
   Flash Pass card but no longer its 10px bar — these are 8px now, radius halved with it
   so they stay pills rather than slightly-rounded rectangles. Deliberately its own class
   rather than reusing .fp-skl: sections here own their classes, so a Flash Pass tweak
   cannot silently restyle this card.

   align-items:center rather than the default stretch — the second bar is 78% wide, so
   stretched it sat left and pointed the eye off the centre line the icon, eyebrow and
   headline all sit on. */
.xp-sk{display:flex;flex-direction:column;align-items:center;gap:14px;max-width:34ch;margin:0 auto;}
.xp-skl{display:block;width:100%;height:8px;border-radius:4px;background:rgba(255,255,255,.11);}

/* Phone override. MUST live here, after .xp-card: same specificity, and the hero's own
   max-width:700px block sits ~550 lines earlier in this file, so putting it there lost
   to the base rule on source order (measured — the card stayed at 217px).
   70% is right against a wide stage but over-narrows here: at 390px it left a 217px card
   with 174px of content and a two-line headline inside a frame only 350px wide. The
   phone frame already supplies the breathing room the gutters give at desktop. */
@media (max-width:700px){
  .xp-card{width:92%;}
}


/* ---------- deadline extensions ----------
   Off-white as of 2026-08-16, and no border. It was --paper sitting directly under the
   white .ed-sec, and the hairline was the only thing separating them; the surface does
   that now, so the border was drawing a line where there is already an edge. The seam
   below needs nothing either — .flash is dark. */
.ext{background:var(--paper-alt);}
/* Reversed: visual in column 1, copy in column 2. The DOM order is copy-then-visual so
   the stacked layout reads copy first, so the swap is done with `order` — and it has to
   be undone at the stacking breakpoint or the visual jumps above the headline. */
.ext-grid{display:grid;grid-template-columns:1.05fr 1fr;gap:clamp(2rem,5vw,4rem);align-items:center;}
.ext-visual{order:-1;}

/* A SUGGESTION of an interface, not the product's own. Two earlier passes were both
   too loud — first the reused .ec-widget capsules, then big near-black digit blocks.
   This one is a quiet settings panel: small type, warm neutrals, one accent, and the
   only strong contrast reserved for the live time itself. Nothing here is ported from
   the app; the app's greys and 3px radii would not sit on this page. */
.dx-card{
  background:var(--paper);border:1px solid var(--ink-200);
  border-radius:var(--r-lg);box-shadow:var(--sh-md);
  padding:clamp(1.25rem,2.5vw,1.625rem);
}
.dx-top{
  display:flex;align-items:center;justify-content:space-between;
  padding-bottom:.9375rem;border-bottom:1px solid var(--ink-200);margin-bottom:1rem;
}
.dx-top-label{font-size:var(--fs-sm);font-weight:650;color:var(--ink);}
/* Reads as "this is a setting you switch on" — the one piece of overt UI, kept small.

   Same geometry as .ed-toggle in the editor mock above, which is the app's real
   Toggle.tsx: a 30x8 track with a 16px knob OVERLAPPING it rather than a filled pill
   with an inset dot. Two toggles on one page that disagree about what a toggle looks
   like is the kind of thing a reader notices without being able to say why.

   Shown ON — this setting is switched on and the rows below show the extension it
   granted — so the knob sits right, where .ed-toggle's sits left.

   Knob is --brand, matching the ON state of the app's own Toggle. This is a deliberate
   exception to the page's "orange means act, keep it for CTAs" rule — the same licence
   the evergreen timeline's key dots take — on the grounds that the toggle is a picture
   of the app's UI and reads as one. Track stays --ink-300 rather than the app's neutral
   #CFCFCF: it is doing nothing the page's own ramp cannot. */
.dx-switch{
  position:relative;width:30px;height:16px;flex:none;
}
.dx-switch::before{
  content:'';position:absolute;left:0;top:50%;margin-top:-4px;
  width:30px;height:8px;border-radius:999px;background:var(--ink-300);
}
.dx-knob{
  position:absolute;left:14px;top:50%;margin-top:-8px;
  width:16px;height:16px;border-radius:50%;background:var(--brand);
  box-shadow:0 1px 2px 0 rgba(0,0,0,.2);
}

.dx-row{
  display:flex;align-items:center;gap:.625rem;
  padding:.8125rem .9375rem;border-radius:var(--r-md);
}
/* Expired is flat and recessed; live is lifted onto --paper with a hairline and a
   small shadow. The state difference is carried by elevation rather than by colour,
   which is what keeps it quiet. */
.dx-row-off{background:var(--ink-50);}
.dx-row-on{background:var(--paper);border:1px solid var(--ink-200);box-shadow:var(--sh-sm);}
.dx-dot{width:7px;height:7px;border-radius:50%;flex:none;}
.dx-row-off .dx-dot{background:var(--ink-300);}
.dx-row-on .dx-dot{background:var(--acc-teal-fg);}
.dx-name{flex:1;font-size:var(--fs-sm);color:var(--ink-600);}
.dx-time{font-size:var(--fs-sm);font-weight:650;font-variant-numeric:tabular-nums;}
.dx-row-off .dx-time{color:var(--ink-500);}
.dx-row-on .dx-time{color:var(--ink);}
.dx-state{
  font-size:.625rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  width:3.5rem;text-align:right;
}
/* --ink-500, not --ink-300. At --ink-300 this measured 1.42:1 on the row's own
   --ink-50 and was invisible rather than soft; --ink-500 is 4.70:1. It still reads
   lighter than the row's own label beside it (--ink-600), so it stays recessed. */
.dx-row-off .dx-state{color:var(--ink-500);}
.dx-row-on .dx-state{color:var(--acc-teal-fg);}

/* The dashed rule is what says "in between these two". Indented 19px so it drops from
   under the dots: 15px of row padding + half the 7px dot. */
.dx-trigger{
  display:flex;align-items:center;gap:.375rem;
  margin-left:19px;padding:.75rem 0 .75rem 1rem;
  border-left:2px dashed var(--ink-200);
  font-size:var(--fs-xs);
}
.dx-trigger-on{color:var(--ink-500);}
.dx-trigger-dot{color:var(--ink-300);}
/* Teal matches "Extension period" in the analytics card above, so one hue means
   extension across both sections. */
.dx-trigger-add{color:var(--acc-teal-fg);font-weight:700;}

/* ---------- the row runs out of width on a phone ----------
   Measured on the real render, not computed from the CSS: at 15px Inter "Extended
   deadline" inks 131.2px and "Original deadline" 118.8px, "00:00:00" is 66.2px, the
   .dx-state box is 56px, the dot 7px, three gaps 30px and the row padding 30px. Below
   1020px the card is full width, so the label's share works out to exactly
   `viewport - 271.2`. That puts the wrap at 402px for "Extended deadline" and 390px for
   "Original deadline" — which is why BOTH wrapped on a 390px phone and neither does on
   a 430px one.

   Tightening gaps, padding and type was tried on paper first and buys about 16px, which
   still breaks by 375px and badly by 320px. So the row becomes two lines instead: label
   on top, time and state beneath it, time aligned under the label and state still on the
   right edge. Deterministic at every width down to 320 and it reads as a layout rather
   than as text that ran out of room.

   Breakpoint is 420px, not the 402px the arithmetic gives and not the file's usual 430px
   block: at 414px (iPhone Plus/XR) a single line clears by only 11.6px, which is under
   10% and would be eaten by a font fallback. 430px keeps the single line with 27.6px in
   hand. */
@media (max-width:420px){
  .dx-row{
    display:grid;
    grid-template-columns:auto 1fr auto;
    grid-template-areas:"dot name name"
                        ".   time state";
    column-gap:.625rem;row-gap:.25rem;
  }
  .dx-dot{grid-area:dot;}
  .dx-name{grid-area:name;}
  .dx-time{grid-area:time;}
  /* Auto, not the 56px the one-line row reserves. That width exists only to keep the two
     times aligned while the states share a column; here the time has a column of its own
     and does the aligning itself, so the reservation is dead weight worth 31px on the
     LIVE row. */
  .dx-state{grid-area:state;width:auto;}
}

/* ---------- flash pass ---------- */
.flash{background:linear-gradient(180deg,var(--brand-wash) 0%,var(--paper) 66%);border-top:1px solid var(--ink-200);}

/* ---------- dark variant ----------
   Same markup, same components. Only surfaces and text colours change, which is why
   the win cards and the badge did not need their own dark copies — they inherit. */
.flash-dark{
  position:relative;overflow:hidden;
  background:var(--ink);
  border-top:1px solid rgba(255,255,255,.08);
}
.flash-dark::before{
  content:'';position:absolute;inset:0;pointer-events:none;
  background:
    radial-gradient(52% 44% at 50% 0%, rgba(242,102,42,.17), transparent 68%),
    radial-gradient(40% 40% at 88% 88%, rgba(91,75,196,.14), transparent 70%);
}
.flash-dark > .wrap{position:relative;}
.flash-dark h2{color:#fff;}
.flash-dark .lead{color:#A6988E;}
.flash-dark .win{
  background:rgba(255,255,255,.045);
  border-color:rgba(255,255,255,.10);
  box-shadow:none;
}
.flash-dark .win > div > b{color:#fff;}
.flash-dark .win > div > span{color:#A6988E;}
/* the deep green reads as near-black here; on --ink this brighter one is 6.69:1 */
.flash-dark .win-arrow,
.flash-dark .win > div > b em{color:#3CB44B;}
.flash-grid{display:grid;grid-template-columns:1fr 1.05fr;gap:clamp(2rem,5vw,4rem);align-items:center;}
/* ⚠️ DORMANT since 2026-08-13 — both sections now use the page's standard .eyebrow
   with the lightning mark beside it, so nothing carries .flash-badge. Kept while the
   section is still being explored; delete with the rest of the scaffolding at launch
   if the pill does not come back.

   Solid violet. On a page of warm oranges and warm greys it is the one hue with
   nothing else competing, so it reads as "this is a distinct thing" at a glance —
   which the light amber fill did not. White on it is 6.47:1. */
.flash-badge{
  display:inline-flex;align-items:center;gap:.4375rem;
  background:var(--acc-violet-fg);color:#fff;
  box-shadow:0 6px 16px -6px rgba(91,75,196,.55);
  font-size:.6875rem;font-weight:700;letter-spacing:.09em;padding:.375rem .75rem;
  border-radius:var(--r-pill);margin-bottom:1.125rem;
}
.flash-badge svg{width:13px;height:13px;}
/* Two outcome cards. Selectors are scoped to the text column — a bare `.win span`
   would also reach the arrow if it were ever swapped from an <svg> to a <span>. */
.flash-wins{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1.125rem;margin-top:clamp(2rem,4vw,3rem);}
.win{
  display:flex;gap:.9375rem;align-items:flex-start;
  background:var(--paper);border:1px solid var(--ink-200);
  border-radius:var(--r-lg);padding:1.375rem 1.4375rem;box-shadow:var(--sh-sm);
}
.win-arrow{width:26px;height:34px;flex:none;color:var(--acc-green-fg);}
.win > div > b{
  display:block;font-size:1.0625rem;font-weight:700;color:var(--ink);
  letter-spacing:-.02em;margin-bottom:.375rem;
}
.win > div > b em{font-style:normal;color:var(--acc-green-fg);}
/* 0.875rem = 14px. Not a token: the scale has --fs-sm (15px) and --fs-xs (13px) and
   nothing between, and this is the only .win on the page, so it takes a literal rather
   than earning a scale entry. rem not px so it still follows the reader's base size. */
.win > div > span{display:block;font-size:0.875rem;color:var(--ink-500);line-height:1.55;}

/* ============================================================================
   Flash Pass demo — modelled on the app's real overlay.

   ⚠️ PARTLY DORMANT. Both animated sections were removed 2026-08-13, so nothing on
   the page currently carries .fp-demo or the .browser wrapper. Kept deliberately —
   this is still an A/B comparison and the animated version may come back; it comes
   out with the rest of the scaffolding at launch if it does not.
   Dead until then: .browser*, .fake-page, .sk-*, .fp-overlay, .fp-cursor, .fp-live,
   .fp-btn-busy/.fp-btn-done, .fp-spin, and every .browser.ph-* rule.
   STILL LIVE, used by the two static sections: .fp-scale, .fp-card, .fp-icon,
   .fp-eyebrow, .fp-headline, .fp-sub, .fp-btn.

   Ember theme (the product's default) and the default copy from
   DEFAULT_FLASH_PASS_CONFIG. The card is built at the app's own dimensions —
   600px wide, 50px padding, 44px headline — and the whole thing is scaled as one
   unit, so the proportions between icon, headline, body and button stay true to
   what a customer actually sees rather than being re-guessed at small size.
   ========================================================================== */
.browser{background:var(--paper);border-radius:var(--r-lg);box-shadow:var(--sh-lg);overflow:hidden;border:1px solid var(--ink-200);}
.browser-bar{display:flex;align-items:center;gap:.4375rem;padding:.6875rem .875rem;background:var(--ink-100);border-bottom:1px solid var(--ink-200);}
.browser-bar i{width:9px;height:9px;border-radius:50%;background:var(--ink-300);flex:none;}
.browser-url{margin-left:.5rem;flex:1;background:var(--paper);border:1px solid var(--ink-200);border-radius:var(--r-pill);font-size:.6875rem;color:var(--ink-500);padding:.25rem .6875rem;}
.browser-body{position:relative;height:470px;overflow:hidden;background:var(--paper);}

/* the offer page underneath */
/* 5.25rem of bottom padding, not 1.5: the buy button sits at the foot of the page via
   margin-top:auto, and the flash timer floats over that same corner. Without the
   clearance the timer lands on top of it. */
.fake-page{padding:1.5rem 1.625rem 5.25rem;display:flex;flex-direction:column;gap:.6875rem;height:100%;}
.sk-h{height:15px;width:62%;border-radius:5px;background:var(--ink-100);}
.sk-l{height:9px;border-radius:5px;background:var(--ink-50);}
.sk-l.w90{width:90%;} .sk-l.w75{width:75%;} .sk-l.w82{width:82%;}
.sk-btn{margin-top:auto;height:38px;width:172px;border-radius:var(--r-pill);background:var(--brand-deep);display:grid;place-items:center;color:#fff;font-size:.8125rem;font-weight:650;}

/* --- the overlay, Ember --- */
.fp-overlay{
  position:absolute;inset:0;z-index:2;
  background:radial-gradient(120% 96% at 50% 0%, #4D1F13 0%, #210D08 55%, #080302 100%);
  display:grid;place-items:center;
  opacity:1;transition:opacity .5s ease;
}
.browser.ph-open .fp-overlay{opacity:0;}
/* zoom, not transform:scale — scale leaves the layout box at full 600x596, so the card
   still overflowed the frame and clipped its own button even though it *looked* small
   enough. zoom shrinks the box too, so place-items:center has something real to centre. */
.fp-scale{zoom:var(--fp-scale,.72);}
.fp-card{
  width:600px;padding:50px;border-radius:16px;text-align:center;
  background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.12);
  box-shadow:0 10px 34px rgba(30,50,90,.17);
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
}
.fp-icon{width:60px;height:60px;color:#FF8A5E;margin:0 auto;}
.fp-eyebrow{margin-top:18px;font-size:13px;font-weight:500;letter-spacing:.14em;text-transform:uppercase;color:#8A6357;}
.fp-headline{font-size:44px;font-weight:600;line-height:1.15;letter-spacing:-.02em;color:#FF8A5E;margin:14px 0 30px;}
.fp-sub{display:flex;flex-direction:column;gap:16px;}
.fp-sub p{font-size:16px;line-height:1.6;color:#F2C9BD;}
.fp-btn{
  margin-top:50px;display:inline-flex;align-items:center;justify-content:center;
  padding:18px 36px;border-radius:15px;background:#DA5429;color:#fff;
  font-size:17px;font-weight:600;
  box-shadow:0 4px 8px 1px rgba(0,0,0,.15), inset 0 1px 0 rgba(255,255,255,.28);
}
.fp-btn-busy,.fp-btn-done{display:none;align-items:center;}
.fp-btn-done svg{width:19px;height:19px;margin-right:9px;}
.browser.ph-busy .fp-btn-label,.browser.ph-done .fp-btn-label{display:none;}
.browser.ph-busy .fp-btn-busy{display:inline-flex;}
.browser.ph-done .fp-btn-done{display:inline-flex;}
.fp-spin{
  width:16px;height:16px;margin-right:10px;border-radius:50%;
  border:2px solid rgba(255,255,255,.35);border-top-color:#fff;
  animation:fpSpin .7s linear infinite;
}
@keyframes fpSpin{to{transform:rotate(360deg);}}

/* --- cursor. Sits above the overlay, white with a dark outline so it reads on the
       card as well as on the page once the overlay clears. --- */
.fp-cursor{
  position:absolute;left:50%;top:79%;z-index:6;
  width:30px;height:30px;pointer-events:none;
  transform:translate(52px,64px);
  transition:transform .95s cubic-bezier(.32,.72,.28,1), opacity .3s ease;
  filter:drop-shadow(0 3px 7px rgba(0,0,0,.5));
}
.browser.ph-point .fp-cursor{transform:translate(-2px,-2px);}
.browser.ph-click .fp-cursor{transform:translate(-2px,-2px) scale(.8);transition:transform .12s ease;}
.browser.ph-open .fp-cursor{opacity:0;}

/* --- the flash window's timer: parks off the left edge and slides in, the same
       move the hero mock's widget makes --- */
.fp-live{
  position:absolute;left:16px;bottom:16px;z-index:3;
  opacity:0;transform:translateX(calc(-100% - 32px));
  transition:opacity .35s ease, transform .55s cubic-bezier(.16,1,.3,1);
}
.browser.ph-open .fp-live{opacity:1;transform:none;}
.fp-live .ec-widget{transform:scale(.8);transform-origin:left bottom;}

/* --- static variant: the card alone, no chrome, no run ------------------------
   No backdrop panel — the card sits directly on the section. .fp-still is now a
   positioning wrapper only; it shrink-wraps the card (zoom changes the layout
   box, so fit-content measures the scaled size) and centres it in the column. */
.fp-still{width:fit-content;margin-inline:auto;}

/* Deliberate deviation from the app's overlay. In the product the card is a 5%
   white film over the overlay's backdrop, so the ember gradient you read as "the
   card" is really the backdrop showing through. With the backdrop gone the card
   has to paint that itself: the same radial, with the 5% film layered back over
   it so the interior colour is unchanged from the framed version. */
.flash-static .fp-card{
  background:
    linear-gradient(rgba(255,255,255,.05), rgba(255,255,255,.05)),
    radial-gradient(120% 96% at 50% 0%, #4D1F13 0%, #210D08 55%, #080302 100%);
  /* 22px, not the app's 16 — the frame's 20px radius used to carry the outer
     silhouette, and at this zoom 16 renders ~12px against the section's 20px
     win cards. The 1px light border stays: on --ink it is the only thing
     separating the card's dark foot from the section. */
  border-radius:22px;
  box-shadow:0 34px 78px -28px rgba(0,0,0,.85);
}
/* Card styling, shared by both remaining sections so the only difference between
   them is the surface. Eyebrow takes the icon's orange: on the card's top gradient
   (#4D1F13 under the 5% film) that is 5.17:1, where the app's #8A6357 was 2.29:1
   and failed at 13px. The near-white headline is 9.69:1 there. Both figures are
   properties of the card, so they hold on either surface. */
.flash-static .fp-eyebrow{color:#FF8A5E;}
.flash-static .fp-headline{color:#FFE1D5;}
.flash-static .fp-sub p{text-align:left;}

/* Skeleton body. Every selector is class-scoped and the bars carry their own class
   rather than being reached as bare children — a bare `.fp-skp span` would be one
   refactor away from matching something else, which has bitten this file twice.
   Not reusing .sk-l from .fake-page: that one is --ink-50/--ink-100, sized to read
   on a white page, and would be invisible here. */
.fp-sub-sk{gap:18px;}
.fp-skp{display:flex;flex-direction:column;gap:14px;}
.fp-skl{display:block;height:10px;border-radius:5px;background:rgba(255,255,255,.11);}

/* Single outcome card, so one column — it sits in the text column here, not in the
   full-width row under the grid. */
.flash-static .flash-wins{grid-template-columns:1fr;margin-top:clamp(1.75rem,3.5vw,2.5rem);}

/* Its own scale steps, and they have to be restated rather than inherited: the
   demo's mobile rules are plain `.fp-scale` (0,1,0) and this is `.flash-static
   .fp-scale` (0,2,0), so this wins at every width regardless of source order.
   Dropping the breakpoints below would silently leave the card at .78 on a phone.

   Widths are now the card alone — losing the frame gave back its 28px of padding.
   .78 puts the card at 468px. The two-column layout ends at 1020px, where the
   right column is at its narrowest 493px, so that clears with 25px to spare;
   stacked below 1020 it has the full content width and holds until ~510px. */
.flash-static .fp-scale{--fp-scale:.78;}
@media (max-width:520px){ .flash-static .fp-scale{--fp-scale:.56;} }
@media (max-width:400px){ .flash-static .fp-scale{--fp-scale:.45;} }

/* ---------- pricing (layout kept as drawn — founder is happy with it) ---------- */
/* ---------- everything else (catch-all features) ----------
   White with a border-bottom: #analytics above is off-white so this alternates against
   it, and the hairline is the seam against #pricing below, which is white too and has
   no border of its own. Same device .esp and .ext use. */
.fx-sec{background:var(--paper);border-bottom:1px solid var(--ink-200);}
.fx-grid{
  display:grid;grid-template-columns:repeat(3,1fr);
  gap:clamp(2.5rem,5vw,3.5rem) clamp(1.5rem,4vw,3rem);
  margin-top:clamp(2.5rem,5vw,3.5rem);
}
.fx{text-align:center;}
/* Circular here, where the rest of the page uses a rounded square, because the
   reference is a centred column and a circle sits better over centred text. Keeps the
   1px brand border the other tiles carry so it still reads as the same icon system. */
.fx-ico{
  width:44px;height:44px;border-radius:50%;display:grid;place-items:center;
  margin:0 auto .9375rem;
  background:var(--brand-wash);color:var(--brand);border:1px solid var(--brand);
}
.fx-ico svg{width:19px;height:19px;}
.fx h3{margin-bottom:.4375rem;}
/* measure caps the line length: centred text past ~34ch is hard to track back to the
   start of the next line. */
.fx p{font-size:var(--fs-sm);color:var(--ink-500);line-height:1.55;max-width:34ch;margin-inline:auto;}

.pricing{background:var(--paper);}
.price-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1.5rem;max-width:760px;margin:clamp(2.5rem,5vw,3.25rem) auto 0;}
.plan{
  position:relative;background:var(--paper);border:1px solid var(--ink-200);
  border-radius:var(--r-lg);padding:1.75rem 1.625rem;box-shadow:var(--sh-sm);
  display:flex;flex-direction:column;
}
.plan.featured{border:2px solid var(--brand);box-shadow:var(--sh-md);}
/* --brand, matching .plan.featured's border above, so the tag reads as part of the
   outline rather than a second, darker orange sitting on it.

   Centred ON the border rather than offset by a fixed amount: `top` is measured from
   the padding box, so -1px is the middle of the 2px border, and translateY(-50%) puts
   the tag's own centre there whatever height its text ends up. The old -11px was tuned
   to one specific font size and would drift the moment that changed — as it just did. */
.ribbon{
  position:absolute;top:-1px;left:1.625rem;transform:translateY(-50%);
  background:var(--brand);color:#fff;
  font-size:.75rem;font-weight:600;letter-spacing:.03em;
  padding:.25rem .6875rem;border-radius:var(--r-pill);
}
.plan-name{font-size:.875rem;font-weight:700;letter-spacing:.11em;color:var(--ink-500);margin-bottom:.625rem;}
/* the "Everything in Lifetime, plus:" line — sits with the list, not the description */
.plan-plus{font-size:var(--fs-sm);color:var(--ink-700);margin-bottom:.875rem;}

/* Bump the Lifetime card when someone tries Pro first. Restarted from JS by removing
   the class, forcing a reflow, then re-adding — without the reflow the browser
   coalesces the two changes and the animation never replays on a second click. */
@keyframes planBump{
  0%   {transform:none;                          box-shadow:var(--sh-md);}
  35%  {transform:translateY(-7px) scale(1.025); box-shadow:var(--sh-lg);}
  70%  {transform:translateY(0) scale(.997);     box-shadow:var(--sh-md);}
  100% {transform:none;                          box-shadow:var(--sh-md);}
}
.plan.bump{animation:planBump .62s cubic-bezier(.22,1,.3,1);}
.plan-price{display:flex;align-items:baseline;gap:.5rem;flex-wrap:wrap;margin-bottom:.875rem;}
.plan-price b{font-size:2.5rem;font-weight:750;color:var(--ink);letter-spacing:-.04em;line-height:1;}
/* Both cards use this — "/ ONE TIME" and "/month" — so the two prices sit in the same
   position and at the same size, and the eye compares the figures. The pill-badge .once
   the Lifetime card used to carry is gone. */
.plan-price .per{font-size:var(--fs-sm);color:var(--ink-500);font-weight:500;}
/* Lifetime only. --brand-deep, not --brand: 4.53:1 on white where --brand is 3.13:1 and
   fails AA at this size. Tracking because it is uppercase — the same reason .an-label
   LOST its when that went sentence case. */
/* 14px rather than the .per default of --fs-sm (15px). Scoped to .per-once, which is
   only on the Lifetime card, so the Pro card's "/month" keeps its 15px. */
.plan-price .per-once{color:var(--brand-deep);letter-spacing:.06em;font-size:14px;}
.plan-desc{font-size:var(--fs-sm);color:var(--ink-500);line-height:1.55;margin-bottom:1.25rem;}
.plan ul{display:flex;flex-direction:column;gap:.625rem;margin-bottom:1.75rem;}
.plan li{display:flex;gap:.5625rem;align-items:flex-start;font-size:var(--fs-sm);color:var(--ink-700);}
.plan li svg{width:16px;height:16px;color:var(--teal);flex:none;margin-top:.1875rem;}
.plan .btn{width:100%;margin-top:auto;}

/* Guarantee strip under the plans. Asked for explicitly as the quiet version of the
   usual big centred guarantee block, so it carries none of the section furniture:
   no eyebrow, no h2, body-small type throughout, and a flat --ink-50 fill instead of
   a lifted card, which would pull focus straight back off the two plans above it.
   The shield is --brand as of 2026-08-16. It was teal, matching the plan checkmarks
   above it; orange now separates the guarantee from those lists rather than reading as
   one more ticked feature. 3.13:1 on white, which clears the 3:1 that applies to icons
   even though it would fail the 4.5:1 for text.
   NOT named .guarantee: that class is already the hero's guarantee line (line ~205), and
   a second rule under the same name silently restyled it from further down the file.
   (That one was a pill until 2026-08-17; it is borderless now, but the name clash is
   unchanged and this rule must still keep its own name.) */
.pg-guarantee{
  max-width:760px;
  margin:clamp(1.75rem,3vw,2.25rem) auto 0;
  display:flex;gap:.875rem;align-items:flex-start;
  padding:1.125rem 1.25rem;
  /* No fill: the border alone holds the shape. The section is --paper, so an --ink-50
     panel read as a second surface sitting on it right after two plan cards that are
     already raised. */
  /* Dashed, so it reads as a note pinned under the plans rather than a third card
     competing with the two above it. --ink-300 rather than --ink-200: dashes put less
     ink on the page than a solid rule, so the lighter tone all but disappeared at 2px. */
  border:2px dashed var(--ink-300);
  border-radius:var(--r-md);
}
.pg-guarantee-mark{width:22px;height:22px;flex:none;color:var(--brand);margin-top:.125rem;}
.pg-guarantee-title{font-size:16px;font-weight:650;color:var(--ink-800);margin-bottom:.1875rem;}
.pg-guarantee-body{font-size:var(--fs-sm);color:var(--ink-500);line-height:1.55;}

/* ---------- faq ---------- */
.faq{background:var(--paper-alt);}
.faq-list{max-width:760px;margin:clamp(2.25rem,4vw,3rem) auto 0;display:flex;flex-direction:column;gap:.75rem;}
.qa{background:var(--paper);border:1px solid var(--ink-200);border-radius:var(--r-md);overflow:hidden;transition:border-color .22s ease, box-shadow .22s ease;}
.qa[open]{border-color:var(--ink-300);box-shadow:var(--sh-sm);}
.qa summary{
  display:flex;align-items:center;gap:1rem;justify-content:space-between;
  padding:1.125rem 1.375rem;cursor:pointer;list-style:none;
  font-size:1rem;font-weight:600;color:var(--ink);letter-spacing:-.015em;
}
.qa summary::-webkit-details-marker{display:none;}
.qa summary svg{width:18px;height:18px;color:var(--ink-500);flex:none;transition:transform .28s cubic-bezier(.16,1,.3,1);}
.qa[open] summary svg{transform:rotate(180deg);color:var(--brand);}
.qa .ans{padding:0 1.375rem 1.25rem;font-size:var(--fs-sm);color:var(--ink-500);line-height:1.65;max-width:62ch;}

/* ---------- closing CTA ---------- */
.cta-band{background:var(--paper);padding-bottom:var(--sect);}
.cta-inner{
  position:relative;overflow:hidden;border-radius:var(--r-xl);
  background:linear-gradient(140deg,#241C17 0%,#1B1613 55%,#20160F 100%);
  padding:clamp(2.75rem,6vw,4.25rem) clamp(1.5rem,5vw,3.5rem);text-align:center;
}
.cta-inner::before{
  content:'';position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(46% 70% at 50% 0%, rgba(242,102,42,.26), transparent 70%);
}
.cta-inner > *{position:relative;}
.cta-inner h2{color:#fff;}
.cta-inner p{color:#A6988E;margin:1rem auto 0;max-width:46ch;font-size:var(--fs-lead);}
.cta-inner .cta-row{justify-content:center;}
.cta-inner .btn-ghost{background:rgba(255,255,255,.07);color:#fff;box-shadow:inset 0 0 0 1px rgba(255,255,255,.18);}
.cta-inner .btn-ghost:hover{background:rgba(255,255,255,.13);box-shadow:inset 0 0 0 1px rgba(255,255,255,.3);}
.cta-note{margin-top:1.5rem !important;font-size:var(--fs-xs) !important;color:#8A7B71 !important;max-width:none !important;}

/* ---------- footer ---------- */
.site-footer{background:var(--ink);color:#8A7B71;padding-block:clamp(3rem,6vw,4.5rem) 2rem;}
.foot-grid{display:grid;grid-template-columns:1.5fr repeat(3,1fr);gap:2.5rem;}
.foot-brand .logo{color:#fff;margin-bottom:1rem;}
.foot-brand p{font-size:var(--fs-sm);line-height:1.6;max-width:30ch;color:#8A7B71;}
.socials{display:flex;gap:.625rem;margin-top:1.375rem;}
.socials a{
  width:34px;height:34px;border-radius:9px;display:grid;place-items:center;
  background:rgba(255,255,255,.06);color:#A6988E;transition:background .2s ease,color .2s ease;
}
.socials a:hover{background:rgba(242,102,42,.18);color:var(--brand);}
.socials svg{width:16px;height:16px;}
.foot-col h4{font-size:var(--fs-xs);font-weight:700;letter-spacing:.11em;color:#fff;margin-bottom:1.125rem;}
.foot-col li + li{margin-top:.6875rem;}
.foot-col a{font-size:var(--fs-sm);color:#8A7B71;transition:color .18s ease;}
.foot-col a:hover{color:var(--brand);}
.foot-bar{
  margin-top:clamp(2.5rem,5vw,3.5rem);padding-top:1.75rem;
  border-top:1px solid rgba(255,255,255,.09);
  display:flex;flex-wrap:wrap;gap:.75rem 1.5rem;justify-content:space-between;align-items:center;
  font-size:var(--fs-xs);color:#6E6058;
}
.foot-bar .dot-sep{display:flex;gap:1.25rem;}

/* ---------- scroll reveal ---------- */
/* Reveal is OFF by default and switched back ON for three sections only (founder's call,
   2026-08-18): the hero, the revenue chart in #proof, and pricing. It previously ran on all
   93 .reveal elements, 76 of them staggered, so almost nothing on the page arrived at once
   and the long feature run in the middle just delayed reading.

   The class stays in the markup everywhere. This is the VISUAL switched off, not the hook
   removed, so re-enabling a section means adding its id on the line below rather than
   editing HTML. landing.js still collects all 93 and still adds .in to each; on the off
   sections that class simply lands on an element that already looks the way it will end up.

   ⚠️ :where() is what makes this safe, and it is not cosmetic. It contributes ZERO
   specificity, so the enabling rule stays at 0,1,0 -- the same weight as .reveal itself --
   which leaves .reveal.in (0,2,0) still able to beat it. Written the obvious way as
   `#hero .reveal` (1,1,0) it would OUTRANK .reveal.in, and every element in these three
   sections would sit at opacity 0 forever: the reveal would fire, the class would land,
   and nothing would appear.

   ⚠️ Longhand transition-* rather than the shorthand. `transition:` also resets
   transition-delay to 0, which would silently flatten the data-d stagger set below. */
.reveal{opacity:1;transform:none;transition:none;}
:where(#hero,#proof,#pricing) .reveal{
  opacity:0;transform:translateY(22px);
  transition-property:opacity,transform;
  transition-duration:.7s;
  transition-timing-function:cubic-bezier(.16,1,.3,1);
}
.reveal.in{opacity:1;transform:none;}
.reveal[data-d="1"]{transition-delay:.08s;} .reveal[data-d="2"]{transition-delay:.16s;}
.reveal[data-d="3"]{transition-delay:.24s;} .reveal[data-d="4"]{transition-delay:.32s;}
.reveal[data-d="5"]{transition-delay:.4s;}  .reveal[data-d="6"]{transition-delay:.48s;}

/* ---------- responsive ---------- */
/* ---------- where the two-column rows collapse ----------
   1020px, chosen so that iPad PORTRAIT stays two-column rather than stacking. The two
   largest iPads are 1024px (12.9") and 1032px (13" M4) wide in portrait, and both need
   to sit ABOVE this number to keep their columns; every smaller iPad (mini 744, 10.2"
   810, Air 820, Pro 11" 834) is below it and still stacks.

   ⚠️ It was 1080, which put 1024 and 1032 INSIDE the stacked range. Moving it DOWN is
   what widens the two-column range -- the query is a max-width, so a smaller number
   means fewer widths stack. Anyone "raising the breakpoint to fit bigger tablets" will
   get the opposite of what they intend.

   The margin above 1032 is only 12px, so this is deliberately tight. Do not lower it
   further without re-checking the 13" M4. */
@media (max-width:1020px){
  /* minmax(0,…) not 1fr: a plain 1fr track has an `auto` minimum, so the widget's
     intrinsic (pre-transform) width would stretch the column past the viewport and
     clip the hero copy. */
  .hero-grid{grid-template-columns:minmax(0,1fr);gap:0;}
  .hero p.lead,.measure{max-width:none;}

  /* Stacked order is headline → subhead → visual → buttons → guarantee. The visual is
     a sibling of .hero-copy, not one of its children, so it cannot be slotted between
     them by `order` alone. display:contents drops .hero-copy's own box and promotes
     its children to grid items, which puts all five on one `order` scale.
     Grid gap goes to 0 with it: the copy elements already carry their own margins, and
     a gap would stack on top of every one of them. */
  .hero-copy{display:contents;}
  .hero-copy > h1{order:1;}
  .hero-copy > .lead{order:2;}
  .hero-copy > .cta-row{order:4;}
  .hero-copy > .guarantee{order:5;}

  /* stacked hero: the timer left-aligns under the copy */
  .timer-stage{order:3;margin-top:2.25rem;min-height:auto;align-items:flex-start;gap:1rem;}
  /* The badge is the exception to that flex-start: centred in the content column, which
     is what `align-self` gives here since the stage spans it. `left` must go back to 0
     with it, or the -15px paint offset that nudges the badge under the desktop layout
     drags the newly centred box 15px off centre. Desktop is untouched: .timer-stage is
     align-items:center up there, so the badge is already centred and keeps its offset.
     The figure comes down from 1.8rem (28.8px) to 26px at this width only. */
  .lift-badge{align-self:center;left:0;}
  .lift-pct{font-size:26px;}
  /* Stacked order is eyebrow → h2 → lead → the three steps → CTA. The CTA is nested
     inside .steps-copy, not a child of the grid, so `order` alone cannot move it past
     .step-list; display:contents drops that wrapper's box and puts all five on one order
     scale. Same device as .hero-copy above, for the same reason.
     Gap goes to 0 with it, or the 2.25rem would land between every one of the five
     instead of just once. Four of them already carry their own margins (eyebrow 14.4px
     bottom, lead 18px top, CTA 34px top); .step-list was the only one leaning on the gap,
     so it takes the 2.25rem back explicitly. */
  .steps-grid{grid-template-columns:1fr;gap:0;}
  .steps-copy{display:contents;}
  .steps-copy > .eyebrow{order:1;}
  .steps-copy > h2{order:2;}
  .steps-copy > .lead{order:3;}
  .step-list{order:4;margin-top:2.25rem;}
  .steps-copy > .cta-row{order:5;}
  .uc-grid{grid-template-columns:repeat(2,1fr);}
  .fx-grid{grid-template-columns:repeat(2,1fr);}
  /* Four wireframes go to 2x2 rather than one column — they are diagrams, so they stay
     readable small, and an even count leaves no orphan row. */
  .pl-grid{grid-template-columns:repeat(2,minmax(0,1fr));}
  /* Stacked order is eyebrow → h2 → lead → the card → "Turn on and boost sales". That
     last one lives inside .flash-copy, not in the grid, so `order` alone cannot move it
     past the visual; display:contents drops the wrapper's box and puts all five on one
     order scale. Same device as .hero-copy and .steps-copy.
     Gap goes to 0 with it, or the 2.5rem lands between every one of the five instead of
     once. Four already carry their own margins (eyebrow 14.4px bottom, lead 18px top,
     .flash-wins 28px top); only the visual leaned on the gap, so it takes 2.5rem back. */
  .flash-grid{grid-template-columns:1fr;gap:0;}
  .flash-copy{display:contents;}
  .flash-copy > .eyebrow{order:1;}
  .flash-copy > h2{order:2;}
  .flash-copy > .lead{order:3;}
  .flash-visual{order:4;margin-top:2.5rem;}
  .flash-copy > .flash-wins{order:5;}
  /* order must be reset, not just the columns — otherwise the visual stacks ABOVE the
     eyebrow and headline, which is the one thing the DOM order exists to prevent. */
  .ext-grid{grid-template-columns:1fr;gap:2.5rem;}
  .ext-visual{order:0;}
  .an-grid{grid-template-columns:1fr;gap:2.5rem;}
  .an-visual{order:0;}
  /* .xp-grid needs no order reset — copy is already column one there.
     .ef-grid DOES now, but for the opposite reason to .ext-grid and .an-grid above.
     Those two have their visual first in the DOM and reset it to 0 so the copy leads.
     Here the DOM is list-then-visual and the visual is wanted FIRST on a phone, so the
     order is a deliberate swap rather than a reset. Both children are direct children of
     the grid, so this needs no display:contents the way the hero, steps and flash
     sections do -- nothing is nested a level down. */
  .ef-grid{grid-template-columns:1fr;gap:2.5rem;}
  .ef-visual{order:0;}
  /* .ef-copy, not .ef-list: the list is wrapped now so it can carry the CTA below it,
     which makes the wrapper the grid item and .ef-list an ordinary child. Ordering
     .ef-list here would silently do nothing. */
  .ef-copy{order:1;}
  .xp-grid{grid-template-columns:1fr;gap:2.5rem;}

  /* ---------- stacked visuals keep their desktop width ----------
     Every grid above just went to one column, so each visual inherited the full 1140px
     wrap instead of its old ~half. The cards have no width of their own and stretch to
     whatever they are given: measured at a 1000px viewport they render 936 wide against
     520 on desktop. That is not the same design at a larger size, it is a different one
     -- the dx rows, the stats row and the mock browser were all proportioned against
     roughly 500px.

     Capped at their real desktop widths, measured off the render at >=1140px where the
     wrap stops growing and the columns are at their widest:
       .ext-visual / .an-visual  1.05fr  ->  520px
       .ef-visual                        ->  543px
       .xp-visual                1.05fr  ->  520px

     ⚠️ .xp-grid's tracks are `1fr 1.05fr`, REVERSED from .ext-grid and .an-grid, so it
     is the visual that takes the wider track there, not the copy. Reading the grid's
     first child to get "the visual" returns .xp-copy (496px) and caps it 24px too
     narrow -- which is exactly the mistake this note exists to stop. Measure
     .xp-visual itself.

     CENTRED in the column. margin-inline:auto is doing real work here and is not
     decorative: a grid item with a max-width left-aligns on its own, because `stretch`
     resolves to `start` once the width is capped. Removing the margin silently returns
     all four to the left edge.

     Centred also matches .fp-still in the Flash Pass section, which shrink-wraps itself
     and was already centred in its column before any of this -- left-aligning these four
     had made it the odd one out.

     Inert on phones -- these are maxima and the column is narrower than all of them
     below ~500px, so nothing here interacts with the .cs-card zoom tiers.

     .flash-visual is deliberately absent: .fp-still already shrink-wraps with
     width:fit-content and measured 468px at 1200, 1000 and 820 alike, so it never
     stretched and needs no cap. */
  .ext-visual,.an-visual,.xp-visual{max-width:520px;margin-inline:auto;}
  .ef-visual{max-width:543px;margin-inline:auto;}

  .foot-grid{grid-template-columns:1fr 1fr;gap:2.25rem;}
}
@media (max-width:760px){
  /* The overlay card is a fixed 600px and only shrinks by transform, which does not
     change its layout box — so the frame has to come down with it or the card
     overflows. Scale and height move together; changing one alone breaks it. */
  .fp-scale{--fp-scale:.5;}
  .browser-body{height:350px;}
  .fp-live .ec-widget{transform:scale(.66);}
}
@media (max-width:430px){
  .fp-scale{--fp-scale:.42;}
  .browser-body{height:305px;}
  .fp-live{left:10px;bottom:10px;}

  /* Sized to fit rather than leaning on object-fit's letterbox. At 5.95:1 a 26px logo is
     155px wide, which clears both tight spots on a phone: the header has 350 content - 12
     gap - 145 CTA = 193 free, and the footer's brand column is 157 (.foot-grid is two
     columns here). One rule covers both, so the footer needs no override of its own.
     The gap is what buys the header its headroom, so keep it if the height moves. */
  .hdr{gap:.75rem;}
  .logo img{height:26px;}
}
/* ---------- contact stream on small screens ----------
   The card has a real minimum width: --cs-lane is a fixed 248px because a contact's
   name, id and deadline chip have to fit side by side, and a grid item cannot shrink
   below its content. Below ~484px that floor is wider than the column, so the card
   pushed past the viewport.

   Scaled with zoom, the same answer .ed-zoom and .fp-scale already use here, and for
   the same reason: zoom changes the LAYOUT box, so the card genuinely occupies less
   space. transform:scale() would shrink the picture and leave the box overflowing.
   Zoom also hands the card MORE css px to lay out in (available / zoom), so the design
   renders exactly as drawn and is then reduced, rather than reflowing into a version
   nobody designed.

   ⚠️ Tiers, not a formula: zoom does not accept calc(), it silently computes to 1.
   Each step is set from the NARROWEST width it covers, needing zoom <= (W - 40) / 454,
   where 454 is the card's own minimum and 40 the wrap's padding. Re-derive these if
   --cs-lane changes — it is what sets that 454. */
/* The 40px right gutter is a desktop nicety. Kept at these sizes it takes exactly the
   room "Conversion tracked" needs and pushes that label onto two lines, which makes one
   row taller than the other four in what is meant to read as an even descent. */
@media (max-width:500px){ .cs-stage{margin-right:0;} }

/* The SAME wrap came back at the top of the two-column range when the collapse
   breakpoint moved to 1020 (e0e5392), because two columns at iPad-portrait width give
   this card less room than stacking ever did. The row is
   card - padding(58) - gutter(40) - lane(258) - dot(10) - two gaps(20), so at 1024 the
   card is 486 and the label column is left with 100px while "Conversion tracked" needs
   137.5. Four of the six labels wrapped.

   Zeroing the gutter returns all 40px to the label column, which is the only place the
   space can come from without touching --cs-lane -- and --cs-lane must NOT be touched
   casually, since it sets the card's 454px minimum and every zoom tier is derived from
   that.

   Banded rather than folded into a plain max-width, because the stacked range below is
   already fine: there the card is capped at 543 and the labels clear the gutter easily.
   Hence min-width 1021, one past the collapse.

   The upper end is 1140, not the 1101 where the gutter first becomes affordable.
   Affordable is not the same as comfortable: at 1101 the label column is 139px against
   the 137.5 "Conversion tracked" needs, and 1.5px is inside the error of a fallback font
   rendering before Inter loads. 1140 is where --wrap stops growing, so the card reaches
   its full 543 and the column gets 157px, a real 19.5px of headroom. Two-column widths
   between the two keep the gutter off and run 179-219px.

   ⚠️ Re-derive both ends if the collapse breakpoint, --wrap or --cs-lane moves. */
@media (min-width:1021px) and (max-width:1140px){ .cs-stage{margin-right:0;} }

/* Enlarged on phones 2026-08-17. Two things changed, and the governing number is not the
   454 above.
   1. The card pulls -16px into the wrap's 20px gutter, as .chart-card does. The margin
      sits on a ZOOMED element, so it renders at 16*zoom and the box gains 32*zoom.
   2. Steps are denser, because each can only be as large as its NARROWEST width allows
      and wide bands throw the gain away at the top of each.

   ⚠️ THE BINDING CONSTRAINT IS THE LABEL COLUMN, NOT THE CARD MINIMUM. Raising zoom
   SHRINKS the layout box (layout = column / zoom), and the label column is the 1fr that
   absorbs it. "Conversion tracked" holds one line down to a ~480px layout width and wraps
   below it — which is the exact two-line row the note above says margin-right:0 exists to
   prevent.

   That threshold MOVES with the padding and step-gap trim below, so the two are one
   change. Measured by ramping zoom in .01 steps and counting every label's lines:
     before the trim   holds at .78, wraps at .80   (threshold ~470 layout)
     after  the trim   holds at .85, wraps at .86   (threshold ~441 layout)
   Steps are set from zoom <= column / (448 - 32): that 441 plus ~7 of margin. .85 is
   reachable at 390px and deliberately NOT taken, because it leaves about 2px of slack
   and a platform whose fallback font runs wider than Inter would spend it. Measured the
   fallback here for exactly that reason and it came out 3.6% NARROWER (SF Pro at 132.6
   against Inter's 137.5), so Inter is the worst case and these numbers already cover it.

   FYI the card's own min-content is ~413, under everything above, so it never binds. */
/* Reclaim the dead width to the LEFT of the labels, so the zoom above can go up again.
   Measured in layout units at 390px, the card needed 469.4 and here is all of it:
     44   card padding, 22 a side
     137  the widest label, "Conversion tracked"
     30   .cs-step's gap + 10px dot + gap
     258  --cs-lane
   Only the first and third are spare. The lane is measured to its contents (see the note
   above it) and the label is the text itself, so neither can give. Trimming the padding
   to 12 and the step gap to 6 takes 28 off the requirement, which is what pays for the
   higher zoom tiers below. --cs-pad also drives .cs-head's padding-bottom, so the card
   tightens vertically by the same amount, which suits a phone. */
@media (max-width:500px){
  .cs-card{margin-inline:-16px;--cs-pad:12px;}
  .cs-step{gap:.375rem;}
}
@media (max-width:480px){ .cs-card{zoom:1;} }
@media (max-width:460px){ .cs-card{zoom:.96;} }
@media (max-width:440px){ .cs-card{zoom:.91;} }
@media (max-width:420px){ .cs-card{zoom:.86;} }
@media (max-width:400px){ .cs-card{zoom:.83;} }
@media (max-width:385px){ .cs-card{zoom:.79;} }
@media (max-width:370px){ .cs-card{zoom:.75;} }
@media (max-width:355px){ .cs-card{zoom:.72;} }
@media (max-width:340px){ .cs-card{zoom:.67;} }
@media (max-width:320px){ .cs-card{zoom:.57;} }

@media (max-width:680px){
  .uc-grid{grid-template-columns:1fr;}
  .fx-grid{grid-template-columns:1fr;}
  .flash-wins{grid-template-columns:1fr;}
  .price-grid{grid-template-columns:1fr;}

  /* The guarantee moves ABOVE the plans here. It is authored after .price-grid because
     on desktop it reads as a footnote pinned under two side-by-side cards; the moment
     they stack it is a full screen's scroll from the prices and lands after the second
     CTA, where it stops doing that job. Same breakpoint as the stack for that reason --
     the two changes are the same event.

     .wrap is a plain block, so `order` has nothing to act on: it needs a flex or grid
     formatting context first. All three children are ordered explicitly rather than just
     giving the guarantee order:-1, which would have lifted it above the section heading
     as well.

     ⚠️ The top margins swap along with the order. Both blocks carry their spacing as
     margin-top, so leaving each on its own element would have put the small gap under the
     heading and the large one in the middle, which reads as a dropped element rather than
     a deliberate order. The guarantee therefore takes the heading gap
     clamp(2.5rem,5vw,3.25rem) = 40px here.

     The gap BELOW the guarantee is a flat 3rem (founder's call, 2026-08-18) rather than
     the clamp(1.75rem,3vw,2.25rem) it inherited from .price-grid, which wanted more air
     between the guarantee and the plans. No responsive behaviour is lost: 3vw only
     reaches that clamp's 1.75rem floor at 933px, far outside this query, so the old value
     was already a constant 28px at every width it covered. */
  .pricing .wrap{display:flex;flex-direction:column;}
  .pricing .wrap > .center{order:1;}
  /* Delay overridden from its data-d="3" (.24s): that stagger was written for an element
     arriving last, and a lead-in that fades in a beat behind the thing it introduces
     reads as lag. Same specificity as .reveal[data-d="3"], so it wins on source order. */
  .pricing .pg-guarantee{order:2;margin-top:clamp(2.5rem,5vw,3.25rem);transition-delay:.08s;}
  .pricing .price-grid{order:3;margin-top:3rem;}
  /* Cards are stacked here, so the Lifetime card is ABOVE the Pro button, not beside it. */
  .pro-arrow-left{display:none;}
  .pro-arrow-up{display:block;}
  /* Three across rather than stacked. The type steps down to fit: at 390px the card's
     content box is ~310px, so each column gets roughly a third of that and the default
     13px labels would wrap awkwardly. */
  .chart-foot{grid-template-columns:repeat(3,1fr);gap:.5rem;}
  .chart-foot b{font-size:1.125rem;}
  .chart-foot span{font-size:.6875rem;line-height:1.3;}
  /* Widen the chart on phones, in two moves rather than one.
     1. The card pulls -16px into the wrap's own 20px gutter, leaving 4px of page either
        side instead of 8. -16px stays inside the 20px at every width this query covers
        (the wrap's padding only grows above 500px), so it can never overflow.
     2. The CHART ALONE then cancels the card's horizontal padding with a matching
        negative margin, so the plot runs the full width of the card while .chart-head and
        .chart-foot keep their inset. Shrinking the card's padding instead would have
        widened the plot by the same amount but shoved the title, legend and the three
        stat figures hard against the screen edge.
     Net at 390px: the plot goes 350 -> 382, and since the svg is width:100% against a
     fixed viewBox, it gains the same 9% in height for free.
     The radius is 10px here, down from --r-xl (28px). A 28px corner is drawn for a card
     floating in the page; this one is nearly edge to edge. Deliberately off the --r-*
     scale (8/14/20/28), founder's number. */
  /* The card's own title, not a section heading, so it does not follow the h3 scale on a
     phone where it sits directly above a much smaller subhead. */
  .chart-head h3{font-size:15px;}
  .chart-card{margin-inline:-16px;padding:1rem .75rem;border-radius:10px;}
  .chart{margin-inline:-.75rem;}

  /* The svg is width:100% against a fixed 820-wide viewBox, so on a phone everything in
     it renders at 0.466x: "Deadline added" came out at 6.1px, the chip text at 5.8px and
     the connector stroke at 0.82px, which is under one device-independent pixel.
     Scaling the GROUPS rather than bumping font-size keeps each annotation's proportions
     — the chip is a fixed <rect width="112"> with centred text, so enlarging the type
     alone would have overflowed the pill it sits in.
     Both origins are picked to protect a neighbour rather than for symmetry:
       anno  100% 100% — pins the connector's arrow tip, so it keeps pointing at the
                         October ring instead of drifting off it as the group grows.
       lift   50% 100% — pins the pill's bottom edge at y=52 and grows up into the 26
                         units of empty headroom above it. Growing from the centre would
                         push its underside onto the peak dot at (649, 68).
     .surge is not inside either group, so it needs its own stroke bump; .connector is
     inside one and gets 1.75x on top of the value below. */
  .chart .anno[data-anim="anno"]{transform-box:fill-box;transform-origin:100% 100%;transform:scale(1.75);}
  /* 1.9, not 1.75: the chip text was still the hardest thing here to read at 10.2px and
     now draws at 11.1px. The ceiling is 2.0 and it is the TOP of the viewBox, not the
     right edge (2.09): the pill is bottom-anchored at y=52 and 26 units tall, so 26*2
     puts its top on 0 exactly. 1.9 leaves 2.6 units of headroom. Do not switch this
     origin to 50% 50% to gain room — growing from the centre pushes the pill's underside
     onto the peak dot at (649, 68), which is only 11.5 units below its bottom edge. */
  /* Chip text at 11px with the pill sized around it and drawn tighter than before.
     Measured: the string is 79.9 x 13.2 units at 11px, so the pill is 92.4 x 19 -- 6.25
     units of padding a side horizontally and 2.9 vertically. Horizontal went 10.4 -> 8.5
     -> 4, which was too tight, and is now back to the midpoint of that last step.
     One viewBox unit draws as 1.024 screen px here (scale 2.2 on a 390px screen), so
     these two are near enough interchangeable and 6.25 units reads as ~6.4px.
     ⚠️ That does NOT hold for the translate below, which is unscaled: one unit there is
     0.466px. Do not carry a number from one to the other.
     ⚠️ The translate is NOT scaled. It applies after scale() in the parent's coordinate
     system, which the computed matrix confirms: matrix(2.2,0,0,2.2,-63,-5.1) carries the
     translate unscaled in e/f. So one viewBox unit of translate is 0.466 screen px at
     390px, and moving the chip 5 screen px up took 10.7 units, ty 11 -> -5.1. Reading it
     as 5/(2.2*0.466) would have moved it less than half as far.
     Pinned at 12px here rather than left to inherit the base rule, even though the base
     is also 12px today. Mobile and desktop have already moved each other once by
     accident: dropping the desktop base 12.5 -> 12 quietly shrank mobile too. This
     decouples them.
     Earlier sizes for reference, all as the CSS value: 12.5 originally, then 15, now 12.
     Rect at 15px was 134 x 32 (108.6 of text + 12.4 padding a side, height in proportion)
     and is re-centred on the text: x = 703 - 67, and y chosen so the pill centres on the
     text's VISUAL middle rather than on its baseline. The <text> keeps y=43 from the
     markup and CSS cannot move it reliably, so the pill moves instead; y=20 left the
     text sitting 1.3px low.
     rx follows the height so it stays a pill rather than a rounded box. */
  .chart .anno[data-anim="lift"] rect{x:656.8px;y:29.4px;width:92.4px;height:19px;rx:9.5px;}
  .chart .lift-t{font-size:11px;}
  /* Moved over the peak and scaled to 2.2, which is what finally makes the text big.
     The limit was never horizontal. The chip sat at x=703 in the top-right corner where
     the only clear space is between the viewBox top and the peak dot at y=63.5, about 58
     units, and pill height x scale had to fit inside that. Two things bought the room:

       1. The pill lost vertical padding, 32 tall -> 26, so more of those 58 units is
          text rather than air. Horizontal padding came down with it, 12.7 -> 10.2 a side.
       2. translate(-63px, 9.25px) puts the chip's centre on x=640, over the peak instead
          of past it, and drops its bottom to y=60.

     At scale 2.2 it spans x 498-782 and y 2.8-60: 3.5 clear of the peak dot, 8 clear of
     the revenue line's highest point, inside the 820 right edge, and well above the
     "Deadline added" annotation, whose box starts at y=125.
     ⚠️ The rect must stay centred on the TEXT (x = 703 - w/2), because the <text> keeps
     x=703 from the markup and CSS cannot move it. All repositioning goes through the
     group's translate so the two move together.
     ⚠️ The transform is on .lift-inner, NOT on the .anno group. landing.js animates the
     outer group with fill:'forwards', which pins transform:none in the animation origin
     and beats anything a stylesheet says. See the note in landing.html. */
  .chart .lift-inner{transform-box:fill-box;transform-origin:50% 100%;transform:translate(-63px,-5.1px) scale(2.2);}
  .chart .connector{stroke-width:2.4;}
  .chart .surge{stroke-width:4.5;}
  /* The ring drew at 1.16px, thinner than every other mark on the chart after the bumps
     above, and it is the one pointing at the moment the section is about. 4.5 matches
     .surge so the two brand-orange marks read as one weight. */
  .chart .ring{stroke-width:4.5;}
  /* Tighter chips on phones. 24 of them wrap over several rows at this width, so the
     saving is per chip AND per row: the smaller type shortens each one and the reduced
     vertical padding shortens every row the block occupies. 13px is off the --fs-* scale
     deliberately, founder's number. */
  .esp-chip{font-size:13px;padding:.3rem .5rem;}
  /* .625rem -> .3125rem is a WRAP fix, not spacing taste. At .625 the 24 chips fall
     4/4/3/4/4/4/1 and strand "Teachable" on a row of its own, and not only at 390px: it
     orphans identically at 393, 414, 430 and 460, so every large phone got it. .3125rem
     lands 4/4/4/4/4/4 from 375 all the way through 680 and orphans at no width measured
     (320 and 360 end on 2).
     ⚠️ Narrowing the row is what evens a wrapped list, and here the gap does it by
     buying back 9px a row. Widening the gap does NOT help, which is the same thing the
     880px max-width had to solve on desktop.
     ⚠️ THIS NUMBER IS TIED TO font-weight ON .esp-chip. It was .5rem while the chips were
     600; dropping them to 500 narrowed every chip and re-orphaned 375px, which .5rem had
     been fine at. Re-measure across widths after ANY change to the chips' weight, size,
     padding, or the 24 strings themselves: the distribution is a property of the rendered
     text, not of these numbers. */
  .esp-row{gap:.3125rem;}
  .cta-row .btn{width:100%;}
  .hdr-actions .signin{display:none;}
  .timer-stage{overflow:visible;}
}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms !important;transition-duration:.01ms !important;}
  html{scroll-behavior:auto;}
  .reveal,.capsule-enter,.lift-badge{opacity:1 !important;transform:none !important;}
}
