/* ==========================================================================
   Subtitl — design system + sections
   All brand colours live in the :root block below (single source).
   ========================================================================== */

/* ---- Fonts (self-hosted, variable, swap) -------------------------------- */
@font-face{
  font-family:"Gabarito";
  src:url("/assets/fonts/gabarito-var.woff2") format("woff2");
  font-weight:400 900; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"Inter";
  src:url("/assets/fonts/inter-var.woff2") format("woff2");
  font-weight:400 700; font-style:normal; font-display:swap;
}

/* ==========================================================================
   TOKENS
   ========================================================================== */
:root{
  --bg:#FFFFFF; --bg-soft:#FAFAFA;
  --ink:#111113; --ink-soft:#5B5B63; --ink-faint:#9A9AA2;
  --line:rgba(17,17,19,.10);            /* hairline frame */
  --line-strong:rgba(17,17,19,.16);
  /* ---- BRAND BLUE — the single source of truth for every blue on the site.
     --brand-blue is the ONE solid blue (buttons, links, single-colour accents,
     borders, icons, badges). Gradients come in exactly TWO flavours and they are
     documented below the blues — --brand-gradient for UI/decoration and
     --brand-gradient-text for text. Nothing should hardcode a blue again: change
     it here and the whole site follows.

     ACCESSIBILITY: #1286E6 on white measures 3.76:1. That clears AA for large
     text and for UI components/graphical objects (both need 3:1), but is SHORT
     of the 4.5:1 AA requires for normal-size text. --brand-blue-ink is the same
     hue two steps darker (4.84:1) and is what body-size link text and solid
     button fills carrying white labels use, so nothing legible-critical sits at
     3.76:1. The two are close enough to read as the same blue.

     EXCEPTION, deliberate: the hero background (.bloom-wash + the AsciiBloom
     canvas palette in app.js colorFor()) is NOT tokenised. It is art-directed
     and the owner asked for it to stay exactly as-is. */
  --brand-blue:#1286E6;
  --brand-blue-ink:#0F74C7;

  /* ---- THE TWO-PART GRADIENT SYSTEM (2026-08 rebrand) ----------------------
     These are two DIFFERENT gradients and they are not interchangeable.

     --brand-gradient      general UI: buttons, backgrounds, decorative fills,
                           progress bars, rules. NEVER clipped to text.
     --brand-gradient-text text only, via background-clip:text.

     The text one is a RADIAL, not a linear, and that is load-bearing — it was
     derived from the reference artwork, not invented. Its circle is centred at
     (91%, 31%) of the text box with a radius of 46% of the box width, i.e.
     SMALLER than the text it sits on: the light blooms off the top-right and
     everything left of it falls outside the gradient entirely and sits flat at
     #01458F. That flat dark left-hand side is the effect, not a bug. Replacing
     it with a linear ramp loses it — a 2-stop linear is affine in position and
     provably cannot reproduce the reference's falloff.

     The %-radii form is deliberate over an equivalent `circle <len>` form: the
     circle is exact only at the reference's aspect ratio, and collapses into an
     arbitrary mid-phrase hotspot on longer headings. Percentages keep the
     composition across text lengths and wrap sanely to two lines.

     CONTRAST, read before extending this: #5D9EEE is 2.77:1 on white, under the
     3:1 floor for large text. That is a deliberate brand call for DISPLAY type
     and nothing else — do not put --brand-gradient-text on body-sized copy; use
     --brand-blue-ink there. Likewise white labels on the bright end of the UI
     gradient (#0088FF) are 3.52:1, so solid gradient surfaces carrying white
     text use --brand-gradient-ink below. */
  --brand-gradient:linear-gradient(135deg,#003D79 0%,#0088FF 100%);
  --brand-gradient-text:radial-gradient(46% 187% at 91% 31%,#5D9EEE 0%,#01458F 100%);

  /* --brand-gradient with the bright end held back to #0075DE, the lightest stop
     on that ramp where WHITE still clears 4.5:1 (4.57:1; the true bright end
     #0088FF gives only 3.52:1). Use it on any gradient surface that carries white
     text — large brand panels, filled buttons, the tiny initial-avatars. Same
     relationship --brand-blue-ink has to --brand-blue, for the same reason.
     Purely decorative gradient fills should use --brand-gradient. */
  --brand-gradient-ink:linear-gradient(135deg,#003D79 0%,#0075DE 100%);

  /* Dark-surface twin of --brand-gradient-text. NOT the same colours: on a
     near-black panel the light-mode far stop (#01458F) measures 2.1:1 and the
     bulk of the word disappears. Same radial geometry, whole range shifted up so
     the BULK is the legible end (#5D9EEE, 7.2:1) and the bloom goes paler still
     (#B6D5F7, 13:1) — the light still arrives from the top-right, which is the
     point. Use anywhere gradient text sits on --panel-dark / --nav-dark. */
  --brand-gradient-text-dark:radial-gradient(46% 187% at 91% 31%,#B6D5F7 0%,#5D9EEE 100%);

  /* Legacy aliases, retargeted onto the brand blue. 67 call sites already say
     var(--blue), so pointing them here reconciles them all at once instead of
     rewriting each one. -bright/-deep are lighter/darker steps of the SAME hue
     (207deg), not unrelated blues as before. */
  --blue:var(--brand-blue); --blue-bright:#4BA6F1; --blue-deep:#0D63AA;
  /* Deliberately the UI gradient, not the text one. After the 2026-08 split its
     two remaining call sites are both UI fills (the auth plan badge and the
     scrolly progress bar) — every text-clip use moved to --brand-gradient-text.
     Don't point this at the text gradient. */
  --blue-grad:var(--brand-gradient);
  --blue-wash:rgba(18,134,230,.06);
  --nav-dark:#111113; --panel-dark:#0E0E11;              /* pill nav + mega CTA panel */
  --radius-card:20px; --radius-pill:999px;
  --shadow-soft:0 10px 30px rgba(17,17,40,.07);
  --shadow-lift:0 24px 60px rgba(17,17,40,.12);

  /* layout — framed artboard; spacing scale kept tight */
  --maxw:min(1280px,100vw);
  --rail:0px;
  --gutter:clamp(18px,3.5vw,40px);
  --section-y:clamp(56px,6.5vw,88px);
  --stack-gap:clamp(24px,3vw,40px);
  /* Smaller/tighter range than --stack-gap — for microcopy sitting directly
     under a CTA (button->helper-text), not a section-level stack gap. Was
     copy-pasted as a bare clamp() in 3 places (a code-review pass, 2026-08,
     found the duplication self-acknowledged in a comment but never
     extracted); now one source. */
  --cta-note-gap:clamp(18px,2.2vw,28px);
  --sans:"Gabarito",system-ui,-apple-system,"Segoe UI",sans-serif;
  --body:"Inter",system-ui,-apple-system,"Segoe UI",sans-serif;
  /* mixed Devanagari+Latin runs: Gabarito/Inter carry the Latin, and the named
     per-OS Devanagari faces catch नमस्ते BEFORE the generic sans-serif would
     hand it to an arbitrary OS default (metrics/weight then vary per platform).
     Named fallbacks must precede the generic or they are never consulted. */
  --sans-indic:"Gabarito","Nirmala UI","Kohinoor Devanagari","Noto Sans Devanagari",system-ui,-apple-system,"Segoe UI",sans-serif;
  --body-indic:"Inter","Nirmala UI","Kohinoor Devanagari","Noto Sans Devanagari",system-ui,-apple-system,"Segoe UI",sans-serif;
  --mono:ui-monospace,"SF Mono","JetBrains Mono",Menlo,Consolas,monospace;

  --ease:cubic-bezier(.16,1,.3,1);
  --ease-expo:cubic-bezier(.19,1,.22,1);
  /* REAL spring physics baked into linear() (there's no CSS spring() yet).
     mass 1, stiffness 300, damping 22 -> damping ratio .635, ~7.5% overshoot,
     one clean bounce that settles (raise damping to kill wobble, raise stiffness
     to snap). Natural settle ~0.59s, so the transitions that use it run ~0.5s.
     Browsers without linear() (pre-2023) fall back to `ease` -- still animates. */
  --ease-spring:linear(0, 0.0701, 0.2317, 0.4268, 0.6167, 0.779, 0.904, 0.9907, 1.0434, 1.0691, 1.0755, 1.0696, 1.0571, 1.0423, 1.028, 1.0159, 1.0067, 1.0004, 0.9965, 0.9947, 0.9943, 0.9948, 0.9958, 0.9969, 0.998, 1);
  --ease-soft:cubic-bezier(.22,1,.36,1);        /* softer ease-out — gentler tail than --ease */
}
@media (min-width:1440px){
  :root{ --maxw:1360px; --gutter:48px; }
}
/* Widescreen (27"+) tier — was min-width:1680px at maxw:1440/gutter:52; raised
   here so it kicks in earlier and goes further, otherwise --maxw would have
   shrunk back down between 1600–1679px relative to this tier. --maxw only
   feeds .shell's max-width (grepped); --gutter feeds .section/.scrolly-pin/
   .mega-track/.footer padding-inline — both single source, safe to bump. */
@media (min-width:1600px){
  :root{ --maxw:1520px; --gutter:56px; }
}

/* ==========================================================================
   RESET / BASE
   ========================================================================== */
*,*::before,*::after{box-sizing:border-box}
/* The `hidden` attribute must actually hide, everywhere, full stop.
   The UA rule is `[hidden]{display:none}` — author-origin and the SAME
   specificity as any plain `.thing{display:flex}`, so the author rule wins the
   cascade and `el.hidden = true` silently does NOTHING. That has now caused
   four real bugs on this site: the test-mode "no card will be charged" banner
   staying visible on a LIVE payment page, the mobile plan toggle, the
   post-checkout confirmation strip showing on every visit, and both platform
   glyphs rendering at once on the download button.
   !important is deliberate: it is the only thing that reliably beats an
   arbitrary later `display` rule, and nothing on this site ever wants a
   [hidden] element to be visible. If you need something conditionally shown,
   toggle a class — do not fight this rule.
   (The handful of `.x[hidden]{display:none}` patches further down are the
   older per-element fixes for the same trap; harmless no-ops now.) */
[hidden]{display:none !important}
html{-webkit-text-size-adjust:100%;scroll-behavior:auto;overflow-x:clip}
body{
  margin:0; background:var(--bg); color:var(--ink);
  font-family:var(--body); font-size:16px; line-height:1.6;
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
  overflow-x:clip;   /* clip (not hidden) so it doesn't break position:sticky */
}
img,video,canvas,svg{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;cursor:pointer;border:0;background:none}
h1,h2,h3,h4{margin:0;font-family:var(--sans);font-weight:700;letter-spacing:-.02em;line-height:1.03}
p{margin:0}
ul{margin:0;padding:0;list-style:none}
:focus-visible{outline:2px solid var(--blue);outline-offset:3px;border-radius:4px}

/* Lenis */
html.lenis,html.lenis body{height:auto}
.lenis.lenis-smooth{scroll-behavior:auto !important}
.lenis.lenis-stopped{overflow:hidden}

/* ==========================================================================
   THE FRAME  (site-wide signature)
   Centred artboard + L/R hairlines. No full-page hatch wallpaper.
   ========================================================================== */
.shell{
  max-width:var(--maxw);
  margin-inline:auto;
  position:relative;
  background:var(--bg);
  z-index:1;
}
/* Frame + rail hairlines removed (per design). --rail is retained only to keep
   the content's horizontal inset unchanged now that the rules no longer draw. */
@media (min-width:1080px){
  :root{ --rail:56px; }
  .section:not(.section--flush){
    padding-inline:calc(var(--rail) + 20px);
  }
}
@media (min-width:1440px){
  :root{ --rail:64px; }
}

.section{
  position:relative;
  padding-block:var(--section-y);
  padding-inline:var(--gutter);
  z-index:1;
}
.section--tight{padding-block:clamp(40px,5vw,64px)}
.section--flush{padding-inline:0}
.section[id],[id].mega{scroll-margin-top:88px}
.inner{max-width:960px;margin-inline:auto;position:relative}

.hatch{
  background-image:repeating-linear-gradient(45deg,var(--line) 0 1px,transparent 1px 9px);
  pointer-events:none;
}
/* data-hatch reserved — no auto side stripes (avoids accidental overlap noise) */
.section[data-hatch]::before,
.section[data-hatch]::after{
  content:none;
}

/* ==========================================================================
   SHARED PARTS: label pill, headings, buttons, cards
   ========================================================================== */
.label-pill{
  display:inline-flex;align-items:center;gap:7px;
  font-family:var(--body);font-size:12px;font-weight:500;letter-spacing:.01em;
  color:var(--ink-soft);
  padding:6px 13px;border:1px solid var(--line-strong);border-radius:var(--radius-pill);
  background:var(--bg);
}
.label-pill .tick{width:6px;height:6px;border-radius:50%;background:var(--blue)}

.section-head{text-align:center;max-width:680px;margin-inline:auto}
.section-head .label-pill{margin-bottom:12px}
.section-head h2{
  font-size:clamp(1.85rem,3.8vw,2.85rem);font-weight:700;letter-spacing:-.03em;line-height:1.08;
}
.section-head .sub{
  margin-top:12px;color:var(--ink-soft);font-size:clamp(.98rem,1.3vw,1.08rem);
  max-width:48ch;margin-inline:auto;line-height:1.5;
}
/* Widescreen: the site is ~90% px-based (only a handful of rem sizes), so
   bumping html{font-size} would grow this heading's type without growing its
   mostly-px surrounding spacing — instead just raise the shared section
   heading's clamp cap directly (+~15%: 2.85rem → 3.28rem). */
@media (min-width:1600px){
  .section-head h2{font-size:clamp(1.85rem,3.8vw,3.28rem)}
}
/* Brand gradient text — only on meaningful words (product differentiators,
   key claims, brand names). Do NOT slap this on the last word of every heading. */
.grad{background:var(--brand-gradient-text);-webkit-background-clip:text;background-clip:text;color:transparent}
.muted{color:var(--ink-faint)}

/* buttons */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:.5em;
  font-family:var(--sans);font-weight:600;font-size:1rem;line-height:1;
  padding:15px 26px;border-radius:var(--radius-pill);
  transition:transform .18s var(--ease),box-shadow .25s var(--ease),background .2s var(--ease);
  white-space:nowrap;
}
.btn:active{transform:translateY(1px) scale(.99)}
/* Rich tactile FINISH rolled out to the normal buttons (kept their colour):
   vertical gradient off the base colour, inset top highlight, hairline border,
   text-shadow, brightness on hover / dim on press — same language as .btn-rich.
   The bespoke special buttons (hero .btn-cta, .btn-go, .btn-dl, .btn-drop-cta)
   are intentionally NOT touched. */
/* THE primary button. Reads as one solid brand blue: the two stops are the
   AA-safe ink shade and one step down from it, close enough to look flat while
   keeping the tactile fall-off the rest of the button family uses.
   Deliberately NOT --brand-blue (#1286E6): white label text on it is 3.76:1,
   under the 4.5:1 AA wants for 16px/600 labels. Every point of this fill is
   >=4.84:1. --brand-blue stays the token for accents, icons and borders, where
   the 3:1 UI-component threshold applies and it passes comfortably. */
.btn-primary{
  color:#fff;
  background:linear-gradient(to bottom, var(--brand-blue-ink), var(--blue-deep));
  border:1px solid rgba(6,44,80,.45);
  box-shadow:inset 0 1px 0 0 rgba(255,255,255,.28), 0 8px 22px rgba(18,134,230,.28);
  text-shadow:0 1px 0 rgba(6,44,80,.5);
  transition:filter .2s var(--ease),transform .18s var(--ease),box-shadow .25s var(--ease);
}
@media (hover: hover) and (pointer: fine){
  .btn-primary:hover{filter:brightness(1.08);transform:translateY(-1px);box-shadow:inset 0 1px 0 0 rgba(255,255,255,.34),0 12px 30px rgba(18,134,230,.36)}
}
.btn-primary:active{filter:brightness(.95);transform:translateY(1px) scale(.99)}
.btn-primary svg{filter:none}
.btn-ghost{background:transparent;color:var(--ink);border:1px solid var(--line-strong)}
@media (hover: hover) and (pointer: fine){
  .btn-ghost:hover{border-color:var(--ink);transform:translateY(-1px)}
}
/* RichButton — the "normal" (non-hero-CTA) button treatment: a near-black
   vertical gradient with an inset top highlight, hairline dark border, soft
   drop shadow and a 1px text-shadow, brightening on hover / dimming on press.
   Ported from the shadcn RichButton (default color) into the vanilla stack;
   keeps the site's pill radius so it sits with the rest of the buttons. */
.btn-rich{
  color:#fff;
  background:linear-gradient(to bottom, rgba(24,24,27,.9), rgb(24,24,27));
  border:1px solid rgba(9,9,11,.4);
  box-shadow:inset 0 1px 0 0 rgba(255,255,255,.22), 0 4px 10px rgba(9,9,11,.22);
  text-shadow:0 1px 0 rgba(0,0,0,.55);
  transition:filter .2s var(--ease),transform .18s var(--ease),box-shadow .25s var(--ease);
}
@media (hover: hover) and (pointer: fine){
  .btn-rich:hover{filter:brightness(1.12);transform:translateY(-1px);box-shadow:inset 0 1px 0 0 rgba(255,255,255,.28),0 8px 18px rgba(9,9,11,.28)}
}
.btn-rich:active{filter:brightness(.95);transform:translateY(1px) scale(.99)}
.btn-rich svg{filter:none}

.section-head .section-sub{margin:12px auto 0;max-width:52ch;color:var(--ink-soft);font-size:1.02rem;line-height:1.5;font-weight:500}
/* ── How it works — video placeholder (replaces the long scroll demo) ─────── */
.how-video{position:relative;max-width:960px;margin:8px auto 0;padding:0 clamp(16px,4vw,32px)}
.how-video-frame{
  position:relative;aspect-ratio:16/9;width:100%;border-radius:24px;overflow:hidden;
  display:grid;place-items:center;
  background:
    radial-gradient(120% 130% at 18% 8%, rgba(75,166,241,.38), transparent 55%),
    radial-gradient(110% 110% at 92% 96%, rgba(18,134,230,.5), transparent 60%),
    linear-gradient(155deg,#0b1324 0%,#0a1c3a 55%,#0b1220 100%);
  border:1px solid rgba(255,255,255,.1);
  box-shadow:0 34px 80px rgba(10,10,25,.4), inset 0 1px 0 rgba(255,255,255,.14);
}
.how-video-el{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
/* soft moving sheen so the empty frame still feels alive */
.how-glow{
  position:absolute;inset:-40%;pointer-events:none;
  background:radial-gradient(closest-side, rgba(75,166,241,.22), transparent 70%);
  filter:blur(10px);animation:how-drift 9s ease-in-out infinite;
}
@keyframes how-drift{
  0%,100%{transform:translate(-6%,-4%) scale(1)}
  50%{transform:translate(8%,6%) scale(1.12)}
}
.how-play{
  position:relative;z-index:2;width:clamp(64px,7vw,84px);height:clamp(64px,7vw,84px);
  border-radius:50%;border:0;cursor:pointer;display:grid;place-items:center;
  background:rgba(255,255,255,.94);color:#0a1c3a;
  box-shadow:0 14px 34px rgba(10,10,25,.4);
  transition:transform .2s var(--ease),background .2s var(--ease);
}
.how-play svg{width:34%;height:34%;margin-left:6%}
@media (hover: hover) and (pointer: fine){
  .how-play:hover{transform:scale(1.06);background:#fff}
}
.how-badge{
  position:absolute;bottom:16px;left:16px;z-index:2;
  font-family:var(--sans);font-weight:600;font-size:.8rem;letter-spacing:.02em;
  color:rgba(255,255,255,.92);background:rgba(10,10,25,.42);
  padding:6px 12px;border-radius:999px;border:1px solid rgba(255,255,255,.16);
}
@supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .how-badge{backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px)}
}
@media (prefers-reduced-motion:reduce){.how-glow{animation:none}}
.btn-light{
  color:var(--ink);
  background:linear-gradient(to bottom,#ffffff,#f1f1f4);
  border:1px solid var(--line-strong);
  box-shadow:inset 0 1px 0 0 rgba(255,255,255,.9), 0 4px 12px rgba(9,9,11,.10);
  transition:filter .2s var(--ease),transform .18s var(--ease),box-shadow .25s var(--ease);
}
@media (hover: hover) and (pointer: fine){
  .btn-light:hover{filter:brightness(.985);transform:translateY(-1px);box-shadow:inset 0 1px 0 0 rgba(255,255,255,.9),0 8px 18px rgba(9,9,11,.14)}
}
.btn-light:active{transform:translateY(1px) scale(.99)}
/* .btn-drop-cta is a bespoke special button that reuses .btn-light — keep its
   own borderless, gradient-free treatment (it re-declares below). */
.btn-drop-cta{background:#fff;border:0;box-shadow:none}
.btn-lg{padding:15px 26px;font-size:1rem}

/* download CTAs (/download) — premium two-line lockup: icon chip + label,
   cursor-glow on the primary mac button, matches .btn-go in auth.css */
.btn-dl{
  --mx:50%; --my:50%;
  position:relative;overflow:hidden;justify-content:flex-start;
  gap:12px;padding:14px 24px;border-radius:14px;text-align:left;
}
.btn-dl::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(130px circle at var(--mx) var(--my),rgba(75,166,241,.35),transparent 65%);
  opacity:0;transition:opacity .25s var(--ease);
}
.btn-dl:hover::before{opacity:1}
.dl-chip{
  position:relative;flex:none;width:28px;height:28px;border-radius:8px;
  display:flex;align-items:center;justify-content:center;
}
.dl-chip svg{width:16px;height:16px;display:block}
/* Hover swap: the platform mark slides out left and an arrow slides in right,
   so the button reads "this is your machine" at rest and "go" under the
   cursor. Both slots animate their WIDTH (not just opacity), so the label
   re-centres as they trade places instead of leaving a hole — the same
   layout hand-off the spring-based React version does, on --ease-spring so
   it matches every other spring on the site rather than importing a new feel.
   Width+transform ride the spring; opacity uses a shorter plain ease so the
   outgoing mark is gone before the spring's overshoot, which would otherwise
   read as a wobble. Gated on real hover: with no pointer the arrow stays
   collapsed at width 0 and the platform mark simply never leaves. */
.dl-chip,.dl-go{
  transition:width .5s var(--ease-spring),margin .5s var(--ease-spring),
             transform .5s var(--ease-spring),opacity .2s var(--ease);
}
.dl-go{
  flex:none;width:0;height:28px;margin-left:-12px;   /* -12px cancels the flex gap while collapsed */
  display:flex;align-items:center;justify-content:center;
  opacity:0;transform:translateX(10px);overflow:hidden;
}
.dl-go svg{width:18px;height:18px;display:block;flex:none}
@media (hover: hover) and (pointer: fine){
  .btn-dl:hover .dl-chip{width:0;margin-right:-12px;opacity:0;transform:translateX(-10px)}
  .btn-dl:hover .dl-go{width:18px;margin-left:0;opacity:1;transform:translateX(0)}
}
@media (prefers-reduced-motion: reduce){
  .dl-chip,.dl-go{transition:opacity .15s linear}
}
.dl-label{position:relative;display:flex;flex-direction:column;line-height:1.25}
.dl-label small{font-size:11px;font-weight:500;opacity:.7}
.dl-label{font-size:15.5px;font-weight:700}
/* look (solid = primary / outline = secondary) is decoupled from platform so
   app.js can promote the detected OS to the solid treatment + first position */
.btn-dl--solid{
  /* Same fill as .btn-primary / .btn-go. This is the primary action on
     /download; it was the last button still wearing the old bespoke navy. */
  background:linear-gradient(to bottom, var(--brand-blue-ink), var(--blue-deep));
  color:#fff;box-shadow:inset 0 1px 0 rgba(255,255,255,.14),0 8px 22px rgba(13,99,170,.22);
}
@media (hover: hover) and (pointer: fine){
  .btn-dl--solid:hover{transform:translateY(-1px);box-shadow:inset 0 1px 0 rgba(255,255,255,.14),0 10px 26px rgba(13,99,170,.35)}
}
.btn-dl--solid .dl-chip{background:rgba(255,255,255,.12);color:#fff}
.btn-dl--outline{background:#fff;color:var(--ink);border:1px solid var(--line-strong)}
@media (hover: hover) and (pointer: fine){
  .btn-dl--outline:hover{border-color:var(--ink);transform:translateY(-1px);box-shadow:0 10px 24px rgba(17,17,19,.10)}
}
.btn-dl--outline .dl-chip{background:rgba(17,17,19,.05);color:var(--ink)}
/* not-yet-available: installer URL is still a placeholder — never a dead
   button that looks clickable. Auto-clears the moment config gets real URLs. */
.btn-dl.is-pending,.btn.is-pending{opacity:.5;pointer-events:none;box-shadow:none;filter:grayscale(.3)}
.dl-note{margin-top:16px;font-size:.88rem;color:var(--ink-soft);display:inline-flex;align-items:center;gap:8px}
.dl-note::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--blue-bright);flex:none}
/* `display:inline-flex` above is author-origin and the SAME specificity as the
   UA's `[hidden]{display:none}`, so author wins and the JS `hidden` attribute
   silently fails — leaving the ::before dot floating on its own with no text.
   Same trap as .price-testnote / .price-seg on the pricing page. */
.dl-note[hidden]{display:none}
.dl-alt{margin-top:14px;font-size:.9rem;color:var(--ink-soft)}
.dl-alt a{color:var(--blue);text-decoration:underline;text-underline-offset:2px;font-weight:600}
/* post-checkout confirmation strip (?purchase=success).
   margin-top clears the fixed nav — this sits at the very top of <main>, so
   without it the strip renders underneath the floating nav pill. */
.dl-paid[hidden]{display:none}   /* see the .dl-note note above — same trap */
/* The one moment in the product where money has just left someone's account
   and it worked. It arrives via `hidden` being removed, so display goes
   none -> flex and a transition cannot run — hence an animation, matching the
   licIn/clIn entrance idiom already used elsewhere in this codebase. */
@keyframes paidIn{from{opacity:0;transform:translateY(-8px) scale(.985)}to{opacity:1;transform:none}}
.dl-paid{
  animation:paidIn .42s var(--ease-soft);   /* no fill — see .plan-state in auth.css */
  max-width:760px;margin:124px auto 0;display:flex;align-items:flex-start;gap:12px;
  padding:14px 18px;border-radius:14px;
  background:rgba(13,138,85,.06);border:1px solid rgba(13,138,85,.22);
  color:#0d6f47;font-size:.95rem;line-height:1.5;text-align:left;
}
.dl-paid strong{color:#0a5636}
.dl-paid a{color:#0a5636;text-decoration:underline;text-underline-offset:2px;font-weight:600}
.dl-paid-ic{
  flex:none;width:22px;height:22px;border-radius:50%;margin-top:1px;
  display:flex;align-items:center;justify-content:center;
  background:rgba(13,138,85,.16);
}
.dl-paid-ic svg{width:13px;height:13px;display:block}

/* CTA base — shared by the HERO's "Get started now" (.btn-signin.btn-cta):
   hover grow + the hover-revealed arrow icon. (The old nav Sign-in/Get-started
   label-swap machinery is gone — the nav's own CTA is now the static .nav-get.) */
.btn-signin{gap:0;transform-origin:center;
  transition:transform .3s var(--ease),box-shadow .3s var(--ease),background .2s var(--ease)}
.btn-signin .si-label{white-space:nowrap}
.btn-signin .si-ic{display:inline-flex;align-items:center;overflow:hidden;
  width:0;opacity:0;margin-left:0;transform:translateX(-4px);
  transition:width .32s var(--ease),opacity .26s var(--ease),margin-left .32s var(--ease),transform .32s var(--ease)}
.btn-signin .si-ic svg{width:16px;height:16px;flex:none}
@media (hover: hover) and (pointer: fine){
  .btn-signin:hover,.btn-signin.is-hover{transform:scale(1.08);box-shadow:0 9px 24px rgba(10,10,25,.22)}
  .btn-signin:hover .si-ic,.btn-signin.is-hover .si-ic{width:16px;opacity:1;margin-left:7px;transform:translateX(0)}
}
.btn-signin:active,.btn-signin.is-hover:active{transform:scale(1.04) translateY(1px)}
@media (prefers-reduced-motion:reduce){
  .btn-signin:hover,.btn-signin.is-hover{transform:none}
  .btn-signin .si-label{transition:opacity .2s linear}
}

.card{
  background:var(--bg);border:1px solid var(--line);
  border-radius:var(--radius-card);box-shadow:var(--shadow-soft);
}

/* wordmark (single shared partial renders this markup) */
/* Artwork, not type — see wordmark() in partials.js. 69px matches what the old
   text measured in the footer (69.3px at 1.4rem/800), so the swap is optically
   like-for-like. */
.wordmark{line-height:0}
.wordmark img{display:block;width:69px;height:auto}

/* ==========================================================================
   NAV — collapsing floating pill  (Effect 2)
   ========================================================================== */
.nav{position:fixed;top:16px;left:0;right:0;z-index:80;display:flex;justify-content:center;
  padding-inline:16px;pointer-events:none}
.nav-pill{
  pointer-events:auto;
  display:flex;align-items:center;gap:6px;
  /* near-black tinted glass — opaque fallback first, upgraded below where
     backdrop-filter is supported so unsupported browsers still read as a
     solid dark pill rather than a flat, un-blurred smear */
  background:rgba(12,12,14,.95);color:#fff;
  border-radius:var(--radius-pill);
  padding:9px 9px 9px 22px;
  box-shadow:
    0 12px 34px rgba(10,10,25,.32),
    inset 0 1px 0 rgba(255,255,255,.16),   /* highlighted top edge — the "glass rim" */
    inset 0 0 0 1px rgba(255,255,255,.06); /* faint all-around edge so it reads as a solid pane */
  max-width:100%;width:max-content;
}
@supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .nav-pill{
    /* near-black frosted glass (per the reference): a mostly-opaque charcoal tint
       so it reads solid/dark, lifted by a strong blur + saturation for the frosted
       halo. This pill is position:fixed over the animated ASCII field, so the blur
       re-renders each frame — 36px is the strong-but-smooth value chosen; the tint
       stays near-black (90%) so it reads solid, not see-through (the previous 58%
       was "too glassy"). */
    background:rgba(13,13,16,.90);
    backdrop-filter:blur(36px) saturate(165%);
    -webkit-backdrop-filter:blur(36px) saturate(165%);
  }
}
/* Every width below is set to a MEASURED px value by JS (initNav) and animated
   on the real spring (--ease-spring = stiffness 300 / damping 22 baked into
   linear()). Measured widths — not max-width caps — so the spring's overshoot
   reads for its full travel with no dead zone. */

/* ---- brand: the full wordmark, ALWAYS — never swaps to a compact mark, in
   either state, on either breakpoint (per design: the logo must not change) --- */
.nav-brand{display:flex;align-items:center;height:40px;text-decoration:none}
.nb-word{display:flex;align-items:center;flex:none}
/* max-width:none beats the global `img{max-width:100%}` reset */
.nb-word img{height:40px;width:auto;flex:none;display:block;max-width:none}

/* ---- top-of-page state: inline links ---- */
.nav-links{overflow:hidden;margin-left:12px;margin-right:6px;
  transition:width .5s var(--ease-spring),opacity .26s var(--ease),margin .5s var(--ease-spring)}
/* overflow:hidden on .nav-links above is load-bearing — it is what lets the nav
   collapse by animating width. It also clipped the focus ring off these links
   entirely: the link box is flush with that container, so a +3px offset ring
   painted outside it and vanished. Keyboard users saw NO indicator at all on
   the primary nav. An inset ring paints inside the clip box, so it survives. */
.nav-links a:focus-visible{outline:2px solid var(--blue);outline-offset:-2px;border-radius:8px}
.nl-inner{display:flex;align-items:center;gap:4px;width:max-content}
.nav-links a{
  font-family:var(--sans);font-weight:500;font-size:.94rem;color:rgba(255,255,255,.72);
  padding:9px 13px;border-radius:var(--radius-pill);white-space:nowrap;
  /* lift uses the individual `translate` prop (not `transform`) so it composes
     with anything on `transform` instead of fighting it */
  transition:color .2s var(--ease),background .2s var(--ease),translate .22s var(--ease),
             scale .16s var(--ease),text-shadow .22s var(--ease);
}
/* hover = a tiny lift + a glow ON THE TEXT (text-shadow, no box-shadow — a
   box-shadow casts a rounded-box silhouette even with no fill). ONE rule for
   all three hover surfaces (bar links, dropdown links, Log out) so the
   treatment can never drift apart again. */
.nav-links a:hover,
.nav-drop a:not(.btn):hover,
.nav-drop .drop-acct button[data-logout]:hover{
  color:#fff;
  translate:0 -1px;
  text-shadow:0 0 6px rgba(75,166,241,.5), 0 0 14px rgba(75,166,241,.3);
}
.nav-links a:active,
.nav-drop a:not(.btn):active,
.nav-drop .drop-acct button[data-logout]:active{translate:0 0;scale:.98}
.nav.is-collapsed .nav-links{opacity:0;margin-left:0;margin-right:0;pointer-events:none}

/* ---- scrolled state: current-section label + whole-page progress ring ----
   The label pair ping-pongs inside a width-sprung viewport: incoming rolls up
   from below on the spring while the outgoing rolls up and out — and the
   viewport width springs to the incoming label's measured width, so the pill
   breathes with each section change (the reference's behavior). */
.nav-ind{display:flex;align-items:center}
.ni-viewport{position:relative;display:block;overflow:hidden;height:1.5em;width:0;
  font-family:var(--sans);font-weight:600;font-size:.95rem;letter-spacing:-.01em;color:#fff;
  transition:width .5s var(--ease-spring)}
.ni-label{position:absolute;left:2px;top:50%;white-space:nowrap;width:max-content;
  transition:transform .5s var(--ease-spring),opacity .26s var(--ease)}
.ni-label.is-on{transform:translateY(-50%);opacity:1}
.ni-label.is-down{transform:translateY(70%);opacity:0}
.ni-label.is-up{transform:translateY(-170%);opacity:0}
.ni-ring{width:0;height:22px;flex:none;opacity:0;margin-left:0;transform:rotate(-90deg);
  transition:width .5s var(--ease-spring),opacity .26s var(--ease),margin .5s var(--ease-spring)}
.nav.is-collapsed .ni-ring{width:22px;opacity:1;margin-left:10px}
.ni-track{fill:none;stroke:rgba(255,255,255,.16);stroke-width:3.6}
/* --prog (0-100, whole-page scroll) is set by JS on the pill */
.ni-fill{fill:none;stroke:var(--blue-bright);stroke-width:3.6;stroke-linecap:round;
  stroke-dasharray:100;stroke-dashoffset:calc(100 - var(--prog,0))}

/* ---- static right side: Get started (guests) + menu opener (everyone).
   NEITHER changes with scrolling — no label swaps, no hiding. Get started
   reuses .btn-signin (hero CTA's hover-grow + arrow reveal), just without
   the hero's bespoke .btn-cta grain/ring/blue treatment. ---- */
.nav-get{padding:11px 20px;font-size:.94rem;margin-inline-start:8px;flex:none}
.nav-get .si-ic svg{width:14px;height:14px}
.nav.is-authed .nav-get{display:none}
.nav-burger{
  display:flex;width:42px;height:42px;border-radius:50%;flex:none;
  align-items:center;justify-content:center;margin-inline-start:4px;
  background:rgba(255,255,255,.08);
  transition:background .2s var(--ease),transform .18s var(--ease);
}
@media (hover: hover) and (pointer: fine){
  .nav-burger:hover{background:rgba(255,255,255,.14);transform:scale(1.03)}
}
.nav-burger:active{transform:scale(.96)}
.nav-burger svg{width:18px;height:18px;stroke:#fff}

/* mobile: links never fit inline — the menu (burger) is the navigation.
   The section label + progress ring are hidden too, so the pill DOESN'T change
   with scrolling at all: it's just the (unchanging) wordmark + Get started +
   menu opener, top of page and scrolled alike. */
@media (max-width:820px){
  .nav-links{display:none}
  .nav-ind{display:none}
}
@media (max-width:560px){
  .nav-get{padding:10px 15px;font-size:.9rem}
}
@media (prefers-reduced-motion:reduce){
  .nav-links,.ni-viewport,.ni-label,.ni-ring{transition:none}
}

/* dropdown (opens from the pill in collapsed state) */
.nav-drop{
  pointer-events:auto;position:absolute;top:calc(100% + 10px);
  left:50%;transform-origin:top center;
  transform:translateX(-50%) translateY(-10px) scale(.92);
  /* Concentric-corner rule: outer radius = inner radius + gap. The Sign-in
     pill's own end-cap curve is height/2 = 29px, sitting inset by the panel's
     14px padding — so the panel radius must be 29 + 14 = 43px for the two
     curves to read as parallel/nested instead of the pill's rounder corner
     visually fighting the panel's tighter one. (28px, tuned before the pill
     CTA existed, was noticeably tighter than the nested math calls for.) */
  width:min(300px,90vw);border-radius:43px;
  background:rgba(11,11,14,.98);   /* opaque fallback, upgraded below — kept
    fairly dark/solid on its own (not just a faint tint) so a browser that
    silently fails to render backdrop-filter (some in-app/WebView browsers
    claim @supports but don't actually blur) still reads cleanly against
    whatever big text sits behind the pill, instead of washing out */
  padding:14px;   /* generous, even inset — content + the Sign-in pill sit off the panel edges (per the layout mock) */
  box-shadow:
    0 24px 60px rgba(10,10,25,.45),
    inset 0 1px 0 rgba(255,255,255,.14),
    inset 0 0 0 1px rgba(255,255,255,.06);
  opacity:0;visibility:hidden;
  transition:opacity .2s var(--ease),transform .4s var(--ease-spring),visibility 0s .4s;
}
@supports ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .nav-drop{
    /* near-black frosted charcoal. Now that the drop is un-nested from the pill
       (see partials.js) its backdrop-filter actually works, so the tint is eased
       to ~.74 — opaque enough to read dark/solid, but sheer enough that the
       strong 42px blur reads as a real frosted wash instead of being hidden
       under a near-solid fill (at the old .92 the working blur would still be
       invisible). saturate lifts the frosted colour behind it. */
    background:rgba(13,13,16,.74);
    backdrop-filter:blur(42px) saturate(165%);
    -webkit-backdrop-filter:blur(42px) saturate(165%);
  }
}
.nav.is-open .nav-drop{
  opacity:1;visibility:visible;transform:translateX(-50%) translateY(0) scale(1);
  transition:opacity .22s var(--ease),transform .46s var(--ease-spring),visibility 0s;
}
.nav-drop a{
  display:block;font-family:var(--sans);font-weight:500;color:rgba(255,255,255,.8);
  padding:13px 16px;border-radius:var(--radius-pill);   /* pill highlight (was 14px rounded-rect) */
  /* `transform` carries the staggered open-reveal (translateY 7px→0), so the
     hover LIFT rides the independent `translate` prop. NOTE: the hover-eased
     properties live in the reveal rule's transition list below — a `transition`
     here would be dead (the later same-specificity reveal rule re-declares the
     shorthand and wins the cascade). */
}
/* shrink each item to its own text width so the hover glow sits compactly UNDER
   the text — the exact navbar-link effect — instead of spanning the full row
   width (which read as a box behind the text). POINTER DEVICES ONLY: on touch
   there is no hover to compact, and the full-width row IS the tap target
   (this menu is the only nav on mobile). */
@media (hover:hover) and (pointer:fine){
  .nav-drop a:not(.btn){width:-moz-fit-content;width:fit-content}
  .nav-drop .drop-acct button[data-logout]{width:-moz-fit-content;width:fit-content}
}
/* reduced motion: keep the glow (no movement), drop the lift */
@media (prefers-reduced-motion:reduce){
  .nav-links a:hover,.nav-drop a:not(.btn):hover,
  .nav-drop .drop-acct button[data-logout]:hover{translate:0}
}
/* staggered reveal: links (and the CTA) fade/rise in one after another as the
   pill pops open, instead of appearing all at once with the panel — only
   delayed on the way IN (.nav.is-open); closing collapses together, no lag */
/* EVERY actionable drop item animates identically (blur+rise+fade, matching
   the OverflowActions item variants). The stagger index --i is set in JS
   (openDrop) over only the VISIBLE items in order, so guest (links + CTA) and
   authed (links + account links + Log out) both stagger cleanly — previously
   the logout button and account links were skipped, so "some animated, some
   didn't". */
.nav-drop a,.nav-drop .btn-drop-cta,.nav-drop .drop-acct button{
  opacity:0;transform:translateY(7px);filter:blur(3px);
  /* ONE merged transition list: the reveal props (opacity/transform/filter)
     PLUS the hover props (color/translate/scale/text-shadow) — split across
     two rules, whichever declared `transition` last silently killed the
     other's easing (the hover snapped with no ease before this merge) */
  transition:opacity .26s var(--ease),transform .3s var(--ease),filter .26s var(--ease),
             color .2s var(--ease),translate .22s var(--ease),scale .16s var(--ease),
             text-shadow .22s var(--ease);
}
.nav.is-open .nav-drop a,
.nav.is-open .nav-drop .btn-drop-cta,
.nav.is-open .nav-drop .drop-acct button{
  opacity:1;transform:translateY(0);filter:blur(0);
  transition-delay:calc(var(--i,0) * .045s);
}
/* the stagger delay above must not lag the hover ease on later items — while
   the pointer is on an item, its transitions respond immediately */
.nav.is-open .nav-drop a:hover,
.nav.is-open .nav-drop .drop-acct button:hover{transition-delay:0s}
@media (prefers-reduced-motion:reduce){
  .nav-drop a,.nav-drop .btn-drop-cta,.nav-drop .drop-acct button{
    transition:opacity .18s linear !important;transform:none !important;filter:none !important;
  }
}

/* drop CTA — always "Sign in" (the drop is only ever reachable once the nav
   is already collapsed, so a swap tied to that same collapse state could
   never show anything else — see partials.js dropCtaHTML). A clear, fuller
   primary action rather than a plain link: full-width white pill, small
   divider gap above it, label + a PERSISTENT icon circle (not hover-reveal
   — this surface is touch-first, and touch has no hover to reveal from). */
.btn-drop-cta{
  width:100%;margin-top:10px;   /* gap above separates it from the last link; equal side + bottom inset comes from the panel's own 14px padding (per the layout mock) */
  display:flex;align-items:center;justify-content:space-between;gap:10px;   /* label LEFT (aligned w/ menu items), arrow chip RIGHT — see the specificity fix below */
  font-weight:700;color:var(--ink);   /* white pill → dark label (was inheriting the nav's white text = invisible) */
}
.btn-drop-cta .dc-label{padding-left:0}   /* sits at the button's own 16px left padding, matching the menu items' text inset */
/* The drop CTA is an <a> inside .nav-drop, so `.nav-drop a{display:block;
   font-weight:500;color:rgba(255,255,255,.8)}` (0,1,1) out-specified the plain
   `.btn-drop-cta{...}` (0,1,0) above on EVERY property they share:
     · color   → label rendered white-on-white (the "no text" glitch)
     · display → button computed BLOCK, so justify-content died and the label +
                 arrow bunched hard-left with ~166px of empty pill to the right
                 (the "looks weird" symptom)
     · weight  → label shipped at 500, not the intended 700
   Re-assert all four (flex + centered + weight + dark label) at `.nav-drop
   a.btn-drop-cta` (0,2,1) so it beats `.nav-drop a`. (The authed-state
   `display:none` at (0,4,0) still wins, so logged-in users keep hiding it.) */
.nav-drop a.btn-drop-cta{display:flex;align-items:center;justify-content:space-between;gap:10px;font-weight:700;color:var(--ink);border-radius:var(--radius-pill)}
.nav-drop a.btn-drop-cta .dc-label{color:var(--ink)}
.btn-drop-cta .dc-ic{
  display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:32px;height:32px;border-radius:50%;
  background:var(--ink);color:#fff;
  transition:transform .25s var(--ease);
}
.btn-drop-cta .dc-ic svg{width:15px;height:15px}
.btn-drop-cta:active .dc-ic{transform:scale(.88)}

/* menu: swap the Sign-in CTA for account links when authed (the bar itself
   never shows an avatar — signed-in users get the menu opener only) */
.nav-drop .drop-acct{display:none}
.nav.is-authed .nav-drop .btn-drop-cta{display:none}
.nav.is-authed .nav-drop .drop-acct{
  display:block;border-top:1px solid rgba(255,255,255,.10);margin-top:8px;padding-top:6px;
}
.nav-drop .drop-acct button[data-logout]{
  /* full-width block on touch (tap target); shrinks to fit-content on pointer
     devices via the shared (hover:hover) media rule up with the drop links.
     Hover/active/transition all come from the shared nav-hover rules — no
     per-button copies to drift out of sync. */
  display:block;width:100%;text-align:left;background:none;border:0;cursor:pointer;
  font-family:var(--sans);font-weight:500;font-size:1rem;color:rgba(255,255,255,.8);
  padding:13px 16px;border-radius:14px;
  /* full transition (incl. the reveal's opacity/transform/filter) so overriding
     it here doesn't kill the staggered open animation; lift rides `translate` */
  transition:color .2s var(--ease),translate .22s var(--ease),scale .16s var(--ease),
             text-shadow .22s var(--ease),
             opacity .26s var(--ease),transform .3s var(--ease),filter .26s var(--ease);
}

.nav-pill{max-width:calc(100vw - 32px)}
/* mobile: nav is always compact (wordmark + CTA + hamburger); links live in the drop */
@media (max-width:720px){
  .nav-pill{padding:9px 9px 9px 16px}
}

/* ==========================================================================
   HERO — dark atmospheric field (Alvio-style depth) + light type
   Line lock:
     Best Looking
     Captions for
     Indian Creators   ← bright gradient on dark
   ASCII bloom kept (white glyphs + screen blend on dark wash).
   ========================================================================== */
.hero{
  position:relative;
  /* clip horizontally (keeps the 100vw full-bleed from adding scroll) but stay
     visible vertically so the caption card's glow can bleed past the hero's
     top/bottom instead of being sliced. Nothing else here overflows the box —
     the bloom canvas + wash are exactly hero-sized. */
  overflow-x:clip;overflow-y:visible;
  /* full-bleed: break out of the framed shell so the gradient/bloom spans the
     entire viewport and isn't bounded by the frame rail lines */
  width:100vw;
  margin-inline:calc(50% - 50vw);
  z-index:3;                 /* paint over the shell frame hairlines in the hero band */
  color:#fff;
  /* Deep navy → cobalt → a white DOME rising from the bottom centre. The lead
     radial is a wide, tall white ellipse anchored just below the bottom edge:
     the centre clears to white first while the blue lingers at the two lower
     corners, forming the dome/horizon arc. Its stop ramp is long and dense
     (8 stops on a smooth alpha curve) so the feather is imperceptibly soft —
     no banding — and it lands on a fully-white bottom edge that meets the
     white platforms section below with no seam. The lower blue accent radials
     were pulled toward the corners so they read UNDER the dome, not through it. */
  background:
    /* DOME with a TALL, SOFT feather. Moderate 71% radius so the luminance
       rings still CURVE across the viewport (not the flat 96%), pulled in a
       touch from 76% so the sides drop LOWER (~89% vs 81%). Big 60% vertical
       radius + an 11-stop ramp make the feather thick + very gradual — the
       edges fade over a wide band instead of snapping to a line. Peak sits
       ~72% down (below the copy/CTA, legibility intact); the base linear still
       whitens the bottom edge so the seam holds. */
    radial-gradient(ellipse 71% 60% at 50% 132%,
      #ffffff 0%,
      rgba(255,255,255,.98) 12%,
      rgba(255,255,255,.92) 22%,
      rgba(255,255,255,.82) 32%,
      rgba(255,255,255,.68) 43%,
      rgba(255,255,255,.52) 54%,
      rgba(255,255,255,.36) 65%,
      rgba(255,255,255,.22) 76%,
      rgba(255,255,255,.11) 86%,
      rgba(255,255,255,.03) 94%,
      transparent 100%),
    radial-gradient(ellipse 90% 55% at 50% -8%, rgba(80,130,230,.40) 0%, transparent 58%),
    radial-gradient(ellipse 62% 46% at 80% 26%, rgba(50,100,210,.34) 0%, transparent 62%),
    /* lower-left accent sits high enough that its tint fully fades BEFORE the
       hero's bottom edge — at the old 66%+40% extent it fed blue right into
       the bottom-left corner, where the section boundary then cut it */
    radial-gradient(ellipse 50% 32% at 16% 58%, rgba(30,70,160,.18) 0%, transparent 70%),
    /* base linear: deeper navy/cobalt, held even LONGER (deep #0a1a45 to 42%,
       deep cobalt #103470 to 62%) so the band behind the headline + sub reads
       clearly darker, then it approaches white asymptotically over the last
       ~15% to a seamless white bottom edge that meets the platforms section */
    linear-gradient(180deg,
      #03060f 0%,
      #050c22 18%,
      #0a1a45 42%,
      #103470 62%,
      #245aa8 78%,
      #6191d6 89%,
      #aecbef 95%,
      #e6f0fb 98.5%,
      #ffffff 100%
    );
  padding-top:clamp(112px,13vh,148px);
  /* taller bottom band gives the dome room to dip "a bit below" where the
     straight fade used to end */
  padding-bottom:clamp(110px,13vw,170px);
  min-height:min(88svh,900px);
  border-bottom:none;
}
.hero-wave{display:none}
.hero-grid{
  display:grid;grid-template-columns:1.05fr .95fr;
  gap:clamp(20px,3.5vw,48px);
  align-items:center;
  max-width:1080px;
  margin-inline:auto;
  width:100%;
  position:relative;z-index:2;
}
.hero-copy{position:relative;z-index:2;min-width:0}

/* Kicker — frosted glass on dark */
.hero .kicker{
  display:inline-flex;align-items:center;gap:8px;
  font-family:var(--body);
  font-size:12.5px;font-weight:500;letter-spacing:-.005em;
  color:rgba(255,255,255,.78);
  line-height:1.2;
  padding:7px 14px 7px 12px;
  border:1px solid rgba(255,255,255,.16);
  border-radius:999px;
  background:rgba(255,255,255,.08);
  backdrop-filter:blur(12px);
  -webkit-backdrop-filter:blur(12px);
  white-space:nowrap;
  max-width:100%;
  box-sizing:border-box;
}
.hero .kicker .tick{
  width:6px;height:6px;border-radius:50%;
  background:var(--blue-bright);flex:0 0 auto;
  box-shadow:0 0 8px rgba(75,166,241,.7);
}

/* Display stack — light type on dark */
.hero h1{
  margin-top:16px;
  font-family:var(--body) !important;
  font-size:clamp(2.4rem,5vw,3.85rem);
  font-weight:500 !important;
  letter-spacing:-.03em;
  line-height:1.05;
  color:#ffffff;
  /* tight dark-blue contact shadow gives the white letters a crisp edge over
     the ASCII glyphs (legibility on mobile), + a soft dark halo underneath */
  text-shadow:0 2px 4px rgba(2,10,34,.55), 0 1px 22px rgba(2,14,44,.5);
}
.hero h1 .reveal-line{
  display:block;
  overflow:hidden;
  padding-bottom:.02em;
}
.hero h1 .h1-line{
  display:block;
  white-space:nowrap;
}
.hero .sub{
  margin-top:14px;max-width:36ch;
  /* whiter + a soft dark halo so it stays legible over the (variable) gradient */
  color:rgba(238,244,255,.95);
  text-shadow:0 1px 16px rgba(2,12,38,.5);
  font-size:clamp(1rem,1.35vw,1.12rem);line-height:1.5;
}
.hero-cta{display:flex;flex-wrap:wrap;gap:12px;margin-top:22px}

/* CTAs tuned for dark field */
.hero .btn-primary{
  background:linear-gradient(180deg,rgba(255,255,255,.22),rgba(255,255,255,.08));
  color:#fff;
  border:1px solid rgba(255,255,255,.28);
  box-shadow:0 10px 32px rgba(0,20,60,.35), inset 0 1px 0 rgba(255,255,255,.25);
  backdrop-filter:blur(10px);
  -webkit-backdrop-filter:blur(10px);
}
.hero .btn-primary:hover{
  background:linear-gradient(180deg,rgba(255,255,255,.3),rgba(255,255,255,.12));
  border-color:rgba(255,255,255,.42);
  box-shadow:0 14px 40px rgba(0,20,60,.4), inset 0 1px 0 rgba(255,255,255,.3);
  transform:translateY(-1px);
}
.hero .btn-ghost{
  color:#fff;
  border:1px solid rgba(255,255,255,.28);
  background:transparent;
}
.hero .btn-ghost:hover{
  border-color:rgba(255,255,255,.55);
  background:rgba(255,255,255,.08);
  transform:translateY(-1px);
}

/* ---- Hero CTA: a pill, light on the left darkening to #234DC4 on the right,
        random grain concentrated at the edges, ONE light stroke that follows
        the pill exactly with a bright bit running around it, + hover reveal of
        a white-circle arrow (nav grow animation) ---- */
.btn-cta{
  position:relative;isolation:isolate;border:0;
  /* no overflow:hidden — lets the stroke's glow bleed past the pill; each inner
     layer is already rounded via its own border-radius */
  border-radius:999px;padding:17px 30px;      /* pill */
  font-size:1.08rem;font-weight:600;color:#fff;
  background:transparent;
  box-shadow:0 16px 34px rgba(12,26,80,.5);
}
/* face — gradient (light left -> #080F20 right), top gloss + inner shadow */
.btn-cta::after{
  content:"";position:absolute;z-index:0;inset:0;border-radius:inherit;pointer-events:none;
  background:
    radial-gradient(150% 180% at 0% 22%, rgba(120,152,232,.34) 0%, rgba(120,152,232,0) 46%),
    linear-gradient(104deg, #35538f 0%, #17294f 52%, #080f20 100%);
  box-shadow:
    inset 0 1.5px 0 rgba(255,255,255,.30),
    inset 0 -16px 28px rgba(2,6,20,.5);
}
/* white speckles — discrete dots (turbulence thresholded to white), denser at
   the edges and thinning toward the centre via a radial mask */
.cta-grain{
  position:absolute;z-index:1;inset:0;border-radius:inherit;pointer-events:none;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='190' height='190'%3E%3Cfilter id='s'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='1' seed='7' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 14 -9'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23s)'/%3E%3C/svg%3E") 0 0/190px 190px;
  opacity:.65;
  -webkit-mask:radial-gradient(115% 140% at 50% 50%, transparent 8%, rgba(0,0,0,.28) 42%, #000 95%);
  mask:radial-gradient(115% 140% at 50% 50%, transparent 8%, rgba(0,0,0,.28) 42%, #000 95%);
}
/* the stroke — a spinning conic clipped to the EXACT pill border ring, so the
   running highlight follows the button's outline precisely */
/* glow wrapper — filter only, no mask, so the drop-shadow bleeds (isn't
   clipped by the ring mask below) */
.cta-stroke{
  position:absolute;z-index:2;inset:0;border-radius:inherit;pointer-events:none;
  filter:drop-shadow(0 0 3px rgba(255,255,255,.95)) drop-shadow(0 0 8px rgba(205,228,255,.6)) drop-shadow(0 0 16px rgba(150,195,255,.42));
}
.cta-ring{
  position:absolute;inset:0;border-radius:inherit;overflow:hidden;
  padding:1.6px;
  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;
  mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite:exclude;
}
.cta-ring i{
  position:absolute;top:50%;left:50%;width:250%;aspect-ratio:1;
  transform:translate(-50%,-50%);
  background:conic-gradient(from 0deg,
    rgba(210,226,255,.55) 0deg, rgba(210,226,255,.55) 250deg,
    #ffffff 295deg, #ffffff 312deg, rgba(210,226,255,.55) 350deg, rgba(210,226,255,.55) 360deg);
  animation:cta-run 2.4s linear infinite;      /* quicker base loop */
}
.btn-cta:hover .cta-ring i,.btn-cta.is-hover .cta-ring i{ animation-duration:1s; }  /* speeds up on hover */
@keyframes cta-run{ to{ transform:translate(-50%,-50%) rotate(1turn); } }
.btn-cta .si-label,.btn-cta .si-ic{ position:relative;z-index:3; }
/* hover: grow (nav-style) + reveal the white-circle arrow icon */
@media (hover: hover) and (pointer: fine){
  .btn-cta:hover,.btn-cta.is-hover{ transform:scale(1.04); }
}
.btn-cta .si-ic .ic-circle{
  display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:30px;height:30px;border-radius:50%;
  background:#eef1fb;color:#182449;box-shadow:0 3px 8px rgba(10,20,60,.3);
}
.btn-cta .si-ic svg{ width:15px;height:15px; }
@media (hover: hover) and (pointer: fine){
  .btn-cta:hover .si-ic,.btn-cta.is-hover .si-ic{ width:30px;opacity:1;margin-left:12px;transform:none; }
}
.btn-cta:active,.btn-cta.is-hover:active{ transform:scale(1.0) translateY(1px); }
@media (prefers-reduced-motion:reduce){
  .cta-ring i{ animation:none; }
}

/* ASCII bloom — ambient field + glyph layer (kept) */
.bloom{position:absolute;pointer-events:none;z-index:0}
.bloom--hero{
  inset:0;width:100%;height:100%;
  top:0;right:0;left:0;
  /* no blend mode: glyphs are pure white, and screen(B, white)=white makes
     normal compositing pixel-identical — without forcing the GPU to blend a
     full-viewport layer on every animation frame */
  opacity:1;
}
/* Soft blue wash under ASCII — brighter core so the field genuinely blooms */
.bloom-wash{
  position:absolute;inset:0;width:100%;height:100%;
  top:0;right:0;left:0;
  z-index:0;pointer-events:none;
  /* Blobs pulled UP into the darker upper band (mirrored 1:1 by the glyph
     luminance mask in app.js — see AsciiBloom luminanceMap). The second echo
     was at 60% (now-lightening zone); raised to 42% so glow AND glyphs cluster
     up top and fade out of the lower half, matching the deeper/lowered
     gradient — glyphs now show "only in some parts" (top + flanks). */
  background:
    radial-gradient(ellipse 56% 46% at 74% 28%, rgba(120,180,255,.85) 0%, rgba(70,140,250,.5) 30%, rgba(45,105,220,.24) 55%, transparent 74%),
    radial-gradient(ellipse 38% 30% at 50% 42%, rgba(110,180,255,.38) 0%, transparent 70%),
    radial-gradient(ellipse 36% 30% at 24% 24%, rgba(80,150,240,.34) 0%, transparent 68%);
}
.bloom--soft{opacity:.5}

/* hero reveal masking (translateY + clip) */
.reveal-line{display:block;overflow:hidden;padding-bottom:.04em}
.reveal-line > *{display:block;will-change:transform;transform:translateY(116%);transition:transform .85s var(--ease)}
.reveal-line.in > *{transform:translateY(0)}
.reveal-line:nth-of-type(2) > *{transition-delay:.09s}
.reveal-line:nth-of-type(3) > *{transition-delay:.16s}

@media (max-width:900px){
  .hero{min-height:auto;padding-bottom:clamp(48px,10vw,80px)}
  .hero-grid{grid-template-columns:1fr;gap:36px;max-width:100%}
  .hero .sub{max-width:46ch}
  .hero-card-wrap{order:2;justify-self:center}
  .hero h1{font-size:clamp(2.35rem,9.2vw,3.25rem)}
  .hero h1 .h1-line{white-space:normal}
  .hero .kicker{white-space:normal;max-width:min(100%,42ch);text-align:left}
  .bloom--hero,.bloom-wash{opacity:.95}
}

/* Phone hero (≤700px): centred, stripped to the essentials — no eyebrow, no
   preview card, a smaller CTA. Placed AFTER the ≤900 block so these win. */
@media (max-width:700px){
  .hero-copy{text-align:center}
  .hero .kicker{display:none}                      /* remove the eyebrow */
  /* 11.3vw = 44px at 390px (the approved look) but, unlike the old 10.5vw
     whose floor kicked in below 419px, it keeps scaling on real phones */
  .hero h1{margin-top:0;font-size:clamp(2.4rem,11.3vw,3.7rem)}  /* bigger headline on phones */
  /* softer, lighter legibility shadow on phones: wider blur, lower alpha —
     the glyph field behind is already dimmer here, so the hard halo the
     desktop needs reads as grime on a small screen */
  .hero h1{text-shadow:0 2px 12px rgba(2,10,34,.34), 0 3px 30px rgba(2,14,44,.28)}

  /* ---- WAVE WATERLINE (mobile only) ----------------------------------
     The wave is the <svg class="hero-wave"> in the markup, NOT a gradient:
     stacked radial-gradients can only produce bumps, and squeezed into the
     ~67px left at the bottom of a 450px hero they always flattened into a
     line. The SVG hangs past the hero's bottom edge so the curve has real
     amplitude, and its white fill runs to the path's baseline — covering the
     hero's last pixels edge-to-edge, so no seam is possible.
     The base linear therefore holds BLUE all the way down: the wave alone
     brings the white in. */
  .hero{
    background:
      radial-gradient(ellipse 90% 55% at 50% -8%, rgba(80,130,230,.40) 0%, transparent 58%),
      radial-gradient(ellipse 62% 46% at 80% 26%, rgba(50,100,210,.34) 0%, transparent 62%),
      radial-gradient(ellipse 50% 32% at 16% 58%, rgba(30,70,160,.18) 0%, transparent 70%),
      linear-gradient(180deg,
        #03060f 0%, #050c22 20%, #0a1a45 46%, #10346f 68%,
        #1a4a94 84%, #24579f 94%, #2b62ac 100%);
  }
  /* taller section: the deeper wave needs clear water below the CTA (its
     crest now reaches ~162px above the hero's edge) */
  .hero{padding-bottom:460px}
  .hero-wave{
    display:block;
    /* GEOMETRY CONTRACT (keep these in step):
       The FEATHER now lives in the SVG gradient (see #hw-feather), not in a
       big blur — a blur wide enough for this much softness smears the curve
       into a flat fade and, because it also blooms downward past the fill,
       it thins the white at the hero's edge and opens a seam. The blur here
       is only a light smoother.
       THE BINDING CONSTRAINT: the visible feather is the distance between
       the curve and where the ramp goes opaque, and ALL of that has to fit
       between the CTA and the hero's bottom edge. So feather length is
       capped by the hero's height — which is why padding-bottom grows every
       time the feather does (300→460px here).
       viewBox is 200 tall with the curve flattened to y 45→132 (the taller
       SVG multiplies it back into a DEEPER wave in px without the crest
       climbing into the button). Trough sits at 0.66·height from the SVG
       top: height 640 / bottom -90 puts the crest ~406px above the hero's
       edge (padding 460 clears it) and the trough ~118px above. The ramp
       hits full opacity at 90% of the path, so the hero's edge sits inside
       the solid zone and no seam can open. Overhang ±20% keeps the blur's
       own side edges off-screen. */
    position:absolute;left:-20%;bottom:-90px;
    /* max-width:none is REQUIRED — the global img/svg reset caps svg at
       100% of the container, which silently shrank this to the hero's width
       and left a bare strip of blue down the right edge.
       Oversize (±10%) scales WITH the blur: the feather's own soft edges
       must fall off-screen, so a bigger blur needs a wider element. */
    width:140%;max-width:none;height:640px;
    z-index:1;   /* over the glyph canvas (0), under the copy (2) */
    pointer-events:none;
    filter:blur(57px);                   /* scaled with the ramp; still < the 90px bleed and < the ±78px overhang, so no seam / no side fade */
  }
  .hero-wave path{fill:#fff}
  /* close the dead space: the platforms section opened ~186px of empty white
     under the hero on phones (its sticky wheel layout, not padding) */
  .platforms{margin-top:-80px}   /* clear of the feather's downward bloom */
  .hero .sub{margin-inline:auto}                   /* centre the (max-width) sub */
  .hero .sub{font-size:.84rem;max-width:32ch;margin-top:12px} /* smaller sub on phones */
  .hero-cta{justify-content:center;margin-top:20px}/* centre the CTA */
  .hero .btn-cta{padding:13px 24px;font-size:.98rem}   /* smaller Get started */
  .hero-card-wrap{display:none}                    /* remove the floating preview card */
}

/* Widescreen (≥1600px): h1 was already flat-capping at 3.85rem past ~1232px
   wide (5vw outgrows the cap), so on 27"+ monitors it never grew with the
   page — raise the cap and give the grid a bit more room inside the wider
   shell. */
@media (min-width:1600px){
  .hero h1{font-size:clamp(2.4rem,5vw,4.4rem)}
  .hero-grid{max-width:1240px}
}

/* ==========================================================================
   HERO TILT CARD  (Effect 3)
   ========================================================================== */
/* wrap hugs the card so the arrows can sit just outside its edges */
.hero-card-wrap{
  perspective:1200px;position:relative;z-index:2;width:min(320px,80vw);margin-inline:auto;
  /* pointer angle + edge-closeness (0..100), driven by app.js — the card
     stroke glows only where the cursor nears an edge */
  --pointer-deg:0deg; --pointer-d:0;
}
@media (min-width:1600px){
  .hero-card-wrap{width:min(370px,80vw)}
}
/* the single 3D plane that tilts — the card and its running stroke are children,
   so they rotate together as one rigid surface */
.tilt-3d{
  position:relative;z-index:1;width:100%;aspect-ratio:9/16;
  transform-style:preserve-3d;
  will-change:transform;
}
.tilt-card{
  position:relative;z-index:1;width:100%;height:100%;
  border-radius:26px;overflow:hidden;isolation:isolate;
  background:#0b0b0f;border:1px solid rgba(255,255,255,.18);
  box-shadow:
    0 28px 70px rgba(0,12,40,.55),
    0 0 90px rgba(70,140,255,.28),
    inset 0 1px 0 rgba(255,255,255,.12);
}
/* border-radius:inherit keeps the video clipped to the rounded card during 3D tilt */
.tilt-card video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:inherit}
/* WebGL ripple canvas — during a style change it draws both videos composited
   through a circular shockwave shader (refraction + glowing wavefront). Sits
   above both videos (z2 + later in DOM) but under the style chip (z3). */
.tilt-card .ripple-gl{
  position:absolute;inset:0;width:100%;height:100%;z-index:2;
  pointer-events:none;border-radius:inherit;opacity:0;
}
.tilt-card .ripple-gl.on{ opacity:1; }
/* fallback (no WebGL): feathered directional wipe between caption styles (two
   stacked videos; the incoming one slides its soft mask edge across) */
.tilt-card video.wipe-ltr,.tilt-card video.wipe-rtl{
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-size:220% 100%;mask-size:220% 100%;
}
.tilt-card video.wipe-ltr{        /* reveal left -> right (prev): start hidden */
  -webkit-mask-image:linear-gradient(to right,#000 0 45%,transparent 55% 100%);
  mask-image:linear-gradient(to right,#000 0 45%,transparent 55% 100%);
  -webkit-mask-position:100% 0;mask-position:100% 0;
}
.tilt-card video.wipe-rtl{        /* reveal right -> left (next): start hidden */
  -webkit-mask-image:linear-gradient(to right,transparent 0 45%,#000 55% 100%);
  mask-image:linear-gradient(to right,transparent 0 45%,#000 55% 100%);
  -webkit-mask-position:0% 0;mask-position:0% 0;
}
/* the transition lives ONLY on .go, so applying the start position is instant
   (otherwise the start-position set itself animates and prev double-wipes).
   Linear is reserved for continuous loops/marquees; this is a one-shot
   reveal, so it gets the standard fast-start/soft-landing curve instead. */
.tilt-card video.wipe-ltr.go,.tilt-card video.wipe-rtl.go{
  transition:-webkit-mask-position .5s cubic-bezier(.4,0,.2,1), mask-position .5s cubic-bezier(.4,0,.2,1);
}
.tilt-card video.wipe-ltr.go{ -webkit-mask-position:0% 0;mask-position:0% 0; }
.tilt-card video.wipe-rtl.go{ -webkit-mask-position:100% 0;mask-position:100% 0; }
/* spray particles kicked up along the ripple wavefront — 2D canvas above the
   GL layer (screen blend so the droplets add light over the video) */
.tilt-card .wipe-fx{ position:absolute;inset:0;width:100%;height:100%;z-index:5;pointer-events:none;mix-blend-mode:screen; }
@media (prefers-reduced-motion:reduce){
  .tilt-card .wipe-fx,.tilt-card .ripple-gl{ display:none; }
}
.tilt-card .glare{
  position:absolute;inset:0;pointer-events:none;
  /* white-only sheen — normal compositing is pixel-identical to screen blend */
  background:linear-gradient(105deg,rgba(255,255,255,0) 30%,rgba(255,255,255,.28) 50%,rgba(255,255,255,0) 70%);
  opacity:.0;transition:opacity .3s var(--ease);
}
.tilt-card.is-live .glare{opacity:1}
.tilt-card .frame-ring{position:absolute;inset:0;border-radius:inherit;box-shadow:inset 0 1px 0 rgba(255,255,255,.18),inset 0 0 0 1px rgba(255,255,255,.06);pointer-events:none}

/* Glowing edge stroke — a white outline hugging the card's exact rounded
   border, but it only lights up where the cursor is near an edge. Its
   brightness follows --pointer-d (edge closeness) and the lit segment follows
   --pointer-deg (pointer angle). Lives inside .tilt-3d so it tilts with the
   card; drop-shadow gives it a real glow. Invisible at rest. */
.card-stroke{
  position:absolute;inset:0;z-index:3;border-radius:26px;pointer-events:none;
  /* glow lives here (no mask) so the drop-shadow bleeds instead of being
     clipped away by the ring mask (filter is applied before mask) */
  filter:drop-shadow(0 0 4px rgba(210,232,255,1)) drop-shadow(0 0 13px rgba(160,200,255,.85)) drop-shadow(0 0 26px rgba(120,175,255,.55));
  /* glows in only as the cursor approaches an edge (starts ~edge 0.35) */
  opacity:calc((var(--pointer-d) - 35) / 65);
  transition:opacity .28s ease;
}
.card-stroke i{
  position:absolute;inset:0;border-radius:inherit;
  padding:2px;                                /* stroke thickness — the ring is masked here */
  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;
  mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite:exclude;
  /* a bright segment centred on the pointer's angle, fading around the ring */
  background:conic-gradient(from var(--pointer-deg) at center,
    #ffffff 0deg, rgba(255,255,255,.35) 28deg, transparent 58deg,
    transparent 302deg, rgba(255,255,255,.35) 332deg, #ffffff 360deg);
}

/* style chip */
.style-chip{
  position:absolute;top:14px;left:14px;z-index:3;
  display:inline-flex;align-items:center;gap:7px;
  font-family:var(--sans);font-weight:600;font-size:12.5px;color:#fff;
  padding:7px 12px;border-radius:var(--radius-pill);
  background:rgba(10,10,20,.5);backdrop-filter:blur(10px);
  border:1px solid rgba(255,255,255,.16);
}
.style-chip .tick{width:6px;height:6px;border-radius:50%;background:var(--blue-bright)}

/* arrow buttons */
.tilt-arrow{
  position:absolute;top:50%;transform:translateY(-50%);z-index:4;
  width:40px;height:40px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  background:rgba(255,255,255,.92);color:var(--ink);
  box-shadow:0 6px 18px rgba(0,0,0,.28);
  /* explicit standard curve for plain UI moves per the site's motion rules
     (var(--ease) is the site's decorative ease-out; these arrows are
     functional controls, not decoration) */
  transition:transform .18s cubic-bezier(.4,0,.2,1),background .2s cubic-bezier(.4,0,.2,1);
}
@media (hover: hover) and (pointer: fine){
  .tilt-arrow:hover{background:#fff;transform:translateY(-50%) scale(1.08)}
}
.tilt-arrow:active{transform:translateY(-50%) scale(.94)}
/* arrows live outside the clipped card now, so they sit clear of the box */
.tilt-arrow.prev{left:-28px}
.tilt-arrow.next{right:-28px}
.tilt-arrow svg{width:18px;height:18px;stroke:currentColor;stroke-width:2.4;fill:none}

/* small caption under card */
.card-caption{margin-top:16px;text-align:center;font-size:13px;color:var(--ink-faint)}

/* ==========================================================================
   ADOBE STRIP
   ========================================================================== */
.adobe-strip{position:relative;padding-block:clamp(56px,8vw,104px);overflow:hidden}
.adobe-strip .flank{position:absolute;top:0;bottom:0;width:clamp(40px,12vw,150px);opacity:.7}
.adobe-strip .flank.l{left:0}
.adobe-strip .flank.r{right:0}
.adobe-line{
  text-align:center;font-family:var(--sans);font-weight:700;letter-spacing:-.02em;
  font-size:clamp(1.9rem,5.4vw,3.7rem);line-height:1.12;
  max-width:1000px;margin-inline:auto;padding-inline:clamp(48px,10vw,150px);
}
.adobe-line .app{display:inline-flex;align-items:center;gap:.28em;vertical-align:middle}
.adobe-line img{width:.95em;height:.95em;border-radius:.2em;display:inline-block;
  filter:drop-shadow(0 4px 14px rgba(18,134,230,.28))}
.soon{font-family:var(--body);font-size:.32em;font-weight:600;color:var(--blue);
  vertical-align:super;margin-inline-start:.15em;letter-spacing:0}

/* ==========================================================================
   SCROLLYTELLING  (Effect 4)
   ========================================================================== */
.scrolly{position:relative;padding:0}
.scrolly-pin{min-height:100dvh;display:flex;flex-direction:column;justify-content:center;
  padding-block:clamp(64px,8vw,90px);padding-inline:var(--gutter);position:relative}
.scrolly-head{text-align:center;margin-bottom:clamp(28px,4vw,44px)}
.scrolly-progress{
  position:relative;height:3px;width:min(560px,80%);margin:18px auto 0;
  background:var(--line);border-radius:2px;overflow:hidden;
}
.scrolly-progress i{position:absolute;inset:0;transform-origin:left;transform:scaleX(0);
  background:var(--blue-grad);border-radius:2px}
.scrolly-steplabels{display:flex;justify-content:center;gap:10px;margin-top:16px;flex-wrap:wrap}
.scrolly-steplabels span{
  font-family:var(--sans);font-weight:600;font-size:12.5px;color:var(--ink-faint);
  padding:5px 12px;border-radius:var(--radius-pill);border:1px solid var(--line);
  transition:color .3s var(--ease),border-color .3s var(--ease),background .3s var(--ease);
}
.scrolly-steplabels span.active{color:#fff;background:var(--ink);border-color:var(--ink)}

.stage{
  display:grid;grid-template-columns:340px 1fr;gap:clamp(24px,4vw,52px);
  align-items:center;max-width:940px;margin-inline:auto;width:100%;
}

/* plugin panel mock (left) */
.plugin{
  background:var(--bg);border:1px solid var(--line);border-radius:var(--radius-card);
  box-shadow:var(--shadow-soft);overflow:hidden;
}
.plugin-top{display:flex;align-items:center;gap:8px;padding:13px 16px;border-bottom:1px solid var(--line)}
.plugin-top .wordmark{font-size:1rem}
.plugin-top .dots{margin-inline-start:auto;display:flex;gap:5px}
.plugin-top .dots i{width:8px;height:8px;border-radius:50%;background:var(--line-strong)}
.plugin-body{padding:16px}
.plugin-row{display:flex;align-items:center;gap:10px;font-size:13px;color:var(--ink-soft);
  padding:11px 12px;border:1px solid var(--line);border-radius:12px;margin-bottom:9px;background:var(--bg-soft)}
.plugin-row .ic{width:22px;height:22px;border-radius:6px;background:var(--blue-wash);
  display:flex;align-items:center;justify-content:center;color:var(--blue);flex:none}
.plugin-row .ic svg{width:13px;height:13px;stroke:currentColor;stroke-width:2;fill:none}
.plugin-generate{
  width:100%;margin-top:4px;background:var(--blue);color:#fff;font-family:var(--sans);font-weight:600;
  padding:13px;border-radius:12px;display:flex;align-items:center;justify-content:center;gap:8px;
  transition:background .2s var(--ease);
}
.plugin-generate.is-hot{box-shadow:0 8px 22px rgba(18,134,230,.34)}
.plugin-generate .spin{width:15px;height:15px;border-radius:50%;border:2px solid rgba(255,255,255,.4);
  border-top-color:#fff;opacity:0}
.plugin.is-working .plugin-generate .spin{opacity:1;animation:spin 1s linear infinite}
.plugin-style{display:flex;align-items:center;justify-content:space-between;margin-top:10px;
  padding:9px 12px;border:1px solid var(--line);border-radius:12px}
.plugin-style .name{font-family:var(--sans);font-weight:600;font-size:13.5px}
.plugin-style .arrows{display:flex;gap:6px}
.plugin-style .arrows i{width:24px;height:24px;border-radius:7px;border:1px solid var(--line);
  display:flex;align-items:center;justify-content:center;color:var(--ink-soft);font-size:12px}
@keyframes spin{to{transform:rotate(360deg)}}

/* preview (right) */
.stage-preview{position:relative;justify-self:center;width:min(300px,78vw);aspect-ratio:9/16;
  border-radius:24px;overflow:hidden;background:#0b0b0f;border:1px solid rgba(255,255,255,.12);
  box-shadow:var(--shadow-lift)}
/* "dark simulated footage" — a moody lit scene, no baked captions, so the
   live word-by-word caption is the only text on the frame */
.stage-preview.sim{background:
  radial-gradient(120% 70% at 50% 8%, rgba(78,92,140,.55), transparent 52%),
  radial-gradient(80% 55% at 68% 40%, rgba(18,134,230,.18), transparent 60%),
  radial-gradient(100% 60% at 50% 112%, rgba(8,10,18,.95), transparent 58%),
  linear-gradient(180deg,#14161f 0%,#0a0b10 100%)}
.stage-preview .bgvid,.stage-preview .bgimg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:.9}
.stage-preview .scrim{position:absolute;inset:0;background:linear-gradient(180deg,rgba(0,0,0,0) 40%,rgba(0,0,0,.35))}
/* live caption words rendered by JS */
.cap{position:absolute;left:50%;bottom:22%;transform:translateX(-50%);width:86%;
  display:flex;flex-wrap:wrap;gap:.28em .34em;justify-content:center;z-index:2}
.cap .w{
  font-family:var(--sans);font-weight:800;letter-spacing:-.01em;
  font-size:clamp(18px,4.6vw,26px);color:#fff;
  text-shadow:0 2px 10px rgba(0,0,0,.5);
  opacity:0;transform:translateY(8px) scale(.9);
  transition:opacity .18s var(--ease),transform .22s var(--ease),color .2s var(--ease);
}
.cap .w.on{opacity:1;transform:translateY(0) scale(1)}
.cap .w.emph{color:var(--blue-bright)}
.cap.style-box .w.on{background:var(--blue);padding:.02em .16em;border-radius:.16em;color:#fff}
.cap.style-fade .w{transition:opacity .4s var(--ease)}
.cap.style-fade .w.emph{color:#fff;-webkit-text-stroke:0}
/* tap-to-fix pulse */
.cap .w.fixing{outline:2px dashed rgba(75,166,241,.9);outline-offset:3px;border-radius:4px}
.preview-cursor{position:absolute;width:18px;height:18px;z-index:3;opacity:0;
  transition:opacity .3s var(--ease)}
.preview-cursor svg{width:100%;height:100%;filter:drop-shadow(0 2px 4px rgba(0,0,0,.5))}

/* timeline layers (final beat) */
.timeline{position:absolute;left:0;right:0;bottom:0;z-index:2;padding:10px;background:rgba(6,6,12,.72);
  backdrop-filter:blur(6px);opacity:0;transform:translateY(12px);transition:opacity .4s var(--ease),transform .4s var(--ease)}
.timeline.on{opacity:1;transform:translateY(0)}
.timeline .track{display:flex;gap:4px}
.timeline .layer{height:16px;border-radius:4px;background:var(--blue);flex:1;opacity:.9;
  display:flex;align-items:center;justify-content:center;font-size:8px;color:#fff;font-family:var(--sans);font-weight:600}
.timeline .layer:nth-child(2){background:var(--blue-bright)}
.timeline .layer:nth-child(3){background:var(--blue-deep)}
.timeline .cap2{margin-top:6px;font-size:9px;color:rgba(255,255,255,.7);text-align:center;font-family:var(--body)}

/* scrolly mobile / reduced-motion fallback: stacked steps */
.scrolly-mobile{display:none;padding-block:clamp(48px,8vw,72px);padding-inline:var(--gutter)}
.scrolly-mobile .stack{display:grid;gap:16px;margin-top:28px;max-width:640px;margin-inline:auto}
.m-step{background:var(--bg);border:1px solid var(--line);border-radius:var(--radius-card);
  box-shadow:var(--shadow-soft);padding:22px;display:flex;gap:16px;align-items:flex-start}
.m-step .n{width:32px;height:32px;flex:none;border-radius:10px;background:var(--blue-wash);color:var(--blue);
  font-family:var(--sans);font-weight:700;display:flex;align-items:center;justify-content:center}
.m-step h3{font-size:1.15rem;margin-bottom:5px}
.m-step p{font-size:.95rem;color:var(--ink-soft)}
@media (max-width:820px){
  .scrolly-pin{display:none}
  .scrolly-mobile{display:block}
}
/* reduced-motion users get the readable stacked sequence on any width */
.rm .scrolly-pin{display:none}
.rm .scrolly-mobile{display:block}

/* ==========================================================================
   TEMPLATES SHOWCASE
   ========================================================================== */
/* ---- caption-style gallery (left) + one live preview (right) --------------
   Desktop: 2-col. Picking a chip swaps ONLY the caption on the preview (JS syncs
   currentTime so the footage keeps playing). Gallery chips are static posters --
   only the preview decodes video. Mobile reflows into a snap filmstrip below. */
.styles-wrap{
  display:grid;grid-template-columns:1fr minmax(272px,336px);
  gap:clamp(20px,3vw,44px);align-items:center;
  max-width:900px;margin:var(--stack-gap) auto 0;
}
/* left: compact 2-col grid of poster chips */
.styles-gallery{ display:grid;grid-template-columns:repeat(2,1fr);gap:10px; }
.style-chip-item{
  display:flex;align-items:center;gap:11px;width:100%;text-align:left;
  padding:8px 12px 8px 8px;border-radius:14px;cursor:pointer;
  background:var(--bg);border:1px solid var(--line);
  transition:border-color .2s var(--ease),background .2s var(--ease),box-shadow .2s var(--ease),transform .16s var(--ease);
}
@media (hover: hover) and (pointer: fine){
  .style-chip-item:hover{border-color:var(--line-strong);transform:translateY(-1px);box-shadow:var(--shadow-soft)}
}
.style-chip-item:active{transform:scale(.98)}
.style-chip-item.is-active{
  border-color:var(--blue);background:var(--blue-wash);
  box-shadow:0 0 0 3px rgba(18,134,230,.14);
}
.sci-thumb{
  flex:none;width:36px;aspect-ratio:9/16;border-radius:8px;overflow:hidden;
  background:#0c0c10;border:1px solid var(--line);
}
.sci-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.sci-name{
  flex:1;min-width:0;font-family:var(--sans);font-weight:600;font-size:.92rem;color:var(--ink);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.style-chip-item.is-active .sci-name{color:var(--blue-deep)}
/* right: the single live 9:16 preview (two stacked videos, z-index toggled in JS) */
.styles-preview{position:relative}
.styles-card{
  position:relative;aspect-ratio:9/16;border-radius:var(--radius-card);overflow:hidden;
  background:#0c0c10;border:1px solid var(--line);box-shadow:var(--shadow-lift);
  margin-inline:auto;width:100%;
}
.styles-card video.pv{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:inherit}

/* ---- mobile: bespoke camera-style picker -- big preview on top, horizontal
   scroll-snapping filmstrip of vertical poster chips below ---- */
@media (max-width:820px){
  .styles-wrap{grid-template-columns:1fr;gap:16px;max-width:460px}
  .styles-preview{order:-1}
  .styles-card{aspect-ratio:9/16;max-height:56svh;width:auto;max-width:100%}
  .styles-gallery{
    display:flex;gap:10px;grid-template-columns:none;
    overflow-x:auto;scroll-snap-type:x proximity;-webkit-overflow-scrolling:touch;
    padding:4px 2px 10px;scrollbar-width:none;scroll-padding-inline:16px;
    /* The scrollbar is hidden here AND is an invisible overlay on iOS, so the
       only cue that 11 more styles exist is a chip half-crossing the edge — and
       whether one does is pure arithmetic. Measured across common widths, the
       peek collapses to 0px at 375px and 10px at 390px (the two most common
       iPhone widths) while ~2/3 of the strip is still hidden, so it reads as
       complete. This fade makes the cue independent of where a chip lands.
       `to right` is deliberate physical geometry, not a missed logical prop. */
    -webkit-mask-image:linear-gradient(to right,#000 calc(100% - 26px),transparent);
    mask-image:linear-gradient(to right,#000 calc(100% - 26px),transparent);
  }
  .styles-gallery::-webkit-scrollbar{display:none}
  .style-chip-item{
    flex:0 0 auto;width:auto;flex-direction:column;gap:7px;padding:7px 7px 9px;
    scroll-snap-align:center;
  }
  .sci-thumb{width:58px}
  .sci-name{flex:none;font-size:.8rem;text-align:center;max-width:64px}
}
/* ==========================================================================
   FEATURES — 5-card bento, "exact crop" anatomy (reference-final)
   No section shell: each card sits directly on the plain page and is exactly
   the reference card — a light-gray rounded card, ONE white/gradient
   illustration panel floating inside it (own soft shadow), centered title +
   one-liner on the card surface below. Nothing else. Scenes are SPARSE by
   rule: one glossy focal element + a few satellites, no mini-UI chrome.
   The reference's photo avatars become LANGUAGE CIRCLES — avatar-styled white
   coins holding single Indic letters (अ த త ગ ಕ: Hindi, Tamil, Telugu,
   Gujarati, Kannada) — Subtitl's languages standing in for people.
   Motion: transform/opacity keyframes only, paused unless #why.bento-live
   (existing IO in app.js -> initFeatureBento, untouched). Base styles ARE the
   composed rests; reduced-motion just kills keyframes. --bento-blue stays
   scoped (#1286E6 per owner; site-wide reconciliation = one line).
   ========================================================================== */
#why{
  --bento-blue:#1286E6;
  --bento-ink:#17181A;
  --bento-mut:#82868D;
}

.bento-grid{
  display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(14px,1.8vw,22px);
  max-width:1140px;margin:var(--stack-gap,40px) auto 0;
}
.bento-card{
  /* white card on the (white) page — no grey. It reads via a soft diffused
     shadow + hairline edge (a touch stronger than before so it separates from
     white), exactly the reference card sans its grey backdrop. */
  background:#fff;
  border-radius:28px;
  border:1px solid rgba(15,17,21,.06);
  box-shadow:0 22px 48px -20px rgba(17,20,28,.16), 0 3px 10px -4px rgba(17,20,28,.05);
  padding:8px 8px 0;
  display:flex;flex-direction:column;
}
.bento-card--hero{grid-column:span 2}
/* the illustration shares the card's white surface — no floating box, no
   border, no shadow. Its BOTTOM feathers to transparent so the scene melts
   into the white the text sits on (the reference's "feathered white down").
   overflow:hidden still clips the blurred style blobs / ripple edges. */
.bento-panel{
  position:relative;overflow:hidden;
  height:clamp(236px,23vw,278px);
  border-radius:22px 22px 0 0;
  background:#fff;
  -webkit-mask-image:linear-gradient(to bottom,#000 82%,transparent 100%);
  mask-image:linear-gradient(to bottom,#000 82%,transparent 100%);
}
.bento-text{text-align:center;padding:6px 20px 24px;margin-top:-6px}
.bento-text h3{font-size:1.06rem;letter-spacing:-.015em;margin-bottom:6px;color:var(--bento-ink)}
.bento-text p{font-size:.9rem;line-height:1.55;color:var(--bento-mut);max-width:33ch;margin-inline:auto}

/* ---- language circles: the avatar stand-ins ----------------------------- */
.lang-coin{
  position:absolute;display:grid;place-items:center;
  width:var(--sz,42px);height:var(--sz,42px);border-radius:50%;
  background:#fff;
  border:2px solid rgba(255,255,255,.9);
  box-shadow:0 8px 20px -6px rgba(23,32,64,.28);
  font-family:var(--body);font-weight:600;font-size:calc(var(--sz,42px)*.42);
  line-height:1;color:var(--lc,#1286E6);
  transform:rotate(var(--rot,0deg));
}
@keyframes bentoFloat{
  0%,100%{transform:translateY(0) rotate(var(--rot,0deg))}
  50%{transform:translateY(-5px) rotate(var(--rot,0deg))}
}
.is-float{animation:bentoFloat var(--fdur,7s) ease-in-out var(--fdel,0s) infinite}

/* ---- 01 HERO — the reference scene itself: glossy counter pill, breathing
        ripples, five language circles orbiting like the avatars ------------ */
.scene-odo{background:
  radial-gradient(120% 90% at 50% 30%, #CFE2FF 0%, #E6F0FF 46%, #F9FBFF 82%, #FFFFFF 100%)}
.odo-ring{position:absolute;left:50%;top:44%;border-radius:999px;pointer-events:none}
.odo-ring.ring-1{width:38%;height:40%;background:rgba(75,166,241,.22);box-shadow:inset 0 0 0 1px rgba(75,166,241,.25);transform:translate(-50%,-50%)}
.odo-ring.ring-2{width:56%;height:62%;background:rgba(75,166,241,.13);box-shadow:inset 0 0 0 1px rgba(75,166,241,.16);transform:translate(-50%,-50%)}
.odo-ring.ring-3{width:74%;height:86%;background:rgba(75,166,241,.07);box-shadow:inset 0 0 0 1px rgba(75,166,241,.09);transform:translate(-50%,-50%)}
@keyframes odoBreathe{
  0%,100%{transform:translate(-50%,-50%) scale(1)}
  50%{transform:translate(-50%,-50%) scale(1.04)}
}
.odo-ring.ring-1{animation:odoBreathe 5.5s ease-in-out infinite}
.odo-ring.ring-2{animation:odoBreathe 5.5s ease-in-out .55s infinite}
.odo-ring.ring-3{animation:odoBreathe 5.5s ease-in-out 1.1s infinite}
.odo-pill{
  position:absolute;left:50%;top:44%;transform:translate(-50%,-50%);
  display:flex;align-items:center;
  font-family:var(--body);font-weight:700;font-size:1.34rem;color:#fff;
  font-variant-numeric:tabular-nums;letter-spacing:.01em;line-height:1;
  background:linear-gradient(180deg,var(--blue-bright) 0%,var(--brand-blue) 55%,var(--blue-deep) 100%);
  border-radius:999px;padding:13px 22px;
  box-shadow:0 12px 28px -6px rgba(18,134,230,.55),
             inset 0 1px 0 rgba(255,255,255,.6),
             inset 0 -2px 6px rgba(6,40,120,.28);
}
.odo-suffix{font-size:.68rem;font-weight:600;color:rgba(255,255,255,.78);margin-inline-start:7px}
.od-cell{display:block;overflow:hidden;height:1em;width:.62em;text-align:center}
.od-strip{display:block}
.od-strip span{display:block;height:1em;line-height:1}
/* roll-then-hold odometer; 11th digit = 1st so -10em loops seamlessly.
   translateY(0) (base + reduced rest) reads 12,847. */
@keyframes odoRoll{
  0%{transform:translateY(0)}7%{transform:translateY(0)}
  10%{transform:translateY(-1em)}17%{transform:translateY(-1em)}
  20%{transform:translateY(-2em)}27%{transform:translateY(-2em)}
  30%{transform:translateY(-3em)}37%{transform:translateY(-3em)}
  40%{transform:translateY(-4em)}47%{transform:translateY(-4em)}
  50%{transform:translateY(-5em)}57%{transform:translateY(-5em)}
  60%{transform:translateY(-6em)}67%{transform:translateY(-6em)}
  70%{transform:translateY(-7em)}77%{transform:translateY(-7em)}
  80%{transform:translateY(-8em)}87%{transform:translateY(-8em)}
  90%{transform:translateY(-9em)}97%{transform:translateY(-9em)}
  100%{transform:translateY(-10em)}
}
.od-strip--ones{animation:odoRoll 15s linear infinite}
.od-strip--tens{animation:odoRoll 150s linear infinite}
/* five circles, orbital-asymmetric like the reference avatars */
.scene-odo .lc-hi{left:17%;top:18%;--sz:46px;--lc:#1286E6;--rot:-4deg;--fdel:.4s}
.scene-odo .lc-ta{right:15%;top:14%;--sz:40px;--lc:#D9480F;--rot:5deg;--fdel:1.6s;--fdur:8s}
.scene-odo .lc-te{right:20%;bottom:27%;--sz:44px;--lc:#5F3DC4;--rot:-3deg;--fdel:2.6s;--fdur:9s}
.scene-odo .lc-gu{left:13%;bottom:31%;--sz:38px;--lc:#087F5B;--rot:4deg;--fdel:.9s;--fdur:8.5s}
.scene-odo .lc-ka{left:44%;bottom:24%;--sz:34px;--lc:#E8590C;--rot:-2deg;--fdel:2s}

/* ---- 02 offline — circuit board: audio waveform rides a trace INTO the
        "Your PC" chip, a finished caption rides OUT; blue glow pulses travel
        the remaining traces. Movers animate along `offset-path` copies of the
        SVG trace d's — element origin (0,0) travels the path, so all glyph
        geometry is drawn around 0,0. Base opacity:0 = the composed rest AND
        the graceful fallback when offset-path is unsupported (no strays
        parked at the SVG origin). ------------------------------------------ */
.scene-offline{background:radial-gradient(110% 95% at 50% 38%, #F2F7FC 0%, #FAFCFE 60%, #FFFFFF 100%)}
.cpu-svg{position:absolute;inset:0;width:100%;height:100%}
.cpu-grid{opacity:.4}
.cpu-traces path{stroke:#C9D7E9;stroke-width:.8}
.cpu-dots circle{fill:#fff;stroke:#B7C7DC;stroke-width:.8}
.cpu-label{font:700 7px var(--sans);fill:#fff;letter-spacing:.03em}
.cpu-wave rect{fill:var(--bento-blue);transform-box:fill-box;transform-origin:center}
.cpu-cap{transform-box:fill-box;transform-origin:center}   /* pop scales about the pill, not the viewBox */
.cpu-cap rect{fill:#fff;stroke:rgba(15,17,21,.14);stroke-width:.6}
.cpu-cap text{font:600 6.8px var(--body-indic)}
.cpu-mover{opacity:0}
.cpu-chipglow{opacity:0}
.cpu-led{opacity:.3}   /* dim at rest — also the reduced-motion state */
@supports (offset-path: path("M 0 0 h 1")){
  /* pulses rotate with the path so the fx=1 comet-head gradient leads */
  .cpu-pulse{offset-rotate:auto}
  /* glyphs stay upright while traveling */
  .cpu-wave,.cpu-cap{offset-rotate:0deg}
  .cpu-p2{offset-path:path("M 28 14 h 50 q 6 0 6 6 v 42");animation:cpuTravel 2.6s linear .5s infinite}
  .cpu-p3{offset-path:path("M 176 24 h -58 q -6 0 -6 6 v 32");animation:cpuTravel 2.9s linear 1.4s infinite}
  .cpu-p5{offset-path:path("M 34 114 h 54 q 6 0 6 -6 v -20");animation:cpuTravel 3.1s linear 0s infinite}
  .cpu-p6{offset-path:path("M 170 122 h -52 q -6 0 -6 -6 v -28");animation:cpuTravel 2.7s linear 1.9s infinite}
  /* one shared 3.6s clock tells the story in beats:
       wave departs .2s, accelerates INTO the chip (ease-in), absorbed ~3.3s
       → chip glow flashes + LED blinks at 3.3s
       → caption launches .25s later, pops to size, decelerates out (ease-out)
     next cycle's wave is already inbound while the caption clears. NOT
     var(--ease): the expo-out token teleports ~95% of the journey. */
  .cpu-wave{offset-path:path("M 10 42 h 40 q 6 0 6 6 v 21 q 0 6 6 6 h 14");animation:cpuWave 3.6s cubic-bezier(.5,.1,.75,.7) .2s infinite}
  .cpu-cap{offset-path:path("M 124 78 h 30 q 6 0 6 -6 v -26 q 0 -6 6 -6 h 14");animation:cpuCap 3.6s cubic-bezier(.2,.7,.3,1) 3.55s infinite}
  .cpu-chipglow{animation:cpuFlash 3.6s linear 3.3s infinite}
  .cpu-led{animation:cpuLed 3.6s linear 3.3s infinite}
  /* the equalizer: every bar breathes scaleY at its own tempo + phase */
  .cpu-wave rect{animation:cpuBar .46s ease-in-out infinite}
  .cpu-wave rect:nth-child(1){animation-duration:.5s;animation-delay:.05s}
  .cpu-wave rect:nth-child(2){animation-duration:.42s;animation-delay:.18s}
  .cpu-wave rect:nth-child(3){animation-duration:.55s;animation-delay:.3s}
  .cpu-wave rect:nth-child(5){animation-duration:.52s;animation-delay:.22s}
  .cpu-wave rect:nth-child(6){animation-duration:.44s;animation-delay:.12s}
  .cpu-wave rect:nth-child(7){animation-duration:.5s;animation-delay:.28s}
}
/* runtime backstop for the @supports gap: engines can parse offset-path yet
   not move SVG children — app.js probes real geometry once and stamps
   .no-svg-offset on #why if the probe fails, collapsing the circuit to its
   static-board fallback instead of fading movers in clumped at the origin */
#why.no-svg-offset .cpu-mover,
#why.no-svg-offset .cpu-wave rect,
#why.no-svg-offset .cpu-chipglow,
#why.no-svg-offset .cpu-led{animation:none !important}
@keyframes cpuTravel{0%{offset-distance:0%;opacity:0}12%{opacity:1}80%{opacity:1}100%{offset-distance:100%;opacity:0}}
@keyframes cpuWave{0%{offset-distance:0%;opacity:0}6%{opacity:1}66%{opacity:1}86%{offset-distance:100%;opacity:0}100%{offset-distance:100%;opacity:0}}
/* the pop uses `transform` (applied AFTER offset in the transform chain), not
   the individual `scale` property — scale composes BEFORE offset, so it also
   multiplied the offset translation about the viewBox origin and the pill
   slid in from under the chip instead of popping in place on the path */
@keyframes cpuCap{0%{offset-distance:0%;opacity:0;transform:scale(.85)}10%{opacity:1;transform:scale(1)}70%{opacity:1}80%{offset-distance:100%;opacity:0}100%{offset-distance:100%;opacity:0}}
@keyframes cpuFlash{0%{opacity:0}7%{opacity:.9}32%{opacity:0}100%{opacity:0}}
@keyframes cpuLed{0%{opacity:.3}3%{opacity:1}6%{opacity:.3}9%{opacity:1}12%{opacity:.3}15%{opacity:1}22%{opacity:.3}100%{opacity:.3}}
@keyframes cpuBar{0%,100%{transform:scaleY(.5)}50%{transform:scaleY(1.15)}}

/* ---- 03 layers — the caption IS a text object. A mini video frame with the
        caption on it: typo "dostan" + red squiggle → selection sweep →
        corrected "doston", caret blinking — mirrored by a mini timeline of
        text-layer rows below (the edited row highlighted). One 5.4s clock;
        base styles = corrected rest state; all movers carry .ly-a. -------- */
.scene-layers{background:radial-gradient(110% 90% at 50% 30%, #F4F6FA 0%, #FBFCFE 70%, #FFFFFF 100%)}
/* these two scenes park story-critical chrome (timeline rows / the AE window's
   dark timeline strip) in the panel's bottom fifth, where the shared 82%->100%
   feather was ghosting it — start their melt at 92% instead, past the content */
.bento-panel.scene-layers,.bento-panel.scene-editor{
  -webkit-mask-image:linear-gradient(to bottom,#000 92%,transparent 100%);
  mask-image:linear-gradient(to bottom,#000 92%,transparent 100%);
}
.ly-frame{
  /* 6%+50%: frame bottom at 56% so the 74px row block (22px rows + 4px gaps)
     clears it on the SHORTEST panel (210px mobile) — at 82px/54% they
     overlapped on every viewport under ~1150px */
  position:absolute;left:50%;top:6%;transform:translateX(-50%);
  width:80%;height:50%;border-radius:12px;overflow:hidden;
  background:
    radial-gradient(90% 80% at 70% 20%, rgba(75,166,241,.35) 0%, transparent 60%),
    linear-gradient(135deg,#31394B 0%,#232936 55%,#1A1F2A 100%);
  box-shadow:0 14px 30px -14px rgba(23,32,64,.45);
  display:flex;align-items:flex-end;justify-content:center;padding-bottom:13px;
}
.ly-cap{
  display:flex;align-items:baseline;gap:5px;
  font-family:var(--sans-indic);font-weight:700;font-size:13.5px;color:#fff;
  background:rgba(8,10,14,.48);border-radius:7px;padding:5px 9px;
}
.ly-sel{position:relative;display:inline-block}
/* AE-style selection: hairline blue box + 4 corner handles */
.ly-selbox{position:absolute;inset:-3px -4px;border:1px solid var(--bento-blue);border-radius:2px;pointer-events:none}
.ly-selbox i{position:absolute;width:4px;height:4px;background:#fff;border:1px solid var(--bento-blue)}
.ly-selbox i:nth-child(1){left:-2px;top:-2px}
.ly-selbox i:nth-child(2){right:-2px;top:-2px}
.ly-selbox i:nth-child(3){left:-2px;bottom:-2px}
.ly-selbox i:nth-child(4){right:-2px;bottom:-2px}
/* the double-click selection highlight that sweeps the word */
.ly-sweep{
  position:absolute;inset:-1px -2px;background:color-mix(in srgb, var(--bento-blue) 32%, transparent);border-radius:2px;
  transform:scaleX(0);transform-origin:left center;opacity:0;
}
/* the two word states stacked: corrected in flow, typo painted on top */
.ly-word--fix{opacity:1}
.ly-word--typo{position:absolute;left:0;top:0;opacity:0}
.ly-squig{position:absolute;left:0;right:8px;bottom:-3px;height:4px;opacity:0}
.ly-squig path{stroke:#FF5A5A;stroke-width:1.6}
.ly-caret{position:absolute;right:-3px;top:8%;height:84%;width:1.5px}
.ly-caret i{position:absolute;inset:0;border-radius:1px;background:#fff;display:block}
/* mini timeline: the SAME captions as layer rows */
.ly-rows{position:absolute;left:50%;bottom:8%;transform:translateX(-50%);width:80%;display:flex;flex-direction:column;gap:4px}
.ly-row{
  display:flex;align-items:center;gap:7px;height:22px;padding:0 9px;
  background:#fff;border-radius:8px;border:1px solid rgba(15,17,21,.05);
  box-shadow:0 5px 14px -8px rgba(23,32,64,.18);
}
.ly-row--sel{border-color:color-mix(in srgb, var(--bento-blue) 55%, transparent);box-shadow:0 6px 16px -8px rgba(18,134,230,.35);background:linear-gradient(180deg,#F4F9FF 0%,#EAF3FF 100%)}
.ly-t{
  flex:0 0 auto;width:13px;height:13px;border-radius:4px;display:grid;place-items:center;
  background:#E8ECF2;color:#5A6270;font-family:var(--sans);font-weight:800;font-size:8.5px;line-height:1;
}
.ly-row--sel .ly-t{background:var(--bento-blue);color:#fff}
.ly-name{font-family:var(--sans-indic);font-weight:600;font-size:10px;color:#3A3F49;white-space:nowrap;overflow:hidden}
.ly-row--sel .ly-name{color:var(--brand-blue-ink)}
.ly-clip{flex:1 1 auto;height:7px;border-radius:4px;background:#E4E8EF;margin-left:2px;position:relative}
.ly-clip::after{content:"";position:absolute;left:0;top:0;bottom:0;width:68%;border-radius:4px;background:linear-gradient(90deg,var(--blue-bright),var(--brand-blue))}
.ly-c2::after{width:46%;background:#CBD4E0}
.ly-c3::after{width:57%;background:#CBD4E0}
/* -- the 5.4s edit loop ------------------------------------------------- */
@keyframes lyTypo{0%,32%{opacity:1}34%,100%{opacity:0}}
@keyframes lyFix{0%,32%{opacity:0}34%,100%{opacity:1}}
@keyframes lySquig{0%,31%{opacity:1}37%,100%{opacity:0}}
@keyframes lySweep{
  0%,19%{transform:scaleX(0);opacity:1}
  25%,32%{transform:scaleX(1);opacity:1}
  38%,100%{transform:scaleX(1);opacity:0}
}
@keyframes lyGate{0%,35%{opacity:0}37%,100%{opacity:1}}
@keyframes lyBlink{0%,46%{opacity:1}52%,100%{opacity:0}}
.ly-word--typo{animation:lyTypo 5.4s linear infinite}
.ly-word--fix{animation:lyFix 5.4s linear infinite}
.ly-squig{animation:lySquig 5.4s linear infinite}
.ly-sweep{animation:lySweep 5.4s var(--ease) infinite}
.ly-caret{animation:lyGate 5.4s linear infinite}
.ly-caret i{animation:lyBlink .95s steps(1,end) infinite}

/* ---- 04 editor — a real After Effects window, Subtitl panel DOCKED inside.
        6s loop: cursor → click Generate → progress fills → styled caption
        pops on the comp + "42 captions added" + two T layer bars slide into
        the timeline → everything settles, fades, loops. Base styles = the
        finished rest state; every mover carries .ed-a. --------------------- */
.scene-editor{background:radial-gradient(110% 90% at 50% 32%, #F0F4FA 0%, #FAFBFD 70%, #FFFFFF 100%)}
.ae-win{
  position:absolute;left:50%;top:47%;transform:translate(-50%,-50%);
  width:88%;height:82%;border-radius:11px;overflow:hidden;
  background:#17181C;
  box-shadow:0 18px 40px -16px rgba(15,20,34,.5), 0 0 0 1px rgba(255,255,255,.06);
  display:flex;flex-direction:column;gap:3px;
}
.ae-tb{
  flex:0 0 auto;display:flex;align-items:center;gap:4.5px;
  height:20px;padding:0 9px;background:#26282E;
}
.ae-tb i{width:6px;height:6px;border-radius:50%}
.ae-tb i:nth-child(1){background:#FF5F57}
.ae-tb i:nth-child(2){background:#FEBC2E}
.ae-tb i:nth-child(3){background:#28C840}
.ae-tb span{
  flex:1;text-align:center;font-family:var(--sans);font-weight:600;font-size:7.5px;
  color:#9DA2AD;letter-spacing:.01em;padding-right:20px;   /* optically recentre past the lights */
  white-space:nowrap;overflow:hidden;
}
.ae-main{flex:1 1 auto;display:flex;gap:3px;min-height:0}
/* comp viewer: cinematic footage wash + the freshly generated caption */
.ae-comp{
  flex:1.5 1 0;background:
    radial-gradient(120% 90% at 28% 18%, rgba(75,166,241,.34) 0%, transparent 55%),
    radial-gradient(100% 90% at 85% 80%, rgba(131,92,255,.22) 0%, transparent 60%),
    linear-gradient(150deg,#2C3444 0%,#20242F 55%,#181C25 100%);
  display:flex;align-items:flex-end;justify-content:center;padding-bottom:11px;
  min-width:0;
}
.ae-capline{display:flex;gap:4px;align-items:baseline}
.ae-capw{
  font-family:var(--sans-indic);font-weight:700;font-size:11.5px;color:#fff;
  background:rgba(8,10,14,.5);border-radius:5px;padding:3px 6px;
}
.ae-capw--hi{background:#FFE23D;color:#141519}
/* the docked Subtitl panel — bright card inside the dark chrome */
.ae-dock{
  flex:1 1 0;background:#26282E;display:flex;flex-direction:column;gap:6px;
  padding:9px 9px 8px;min-width:0;
}
.ae-dhead{font-family:var(--body);font-weight:800;font-size:11px;color:#fff;letter-spacing:-.02em}
.ae-dhead span{color:var(--blue-bright)}
.ae-field{
  display:flex;align-items:center;justify-content:space-between;
  background:#1D1F24;border:1px solid #33363E;border-radius:6px;padding:4px 7px;
  font-family:var(--sans);font-size:7.5px;color:#9DA2AD;
}
.ae-field b{color:#E8EAEF;font-weight:700}
.ae-btnwrap{position:relative}
.ae-btn{
  display:grid;place-items:center;height:22px;border-radius:6px;
  background:linear-gradient(180deg,var(--blue-bright) 0%,var(--brand-blue) 100%);
  font-family:var(--sans);font-weight:700;font-size:8.5px;color:#fff;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.3), 0 4px 10px -4px rgba(18,134,230,.6);
}
/* click ring — pops at the moment of the click */
.ae-click{
  position:absolute;right:13px;bottom:1px;width:16px;height:16px;border-radius:50%;
  border:1.5px solid #fff;opacity:0;transform:scale(.4);pointer-events:none;
}
/* the cursor glides in from the comp, parks on the button */
/* opacity:0 rest (like .ae-click): without it, reduced-motion's animation:none
   left a stray cursor arrow permanently parked over the Generate button */
.ae-cursor{position:absolute;right:16px;bottom:-6px;width:11px;height:11px;z-index:2;opacity:0}
.ae-cursor path{fill:#fff;stroke:#1A1C22;stroke-width:1.4;stroke-linejoin:round}
.ae-prog{height:5px;border-radius:3px;background:#1D1F24;overflow:hidden}
.ae-prog i{
  display:block;height:100%;border-radius:3px;
  background:linear-gradient(90deg,var(--brand-blue),var(--blue-bright));
  transform:scaleX(1);transform-origin:left center;   /* rest: complete */
}
.ae-done{font-family:var(--sans);font-weight:600;font-size:7.5px;color:#54D58A}
/* timeline: V + A clips always there; the two T rows are Subtitl's output */
.ae-tl{flex:0 0 auto;display:flex;flex-direction:column;gap:3px;padding:3px 8px 7px;background:#1B1D22}
.ae-tlrow{display:flex;align-items:center;gap:5px}
.ae-chip{
  flex:0 0 auto;width:11px;height:11px;border-radius:3px;display:grid;place-items:center;
  font-family:var(--sans);font-weight:800;font-size:6.5px;line-height:1;color:#fff;
}
.ae-chip--v{background:#7D6BE8}
.ae-chip--a{background:#3FBF7F}
.ae-chip--t{background:var(--brand-blue)}
.ae-bar{display:block;height:8px;border-radius:3px}
.ae-bar--v{width:78%;background:linear-gradient(90deg,#8E7BF2,#6C59D8)}
.ae-bar--a{width:78%;background:linear-gradient(90deg,#49C98A,#2FA96C)}
.ae-bar--t{width:34%;background:linear-gradient(90deg,var(--blue-bright),var(--brand-blue))}
.ae-bar--t2{width:26%;margin-left:9%}
/* -- the 6s generate loop ----------------------------------------------- */
@keyframes aeCursor{
  0%{transform:translate(-46px,-34px);opacity:0}
  6%{opacity:1}
  15%,28%{transform:translate(0,0);opacity:1}
  36%,100%{transform:translate(0,0);opacity:0}
}
@keyframes aeClick{
  0%,15%{opacity:0;transform:scale(.4)}
  17%{opacity:.9;transform:scale(.55)}
  24%{opacity:0;transform:scale(1.25)}
  100%{opacity:0;transform:scale(1.25)}
}
@keyframes aeProg{
  0%,17%{transform:scaleX(0)}
  42%{transform:scaleX(1)}
  92%{transform:scaleX(1);opacity:1}
  97%,100%{transform:scaleX(1);opacity:0}
}
@keyframes aeCapA{
  0%,43%{opacity:0;transform:translateY(4px) scale(.92)}
  48%,92%{opacity:1;transform:none}
  97%,100%{opacity:0;transform:none}
}
@keyframes aeDone{0%,45%{opacity:0}50%,92%{opacity:1}97%,100%{opacity:0}}
@keyframes aeRowA{
  0%,49%{opacity:0;transform:translateX(-9px)}
  55%,92%{opacity:1;transform:none}
  97%,100%{opacity:0;transform:none}
}
.ae-cursor{animation:aeCursor 6s var(--ease) infinite}
.ae-click{animation:aeClick 6s linear infinite}
.ae-prog i{animation:aeProg 6s linear infinite}
.ae-capw{animation:aeCapA 6s var(--ease) infinite}
/* the --hi word is the same choreography ~0.24s later — a phase shift, not a
   second keyframe set; fill:backwards keeps it hidden through its delay */
.ae-capw--hi{animation-delay:.24s;animation-fill-mode:backwards}
.ae-done{animation:aeDone 6s linear infinite}
.ae-trow{animation:aeRowA 6s var(--ease) infinite}
.ae-trow--2{animation-delay:.36s;animation-fill-mode:backwards}

/* ---- 05 styles — three real caption styles fanned over a soft wash ------- */
.scene-styles{background:radial-gradient(115% 95% at 50% 30%, #FBF8F4 0%, #FDFCFB 60%, #FFFFFF 100%)}
.blob{position:absolute;border-radius:50%;filter:blur(38px);opacity:.4;pointer-events:none}
.blob-a{width:56%;aspect-ratio:1;left:2%;top:8%;background:#C9B8FF}
.blob-b{width:60%;aspect-ratio:1;right:-4%;top:30%;background:#FFD9AE}
.style-tag{position:absolute;font-family:var(--body);transform:rotate(var(--rot,0deg));white-space:nowrap;z-index:2}
.tag-glass{
  left:14%;top:20%;--rot:-6deg;--fdur:8s;
  font-weight:600;font-size:15px;color:#1D2026;text-shadow:0 1px 2px rgba(255,255,255,.55);
  background:rgba(255,255,255,.35);border:1px solid rgba(255,255,255,.7);
  border-radius:999px;padding:11px 20px;
  -webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
  box-shadow:0 10px 24px -10px rgba(23,32,64,.25);
}
.tag-bold{
  right:12%;top:42%;--rot:4deg;--fdur:9s;--fdel:1.2s;
  font-family:var(--sans);font-weight:800;font-size:16px;letter-spacing:.05em;
  color:#141519;background:#FFE23D;border-radius:11px;padding:11px 16px;
  box-shadow:0 10px 22px -10px rgba(120,90,0,.4);
}
.tag-serif{
  left:24%;bottom:20%;--rot:-2deg;--fdur:10s;--fdel:2.2s;
  font-family:Georgia,"Times New Roman",serif;font-style:italic;font-weight:500;
  font-size:15px;color:#23252B;background:#fff;border-radius:999px;padding:10px 19px;
  border:1px solid rgba(15,17,21,.07);
  box-shadow:0 10px 22px -10px rgba(23,32,64,.28);
}

/* ---- motion control: paused offscreen, running only under .bento-live ----
   Gated behind html.js (set by an inline <head> script in index.html): without
   JS the IntersectionObserver never adds .bento-live, and an ungated pause
   would freeze every scene at its 0% keyframe forever — empty editor window,
   the typo caption stuck mid-fix. No JS → animations simply run ungated. */
.js #why .od-strip,
.js #why .odo-ring,
.js #why .cpu-mover,
.js #why .cpu-wave rect,
.js #why .cpu-chipglow,
.js #why .cpu-led,
.js #why .is-float,
.js #why .ly-a,
.js #why .ed-a{
  animation-play-state:paused;
}
#why.bento-live .od-strip,
#why.bento-live .odo-ring,
#why.bento-live .cpu-mover,
#why.bento-live .cpu-wave rect,
#why.bento-live .cpu-chipglow,
#why.bento-live .cpu-led,
#why.bento-live .is-float,
#why.bento-live .ly-a,
#why.bento-live .ed-a{
  animation-play-state:running;
}
@media (prefers-reduced-motion:reduce){
  #why .od-strip,
  #why .odo-ring,
  #why .cpu-mover,
  #why .cpu-wave rect,
  #why .cpu-chipglow,
  #why .cpu-led,
  #why .is-float,
  #why .ly-a,
  #why .ed-a{
    animation:none !important;
  }
  /* base styles are the composed rests — odometer parks on 12,847, caret and
     caption fully visible, circuit static (traces + chip, movers hidden).
     Nothing extra needed. */
}

/* ---- responsive --------------------------------------------------------- */
@media (max-width:820px){
  .bento-grid{grid-template-columns:1fr;max-width:420px}
  .bento-card--hero{grid-column:span 1}
  .bento-panel{height:clamp(210px,58vw,258px)}
  .odo-pill{font-size:1.2rem;padding:12px 19px}
  .scene-odo .lc-hi{--sz:40px}
  .scene-odo .lc-te{--sz:38px}
  .ae-win{width:92%}
}

/* ==========================================================================
   PRICING
   ========================================================================== */
.price-wrap{max-width:480px;margin:var(--stack-gap) auto 0;position:relative}

/* ---- Founders strip (seats/urgency bar above the price grid) ------------ */
.founders-wrap{max-width:940px;margin:calc(var(--stack-gap)*.6) auto 0}
.founders-top{display:flex;justify-content:space-between;align-items:baseline;gap:12px;
  font-family:var(--sans);margin-bottom:10px}
.founders-pill{
  display:inline-flex;align-items:center;gap:8px;background:#fff;
  border:1px solid var(--line-strong);border-radius:999px;padding:6px 14px;
  font-weight:600;font-size:.82rem;color:var(--ink);
}
.fd-dot{width:7px;height:7px;border-radius:50%;background:var(--blue);flex:none;
  animation:fdPulse 2s infinite}
@keyframes fdPulse{
  0%{box-shadow:0 0 0 0 rgba(18,134,230,.45)}
  70%{box-shadow:0 0 0 7px rgba(18,134,230,0)}
  100%{box-shadow:0 0 0 0 rgba(18,134,230,0)}
}
.founders-count{font-size:.9rem;color:var(--ink-soft)}
.founders-count strong{color:var(--blue);font-weight:800;font-variant-numeric:tabular-nums}
/* offer-only state: no real claimed-count source yet, so the live count and the
   progress fill are not rendered (a bar frozen at "500 of 500 left" reads as a
   dead site). The launch-price line takes their place; flipping
   launchOffer.showCounter restores the counter and its animation untouched. */
/* ---- Pricing gate: never offer a plan the visitor already owns -----------
   Three states, driven by initPricingGate() from the real license-me
   response. Visibility of the OWNED/CHECKING blocks is toggled by the
   `hidden` ATTRIBUTE in JS, not by these rules: the global
   `[hidden]{display:none !important}` would beat any class selector here,
   so anything that must become visible has its attribute removed instead.
   These rules only handle hiding surfaces that have no `hidden` attribute
   of their own — an id+class selector, so it outranks .price-grid{display:grid}. */

/* checking — a signed-in visitor's plan is still loading */
#pricing.is-checking .price-grid,
#pricing.is-checking .founders-wrap,
#pricing.is-checking .price-terms{display:none}

/* owns MONTHLY — Lifetime is the only thing left to sell, so drop every
   Monthly surface: the mobile segmented control, the stacked Monthly face,
   and the desktop Monthly sibling card. */
#pricing.owns-monthly .price-seg,
#pricing.owns-monthly .pc-monthly,
#pricing.owns-monthly .price-card--monthly{display:none}
/* the grid was a 2-up; with one card left, centre it rather than leaving a hole */
#pricing.owns-monthly .price-grid{grid-template-columns:minmax(0,520px);justify-content:center}
/* "Own it forever. Or go monthly." — the second half is nonsense to someone
   who IS on monthly, so drop it and let the headline read "Own it forever." */
#pricing.owns-monthly .section-head h2 [data-monthly-plan]{display:none}

/* owns LIFETIME — nothing to sell at all */
#pricing.owns-lifetime .price-grid,
#pricing.owns-lifetime .founders-wrap,
#pricing.owns-lifetime .price-terms,
#pricing.owns-lifetime .price-seg{display:none}

.price-checking{
  display:flex;align-items:center;justify-content:center;gap:10px;
  margin:calc(var(--stack-gap)*.6) auto 0;color:var(--ink-faint);
  font-size:.95rem;font-weight:500;min-height:120px;
}
.pchk-dot{
  width:9px;height:9px;border-radius:50%;background:var(--brand-blue);flex:none;
  animation:pchkPulse 1.1s ease-in-out infinite;
}
@keyframes pchkPulse{0%,100%{opacity:.25;transform:scale(.8)}50%{opacity:1;transform:scale(1)}}
@media (prefers-reduced-motion:reduce){.pchk-dot{animation:none;opacity:.7}}

/* the "you already own Lifetime" panel — intentionally a real card, so the
   section never reads as a blank gap where the plans used to be */
.price-owned{
  max-width:560px;margin:var(--stack-gap) auto 0;text-align:center;
  padding:clamp(30px,4vw,44px) clamp(22px,4vw,40px);
  border-radius:var(--radius-card);border:1px solid var(--line);
  background:linear-gradient(180deg,#fff,#fbfcfe);
  box-shadow:var(--shadow-soft);
}
.price-owned .po-badge{
  display:grid;place-items:center;width:52px;height:52px;margin:0 auto 18px;
  border-radius:50%;background:var(--brand-blue);color:#fff;
  box-shadow:0 8px 22px rgba(18,134,230,.32);
}
.price-owned .po-badge svg{width:26px;height:26px}
.price-owned h3{
  font-family:var(--sans);font-weight:800;letter-spacing:-.02em;
  font-size:clamp(1.3rem,2.6vw,1.6rem);margin:0 0 10px;color:var(--ink);
}
.price-owned p{margin:0 0 22px;color:var(--ink-soft);font-size:1rem;line-height:1.55}
@media (max-width:600px){.price-owned{margin-top:calc(var(--stack-gap)*.8)}}

/* "Upgrade from Monthly" note on the Lifetime card (owns-monthly only) */
.ph-upgrade{
  margin:0 0 12px;font-family:var(--sans);font-weight:700;font-size:.82rem;
  letter-spacing:.01em;color:var(--blue-bright);
}

/* ---- Device selector (1-3) -----------------------------------------------
   Hidden in markup until CONFIG.deviceTiers.live; initDeviceTiers() reveals it.
   Boundaries DISABLE the button rather than silently ignoring the click — a
   control that looks live but does nothing reads as broken. */
/* Vertical rhythm through the head: price -> stepper -> rate -> founding line.
   The stepper used to have margin-top:0, so it read as tucked under the price
   rather than as its own control. The scale below is 16 / 12 / 12 / 20, which
   holds at every device count and in both currencies (the price and the label
   are both width-animated, so nothing reflows the column). */
.dev-sel{
  display:flex;align-items:center;gap:4px;margin:16px 0 12px;
  background:rgba(18,134,230,.07);border:1px solid rgba(18,134,230,.16);
  border-radius:var(--radius-pill);padding:4px;width:max-content;
}
.dev-btn{
  width:30px;height:30px;flex:none;display:grid;place-items:center;
  border:0;border-radius:50%;cursor:pointer;
  background:#fff;color:var(--brand-blue-ink);
  font-family:var(--sans);font-weight:800;font-size:1.05rem;line-height:1;
  box-shadow:0 1px 3px rgba(9,9,11,.12);
  transition:transform .14s var(--ease),box-shadow .2s var(--ease),opacity .18s var(--ease);
}
@media (hover:hover) and (pointer:fine){ .dev-btn:not([disabled]):hover{transform:translateY(-1px);box-shadow:0 4px 10px rgba(9,9,11,.16)} }
.dev-btn:not([disabled]):active{transform:translateY(1px) scale(.96)}
.dev-btn[disabled]{opacity:.32;cursor:default;box-shadow:none}
/* No min-width: the label is a roller that animates its own width, so a fixed
   floor would fight the transition and reintroduce the jump it exists to
   avoid. Padding keeps it off the +/- buttons at every label length. */
.dev-read{
  text-align:center;font-family:var(--body);font-size:.9rem;padding:0 10px;
  color:var(--ink-soft);font-variant-numeric:tabular-nums;white-space:nowrap;
}
.dev-read strong{color:var(--ink);font-weight:700}
.dev-extra{margin:0 0 12px;font-size:.84rem;color:var(--ink-faint);font-weight:500}

/* ---- Roller: 3D digit wheel + word roll ---------------------------------
   Used by the price, the stepper label and the "Use on N devices" bullet so
   the whole card moves as one mechanism. Purely presentational — the settled
   value is written by JS the instant a change is committed, never derived
   from a frame, so a wheel that never animates (hidden tab, reduced motion)
   still leaves the correct figure on screen. */
/* BASELINE. An inline-block with overflow:hidden takes its baseline from the
   bottom margin edge, not from its text — so a clipped wheel dropped inline
   next to ordinary words ("Use on 3 devices") sits a few px high, and the
   sentence visibly breaks. Every roller box therefore carries an in-flow,
   invisible ANCHOR glyph: the box then has a real text baseline and aligns
   with surrounding copy for free, at any font size, in any font. The anchor
   also gives the wheel its intrinsic width, so no 1ch guess is needed. */
.rl{display:inline-flex;align-items:baseline;white-space:nowrap}
.rl-c{white-space:pre}                            /* static glyph: ₹ $ , space */
.rl-d{                                            /* one digit wheel */
  position:relative;display:inline-block;vertical-align:baseline;
}
/* CLIPPING IS A SEPARATE LAYER, and it has to be. CSS gives an inline-block
   whose overflow is not `visible` a baseline at its BOTTOM MARGIN EDGE — the
   content is ignored — so putting overflow:hidden on the box the text flows
   against is what pushed "3" above "devices" and both above "Use on". The
   outer box therefore stays overflow:visible and takes its baseline from the
   in-flow anchor; this absolutely-positioned layer does the clipping and, being
   out of flow, contributes no baseline of its own. */
.rl-clip{position:absolute;inset:0;overflow:hidden}
.rl-anchor{visibility:hidden;pointer-events:none}
/* NOTE: no `perspective` property here. An element that establishes a 3D
   rendering context cannot reliably clip its 3D-transformed descendants, so
   the wheel's faces leaked out and stacked into a blur. The perspective now
   lives INSIDE the ring's own transform, leaving this box a plain 2D clip. */
.rl-ring{
  position:absolute;inset:0;transform-style:preserve-3d;
  transition:transform var(--rl-dur,420ms) cubic-bezier(.2,.85,.25,1);
  transition-delay:var(--rl-delay,0ms);
}
/* Screen readers and any text extraction get the real formatted value once;
   the ten faces of every wheel are decorative and hidden from them. Without
   this the price reads as "₹0123456789,0123456789…". */
.rl-sr{position:absolute!important;width:1px;height:1px;margin:-1px;padding:0;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
/* Faces inherit the cell's line-height and fill it exactly, so each face's own
   text baseline lands where the anchor's does — the glyph does not drift as it
   rotates through the front. (place-items:center would centre the glyph box
   instead, which is what threw the digits off the baseline.) */
.rl-f{position:absolute;inset:0;text-align:center;backface-visibility:hidden}
/* Depth by MASK, not by a colour overlay. A translucent dark gradient has to
   pick a colour, and whichever it picks shows as a visible rectangle on one of
   the two cards (the price sits on a dark panel and on a light one). Masking
   fades the glyph itself as it rolls away from the front of the wheel, so the
   recede reads identically on any background and no box can appear.
   The flat middle band keeps the settled digit at full opacity. */
.rl-clip{
  -webkit-mask-image:linear-gradient(180deg,transparent 0,#000 11%,#000 89%,transparent 100%);
          mask-image:linear-gradient(180deg,transparent 0,#000 11%,#000 89%,transparent 100%);
}
/* A whisper of shading on the faces themselves adds the turn-away without
   tinting the cell: only the rolling faces carry it, never the front one. */
.rl-f{transition:opacity 0s}
/* Word roll — "device" <-> "devices". The container animates its own width so
   the pluralisation does not snap the layout sideways. */
.rl-w{
  display:inline-block;position:relative;vertical-align:baseline;
  transition:width var(--rl-wdur,300ms) cubic-bezier(.2,.85,.25,1);
}
.rl-w-strip{
  position:absolute;left:0;top:0;
  transition:transform var(--rl-wdur,300ms) cubic-bezier(.2,.85,.25,1);
}
/* MUST be block. As inline spans the two words laid out side by side on one
   line instead of stacking, so the strip was one line tall and "rolling" it
   slid the pair sideways-ish rather than turning one word over the other. */
.rl-w-i{display:block;white-space:nowrap}
/* Whole-line roll (the founding-member sentence). Block-level, so no baseline
   to preserve; the height animates because the line can wrap to two. */
.rl-line{position:relative;overflow:visible}
.rl-line>.rl-clip{position:absolute;inset:0;overflow:hidden}
.rl-line-strip{
  position:absolute;left:0;top:0;width:100%;
  transition:transform var(--rl-wdur,300ms) cubic-bezier(.2,.85,.25,1);
}
.rl-line-i{display:block;width:100%}
.rl-line{transition:height var(--rl-wdur,300ms) cubic-bezier(.2,.85,.25,1)}
@media (prefers-reduced-motion:reduce){
  .rl-line,.rl-line-strip{transition:none!important}
}
/* The anchor is the in-flow baseline/size box, so it must NOT be block —
   it has to sit on the text line like the word it stands in for. */
.rl-w>.rl-anchor{display:inline}
/* Someone who asked for less motion gets the value, not the mechanism. */
@media (prefers-reduced-motion:reduce){
  .rl-ring,.rl-w,.rl-w-strip{transition:none!important}
  /* no wheel to recede into, so drop the fade and show a plain flat digit */
  .rl-clip{-webkit-mask-image:none;mask-image:none}
}
/* on the dark featured card the light-on-light treatment inverts */
.price-card--featured .dev-sel{background:rgba(255,255,255,.08);border-color:rgba(255,255,255,.16)}
.price-card--featured .dev-read{color:rgba(255,255,255,.72)}
.price-card--featured .dev-read strong{color:#fff}
.price-card--featured .dev-extra{color:rgba(255,255,255,.55)}
.price-card--featured.is-monthly .dev-sel{background:rgba(18,134,230,.07);border-color:rgba(18,134,230,.16)}
.price-card--featured.is-monthly .dev-read{color:var(--ink-soft)}
.price-card--featured.is-monthly .dev-read strong{color:var(--ink)}
.price-card--featured.is-monthly .dev-extra{color:var(--ink-faint)}
@media (max-width:600px){ .dev-sel{width:100%;justify-content:space-between} .dev-read{flex:1} }

/* billing-terms block under the pricing grid — plain, scannable, no marketing */
.price-terms{
  max-width:940px;margin:calc(var(--stack-gap)*.7) auto 0;
  border:1px solid var(--line);border-radius:var(--radius-card);
  background:var(--bg-soft);padding:22px 24px 18px;
}
.price-terms h3{font-family:var(--sans);font-weight:700;font-size:1.02rem;margin:0 0 12px;color:var(--ink)}
.price-terms ul{margin:0;padding-inline-start:20px}
.price-terms li{color:var(--ink-soft);font-size:.92rem;line-height:1.6;margin-bottom:8px}
.price-terms strong{color:var(--ink);font-weight:650}
.price-terms a{color:var(--brand-blue-ink);text-decoration:underline;text-underline-offset:2px}
@media (max-width:600px){.price-terms{padding:18px 18px 14px}.price-terms li{font-size:.88rem}}

/* test-mode banner — sits directly above the pricing cards so nobody can click
   Buy without having seen it. Calm amber: noticeable, not alarming. */
.price-testnote{
  max-width:940px;margin:0 auto calc(var(--stack-gap)*.5);
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;justify-content:center;
  padding:13px 18px;border:1px solid #E8D8A8;border-radius:12px;background:#FDF8EC;
  color:#7A5B12;font-size:.95rem;line-height:1.5;text-align:center;
}
/* Author `display:flex` above and the UA `[hidden]{display:none}` rule are
   equal specificity, so author wins the cascade and the JS `hidden` attribute
   (initTestNotice in app.js) silently failed to hide this — the "sandbox,
   no card charged" banner would have stayed visible on a LIVE, real-money
   page. Caught in go-live verification, 2026-07-25. */
.price-testnote[hidden]{display:none}
.price-testnote strong{color:#5E4409;font-weight:650}
.ptn-chip{
  flex:none;font-family:var(--sans);font-weight:700;font-size:.72rem;
  letter-spacing:.06em;text-transform:uppercase;
  padding:4px 10px;border-radius:var(--radius-pill);background:#7A5B12;color:#fff;
}
@media (max-width:600px){.price-testnote{font-size:.88rem;padding:12px 14px}}

/* checkout status line — order/payment/verification outcomes. Sits between the
   price grid and the billing terms so a failure is read where the click was. */
/* Checkout status/error. Deliberately understated — this block usually
   carries bad news, so it should settle in, not announce itself. */
@keyframes msgIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
.price-msg{
  animation:msgIn .2s var(--ease-soft);     /* no fill — see .plan-state in auth.css */
  max-width:940px;margin:18px auto 0;padding:13px 16px;
  border:1px solid var(--line);border-radius:12px;background:var(--bg-soft);
  font-size:.92rem;line-height:1.55;color:var(--ink-soft);text-align:center;
}
.price-msg.is-err{background:rgba(192,57,43,.06);border-color:rgba(192,57,43,.28);color:#a5342a}
.price-msg.is-ok{background:rgba(13,138,85,.06);border-color:rgba(13,138,85,.28);color:#0d6f47}
.price-msg.is-info{background:var(--blue-wash);border-color:rgba(18,134,230,.22);color:var(--blue)}
/* buy button mid-payment: <a> ignores [disabled], so block the pointer instead.
   The clicked button keeps full contrast (it carries the status text); the
   other buttons dim to show they're unavailable until this payment settles. */
.btn.is-busy{pointer-events:none;cursor:default}
.btn.is-busy:not([aria-busy="true"]){opacity:.5}
.btn[aria-busy="true"]{opacity:.92}

.founders-offer{display:none;font-size:.9rem;font-weight:600;color:var(--blue)}
.founders--offer-only .founders-count,
.founders--offer-only .founders-bar,
.founders--offer-only .founders-claim{display:none}
.founders--offer-only .founders-offer{display:inline}
.founders-bar{height:10px;border-radius:999px;background:var(--blue-wash);overflow:hidden}
.founders-fill{
  position:relative;display:block;height:100%;border-radius:999px;
  background:linear-gradient(90deg,var(--brand-blue),var(--blue-bright));width:100%;
  transition:width 1.1s var(--ease);
}
.founders-tip{
  position:absolute;right:-7px;top:50%;width:14px;height:14px;border-radius:50%;
  transform:translateY(-50%);
  background:radial-gradient(circle,rgba(75,166,241,.9) 0%,transparent 70%);
  animation:fdTip 2.2s ease-in-out infinite;
}
@keyframes fdTip{0%,100%{transform:translateY(-50%) scale(1);opacity:.7}50%{transform:translateY(-50%) scale(1.35);opacity:1}}
.founders-claim{margin-top:8px;text-align:center;font-size:.85rem;color:var(--ink-soft)}
@media (prefers-reduced-motion:reduce){
  .fd-dot{animation:none}
  .founders-tip{animation:none}
}
@media (max-width:760px){.founders-top{flex-wrap:wrap}}

/* ---- Price grid ----------------------------------------------------------
   desktop: 1.45fr/1fr, capped so the featured card doesn't sprawl; mobile
   collapses to one column and hides the ghost monthly card in favour of the
   in-card toggle. */
.price-grid{
  max-width:940px;margin:var(--stack-gap) auto 0;display:grid;
  grid-template-columns:1.45fr 1fr;gap:clamp(16px,2vw,24px);align-items:start;position:relative;
}
/* Lifetime-only launch: the Monthly sibling card is hidden via [data-monthly-plan]
   (see initFlags in app.js) — collapse to one centered column instead of leaving
   a gap where it used to sit. Reverts automatically once Monthly is shown again. */
.price-grid:has(.price-card--monthly[hidden]){grid-template-columns:1fr;max-width:520px}
/* wraps .price-tabs + .price-card--featured so the grid's row-gap can never
   open a seam between them (they used to be separate grid children; on
   mobile the row-gap detached the welded tabs from the card top). On desktop
   (≥821px) this is just a pass-through column holding the featured card. */
.price-stagecol{display:flex;flex-direction:column;min-width:0}
.price-card{
  position:relative;border-radius:var(--radius-card,24px);padding:clamp(26px,4vw,38px);
  overflow:hidden;
}

#pricing{position:relative}

/* ---- Featured (Lifetime) card — dark two-zone "membership card" object --
   outer navy zone + an elevated inner header panel (.price-head), matching
   the reference: tight card padding hugs the panel, checklist sits on the
   darker outer zone below it with no divider lines. */
.price-card--featured{
  border:1px solid transparent;
  border-radius:32px;
  padding:10px 10px 26px;
  /* WHITE glow, not a dark drop — VERY soft: no hard ring, just two wide
     feathered luminous blooms (reads as backlight against the dotted page) */
  box-shadow:
    0 2px 0 rgba(255,255,255,.55),
    0 22px 60px rgba(182,197,235,.38),
    0 60px 140px rgba(188,203,242,.4);
  transition:box-shadow .6s var(--ease-soft);
  background:
    linear-gradient(180deg,#1E2942 0%,#131A2B 55%,#101522 100%) padding-box,
    linear-gradient(180deg, rgba(143,176,255,.85) 0%, rgba(143,176,255,.25) 18%, rgba(255,255,255,.07) 45%, rgba(255,255,255,.05) 100%) border-box;
}
/* subtle top glow — the gradient border rim now carries the top-edge highlight */
.price-card--featured::before{
  content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:radial-gradient(55% 30% at 50% 0%, rgba(75,166,241,.16), transparent 70%);
  transition:opacity .5s var(--ease-soft);
}
.price-card--featured.is-monthly::before{opacity:0}
/* light-card overlay used by the mobile monthly toggle state */
.price-card--featured::after{
  content:"";position:absolute;inset:-1px;background:#fff;opacity:0;
  border-radius:32px;               /* == the card's outer radius exactly */
  border:1px solid rgba(17,17,19,.12);   /* the monthly hairline lives ON the
      overlay: covering the border box kills the navy corner crescents the
      old padding-box overlay left showing, and the rim fades WITH the card */
  transition:opacity .6s var(--ease-soft);pointer-events:none;z-index:0;
}
.price-card--featured.is-monthly::after{opacity:1}
.price-card--featured > *{position:relative;z-index:1}
/* mobile monthly flip: NO background flip here — the ::after overlay does
   ALL the lightening incl. its own hairline rim (a bg flip would snap
   instantly beneath the fade). Only the shadow swaps. */
.price-card--featured.is-monthly{
  box-shadow:0 16px 44px rgba(20,26,52,.10);
}

/* elevated inner header panel */
.price-head{
  border-radius:22px;padding:22px 22px 20px;
  transition:background .35s var(--ease),border-color .35s var(--ease),box-shadow .35s var(--ease);
}
.price-card--featured .price-head{
  /* top light wash layered over the dark well gradient — wash first, well second */
  background:
    linear-gradient(180deg, rgba(122,162,255,.12) 0%, rgba(122,162,255,.035) 26%, transparent 45%),
    linear-gradient(180deg,#121A2E 0%,#0E1424 100%);
  border:1px solid rgba(255,255,255,.075);
  box-shadow:none;
}
.price-card--featured.is-monthly .price-head{
  background:#F6F8FB;border-color:var(--line);box-shadow:none;
}

.ph-row{display:flex;align-items:center;justify-content:space-between;gap:12px}
/* lifetime desc was removed — give the plan row room above the price */
.pc-life .ph-row{margin-bottom:16px}
.price-plan{font-family:var(--sans);font-weight:800;font-size:1.35rem;letter-spacing:-.01em;margin:0;transition:color .45s var(--ease-soft)}
.price-card--featured .price-plan{color:#fff}
.price-card--featured.is-monthly .price-plan{color:var(--ink)}

.price-flag{
  flex:none;background:linear-gradient(90deg,var(--blue-bright),var(--brand-blue));color:#FFE9C4;font-family:var(--sans);font-weight:700;
  font-size:.74rem;letter-spacing:.02em;padding:5px 13px;text-shadow:0 1px 6px rgba(255,200,80,.25);
  border-radius:999px;box-shadow:0 4px 14px rgba(18,134,230,.35);
  transition:opacity .45s var(--ease-soft);
}
.price-card--featured.is-monthly .price-flag{opacity:0}

.ph-desc{font-family:var(--body);font-size:.92rem;line-height:1.5;margin:8px 0 18px;max-width:30ch;transition:color .45s var(--ease-soft)}
.price-card--featured .ph-desc{color:rgba(226,234,250,.62);margin:10px 0 30px}
.price-card--monthly .ph-desc{color:var(--ink-soft)}
.price-card--featured.is-monthly .ph-desc{color:var(--ink-soft);margin:8px 0 18px}

.price-amount{display:flex;align-items:flex-end;gap:8px}
.price-amount .now{font-family:var(--sans);font-weight:800;letter-spacing:-.03em;line-height:.95;font-variant-numeric:tabular-nums;transition:color .45s var(--ease-soft)}
.price-card--featured .price-amount .now{color:#fff;font-size:clamp(2.6rem,5vw,3.3rem)}
.price-card--monthly .price-amount .now{color:var(--ink);font-size:clamp(2rem,3.6vw,2.4rem)}
.price-card--featured.is-monthly .price-amount .now{color:var(--ink)}
.price-amount .per{font-family:var(--body);font-size:.95rem;margin-inline-start:8px;margin-bottom:.25em;transition:color .45s var(--ease-soft)}
.price-card--featured .price-amount .per{color:rgba(255,255,255,.5)}
.price-card--monthly .price-amount .per{color:var(--ink-soft)}
.price-card--featured.is-monthly .price-amount .per{color:var(--ink-soft)}

.ph-sub{font-family:var(--body);font-size:.85rem;margin:6px 0 20px;transition:color .45s var(--ease-soft)}
.price-card--featured .ph-sub{color:rgba(240,245,255,.78);margin:6px 0 22px}
.price-card--monthly .ph-sub{color:var(--ink-faint)}
.price-card--featured.is-monthly .ph-sub{color:var(--ink-soft);margin:6px 0 18px}
.ph-sub .was{text-decoration:line-through;transition:color .45s var(--ease-soft)}
.price-card--featured .ph-sub .was{color:rgba(255,255,255,.4)}
.price-card--monthly .ph-sub .was{color:var(--ink-faint)}
.price-card--featured.is-monthly .ph-sub .was{color:var(--ink-faint)}

/* checklist — sits on the outer zone below the panel, no divider lines */
.price-list{display:grid;gap:13px;padding:22px 16px 0}
.price-list li{display:flex;gap:11px;align-items:flex-start;font-size:.95rem;transition:color .45s var(--ease-soft)}
.price-list .li-t{flex:1 1 auto;min-width:0}
.price-card--featured .price-list li{color:rgba(233,240,252,.88)}
.price-card--monthly .price-list li{color:var(--ink)}
.price-card--featured.is-monthly .price-list li{color:var(--ink)}
.price-card--featured .price-list{gap:15px;padding:24px 18px 4px}
.price-list .ck{
  flex:none;width:20px;height:20px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;margin-top:1px;transition:background .45s var(--ease-soft);
}
.price-card--featured .price-list .ck{background:rgba(255,255,255,.07)}
.price-card--monthly .price-list .ck{background:rgba(18,134,230,.10)}
.price-card--featured.is-monthly .price-list .ck{background:rgba(18,134,230,.10)}
.price-list .ck svg{width:12px;height:12px;stroke-width:2.6;fill:none;transition:stroke .45s var(--ease-soft)}
.price-card--featured .price-list .ck svg{stroke:#C7D3EC}
.price-card--monthly .price-list .ck svg{stroke:var(--brand-blue)}
.price-card--featured.is-monthly .price-list .ck svg{stroke:var(--brand-blue)}
.founders-claim{margin-top:8px}

/* ---- Ghost monthly card (desktop only, quiet) — same two-zone anatomy,
   simpler/less colorful: neutral ink card, dark neutral CTA. -------------- */
.price-card--monthly{
  background:#fff;border:1px solid var(--line-strong);border-radius:32px;
  padding:10px 10px 22px;box-shadow:none;
}
.price-card--monthly .price-head{background:#F6F8FB;border:1px solid var(--line)}
.price-card--monthly .price-plan{color:var(--ink)}

/* ---- CTAs ------------------------------------------------------------- */
/* the LIVE site's button components (.btn-primary for Lifetime, .btn-rich for
   Monthly) — exactly as shipped, no custom variant. Only the card-scoped
   width rule lives here, mirroring the live `.price-header .btn` rule on the
   new .price-head panel. */
.price-head .btn{width:100%;margin-top:18px;position:relative}

/* ---- Mobile plan tabs (Chrome-style fused tabs) ---------------------------
   lives OUTSIDE the featured card, directly above .price-card--featured;
   hidden on desktop where both cards show (display flips on in the ≤820px
   media block below, mirroring the old .price-toggle pattern). --tabfill
   drives both the active-tab fill and its concave corner cutouts so the
   fused-with-the-card colour only lives in one place. */
/* ═══ SEGMENTED CONTROL (mobile plan switcher) ════════════════════════════
   Deliberately DETACHED from the card: it sits above with a gap, so there is
   no shared edge to match — the class of bug that plagued the welded tabs is
   structurally impossible here. A white thumb slides between the two halves
   on the site's spring curve; labels just crossfade their ink. */
.price-seg{
  display:none;
  position:relative;
  width:100%;max-width:320px;margin:0 auto 16px;
  padding:4px;
  background:rgba(17,17,19,.055);
  border:1px solid var(--line);
  border-radius:999px;
}
.seg-thumb{
  position:absolute;top:4px;left:4px;bottom:4px;
  width:calc(50% - 4px);
  background:#fff;border-radius:999px;
  box-shadow:0 2px 8px rgba(17,17,19,.13), 0 1px 2px rgba(17,17,19,.07);
  transition:transform .55s var(--ease-spring);
}
.price-seg.is-monthly .seg-thumb{transform:translateX(100%)}
.seg-opt{
  position:relative;z-index:1;flex:1 1 0;min-width:0;
  appearance:none;border:0;background:transparent;cursor:pointer;
  padding:11px 10px;border-radius:999px;
  font-family:var(--sans);font-weight:700;font-size:.95rem;
  color:var(--ink-soft);
  transition:color .3s var(--ease);
}
.seg-opt.is-on{color:var(--ink)}
.seg-opt:focus-visible{outline:2px solid var(--blue);outline-offset:2px}
@media (prefers-reduced-motion:reduce){ .seg-thumb{transition:none} }

/* ---- state-swap (lifetime <-> monthly content crossfade) ----------------
   The two states crossfade SIMULTANEOUSLY in the grid stack (JS reveals the
   incoming block immediately, no wait-for-out), while .pc-stack's height
   transitions in parallel — one continuous, smooth resize. Inside the
   incoming block the elements CASCADE in (staggered elIn) so the content
   feels rebuilt piece by piece rather than swapped as a slab. */
.pc-stack{display:grid;overflow:hidden;transition:height .5s var(--ease-soft)}
.pc-state{
  grid-area:1/1;min-width:0;
  /* start, NOT the default stretch: stretched grid items report the TRACK
     height when measured, so the FLIP target equalled the frozen height and
     the transition never ran — the 650ms fallback then SNAPPED the card
     short. With natural heights the glide actually animates. */
  align-self:start;
}
.pc-state.pc-out{animation:pcOut .28s var(--ease) both}
.pc-state.pc-in{animation:pcIn .45s var(--ease-soft) both}
@keyframes pcOut{from{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(-6px)}}
@keyframes pcIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
/* staggered element entrance inside the incoming state */
@keyframes elIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
.pc-state.pc-in .ph-row{animation:elIn .45s var(--ease-soft) .04s both}
.pc-state.pc-in .ph-desc{animation:elIn .45s var(--ease-soft) .09s both}
.pc-state.pc-in .price-amount{animation:elIn .45s var(--ease-soft) .13s both}
.pc-state.pc-in .ph-sub{animation:elIn .45s var(--ease-soft) .17s both}
.pc-state.pc-in .btn{animation:elIn .45s var(--ease-soft) .22s both}
.pc-state.pc-in .price-list li{animation:elIn .4s var(--ease-soft) both}
.pc-state.pc-in .price-list li:nth-child(1){animation-delay:.28s}
.pc-state.pc-in .price-list li:nth-child(2){animation-delay:.33s}
.pc-state.pc-in .price-list li:nth-child(3){animation-delay:.38s}
.pc-state.pc-in .price-list li:nth-child(4){animation-delay:.43s}
.pc-state.pc-in .price-list li:nth-child(5){animation-delay:.48s}
.pc-state.pc-in .price-list li:nth-child(6){animation-delay:.53s}
@media (prefers-reduced-motion:reduce){
  .pc-state.pc-out,.pc-state.pc-in{animation-duration:.01s}
  .pc-state.pc-in .ph-row,.pc-state.pc-in .ph-desc,.pc-state.pc-in .price-amount,
  .pc-state.pc-in .ph-sub,.pc-state.pc-in .btn,.pc-state.pc-in .price-list li{
    animation:none;
  }
}

/* ---- purchase particles (JS sets --x/--y/--dx/--dy/--s/--c inline) ------ */
.price-particles{position:absolute;inset:0;pointer-events:none;z-index:3;overflow:visible}
.price-particles .pp{
  position:absolute;width:6px;height:6px;border-radius:50%;background:var(--c,var(--blue-bright));
  left:var(--x);top:var(--y);animation:ppFly .65s cubic-bezier(.16,1,.3,1) forwards;
}
@keyframes ppFly{
  from{transform:translate(0,0) scale(var(--s,1));opacity:1}
  to{transform:translate(var(--dx),var(--dy)) scale(.2);opacity:0}
}

@media (max-width:820px){
  .price-grid{grid-template-columns:1fr}
  .price-card--monthly{display:none}
  .price-seg{display:flex}
  .price-card--featured{padding:8px 8px 20px}
  .price-card--featured .price-head{padding:18px}
}
/* Same cascade trap as .price-testnote above: this media query's
   `display:flex` is equal-specificity, same-origin as the UA `[hidden]` rule,
   so on mobile the JS `hidden` attribute (Lifetime-only launch, see
   initFlags/[data-monthly-plan] in app.js) silently lost. Higher-specificity
   attribute selector below always wins instead. */
.price-seg[hidden]{display:none}

/* small live-style label on the scrolly preview */
.stage-preview .pv-style{position:absolute;top:12px;left:12px;z-index:3;font-family:var(--sans);font-weight:600;
  font-size:11.5px;color:#fff;padding:5px 10px;border-radius:var(--radius-pill);
  background:rgba(10,10,20,.5);backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,.16)}

/* ==========================================================================
   TESTIMONIALS
   ========================================================================== */
.tst-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-top:var(--stack-gap);max-width:1000px;margin-inline:auto}
.tst-card{padding:24px}
.tst-card p{font-size:1rem;color:var(--ink);margin-bottom:18px}
.tst-who{display:flex;align-items:center;gap:12px}
.tst-who .av{width:40px;height:40px;border-radius:50%;background:var(--line);flex:none}
.tst-who .nm{font-family:var(--sans);font-weight:600;font-size:.92rem}
.tst-who .hd{font-size:.82rem;color:var(--ink-faint)}
@media (max-width:820px){.tst-grid{grid-template-columns:1fr;max-width:460px}}

/* ==========================================================================
   CREATOR STORY
   ========================================================================== */
.story{display:grid;grid-template-columns:.85fr 1.15fr;gap:clamp(20px,3vw,40px);align-items:center;
  max-width:960px;margin-inline:auto}
/* ---- founder card: light-rethemed 3D-tilt photo card (React-Bits ProfileCard
   ported to vanilla; see founder-card.js). Pointer-driven vars (--pointer-*,
   --rotate-*, --from-*) set by the JS; base values below are the reduced-motion
   / no-JS resting pose (flat, glare hidden) and read as a finished card. ---- */
.founder-card-wrap{
  --pointer-x:50%;--pointer-y:50%;--from-left:.5;--from-top:.5;--from-center:0;
  --rotate-x:0deg;--rotate-y:0deg;
  position:relative;perspective:900px;touch-action:pan-y;
}
.fc-behind{
  position:absolute;inset:-8% -8% -4%;z-index:0;pointer-events:none;border-radius:inherit;
  background:radial-gradient(circle at var(--pointer-x) var(--pointer-y),
    rgba(90,160,255,.40) 0%, transparent 55%);
  filter:blur(38px);opacity:0;transition:opacity .35s var(--ease);
}
.founder-card-wrap:hover .fc-behind{opacity:.75}
.founder-card{
  position:relative;z-index:1;width:100%;aspect-ratio:4/5;border-radius:var(--radius-card);
  overflow:hidden;transform-style:preserve-3d;
  /* the source maps --rotate-y→rotateX and --rotate-x→rotateY; kept as-is */
  transform:rotateX(var(--rotate-y)) rotateY(var(--rotate-x));
  transition:transform .5s var(--ease);
  background:radial-gradient(120% 95% at 50% 26%,#DCEBFF 0%,#E9F2FF 44%,#F6FAFF 78%,#fff 100%);
  border:1px solid rgba(15,17,21,.06);
  box-shadow:0 22px 48px -20px rgba(17,20,28,.20), 0 3px 10px -4px rgba(17,20,28,.06);
}
.founder-card.active{transition:none}   /* live pointer tilt — no lag while active */
/* the founder photo: a bottom-anchored (ideally transparent-PNG) cutout, with a
   whisper of parallax from the pointer. Falls back gracefully if absent. */
.fc-photo{position:absolute;inset:0;z-index:2;border-radius:inherit;overflow:hidden}
/* cover-fill the 4:5 card, framed on the face; the scale gives overscan so the
   pointer parallax never exposes the card edge. Works with a normal photo (the
   founder's light-background shot blends into the light card). */
.fc-photo img{
  position:absolute;inset:0;width:100%;height:100%;display:block;
  object-fit:cover;object-position:50% 22%;
  transform:scale(1.06)
            translate(calc((var(--from-left) - .5) * -2.2%), calc((var(--from-top) - .5) * -2.2%));
  transition:transform .18s ease-out;will-change:transform;
}
/* soft cursor glare — the only survivor of the source's holographic stack */
.fc-glare{
  position:absolute;inset:0;z-index:3;pointer-events:none;border-radius:inherit;
  background:radial-gradient(farthest-corner circle at var(--pointer-x) var(--pointer-y),
    rgba(255,255,255,.60) 6%, rgba(175,208,255,.22) 38%, transparent 70%);
  mix-blend-mode:soft-light;opacity:0;transition:opacity .3s var(--ease);
}
.founder-card:hover .fc-glare,.founder-card.active .fc-glare{opacity:1}
/* name + title, over a light bottom scrim so it reads on the photo */
.fc-caption{
  position:absolute;left:0;right:0;bottom:0;z-index:4;pointer-events:none;border-radius:inherit;
  padding:34px 18px 18px;text-align:center;
  background:linear-gradient(to top,#fff 16%,rgba(255,255,255,.86) 46%,transparent 100%);
}
.fc-caption .fc-name{font-family:var(--sans);font-weight:700;font-size:1.16rem;letter-spacing:-.02em;color:var(--ink);line-height:1.15}
.fc-caption .fc-title{font-family:var(--body);font-size:.9rem;color:var(--ink-soft);margin-top:3px}

/* .section-head .label-pill{margin-bottom:12px} is the site-wide eyebrow rule,
   but the story block is a bare <div>, not a .section-head — so its pill fell
   back to 0 and sat flush against a 43px headline (7.8px of optical air at
   1280, ~5px at 390). Same defect the megaPanel CTA had. Fluid, because this
   h2 is fluid and the pill's height is not. */
.story .label-pill{margin-bottom:clamp(12px,1.1vw,18px)}
.story h2{font-size:clamp(1.8rem,3.6vw,2.7rem);margin-bottom:18px}
.story p{color:var(--ink-soft);font-size:1.02rem;margin-bottom:14px}
.story .sig{font-family:var(--sans);font-weight:600;color:var(--ink);margin-top:6px}
@media (max-width:820px){.story{grid-template-columns:1fr;max-width:520px}.founder-card-wrap{width:100%;max-width:340px;margin-inline:auto}}
@media (prefers-reduced-motion:reduce){
  .founder-card{transform:none}
  .fc-photo img{transition:none}
}

/* ==========================================================================
   FAQ
   ========================================================================== */
.faq-wrap{max-width:720px;margin:var(--stack-gap) auto 0}
.faq-item{border-bottom:1px solid var(--line)}
.faq-q{width:100%;display:flex;align-items:center;justify-content:space-between;gap:16px;
  text-align:left;padding:16px 4px;font-family:var(--sans);font-weight:600;font-size:clamp(1rem,1.5vw,1.12rem);
  transition:transform .16s var(--ease)}
.faq-q:active{transform:scale(.985)}
.faq-q .pm{flex:none;width:26px;height:26px;border-radius:50%;border:1px solid var(--line-strong);
  display:flex;align-items:center;justify-content:center;transition:transform .3s var(--ease),background .3s var(--ease)}
.faq-q .pm svg{width:13px;height:13px;stroke:var(--ink);stroke-width:2.2;fill:none}
.faq-item.open .faq-q .pm{background:var(--ink);transform:rotate(45deg)}
.faq-item.open .faq-q .pm svg{stroke:#fff}
.faq-a{overflow:hidden;height:0;transition:height .34s var(--ease)}
.faq-a-inner{padding:0 4px 16px;color:var(--ink-soft);font-size:.98rem;max-width:60ch;line-height:1.55}
.faq-a-inner a{color:var(--blue);text-decoration:underline;text-underline-offset:2px}

/* ==========================================================================
   MEGA PANEL  (Effect 5) + FOOTER
   ========================================================================== */
.mega{position:relative;padding:0}
.mega-track{padding-block:var(--section-y);padding-inline:var(--gutter)}
.mega-panel{
  position:relative;max-width:1080px;margin-inline:auto;
  background:var(--panel-dark);color:#fff;border-radius:clamp(20px,3vw,32px);
  padding:clamp(52px,7vw,88px) clamp(26px,4.5vw,64px);
  overflow:hidden;text-align:center;
  /* isolation (not permanent will-change) keeps the stacking context the
     blend-mode canvas + z-index children need; GSAP promotes via inline
     transforms while its scrubbed tween actually runs */
  isolation:isolate;
}
/* small static layer — blend kept here deliberately (cheap at 520x420) */
.mega-panel canvas.bloom{top:auto;bottom:-40px;right:-40px;left:auto;width:520px;height:420px;opacity:.55;mix-blend-mode:screen}
.mega-panel h2{position:relative;z-index:2;font-size:clamp(2.2rem,5.4vw,4rem);font-weight:800;letter-spacing:-.03em;line-height:1.02}
.mega-panel h2 .grad{background:var(--brand-gradient-text-dark);-webkit-background-clip:text;background-clip:text;color:transparent}
/* Spacing inside the panel is fluid, not the flat 14/24/14px it used to be —
   that read cramped on a panel this large, and identical at 390px and 1280px.
   The pill that used to sit above the h2 is gone, so the h2's top space is the
   panel's own padding. */
.mega-panel .sub{position:relative;z-index:2;margin:var(--cta-note-gap) auto 0;max-width:42ch;color:rgba(255,255,255,.66);font-size:1.05rem;line-height:1.5}
.mega-panel .btn{position:relative;z-index:2;margin-top:clamp(30px,3.4vw,44px)}
.mega-panel .trust{position:relative;z-index:2;margin-top:var(--cta-note-gap);font-size:12.5px;color:rgba(255,255,255,.5)}

.footer{background:var(--bg);padding:var(--section-y) var(--gutter) 32px}
.footer-inner{max-width:1000px;margin-inline:auto;display:grid;grid-template-columns:1.6fr 1fr 1fr 1fr;gap:28px}

.footer-brand p{margin-top:14px;color:var(--ink-soft);font-size:.95rem;max-width:30ch}
.footer-col h4{font-family:var(--sans);font-size:.82rem;font-weight:600;color:var(--ink-faint);
  text-transform:uppercase;letter-spacing:.08em;margin-bottom:14px}
.footer-col a{display:block;padding:6px 0;color:var(--ink-soft);font-size:.95rem;transition:color .2s var(--ease)}
.footer-col a:hover{color:var(--ink)}
.footer-bottom{max-width:1000px;margin:28px auto 0;padding-top:18px;border-top:1px solid var(--line);
  display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap;
  color:var(--ink-faint);font-size:13px}
.footer-social{display:flex;gap:10px}
.footer-social a{width:34px;height:34px;border-radius:50%;border:1px solid var(--line);
  display:flex;align-items:center;justify-content:center;color:var(--ink-soft)}
.footer-social a:hover{color:var(--ink);border-color:var(--ink)}
.footer-social svg{width:16px;height:16px;fill:currentColor}
@media (max-width:820px){
  .footer-inner{grid-template-columns:1fr 1fr}
  .footer-brand{grid-column:1/-1}
}

/* ==========================================================================
   SIMPLE PAGE (download + legal)
   ========================================================================== */
.page-hero{padding-top:clamp(130px,16vh,170px);text-align:center}
.page-hero h1{font-size:clamp(2.2rem,5vw,3.4rem);font-weight:800}
.page-hero .sub{margin:18px auto 0;max-width:52ch;color:var(--ink-soft);font-size:1.1rem}
/* .price-trial had NO rule in any stylesheet — a dead class name, so its box
   came from the p{margin:0} reset and it sat at a 0px gap against the download
   button (and, for a visitor who sees .dl-note, fused into that warning as a
   fourth line). Its siblings .dl-alt and .dl-note both have rules; this is the
   one that was missed. Margin goes on .price-trial rather than a bottom margin
   on .dl-cta because it can follow the button, .dl-alt or .dl-note depending on
   platform detection. Same fluid value the megaPanel button->microcopy uses. */
.price-trial{margin-top:var(--cta-note-gap);font-size:.9rem;color:var(--ink-soft)}
.dl-cta{display:flex;gap:14px;justify-content:center;flex-wrap:wrap;margin-top:34px}
.steps{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;max-width:1000px;margin:clamp(36px,5vw,56px) auto 0}
.step{padding:22px}
.step .n{font-family:var(--sans);font-weight:800;font-size:1.6rem;color:var(--blue);margin-bottom:10px}
.step h3{font-size:1.05rem;margin-bottom:6px}
.step p{font-size:.92rem;color:var(--ink-soft)}
.req{max-width:760px;margin:clamp(32px,4vw,48px) auto 0}
.req li{display:flex;gap:12px;padding:14px 0;border-bottom:1px solid var(--line);color:var(--ink-soft)}
.req li b{color:var(--ink);font-family:var(--sans);min-width:150px;font-weight:600}
.legal{max-width:68ch;margin-inline:auto}   /* ch measures CHARACTERS, so this
   stays in the 60-75 range whatever the font renders at; the previous 720px
   ran to roughly 90 characters, past the point where the eye reliably finds
   the start of the next line. These are the longest reading surfaces here. */
.legal h2{font-size:1.5rem;margin:32px 0 12px}
.legal p{color:var(--ink-soft);margin-bottom:14px}
/* numbered legal clauses + lists: the pages are long, so give them air and a
   clear hierarchy a reader (or a payment-gateway reviewer) can scan */
.legal .legal-lead{font-size:1.05rem;line-height:1.65;color:var(--ink);
  padding-bottom:8px;border-bottom:1px solid var(--line);margin-bottom:4px}
.legal ul{margin:0 0 16px;padding-inline-start:22px;color:var(--ink-soft)}
.legal li{margin-bottom:9px;line-height:1.6}
.legal strong{color:var(--ink);font-weight:650}
.legal a{color:var(--blue);text-decoration:underline;text-underline-offset:2px}
.legal h2 + p{margin-top:0}
/* contact page: the address block is the point of the page — make it the
   first thing the eye lands on */
.contact-card{
  border:1px solid var(--line);border-radius:var(--radius-card);background:var(--bg-soft);
  padding:26px 26px 22px;margin-bottom:34px;box-shadow:var(--shadow-soft);
}
.contact-label{display:block;font-size:.78rem;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;color:var(--ink-faint);margin-bottom:8px}
.contact-email{
  font-family:var(--sans);font-weight:800;letter-spacing:-.02em;
  font-size:clamp(1.35rem,3.2vw,1.85rem);color:var(--blue);
  text-decoration:none;overflow-wrap:anywhere;
}
.contact-email:hover{text-decoration:underline;text-underline-offset:3px}
.contact-note{margin:12px 0 0 !important;font-size:.95rem}
/* "how to buy" block — visible only until card checkout is live */
.contact-buy{border-left:3px solid var(--blue);padding:2px 0 2px 18px;margin:0 0 30px}
.contact-buy h2{margin-top:0}
.legal .stub{padding:16px 20px;border:1px dashed var(--line-strong);border-radius:12px;color:var(--ink-faint);font-size:.92rem}
@media (max-width:820px){.steps{grid-template-columns:1fr 1fr}}
@media (max-width:520px){.steps{grid-template-columns:1fr}}

/* ==========================================================================
   REVEAL (motion-in) defaults — JS adds .in
   ========================================================================== */
[data-reveal]{opacity:0;transform:translateY(22px);transition:opacity .7s var(--ease),transform .7s var(--ease)}
[data-reveal].in{opacity:1;transform:none}
[data-reveal-stagger] > *{opacity:0;transform:translateY(20px)}

/* ==========================================================================
   REDUCED MOTION — drop movement, keep opacity/color feedback
   Keyframe animations are flattened universally (a safety net; most already
   have per-component overrides that explicitly disable them — those win by
   source order/specificity). Transitions are NOT flattened by duration
   site-wide (that would also kill the opacity/color feedback layered into
   the same declaration) — instead every selector that animates a movement
   property (transform/width/height/margin/padding/filter) under normal
   motion gets its transition explicitly redeclared here: dropped entirely
   if it's movement-only, or narrowed to just the opacity/color part if one
   is layered in. Hover/press micro-feedback (button lifts, icon rotations)
   is deliberately left animating — user-initiated, brief, aids comprehension.
   ========================================================================== */
@media (prefers-reduced-motion:reduce){
  *{animation-duration:.001ms !important;animation-iteration-count:1 !important;scroll-behavior:auto !important}
  [data-reveal]{opacity:1 !important;transform:none !important}
  [data-reveal-stagger] > *{opacity:1 !important;transform:none !important}
  .tilt-3d{transform:none !important}
  .plugin.is-working .plugin-generate .spin{animation:none}

  /* movement-only: drop the transition entirely (value still applies instantly) */
  .nav-pill{transition:none !important}
  .faq-a{transition:none !important}
  .reveal-line > *{transition:none !important}
  .founders-fill{transition:none !important}

  /* mixed opacity+movement: keep only the opacity (and color, where present)
     part, at its original duration, dropping size/position/filter */
  .nav-links,.ni-ring{transition:opacity .26s var(--ease) !important}
  .ni-viewport,.ni-label{transition:none !important}
  .nav-drop{transition:opacity .2s var(--ease),visibility 0s .2s !important}
  .nav.is-open .nav-drop{transition:opacity .22s var(--ease),visibility 0s !important}
  .cap .w{transition:opacity .18s var(--ease),color .2s var(--ease) !important}
  .timeline{transition:opacity .4s var(--ease) !important}
}

/* ==========================================================================
   PERFORMANCE — content-visibility
   Below-fold sections skip style/layout/paint until approached. Estimates
   match measured heights (desktop / <=820px); after first render the browser
   remembers the real size ("auto"). #pricing is EXCLUDED (its softBloom
   canvas hangs outside the box; cv's contain:paint would clip it). The pinned
   .platforms / .workflow sections are excluded by design.
   app.js force-renders these before any scripted scroll (see forceRenderCV).
   ========================================================================== */
#templates,#why,#story,#faq{content-visibility:auto}
#templates{contain-intrinsic-size:auto 900px}
#why{contain-intrinsic-size:auto 1115px}
#story{contain-intrinsic-size:auto 657px}
#faq{contain-intrinsic-size:auto 732px}
@media (max-width:820px){
  #templates{contain-intrinsic-size:auto 820px}
  #why{contain-intrinsic-size:auto 2010px}
  #story{contain-intrinsic-size:auto 1072px}
  #faq{contain-intrinsic-size:auto 627px}
}

/* ==========================================================================
   WIDESCREEN SIDE RAILS (≥1600px)
   Two hairline-faint vertical washes hugging the true viewport edges so the
   big empty margins outside the (still-capped) --maxw shell read as
   deliberate atmosphere rather than dead space. Fixed + pointer-events:none
   so they never intercept clicks; z-index:0 keeps them under every real
   stacking layer (.shell/.section are z-index:1, .hero is z-index:3, the
   nav pill is z-index:80) so nothing gets covered. Alpha is kept low enough
   (.06) that laid over the dark hero — which is already a deep blue — it's
   effectively invisible; no blend-mode needed.
   ========================================================================== */
@media (min-width:1600px){
  body::before,body::after{
    content:"";
    position:fixed;top:0;bottom:0;width:120px;
    pointer-events:none;z-index:0;
  }
  body::before{
    left:0;
    background:linear-gradient(90deg,rgba(18,134,230,.06) 0%,transparent 100%);
  }
  body::after{
    right:0;
    background:linear-gradient(270deg,rgba(18,134,230,.06) 0%,transparent 100%);
  }
}

/* ==========================================================================
   CHANGELOG (/changelog) — vertical timeline rendered from the shared
   changelog.json on R2 (single source of truth; the site never keeps its own
   copy). Calm and scannable: hairline rail, version dot, Gabarito headings.
   ========================================================================== */
.cl-wrap{max-width:640px;margin-inline:auto;padding-bottom:60px}
.cl-list{list-style:none;margin:0;padding:0;position:relative}
.cl-list::before{content:"";position:absolute;left:5px;top:8px;bottom:8px;width:1px;background:var(--line)}
.cl-item{position:relative;padding:0 0 44px 34px;animation:clIn .55s var(--ease) both;animation-delay:calc(var(--i,0)*70ms)}
@keyframes clIn{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}
.cl-item:last-child{padding-bottom:8px}
.cl-item::before{
  content:"";position:absolute;left:0;top:7px;width:11px;height:11px;border-radius:50%;
  background:#fff;border:2px solid var(--blue);box-sizing:border-box;
}
.cl-item:first-child::before{ animation:clPulse 2.4s ease-in-out infinite; }
@keyframes clPulse{ 0%,100%{ box-shadow:0 0 0 0 rgba(18,134,230,.25); } 50%{ box-shadow:0 0 0 6px rgba(18,134,230,0); } }
@media (prefers-reduced-motion:reduce){ .cl-item:first-child::before{ animation:none; } }
.cl-head{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
.cl-version{
  font-family:var(--sans);font-weight:800;letter-spacing:-.02em;
  font-size:1.35rem;color:var(--ink);
}
.cl-badge{
  font-family:var(--body);font-weight:600;font-size:.7rem;letter-spacing:.03em;
  padding:3px 9px;border-radius:var(--radius-pill);vertical-align:middle;
}
.cl-badge.new{background:rgba(18,134,230,.10);color:var(--blue)}
.cl-badge.fix{background:rgba(17,17,19,.06);color:var(--ink-soft)}
.cl-date{color:var(--ink-faint);font-size:.85rem}
/* Product filter. Reuses the pill vocabulary already on the page (.cl-product,
   .label-pill) rather than importing the pricing segmented control, which owns
   a spring-animated thumb this doesn't need. */
.cl-filter{
  display:flex;flex-wrap:wrap;align-items:center;gap:8px;
  margin:0 0 26px;
}
.cl-filter[hidden]{display:none}
.cl-fbtn{
  display:inline-flex;align-items:center;gap:7px;
  appearance:none;cursor:pointer;
  padding:7px 13px;border-radius:999px;
  border:1px solid var(--line);background:var(--bg);
  font-family:var(--sans);font-weight:650;font-size:.88rem;color:var(--ink-soft);
  transition:color .18s var(--ease),border-color .18s var(--ease),background .18s var(--ease);
}
@media (hover:hover) and (pointer:fine){
  .cl-fbtn:hover{color:var(--ink);border-color:var(--line-strong)}
}
.cl-fbtn:active{transform:scale(.97)}
.cl-fbtn.is-on{
  background:var(--brand-blue);border-color:var(--brand-blue);color:#fff;
}
/* The count is the second most useful thing here after the label — it tells you
   whether a filter is worth clicking before you click it. */
.cl-fcount{
  font-variant-numeric:tabular-nums;font-weight:700;font-size:.78rem;
  opacity:.72;
}
.cl-fbtn.is-on .cl-fcount{opacity:.85}
/* Visually hidden: the count change is announced, not shown twice. */
.cl-fstatus{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
}
@media (prefers-reduced-motion:reduce){
  .cl-fbtn{transition:none}
  .cl-fbtn:active{transform:none}
}
.cl-product{
  font-family:var(--body);font-size:.7rem;font-weight:700;letter-spacing:.04em;
  text-transform:uppercase;padding:3px 8px;border-radius:999px;
  background:var(--bg-soft);color:var(--ink-soft);border:1px solid var(--line);
  white-space:nowrap;
}
/* The markers here were killed by inheritance, not by intent: .cl-list sets
   list-style:none (it is a timeline, not a bulleted list) and list-style is an
   INHERITED property, so it flows down into this nested ul and suppresses its
   bullets too. The 18px of padding-inline-start was still reserving room for
   markers that never rendered, and 4px of separation on a 25px line box left
   consecutive highlights reading as one run-on paragraph — on the one page whose
   whole job is a scannable list of discrete changes. Restoring the marker also
   restores the hanging indent, so a wrapped line can no longer be mistaken for a
   new item. */
.cl-item ul{margin:12px 0 0;padding-inline-start:18px;color:var(--ink-soft);font-size:.95rem;line-height:1.65}
.cl-item ul li{margin-bottom:8px;list-style:disc}
@media (prefers-reduced-motion:reduce){ .cl-item{ animation:none; } }
.cl-empty{
  text-align:center;color:var(--ink-soft);font-size:.98rem;
  border:1px dashed var(--line-strong);border-radius:var(--radius-card);
  padding:44px 24px;
}

/* ==========================================================================
   404 — one screen, no scroll: big line, glyph accents, two ways out.
   ========================================================================== */
.nf{
  min-height:calc(100svh - 80px);position:relative;overflow:hidden;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  text-align:center;padding:120px 22px 60px;
}
.nf::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(60% 45% at 50% 38%,rgba(18,134,230,.07),transparent 70%);
}
.nf .code{
  position:relative;display:inline-flex;align-items:center;gap:8px;
  font-family:ui-monospace,Menlo,monospace;font-weight:600;font-size:.85rem;
  color:var(--blue);letter-spacing:.18em;margin-bottom:18px;
  padding:6px 14px;border:1px solid var(--line);border-radius:var(--radius-pill);background:#fff;
}
.nf .code::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--blue-bright);flex:none}
.nf h1{
  font-family:var(--sans);font-weight:800;letter-spacing:-.025em;
  font-size:clamp(2.2rem,5.4vw,3.6rem);color:var(--ink);margin:0 0 12px;
  max-width:16ch;line-height:1.06;
}
.nf p{position:relative;color:var(--ink-soft);font-size:1.02rem;margin:0 0 30px;max-width:44ch;line-height:1.55}
.nf .nf-actions{position:relative;display:flex;gap:12px;flex-wrap:wrap;justify-content:center}
@media (hover: hover) and (pointer: fine){
  .nf-actions .btn:hover{transform:translateY(-2px)}
}
.nf-glyph{
  position:absolute;pointer-events:none;user-select:none;
  font-family:ui-monospace,Menlo,monospace;font-weight:600;color:var(--ink);
  opacity:.06;animation:nfDrift var(--dur,28s) ease-in-out var(--del,0s) infinite alternate;
}
.nf-glyph.is-blue{color:var(--blue);opacity:.10}
@keyframes nfDrift{
  from{transform:translate3d(0,0,0) rotate(0deg)}
  to{transform:translate3d(var(--dx,10px),var(--dy,-24px),0) rotate(var(--rot,6deg))}
}
@media (prefers-reduced-motion:reduce){ .nf-glyph{animation:none} }

/* ============================================================================
   Cross-document view transitions — subtle fade/slide between page loads.
   Progressive: unsupported browsers just ignore @view-transition.
   ========================================================================== */
@view-transition{ navigation:auto; }
::view-transition-old(root){ animation:pageOut .22s var(--ease) both; }
::view-transition-new(root){ animation:pageIn .28s var(--ease) both; }
@keyframes pageOut{ to{ opacity:0; transform:translateY(-6px); } }
@keyframes pageIn{ from{ opacity:0; transform:translateY(8px); } }
@media (prefers-reduced-motion:reduce){
  ::view-transition-old(root),::view-transition-new(root){ animation:none; }
}

/* Motion added for state changes that previously teleported (2026-08-02).
   Reduced motion keeps the fade and drops the travel — gentler, never zero,
   so the state change is still legible without movement. */
@media (prefers-reduced-motion:reduce){
  .dl-paid{animation:none;opacity:1;transform:none}
  .price-msg{animation-name:none;opacity:1;transform:none}
}
