/* ─────────────────────────────────────────────────────────────────────────────
 *  NEXFAN sales page — editorial style, DARK canvas ("broadsheet at midnight").
 *
 *  Near-monochrome discipline with a single blue purchase accent. Serif
 *  display (Fraunces, self-hosted) carries the brand; system sans runs the UI.
 *  Near-black canvas, bone ink, pill buttons and hairline borders.
 *
 *  Dark-mode contrast rule: structural emphasis still uses light inversion;
 *  purchase decisions alone use blue so the action hierarchy stays obvious.
 *
 *  Hierarchy rule: analytical sections are LEFT-aligned with the heading and
 *  its lede side by side. Centring is reserved for pricing, contact and the
 *  final CTA, so it still means something when it happens.
 * ────────────────────────────────────────────────────────────────────────────*/

@font-face {
  font-family: 'Fraunces';
  src: url('assets/fonts/fraunces.woff2') format('woff2');
  font-weight: 400 600;
  font-display: swap;
  font-style: normal;
}

:root {
  --bg: #0a0a0b;
  --bg-alt: #101012;
  --surface: #151517;
  --surface-2: #1c1c1f;
  --line: #2a2a2e;
  --line-soft: #1f1f22;
  --fg: #fafafb;
  --fg-mid: #cfcfd3;
  --fg-dim: #9a9a9f;
  --fg-faint: #6d6d72;
  --ink: #0a0a0b;
  --light: #fafafb;
  --buy-blue: #0071e3;
  --buy-blue-hover: #0077ed;
  --buy-blue-ring: #409cff;

  --radius: 18px;
  --wrap: 1140px;
  --serif: 'Fraunces', Georgia, 'Times New Roman', serif;
  --sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
}

/* Product-led evolution: preserve Nexfan's dark palette and Fraunces brand.
   Design variance 4, motion 2, density 4. Screens are real application captures. */
body .hero { padding: 48px 0 64px; }
body .hero-grid { gap: 32px; }
body .hero-copy { text-align: center; max-width: 940px; margin: auto; }
body .hero-copy h1 { font-size: clamp(2.35rem, 4.8vw, 3.8rem); line-height: 1.14; margin-bottom: 18px; }
body .hero-copy .lede { max-width: 660px; margin: 0 auto 24px; }
body .hero-copy .cta-row { justify-content: center; }
.hero-product, .product-shot { margin: 0; min-width: 0; }
.hero-product > a, .product-shot > a { display: block; border: 1px solid var(--line); border-radius: 16px; overflow: hidden; background: var(--surface); }
.hero-product img, .product-shot img { display: block; width: 100%; height: auto; }
.hero-product { width: 100%; max-width: 1040px; margin: 0 auto; }
.hero-product figcaption, .product-shot figcaption { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px 20px; margin: 12px 2px 0; color: var(--fg-dim); font-size: .8rem; }
.hero-product figcaption a, .product-shot figcaption a { text-decoration: underline; text-underline-offset: 4px; }
body #features .sec-head, body #install .sec-head { display: block; max-width: 760px; margin-bottom: 36px; }
body #features .sec-head p, body #install .sec-head p { margin-top: 18px; }
.product-tabs { display: flex; overflow-x: auto; gap: 4px; padding: 6px; border: 1px solid var(--line); border-radius: 14px; background: var(--surface); scrollbar-width: thin; }
.product-tabs button { flex: 1 0 auto; min-height: 48px; padding: 12px 18px; border: 0; border-radius: 9px; background: transparent; color: var(--fg-mid); font: 600 .92rem var(--sans); cursor: pointer; white-space: nowrap; transition: background-color .15s, color .15s; }
.product-tabs button:hover { background: var(--surface-2); color: var(--fg); }
.product-tabs button[aria-selected="true"] { background: var(--light); color: var(--ink); }
.product-panel { padding-top: 36px; }
.product-panel-copy { max-width: 700px; min-height: 126px; }
.product-panel h3 { font: 500 clamp(1.6rem, 3vw, 2.25rem)/1.2 var(--serif); margin-bottom: 14px; }
.product-panel-copy p { color: var(--fg-mid); max-width: 650px; }
.product-scenario { display: grid; grid-template-columns: 140px 1fr; gap: 8px 24px; margin-top: 32px; padding: 28px 0; border-top: 1px solid var(--line); }
.product-scenario h4 { margin: 0; font-size: .95rem; }
.product-scenario p { margin: 0; max-width: 800px; color: var(--fg-mid); }
.product-scenario .product-caveat { grid-column: 2; font-size: .86rem; color: var(--fg-dim); }
body .support-section { background: var(--surface); border-block: 1px solid var(--line); }
.support-layout { display: grid; grid-template-columns: 1.35fr 1fr; gap: 80px; align-items: start; }
.support-layout h2 { max-width: 650px; }
.support-layout p { color: var(--fg-mid); }
.support-details { padding-left: 32px; border-left: 1px solid var(--line); }
.support-details h3 { font-family: var(--sans); font-size: 1.1rem; font-weight: 600; }
.support-details p { font-size: .95rem; }
.product-showcase :focus-visible, .hero-product :focus-visible, .support-section :focus-visible { outline: 3px solid var(--fg); outline-offset: 5px; }
body .invert { background: var(--surface); color: var(--fg); border-block: 1px solid var(--line); padding: 48px 0; }
body .invert .big-num, body .invert .invert-lede, body .invert .invert-sub { color: var(--fg); }
body .invert .big-num { font-size: clamp(4rem, 10vw, 7rem); }
@media (max-width: 720px) {
 body .hero { padding: 32px 0 42px; }
 body .hero-copy h1 { font-size: clamp(2rem, 7.5vw, 2.8rem); }
 body .hero-copy .lede { font-size: 1rem; }
 body .hero-grid { gap: 26px; }
 .hero-product > a, .product-shot > a { border-radius: 10px; }
 .product-tabs { border-radius: 10px; }
 .product-tabs button { padding: 12px 14px; }
 .product-panel { padding-top: 28px; }
 .product-panel-copy { min-height: 155px; }
 .product-scenario { grid-template-columns: 1fr; gap: 12px; margin-top: 24px; padding: 22px 0; }
 .product-scenario .product-caveat { grid-column: 1; }
 .support-layout { grid-template-columns: 1fr; gap: 32px; }
 .support-details { border-left: 0; border-top: 1px solid var(--line); padding: 24px 0 0; }
}
@media (prefers-reduced-motion: reduce) { .product-tabs button { transition: none; } }

* { box-sizing: border-box; }
[hidden] { display: none !important; }

/* NOTE: do NOT put overflow-x (clip or hidden) on html here.
   A scattered letter from the scroll effect can translate ~40px and briefly
   report the document 8px wider than the screen. The obvious fix is
   `overflow-x: clip` on the root — and it does remove those 8px, but measured
   in-browser it also stops `position: sticky` working on the header: the nav
   scrolled away with the page instead of pinning. `body` already carries
   `overflow-x: hidden`, which propagates to the viewport and prevents any
   actual sideways scrolling; the 8px is a reported number, not a drag. */
html { scroll-behavior: smooth; scroll-padding-top: 88px; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.55;
  letter-spacing: -0.009em;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
body.checkout-open {
  position: fixed;
  left: 0; right: 0;
  width: 100%;
  overflow: hidden;
  overscroll-behavior: none;
}

/* ── Type ─────────────────────────────────────────────────────────────── */
h1, h2, h3 {
  font-family: var(--serif);
  font-weight: 400;
  line-height: 1.08;
  letter-spacing: -0.018em;
  margin: 0 0 .4em;
  color: var(--fg);
  text-wrap: pretty;
}
h1 { font-size: clamp(2.7rem, 5.4vw, 4.3rem); line-height: 1.04; letter-spacing: -.022em; }
h2 { font-size: clamp(2rem, 3.8vw, 3rem); }
h3 { font-size: 1.3rem; letter-spacing: -0.012em; }
h4 { font-family: var(--sans); }
p  { margin: 0 0 1rem; text-wrap: pretty; }
a  { color: var(--fg); text-decoration: none; }
a:hover { color: #fff; }

/* ── Scroll-linked heading assembly ───────────────────────────────────────
   Each letter carries its own --dx/--dy/--rot (set once in JS); the heading
   carries --p, the assembly progress from 0 to 1 (set on scroll). The letter
   interpolates between its scattered offset and zero.
   calc(var(--dx) * (1 - var(--p))) is the whole effect — no per-letter JS on
   any frame, and transform/opacity are the only properties touched, so this
   stays on the compositor and never triggers layout.
   --p defaults to 1: if the script fails or is blocked, headings render
   perfectly assembled and nothing looks broken. */
/* NO min-width:0 here. I added it once to absorb a 3px overhang and it broke
   the hero stat rule: `.hero-stats` is a flex row, so `min-width: 0` let each
   stat shrink to its min-content — the width of a single letter — and "PLATFORM
   FEE" rendered one letter per line straight down the page. The 3px was the
   rotated chevron's transformed box, and it is fixed at source with a margin. */
.fx-split { --p: 1; }
/* Holds the split text so the element's own layout is untouched. When the
   parent is a flex container (FAQ question rows; comparison-table cells once
   they stack on a phone) this is the single flex item — without it every word
   became its own flex item and the row could no longer wrap.
   `min-width: 0` lets it shrink below its content, which is what allows the
   words inside to wrap at all. */
/* Generated spans must be invisible to the rest of the stylesheet.
   The effect injects <span> elements INSIDE existing elements, and this sheet
   has several descendant rules that style any span in a region — `.hero-stats
   span`, `.plan-price span`, `.founder-sig span`. Those matched every letter I
   generated and won on specificity (0,1,1 over 0,1,0), so inside the hero stat
   rule each letter became `display: block` at label size in uppercase: "0%"
   rendered one character per line, straight down the page.
   Scoping under .fx-split gives these (0,2,0), which outranks any
   `.region span` rule, and `inherit` makes each unit take its appearance from
   the element it was split out of — so italics, the serif, and the small-caps
   of a label all survive being cut into pieces. */
.fx-split .fx-hostwrap,
.fx-split .fx-word,
.fx-split .fx-ch {
  /* `!important` is the right tool here, not a shortcut. This block exists so
     generated spans are INVISIBLE to the rest of the stylesheet, and raising
     specificity only wins until the next descendant rule outranks it. It
     already lost once: `.plan.hot .plan-price span` is (0,3,1) against this
     selector's (0,2,0), so every letter of "$999" rendered in #55555a grey on
     the white card while the <b> around them was correctly black.
     Anything that genuinely wants to style split text styles the SPLIT ELEMENT
     — the <b>, the <h2> — and these units inherit from it, which is exactly
     what `inherit` here preserves. */
  font: inherit !important;
  letter-spacing: inherit !important;
  text-transform: inherit !important;
  line-height: inherit !important;
  color: inherit !important;
  text-align: inherit !important;
  margin: 0 !important; padding: 0 !important;
  width: auto !important; height: auto !important;
  background: none !important; border: 0 !important;
}
.fx-split .fx-hostwrap { display: inline; flex: 1 1 auto; min-width: 0; }
.fx-split .fx-word { display: inline-block; white-space: nowrap; }
.fx-split .fx-ch { display: inline-block; }
.fx-hostwrap { display: inline; flex: 1 1 auto; min-width: 0; }
.fx-word { display: inline-block; white-space: nowrap; }
.fx-ch {
  display: inline-block;
  --dx: 0px; --dy: 0px; --rot: 0deg;
  transform:
    translate3d(calc(var(--dx) * (1 - var(--p))), calc(var(--dy) * (1 - var(--p))), 0)
    rotate(calc(var(--rot) * (1 - var(--p))));
  opacity: calc(0.06 + 0.94 * var(--p));
}
/* No `will-change` here, deliberately. It was fine for 174 letters; across the
   whole page there are ~2,200 animated units, and a compositor-layer hint on
   every one of them costs far more memory than it saves — on a phone it is the
   difference between smooth and unusable. transform and opacity are composited
   regardless. */
@media (prefers-reduced-motion: reduce) {
  .fx-ch { transform: none; opacity: 1; will-change: auto; }
}

.eyebrow, .sec-num {
  font-family: var(--sans);
  font-size: .72rem; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--fg-faint);
}
.ital { font-style: italic; color: var(--fg-dim); }

.wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding: 0 28px; }
.narrow { max-width: 760px; }
.center { text-align: center; }
.left { text-align: left; }
.center .cta-row { justify-content: center; }

.skip { position: absolute; left: -9999px; top: 0; z-index: 100; background: var(--light); color: var(--ink); padding: 10px 16px; }
.skip:focus { left: 8px; top: 8px; }

/* ── Nav ──────────────────────────────────────────────────────────────── */
.nav {
  position: sticky; top: 0; z-index: 50;
  background: rgba(10,10,11,.85);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line-soft);
}
.nav-inner { display: flex; align-items: center; gap: 32px; height: 72px; }
.brand { display: flex; align-items: center; gap: 10px; font-weight: 600; letter-spacing: -.01em; font-size: 1.05rem; color: var(--fg); }
.brand-mark {
  display: grid; place-items: center; width: 28px; height: 28px; border-radius: 7px;
  background: var(--light); color: var(--ink); font-family: var(--serif); font-size: .9rem;
}
/* The monogram is wide (roughly 5:3), so it's sized on width and left to find
   its own height — fixing both would squash it. currentColor means the same
   symbol reads dark on the light tile here and light on dark elsewhere. */
.nf { width: 17px; height: auto; display: block; color: inherit; }
.nav-links { display: flex; gap: 28px; margin-left: auto; }
.nav-links a { position: relative; color: var(--fg-dim); font-size: .9rem; font-weight: 500; transition: color .15s; }
.nav-links a:hover { color: var(--fg); }
/* Underline wipes in from the left rather than blinking on, so the marker
   feels like it's tracking the scroll instead of jumping between links. */
.nav-links a::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -7px; height: 1.5px;
  background: currentColor; border-radius: 2px;
  transform: scaleX(0); transform-origin: left;
  transition: transform .3s cubic-bezier(.4, 0, .2, 1);
}
.nav-links a.active { color: var(--fg); }
.nav-links a.active::after { transform: scaleX(1); }
@media (prefers-reduced-motion: reduce) { .nav-links a::after { transition: none; } }
/* Both nav buttons live outside .nav-links, so they survive the mobile
   hamburger — "Demo" and "Buy now" are the two things a visitor should never
   have to open a menu to find. */
.nav-actions { display: flex; align-items: center; gap: 8px; }
.nav-cta { white-space: nowrap; }
.nav-toggle { display: none; background: none; border: 0; color: var(--fg); font-size: 1.35rem; cursor: pointer; padding: 4px 8px; line-height: 1; }

/* Config warning: shows only while config.js still holds placeholders. */
.config-bar {
  background: var(--surface-2); border-bottom: 1px solid var(--line);
  padding: 12px 28px; text-align: center;
  font-family: var(--mono); font-size: .78rem; color: var(--fg-mid);
}

/* ── Buttons (pills) ──────────────────────────────────────────────────── */
.btn {
  display: inline-block; padding: 13px 26px; border-radius: 999px;
  background: var(--light); color: var(--ink); border: 1px solid var(--light);
  font-family: var(--sans); font-weight: 500; font-size: .95rem; cursor: pointer;
  transition: background .15s, color .15s, border-color .15s, opacity .15s;
}
.btn:hover { background: #fff; border-color: #fff; color: var(--ink); }
.btn:disabled { opacity: .5; cursor: progress; }
.btn-lg { padding: 15px 32px; font-size: 1rem; }
.btn-sm { padding: 9px 20px; font-size: .85rem; }
.btn-ghost { background: transparent; color: var(--fg); border-color: var(--line); }
.btn-ghost:hover { background: transparent; color: var(--fg); border-color: var(--fg); }
.cta-row { display: flex; flex-wrap: wrap; gap: 12px; margin: 36px 0 0; }

/* ── Hero ─────────────────────────────────────────────────────────────── */
.hero { padding: 76px 0 80px; border-bottom: 1px solid var(--line-soft); }
/* Single column since the phone came out. The copy keeps its own measure
   via .lede rather than stretching to the full 1140px, which would give
   the hero paragraph an unreadable line length. */
.hero-grid { display: grid; grid-template-columns: 1fr; gap: 60px; align-items: center; }
.hero-copy .eyebrow { display: block; margin-bottom: 24px; }
.hero-copy h1 { margin-bottom: 24px; }
.lede { font-size: 1.14rem; color: var(--fg-mid); max-width: 540px; margin: 0; }

/* Three-cell stat rule: the numbers that decide the sale, at display size. */
.hero-stats {
  display: flex; align-items: stretch; margin: 38px 0 0;
  /* Capped since the phone came out of the hero. Full width, the rule stretched
     to 1084px with two short stats floating in it and read as a stray divider
     rather than a stat block. */
  max-width: 620px;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
/* flex:1 so the cells divide the rule evenly. With three of them they filled
   it naturally; at two, content-width cells left the right half of a
   full-width rule empty and the whole thing read unfinished. */
.hero-stats > div { flex: 1; padding: 20px 28px; border-right: 1px solid var(--line); }
.hero-stats > div:first-child { padding-left: 0; }
.hero-stats > div:last-child { border-right: 0; padding-right: 0; }
.hero-stats b { display: block; font-family: var(--serif); font-weight: 400; font-size: 3.4rem; line-height: 1; letter-spacing: -.03em; }
.hero-stats b em { font-style: normal; font-size: 1.6rem; color: var(--fg-dim); }
.hero-stats span { display: block; font-size: .78rem; letter-spacing: .1em; text-transform: uppercase; color: var(--fg-faint); margin-top: 8px; }

.micro { color: var(--fg-faint); font-size: .85rem; margin-top: 14px; }
.micro.center { text-align: center; margin-top: 22px; }

/* ── Phone mockup ─────────────────────────────────────────────────────── */
.phone-stage { display: flex; justify-content: center; }
.phone {
  position: relative; width: 288px; aspect-ratio: 9 / 19.5;
  background: #050506; border-radius: 44px; padding: 11px;
  border: 1px solid rgba(255,255,255,.09);
  box-shadow: 0 30px 70px rgba(0,0,0,.6);
}
.phone.sm { width: 100%; max-width: 258px; border-radius: 40px; padding: 10px; box-shadow: 0 24px 56px rgba(0,0,0,.55); }
.phone-screen {
  position: relative; width: 100%; height: 100%; border-radius: 34px; overflow: hidden;
  background: var(--surface-2); display: block;
}
.phone.sm .phone-screen { border-radius: 31px; }
/* The background matters. A PNG decodes top-down, and until the bottom arrives
   the image's own box is transparent — so whatever sits behind it shows
   through. Behind it is the striped "replace this file" panel, which meant a
   half-loaded screenshot read as a broken one. An opaque background on the
   image itself means a partial decode shows dark frame instead. */
.phone-screen img {
  position: relative; z-index: 1; width: 100%; height: 100%;
  object-fit: cover; display: block; background: #0c0c0e;
}
/* Hidden by default and revealed only when an image genuinely fails (the
   onerror handler adds .no-shot). It used to sit behind every image
   permanently, waiting to be covered — which works only once the image is
   fully painted, and is exactly the wrong thing to expose during a slow load
   or a progressive repaint. */
.shot-ph { display: none; }
.phone-screen.no-shot .shot-ph { display: flex; }
.shot-ph {
  position: absolute; inset: 0; flex-direction: column;
  align-items: center; justify-content: center; gap: 14px; text-align: center;
  background: repeating-linear-gradient(135deg, #17171a, #17171a 14px, #141417 14px, #141417 28px);
  color: var(--fg-dim); padding: 24px;
}
.shot-ph .mk { width: 46px; height: 46px; border-radius: 12px; background: var(--light); color: var(--ink); display: grid; place-items: center; font-family: var(--serif); font-size: 1.4rem; }
.shot-ph .mk .nf { width: 28px; }
.shot-ph span { font-family: var(--mono); font-size: .72rem; line-height: 1.6; }

/* ── Sections ─────────────────────────────────────────────────────────── */
.section { padding: 96px 0; }
.section.alt { background: var(--bg-alt); border-block: 1px solid var(--line-soft); }
.sec-num { display: block; margin: 0 0 18px; }
.sec-head { display: grid; grid-template-columns: 1fr 1fr; gap: 60px; align-items: end; margin-bottom: 56px; }
.sec-head h2 { margin: 0; font-size: clamp(2rem, 3.6vw, 2.9rem); }
.sec-head p { color: var(--fg-mid); font-size: 1.05rem; margin: 0; }
.section-lede { color: var(--fg-mid); text-align: center; max-width: 620px; margin: 0 auto 56px; font-size: 1.05rem; }
.narrow h2 { text-align: center; margin-bottom: 20px; }

/* ── Pillars ──────────────────────────────────────────────────────────── */
.grid-3 { display: grid; gap: 1px; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); background: var(--line-soft); border: 1px solid var(--line-soft); border-radius: var(--radius); overflow: hidden; }
.pillar { background: var(--surface); padding: 32px; }
.pillar h3 { margin-bottom: .5em; font-size: 1.35rem; }
.pillar p { color: var(--fg-dim); font-size: .96rem; margin: 0; }

/* ── Six differentiators ──────────────────────────────────────────────── */
.feat6 { display: grid; gap: 1px; grid-template-columns: repeat(auto-fit, minmax(270px, 1fr)); background: var(--line-soft); border: 1px solid var(--line-soft); border-radius: var(--radius); overflow: hidden; }
.fcard { background: var(--surface); padding: 34px 30px; }
/* The cards used to open with a serif glyph (7 / AI / $$ / ▶ / ▲ / 1). Removed
   at the client's request — the rule goes with the markup rather than being
   left behind as dead weight. The heading now leads, so it carries the top
   margin the glyph used to provide. */
.fcard h3 { margin-bottom: .5em; }
.fcard p { color: var(--fg-dim); font-size: .95rem; margin: 0; }

/* Full audit: reference material, deliberately demoted behind a disclosure. */
.audit { margin-top: 32px; border: 1px solid var(--line-soft); border-radius: var(--radius); background: var(--surface); }
.audit summary { cursor: pointer; padding: 22px 30px; font-weight: 600; font-size: .95rem; list-style: none; display: flex; justify-content: space-between; align-items: center; gap: 16px; }
.audit summary::-webkit-details-marker { display: none; }
.audit summary em { font-style: normal; color: var(--fg-faint); font-size: .8rem; letter-spacing: .1em; text-transform: uppercase; }
.audit[open] summary em::before { content: 'Close'; }
.audit[open] summary em { font-size: 0; }
.audit[open] summary em::before { font-size: .8rem; }
/* Hairline-separated cells rather than floating columns — the same device the
   pillars and the six feature cards use, so the reference list reads as part of
   the same page instead of a loose appendix. The 1px gap over a line-coloured
   background is what draws the rules; each cell repaints the surface on top. */
.audit-grid {
  display: grid; gap: 1px;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  /* Transparent, NOT a line colour. These hairlines are drawn by the 1px gap
     showing the layer behind the cells — and seven categories never fill a 4-,
     3- or 2-column grid, so there is always at least one empty cell. With a
     pale background behind, that empty cell rendered as a blank grey window;
     with the section colour showing through it is simply not there, and the
     gaps still read as rules because the cells are lighter than the section. */
  background: transparent;
  border-top: 1px solid var(--line-soft);
  padding: 0; margin: 0;
}
.audit-grid > div { background: var(--surface); padding: 26px 26px 28px; }
}
.audit-grid h4 {
  font-size: .68rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--fg); margin: 0 0 16px;
  padding-bottom: 12px; border-bottom: 1px solid var(--line-soft);
}
.audit-grid ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 9px; }
/* Marker dots match the pricing tiers, so a list of facts looks the same
   wherever it appears on the page. */
.audit-grid li {
  position: relative; padding-left: 16px;
  color: var(--fg-dim); font-size: .85rem; line-height: 1.5;
}
.audit-grid li::before {
  content: ''; position: absolute; left: 0; top: .55em;
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--fg); opacity: .45;
}

/* ── Tour ─────────────────────────────────────────────────────────────── */
.tour { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 40px; justify-items: center; }
.tour-item { max-width: 340px; }
.tour-item .phone-stage { margin-bottom: 28px; }
.tour-item .eyebrow { display: block; margin-bottom: 12px; }
.tour-item h3 { font-size: 1.45rem; margin-bottom: .5em; }
.tour-item p { color: var(--fg-dim); font-size: .95rem; margin: 0; }

/* ── Install ──────────────────────────────────────────────────────────── */
.install-grid { display: grid; grid-template-columns: .9fr 1.1fr; gap: 48px; align-items: start; }
/* `min-width: 0` is what makes the terminal actually scroll instead of shoving
   the whole page sideways. A grid item's default min-width is auto, meaning it
   refuses to shrink below its content's min-content width — and the install
   command is one long unbreakable line. So the column grew to ~440px inside a
   319px phone, `overflow-x: auto` on the <pre> never got a chance to engage,
   and the entire document became 92px wider than the screen. That is the
   "page wobbles left and right" symptom: every section then scrolls sideways,
   not just the terminal.
   Applied to all the two-column grids, because any of them can be handed
   unbreakable content later and fail the same way. */
.install-grid > *, .hero-grid > *, .sec-head > *, .fb-grid > *, .invert-grid > * { min-width: 0; }
.steps { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; background: var(--line-soft); border: 1px solid var(--line-soft); border-radius: var(--radius); overflow: hidden; }
.steps li { background: var(--surface); padding: 26px 28px; display: grid; grid-template-columns: 36px 1fr; gap: 18px; align-items: start; }
.step-n { display: grid; place-items: center; width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--fg); color: var(--fg); font-family: var(--serif); font-size: 1rem; }
.steps h3 { margin: 4px 0 .35em; font-size: 1.2rem; }
.steps p { color: var(--fg-dim); font-size: .92rem; margin: 0; }

/* Reassurance sits ABOVE the terminal — non-technical buyers bounce at the
   curl line before they ever reach a note printed underneath it. */
.pre-note { color: var(--fg-mid); font-size: .95rem; margin: 0 0 18px; }
.pre-note b { color: var(--fg); font-weight: 600; }

.terminal {
  background: #050506; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
  box-shadow: 0 24px 60px rgba(0,0,0,.5);
}
.terminal-bar { display: flex; align-items: center; gap: 7px; padding: 12px 16px; border-bottom: 1px solid var(--line); }
.terminal-bar span { width: 11px; height: 11px; border-radius: 50%; background: #333; }
.terminal-bar em { margin-left: auto; font-style: normal; font-size: .74rem; color: var(--fg-faint); font-family: var(--mono); }
.terminal pre { margin: 0; padding: 22px 20px; overflow-x: auto; font-family: var(--mono); font-size: .78rem; line-height: 1.75; color: #d4d4d4; }
.c-prompt { color: var(--fg-faint); }
.c-flag { color: #cbcbcb; }
.c-ok { color: #fff; }

/* ── Comparison table ─────────────────────────────────────────────────── */
.compare { margin-top: 56px; overflow-x: auto; }
.compare table { width: 100%; border-collapse: collapse; min-width: 560px; }
.compare th, .compare td { text-align: left; padding: 16px 20px; border-bottom: 1px solid var(--line-soft); font-size: .93rem; vertical-align: top; }
.compare thead th { color: var(--fg-faint); font-weight: 600; font-size: .72rem; letter-spacing: .1em; text-transform: uppercase; padding-block: 14px; }
.compare tbody th[scope="row"] { color: var(--fg); font-weight: 600; }
.compare tbody td { color: var(--fg-dim); }
.compare .col-us { color: var(--fg); background: var(--surface); border-left: 1px solid var(--line); border-right: 1px solid var(--line); font-weight: 500; }
/* The renting column goes darker than the page so the two sides separate at a
   glance. Ours is already lifted to --surface; without pushing theirs down the
   only difference was a few points of grey, which on a phone reads as one flat
   block. Now it steps: renting #08080a, page #0a0a0b, ours #151517 — and the
   eye lands on the light column, which is the one we want read. */
.compare tbody td:not(.col-us),
.compare thead th:nth-child(2) { background: #08080a; }
.compare thead .col-us { color: var(--fg); }
.compare tbody tr:last-child .col-us { border-bottom: 1px solid var(--line); }

/* Phones: stop being a table.
   A 3-column comparison cannot fit 375px, and the honest options are a
   sideways scroll or a different shape. Sideways scrolling loses: the content
   that matters most — our column — is the one off-screen, and nothing on a
   phone advertises that a region scrolls. People simply read the first two
   columns and conclude that's the whole comparison.
   So each row becomes its own card: the row heading on top, then the two
   answers stacked and labelled from `data-label`. Same markup, same semantics
   for a screen reader (the row headings are real <th scope="row">), no
   horizontal movement anywhere. */
@media (max-width: 720px) {
  /* Phones: still a comparison, not a list.
     The first attempt stacked each row into a card with "Renting" above
     "NEXFAN" — which reads as one long column where the two sides are never on
     screen together, and a comparison you can't see side by side isn't one.
     So each row keeps its two values ADJACENT: the row heading spans the top,
     the two answers sit in a 2-up grid beneath it with their column name above
     each. Two ~145px columns fit a 375px screen comfortably, and the eye can
     still do the left-vs-right jump the table was built for. */
  .compare { margin-top: 40px; overflow-x: visible; }
  .compare table { min-width: 0; }
  /* The real <thead> can't stay laid out once the rows become cards, but it
     must remain in the DOM for screen readers; the data-labels below repeat it
     visually per cell. */
  .compare thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  .compare table, .compare tbody { display: block; width: 100%; }
  .compare tr {
    display: grid; grid-template-columns: 1fr 1fr;
    border: 1px solid var(--line-soft); border-radius: 14px;
    margin-bottom: 10px; overflow: hidden; background: var(--bg-alt);
  }
  .compare tbody th[scope="row"] {
    grid-column: 1 / -1;          /* heading spans both answers */
    padding: 12px 15px; font-size: .9rem;
    background: var(--surface-2); border-bottom: 1px solid var(--line-soft);
  }
  .compare tbody td {
    display: block; padding: 11px 15px 13px;
    font-size: .88rem; line-height: 1.4; border-bottom: 0;
  }
  /* Hairline between the two answers — the column edge, rebuilt. */
  .compare tbody td:first-of-type { border-right: 1px solid var(--line-soft); }
  .compare tbody td::before {
    content: attr(data-label);
    display: block; margin-bottom: 5px;
    color: var(--fg-faint); font-size: .62rem; font-weight: 600;
    letter-spacing: .1em; text-transform: uppercase;
  }
  /* Ours keeps the lighter surface, so the winning column still reads as the
     emphasised one exactly as it does on desktop. */
  .compare .col-us { background: var(--surface); border-left: 0; border-right: 0; }
  /* Restated inside the query: the mobile card paints its own background on the
     row, which would otherwise win over the rule above. */
  .compare tbody td:not(.col-us) { background: #08080a; }
  .compare tbody td.col-us::before { color: var(--fg-dim); }
}

/* ── Scale break ──────────────────────────────────────────────────────────
 * The page's one landmark: full-bleed light, one enormous number. Without a
 * moment like this the whole page reads at a single flat volume. */
.invert { padding: 104px 0; background: var(--light); color: var(--ink); }
.invert-grid { display: grid; grid-template-columns: auto 1fr; gap: 56px; align-items: center; }
.big-num { font-family: var(--serif); font-size: clamp(7rem, 16vw, 15rem); line-height: .82; letter-spacing: -.05em; }
.invert-lede { font-family: var(--serif); font-size: clamp(1.5rem, 2.6vw, 2.2rem); line-height: 1.22; letter-spacing: -.018em; margin: 0 0 20px; }
.invert-sub { color: #55555a; font-size: 1rem; margin: 0; max-width: 46ch; }

/* ── Pricing ──────────────────────────────────────────────────────────── */
.plans { display: grid; gap: 20px; grid-template-columns: repeat(auto-fit, minmax(268px, 1fr)); align-items: stretch; }
.plan {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  padding: 34px 30px;
  display: flex;
  flex-direction: column;
  transition: transform .28s cubic-bezier(.2,.8,.2,1), border-color .28s ease, box-shadow .28s ease;
}
.plan h3 { font-size: 1.5rem; margin-bottom: .25em; }
.plan-blurb { color: var(--fg-dim); font-size: .9rem; margin: 0; }
.plan-price { display: flex; align-items: baseline; gap: 8px; margin: 24px 0 4px; }
.plan-price b { font-family: var(--serif); font-weight: 400; font-size: 2.9rem; letter-spacing: -.02em; line-height: 1; color: var(--fg); }
.plan-price span { color: var(--fg-dim); font-size: .86rem; }
.plan ul { list-style: none; margin: 26px 0 30px; padding: 0; display: grid; gap: 11px; }
.plan li { position: relative; padding-left: 22px; color: var(--fg-dim); font-size: .92rem; }
.plan li::before { content: ''; position: absolute; left: 0; top: .5em; width: 5px; height: 5px; border-radius: 50%; background: var(--fg); opacity: .7; }
.plan .btn {
  margin-top: auto;
  width: 100%;
  text-align: center;
  background: var(--buy-blue);
  border-color: var(--buy-blue);
  color: #fff;
  box-shadow: 0 10px 28px -16px rgba(0, 113, 227, .9);
}
.plan .btn:hover {
  background: var(--buy-blue-hover);
  border-color: var(--buy-blue-hover);
  color: #fff;
}
.plan .btn:active { transform: translateY(1px); }
.plan .btn:focus-visible {
  outline: 3px solid var(--buy-blue-ring);
  outline-offset: 3px;
}
.plan-tag { position: absolute; top: -12px; left: 50%; transform: translateX(-50%); background: var(--light); color: var(--ink); font-size: .68rem; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; padding: 5px 14px; border-radius: 999px; }
.plan-error { color: var(--fg); font-size: .85rem; margin: 12px 0 0; }
.placeholder-warn { display: block; background: var(--surface-2); border: 1px dashed var(--fg-dim); color: var(--fg-mid); border-radius: 8px; padding: 10px 12px; font-size: .8rem; font-family: var(--mono); margin: 14px 0; }

/* Featured plan keeps the original white inversion; only its action is blue. */
.plan.hot { background: var(--light); border-color: var(--light); color: var(--ink); }
.plan.hot h3, .plan.hot .plan-price b { color: var(--ink); }
.plan.hot .plan-blurb, .plan.hot .plan-price span { color: #55555a; }
.plan.hot li { color: #3a3a3d; }
.plan.hot li::before { background: var(--ink); opacity: 1; }
.plan.hot .plan-tag { background: var(--ink); color: var(--light); }
.plan.hot .btn { background: var(--buy-blue); color: #fff; border-color: var(--buy-blue); }
.plan.hot .btn:hover { background: var(--buy-blue-hover); border-color: var(--buy-blue-hover); color: #fff; }

/* Lift cards only where hover is real; touch layouts stay still. */
@media (hover: hover) and (pointer: fine) {
  .plan:hover,
  .plan:focus-within {
    z-index: 2;
    transform: translateY(-8px) scale(1.012);
    border-color: rgba(255,255,255,.34);
    box-shadow: 0 30px 58px -30px rgba(0,0,0,.92);
  }
  .plan.hot:hover,
  .plan.hot:focus-within {
    border-color: var(--light);
    box-shadow: 0 30px 58px -34px rgba(255,255,255,.32), 0 24px 48px -34px rgba(0,0,0,.72);
  }
}
@media (prefers-reduced-motion: reduce) {
  .plan { transition: none; }
  .plan:hover,
  .plan:focus-within { transform: none; }
}

/* ── Includes grid ────────────────────────────────────────────────────── */
.includes { margin-top: 48px; border: 1px solid var(--line-soft); border-radius: var(--radius); padding: 36px; background: var(--bg-alt); }
.includes h3 { text-align: center; margin-bottom: 28px; }
.includes ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px 28px; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.includes li { position: relative; padding-left: 22px; color: var(--fg-dim); font-size: .88rem; }
.includes li::before { content: ''; position: absolute; left: 0; top: .5em; width: 5px; height: 5px; border-radius: 50%; background: var(--fg); }

/* ── FAQ (grouped, so 17 questions stay scannable) ────────────────────── */
/* ── FAQ: the whole section folds ───────────────────────────────────────────
   Same constraint as the groups below — a <details> must keep a block display
   or the browser stops collapsing it, so the section heading becomes a
   full-width summary row rather than sitting in a grid. */
.faq-all > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: baseline; justify-content: space-between; gap: 20px;
}
.faq-all > summary::-webkit-details-marker { display: none; }
.faq-all > summary h2 { margin: 0; font-size: clamp(2rem, 3.6vw, 2.9rem); }
.faq-all > summary:hover h2 { color: #fff; }
/* Each chevron is scaled to the text it sits beside, not to its depth in the
   hierarchy: 13px next to a 2.9rem heading, 9px next to a 1rem question, 7px
   next to a .72rem small-caps label. Sizing them by nesting level instead would
   put an oversized arrow next to the smallest type on the page. */
.faq-all > summary::after {
  content: ''; flex: none;
  width: 13px; height: 13px;
  /* A 13px square rotated 45° has an 18.4px diagonal, so its transformed box
     sticks ~3px past the layout box — enough to register as scrollable
     overflow on the section. The margin gives the rotation room. */
  margin-right: 3px;
  align-self: center;
  border-right: 2px solid var(--fg-dim);
  border-bottom: 2px solid var(--fg-dim);
  transform: rotate(45deg) translate(-3px, -3px);
  transition: transform .24s cubic-bezier(.4, 0, .2, 1), border-color .15s;
}
.faq-all > summary:hover::after { border-color: var(--fg); }
.faq-all[open] > summary::after { transform: rotate(-135deg) translate(-3px, -3px); }
@media (prefers-reduced-motion: reduce) { .faq-all > summary::after { transition: none; } }
/* Was the right-hand cell of .sec-head; now a normal paragraph under the
   heading, capped so it doesn't run the full 1140px measure. */
.faq-lede { color: var(--fg-mid); font-size: 1.05rem; margin: 18px 0 40px; max-width: 62ch; }

/* Each category is itself a <details>, so a whole block of questions can be
   folded away. Two levels of disclosure sounds fussy but matches how people
   actually use an FAQ: find your category, then find your question. It also
   takes the section from 17 visible rows to 3 when everything is closed. */
/* NOT display:grid. Putting grid (or flex) on a <details> stops the browser
   collapsing it — the closed content stays laid out and visible, and here it
   also blew the page 335px wider than the viewport. The element has to keep a
   block display for the disclosure to work at all, so the old 220px label
   gutter goes and the category header becomes a full-width row instead:
   label left, chevron right, questions beneath. Conventional accordion
   shape, and it reclaims the gutter. */
.faq-group { padding: 30px 0; border-top: 1px solid var(--line); }
.faq-group > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 6px 0 14px;
}
.faq-group > summary::-webkit-details-marker { display: none; }
.faq-group > summary h3 { font-family: var(--sans); font-size: .72rem; font-weight: 600; letter-spacing: .14em; text-transform: uppercase; color: var(--fg-faint); margin: 0; }
.faq-group > summary:hover h3 { color: var(--fg-dim); }
/* Same chevron as the questions, one size down — a category header shouldn't
   shout louder than the questions inside it. */
.faq-group > summary::after {
  content: ''; flex: none;
  width: 7px; height: 7px;
  border-right: 1.6px solid var(--fg-faint);
  border-bottom: 1.6px solid var(--fg-faint);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .22s cubic-bezier(.4, 0, .2, 1), border-color .15s;
}
.faq-group > summary:hover::after { border-color: var(--fg-dim); }
.faq-group[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
@media (prefers-reduced-motion: reduce) { .faq-group > summary::after { transition: none; } }
.faq details { border-bottom: 1px solid var(--line-soft); }
.faq summary { cursor: pointer; padding: 18px 0; font-weight: 600; font-size: 1.02rem; list-style: none; display: flex; justify-content: space-between; align-items: center; gap: 16px; color: var(--fg); }
.faq summary::-webkit-details-marker { display: none; }
/* Chevron, not a +/– glyph. Two borders rotated 45° gives a real arrow at any
   size and needs no icon font. It points DOWN when closed (the universal "there
   is more below this" signal) and rotates to point up when open, so the control
   animates between two states instead of swapping one character for another —
   which is what makes it read as a dropdown rather than a maths symbol.
   Sized in px deliberately: a chevron built from borders distorts if the
   box scales with the font. */
.faq summary::after {
  content: '';
  flex: none;
  width: 9px; height: 9px;
  margin-right: 4px;
  border-right: 1.7px solid var(--fg-dim);
  border-bottom: 1.7px solid var(--fg-dim);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .22s cubic-bezier(.4, 0, .2, 1), border-color .15s;
}
.faq summary:hover::after { border-color: var(--fg); }
.faq details[open] summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
@media (prefers-reduced-motion: reduce) { .faq summary::after { transition: none; } }
.faq details p { color: var(--fg-dim); font-size: .96rem; margin: 0 0 20px; max-width: 68ch; }

/* ── Feedback form ────────────────────────────────────────────────────── */
.fb-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 60px; align-items: start; }
.fb-lede { color: var(--fg-mid); font-size: 1.05rem; margin: 0; }
.form { display: grid; gap: 18px; }
.field-row { display: grid; gap: 18px; grid-template-columns: 1fr 1fr; }
.form label { display: grid; gap: 8px; font-size: .88rem; font-weight: 600; color: var(--fg); }
.form label span em { font-style: normal; font-weight: 400; color: var(--fg-dim); }
.form input, .form textarea, .form select { background: var(--surface); border: 1px solid var(--line); border-radius: 10px; color: var(--fg); padding: 13px 15px; font: inherit; font-size: .95rem; width: 100%; font-weight: 400; }
.form textarea { resize: vertical; }
.form input::placeholder, .form textarea::placeholder { color: var(--fg-faint); }
.form input:focus, .form textarea:focus, .form select:focus { outline: none; border-color: var(--fg); }
.form .btn { justify-self: start; }
.form-status { font-size: .9rem; margin: 0; min-height: 1.4em; color: var(--fg-dim); }
.form-status.ok { color: var(--fg); font-weight: 600; }
.form-status.err { color: var(--fg); }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ── Floating contact bubble ──────────────────────────────────────────────
   Contact used to be a section you had to scroll to. As a fixed bubble it is
   reachable from any point on the page, which matters more here than on a
   normal SaaS site: the answer to "should I spend $299 on this" is usually a
   conversation, and the moment someone wants that conversation is unpredictable.

   The expanding-pill interaction is adapted from "pretty-deer-58" by
   elijahgummer on uiverse.io, MIT licensed:

     Copyright (c) 2026 elijahgummer (Elijah W Gummer)
     Permission is hereby granted, free of charge, to any person obtaining a
     copy of this software and associated documentation files (the "Software"),
     to deal in the Software without restriction... The above copyright notice
     and this permission notice shall be included in all copies or substantial
     portions of the Software.

   Changed from the original: their version uses an icon font, this one uses the
   inline SVGs already in the page, because the whole site currently makes zero
   third-party requests and an icon CDN would end that for four glyphs. */
.msg-fab {
  position: fixed; right: 20px; bottom: 20px; z-index: 90;
  display: flex; flex-direction: column; align-items: flex-end; gap: 12px;
}
.msg-channels {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; align-items: flex-end; gap: 12px;
}
/* Closed state animates rather than snapping to display:none, so the panel
   retracts toward the button it came from. */
.msg-channels li {
  opacity: 0; transform: translateY(10px) scale(.9);
  transition: opacity .22s ease, transform .22s cubic-bezier(.34,1.4,.64,1);
  pointer-events: none;
}
.msg-fab.open .msg-channels li { opacity: 1; transform: none; pointer-events: auto; }
/* Staggered so they unfold instead of appearing as a block. */
.msg-fab.open .msg-channels li:nth-child(1) { transition-delay: .04s; }
.msg-fab.open .msg-channels li:nth-child(2) { transition-delay: .08s; }
.msg-fab.open .msg-channels li:nth-child(3) { transition-delay: .12s; }
.msg-fab.open .msg-channels li:nth-child(4) { transition-delay: .16s; }

.msg-channels a {
  position: relative; overflow: hidden;
  display: flex; align-items: center; justify-content: flex-end;
  width: 52px; height: 52px; border-radius: 52px;
  background: var(--light); color: var(--ink);
  box-shadow: 0 10px 25px rgba(0,0,0,.35);
  transition: width .42s cubic-bezier(.4,0,.2,1), box-shadow .3s, background .3s;
}
/* The pill grows leftward to reveal its label. Anchored right so it expands
   away from the screen edge instead of off it. */
.msg-channels a:hover, .msg-channels a:focus-visible {
  width: 168px;
  background: linear-gradient(45deg, var(--i, #2a2a2e), var(--j, #4a4a52));
  color: #fff;
  outline: none;
}
/* Each icon wears its own brand colour at rest. `--i` is already set per
   channel on the <a>, so this needs no extra data: Telegram blue, WhatsApp
   green, Instagram its gradient (defined inside that SVG), email a neutral
   graphite. The icons are a mix of filled and stroked paths, and currentColor
   drives both, so one declaration covers all four. */
.msg-channels .ico {
  position: absolute; right: 0; top: 0;
  width: 52px; height: 52px;
  display: grid; place-items: center;
  color: var(--i, var(--ink));
  transition: transform .3s, color .3s;
}
/* Once the pill fills with the brand gradient, a brand-coloured glyph on top
   of it disappears. Flip to white — and force the gradient-referencing paths
   back to currentColor, since a `url(#…)` stroke would otherwise stay put and
   the Instagram outline would vanish into its own background. */
.msg-channels a:hover .ico,
.msg-channels a:focus-visible .ico { color: #fff; }
.msg-channels a:hover .ico [stroke^="url"],
.msg-channels a:focus-visible .ico [stroke^="url"] { stroke: currentColor; }
.msg-channels a:hover .ico [fill^="url"],
.msg-channels a:focus-visible .ico [fill^="url"] { fill: currentColor; }
.msg-channels .ico svg { width: 21px; height: 21px; display: block; }
.msg-channels .label {
  font-size: .82rem; font-weight: 600; letter-spacing: .04em;
  padding-right: 58px; white-space: nowrap;
  opacity: 0; transform: translateX(8px);
  transition: opacity .25s ease .08s, transform .25s ease .08s;
}
.msg-channels a:hover .label, .msg-channels a:focus-visible .label { opacity: 1; transform: none; }

.msg-toggle {
  width: 56px; height: 56px; border-radius: 50%;
  border: 0; cursor: pointer;
  background: var(--light); color: var(--ink);
  display: grid; place-items: center;
  box-shadow: 0 12px 30px rgba(0,0,0,.45);
  transition: transform .3s cubic-bezier(.34,1.4,.64,1), background .2s;
}
.msg-toggle:hover { background: #fff; }
.msg-toggle svg { width: 24px; height: 24px; display: block; transition: transform .3s, opacity .2s; }
.msg-toggle .close-ico { display: none; }
.msg-fab.open .msg-toggle { transform: rotate(90deg); }
.msg-fab.open .msg-toggle .open-ico { display: none; }
.msg-fab.open .msg-toggle .close-ico { display: block; }

@media (max-width: 760px) {
  .msg-fab { right: 14px; bottom: 14px; }
  /* Thumb-sized, and the label can't be hovered on touch — it reveals on tap
     focus instead, so the pill stays a circle in normal use. */
  .msg-toggle { width: 52px; height: 52px; }
  .msg-channels a { width: 48px; height: 48px; }
  .msg-channels .ico { width: 48px; height: 48px; }
}
@media (prefers-reduced-motion: reduce) {
  .msg-channels li, .msg-channels a, .msg-toggle, .msg-channels .label { transition: none; }
  .msg-fab.open .msg-toggle { transform: none; }
}

/* ── Founder note ─────────────────────────────────────────────────────────
   Set in the serif at reading size rather than as a pull-quote: it should read
   like a person talking, not like a testimonial the marketing team wrote. */
.founder { display: grid; grid-template-columns: auto 1fr; gap: 34px; align-items: start; margin-top: 26px; }
/* Set by app.js when there is no photo, or when the file fails to load. Without
   it the empty second track survives and the note is squeezed into the auto
   column instead of using the full width. */
.founder.no-photo { grid-template-columns: 1fr; }
.founder-photo {
  width: 132px; height: 132px; border-radius: 50%; object-fit: cover;
  border: 1px solid var(--line); background: var(--surface); display: block;
}
.founder-note { font-family: var(--serif); font-size: 1.24rem; line-height: 1.42; color: var(--fg-mid); margin: 0 0 .7em; }
.founder-note:last-of-type { margin-bottom: 18px; }
.founder-sig { margin: 0; display: grid; gap: 2px; }
.founder-sig b { font-size: .98rem; color: var(--fg); }
.founder-sig span { font-size: .88rem; color: var(--fg-dim); }

/* ── Contact ──────────────────────────────────────────────────────────── */
/* Left-aligned now that the cards sit in the left column of a two-column
   section rather than centred in a narrow one. */
.contact-row { display: flex; flex-wrap: wrap; gap: 14px; justify-content: flex-start; margin-top: 28px; }
.form-title { font-family: var(--serif); font-weight: 400; font-size: 1.3rem; margin: 0 0 4px; }
.contact-card { display: flex; align-items: center; gap: 12px; background: var(--surface); border: 1px solid var(--line-soft); border-radius: 14px; padding: 16px 22px; min-width: 210px; transition: border-color .15s; }
.contact-card:hover { border-color: var(--fg); }
.contact-card .ico { display: flex; color: var(--fg-dim); }
.contact-card b { display: block; font-size: .95rem; color: var(--fg); }
.contact-card small { color: var(--fg-dim); font-size: .82rem; }

/* ── Final CTA + footer ───────────────────────────────────────────────── */
/* Tightened from 116px. The closing block is a heading, one line and two
   buttons — at that padding it occupied more vertical space than the
   pricing section, which has three cards in it. */
.final { padding: 76px 0; background: var(--bg-alt); border-top: 1px solid var(--line-soft); }
.final h2 { font-size: clamp(2.2rem, 4.6vw, 3.6rem); letter-spacing: -.024em; margin-bottom: 22px; }
.final .lede { color: var(--fg-mid); margin-left: auto; margin-right: auto; max-width: 520px; }

.footer { border-top: 1px solid var(--line-soft); padding: 30px 0; }
.footer-inner { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; justify-content: space-between; color: var(--fg-dim); font-size: .88rem; }
/* One line, not a stack. Three short items in a column made the footer twice
   as tall as its content needed and read like a nav; in a row they read as
   what they are — a copyright line with two links after it. `wrap` so they
   fall onto a second line on a narrow phone rather than overflowing. */
.footer-legal { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 20px; }
.footer p { margin: 0; }
.footer nav { display: flex; flex-wrap: wrap; gap: 22px; }
.footer a { color: var(--fg-dim); }
.footer a:hover { color: var(--fg); }

/* ── Responsive ───────────────────────────────────────────────────────── */
@media (max-width: 960px) {
  .hero-grid, .sec-head, .invert-grid, .install-grid, .fb-grid { grid-template-columns: 1fr; gap: 26px; align-items: start; }
  .hero-grid, .install-grid, .fb-grid { gap: 40px; }
  /* The group is a block <details> at every width now — the old
     grid-template-columns override here was left over from the two-column
     desktop layout and no longer applies to anything. */
  .faq-group { padding: 24px 0; }
  /* The category header is the control that folds a whole block away, so it
     needs a thumb-sized row. It was 32px. */
  .faq-group > summary { padding: 12px 0 16px; min-height: 44px; }
  /* Same for the section header — 35px, and it folds the largest thing on the
     page. The heading's own line box doesn't reach 44 at this size. */
  .faq-all > summary { padding: 6px 0; min-height: 44px; align-items: center; }
  .faq-lede { margin: 14px 0 26px; font-size: 1rem; }
  .big-num { font-size: clamp(6rem, 22vw, 10rem); }
}
@media (max-width: 760px) {
  /* Zoomed out a step on phones.
     The display sizes were picked on a desktop and then just inherited by
     mobile through the clamp() minimums — a 2.7rem minimum on an h1 means a
     375px screen renders it at the same 43px a 1440px screen does, so the
     headline eats the whole first screen and everything reads oversized.
     Each clamp minimum comes down roughly a fifth; the maximums are untouched,
     so nothing changes on desktop. */
  body { font-size: 15px; }
  h1 { font-size: clamp(2.1rem, 9vw, 4.3rem); }
  h2 { font-size: clamp(1.6rem, 7vw, 3rem); }
  .sec-head h2, .faq-all > summary h2 { font-size: clamp(1.6rem, 7vw, 2.9rem); }
  .final h2 { font-size: clamp(1.8rem, 8vw, 3.6rem); }
  .lede { font-size: 1.02rem; }
  .sec-head p, .section-lede, .faq-lede, .fb-lede { font-size: .98rem; }
  .hero-stats b { font-size: 2.6rem; }
  .invert-lede { font-size: clamp(1.25rem, 5.4vw, 2.2rem); }
  .plan-price b { font-size: 2.4rem; }
  .plan h3 { font-size: 1.3rem; }
  .fcard h3, .pillar h3, .steps h3 { font-size: 1.12rem; }
  .section, .invert { padding: 58px 0; }
  .hero { padding: 44px 0 52px; }
  .nav-links {
    display: none; position: absolute; top: 72px; left: 0; right: 0;
    flex-direction: column; gap: 0; background: var(--bg);
    border-bottom: 1px solid var(--line); padding: 8px 28px 18px;
  }
  .nav-links.open { display: flex; }
  .nav-links a { padding: 13px 0; border-bottom: 1px solid var(--line-soft); }
  .nav-links a:last-child { border-bottom: 0; }
  /* In the stacked menu the underline would collide with the row dividers,
     so the active row is marked with a left bar and full-strength text. */
  .nav-links a::after {
    left: -14px; right: auto; top: 13px; bottom: 13px;
    width: 2px; height: auto; border-radius: 2px;
    transform: scaleY(0); transform-origin: top;
  }
  .nav-links a.active::after { transform: scaleY(1); }
  .nav-toggle { display: block; margin-left: auto; }
  /* Two buttons now sit outside the hamburger, and brand + Demo + Buy now +
     toggle came to 422px inside a 375px bar — which pushed the menu button
     clean off the right edge, unreachable. The monogram alone still reads as
     the brand at this size, and dropping the wordmark buys back ~76px. The
     other pages keep theirs; only the home page carries two nav buttons. */
  .brand-name { display: none; }
  .nav-actions .btn-sm { padding: 9px 15px; font-size: .82rem; }
  /* Centred as a pair. Equal auto margins on both sides put the group in the
     middle of the bar; the toggle's own `margin-left: auto` has to go, or the
     two auto rules fight and the group drifts left — which is what it was
     doing. */
  .nav-actions { gap: 6px; margin-left: auto; margin-right: auto; }
  .nav-toggle { margin-left: 0; }
  /* The hero phone, kept whole but scaled down.
     At full size it is ~624px tall — a complete screenful of scrolling for a
     picture of a phone, shown on a phone. Removing it entirely leaves the hero
     ending on a bare button.
     Cropping it was the other option and it's worse: the bezel and the rounded
     corners ARE the thing that says "phone", so a clipped one reads as a
     rounded rectangle with a screenshot in it. Scaling keeps the silhouette
     intact and still cuts the height by a third. */
  .hero .phone { width: 190px; }
  .final { padding: 56px 0; }
  .hero-stats { flex-direction: column; }
  .hero-stats > div { padding: 18px 0; border-right: 0; border-bottom: 1px solid var(--line); }
  .hero-stats > div:last-child { border-bottom: 0; }
  .field-row { grid-template-columns: 1fr; }
  /* Photo above the words, not beside them — a 132px circle next to a 40ch
     column leaves the text nowhere to go on a phone. */
  .founder { grid-template-columns: 1fr; gap: 22px; }
  .founder-photo { width: 96px; height: 96px; }
  /* Wrap the install command instead of scrolling it. Shrinking the type to
     .66rem was an attempt to make one long line fit, and it failed twice over:
     10.6px is below what anyone reads comfortably, and it still didn't fit —
     the line was 438px in a 317px box, so it scrolled sideways anyway.
     Wrapping removes the horizontal movement entirely, which then buys back
     the type size. `pre-wrap` keeps the deliberate line breaks and the
     indentation; `break-word` handles the long --license=… run. Copy and paste
     still yields the same command. */
  .terminal pre { font-size: .72rem; white-space: pre-wrap; overflow-wrap: break-word; word-break: break-word; }
  .cta-row .btn { flex: 1 1 100%; text-align: center; }
  .audit summary { padding-inline: 20px; }
  .audit-grid > div { padding: 22px 20px 24px; }
  /* .68rem lands at 10.9px on a phone — under the ~12px floor where uppercase
     letterspaced text stops being readable and starts being decoration. */
  .audit-grid h4 { font-size: .78rem; letter-spacing: .1em; }
  .includes { padding: 26px 22px; }

  /* Touch targets. The menu button was a 30px-high glyph — the one control a
     phone visitor MUST hit to navigate, and the smallest thing on the page.
     44px is the accepted minimum; negative margin keeps the header height and
     the visual alignment unchanged while the hit area grows. */
  .nav-toggle {
    display: grid; place-items: center;
    min-width: 44px; min-height: 44px; margin-right: -8px;
  }
  .brand { min-height: 44px; }
  /* Footer and fine-print links were 16–17px tall — fine with a mouse, a
     coin-toss with a thumb. */
  .footer nav a, .footer-legal a { display: inline-block; padding: 7px 0; }
  .footer nav { gap: 14px 22px; }
}

/* ── Buy modal — name + licence email, collected before Stripe ───────────── */
.buy-modal {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(6, 6, 8, .78);
  display: flex; align-items: center; justify-content: center; padding: 24px;
  overflow: hidden;
  overscroll-behavior: none;
}
.buy-modal.hidden { display: none; }

.buy-card {
  width: 100%; max-width: 620px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 28px 26px 22px;
  max-height: 90vh; overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
.buy-card h3 { font-family: var(--serif); font-size: 1.5rem; margin: 0 0 6px; font-weight: 400; }
.buy-sub { color: var(--fg-dim); font-size: .9rem; margin: 0 0 20px; }

.buy-card label {
  display: block; font-size: .78rem; letter-spacing: .06em; text-transform: uppercase;
  color: var(--fg-dim); margin: 15px 0 7px;
}
.buy-card input {
  width: 100%; padding: 12px 14px; font: inherit; font-size: 1rem;
  color: var(--fg); background: var(--bg); border: 1px solid var(--line);
  border-radius: 10px;
}
.buy-card input:focus-visible { outline: 2px solid var(--fg); outline-offset: 2px; }
.buy-card button:focus-visible, .buy-card a:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }
.buy-card[tabindex="-1"]:focus { outline: none; }
.buy-summary { border: 1px solid var(--line); border-radius: 10px; padding: 16px; margin: 12px 0; }
.buy-summary strong { display: block; font-size: 1.25rem; font-variant-numeric: tabular-nums; }
.buy-summary p { margin: 8px 0 0; color: var(--fg-mid); font-size: .9rem; line-height: 1.5; }
.purchase-context { color: var(--fg-mid); font-size: 1rem; line-height: 1.6; margin-top: 20px; }
.field-error { color: #ff8b8b; font-size: .9rem; margin: 5px 0 0; }
.field-error:empty { display: none; }
.buy-actions { position: sticky; bottom: -22px; background: var(--surface); padding: 12px 0; z-index: 1; }
.buy-card input::placeholder { color: var(--fg-faint); }

/* The consent row is boxed and full-contrast rather than styled like the
   small print below it. Terms you have to agree to shouldn't look like terms
   you're meant to skim past. */
/* Scoped under .buy-card deliberately: `.buy-card label` above is one class
   plus one element, so a bare `.buy-agree` loses to it on specificity and the
   consent text comes out in the uppercase field-caption style. A block of legal
   text set in caps is measurably harder to read — the opposite of what a term
   someone must actually understand before agreeing to it needs to be. */
.buy-card .buy-agree {
  display: flex; gap: 11px; align-items: flex-start; cursor: pointer;
  margin: 20px 0 0; padding: 14px 15px;
  border: 1px solid var(--line); border-radius: 11px; background: var(--bg-alt);
  font-size: .84rem; line-height: 1.5; color: var(--fg-mid);
  text-transform: none; letter-spacing: normal;
}
.buy-card .buy-agree:hover { border-color: #3a3a40; }
.buy-card .buy-agree input {
  flex: none; width: 17px; height: 17px; margin: 1px 0 0;
  accent-color: var(--light); cursor: pointer;
}
.buy-card .buy-agree strong { color: var(--fg); }
.buy-card .buy-agree a { text-decoration: underline; text-underline-offset: 2px; }
.buy-error { color: #ff8b8b; font-size: .85rem; min-height: 1.1em; margin: 12px 0 0; }

.buy-actions { display: flex; gap: 10px; margin-top: 18px; }
.buy-actions .btn { flex: 1; justify-content: center; }
.buy-actions .btn.primary {
  background: var(--buy-blue);
  color: #fff;
  border-color: var(--buy-blue);
}
.buy-actions .btn.primary:hover {
  background: var(--buy-blue-hover);
  border-color: var(--buy-blue-hover);
  color: #fff;
}
/* Cancel inherits .btn, which is the solid light pill — so next to the primary
   it read as an equally weighted choice, and stacked on a phone it sat directly
   under "Continue to payment" looking like the same button twice. Ghosted, so
   the eye goes to the one that completes the purchase. */
.buy-actions .js-buy-cancel {
  background: transparent; color: var(--fg-dim); border-color: var(--line);
}
.buy-actions .js-buy-cancel:hover { background: transparent; color: var(--fg); border-color: var(--fg-dim); }

.buy-note { color: var(--fg-faint); font-size: .78rem; margin: 14px 0 0; line-height: 1.5; }

/* Embedded Checkout keeps the Stripe form on NEXFAN while Stripe retains
   control of every payment field inside its own isolated iframe. */
.payment-card {
  width: min(600px, calc(100% - 32px));
  max-width: 600px;
  max-height: min(720px, calc(100dvh - 32px));
  padding: 24px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.payment-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
  padding: 0 2px 18px;
  border-bottom: 1px solid var(--line-soft);
  flex: none;
}
.payment-head .buy-sub { margin-bottom: 0; }
.payment-kicker {
  margin: 0 0 7px;
  color: var(--fg-faint);
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.payment-close {
  flex: none;
  min-width: 54px;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: transparent;
  color: var(--fg-dim);
  font: inherit;
  font-size: .8rem;
  cursor: pointer;
}
.payment-close:hover,
.payment-close:focus-visible {
  border-color: var(--fg-dim);
  color: var(--fg);
}
.payment-close:focus-visible { outline: 2px solid var(--fg); outline-offset: 2px; }
.payment-loading {
  min-height: 320px;
  flex: 1;
  display: grid;
  place-items: center;
  color: var(--fg-dim);
  font-size: .9rem;
}
.payment-loading[hidden] { display: none; }
.compact-checkout {
  flex: 1;
  min-height: 0;
  padding: 18px 2px 2px;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
.compact-summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 16px;
  color: var(--fg-mid);
  font-size: .9rem;
}
.compact-summary strong {
  color: var(--fg);
  font-size: 1.05rem;
  white-space: nowrap;
}
.express-checkout { margin-bottom: 14px; }
.express-checkout.wallet-pending { min-height: 44px; visibility: hidden; }
.wallet-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 14px;
  color: var(--fg-faint);
  font-size: .7rem;
  text-transform: uppercase;
  letter-spacing: .08em;
}
.wallet-divider::before,
.wallet-divider::after { content: ''; flex: 1; height: 1px; background: var(--line); }
.payment-element { min-height: 180px; }
.compact-pay {
  width: 100%;
  min-height: 48px;
  margin-top: 16px;
  border: 1px solid var(--light);
  border-radius: 10px;
  background: var(--light);
  color: var(--ink);
  font: 600 .95rem/1 var(--sans);
  cursor: pointer;
}
.compact-pay:hover:not(:disabled) { background: #fff; }
.compact-pay:active:not(:disabled) { transform: translateY(1px); }
.compact-pay:disabled { cursor: wait; opacity: .55; }
.embedded-checkout {
  min-height: 420px;
  padding-top: 12px;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
.embedded-checkout iframe { display: block; width: 100% !important; }
.embedded-checkout[hidden] { display: none; }
.payment-card > .buy-error:empty { display: none; }

@media (max-width: 620px) {
  /* The checkout form ran 919px of content into a 731px card, so ~190px was
     always behind an internal scroll — including the heading, which is why it
     appeared cut off. Everything below trims that gap. */
  .buy-card {
    padding: 18px 16px 0;
    /* dvh, not vh. On iOS `vh` counts the area behind the collapsing browser
       chrome, so a 90vh card is taller than the screen and its top and bottom
       are unreachable — the same trap the vault sheet hit. vh stays first as
       the fallback for engines without dvh. */
    max-height: 92vh;
    max-height: 92dvh;
  }
  .buy-card h3 { font-size: 1.28rem; }
  .buy-sub { margin-bottom: 14px; font-size: .86rem; }
  .buy-card label { margin: 11px 0 5px; font-size: .72rem; }
  .buy-card input { padding: 10px 12px; font-size: 1rem; }
  .buy-hint { font-size: .72rem; margin-top: 5px; }
  .buy-card .buy-agree { margin-top: 14px; padding: 11px 12px; font-size: .78rem; line-height: 1.45; }
  .buy-note { font-size: .72rem; margin-top: 10px; }
  .payment-modal {
    padding: max(8px, env(safe-area-inset-top)) 8px max(8px, env(safe-area-inset-bottom));
    align-items: center;
  }
  .payment-card {
    width: 100%;
    height: auto;
    max-height: calc(100dvh - max(16px, env(safe-area-inset-top) + env(safe-area-inset-bottom)));
    padding: 0;
    border: 1px solid var(--line);
    border-radius: 14px;
  }
  .payment-head {
    min-height: 52px;
    align-items: center;
    gap: 12px;
    padding: 6px 10px;
    background: var(--surface);
  }
  .payment-head h3 {
    margin: 0;
    font-family: var(--sans);
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: -.01em;
  }
  .payment-kicker,
  .payment-head .buy-sub { display: none; }
  .payment-close { min-width: 54px; min-height: 38px; padding: 0 9px; }
  .payment-loading { min-height: 0; }
  .compact-checkout { flex: none; padding: 10px; overflow-y: auto; }
  .compact-summary { margin-bottom: 9px; font-size: .82rem; }
  .compact-summary strong { font-size: .95rem; }
  .express-checkout { margin-bottom: 8px; }
  .wallet-divider { margin-bottom: 8px; font-size: .64rem; }
  .payment-element { min-height: 0; }
  .compact-pay { min-height: 44px; margin-top: 10px; }
  .embedded-checkout {
    flex: 1;
    min-height: 0;
    padding-top: 0;
    background: #fff;
  }
  .payment-card > .buy-error:not(:empty) { margin: 10px 12px; }

  /* The submit button stops scrolling away. It sticks to the bottom of the
     card on its own surface, so "Continue to payment" is reachable the moment
     the form opens instead of after a scroll the buyer has to discover. */
  .buy-actions {
    flex-direction: column-reverse;
    position: sticky; bottom: 0;
    margin-top: 14px;
    padding: 12px 0 14px;
    background: var(--surface);
    border-top: 1px solid var(--line-soft);
  }
}
.buy-opt { color: var(--fg-faint); text-transform: none; letter-spacing: 0; font-size: .72rem; }
.buy-hint { color: var(--fg-faint); font-size: .76rem; margin: 7px 0 0; line-height: 1.5; }

/* ── Glass CTA buttons (adapted from Uiverse by shokat_2650) ───────────────
   Three deliberate changes from the source, each for a reason:

   1. SCOPED to .btn-glass. The original styles a bare `button` selector with
      `all: unset`. This page has four other <button> elements — the hamburger,
      the message bubble toggle, and Cancel/Continue in the buy modal — and that
      rule would have stripped every one of them.

   2. Applied to <a>, not <button>. "Demo" opens an external site and "Buy now"
      is an anchor jump; as buttons they'd lose middle-click, open-in-new-tab,
      copy-link and their meaning to a crawler.

   3. Recoloured for a dark page. The original assumes a light background —
      white frosted glass with rgba(50,50,50) text. On near-black that text is
      invisible. So the light variant keeps a bright fill with ink text (it
      stays the primary), and the dark variant is black glass with light text.

   The dotted-grid <svg> that came with the snippet was page decoration from the
   Uiverse preview, not part of the button. Dropped. */
@property --btn-angle-1 { syntax: "<angle>"; inherits: false; initial-value: -75deg; }
@property --btn-angle-2 { syntax: "<angle>"; inherits: false; initial-value: -45deg; }

.btn-glass-wrap {
  --btn-ease: cubic-bezier(0.25, 1, 0.5, 1);
  --btn-time: 400ms;
  position: relative; z-index: 2;
  display: inline-block;
  border-radius: 999vw;
  pointer-events: none;
  transition: transform var(--btn-time) var(--btn-ease);
}
/* If demoUrl is unset the <a> inside ships hidden; without this the wrap would
   still paint its shadow — a button-shaped smudge with no button. */
.btn-glass-wrap:has(> [hidden]) { display: none; }

.btn-glass-shadow {
  --cut: 2em;
  position: absolute;
  width: calc(100% + var(--cut)); height: calc(100% + var(--cut));
  top: calc(0% - var(--cut) / 2); left: calc(0% - var(--cut) / 2);
  filter: blur(clamp(2px, 0.125em, 12px));
  pointer-events: none;
}
.btn-glass-shadow::after {
  content: ""; position: absolute; z-index: 0; inset: 0;
  border-radius: 999vw;
  background: linear-gradient(180deg, rgba(0,0,0,.5), rgba(0,0,0,.25));
  width: calc(100% - var(--cut) - 0.25em);
  height: calc(100% - var(--cut) - 0.25em);
  top: calc(var(--cut) - 0.5em); left: calc(var(--cut) - 0.875em);
  transition: all var(--btn-time) var(--btn-ease);
  opacity: 1;
}

.btn-glass {
  --bw: clamp(1px, 0.0625em, 4px);
  position: relative; z-index: 3;
  display: inline-block;
  cursor: pointer; pointer-events: auto;
  -webkit-tap-highlight-color: rgba(0,0,0,0);
  border-radius: 999vw;
  background: linear-gradient(-75deg, rgba(255,255,255,.92), rgba(255,255,255,1), rgba(255,255,255,.92));
  box-shadow:
    inset 0 0.125em 0.125em rgba(0,0,0,.05),
    inset 0 -0.125em 0.125em rgba(255,255,255,.5),
    0 0.25em 0.125em -0.125em rgba(0,0,0,.4);
  transition: transform var(--btn-time) var(--btn-ease), box-shadow var(--btn-time) var(--btn-ease);
}
.btn-glass:hover { transform: scale(.975); }

.btn-glass > span {
  position: relative; display: block;
  user-select: none; -webkit-user-select: none;
  font-family: var(--sans);
  letter-spacing: -.01em; font-weight: 500; font-size: .85rem;
  color: var(--ink);
  padding-inline: 1.35em; padding-block: .82em;
  transition: color var(--btn-time) var(--btn-ease);
}
/* The travelling sheen. */
.btn-glass > span::after {
  content: ""; display: block; position: absolute; z-index: 3;
  width: calc(100% - var(--bw)); height: calc(100% - var(--bw));
  top: calc(0% + var(--bw) / 2); left: calc(0% + var(--bw) / 2);
  border-radius: 999vw; overflow: clip;
  background: linear-gradient(var(--btn-angle-2),
    rgba(255,255,255,0) 0%, rgba(255,255,255,.5) 40% 50%, rgba(255,255,255,0) 55%);
  background-size: 200% 200%; background-position: 0% 50%; background-repeat: no-repeat;
  mix-blend-mode: screen; pointer-events: none;
  transition: background-position calc(var(--btn-time) * 1.25) var(--btn-ease),
              --btn-angle-2 calc(var(--btn-time) * 1.25) var(--btn-ease);
}
.btn-glass:hover > span::after { background-position: 25% 50%; }
.btn-glass:active > span::after { background-position: 50% 15%; --btn-angle-2: -15deg; }

/* Conic-gradient rim. */
.btn-glass::after {
  content: ""; position: absolute; z-index: 1; inset: 0;
  border-radius: 999vw;
  width: calc(100% + var(--bw)); height: calc(100% + var(--bw));
  top: calc(0% - var(--bw) / 2); left: calc(0% - var(--bw) / 2);
  padding: var(--bw); box-sizing: border-box;
  background: conic-gradient(from var(--btn-angle-1) at 50% 50%,
      rgba(0,0,0,.5), rgba(0,0,0,0) 5% 40%, rgba(0,0,0,.5) 50%, rgba(0,0,0,0) 60% 95%, rgba(0,0,0,.5)),
    linear-gradient(180deg, rgba(255,255,255,.5), rgba(255,255,255,.5));
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  transition: --btn-angle-1 500ms ease;
}
.btn-glass:hover::after { --btn-angle-1: -125deg; }
.btn-glass:active::after { --btn-angle-1: -75deg; }

/* Purchase actions share one unmistakable blue across the page. */
.btn.js-buy-jump {
  background: var(--buy-blue);
  border-color: var(--buy-blue);
  color: #fff;
  box-shadow: 0 10px 28px -16px rgba(0, 113, 227, .9);
}
.btn.js-buy-jump:hover {
  background: var(--buy-blue-hover);
  border-color: var(--buy-blue-hover);
  color: #fff;
}
.btn-glass.js-buy-jump {
  background: linear-gradient(-75deg, #0068d1, #1684ed, #0068d1);
  box-shadow:
    inset 0 .125em .125em rgba(255, 255, 255, .18),
    inset 0 -.125em .125em rgba(0, 36, 84, .3),
    0 .3em .55em -.35em rgba(0, 113, 227, .7);
}
.btn-glass.js-buy-jump > span { color: #fff; text-shadow: 0 1px 1px rgba(0, 37, 82, .45); }
.btn-glass-wrap:has(.js-buy-jump) .btn-glass-shadow::after {
  background: rgba(0, 113, 227, .14);
}
.js-buy-jump:focus-visible {
  outline: 3px solid var(--buy-blue-ring);
  outline-offset: 3px;
}

/* Dark variant — the Demo button, kept black as it is today. */
.btn-glass--dark {
  background: linear-gradient(-75deg, rgba(28,28,32,.85), rgba(20,20,24,.95), rgba(28,28,32,.85));
  -webkit-backdrop-filter: blur(clamp(1px, 0.125em, 4px));
  backdrop-filter: blur(clamp(1px, 0.125em, 4px));
  box-shadow:
    inset 0 0.125em 0.125em rgba(0,0,0,.3),
    inset 0 -0.125em 0.125em rgba(255,255,255,.08),
    0 0.25em 0.125em -0.125em rgba(0,0,0,.5);
}
.btn-glass--dark > span { color: var(--fg); text-shadow: 0 0.15em 0.1em rgba(0,0,0,.35); }
.btn-glass--dark::after {
  background: conic-gradient(from var(--btn-angle-1) at 50% 50%,
      rgba(255,255,255,.45), rgba(255,255,255,0) 5% 40%, rgba(255,255,255,.45) 50%,
      rgba(255,255,255,0) 60% 95%, rgba(255,255,255,.45)),
    linear-gradient(180deg, rgba(255,255,255,.14), rgba(255,255,255,.14));
}

/* The press tilt. Skipped for reduced-motion, and on coarse pointers the
   angle animations are pinned since there is no hover to drive them. */
.btn-glass-wrap:has(.btn-glass:active) { transform: rotate3d(1, 0, 0, 25deg); }
@media (hover: none) and (pointer: coarse) {
  .btn-glass::after, .btn-glass:hover::after, .btn-glass:active::after { --btn-angle-1: -75deg; }
  .btn-glass > span::after, .btn-glass:active > span::after { --btn-angle-2: -45deg; }
}
@media (prefers-reduced-motion: reduce) {
  .btn-glass, .btn-glass-wrap, .btn-glass > span::after, .btn-glass::after { transition: none; }
  .btn-glass:hover { transform: none; }
  .btn-glass-wrap:has(.btn-glass:active) { transform: none; }
}
