/* ============================================================
   ki-skape · motion layer
   ------------------------------------------------------------
   Dependency-free animation for the prelaunch landing page.
   No GSAP, no Lenis, no CDN — adding a third-party script to a
   page that sells EU data residency is a bad trade.

   Rules this file follows:
     · Animate transform / opacity / filter only. Never top,
       left, width, or height on anything that runs per-frame.
     · Every effect degrades to the static page when JS is off
       (the `.js` guard) and when the visitor asks for less
       motion (`prefers-reduced-motion`).
     · Off-screen loops are paused by the IntersectionObserver
       in motion.js, so an idle tab costs nothing.
   ============================================================ */

:root {
  --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-io: cubic-bezier(0.65, 0, 0.35, 1);
}

/* ============================================================
   1 · Reveal primitive
   ------------------------------------------------------------
   Opt in with data-reveal. Stagger with --d. The `.js` guard
   means a visitor with JS disabled sees the finished page
   rather than a blank one.
   ============================================================ */

.js [data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 0.6s var(--ease-out) var(--d, 0s),
    transform 0.6s var(--ease-out) var(--d, 0s);
}
.js [data-reveal="scale"] { transform: translateY(10px) scale(0.97); }
.js [data-reveal="left"]  { transform: translateX(-18px); }
.js [data-reveal="right"] { transform: translateX(18px); }

.js [data-reveal].in {
  opacity: 1;
  transform: none;
}

/* ============================================================
   2 · Scroll progress
   ------------------------------------------------------------
   Native scroll-driven where supported; motion.js drives the
   same transform via rAF everywhere else.
   ============================================================ */

.scroll-progress {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  background: linear-gradient(90deg, var(--accent), #9fe000);
  transform: scaleX(var(--p, 0));
  transform-origin: 0 50%;
  pointer-events: none;
}

@supports (animation-timeline: scroll()) {
  .scroll-progress {
    animation: progress-grow linear both;
    animation-timeline: scroll(root block);
  }
}
@keyframes progress-grow {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

nav.top { transition: background 0.25s, box-shadow 0.25s; }
nav.top.scrolled {
  background: color-mix(in srgb, var(--bg) 94%, transparent);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45);
}

/* ============================================================
   3 · Ticker → marquee
   ============================================================ */

.ticker { overflow: hidden; }
.ticker-track {
  display: flex;
  width: max-content;
  animation: marquee 46s linear infinite;
}
.ticker-track > span {
  padding: 0 var(--s-6);
  white-space: nowrap;
}
.ticker:hover .ticker-track { animation-play-state: paused; }

@keyframes marquee {
  to { transform: translateX(-50%); }
}

/* ============================================================
   4 · Hero — ambient field + word rise
   ============================================================ */

header.hero {
  position: relative;
  overflow: hidden;
}
header.hero > .wrap { position: relative; z-index: 1; }

.hero-bg {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}

/* Hairline grid that drifts and parallaxes on scroll. */
.hero-bg .grid {
  position: absolute;
  inset: -30% -10%;
  background-image:
    linear-gradient(var(--border) 1px, transparent 1px),
    linear-gradient(90deg, var(--border) 1px, transparent 1px);
  background-size: 56px 56px;
  opacity: 0.5;
  -webkit-mask-image: radial-gradient(ellipse 70% 60% at 50% 35%, #000 0%, transparent 75%);
          mask-image: radial-gradient(ellipse 70% 60% at 50% 35%, #000 0%, transparent 75%);
  animation: grid-drift 24s linear infinite;
}
@keyframes grid-drift {
  to { transform: translate3d(56px, 56px, 0); }
}

/* Two slow accent blooms. */
.hero-bg .glow {
  position: absolute;
  border-radius: 50%;
  filter: blur(90px);
  will-change: transform;
}
.hero-bg .glow.a {
  width: 480px; height: 480px;
  top: -140px; left: 50%;
  margin-left: -380px;
  background: rgba(196, 242, 74, 0.13);
  animation: bloom-a 18s var(--ease-io) infinite alternate;
}
.hero-bg .glow.b {
  width: 380px; height: 380px;
  top: 40px; left: 50%;
  margin-left: 80px;
  background: rgba(120, 200, 255, 0.07);
  animation: bloom-b 22s var(--ease-io) infinite alternate;
}
@keyframes bloom-a {
  to { transform: translate3d(90px, 60px, 0) scale(1.25); }
}
@keyframes bloom-b {
  to { transform: translate3d(-70px, 40px, 0) scale(1.15); }
}

/* Scroll-linked parallax where the browser supports it. */
@supports (animation-timeline: view()) {
  .hero-bg .grid {
    animation:
      grid-drift 24s linear infinite,
      hero-parallax linear both;
    animation-timeline: auto, view();
    animation-range: normal, contain 0% cover 100%;
  }
}
@keyframes hero-parallax {
  to { transform: translate3d(0, 80px, 0); }
}

/* Headline words rise in on load. */
.h1 .w {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.45em);
  animation: word-rise 0.75s var(--ease-out) forwards;
  animation-delay: calc(var(--i, 0) * 55ms + 80ms);
}
@keyframes word-rise {
  to { opacity: 1; transform: none; }
}

/* ============================================================
   5 · Pipeline — the hero's centrepiece
   ------------------------------------------------------------
   Five nodes on a 6s loop, each lighting 1.2s after the last,
   with the connector between them filling left to right just
   before the next node fires. Reads as one packet travelling
   the chain, but it is only transform + opacity.
   ============================================================ */

.pipeline {
  margin: var(--s-12) auto 0;
  max-width: 760px;
  padding: var(--s-5) var(--s-4);
  background: color-mix(in srgb, var(--surface) 70%, transparent);
  border: 1px solid var(--border);
  border-radius: var(--r-4);
  backdrop-filter: blur(8px);
}
.pl-track {
  display: flex;
  align-items: center;
  gap: 0;
}
.pl-node {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
.pl-dot {
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--surface-3);
  border: 1px solid var(--border-strong);
  animation: pl-pulse 6s linear infinite;
  animation-delay: calc(var(--i) * 1.2s);
}
.pl-label {
  font-family: var(--font-mono);
  font-size: var(--fs-11);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--subtle);
  animation: pl-label 6s linear infinite;
  animation-delay: calc(var(--i) * 1.2s);
}
@keyframes pl-pulse {
  0%          { background: var(--surface-3); border-color: var(--border-strong); box-shadow: 0 0 0 0 var(--accent-glow); }
  4%          { background: var(--accent);    border-color: var(--accent);        box-shadow: 0 0 0 6px transparent; }
  16%         { background: var(--accent);    border-color: var(--accent);        box-shadow: 0 0 0 10px rgba(196, 242, 74, 0); }
  34%, 100%   { background: var(--surface-3); border-color: var(--border-strong); box-shadow: 0 0 0 0 transparent; }
}
@keyframes pl-label {
  0%        { color: var(--subtle); }
  5%, 16%   { color: var(--accent); }
  34%, 100% { color: var(--subtle); }
}

.pl-link {
  flex: 1 1 auto;
  height: 1px;
  min-width: 12px;
  background: var(--border);
  position: relative;
  margin-bottom: 20px;
  overflow: hidden;
}
.pl-link::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  animation: pl-fill 6s linear infinite;
  animation-delay: calc(var(--i) * 1.2s + 0.25s);
}
@keyframes pl-fill {
  0%        { transform: scaleX(0); opacity: 1; }
  14%       { transform: scaleX(1); opacity: 1; }
  26%       { transform: scaleX(1); opacity: 0; }
  27%, 100% { transform: scaleX(0); opacity: 0; }
}

.pl-caption {
  margin-top: var(--s-4);
  text-align: center;
  font-size: var(--fs-12);
  color: var(--muted);
}

@media (max-width: 640px) {
  .pl-label { font-size: 9px; letter-spacing: 0.02em; }
  .pipeline { padding: var(--s-4) var(--s-2); }
}

/* ============================================================
   6 · Demo window
   ------------------------------------------------------------
   Browser chrome around the real product screenshots, cross-
   fading with a slow push-in. Drop a file in and set
   data-demo-video on the section to swap the stills for video.
   ============================================================ */

.demo-window {
  border: 1px solid var(--border);
  border-radius: var(--r-5);
  background: var(--surface);
  overflow: hidden;
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);
}
.demo-chrome {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}
.demo-dots { display: flex; gap: 6px; }
.demo-dots i {
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--surface-3);
}
.demo-url {
  flex: 1;
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--fs-11);
  color: var(--subtle);
}

.demo-stage {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--bg);
  overflow: hidden;
}
/* --- the mock workspace -----------------------------------------
   Drawn in CSS rather than screenshotted. The screenshots in
   assets/ are from the retired "Ethereal Edge" violet design
   (#7c3aed, old model names), which this page's palette forbids —
   and a mock also lets the stage actually walk the pipeline:
   image → video → timeline, on a 15s loop of three 5s phases.
   ---------------------------------------------------------------- */

.demo-mock { position: absolute; inset: 0; }
.demo-body { display: grid; grid-template-columns: 138px 1fr; height: 100%; }

.demo-rail {
  border-right: 1px solid var(--border);
  background: var(--surface-2);
  padding: var(--s-3) 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.demo-rail b {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px var(--s-4);
  margin: 0 var(--s-2);
  border-radius: var(--r-2);
  font-size: var(--fs-12);
  font-weight: 500;
  color: var(--subtle);
}
.demo-rail b::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 2px;
  background: currentColor;
}
.demo-rail b.hot {
  animation: rail-hot 15s var(--ease-io) infinite;
  animation-delay: calc(var(--i) * 5s);
}
@keyframes rail-hot {
  0%, 3%    { color: var(--subtle); background: transparent; }
  7%, 30%   { color: var(--accent); background: var(--accent-soft); }
  34%, 100% { color: var(--subtle); background: transparent; }
}

.demo-main { display: flex; flex-direction: column; min-width: 0; }
.demo-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-5);
  border-bottom: 1px solid var(--border);
}
.dm-title { position: relative; height: 16px; flex: 1; }
.dm-title span {
  position: absolute;
  inset: 0;
  font-size: var(--fs-13);
  font-weight: 500;
  opacity: 0;
  animation: panel-cycle 15s var(--ease-io) infinite;
  animation-delay: calc(var(--i) * 5s);
}
.dm-cr {
  font-family: var(--font-mono);
  font-size: var(--fs-11);
  color: var(--accent);
  background: var(--accent-soft);
  padding: 2px 9px;
  border-radius: 999px;
}

.demo-panels { position: relative; flex: 1; }
.demo-panel {
  position: absolute;
  inset: 0;
  padding: var(--s-5);
  opacity: 0;
  animation: panel-cycle 15s var(--ease-io) infinite;
  animation-delay: calc(var(--i) * 5s);
}
@keyframes panel-cycle {
  0%        { opacity: 0; transform: translateY(8px); }
  4%, 30%   { opacity: 1; transform: none; }
  34%, 100% { opacity: 0; transform: translateY(-8px); }
}

/* phase 1 — image grid */
.dm-grid { display: grid; grid-template-columns: repeat(4, 1fr); grid-auto-rows: 1fr; gap: var(--s-3); height: 100%; }
.dm-tile {
  border-radius: var(--r-3);
  border: 1px solid var(--border);
  background: linear-gradient(135deg, var(--surface-3), #232a17);
  opacity: 0;
  animation: tile-pop 15s var(--ease-out) infinite;
  animation-delay: calc(var(--j) * 0.32s);
}
.dm-tile:nth-child(even) { background: linear-gradient(135deg, #1a1f14, var(--surface-2)); }
@keyframes tile-pop {
  0%        { opacity: 0; transform: scale(0.94); }
  5%, 30%   { opacity: 1; transform: none; }
  34%, 100% { opacity: 0; transform: scale(0.98); }
}

/* phase 2 — video render */
.dm-vid {
  height: 100%;
  border-radius: var(--r-3);
  border: 1px solid var(--border);
  background: linear-gradient(135deg, #1b2016, var(--surface-3));
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--s-4);
}
.dm-play {
  width: 0; height: 0;
  border-left: 16px solid var(--accent);
  border-top: 11px solid transparent;
  border-bottom: 11px solid transparent;
  margin-left: 4px;
}
.dm-prog {
  width: 55%;
  height: 3px;
  border-radius: 2px;
  background: var(--surface-3);
  overflow: hidden;
}
.dm-prog i {
  display: block;
  height: 100%;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  animation: vid-prog 15s linear infinite;
  animation-delay: 5s;
}
@keyframes vid-prog {
  0%, 5%    { transform: scaleX(0); }
  29%       { transform: scaleX(1); }
  100%      { transform: scaleX(1); }
}
.dm-cap { font-family: var(--font-mono); font-size: var(--fs-11); color: var(--muted); }

/* phase 3 — editor: preview above, timeline below */
.dm-edit { display: grid; grid-template-rows: 1fr auto; gap: var(--s-4); height: 100%; }
.dm-preview {
  border-radius: var(--r-3);
  border: 1px solid var(--border);
  background: linear-gradient(135deg, #1b2016, var(--surface-3));
}
.dm-tl { display: flex; flex-direction: column; gap: 7px; position: relative; }
.dm-row { position: relative; height: 17px; border-radius: 4px; background: var(--surface-2); }
.dm-clip {
  position: absolute;
  top: 0; bottom: 0;
  left: var(--x);
  width: var(--w);
  border-radius: 4px;
  background: color-mix(in srgb, var(--accent) 38%, var(--surface-3));
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
}
.dm-clip.alt {
  background: color-mix(in srgb, #78c8ff 28%, var(--surface-3));
  border-color: rgba(120, 200, 255, 0.38);
}
.dm-head { position: absolute; inset: 0; animation: head-sweep 5s linear infinite; animation-delay: 10s; }
.dm-head::before {
  content: "";
  position: absolute;
  top: -4px; bottom: -4px; left: 0;
  width: 1px;
  background: var(--accent);
  box-shadow: 0 0 7px var(--accent-glow);
}

/* Scan line sweeping the stage — reads as "something is running". */
.demo-stage::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    transparent 40%,
    rgba(196, 242, 74, 0.06) 50%,
    transparent 60%
  );
  transform: translateX(-100%);
  animation: demo-sweep 5.5s var(--ease-io) infinite;
  pointer-events: none;
}
@keyframes demo-sweep {
  0%, 55%  { transform: translateX(-100%); }
  100%     { transform: translateX(100%); }
}

.demo-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: none;
}
/* Set by motion.js once a real demo file is configured + loaded. */
.demo-window.has-video .demo-mock { display: none; }
.demo-window.has-video .demo-video { display: block; }
.demo-window.has-video .demo-stage::after { display: none; }

.demo-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  justify-content: center;
  margin-top: var(--s-5);
}

/* ============================================================
   6b · Canvas particle field + cursor glow + parallax
   ============================================================ */

.hero-particles {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0.7;
}

.cursor-glow {
  position: fixed;
  top: 0; left: 0;
  width: 480px; height: 480px;
  margin: -240px 0 0 -240px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(196, 242, 74, 0.06) 0%, transparent 65%);
  pointer-events: none;
  z-index: 1;
  opacity: 0;
  transition: opacity 0.4s;
  will-change: transform;
}
.cursor-glow.on { opacity: 1; }
/* Touch devices never allocate it. */
@media (hover: none), (pointer: coarse) {
  .cursor-glow { display: none; }
}

[data-parallax] {
  transform: translate3d(0, var(--py, 0px), 0);
  will-change: transform;
}

/* ============================================================
   7 · Workflow steps + scroll pinning
   ------------------------------------------------------------
   Pinning only engages when <html> carries `.pinned`, which
   motion.js sets on wide, tall, motion-tolerant viewports. In
   every other case these rules collapse and the section renders
   as the ordinary stacked grid it was before.
   ============================================================ */

/* ---- base: the section as it renders unpinned ---- */

.step {
  position: relative;
  overflow: hidden;
  transition: transform 0.2s var(--ease-out), border-color 0.2s;
}
.step::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 0.7s var(--ease-out) var(--d, 0s);
}
.js .step.in::before { transform: scaleX(1); }
:root:not(.js) .step::before { transform: scaleX(1); }

/* Unpinned reveal. Scoped to :not(.pinned) rather than using
   [data-reveal], so it cannot collide with the pinned dim state
   below — the two modes are mutually exclusive by selector. */
html.js:not(.pinned) .step {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 0.6s var(--ease-out) var(--d, 0s),
    transform 0.6s var(--ease-out) var(--d, 0s),
    border-color 0.2s;
}
html.js:not(.pinned) .step.in { opacity: 1; transform: none; }
html.js:not(.pinned) .step.in:hover { transform: translateY(-3px); }

.step:hover {
  transform: translateY(-3px);
  border-color: var(--border-strong);
}
.step .n { transition: transform 0.25s var(--ease-out); }
.step:hover .n { transform: translateX(3px); }

.pin-stage > .wrap { width: 100%; }

.pin-rail {
  display: none;
  justify-content: center;
  gap: var(--s-2);
  margin-top: var(--s-10);
}
.pin-rail i {
  width: 28px;
  height: 2px;
  border-radius: 2px;
  background: var(--border-strong);
  transition: background 0.3s, transform 0.3s var(--ease-out);
}
.pin-rail i.on {
  background: var(--accent);
  transform: scaleY(2);
}

/* ---- pinned overrides ----------------------------------------
   These must sit after the base rules: `.pinned .step:not(.active)
   ::before` and `.js .step.in::before` have identical specificity
   (0,3,1), so source order is what decides which one wins.
   --------------------------------------------------------------- */

.pinned .pin-scroll { height: 380vh; }
.pinned .pin-stage {
  position: sticky;
  top: 0;
  min-height: 100vh;
  display: flex;
  align-items: center;
  padding: var(--s-12) 0;
}
.pinned .pin-rail { display: flex; }

.pinned .step {
  opacity: 0.32;
  transform: scale(0.975);
  transition: opacity 0.45s var(--ease-out), transform 0.45s var(--ease-out),
              border-color 0.3s;
}
.pinned .step.active {
  opacity: 1;
  transform: none;
  border-color: var(--border-strong);
}
/* Hover must not cancel the dim state on an inactive card. */
.pinned .step:not(.active):hover { transform: scale(0.975); }
.pinned .step.active:hover { transform: translateY(-3px); }

.pinned .step::before { transition: transform 0.55s var(--ease-out); }
.pinned .step.active::before { transform: scaleX(1); }
.pinned .step:not(.active)::before { transform: scaleX(0); }

/* ============================================================
   8 · Surface motifs
   ------------------------------------------------------------
   One small running diagram per card. Paused until scrolled
   into view (motion.js toggles [data-anim] → .in).
   ============================================================ */

.surface { transition: transform 0.2s var(--ease-out), border-color 0.2s; }
.surface:hover { transform: translateY(-3px); border-color: var(--border-strong); }

.motif {
  position: relative;
  height: 72px;
  margin-top: auto;
  padding: var(--s-3);
  border-radius: var(--r-2);
  background: var(--surface-2);
  border: 1px solid var(--border);
  overflow: hidden;
}
/* Pause rules live at the end of this section — see the note there.
   A low-specificity `.motif *` rule cannot do this job. */

/* --- Builder: code typing --- */
.bd-code {
  font-family: var(--font-mono);
  font-size: var(--fs-11);
  line-height: 1.65;
  color: var(--fg-2);
}
.bd-line {
  display: block;
  width: 0;
  overflow: hidden;
  white-space: nowrap;
  animation: type-in 9s linear infinite;
  animation-delay: calc(var(--i) * 1.15s);
}
.bd-line .k { color: var(--accent); }
@keyframes type-in {
  0%        { width: 0; }
  12%       { width: var(--w); }
  88%       { width: var(--w); }
  92%, 100% { width: 0; }
}
/* Sits at the start of the line below the code, like a terminal
   cursor. Anchoring it to the right edge left it stranded in the
   corner with nothing to relate to. */
.bd-caret {
  position: absolute;
  left: var(--s-3);
  bottom: var(--s-3);
  width: 6px; height: 12px;
  background: var(--accent);
  animation: caret-blink 1s steps(2, jump-none) infinite;
}
@keyframes caret-blink { 50% { opacity: 0; } }

/* --- Editor: mini timeline with a sweeping playhead --- */
.ed-tracks { display: flex; flex-direction: column; gap: 5px; }
.ed-row {
  position: relative;
  height: 12px;
  border-radius: 3px;
  background: var(--surface-3);
}
.ed-clip {
  position: absolute;
  top: 0; bottom: 0;
  left: var(--x);
  width: var(--w);
  border-radius: 3px;
  background: color-mix(in srgb, var(--accent) 40%, var(--surface-3));
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
}
.ed-clip.alt { background: color-mix(in srgb, #78c8ff 32%, var(--surface-3)); border-color: rgba(120, 200, 255, 0.4); }
/* Full-width carrier: translating it 100% moves the 1px line
   exactly one container width, with no layout per frame. */
.ed-head {
  position: absolute;
  inset: 0;
  animation: head-sweep 4.5s linear infinite;
}
.ed-head::before {
  content: "";
  position: absolute;
  top: -2px; bottom: -2px; left: 0;
  width: 1px;
  background: var(--accent);
  box-shadow: 0 0 6px var(--accent-glow);
}
@keyframes head-sweep {
  from { transform: translateX(0); }
  to   { transform: translateX(100%); }
}

/* --- Audio: waveform --- */
.au-wave {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 3px;
  height: 100%;
}
.au-wave i {
  /* Capped so 20 bars read as a waveform rather than a bar chart —
     unbounded flex:1 makes each bar ~24px wide in this card. */
  flex: 1 1 0;
  max-width: 5px;
  min-width: 2px;
  border-radius: 2px;
  background: color-mix(in srgb, var(--accent) 65%, transparent);
  height: var(--h);
  transform-origin: 50% 50%;
  animation: wave 1.4s var(--ease-io) infinite alternate;
  animation-delay: calc(var(--i) * 55ms);
}
@keyframes wave {
  from { transform: scaleY(0.35); opacity: 0.5; }
  to   { transform: scaleY(1);    opacity: 1; }
}

/* --- Convert: cycling format pairs --- */
.cv-stack { position: relative; height: 100%; }
.cv-pair {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  font-family: var(--font-mono);
  font-size: var(--fs-13);
  opacity: 0;
  animation: chip-cycle 8s var(--ease-out) infinite;
  animation-delay: calc(var(--i) * 2s);
}
.cv-pair b {
  padding: 3px 10px;
  border-radius: var(--r-1);
  background: var(--surface-3);
  border: 1px solid var(--border-strong);
  font-weight: 500;
  color: var(--fg-2);
}
.cv-pair b.out { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.cv-pair .arw { color: var(--accent); }
@keyframes chip-cycle {
  0%        { opacity: 0; transform: translateY(8px); }
  4%, 21%   { opacity: 1; transform: none; }
  25%, 100% { opacity: 0; transform: translateY(-8px); }
}

/* --- pause everything until the card is on screen ---------------
   These must be listed explicitly and must come last. Each rule
   above sets `animation`, and the shorthand implicitly resets
   animation-play-state to `running` — so a generic `.motif *`
   selector (0,1,0) is beaten by `.au-wave i` (0,1,1) and the pause
   silently never applies. Naming the elements under `.motif:not(.in)`
   puts these at (0,3,x), which wins.
   ---------------------------------------------------------------- */
.motif:not(.in) .bd-line,
.motif:not(.in) .bd-caret,
.motif:not(.in) .ed-head,
.motif:not(.in) .au-wave i,
.motif:not(.in) .cv-pair {
  animation-play-state: paused;
}

/* ============================================================
   9 · Stats + billing figures
   ============================================================ */

.stat .value { transition: transform 0.3s var(--ease-out); }
.stat:hover .value { transform: translateY(-2px); }

/* motion.js counts these; the markup ships with the final
   value as text so a JS-less visitor still reads the number. */
[data-count] { font-variant-numeric: tabular-nums; }

.ledger-row {
  opacity: 0;
  transform: translateX(-8px);
  transition: opacity 0.45s var(--ease-out) var(--d, 0s),
              transform 0.45s var(--ease-out) var(--d, 0s);
}
:root:not(.js) .ledger-row,
.ledger.in .ledger-row { opacity: 1; transform: none; }

/* ============================================================
   10 · Pricing
   ============================================================ */

.plan { transition: transform 0.2s var(--ease-out), border-color 0.2s, box-shadow 0.3s; }
.plan:hover { transform: translateY(-4px); }
.plan:not(.featured):hover { border-color: var(--border-strong); }
.plan.featured { animation: featured-breathe 5s var(--ease-io) infinite; }
@keyframes featured-breathe {
  0%, 100% { box-shadow: 0 0 0 1px var(--accent), 0 12px 40px var(--accent-glow); }
  50%      { box-shadow: 0 0 0 1px var(--accent), 0 12px 56px rgba(196, 242, 74, 0.4); }
}
.plan .features li { transition: color 0.15s; }
.plan:hover .features li { color: var(--fg); }

/* ============================================================
   11 · FAQ — animated disclosure
   ------------------------------------------------------------
   grid-template-rows 0fr → 1fr is the only way to transition to
   an intrinsic height. motion.js defers the `open` removal so
   closing animates too.
   ============================================================ */

details.faq { transition: border-color 0.2s, background 0.2s; }
details.faq:hover { border-color: var(--border-strong); }
details.faq summary::after { transition: transform 0.25s var(--ease-out); }
details.faq[open] summary::after { transform: rotate(180deg); }

.js details.faq .a-wrap {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.28s var(--ease-out);
}
.js details.faq[open]:not(.closing) .a-wrap { grid-template-rows: 1fr; }
.js details.faq .a-wrap > .a { overflow: hidden; min-height: 0; }

/* ============================================================
   12 · Buttons + links
   ============================================================ */

.btn.primary { position: relative; overflow: hidden; }
.btn.primary::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 35%, rgba(255, 255, 255, 0.4) 50%, transparent 65%);
  transform: translateX(-100%);
  transition: transform 0.6s var(--ease-io);
}
.btn.primary:hover::after { transform: translateX(100%); }
.btn { transition: background 0.12s, border-color 0.12s, transform 0.12s; }
.btn:active { transform: translateY(1px); }

.nav-links a:not(.btn) { position: relative; }
.nav-links a:not(.btn)::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -4px;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 0.22s var(--ease-out);
}
.nav-links a:not(.btn):hover::after { transform: scaleX(1); }

.live-dot { will-change: opacity; }

/* ============================================================
   13 · Reduced motion
   ------------------------------------------------------------
   Everything above collapses to the static page. Reveals resolve
   immediately rather than never — a visitor who asks for less
   motion must still get the whole page.
   ============================================================ */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    animation-delay: 0ms !important;
    transition-delay: 0ms !important;
  }

  .js [data-reveal] { opacity: 1; transform: none; }
  .h1 .w { opacity: 1; transform: none; }
  .js .step::before { transform: scaleX(1); }
  html.js:not(.pinned) .step { opacity: 1; transform: none; }
  .ledger-row { opacity: 1; transform: none; }

  .hero-bg .glow { display: none; }
  .hero-bg .grid { opacity: 0.28; }
  .hero-particles { display: none; }
  .cursor-glow { display: none; }
  [data-parallax] { transform: none; }

  /* Belt and braces — motion.js already refuses to set `.pinned`
     under reduced motion, but if it ever did, nothing here pins. */
  .pinned .pin-scroll { height: auto; }
  .pinned .pin-stage { position: static; min-height: 0; padding: 0; }
  .pinned .step { opacity: 1; transform: none; }
  .pinned .step::before { transform: scaleX(1); }
  .pin-rail { display: none !important; }

  /* Freeze the mock on its first phase rather than cycling. */
  .demo-stage::after { display: none; }
  .demo-panel:not(:first-child) { display: none; }
  .demo-panel:first-child { opacity: 1; transform: none; }
  .dm-title span:not(:first-child) { display: none; }
  .dm-title span:first-child { opacity: 1; }
  .dm-tile { opacity: 1; transform: none; }
  .demo-rail b.hot:not(:first-of-type) { color: var(--subtle); background: transparent; }
  .bd-line { width: var(--w); }
  .cv-pair:not(:first-child) { display: none; }
  .cv-pair:first-child { opacity: 1; }
  .ticker-track { animation: none; }
  .pl-dot { background: var(--accent); border-color: var(--accent); }
  .pl-label { color: var(--accent); }
  .pl-link::after { transform: scaleX(1); opacity: 1; }
}
