/* AE2CSS landing — static, no framework, compositor-safe motion only.
   The site itself is the proof: every animation here is transform/opacity/mask. */

:root {
  --bg: #070b11;
  --bg-2: #0a1119;
  --panel: rgba(255, 255, 255, 0.028);
  --panel-2: rgba(255, 255, 255, 0.045);
  --line: rgba(255, 255, 255, 0.09);
  --line-strong: rgba(255, 255, 255, 0.16);
  --text: #eef4fb;
  --muted: #93a4b8;
  /* #6a7c91 measured 4.39:1 on a card and 4.00:1 behind "Coming soon" — both below
     AA. This clears every surface it lands on with margin (5.17:1 worst). */
  --muted-2: #7d8fa4;
  --accent: #5fc4ff;
  --accent-2: #0047d1;
  --accent-mid: #2f7dff;
  /* Flat fill for confirm buttons. Two steps deeper than --accent-mid because
     white on #2f7dff is only 3.82:1 — below AA. This is 5.0:1. */
  --accent-solid: #1f6ae0;
  /* Two stops only. The cyan used to live at the end of this diagonal, which put
     it straight under the label — 2.37:1 on the Download CTA, the worst text on
     the site. Cyan is now a separate bottom band on .btn--primary, where there is
     no text to wash out. Both stops carry white safely (#1f6ae0 is 5.0:1). */
  --accent-grad: linear-gradient(135deg, #0040c4 0%, #1f6ae0 100%);
  /* Cyan light source, kept as its own layer so it can be placed away from text. */
  --accent-cyan: #5fc4ff;
  /* lighter, higher-contrast 2-stop sweep for text-clipped gradients — the 3-stop
     surface gradient reads as flat/dark when clipped to short headline/stat text. */
  --text-grad: linear-gradient(100deg, #0073ff 0%, #49cbff 100%);
  --good: #57d9a3;
  --radius: 22px;
  --radius-sm: 14px;
  --maxw: 1080px;
  --ease: cubic-bezier(0.2, 0.8, 0.2, 1);
  --float: cubic-bezier(0.37, 0, 0.63, 1);    /* ease-in-out-sine — smooth glide between float waypoints */
  --float-in: cubic-bezier(0.25, 1, 0.5, 1);  /* ease-out-quart — gentle decelerating lift-off */
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  min-height: 100vh;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  line-height: 1.6;
  overflow-x: hidden;
}

a { color: inherit; }

.wrap {
  width: min(var(--maxw), 100% - 44px);
  margin-inline: auto;
}

section { position: relative; }

.eyebrow {
  margin: 0 0 14px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
}

h1, h2, h3 { letter-spacing: -0.03em; line-height: 1.05; margin: 0; }

.section-head { max-width: 640px; margin-bottom: 44px; }
.section-head h2 { font-size: clamp(28px, 4vw, 42px); }
.section-head p { color: var(--muted); font-size: 17px; margin: 16px 0 0; }

.grad-text {
  background: var(--text-grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* background-clip:text paints to the content box; the right overhang of a bold
     final glyph ("b") gets shaved. Pad the paint box and undo it in layout. */
  padding-right: 0.08em;
  margin-right: -0.08em;
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 14px 24px;
  border-radius: var(--radius-sm);
  font-size: 16px;
  font-weight: 600;
  text-decoration: none;
  border: 1px solid transparent;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform 0.14s var(--ease), box-shadow 0.14s var(--ease), background 0.14s;
}
/* RESERVED FOR THE DOWNLOAD CTA. The gradient + glow is the one loudest thing on
   the page, so it marks exactly one action: get the extension. Anything else that
   needs an affirmative button — form submits, dialog confirms — uses .btn--confirm.
   Adding a second gradient button anywhere costs the real CTA its pull. */
.btn--primary {
  background:
    /* Gloss is brighter than it looks safe to be, because the fade ends at 42% —
       above the label's cap height (y=18 of 56 = 32%) the highlight is free, and
       the button's worst point is down in the cyan band anyway. Raising .22 -> .38
       while tightening 50% -> 42% costs exactly nothing: 4.76:1 either way. */
    linear-gradient(180deg, rgba(255, 255, 255, 0.38) 0%, rgba(255, 255, 255, 0) 42%),
    /* Cyan hugs the bottom edge and is gone by 40% up — the label's descenders sit
       at 66% of the height, so it never crosses the text. Putting it here instead
       of at the end of the diagonal is what lets it be genuinely visible (16% of
       the face vs 0.5% in the corner) while contrast goes UP, not down. */
    linear-gradient(to top, rgba(95, 196, 255, 0.7) 0%, rgba(95, 196, 255, 0) 40%),
    var(--accent-grad);
  color: #fff;
  box-shadow:
    /* Top edge catch-light. Sits in the first 2px, far above the label, so its
       brightness is unconstrained by contrast — raised .4 -> .7. */
    inset 0 1px 2px rgba(255, 255, 255, 0.7),
    inset 0 -14px 18px rgba(0, 30, 130, 0.4),
    0 14px 34px rgba(0, 96, 255, 0.34);
}
.btn--primary:hover {
  transform: translateY(-2px);
  box-shadow:
    inset 0 1px 2px rgba(255, 255, 255, 0.45),
    inset 0 -14px 18px rgba(0, 30, 130, 0.4),
    0 20px 44px rgba(0, 96, 255, 0.46);
}
.btn--ghost {
  background: var(--panel);
  border-color: var(--line-strong);
  color: var(--text);
}
.btn--ghost:hover { transform: translateY(-2px); background: var(--panel-2); }

/* Confirm — the affirmative action in a dialog or form (cookie Accept, form
   submits). Flat brand blue: reads as the active choice without borrowing the
   gradient + glow, which are reserved for the Download CTA (see --accent-solid). */
.btn--confirm {
  background: var(--accent-solid);
  border-color: transparent;
  color: #fff;
}
.btn--confirm:hover { background: #2775f0; }

/* Kill the square mobile tap flash; give a rounded focus ring (box-shadow follows border-radius) */
a, button, summary { -webkit-tap-highlight-color: transparent; }
:focus:not(:focus-visible) { outline: none; }
.btn:focus-visible,
.code-toggle:focus-visible,
.nav-links a:focus-visible {
  outline: none;
  /* small blur (not just spread) so the ring's implied corner radius softens into
     the element's own curve instead of tracing a crisper, larger-radius arc */
  box-shadow: 0 0 2px 2px rgba(90, 150, 255, 0.6);
}
.btn--primary:focus-visible {
  box-shadow: 0 0 2px 2px rgba(160, 205, 255, 0.85), 0 14px 34px rgba(0, 96, 255, 0.34);
}
.btn--confirm:focus-visible {
  box-shadow: 0 0 2px 2px rgba(160, 205, 255, 0.85);
}

/* ---------- Nav ---------- */
.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  backdrop-filter: blur(14px);
  background: rgba(7, 11, 17, 0.72);
  border-bottom: 1px solid var(--line);
}
.nav .wrap { display: flex; align-items: center; justify-content: space-between; height: 64px; }
.brand { display: flex; align-items: center; gap: 10px; font-weight: 700; letter-spacing: -0.02em; text-decoration: none; }
.brand-mark {
  width: 42px; height: 42px;
  background: url("/logo.svg") center / contain no-repeat;
  /* drop-shadow lives here (not baked into the SVG as a filter) — SVG-internal
     feGaussianBlur filters get rasterized off-DPR in some mobile/WebKit paths and
     render soft/blurry on Retina phones; a CSS filter stays crisp at any DPR. */
  filter: drop-shadow(0 3px 3px rgba(0, 45, 150, 0.5));
  /* compositor-safe: only transform/filter animate on hover */
  transform: scale(1);
  transition: transform 0.25s ease, filter 0.25s ease;
  will-change: transform;
}
.brand:hover .brand-mark {
  transform: scale(1.12);
  filter: drop-shadow(0 6px 10px rgba(0, 90, 255, 0.65));
}
@media (prefers-reduced-motion: reduce) {
  .brand-mark, .brand:hover .brand-mark { transition: filter 0.25s ease; transform: none; }
}
.nav-links { display: flex; align-items: center; gap: 26px; }
.nav-links a { color: var(--muted); text-decoration: none; font-size: 15px; transition: color 0.14s; }
.nav-links a:hover { color: var(--text); }
.nav-links a.nav-cta { padding: 9px 16px; font-size: 14px; color: #fff; }

/* Mobile menu — CSS-only (hidden checkbox + label), so it works on every page,
   including the ones that don't load app.js. The checkbox is a preceding sibling of
   both the burger and .nav-links, so :checked ~ … drives them without JS. */
/* Visually hidden but still operable — the burger <label> is its visible surface.
   Was opacity:0, which left an invisible 1x1 control catching the first Tab on
   every page. clip-path hides it the same way without lying about where focus is;
   the ring is drawn on the sibling burger instead (see .nav-toggle:focus-visible). */
.nav-toggle {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* Above 720px the burger is hidden and the toggle controls nothing, so take it out
   of the tab order entirely rather than leaving a dead stop before the nav links. */
@media (min-width: 721px) {
  .nav-toggle { display: none; }
}
.nav-burger {
  display: none; /* desktop: hidden, links show inline */
  flex-direction: column; justify-content: center; gap: 5px;
  width: 44px; height: 44px; padding: 11px; margin-right: -11px;
  box-sizing: border-box; cursor: pointer; z-index: 2;
}
.nav-burger span {
  display: block; width: 22px; height: 2px; border-radius: 2px; background: var(--text);
  transition: transform .25s var(--ease), opacity .2s ease;
}
@media (max-width: 720px) {
  .nav-burger { display: flex; border-radius: 10px; }
  /* The input is clipped, so its focus ring has to be drawn on the visible label. */
  .nav-toggle:focus-visible ~ .nav-burger { box-shadow: 0 0 2px 2px rgba(90, 150, 255, 0.6); }
  .nav-links {
    position: absolute; top: 100%; left: 0; right: 0;
    display: none; flex-direction: column; align-items: stretch; gap: 0;
    padding: 6px 22px 14px;
    background: rgba(7, 11, 17, 0.97);
    backdrop-filter: blur(14px);
    border-bottom: 1px solid var(--line);
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.4);
  }
  .nav-toggle:checked ~ .nav-links { display: flex; }
  .nav-links a { padding: 14px 2px; font-size: 16px; border-bottom: 1px solid var(--line); }
  .nav-links a.nav-cta { margin-top: 12px; text-align: center; justify-content: center; padding: 13px 16px; border-bottom: none; }
  /* burger morphs to an X while open */
  .nav-toggle:checked ~ .nav-burger span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .nav-toggle:checked ~ .nav-burger span:nth-child(2) { opacity: 0; }
  .nav-toggle:checked ~ .nav-burger span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
}
@media (prefers-reduced-motion: reduce) { .nav-burger span { transition: none; } }

/* ---------- Hero ---------- */
.hero { padding: 96px 0 72px; text-align: center; overflow: hidden; }
.hero h1 { font-size: clamp(38px, 6.4vw, 72px); max-width: 14ch; margin-inline: auto; }
.hero-sub { color: var(--muted); font-size: clamp(17px, 2.2vw, 20px); max-width: 56ch; margin: 22px auto 0; }
.hero-cta { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; margin-top: 34px; }
.hero-trust { margin-top: 22px; color: var(--muted-2); font-size: 14px; letter-spacing: 0.01em; }
.hero-trust b { color: var(--muted); font-weight: 600; }

/* Aurora glow behind hero — static blur, animated transform only (compositor-safe) */
.aurora { position: absolute; inset: -20% 0 auto 0; height: 120%; z-index: -1; pointer-events: none; }
.aurora span {
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
  opacity: 0.55;
  will-change: transform;
}
.aurora .a1 { width: 460px; height: 460px; left: 8%; top: 2%; background: radial-gradient(circle, #2f6dff, transparent 70%); animation: drift1 16s var(--ease) infinite; }
.aurora .a2 { width: 520px; height: 520px; right: 6%; top: 8%; background: radial-gradient(circle, #0047d1, transparent 70%); animation: drift2 19s var(--ease) infinite; }
.aurora .a3 { width: 380px; height: 380px; left: 42%; top: 26%; background: radial-gradient(circle, #2cc2ff, transparent 70%); opacity: 0.4; animation: drift1 22s var(--ease) infinite reverse; }
@keyframes drift1 { 50% { transform: translate3d(60px, 40px, 0) scale(1.12); } }
@keyframes drift2 { 50% { transform: translate3d(-50px, 30px, 0) scale(1.08); } }

/* Hero motion — a one-shot staged entrance on load (plays once, then rests, so it never
   competes with reading) + a slow recurring light-sweep across the gradient phrase.
   Pure CSS: no JS, and only transform / opacity / mask animate. Degrades to fully visible. */
.hero .eyebrow,
.hero h1,
.hero-sub,
.hero-cta,
.hero-trust { opacity: 0; animation: hero-rise 0.9s var(--ease) both; }
.hero .eyebrow { animation-delay: 0.06s; }
.hero h1 { animation-delay: 0.16s; animation-timing-function: cubic-bezier(0.2, 1.15, 0.3, 1); } /* gentle spring */
.hero-sub { animation-delay: 0.32s; }
.hero-cta { animation-delay: 0.46s; }
.hero-trust { animation-delay: 0.6s; }
@keyframes hero-rise { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }

/* light catches the gradient words every few seconds — a bright band layered as a SECOND
   background on top of the accent gradient, both clipped to the glyphs. Because the sweep
   rides the element's own letters (not a duplicated text copy), it tracks the text even when
   the phrase wraps to multiple lines. Only background-position animates (a tiny text-sized
   repaint), so the cost stays negligible. */
.hero .grad-text {
  background:
    linear-gradient(100deg, transparent 44%, rgba(233, 240, 255, 0.9) 50%, transparent 56%),
    var(--text-grad);
  -webkit-background-clip: text;
          background-clip: text;
  background-size: 250% 100%, auto;
  background-position: 120% 0, 0 0;
  animation: hero-shine 5s var(--ease) infinite;
}
@keyframes hero-shine {
  /* glint glides across slowly over ~4s (0→80% of the 5s loop), its bright center crossing
     the text at ~2s after load, then parks off-text and rests for the final ~1s of the loop. */
  0%   { background-position: 120% 0, 0 0; }  /* starts off-text, ready to sweep in */
  80%  { background-position: -20% 0, 0 0; }  /* ~4s slow glide completes its pass */
  100% { background-position: -20% 0, 0 0; }  /* brief rest until the loop wraps */
}

/* ---------- Generic card ---------- */
.card {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}

/* ---------- Showcase ---------- */
.showcase { padding: 40px 0 90px; }
.demo-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 22px; }
@media (max-width: 760px) { .demo-grid { grid-template-columns: 1fr; } }

.demo-card { position: relative; overflow: hidden; display: flex; flex-direction: column; }
.demo-stage {
  position: relative;
  height: 260px;
  display: grid;
  place-items: center;
  background:
    radial-gradient(circle at 50% 40%, rgba(47, 125, 255, 0.1), transparent 70%),
    linear-gradient(180deg, #0b1623, #080d15);
  border-bottom: 1px solid var(--line);
  overflow: hidden;
}
/* Embedded AE2CSS export (self-contained looping widget, transparent bg) */
.demo-frame { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; background: transparent; }
.demo-meta { padding: 16px 18px; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.demo-meta h3 { font-size: 16px; }
.demo-meta .cap { color: var(--muted-2); font-size: 13px; margin-top: 3px; font-variant-numeric: tabular-nums; }
.code-toggle {
  flex: none;
  padding: 7px 13px;
  font-size: 13px;
  font-weight: 600;
  border-radius: 10px;
  border: 1px solid var(--line-strong);
  background: var(--panel-2);
  color: var(--text);
  cursor: pointer;
  transition: background 0.14s, transform 0.14s;
}
.code-toggle:hover { background: rgba(47, 125, 255, 0.16); transform: translateY(-1px); }
/* code overlays the preview in place — no layout shift when toggled */
.demo-code {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 260px; /* = .demo-stage height */
  background: rgba(6, 10, 16, 0.97);
  overflow: auto;
  z-index: 2;
  opacity: 0;
  visibility: hidden;
  transform: translateY(6px);
  transition: opacity 0.18s var(--ease), transform 0.18s var(--ease), visibility 0.18s;
}
.demo-card.is-open .demo-code { opacity: 1; visibility: visible; transform: none; }
/* ─────────────────────────────────────────────────────────────────────────
   CANONICAL SCROLLBAR — dark, on-brand. Design-system utility.
   Add class `fpt-scroll` to ANY scrollable container (overflow:auto/scroll).
   This is the ONLY scrollbar style on the site — do NOT hand-roll
   ::-webkit-scrollbar rules elsewhere. `.demo-code` and `.cmp-scroll` are
   retrofitted below so existing scroll areas already match.
   ───────────────────────────────────────────────────────────────────────── */
:root { --scroll-thumb: rgba(124, 183, 255, 0.28); --scroll-thumb-hover: rgba(124, 183, 255, 0.5); }
.fpt-scroll, .demo-code, .cmp-scroll { scrollbar-width: thin; scrollbar-color: var(--scroll-thumb) transparent; }
.fpt-scroll::-webkit-scrollbar, .demo-code::-webkit-scrollbar, .cmp-scroll::-webkit-scrollbar { width: 8px; height: 8px; }
.fpt-scroll::-webkit-scrollbar-track, .demo-code::-webkit-scrollbar-track, .cmp-scroll::-webkit-scrollbar-track { background: transparent; }
.fpt-scroll::-webkit-scrollbar-thumb, .demo-code::-webkit-scrollbar-thumb, .cmp-scroll::-webkit-scrollbar-thumb {
  background: var(--scroll-thumb);
  border-radius: 8px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
.fpt-scroll::-webkit-scrollbar-thumb:hover, .demo-code::-webkit-scrollbar-thumb:hover, .cmp-scroll::-webkit-scrollbar-thumb:hover { background: var(--scroll-thumb-hover); background-clip: padding-box; }
.fpt-scroll::-webkit-scrollbar-corner, .demo-code::-webkit-scrollbar-corner, .cmp-scroll::-webkit-scrollbar-corner { background: transparent; }
.demo-code pre { margin: 0; padding: 16px 18px; }
.demo-code code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12.5px;
  line-height: 1.7;
  color: #b9c8da;
  white-space: pre;
}
.demo-code .k { color: #7cb7ff; }   /* selector/keyword */
.demo-code .p { color: #c89bff; }   /* property */
.demo-code .v { color: #8fe3c0; }   /* value */
.demo-code .c { color: #7a8b9e; }   /* comment — #5a6b7d was 3.62:1 on this panel */

/* ===== Demo 1: orbital particles ===== */
.orbit { position: relative; width: 150px; height: 150px; animation: spin 7s linear infinite; will-change: transform; }
.orbit b { position: absolute; top: 50%; left: 50%; transform: rotate(var(--a)) translateX(64px); }
.orbit i {
  position: absolute; width: 15px; height: 15px; margin: -7.5px;
  border-radius: 50%; background: var(--accent-grad);
  animation: pulse 2.2s var(--ease) infinite; animation-delay: var(--d);
  will-change: transform, opacity;
}
.orbit .core {
  position: absolute; top: 50%; left: 50%; width: 26px; height: 26px; margin: -13px;
  border-radius: 50%; background: radial-gradient(circle at 35% 35%, #bcd6ff, #5fc4ff 60%, #3a64c8);
  animation: pulse 2.2s var(--ease) infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes pulse { 0%, 100% { transform: scale(0.55); opacity: 0.45; } 50% { transform: scale(1); opacity: 1; } }

/* ===== Demo 2: radial spectrum — circular visualizer, 3 acts on one 12s master =====
   Act 1 calm bars → Act 2 radar scan → Act 3 beat-drop with pulse rings, then loops.
   Everything is transform/opacity only; the whole ring rotates once per loop. */
.rsp { position: relative; width: 200px; height: 200px; animation: rsp-spin 12s linear infinite; will-change: transform; }
@keyframes rsp-spin { to { transform: rotate(360deg); } }

/* pulsing nucleus — calm, then beats on the act-3 drop */
.rsp-core {
  position: absolute; left: 50%; top: 50%; width: 36px; height: 36px; margin: -18px;
  border-radius: 50%;
  background: radial-gradient(circle at 36% 32%, #cfe0ff, #5fc4ff 58%, #3a64c8);
  animation: rsp-beat 12s var(--ease) infinite; will-change: transform;
}
@keyframes rsp-beat {
  0%, 30% { transform: scale(0.82); }
  40% { transform: scale(0.92); }
  68% { transform: scale(0.9); }
  73% { transform: scale(1.32); }
  79% { transform: scale(1); }
  85% { transform: scale(1.28); }
  92% { transform: scale(0.95); }
  100% { transform: scale(0.82); }
}

/* 36 frequency bars — bottom pinned at the hub, scaleY = level, delay = travelling wave */
.rsp b {
  position: absolute; left: 50%; top: 50%; width: 3.5px; height: 30px; margin: -30px 0 0 -1.75px;
  border-radius: 3px; background: linear-gradient(#0047d1, #5fc4ff);
  transform-origin: 50% 100%;
  transform: rotate(var(--a)) translateY(-24px) scaleY(0.25);
  animation: rsp-bar 2.4s var(--ease) infinite alternate;
  animation-delay: calc(var(--i) * -0.16s);
  will-change: transform;
}
@keyframes rsp-bar {
  0% { transform: rotate(var(--a)) translateY(-24px) scaleY(0.22); }
  100% { transform: rotate(var(--a)) translateY(-24px) scaleY(1); }
}

/* act 2 — radar wedge sweeps the ring, only visible mid-loop */
.rsp-sweep {
  position: absolute; left: 50%; top: 50%; width: 150px; height: 150px; margin: -75px;
  border-radius: 50%;
  background: conic-gradient(from 0deg, transparent 0deg, rgba(95, 196, 255, 0.04) 30deg, rgba(95, 196, 255, 0.5) 78deg, transparent 90deg);
  opacity: 0; animation: rsp-radar 12s linear infinite; will-change: transform, opacity;
}
@keyframes rsp-radar {
  0% { transform: rotate(0deg); opacity: 0; }
  30% { transform: rotate(150deg); opacity: 0; }
  37% { opacity: 0.9; }
  58% { opacity: 0.9; }
  66% { transform: rotate(620deg); opacity: 0; }
  100% { transform: rotate(720deg); opacity: 0; }
}

/* act 3 — concentric pulse rings burst outward on the beat (static border, scaled) */
.rsp-ring {
  position: absolute; left: 50%; top: 50%; width: 60px; height: 60px; margin: -30px;
  border-radius: 50%; border: 1.5px solid rgba(124, 226, 255, 0.85);
  opacity: 0; animation: rsp-pulse 12s var(--ease) infinite; animation-delay: var(--p);
  will-change: transform, opacity;
}
@keyframes rsp-pulse {
  0%, 64% { transform: scale(0.5); opacity: 0; }
  68% { opacity: 0.85; }
  90% { transform: scale(2.4); opacity: 0; }
  100% { transform: scale(2.4); opacity: 0; }
}

/* ===== Demo 3: particle drift — a swarm of particles wanders on smooth, out-of-phase
   loops (horizontal sway × vertical bob = organic Lissajous paths) and gently twinkles.
   Fixed 16-particle count, everything compositor-only (transform + opacity) → smooth on
   phones. No shared clock: every particle has its own periods so the field never syncs. */
.pf { position: relative; width: 240px; height: 240px; }

/* <b> = the carrier: holds the base position and the horizontal sway loop */
.pf b {
  position: absolute; left: var(--x); top: var(--y);
  animation: pf-sway var(--sx, 8s) ease-in-out infinite alternate;
  animation-delay: var(--dx, 0s); will-change: transform;
}
/* <i> = the particle itself: vertical bob + opacity twinkle, on their own periods */
.pf i {
  display: block; width: var(--w, 8px); height: var(--w, 8px); border-radius: 50%;
  background: var(--pc, #7cc0ff); opacity: var(--o0, 0.3);
  animation: pf-bob var(--sy, 9s) ease-in-out infinite alternate,
             pf-tw var(--tw, 4.5s) ease-in-out infinite;
  animation-delay: var(--dy, 0s), var(--dt, 0s); will-change: transform, opacity;
}
/* soft bokeh dots — a radial falloff instead of a hard edge */
.pf .glow { background: radial-gradient(circle, var(--pc), transparent 68%); }
/* tiny bright sparks */
.pf .spark { background: #eaf2ff; }

@keyframes pf-sway { from { transform: translateX(calc(var(--ax, 20px) * -1)); } to { transform: translateX(var(--ax, 20px)); } }
@keyframes pf-bob  { from { transform: translateY(calc(var(--ay, 16px) * -1)); } to { transform: translateY(var(--ay, 16px)); } }
@keyframes pf-tw   { 0%, 100% { opacity: var(--o0, 0.3); } 50% { opacity: var(--o1, 1); } }

/* ===== Demo 4: kinetic title — letters assemble, payoff line rises ===== */
.title-reveal { width: 100%; padding: 0 26px; }
.title-reveal .line { display: block; }
.title-reveal .line + .line { margin-top: 8px; }

/* line 1: each letter flies in from below and snaps into place (springy overshoot) */
.title-reveal .l1 { font-size: 0; line-height: 1.12; white-space: nowrap; }
.title-reveal .ch {
  display: inline-block;
  font-size: clamp(24px, 5vw, 34px);
  font-weight: 700;
  letter-spacing: -0.03em;
  opacity: 0;
  transform: translateY(120%) scale(0.6) rotate(-10deg);
  animation: letterIn 4.0s infinite;
  animation-delay: calc(var(--i) * 0.07s);
  will-change: transform, opacity;
}

/* line 2: typed like a terminal — single monospaced element, width revealed in ch units
   (1 char = 1ch, so word pauses + a caret that follows the typing need no measurement) */
.title-reveal .l2 { overflow: visible; white-space: nowrap; }
.title-reveal .typed {
  display: inline-block;
  box-sizing: content-box;  /* width = exact text width; the border-right caret must NOT eat into it */
  overflow: hidden;
  white-space: nowrap;
  width: 0;
  font-size: clamp(24px, 5vw, 34px);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.15;             /* caret (border) a touch taller than the glyphs */
  /* gradient fixed to the FULL phrase width so typed text keeps stable colors as it grows */
  background: var(--text-grad);
  background-size: 7.4em 100%;
  background-repeat: no-repeat;
  background-position: left center;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  border-right: 0.06em solid #9fc1ff;   /* the caret — sits at the reveal edge */
  animation:
    typeW 4.0s steps(1, end) infinite,
    caretBlink 0.75s steps(1, end) infinite,
    lineLife 4.0s var(--ease) infinite;
  will-change: width;
}

@keyframes letterIn {
  0% {
    opacity: 0;
    transform: translateY(120%) scale(0.6) rotate(-10deg);
    animation-timing-function: cubic-bezier(0.2, 1.5, 0.3, 1); /* overshoot — the lively bit */
  }
  24% {
    opacity: 1;
    transform: translateY(0) scale(1) rotate(0deg);
  }
  72% {
    opacity: 1;
    transform: translateY(0) scale(1) rotate(0deg);
    animation-timing-function: cubic-bezier(0.6, 0, 0.35, 1);
  }
  90%, 100% {
    opacity: 0;
    transform: translateY(-55%) scale(0.92) rotate(5deg);
  }
}
/* one stop per CHARACTER at its exact measured right edge (em) → the reveal always lands on a
   glyph boundary, never mid-letter. steps(1) on the animation snaps each char in. Flat (equal)
   stops = pauses between words. Widths = per-char cumulative width + ~0.03em safety margin. */
@keyframes typeW {
  0%, 6% { width: 0; }        /* wait — typing starts ~0.8s into the loop */
  8%  { width: 0.53em; }      /* s */
  10% { width: 1.03em; }      /* h */
  12% { width: 1.32em; }      /* i */
  14% { width: 1.89em; }      /* p */
  16% { width: 2.47em; }      /* p */
  18% { width: 3.00em; }      /* e */
  20% { width: 3.58em; }      /* d */
  22% { width: 3.70em; }      /* (space) → "shipped " */
  27% { width: 3.70em; }      /* pause after word */
  29% { width: 4.29em; }      /* a */
  31% { width: 4.79em; }      /* s */
  33% { width: 4.90em; }      /* (space) → "shipped as " */
  37% { width: 4.90em; }      /* pause after word */
  39% { width: 5.49em; }      /* c */
  41% { width: 6.05em; }      /* o */
  43% { width: 6.63em; }      /* d */
  45% { width: 7.16em; }      /* e */
  47% { width: 7.40em; }      /* . → "shipped as code." */
  82.5% { width: 7.40em; }    /* hold fully typed */
  /* un-type: reverse of the entrance through the same char edges, but ~5x faster (caret backspaces) */
  83%   { width: 7.16em; }    /* ⌫ . */
  83.5% { width: 6.63em; }    /* ⌫ e */
  84%   { width: 6.05em; }    /* ⌫ d */
  84.5% { width: 5.49em; }    /* ⌫ o */
  85%   { width: 4.97em; }    /* ⌫ c */
  85.5% { width: 4.79em; }    /* ⌫ (space) */
  86%   { width: 4.29em; }    /* ⌫ s */
  86.5% { width: 3.76em; }    /* ⌫ a */
  87%   { width: 3.58em; }    /* ⌫ (space) */
  87.5% { width: 3.00em; }    /* ⌫ d */
  88%   { width: 2.47em; }    /* ⌫ e */
  88.5% { width: 1.89em; }    /* ⌫ p */
  89%   { width: 1.32em; }    /* ⌫ p */
  89.5% { width: 1.09em; }    /* ⌫ i */
  90%   { width: 0.53em; }    /* ⌫ h */
  90.5% { width: 0; }         /* ⌫ s → gone */
  100%  { width: 0; }
}
@keyframes caretBlink {
  0%, 50%      { border-right-color: #9fc1ff; }
  50.01%, 100% { border-right-color: transparent; }
}
@keyframes lineLife {
  0%, 7%    { opacity: 0; }   /* hidden until the first char types */
  8%        { opacity: 1; }   /* appears exactly with "s" — no lone caret before text */
  90.5%       { opacity: 1; }   /* stays fully visible through the un-type */
  91.5%, 100% { opacity: 0; }   /* hide the residual caret after the line is erased */
}

/* ---------- Performance ---------- */
.performance { padding: 90px 0; }
.perf-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin-top: 8px; }
@media (max-width: 760px) { .perf-grid { grid-template-columns: 1fr; } }
.stat { padding: 28px; }
.stat .big { font-size: clamp(34px, 5vw, 48px); font-weight: 700; letter-spacing: -0.04em; }
.stat .lbl { color: var(--muted); margin-top: 8px; font-size: 15px; }
.perf-note { margin-top: 26px; color: var(--muted); font-size: 15px; }
.perf-note code { background: var(--panel-2); padding: 2px 7px; border-radius: 7px; font-size: 13px; color: #b9c8da; }

/* ---------- Handoff / Roadmap (shared step + pull primitives) ---------- */
.handoff { padding: 90px 0; }
.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin-top: 8px; }
@media (max-width: 760px) { .steps { grid-template-columns: 1fr; } }
.step { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
.step-body { padding: 20px 24px 24px; }
.step-body h3 { font-size: 18px; }
.step-body p { color: var(--muted); font-size: 15px; margin: 8px 0 0; }

/* ---- Handoff step scenes (wireframe/skeleton mini-illustrations) ----
   Each scene is a self-contained loop, compositor-safe (transform/opacity only,
   plus tiny colour fades on ≤14px chips). The step number sits in the corner. */
.step-scene {
  position: relative;
  height: 196px;
  border-bottom: 1px solid var(--line);
  background:
    radial-gradient(120% 90% at 50% -10%, rgba(47, 125, 255, 0.07), transparent 68%),
    linear-gradient(180deg, rgba(12, 22, 35, 0.55), rgba(8, 13, 21, 0.15));
  overflow: hidden;
}
.step-num-badge {
  position: absolute; top: 12px; left: 12px; z-index: 6;
  width: 25px; height: 25px; border-radius: 8px;
  display: grid; place-items: center;
  background: var(--panel-2); border: 1px solid var(--line-strong);
  font-size: 13px; font-weight: 700; color: var(--accent);
}

/* ===== Step 1 — Select ===== */
.h1-panel {
  position: absolute; left: 50%; top: 50%;
  width: 80%; max-width: 214px;
  transform: translate(-50%, -50%);
  padding: 10px 12px 11px;
}
.h1-rows { position: relative; display: flex; flex-direction: column; gap: 6px; }
.h1-row {
  position: relative; z-index: 2;
  display: flex; align-items: center; gap: 9px;
  height: 24px; padding: 0 7px; border-radius: 7px;
  transform-origin: 14px 50%; /* hinge the click-wiggle at the checkbox */
  will-change: transform;
}
.h1-check {
  position: relative; flex: none;
  width: 14px; height: 14px; border-radius: 4px;
  border: 1.5px solid var(--line-strong);
  background: transparent;
}
/* pulse ring on tick */
.h1-check::before {
  content: ""; position: absolute; inset: -3px; border-radius: 6px;
  border: 1.5px solid var(--accent); opacity: 0; transform: scale(0.6);
  will-change: transform, opacity;
}
/* the checkmark itself — centred via translate so it sits true in the box */
.h1-check::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 3.5px; height: 7px;
  border: solid #fff; border-width: 0 2px 2px 0;
  transform: translate(-50%, -62%) rotate(45deg) scale(0); opacity: 0;
  transform-origin: 50% 50%; will-change: transform, opacity;
}
/* chips are filled shapes (no outline) that light grey->accent on select —
   same fill + accent-on-activate as Step 2's shapes. */
.h1-chip { flex: none; width: 14px; height: 14px; border-radius: 3px; background: var(--line-strong); }
/* picked rows (1/3/4) echo Step 2's shapes: circle · square · triangle */
.h1-row:nth-child(1) .h1-chip { border-radius: 50%; }
.h1-row:nth-child(5) .h1-chip { border-radius: 50%; }
.h1-row:nth-child(4) .h1-chip { border-radius: 2px; clip-path: polygon(50% 6%, 94% 92%, 6% 92%); }
/* selected chips light accent as the row is ticked */
.h1-row:nth-child(1) .h1-chip { animation: h1chip1 4.5s var(--ease) infinite; }
.h1-row:nth-child(3) .h1-chip { animation: h1chip3 4.5s var(--ease) infinite; }
.h1-row:nth-child(4) .h1-chip { animation: h1chip4 4.5s var(--ease) infinite; }
.h1-name { height: 5px; border-radius: 3px; background: var(--line-strong); }
.h1-row:nth-child(1) .h1-name { width: 78px; }
.h1-row:nth-child(2) .h1-name { width: 116px; }
.h1-row:nth-child(3) .h1-name { width: 64px; }
.h1-row:nth-child(4) .h1-name { width: 98px; }
.h1-row:nth-child(5) .h1-name { width: 84px; }

/* the branded pointer — glides in, then row to row, dipping on each "click" */
.h1-cursor {
  position: absolute; left: 11px; top: 7px; z-index: 4;
  transform-origin: 2.5px 2.5px; /* pivot at the pointer tip so the press lands on the checkbox */
  opacity: 0; transform: translate(30px, 30px) scale(1.1);
  filter: drop-shadow(0 0 5px rgba(95, 196, 255, 0.55)) drop-shadow(0 1px 2px rgba(0, 0, 0, 0.4));
  will-change: transform, opacity;
  animation: h1cursor 4.5s var(--ease) infinite;
}
@keyframes h1cursor {
  0%   { transform: translate(30px, 30px) scale(1.1);  opacity: 0; }  /* off to the lower-right */
  7%   { transform: translate(0, 0)       scale(1);    opacity: 1; }  /* glides in to row 1 */
  8%   { transform: translate(0, 0)       scale(0.78); opacity: 1; }  /* click row 1 */
  9.5% { transform: translate(0, 0)       scale(1);    opacity: 1; }
  13%  { transform: translate(0, 60px)    scale(1);    opacity: 1; }
  18%  { transform: translate(0, 60px)    scale(1);    opacity: 1; }
  19%  { transform: translate(0, 60px)    scale(0.78); opacity: 1; }  /* click row 3 */
  20.5%{ transform: translate(0, 60px)    scale(1);    opacity: 1; }
  24%  { transform: translate(0, 90px)    scale(1);    opacity: 1; }
  29%  { transform: translate(0, 90px)    scale(1);    opacity: 1; }
  30%  { transform: translate(0, 90px)    scale(0.78); opacity: 1; }  /* click row 4 */
  31.5%{ transform: translate(0, 90px)    scale(1);    opacity: 1; }
  38%  { transform: translate(0, 90px)    scale(1);    opacity: 0; }  /* leave */
  44%  { transform: translate(30px, 30px) scale(1.1);  opacity: 0; }  /* reset off-stage for next loop */
  100% { transform: translate(30px, 30px) scale(1.1);  opacity: 0; }
}

/* selected rows: 1, 3, 4 — ticked in that order as the cursor arrives.
   Step 1 runs on a 4.5s loop (Step 2 is 4s) so the scenes don't end
   together; select moments 8% / 19% / 30%, deselect ~72% to avoid lingering. */
.h1-row:nth-child(1) { animation: h1row1 4.5s var(--ease) infinite; }
.h1-row:nth-child(3) { animation: h1row3 4.5s var(--ease) infinite; }
.h1-row:nth-child(4) { animation: h1row4 4.5s var(--ease) infinite; }
.h1-row:nth-child(1) .h1-check { animation: h1chk1 4.5s var(--ease) infinite; }
.h1-row:nth-child(3) .h1-check { animation: h1chk3 4.5s var(--ease) infinite; }
.h1-row:nth-child(4) .h1-check { animation: h1chk4 4.5s var(--ease) infinite; }
.h1-row:nth-child(1) .h1-check::after { animation: h1tick1 4.5s var(--ease) infinite; }
.h1-row:nth-child(3) .h1-check::after { animation: h1tick3 4.5s var(--ease) infinite; }
.h1-row:nth-child(4) .h1-check::after { animation: h1tick4 4.5s var(--ease) infinite; }
.h1-row:nth-child(1) .h1-check::before { animation: h1pulse1 4.5s var(--ease) infinite; }
.h1-row:nth-child(3) .h1-check::before { animation: h1pulse3 4.5s var(--ease) infinite; }
.h1-row:nth-child(4) .h1-check::before { animation: h1pulse4 4.5s var(--ease) infinite; }
.h1-row:nth-child(1) .h1-name { animation: h1name1 4.5s var(--ease) infinite; }
.h1-row:nth-child(3) .h1-name { animation: h1name3 4.5s var(--ease) infinite; }
.h1-row:nth-child(4) .h1-name { animation: h1name4 4.5s var(--ease) infinite; }

@keyframes h1row1 { 0%,6%{transform:translateY(0) rotate(0);background:transparent} 8%{transform:translateY(0) rotate(0);background:rgba(95,196,255,0.07)} 9.5%{transform:translateY(-1px) rotate(-1.6deg)} 11%{transform:translateY(-1px) rotate(1deg)} 12.5%{transform:translateY(-1px) rotate(-0.4deg)} 14%{transform:translateY(-1px) rotate(0)} 72%{transform:translateY(-1px) rotate(0);background:rgba(95,196,255,0.07)} 80%,100%{transform:translateY(0) rotate(0);background:transparent} }
@keyframes h1row3 { 0%,17%{transform:translateY(0) rotate(0);background:transparent} 19%{transform:translateY(0) rotate(0);background:rgba(95,196,255,0.07)} 20.5%{transform:translateY(-1px) rotate(-1.6deg)} 22%{transform:translateY(-1px) rotate(1deg)} 23.5%{transform:translateY(-1px) rotate(-0.4deg)} 25%{transform:translateY(-1px) rotate(0)} 72%{transform:translateY(-1px) rotate(0);background:rgba(95,196,255,0.07)} 80%,100%{transform:translateY(0) rotate(0);background:transparent} }
@keyframes h1row4 { 0%,28%{transform:translateY(0) rotate(0);background:transparent} 30%{transform:translateY(0) rotate(0);background:rgba(95,196,255,0.07)} 31.5%{transform:translateY(-1px) rotate(-1.6deg)} 33%{transform:translateY(-1px) rotate(1deg)} 34.5%{transform:translateY(-1px) rotate(-0.4deg)} 36%{transform:translateY(-1px) rotate(0)} 72%{transform:translateY(-1px) rotate(0);background:rgba(95,196,255,0.07)} 80%,100%{transform:translateY(0) rotate(0);background:transparent} }

@keyframes h1chk1 { 0%,6%{background:transparent;border-color:var(--line-strong)} 8%{background:var(--accent);border-color:var(--accent)} 72%{background:var(--accent);border-color:var(--accent)} 80%,100%{background:transparent;border-color:var(--line-strong)} }
@keyframes h1chk3 { 0%,17%{background:transparent;border-color:var(--line-strong)} 19%{background:var(--accent);border-color:var(--accent)} 72%{background:var(--accent);border-color:var(--accent)} 80%,100%{background:transparent;border-color:var(--line-strong)} }
@keyframes h1chk4 { 0%,28%{background:transparent;border-color:var(--line-strong)} 30%{background:var(--accent);border-color:var(--accent)} 72%{background:var(--accent);border-color:var(--accent)} 80%,100%{background:transparent;border-color:var(--line-strong)} }

@keyframes h1tick1 { 0%,7%{transform:translate(-50%,-62%) rotate(45deg) scale(0);opacity:0} 9%{transform:translate(-50%,-62%) rotate(45deg) scale(1.18);opacity:1} 11%{transform:translate(-50%,-62%) rotate(45deg) scale(1);opacity:1} 72%{transform:translate(-50%,-62%) rotate(45deg) scale(1);opacity:1} 79%,100%{transform:translate(-50%,-62%) rotate(45deg) scale(0);opacity:0} }
@keyframes h1tick3 { 0%,18%{transform:translate(-50%,-62%) rotate(45deg) scale(0);opacity:0} 20%{transform:translate(-50%,-62%) rotate(45deg) scale(1.18);opacity:1} 22%{transform:translate(-50%,-62%) rotate(45deg) scale(1);opacity:1} 72%{transform:translate(-50%,-62%) rotate(45deg) scale(1);opacity:1} 79%,100%{transform:translate(-50%,-62%) rotate(45deg) scale(0);opacity:0} }
@keyframes h1tick4 { 0%,29%{transform:translate(-50%,-62%) rotate(45deg) scale(0);opacity:0} 31%{transform:translate(-50%,-62%) rotate(45deg) scale(1.18);opacity:1} 33%{transform:translate(-50%,-62%) rotate(45deg) scale(1);opacity:1} 72%{transform:translate(-50%,-62%) rotate(45deg) scale(1);opacity:1} 79%,100%{transform:translate(-50%,-62%) rotate(45deg) scale(0);opacity:0} }

@keyframes h1pulse1 { 0%,7%{transform:scale(0.6);opacity:0} 8%{opacity:0.5} 14%{transform:scale(2.2);opacity:0} 100%{transform:scale(2.2);opacity:0} }
@keyframes h1pulse3 { 0%,18%{transform:scale(0.6);opacity:0} 19%{opacity:0.5} 25%{transform:scale(2.2);opacity:0} 100%{transform:scale(2.2);opacity:0} }
@keyframes h1pulse4 { 0%,29%{transform:scale(0.6);opacity:0} 30%{opacity:0.5} 36%{transform:scale(2.2);opacity:0} 100%{transform:scale(2.2);opacity:0} }

@keyframes h1name1 { 0%,6%{background:var(--line-strong)} 8%{background:rgba(95,196,255,0.65)} 72%{background:rgba(95,196,255,0.65)} 80%,100%{background:var(--line-strong)} }
@keyframes h1name3 { 0%,17%{background:var(--line-strong)} 19%{background:rgba(95,196,255,0.65)} 72%{background:rgba(95,196,255,0.65)} 80%,100%{background:var(--line-strong)} }
@keyframes h1name4 { 0%,28%{background:var(--line-strong)} 30%{background:rgba(95,196,255,0.65)} 72%{background:rgba(95,196,255,0.65)} 80%,100%{background:var(--line-strong)} }

/* chip fill lights accent as its row is ticked (same grey->accent as Step 2) */
@keyframes h1chip1 { 0%,6%{background:var(--line-strong)} 8%{background:var(--accent)} 72%{background:var(--accent)} 80%,100%{background:var(--line-strong)} }
@keyframes h1chip3 { 0%,17%{background:var(--line-strong)} 19%{background:var(--accent)} 72%{background:var(--accent)} 80%,100%{background:var(--line-strong)} }
@keyframes h1chip4 { 0%,28%{background:var(--line-strong)} 30%{background:var(--accent)} 72%{background:var(--accent)} 80%,100%{background:var(--line-strong)} }

/* ===== Step 2 — Analyze (scan-and-measure the comp) =====
   A scan line sweeps the stage; as it crosses each wireframe shape the shape
   snaps into place, lights up accent and gets a dimension bracket. A progress
   bar fills with the sweep. All transform/opacity (+ tiny colour fades). */
.h2-stage {
  position: absolute; left: 50%; top: 48%;
  transform: translate(-50%, -50%);
  width: 230px; height: 138px;
}
.h2-grid {
  position: absolute; inset: 0; border-radius: 6px; opacity: 0.5;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
  background-size: 20px 20px;
}
.h2-corner { position: absolute; width: 9px; height: 9px; border: 1.5px solid var(--line-strong); opacity: 0.6; }
.h2-corner.tl { left: -1px; top: -1px; border-right: 0; border-bottom: 0; }
.h2-corner.tr { right: -1px; top: -1px; border-left: 0; border-bottom: 0; }
.h2-corner.bl { left: -1px; bottom: -1px; border-right: 0; border-top: 0; }
.h2-corner.br { right: -1px; bottom: -1px; border-left: 0; border-top: 0; }

.h2-shape { position: absolute; background: var(--line-strong); will-change: transform, opacity; }
.s-circle   { width: 32px; height: 32px; border-radius: 50%; left: 24px; top: 26px; animation: h2circle 4s var(--ease) infinite; }
.s-square   { width: 32px; height: 32px; border-radius: 5px; left: 174px; top: 26px; animation: h2square 4s var(--ease) infinite; }
.s-triangle { width: 36px; height: 30px; left: 97px; top: 64px; background: transparent; color: var(--line-strong); transform-origin: 50% 60%; animation: h2triangle 4s var(--ease) infinite; }

/* dimension bracket that snaps under a measured shape */
.h2-guide {
  position: absolute; height: 5px; opacity: 0;
  border: 1.5px solid var(--accent); border-top: 0; border-radius: 0 0 2px 2px;
  will-change: transform, opacity;
}
.h2-guide::after { content: ""; position: absolute; left: 50%; top: 7px; transform: translateX(-50%); width: 12px; height: 4px; border-radius: 2px; background: var(--accent); opacity: 0.85; }
.g-circle   { left: 24px;  top: 64px;  width: 32px; animation: h2gcircle 4s linear infinite; }
.g-square   { left: 174px; top: 64px;  width: 32px; animation: h2gsquare 4s linear infinite; }
.g-triangle { left: 100px; top: 98px;  width: 30px; animation: h2gtriangle 4s linear infinite; }

.h2-scan {
  position: absolute; left: 0; top: 0; width: 2px; height: 100%; opacity: 0;
  background: linear-gradient(var(--accent), rgba(95, 196, 255, 0.2));
  box-shadow: 0 0 8px 1px rgba(95, 196, 255, 0.55);
  will-change: transform, opacity;
  animation: h2scan 4s linear infinite;
}
.h2-progress {
  position: absolute; left: 0; bottom: -9px; width: 100%; height: 2px; border-radius: 2px;
  background: var(--accent); opacity: 0.85; clip-path: inset(0 100% 0 0);
  will-change: clip-path; animation: h2prog 4s linear infinite;
}

/* No start delay: the scan begins sweeping at 0% (LINEAR, 0→67.5% = 2.7s). Shape
   light-up times are matched to the moment the line reaches each shape. */
@keyframes h2scan { 0%{transform:translateX(0);opacity:1} 67.5%{transform:translateX(228px);opacity:1} 70.5%{transform:translateX(228px);opacity:0} 100%{transform:translateX(228px);opacity:0} }
/* progress fills left→right with the scan, then clears left→right, finishing at 95%
   with the last shape so the final ~150ms (96.25→100%) settles before the loop restarts */
@keyframes h2prog { 0%{clip-path:inset(0 100% 0 0)} 67.5%{clip-path:inset(0 0 0 0)} 96.25%,100%{clip-path:inset(0 0 0 100%)} }

/* Shapes are always visible: grey = disabled, accent = analysed. When the scan
   crosses a shape it lights accent, scales up and LIFTS off — then it stays
   levitating (a gentle up/down bob) for the whole active window. On deactivation
   it flies back down to its original spot and fades to grey.
   Appear = the instant the scan crosses (circle 12%, triangle 34%, square 56%).
   Deactivation cascades left→right; the LAST one (square) lands at 96.25% — the
   remaining ~150ms (96.25→100%) lets the scene settle before the loop restarts. */
/* Motion easing: gentle decelerating rise on lift-off (--float-in), then
   ease-in-out-sine (--float) between every float waypoint so velocity glides
   through smoothly and eases to near-zero at each extreme — buoyant, not bouncy.
   x/y/rotation are kept out of phase (y does ~2 cycles, x/tilt ~1) so the shape
   traces a slow figure-8 rather than a straight diagonal hop. */
@keyframes h2circle {
  0%,9%{background:var(--line-strong);transform:translate(0,0) rotate(0deg) scale(1);animation-timing-function:cubic-bezier(0.25,1,0.5,1)}
  12%{background:var(--accent);transform:translate(0,-9px) rotate(0deg) scale(1.08);animation-timing-function:cubic-bezier(0.37,0,0.63,1)}
  30%{transform:translate(3px,-6px) rotate(2.5deg) scale(1.065);animation-timing-function:cubic-bezier(0.37,0,0.63,1)}
  48%{transform:translate(0,-9px) rotate(0deg) scale(1.08);animation-timing-function:cubic-bezier(0.37,0,0.63,1)}
  66%{transform:translate(-3px,-6px) rotate(-2.5deg) scale(1.065);animation-timing-function:cubic-bezier(0.37,0,0.63,1)}
  80%{background:var(--accent);transform:translate(0,-7px) rotate(0deg) scale(1.07);animation-timing-function:cubic-bezier(0.37,0,0.63,1)}
  83%,100%{background:var(--line-strong);transform:translate(0,0) rotate(0deg) scale(1)}
}
@keyframes h2triangle {
  0%,31%{color:var(--line-strong);transform:translate(0,0) rotate(0deg) scale(1);animation-timing-function:cubic-bezier(0.25,1,0.5,1)}
  34%{color:var(--accent);transform:translate(0,-9px) rotate(0deg) scale(1.08);animation-timing-function:cubic-bezier(0.37,0,0.63,1)}
  51%{transform:translate(3px,-6px) rotate(2.5deg) scale(1.065);animation-timing-function:cubic-bezier(0.37,0,0.63,1)}
  68%{transform:translate(-3px,-9px) rotate(-2.5deg) scale(1.08);animation-timing-function:cubic-bezier(0.37,0,0.63,1)}
  86.5%{color:var(--accent);transform:translate(0,-7px) rotate(0deg) scale(1.07);animation-timing-function:cubic-bezier(0.37,0,0.63,1)}
  89.5%,100%{color:var(--line-strong);transform:translate(0,0) rotate(0deg) scale(1)}
}
@keyframes h2square {
  0%,53%{background:var(--line-strong);transform:translate(0,0) rotate(0deg) scale(1);animation-timing-function:cubic-bezier(0.25,1,0.5,1)}
  56%{background:var(--accent);transform:translate(0,-9px) rotate(0deg) scale(1.08);animation-timing-function:cubic-bezier(0.37,0,0.63,1)}
  74%{transform:translate(3px,-6px) rotate(2.5deg) scale(1.065);animation-timing-function:cubic-bezier(0.37,0,0.63,1)}
  93.25%{background:var(--accent);transform:translate(0,-7px) rotate(0deg) scale(1.07);animation-timing-function:cubic-bezier(0.37,0,0.63,1)}
  96.25%,100%{background:var(--line-strong);transform:translate(0,0) rotate(0deg) scale(1)}
}

/* guides fade in/out with their shape — same uniform timing, opacity only */
@keyframes h2gcircle   { 0%,12%{opacity:0} 15%{opacity:1} 80%{opacity:1} 83%,100%{opacity:0} }
@keyframes h2gtriangle { 0%,34%{opacity:0} 37%{opacity:1} 86.5%{opacity:1} 89.5%,100%{opacity:0} }
@keyframes h2gsquare   { 0%,56%{opacity:0} 59%{opacity:1} 93.25%{opacity:1} 96.25%,100%{opacity:0} }

@media (prefers-reduced-motion: reduce) {
  .h1-cursor { animation: none; opacity: 0; }
  .h1-row, .h1-check, .h1-name, .h1-chip, .h1-check::after, .h1-check::before { animation: none; }
  .h1-row:nth-child(1) .h1-chip, .h1-row:nth-child(3) .h1-chip, .h1-row:nth-child(4) .h1-chip { background: var(--accent); }
  .h1-row:nth-child(1) .h1-check, .h1-row:nth-child(3) .h1-check, .h1-row:nth-child(4) .h1-check { background: var(--accent); border-color: var(--accent); }
  .h1-row:nth-child(1) .h1-check::after, .h1-row:nth-child(3) .h1-check::after, .h1-row:nth-child(4) .h1-check::after { transform: translate(-50%, -62%) rotate(45deg) scale(1); opacity: 1; }
  .h1-row:nth-child(1) .h1-name, .h1-row:nth-child(3) .h1-name, .h1-row:nth-child(4) .h1-name { background: rgba(95, 196, 255, 0.65); }
  /* Step 2 — static analysed snapshot */
  .h2-scan { animation: none; opacity: 0; }
  .h2-progress { animation: none; clip-path: inset(0 0 0 0); }
  .h2-shape { animation: none; background: var(--accent); }
  .s-triangle { color: var(--accent); background: transparent; }
  .h2-guide { animation: none; opacity: 1; transform: none; }
}
.pull {
  margin-top: 30px; padding: 26px 30px;
  border-left: 3px solid var(--accent);
  background: var(--panel);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: clamp(18px, 2.4vw, 22px);
  font-weight: 600; letter-spacing: -0.02em;
}

/* ---------- Compare ---------- */
.compare { padding: 90px 0; }
.table-scroll { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); }
table { border-collapse: collapse; width: 100%; min-width: 720px; font-size: 14.5px; }
th, td { padding: 15px 16px; text-align: left; border-bottom: 1px solid var(--line); }
thead th { font-size: 13px; color: var(--muted); font-weight: 600; white-space: nowrap; }
thead th.us { color: var(--text); }
tbody th { font-weight: 500; color: var(--muted); white-space: nowrap; }
td.us, th.us { background: rgba(47, 125, 255, 0.07); }
.col-us { color: var(--text); font-weight: 600; }
.yes { color: var(--good); font-weight: 600; }
.no { color: var(--muted-2); }
tbody tr:last-child td, tbody tr:last-child th { border-bottom: none; }
.compare-note { margin-top: 22px; color: var(--muted); font-size: 15px; max-width: 70ch; }

/* ---------- Features ---------- */
.features { padding: 90px 0; }
/* One row per group; overflow becomes a horizontal scroll/swipe with edge fade. */
.feat-grid {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  overflow-y: hidden;
  /* Desktop settling is JS-owned (soft glide on drag/scroll release); native snap off here.
     Mobile re-enables mandatory snap below. */
  scroll-snap-type: none;
  padding: 4px 0 12px;
  scrollbar-width: none;
  -ms-overflow-style: none;
  --fl: 0px; --fr: 0px;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--fl), #000 calc(100% - var(--fr)), transparent 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 var(--fl), #000 calc(100% - var(--fr)), transparent 100%);
}
.feat-grid::-webkit-scrollbar { height: 0; width: 0; display: none; }
.feat-grid > .feat { flex: 0 0 clamp(222px, 23vw, 248px); scroll-snap-align: start; }
@media (max-width: 640px) {
  /* Firmer paging on touch: every swipe locks onto exactly one card. */
  .feat-grid { scroll-snap-type: x mandatory; }
  .feat-grid > .feat { flex-basis: 84%; scroll-snap-stop: always; }
}
.feat {
  position: relative;
  overflow: hidden;
  padding: 22px;
  transition: transform .34s cubic-bezier(.22,.61,.36,1),
              box-shadow .34s cubic-bezier(.22,.61,.36,1),
              border-color .34s ease;
}
/* Cursor-following highlight spot — JS sets --mx/--my; idle position is a soft top glow. */
.feat::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(240px circle at var(--mx, 50%) var(--my, 0%),
              rgba(70, 140, 255, 0.18), transparent 62%);
  opacity: 0;
  transition: opacity .3s ease;
  pointer-events: none;
  z-index: 0;
}
.feat > * { position: relative; z-index: 1; }
@media (hover: hover) {
  .feat:hover {
    transform: translateY(-4px);
    border-color: var(--line-strong);
    box-shadow: 0 18px 40px -14px rgba(0,0,0,0.55),
                0 10px 30px -12px rgba(47,125,255,0.35);
    will-change: transform;
  }
  .feat:hover::before { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .feat { transition: border-color .2s ease; }
  .feat:hover { transform: none; }
  .feat::before { display: none; }
}
.feat .dot { width: 10px; height: 10px; border-radius: 50%; background: var(--accent-grad); margin-bottom: 14px; }
.feat h3 { font-size: 16px; }
.feat p { color: var(--muted); font-size: 14px; margin: 7px 0 0; }
.feat code { font-size: 0.86em; padding: 1px 5px; border-radius: 5px; background: rgba(255,255,255,0.06); color: var(--muted); }

.feat-group { margin-top: 40px; }
.feat-group:first-of-type { margin-top: 8px; }
.feat-group-label {
  font-size: 13px; font-weight: 600; letter-spacing: 0.02em;
  color: var(--muted-2); text-transform: uppercase;
  margin: 0 0 16px; padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
}
.feat--engine { border-color: var(--line-strong); background: linear-gradient(180deg, rgba(95,196,255,0.05), rgba(0,71,209,0.04)); }
.feat--engine h3 { background: var(--text-grad); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; }
.feat--engine .dot { box-shadow: 0 0 12px rgba(95,196,255,0.5); }


/* ---------- Pricing ---------- */
.pricing { padding: 90px 0; }
.price-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; align-items: stretch; }
@media (max-width: 980px) { .price-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .price-grid { grid-template-columns: 1fr; } }

.price { padding: 28px 24px; display: flex; flex-direction: column; position: relative; }
.price--featured {
  border-color: var(--line-strong);
  background:
    radial-gradient(circle at 50% 0%, rgba(47, 125, 255, 0.16), transparent 62%),
    var(--panel);
}
.price-badge {
  position: absolute; top: -11px; left: 24px;
  padding: 4px 11px; border-radius: 999px;
  font-size: 11px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
  background: var(--accent-grad); color: #fff;
  box-shadow: 0 6px 18px rgba(47, 125, 255, 0.4);
}
.price-name { font-size: 20px; }
.price-who { color: var(--muted-2); font-size: 13px; margin: 6px 0 0; min-height: 32px; } /* baseline-align across cards */
.price-amount { display: flex; align-items: baseline; gap: 7px; margin: 14px 0 18px; flex-wrap: wrap; }
.price-num { font-size: 34px; font-weight: 700; letter-spacing: -0.03em; }
.price-per { color: var(--muted-2); font-size: 14px; }

.btn--block { width: 100%; justify-content: center; }
/* Uniform CTA across pricing cards: one line, equal height, so every card's feature
   list starts at the same y. Trimmed side padding keeps "Download for macOS" on one
   line through the whole 4-column range (down to the 980px → 2-col breakpoint). */
.price .btn--block { min-height: 48px; padding-left: 16px; padding-right: 16px; white-space: nowrap; }
.btn--soon {
  background: var(--panel-2);
  border-color: var(--line);
  color: var(--muted-2);
  cursor: default;
  pointer-events: none;
}

.price-feats { list-style: none; margin: 22px 0 0; padding: 0; display: flex; flex-direction: column; gap: 11px; }
.price-feats li { position: relative; padding-left: 25px; color: var(--muted); font-size: 14px; line-height: 1.5; }
.price-feats li::before { content: "✓"; position: absolute; left: 0; top: 0; color: var(--good); font-weight: 700; }
.price-feats li.price-inherit {
  padding-left: 0; margin-bottom: 2px;
  color: var(--muted-2); font-weight: 600; font-size: 12px;
  letter-spacing: 0.05em; text-transform: uppercase;
}
.price-feats li.price-inherit::before { content: none; }
.price-feats code { background: var(--panel-2); padding: 1px 6px; border-radius: 6px; font-size: 12px; color: #b9c8da; }

.price-note { margin-top: 28px; color: var(--muted-2); font-size: 14px; text-align: center; max-width: 70ch; margin-inline: auto; }

/* ---------- FAQ ---------- */
.faq { padding: 90px 0; }
.faq-item { border-bottom: 1px solid var(--line); }
.faq-item summary {
  list-style: none; cursor: pointer;
  position: relative;
  display: flex; align-items: center;
  padding: 20px 40px 20px 0; font-size: 17px; font-weight: 600;
  transition: color .2s ease;
}
.faq-item summary:hover { color: var(--accent); }
.faq-item summary::-webkit-details-marker { display: none; }
/* Plus/minus drawn as two bars. On expand the whole icon spins a half-turn (spring easing)
   while the vertical arm retracts into the centre — so + resolves into − as one deliberate
   motion, not a flat 90° fold. Both arms travel the same +180°, so the plus stays rigid as it
   turns; the vertical one shrinks to nothing (scaleX 0) on the way, leaving just the minus. */
.faq-item summary::before,
.faq-item summary::after {
  content: "";
  position: absolute;
  right: 4px; top: 50%;
  width: 15px; height: 2px;
  background: var(--accent);
  border-radius: 2px;
  transform: translateY(-50%);
  /* subtle spring (slight overshoot) reads more premium than the linear-ish box curve; the
     main travel still lands ~.3s, roughly in step with the JS height animation in app.js */
  transition: transform .38s cubic-bezier(0.34, 1.4, 0.64, 1);
}
/* closed = plus: ::before is the vertical arm, ::after the horizontal one (base) */
.faq-item summary::before { transform: translateY(-50%) rotate(90deg); }
/* open = minus: spin +180°, vertical arm collapses mid-spin */
.faq-item[open] summary::before { transform: translateY(-50%) rotate(270deg) scaleX(0); }
.faq-item[open] summary::after  { transform: translateY(-50%) rotate(180deg); }
/* JS-managed items morph the icon off the intent class, not the [open] attr — on close
   [open] lingers until the height animation finishes, which would make the icon lag a beat
   behind the collapse (the "2 movements" bug). Later source order beats the equal-specificity
   [open] rules when closing; .is-open (higher specificity) wins while open. No-JS /
   reduced-motion items never get .faq-managed, so they keep the [open] rules above. */
.faq-item.faq-managed summary::before { transform: translateY(-50%) rotate(90deg); }
.faq-item.faq-managed summary::after  { transform: translateY(-50%) rotate(0deg); }
.faq-item.faq-managed.is-open summary::before { transform: translateY(-50%) rotate(270deg) scaleX(0); }
.faq-item.faq-managed.is-open summary::after  { transform: translateY(-50%) rotate(180deg); }
.faq-item .ans { padding: 0 0 22px; color: var(--muted); font-size: 15.5px; max-width: 72ch; }
@media (prefers-reduced-motion: reduce) {
  .faq-item summary::before, .faq-item summary::after { transition: none; }
}

/* ---------- Download / CTA ---------- */
.cta { padding: 90px 0; text-align: center; }
.cta-card {
  padding: 60px 40px;
  border-radius: 28px;
  border: 1px solid var(--line-strong);
  background:
    radial-gradient(circle at 50% 0%, rgba(47, 125, 255, 0.18), transparent 60%),
    var(--panel);
  position: relative;
  overflow: hidden;
}
.cta-card h2 { font-size: clamp(30px, 5vw, 46px); }
.cta-card p { color: var(--muted); margin: 14px 0 28px; font-size: 17px; }

/* ---------- Footer ---------- */
.footer { border-top: 1px solid var(--line); padding: 40px 0; }
.footer .wrap { display: flex; flex-wrap: wrap; gap: 16px; justify-content: space-between; align-items: center; }
.footer .links { display: flex; gap: 22px; flex-wrap: wrap; }
.footer a { color: var(--muted); text-decoration: none; font-size: 14px; }
.footer a:hover { color: var(--text); }
.footer .copy { color: var(--muted-2); font-size: 14px; }

/* ---------- Reveal on scroll (progressive enhancement) ---------- */
.reveal-up { opacity: 0; transform: translateY(24px); transition: opacity 0.6s var(--ease), transform 0.6s var(--ease); }
.reveal-up.in { opacity: 1; transform: none; }

/* ---------- Account / auth pages (sign-in, dashboard) ---------- */
.auth-main { min-height: calc(100vh - 64px); display: grid; place-items: center; padding: 56px 0; }
.auth-card { width: min(440px, 100%); padding: 36px 32px; }
.auth-card h1 { font-size: 28px; margin-bottom: 10px; }
.auth-card .sub { color: var(--muted); font-size: 15px; margin: 0 0 24px; }
.auth-form { display: flex; flex-direction: column; gap: 14px; }
.field {
  width: 100%; padding: 13px 15px; font-size: 15px; color: var(--text);
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
  outline: none; transition: border-color 0.14s;
}
.field:focus { border-color: var(--accent); }
/* A 1px border tint was the entire keyboard-focus signal. Mouse users still get
   just the tint; keyboard users get a ring they can actually track. */
.field:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.field::placeholder { color: var(--muted-2); }
.auth-note { margin-top: 20px; font-size: 13px; color: var(--muted-2); text-align: center; }
.auth-note a { color: var(--accent); text-decoration: none; }
.auth-msg { margin: 0 0 18px; padding: 11px 14px; border-radius: var(--radius-sm); font-size: 14px; }
.auth-msg--err { background: rgba(255, 99, 99, 0.10); border: 1px solid rgba(255, 99, 99, 0.30); color: #ffb4b4; }
.auth-msg--ok { background: rgba(87, 217, 163, 0.10); border: 1px solid rgba(87, 217, 163, 0.30); color: var(--good); }
.is-hidden { display: none !important; }

.account-main { padding: 50px 0 80px; }
.account-wrap { width: min(640px, 100% - 44px); margin-inline: auto; }
.account-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 26px; flex-wrap: wrap; }
.account-head h1 { font-size: 26px; }
.account-email { color: var(--muted); font-size: 14px; margin-top: 6px; }
.account-grid { display: grid; gap: 16px; }
.account-card { padding: 22px 24px; }
.account-card .label { font-size: 12px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted-2); margin-bottom: 10px; }
.account-card .big { font-size: 22px; font-weight: 700; }
.account-card .sub { color: var(--muted); font-size: 14px; margin: 8px 0 0; }
.plan-badge { display: inline-block; padding: 4px 12px; border-radius: 999px; font-size: 13px; font-weight: 700; background: var(--accent-grad); color: #fff; text-transform: capitalize; }
.account-row { display: flex; align-items: center; justify-content: space-between; gap: 16px 18px; flex-wrap: wrap; }
/* Text grows/shrinks beside the CTA on desktop; below ~640px the basis + button no
   longer fit, so the row wraps and the button stacks under the text (mobile-clean). */
.account-row > :first-child { flex: 1 1 220px; min-width: 0; }
.account-row > .btn { flex: 0 0 auto; }

/* ---------- Legal pages (privacy, terms, refund) ---------- */
.legal-main { padding: 56px 0 90px; }
.legal-wrap { width: min(760px, 100% - 44px); margin-inline: auto; }
.legal-wrap h1 { font-size: clamp(28px, 4vw, 38px); margin-bottom: 10px; }
.legal-updated { color: var(--muted-2); font-size: 14px; margin: 0 0 36px; }
.legal-content h2 { font-size: 20px; margin: 34px 0 12px; }
.legal-content p { color: var(--muted); font-size: 15px; line-height: 1.7; margin: 12px 0; }
.legal-content ul { padding-left: 22px; margin: 12px 0; display: flex; flex-direction: column; gap: 8px; }
.legal-content li { color: var(--muted); font-size: 15px; line-height: 1.6; }
.legal-content a { color: var(--accent); text-decoration: none; }
.legal-content strong { color: var(--text); font-weight: 600; }

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation: none !important; transition: none !important; }
  .reveal-up { opacity: 1; transform: none; }
  .title-reveal .ch { transform: none; opacity: 1; }
  .title-reveal .typed { width: auto; opacity: 1; border-right-color: transparent; }
  .orbit i, .orbit .core { transform: scale(0.85); opacity: 0.9; }
  /* hero — entrance defaults to opacity:0, so force everything visible */
  .hero .eyebrow, .hero h1, .hero-sub, .hero-cta, .hero-trust { opacity: 1; transform: none; }
  .hero .grad-text { background-position: 0 0, 0 0; }
  /* radial spectrum — a calm static ring */
  .rsp { transform: none; }
  .rsp b { transform: rotate(var(--a)) translateY(-24px) scaleY(0.6); }
  .rsp-core { transform: scale(1); }
  .rsp-sweep, .rsp-ring { display: none; }
  /* particle drift — a still, scattered field (no wander, no twinkle) */
  .pf b { transform: none; }
  .pf i { animation: none; opacity: 0.7; }
}

/* ---------- Features page: support matrix ---------- */
.matrix-cat { margin-top: 38px; }
.matrix-cat > h3 { font-size: 13px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); margin: 0 0 14px; }
.matrix tbody th { color: var(--text); font-weight: 600; white-space: normal; }
.matrix td.note { color: var(--muted); font-size: 13.5px; line-height: 1.55; white-space: normal; }
.matrix th:first-child, .matrix td:first-child { width: 26%; }
.matrix td.st-cell { white-space: nowrap; width: 1%; }
.st { display: inline-block; font-size: 12px; font-weight: 600; padding: 3px 11px; border-radius: 999px; white-space: nowrap; border: 1px solid transparent; }
.st--ok { color: var(--good); background: rgba(87, 217, 163, 0.10); border-color: rgba(87, 217, 163, 0.26); }
.st--part { color: #ffce6a; background: rgba(255, 206, 106, 0.10); border-color: rgba(255, 206, 106, 0.26); }
.st--soon { color: var(--muted); background: var(--panel-2); border-color: var(--line); }
.matrix-legend { display: flex; flex-wrap: wrap; gap: 16px; margin: 0 0 8px; color: var(--muted); font-size: 13.5px; }
.matrix-legend span { display: inline-flex; align-items: center; gap: 7px; }

/* ---------- Features page: what ships ---------- */
.ship-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; margin-top: 8px; }
@media (max-width: 760px) { .ship-grid { grid-template-columns: 1fr; } }
.ship-card { padding: 26px; }
.ship-card h3 { font-size: 16px; margin: 0 0 8px; }
.ship-card p { color: var(--muted); font-size: 14.5px; line-height: 1.65; margin: 10px 0 0; }
.ship-card code { color: var(--text); background: var(--panel-2); padding: 1px 6px; border-radius: 6px; font-size: 13px; }
.filetree { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; line-height: 1.7; color: var(--muted); background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 18px 20px; overflow-x: auto; margin: 16px 0 0; }
.filetree b { color: var(--text); font-weight: 600; }
.filetree .opt { color: var(--accent); }
.filetree .cm { color: var(--muted-2); }
.honest-note { margin-top: 22px; color: var(--muted); font-size: 15px; line-height: 1.7; max-width: 74ch; }
.honest-note b { color: var(--text); }

/* ---------- Cookie consent bar ---------- */
/* Non-blocking bottom bar. Decline and Accept are the same size and weight on
   purpose: under GDPR refusing must be no harder than agreeing, so no ghost/
   primary asymmetry that nudges toward Accept beyond ordinary visual hierarchy. */
.btn--sm { padding: 10px 18px; font-size: 15px; border-radius: 12px; }

.cookie-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 60;
  padding: 14px 18px calc(14px + env(safe-area-inset-bottom));
  background: rgba(10, 17, 25, 0.92);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-top: 1px solid var(--line-strong);
  box-shadow: 0 -18px 44px rgba(0, 0, 0, 0.42);
  animation: cookie-bar-in 0.32s var(--ease) both;
}
@keyframes cookie-bar-in {
  from { transform: translateY(100%); }
  to { transform: none; }
}
.cookie-bar-inner {
  max-width: var(--maxw);
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 22px;
}
.cookie-bar-copy { flex: 1; min-width: 0; }
.cookie-bar-copy h2 {
  margin: 0 0 4px;
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
}
.cookie-bar-copy p {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
  max-width: 78ch;
}
.cookie-bar-copy a { color: var(--accent); }
.cookie-bar-actions {
  display: flex;
  gap: 10px;
  flex-shrink: 0;
}
.cookie-bar-actions .btn:hover { transform: none; }

@media (max-width: 720px) {
  .cookie-bar-inner { flex-direction: column; align-items: stretch; gap: 14px; }
  .cookie-bar-actions .btn { flex: 1; justify-content: center; }
}
@media (prefers-reduced-motion: reduce) {
  .cookie-bar { animation: none; }
}

/* Footer "Cookies" — an action, not a destination, so it's a real <button>
   painted to sit in the link row. */
.link-btn {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--muted);
  font-size: 14px;
  cursor: pointer;
}
.link-btn:hover { color: var(--text); }

/* ---------- Clickwrap consent (sign-in) ---------- */
/* Unchecked by default and gating submit: a pre-ticked box isn't consent, and
   api/auth/magic/verify.js records terms_accepted_at on every completed sign-in. */
.consent {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin: 4px 0 4px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
  cursor: pointer;
}
.consent input {
  width: auto;
  flex: none;
  margin: 2px 0 0;
  cursor: pointer;
  accent-color: var(--accent-solid);
}
.consent a { color: var(--accent); }

.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.btn:disabled:hover { transform: none; }

/* ---------- Skip to content ---------- */
/* Off-screen until focused, then it lands under the sticky nav. Not display:none —
   that would take it out of the tab order, which is the whole point of it. */
.skip-link {
  position: fixed;
  top: 0;
  left: 50%;
  transform: translate(-50%, -120%);
  z-index: 100;
  padding: 12px 22px;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  background: var(--accent-solid);
  color: #fff;
  font-size: 15px;
  font-weight: 600;
  text-decoration: none;
  transition: transform 0.16s var(--ease);
}
.skip-link:focus {
  transform: translate(-50%, 0);
  outline: 2px solid #fff;
  outline-offset: -4px;
}
@media (prefers-reduced-motion: reduce) {
  .skip-link { transition: none; }
}

/* The jump target must not hide under the sticky header. */
#main { scroll-margin-top: 72px; }

/* ---------- Motion pause controls (WCAG 2.2.2) ----------
   One control per animated block (see motion.js), not one page-wide button:
   pausing the thing you're reading past shouldn't freeze the rest of the page.

   Namespaced `pause-*`, NOT `motion-*` — `motion-anchor`, `motion-layer`,
   `motion-content` and friends are the AE2CSS export's own class names, and
   step 3 of the handoff section is a real export sitting in this page.

   `!important` because that export writes `animation:` shorthands on #ids, and
   the shorthand resets play-state to `running` — a class selector loses that
   fight on specificity. One rule also beats walking a ~100-node scene to set
   inline styles, which is what the step-3 export actually is. */
.pause-active,
.pause-active *,
.pause-active *::before,
.pause-active *::after {
  animation-play-state: paused !important;
}

/* Every `section` is already position:relative, and so are .demo-stage and the
   guide demo panels — this only matters for the few hosts that aren't. */
.pause-anchor { position: relative; }
[data-pause-click]:hover { cursor: pointer; }

.pause-ctl {
  position: absolute;
  right: 6px;
  bottom: 6px;
  z-index: 7;
  /* No chrome — just the glyph. 24x24 anyway: WCAG 2.5.8 wants a 24px target
     even when the mark inside it is 10px, and an icon-only control is exactly
     where that gets forgotten. */
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  /* The icon floats straight over animated content, so a bright particle can
     pass under it and erase it. This keeps 1.4.11 contrast without chrome. */
  filter: drop-shadow(0 0 3px rgba(4, 8, 14, 0.95));
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.16s var(--ease), color 0.16s var(--ease);
}
/* Focus only — no hover reveal. :focus-within on the block brings it up while
   tabbing toward it, :focus-visible keeps it up once it has focus. */
[data-pause-region]:focus-within .pause-ctl,
.pause-active .pause-ctl { opacity: 1; }
.pause-ctl:focus-visible {
  opacity: 1;
  outline: none;
  border-radius: 6px;
  box-shadow: 0 0 2px 2px rgba(90, 150, 255, 0.6);
}
/* Paused is a state, so it stays visible on its own. */
.pause-active .pause-ctl { color: var(--accent); }

/* Two bars -> a triangle. Drawn in CSS rather than the pause/play glyphs, which
   render as anything from a symbol to an emoji to tofu across font stacks. */
.pause-ctl-icon {
  width: 9px;
  height: 10px;
  box-sizing: border-box;
  border-left: 3px solid currentColor;
  border-right: 3px solid currentColor;
}
.pause-active .pause-ctl-icon {
  width: 0;
  height: 0;
  border: 5px solid transparent;
  border-right: 0;
  border-left: 8px solid currentColor;
}

/* The handoff card is the region (its scene is aria-hidden, so the button can't
   live in there), but the card's bottom edge is body copy — sit over the scene
   instead, opposite the step number badge. */
.step .pause-ctl { top: 6px; bottom: auto; }

/* No always-on variant for touch either: tapping the animation is the gesture
   people already expect from a video, and the tap's own feedback teaches it —
   the motion stops and the play glyph appears in the corner. The button itself
   is only transparent, never hidden, so it stays in the tab order and stays
   announced; VoiceOver and TalkBack reach it by swipe, and focus reveals it. */

/* OS-level reduced motion already sets `animation: none` sitewide, so these would
   toggle nothing. Hide them rather than offer a row of dead controls. */
@media (prefers-reduced-motion: reduce) {
  .pause-ctl { display: none; }
}

/* The feature rows are overflow-x scrollers driven by pointer drag (app.js). Made
   focusable so the keyboard can reach the cards past the right edge — without a
   tabindex those were unreachable in browsers that don't auto-focus scrollers. */
.feat-grid:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: var(--radius-sm);
}

/* Windows High Contrast drops box-shadow entirely, so every ring drawn with it
   above disappears and focus becomes invisible. Redraw with a real outline. */
@media (forced-colors: active) {
  .btn:focus-visible,
  .btn--primary:focus-visible,
  .btn--confirm:focus-visible,
  .code-toggle:focus-visible,
  .nav-links a:focus-visible,
  .pause-ctl:focus-visible,
  .nav-toggle:focus-visible ~ .nav-burger {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}
