/* ==========================================================================
   rejwannirob.me - v4
   Brand Kit v2 deck: void ground, glass hairline panels, violet→cyan energy.
   Token values are byte-identical to the design system
   (_ds/rej-s-hq-and-portfolio-website/tokens/*.css).

   Replaces the retired v1 "Operations Console" palette (Signal Green #4ade80
   on #0b0e11, Instrument Serif). palette.css calls that deck gone.

   CSP NOTE - READ BEFORE EDITING
   vercel.json sets `style-src 'self' https://fonts.googleapis.com` with NO
   'unsafe-inline'. Every rule must live in this file. An inline style="" or a
   <style> block is silently dropped by the browser, which is how the 404 page
   broke on 2026-07-11. Same for JS: `script-src 'self'`, so no inline handlers.
   ========================================================================== */

/* ------------------------------------------------------------ self-host - */
/* Geist Mono is served from this origin, not gstatic. One variable file
   (100-900) replaces the three static weights the Google Fonts request used
   to pull, and drops a cross-origin hop off the critical path. Sora and Geist
   still come from Google. Source: geist npm v1.7.2, SIL OFL 1.1 - the license
   ships alongside the font at /fonts/geist-mono/LICENSE.txt and must stay
   there for redistribution to be legal.
   vercel.json must keep `font-src 'self'` in the CSP or this silently fails. */
@font-face {
  font-family: "Geist Mono";
  src: url("../fonts/geist-mono/GeistMono-Variable.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* ---------------------------------------------------------------- tokens - */
:root {
  color-scheme: dark;

  --app: #08090d;
  --panel: #0e1017;
  --raised: #151824;
  --hairline: rgba(255, 255, 255, 0.07);

  --ink: #eef1f7;        /* 17.59:1 on --app */
  --ink-soft: #9aa2b4;   /*  7.77:1 */
  --ink-faint: #5a6072;  /*  3.18:1 - FAILS WCAG AA. Decorative use only. */

  /* Micro labels: mono text at 9–13px. --ink-faint reads correctly in the
     design but measures 3.18:1, under the 4.5:1 AA floor, and 63 elements on
     this page are that size. The OS build spec already rules on this:
     "ink-faint only >=18px or decorative; micro-labels use --ink-soft".
     This step is 4.84:1 - it clears AA while staying visibly quieter than
     --ink-soft, so the three-level hierarchy the design wants survives. */
  --ink-micro: #767d90;  /*  4.84:1 */

  --violet: #7c5cff;
  --cyan: #22d3ee;
  --magenta: #f5427c;

  --positive: #34d399;
  --negative: #fb7185;
  --gold: #fbbf24;

  --grad-primary: linear-gradient(120deg, var(--violet) 0%, var(--cyan) 100%);
  /* The same ramp mirrored, for surfaces that slide their background on hover.
     Sliding a plain two-stop ramp requires background-size > 100%, which shows
     only a fraction of the ramp and mutes the colour. Mirroring means every
     100% window is a complete violet-to-cyan sweep, so the resting state keeps
     its full saturation and the hover simply reverses the direction. */
  --grad-primary-cycle: linear-gradient(
    120deg,
    var(--violet) 0%,
    var(--cyan) 50%,
    var(--violet) 100%
  );

  --white-soft: rgba(255, 255, 255, 0.03);

  --font-display: "Sora", "Trebuchet MS", system-ui, sans-serif;
  --font-body: "Geist", "Segoe UI", system-ui, sans-serif;
  --font-mono: "Geist Mono", "Cascadia Code", ui-monospace, monospace;

  --pad-page: 40px;
  --max-content: 1200px;

  --r-input: 12px;
  --r-card: 18px;
  --r-hero: 22px;

  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
}

/* ------------------------------------------------------------------ base - */
*,
*::before,
*::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--app);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* The ambient field. Fixed and non-interactive so it never eats a click. */
.field {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  overflow: hidden;
}
.field span {
  position: absolute;
  display: block;
  border-radius: 50%;
  filter: blur(90px);
}
.field .n1 {
  width: 560px; height: 560px; top: -160px; left: -120px;
  background: rgba(124, 92, 255, 0.16);
}
.field .n2 {
  width: 520px; height: 520px; top: 420px; right: -180px;
  background: rgba(34, 211, 238, 0.10);
}
.field .n3 {
  width: 620px; height: 620px; bottom: -260px; left: 50%;
  margin-left: -310px;
  background: rgba(245, 66, 124, 0.07);
}

.page { position: relative; z-index: 1; }

.wrap {
  max-width: var(--max-content);
  margin: 0 auto;
  padding-left: var(--pad-page);
  padding-right: var(--pad-page);
}

a { color: var(--cyan); text-decoration: none; }
a:hover { color: var(--ink); }

img { max-width: 100%; display: block; }

/* Every in-page anchor lands under a 62px sticky header. Without this the
   heading you jumped to sits behind the header and reads as the wrong section. */
[id] { scroll-margin-top: 84px; }

/* Kills the 300ms tap delay and the default blue flash on touch. */
a, button, .btn, summary {
  touch-action: manipulation;
  -webkit-tap-highlight-color: rgba(34, 211, 238, 0.18);
}

/* Headings set their own ragged edge rather than leaving one orphan word. */
h1, h2, h3, .sec-head, blockquote { text-wrap: balance; }
p, .lede, .hero-sub { text-wrap: pretty; }

/* Every interactive thing gets the same visible focus. */
a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 3px;
  border-radius: 4px;
}

/* .skip-link is the name the blog pages use. Aliased rather than renamed
   across three files; without it the skip control renders unstyled. */
.skip,
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--raised);
  color: var(--ink);
  padding: 10px 16px;
  z-index: 100;
}
.skip:focus,
.skip-link:focus { left: 0; }

.vh {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------- type ---- */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  margin: 0;
  letter-spacing: -0.02em;
}

.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  text-transform: uppercase;
}

/* Numerals are always mono + tabular so columns never jitter. */
.num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* Two background layers, not one. The bottom layer is the violet->cyan ramp
   exactly as it shipped; the top layer is a bright band parked off-canvas at
   130%, so the SETTLED paint is unchanged and only `.hero .grad` sweeps the
   band through once on load (see the overture). Because the element now has
   two layers, any keyframe touching background-position must restate BOTH
   values or the ramp layer snaps to 0 mid-animation. */
.grad {
  background-image:
    linear-gradient(
      100deg,
      rgba(255, 255, 255, 0) 44%,
      rgba(255, 255, 255, 0.88) 50%,
      rgba(255, 255, 255, 0) 56%
    ),
    var(--grad-primary);
  background-size: 300% 100%, 100% 100%;
  background-position: 130% 0, 0 0;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.sec-head {
  font-size: 34px;
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.15;
  margin: 0 0 14px;
}

.lede {
  font-size: 15px;
  color: var(--ink-soft);
  max-width: 68ch;
  margin: 0 0 30px;
}

/* Kicker row: mono label + a rule that fades out to the right. */
.kicker-row {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 18px;
}
.kicker {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--cyan);
  white-space: nowrap;
}
.kicker-row::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, rgba(34, 211, 238, 0.30), rgba(124, 92, 255, 0.10), transparent);
  /* Drawn state is the BASE state. Sections above the fold never receive
     .has-risen (see js/main.js), so anything that only exists inside the
     revealed state would never appear for them. */
  transform: scaleX(1);
  transform-origin: 0 50%;
}

/* ------------------------------------------------------------- material - */
.glass {
  background:
    linear-gradient(rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)),
    var(--panel);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.04),
    0 8px 24px -12px rgba(0, 0, 0, 0.7);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}
.glass-raised {
  background:
    linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)),
    var(--panel);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.05),
    0 22px 50px -18px rgba(0, 0, 0, 0.85);
}

/* The hover bloom is a background LAYER, not an overlay pseudo-element. A
   positioned ::after paints above the card's own text, which tints the copy;
   a background layer sits under it by definition. Both states declare the
   same three layers in the same order, which is what lets `background`
   interpolate at all. Mismatched layer counts snap instead of fading. */
.lift {
  background:
    radial-gradient(560px circle at 50% -20%, rgba(124, 92, 255, 0), transparent 60%),
    linear-gradient(rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)),
    var(--panel);
  transition:
    transform 200ms var(--ease-out),
    border-color 200ms var(--ease-out),
    box-shadow 200ms var(--ease-out),
    background 280ms var(--ease-out);
}
.lift:hover {
  transform: translateY(-3px);
  border-color: rgba(124, 92, 255, 0.35);
  background:
    radial-gradient(560px circle at 50% -20%, rgba(124, 92, 255, 0.16), transparent 60%),
    linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0)),
    var(--panel);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.05),
    0 18px 40px -16px rgba(0, 0, 0, 0.8),
    0 0 0 1px rgba(124, 92, 255, 0.08);
}

/* ------------------------------------------------------------- buttons --- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: var(--font-body);
  font-weight: 600;
  border-radius: var(--r-input);
  border: 1px solid transparent;
  cursor: pointer;
  text-align: center;
  /* position+overflow exist so .btn-primary::after can sweep a sheen across
     the face and be clipped by the pill. An absolutely positioned pseudo is
     out of flow, so it never becomes a flex item of this inline-flex box. */
  position: relative;
  overflow: hidden;
  transition:
    transform 150ms var(--ease-out),
    background-position 460ms var(--ease-out),
    background-color 150ms var(--ease-out),
    border-color 150ms var(--ease-out),
    color 150ms var(--ease-out);
  min-height: 44px;
}
/* The old hover dimmed the button to 0.94 opacity. Fading a call to action on
   approach is backwards, so each variant now brightens instead: primary slides
   its gradient toward cyan, ghost raises its fill. */
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); transition-duration: 60ms; }

/* Primary is the gradient, never gold. The gold budget is spent on the hero
   kicker (house law 02: exactly one gold element per screen). */
/* REGRESSION FIXED 2026-07-28. The hover-travel version of this rule used
   var(--grad-primary) at background-size 190%, which meant the visible window
   covered only 8.5% to 61.2% of the ramp: #7466fe to #45a5f5. The button never
   reached cyan and read as a flat blue-violet slab, noticeably duller than the
   headline and the play buttons that use the same token at full width.

   The mirrored token fixes it at the source. At background-position 0% the
   window is the first half of the ramp, violet to cyan, identical to what
   shipped originally. At 100% it is the second half, cyan back to violet. Both
   ends are fully saturated, and the hover now reverses the sweep rather than
   sliding along a washed-out middle.

   Contrast note: #07080c on the violet end measures 4.61:1, which clears the
   4.5:1 AA floor for normal text but not by much. That is the original design's
   margin, not something introduced here. Deepening the violet is the lever if
   it ever needs more room. */
.btn-primary {
  background-image: var(--grad-primary-cycle);
  background-size: 200% 100%;
  background-position: 0% 0;
  color: #07080c;
  font-size: 14px;
  padding: 0 18px;
}
.btn-primary::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 38%;
  background: linear-gradient(
    100deg,
    transparent,
    rgba(255, 255, 255, 0.5),
    transparent
  );
  transform: translateX(-170%);
  opacity: 0;
  pointer-events: none;
}
.btn-primary:hover::after { animation: btnSheen 700ms var(--ease-out); }
.btn-primary:hover { color: #07080c; background-position: 100% 0; }
.btn-primary.lg { font-size: 15px; padding: 0 26px; min-height: 50px; }
/* HIGH-SEVERITY FIX 2026-07-28. Was 38px, the only control on the site under
   the 44px touch floor, and it is the header booking button: the primary
   conversion control, shown on touch viewports from 861px to 1040px. The two
   .lg variants are 50px and the nav toggle is 44x44; this was the outlier. */
.btn-primary.sm { min-height: 44px; font-size: 13px; padding: 0 15px; }

.btn-ghost {
  background: var(--white-soft);
  border-color: var(--hairline);
  color: var(--ink);
  font-size: 14px;
  padding: 0 18px;
}
.btn-ghost:hover { background: rgba(255, 255, 255, 0.06); color: var(--ink); }
.btn-ghost.lg { font-size: 15px; padding: 0 26px; min-height: 50px; }

/* ---------------------------------------------------------------- pills -- */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  padding: 4px 10px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-soft);
  white-space: nowrap;
}
.pill-live { border-color: rgba(52, 211, 153, 0.28); color: var(--positive); }
/* Deliberately NOT gold. House law 02 allows exactly one gold element per
   screen and the hero kicker already spends it (the mark's gold node is
   intrinsic to the mark and exempt). The sample pill still has to read as a
   warning, so it carries weight through a brighter ink and a dashed edge. */
.pill-sample {
  border-style: dashed;
  border-color: rgba(255, 255, 255, 0.22);
  color: var(--ink);
}

.dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex: none;
  background: var(--ink-faint);
}
.dot-live { background: var(--positive); animation: pulse 2.4s ease-in-out infinite; }
.dot-violet { background: var(--violet); animation: pulse 2.4s ease-in-out infinite; }
.dot-cyan { background: var(--cyan); animation: pulse 2.4s ease-in-out infinite; }

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

/* ------------------------------------------------------------ utility bar */
.utility { border-bottom: 1px solid var(--hairline); }
.utility .wrap {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding-top: 7px;
  padding-bottom: 7px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-micro);
}

/* ---------------------------------------------------------------- header - */
.header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: rgba(8, 9, 13, 0.72);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--hairline);
}
.header .wrap {
  position: relative;
  display: flex;
  align-items: center;
  gap: 24px;
  padding-top: 14px;
  padding-bottom: 14px;
}

.brand { display: flex; align-items: center; gap: 11px; }
.brand:hover { color: inherit; }
.brand-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 15px;
  color: var(--ink);
  letter-spacing: -0.01em;
  line-height: 1.2;
  display: block;
}
.brand-tag {
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-micro);
  line-height: 1.4;
  display: block;
}

.nav { display: flex; gap: 24px; margin: 0 auto; }
/* Belt and braces against the header growing: a nav link that wraps makes the
   sticky bar taller and pushes the page down under it. */
.nav a { white-space: nowrap; }
/* Underline drawn as a background layer, not a border or text-decoration, so
   growing it costs no layout and cannot nudge the sticky header's height. */
.nav a {
  font-size: 13.5px;
  font-weight: 500;
  color: var(--ink-soft);
  background-image: var(--grad-primary);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1.5px;
  transition:
    color 160ms var(--ease-out),
    background-size 280ms var(--ease-out);
}
.nav a:hover,
.nav a:focus-visible { color: var(--ink); background-size: 100% 1.5px; }

/* The toggle was the word MENU set in 10px mono, which at that size reads as a
   smudge rather than a control. It is now a drawn mark that morphs into a
   close cross, driven entirely by aria-expanded. That attribute has to be set
   for screen readers regardless, so styling off it means there is no second
   piece of state that can fall out of sync with the first.

   The button keeps an accessible name through aria-label (main.js swaps it
   between "Open menu" and "Close menu"). Dropping the text without that would
   leave an unlabelled button, which is the usual way this pattern breaks. */
.nav-toggle {
  display: none;
  margin-left: auto;
  align-items: center;
  justify-content: center;
  background: var(--white-soft);
  border: 1px solid var(--hairline);
  border-radius: var(--r-input);
  color: var(--ink);
  height: 44px;
  width: 44px;
  padding: 0;
  cursor: pointer;
  transition:
    background 160ms var(--ease-out),
    border-color 160ms var(--ease-out);
}
.nav-toggle:hover { background: rgba(255, 255, 255, 0.07); border-color: rgba(124, 92, 255, 0.35); }

.nav-icon {
  position: relative;
  display: block;
  width: 18px;
  height: 1.5px;
  border-radius: 2px;
  background: currentColor;
  transition: background 160ms var(--ease-out);
}
.nav-icon::before,
.nav-icon::after {
  content: "";
  position: absolute;
  left: 0;
  width: 18px;
  height: 1.5px;
  border-radius: 2px;
  background: currentColor;
  transition: transform 280ms var(--ease-out);
}
.nav-icon::before { transform: translateY(-6px); }
.nav-icon::after { transform: translateY(6px); }

/* Open: the middle bar goes out and the outer two cross. Both halves rotate
   about the same centre because they are already translated back to 0. */
.nav-toggle[aria-expanded="true"] .nav-icon { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-icon::before { transform: translateY(0) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-icon::after { transform: translateY(0) rotate(-45deg); }

/* ----------------------------------------------------------------- hero -- */
.hero .wrap {
  display: grid;
  grid-template-columns: 1.02fr 0.98fr;
  gap: 44px;
  padding-top: 68px;
  align-items: start;
}

.avatar-chip { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; }
.avatar {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.16);
  object-fit: cover;
  flex: none;
}
/* THE one gold element on this page, by house law 02. */
.kicker-gold {
  font-family: var(--font-mono);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--gold);
  display: block;
}
.kicker-sub {
  font-size: 12.5px;
  color: var(--ink-micro);
  display: block;
  margin-top: 3px;
}

.hero h1 {
  font-size: 62px;
  font-weight: 800;
  line-height: 1.04;
  max-width: 14ch;
  margin: 0 0 20px;
}
.hero-sub {
  font-size: 16px;
  line-height: 1.65;
  color: var(--ink-soft);
  max-width: 54ch;
  margin: 0 0 26px;
}
.hero-cta { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 22px; }
.proof-line {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-micro);
}

.hero-right { position: relative; }

/* The OS window: a camera-safe view of the real system. No money on it. */
.os-window {
  border-radius: 20px;
  /* The extra bottom padding is what the answer chip overlaps. Measured: with
     16px padding the footnote's last ink sits only 13px above the window's
     edge, while the chip reaches 26px in, so it still clipped the footnote
     mid-word after the chip was re-anchored. Growing the padding pushes the
     window's bottom edge down, and because .hero-right is sized by the window,
     the chip's `top: calc(100% - 18px)` follows it. The overlap then lands
     entirely on empty panel. */
  padding: 16px 16px 42px;
}
.os-bar {
  display: flex;
  align-items: center;
  gap: 9px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--hairline);
  margin-bottom: 12px;
  flex-wrap: wrap;
}
.os-title {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.os-bar .pill { margin-left: auto; }

.agent-row {
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid var(--hairline);
  border-radius: 10px;
  padding: 9px 12px;
  margin-bottom: 8px;
}
.agent-name { font-size: 12.5px; font-weight: 600; }
.agent-status {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-micro);
  text-align: right;
}
.os-foot {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-micro);
  padding-top: 10px;
  border-top: 1px solid var(--hairline);
  margin-top: 4px;
}

/* The answer chip overlaps the window on desktop; static ≤860px.

   HIGH-SEVERITY FIX 2026-07-28. `bottom: -30px` put the chip's TOP edge 145px
   up inside the window, and the chip's panel is --panel #0e1017, fully opaque,
   so it hid rather than tinted. Measured at 1280: it covered 52% of the
   "Reply Classifier / Triaging replies" row, 58% of "AEO Monitor / 48 questions
   5 engines", and 60% of the "Camera-safe view / 17 agents / supervised by me"
   footnote, truncating it mid-word. Both agent-status strings are right-aligned,
   so they sat ENTIRELY inside the hidden strip. The hero's whole claim is that
   this is a live view of the OS, and half of it was behind another card.

   Anchoring from the top fixes it at every width. .hero-right is
   position: relative and its height IS the window's height, because the chip is
   out of flow, so `top: 100%` is exactly the window's bottom edge. Backing off
   18px keeps the layered overlap the design wants while landing it entirely on
   the window's 26px of empty bottom padding.

   A fixed `bottom` value cannot express this: the offset that clears the
   content depends on the chip's own height, which changes with its text. */
.answer-chip {
  position: absolute;
  right: -16px;
  top: calc(100% - 18px);
  bottom: auto;
  width: 318px;
  border-radius: 14px;
  border-color: rgba(34, 211, 238, 0.22);
  padding: 14px;
}
.answer-label {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-micro);
  margin-bottom: 8px;
}
.answer-q { font-size: 13px; font-weight: 600; margin-bottom: 10px; }
.answer-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 9px;
  border-radius: 8px;
  font-size: 12px;
  margin-bottom: 5px;
}
.answer-row.win {
  background: linear-gradient(90deg, rgba(34, 211, 238, 0.12), rgba(124, 92, 255, 0.05));
  border: 1px solid rgba(34, 211, 238, 0.18);
}
/* HIGH-SEVERITY FIX 2026-07-28. `opacity: 0.45` on the row composited the
   LABEL itself, taking --ink from 17.59:1 down to 4.13:1 and the rank number to
   1.87:1. Opacity on a container cannot be corrected by any colour token,
   because it multiplies whatever that token resolves to.

   De-emphasis now comes from what the WINNING row adds, a tinted fill and a
   cyan border, rather than from dimming the loser. The text steps down to
   --ink-soft, 7.42:1 on this panel. Same hierarchy, readable text. */
.answer-row.lose { color: var(--ink-soft); }
.answer-rank { font-family: var(--font-mono); font-size: 10px; color: var(--ink-micro); }
.answer-row .tag {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: 0.12em;
  color: var(--cyan);
}

/* ----------------------------------------------------------- stat band --- */
.stats { margin-top: 78px; border-top: 1px solid var(--hairline); }
.stats .wrap { display: grid; grid-template-columns: repeat(3, 1fr); }
.stat { padding: 26px 0; }
.stat + .stat { border-left: 1px solid var(--hairline); padding-left: 24px; }
.stat-num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 34px;
  font-weight: 600;
  line-height: 1;
  margin-bottom: 8px;
}
.stat-cap { font-size: 12.5px; color: var(--ink-soft); max-width: 34ch; }
.stats-note {
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}
.stats-note .wrap {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-micro);
  padding-top: 12px;
  padding-bottom: 12px;
}

/* --------------------------------------------------------- engine strip -- */
/* A slow ambient marquee, not a UI animation. It joins the same family as the
   26s nebula drift: long, continuous, low-contrast, ignorable. Note this is a
   SIXTH motion beyond the five in the brand kit; if the gold-budget style audit
   ever trims motions, this is the one to cut.
   The track holds the engine list twice; translating exactly -50% lands the
   duplicate where the original began, so the loop has no visible seam. */
.engines {
  border-bottom: 1px solid var(--hairline);
  background: rgba(255, 255, 255, 0.012);
}
.engines .wrap {
  display: flex;
  align-items: center;
  gap: 22px;
  padding-top: 15px;
  padding-bottom: 15px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-micro);
}
.engines .lead { color: var(--cyan); white-space: nowrap; flex: none; }

/* overflow:hidden is load-bearing: without it the duplicated track widens the
   document and the whole page scrolls sideways. */
.engine-marquee {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
.engine-track {
  display: flex;
  width: max-content;
  gap: 40px;
  animation: engineScroll 42s linear infinite;
}
.engine-track .engine { white-space: nowrap; }

/* Pause on hover so the list can actually be read. */
.engine-marquee:hover .engine-track { animation-play-state: paused; }

/* A live status dot per engine. The strip is headed "Monitored", so this is
   what the row actually is: a readout of systems being watched. It is NOT a
   logo wall, and deliberately so. Drawing approximations of the OpenAI,
   Anthropic, Google, Perplexity, Microsoft, xAI or Meta marks from memory
   would produce eight wrong trademarks, which is worse legally and worse
   visually at 10px than the site's own language.
   To swap in real marks later: drop the official SVGs into
   assets/engines/<name>.svg and replace .engine-dot with an <img>. The flex
   row and the marquee both already accommodate it. */
.engine { display: inline-flex; align-items: center; gap: 7px; }
.engine-dot {
  width: 5px;
  height: 5px;
  flex: none;
  border-radius: 50%;
  background: var(--cyan);
  box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.10);
  animation: pulse 2.8s ease-in-out infinite;
}
/* Offset each dot so the row twinkles instead of blinking in unison. The
   duplicate half of the marquee reuses the same six offsets, which keeps the
   two halves visually identical at the seam. */
.engine:nth-child(6n + 1) .engine-dot { animation-delay: 0s; }
.engine:nth-child(6n + 2) .engine-dot { animation-delay: 0.42s; }
.engine:nth-child(6n + 3) .engine-dot { animation-delay: 0.9s; }
.engine:nth-child(6n + 4) .engine-dot { animation-delay: 1.36s; }
.engine:nth-child(6n + 5) .engine-dot { animation-delay: 1.78s; }
.engine:nth-child(6n + 6) .engine-dot { animation-delay: 2.2s; }

@keyframes engineScroll {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

@media (prefers-reduced-motion: reduce) {
  /* Stop the scroll and let the list wrap as a static row. The duplicate set
     is hidden so the names are not shown twice. */
  .engine-track { animation: none; width: auto; flex-wrap: wrap; gap: 10px 22px; }
  .engine-track .engine[aria-hidden="true"] { display: none; }
  .engine-marquee { -webkit-mask-image: none; mask-image: none; }
}

/* -------------------------------------------------------------- sections - */
.sec { padding-top: 64px; }
.sec-belief { padding-top: 72px; }

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; }

/* --- 01 offers --- */
.offer { padding: 26px; }
.offer-head { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.offer-label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--violet);
}
.offer h3 { font-size: 22px; font-weight: 700; letter-spacing: -0.01em; margin: 0 0 10px; }
.offer > p { font-size: 13.5px; color: var(--ink-soft); margin: 0 0 16px; }
.bullets { list-style: none; margin: 0 0 18px; padding: 0; }
.bullets li {
  position: relative;
  padding-left: 18px;
  font-size: 13px;
  color: var(--ink-soft);
  margin-bottom: 9px;
}
.bullets li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 8px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--cyan);
}
.offer-foot {
  border-top: 1px solid var(--hairline);
  padding-top: 13px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-micro);
}

/* --- 02 why me: open hairline columns, deliberately NOT cards --- */
.cols {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}
.col { padding: 26px 0; }
.col + .col { border-left: 1px solid var(--hairline); padding-left: 26px; }
.col-letter {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  color: var(--violet);
  display: block;
  margin-bottom: 10px;
}
.col h3 { font-size: 15px; font-weight: 600; margin: 0 0 8px; font-family: var(--font-body); letter-spacing: 0; }
.col p { font-size: 13px; color: var(--ink-soft); margin: 0; }

/* --- 03 steps --- */
.step { padding: 20px; border-radius: 16px; }
.step-num {
  font-family: var(--font-mono);
  font-size: 20px;
  font-weight: 600;
  color: var(--violet);
  display: block;
  margin-bottom: 12px;
}
.step h3 { font-size: 15px; font-weight: 600; font-family: var(--font-body); letter-spacing: 0; margin: 0 0 7px; }
.step p { font-size: 13px; color: var(--ink-soft); margin: 0; }

/* --- 04 stack --- */
.stack-grid { display: grid; grid-template-columns: 1fr 1.15fr; gap: 16px; }
.card-pad { padding: 26px; }
.card-pad h3 { font-size: 22px; font-weight: 700; letter-spacing: -0.01em; margin: 0 0 14px; }
.card-head { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; flex-wrap: wrap; }
.card-head h3 { margin: 0; }
.chips { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 16px; }
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  padding: 5px 11px;
  font-size: 11.5px;
  color: var(--ink-micro);
}
.chip-on { color: var(--ink); border-color: rgba(34, 211, 238, 0.22); }

/* This card is now a coverage list rather than a system inventory, so the
   chips are the thing a buyer actually reads. Two additions, both scoped to
   the chips and neither adding a new looping motion to the page's budget.

   1. A cascade on reveal. It is deliberately gated behind .will-rise, which
      JavaScript adds and reduced-motion never does, so with no JS the chips
      are simply visible - the same contract the rest of the reveal system
      uses. Never hide these unconditionally: the coverage list is the proof
      behind the offer, and it must survive with JS off.
   2. A hover state. The chips read at 11.5px, and lifting the hovered one
      makes a 17-item wrapped grid scannable instead of a wall. */
.stack-grid.will-rise .chip {
  opacity: 0;
  transform: translateY(6px);
}
.stack-grid.has-risen .chip {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.4s var(--ease-out),
    transform 0.4s var(--ease-out);
}
/* Across the row, not down it: the eye reads coverage left to right. Capped
   at eight so the last chip is not still arriving after the card has settled. */
.stack-grid.has-risen .chip:nth-child(1) { transition-delay: 0.02s; }
.stack-grid.has-risen .chip:nth-child(2) { transition-delay: 0.05s; }
.stack-grid.has-risen .chip:nth-child(3) { transition-delay: 0.08s; }
.stack-grid.has-risen .chip:nth-child(4) { transition-delay: 0.11s; }
.stack-grid.has-risen .chip:nth-child(5) { transition-delay: 0.14s; }
.stack-grid.has-risen .chip:nth-child(6) { transition-delay: 0.17s; }
.stack-grid.has-risen .chip:nth-child(7) { transition-delay: 0.20s; }
.stack-grid.has-risen .chip:nth-child(n + 8) { transition-delay: 0.23s; }

.chip {
  transition:
    border-color 0.22s var(--ease-out),
    color 0.22s var(--ease-out),
    transform 0.22s var(--ease-out);
}
.chip:hover {
  transform: translateY(-1px);
  border-color: rgba(34, 211, 238, 0.34);
  color: var(--ink);
}

/* --- 05 weekly report --- */
.report { padding: 20px; border-radius: 20px; }
.report-tabs {
  display: flex;
  align-items: center;
  gap: 18px;
  border-bottom: 1px solid var(--hairline);
  padding-bottom: 12px;
  margin-bottom: 18px;
  flex-wrap: wrap;
}
.tab { font-size: 12.5px; color: var(--ink-micro); }
.tab-on { color: var(--ink); font-weight: 600; }
.report-tabs .pill { margin-left: auto; }

.kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 22px; }
.kpi { border: 1px solid var(--hairline); border-radius: 12px; padding: 14px; }
.kpi-label {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-micro);
  margin-bottom: 9px;
}
.kpi-val {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 24px;
  font-weight: 600;
  line-height: 1;
}
.kpi-delta { font-family: var(--font-mono); font-size: 11px; color: var(--positive); margin-left: 6px; }

.chart { display: flex; align-items: flex-end; gap: 10px; height: 118px; margin-bottom: 8px; }
.bar { flex: 1; border-radius: 5px 5px 0 0; background: rgba(34, 211, 238, 0.45); }
.bar-now { background: var(--grad-primary); }
.h1of { height: 38%; } .h2of { height: 46%; } .h3of { height: 42%; }
.h4of { height: 58%; } .h5of { height: 66%; } .h6of { height: 78%; } .h7of { height: 100%; }
.chart-x {
  display: flex;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.1em;
  color: var(--ink-micro);
  margin-bottom: 22px;
}
.chart-x span { flex: 1; text-align: center; }

.matrix { width: 100%; border-collapse: collapse; }
.matrix th,
.matrix td {
  text-align: left;
  padding: 10px 8px;
  border-top: 1px solid var(--hairline);
  font-size: 12.5px;
}
.matrix th {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-micro);
  font-weight: 400;
  border-top: 0;
}
.matrix td.eng, .matrix th.eng { text-align: center; width: 44px; }
.matrix td.rank { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.mdot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: rgba(255, 255, 255, 0.12); }
.mdot-on { background: var(--cyan); }
.matrix-legend {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-micro);
  padding-top: 14px;
}

/* --- 06 fit + proof --- */
.fit-grid { display: grid; grid-template-columns: 1.2fr 1fr; gap: 16px; }
.fit-list { list-style: none; margin: 0 0 16px; padding: 0; }
.fit-list li { display: flex; gap: 10px; font-size: 13.5px; color: var(--ink-soft); margin-bottom: 12px; }
.fit-list .arrow { color: var(--positive); font-family: var(--font-mono); flex: none; }
.fit-note {
  border-top: 1px solid var(--hairline);
  padding-top: 14px;
  font-size: 12.5px;
  color: var(--ink-micro);
}
.kv-list { margin: 0; }
.kv { display: flex; justify-content: space-between; gap: 14px; padding: 10px 0; border-top: 1px solid var(--hairline); }
.kv:first-child { border-top: 0; padding-top: 0; }
.kv dt {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-micro);
}
.kv dd { margin: 0; font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 12px; text-align: right; }

/* --- client testimonials (click-to-play) --- */
/* Nothing is requested from Google until a visitor clicks. Thumbnails are
   self-hosted and the iframe is only injected on activation, so the default
   page load stays first-party and the CSP only needs frame-src. */
/* Two testimonials never filled a 1200px row: the grid was capped at 760px
   and left 400px of dead space that read as a layout accident rather than a
   choice. The section is now copy-left / proof-right, which uses the width,
   drops the cards from 372x661 to a size that does not tower over the page,
   and puts the privacy note beside the prose that explains it. */
/* align-items: start left the copy column 156px tall beside a 463px video
   column, so the section carried a 537x307 hole under the text. Stretching the
   column and pushing its two blocks to the ends fixes it properly: the heading
   sits against the top of the cards and the channel link sits against their
   bottom, so the eye reads a full column with deliberate space in the middle
   rather than a block that ran out. Centring would have balanced the hole
   without removing it. */
.testi-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 46px;
  align-items: stretch;
}
.testi-copy {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 32px;
}
.testi-foot { margin-top: auto; }
.testi-copy .videos-note { margin-top: 14px; }

.testi-channel {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-height: 44px;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  /* Same background-drawn underline as the nav, so growing it costs no layout. */
  background-image: var(--grad-primary);
  background-repeat: no-repeat;
  background-position: 0 calc(50% + 0.86em);
  background-size: 0% 1.5px;
  transition: background-size 280ms var(--ease-out);
}
.testi-channel:hover,
.testi-channel:focus-visible { background-size: calc(100% - 22px) 1.5px; }
.testi-arrow {
  color: var(--cyan);
  transition: transform 240ms var(--ease-out);
}
.testi-channel:hover .testi-arrow { transform: translateX(4px); }
.videos { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }

.video-card {
  position: relative;
  display: block;
  border-radius: var(--r-card);
  overflow: hidden;
  border: 1px solid var(--hairline);
  background: var(--panel);
  aspect-ratio: 9 / 16;
  color: var(--ink);
  transition: transform 200ms var(--ease-out), border-color 200ms var(--ease-out);
}
.video-card:hover { transform: translateY(-3px); border-color: rgba(124, 92, 255, 0.35); color: var(--ink); }
.video-card img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Shorts thumbnails are 4:3 with the subject centred; crop rather than letterbox. */
}
.video-card iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

.video-shade {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(8, 9, 13, 0.15) 0%, rgba(8, 9, 13, 0.35) 45%, rgba(8, 9, 13, 0.92) 100%);
  pointer-events: none;
}
/* The button was a fixed 58px circle. On a 261px card that is 22% of the
   width; on the 161px card the two-up grid gives at 375px it is 36%, a disc
   parked over the speaker's face. It now sizes off the card: 16% of the card
   width, floored at 30px so it stays a recognisable control on the narrowest
   layout and capped at 46px so it cannot grow into the frame on wide screens.

   Centring moved from negative margins to a transform, because -29px only
   centres a 58px box and the whole point is that the box is no longer a fixed
   size. Consequence: the hover state must restate the translate, since
   `transform` is one property and `scale()` alone would drop the centring and
   throw the button to the bottom-right quadrant.

   The triangle is a clip-path at 34% of the button rather than a border trick,
   for the same reason: border widths are absolute and would not follow. */
.video-play {
  position: absolute;
  top: 50%;
  left: 50%;
  width: clamp(30px, 16%, 46px);
  height: auto;
  aspect-ratio: 1;
  margin: 0;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: var(--grad-primary);
  display: grid;
  place-items: center;
  box-shadow: 0 8px 22px -8px rgba(0, 0, 0, 0.8);
  pointer-events: none;
  transition:
    transform 260ms var(--ease-out),
    box-shadow 260ms var(--ease-out);
}
.video-card:hover .video-play {
  transform: translate(-50%, -50%) scale(1.08);
  box-shadow:
    0 12px 30px -8px rgba(0, 0, 0, 0.85),
    0 0 0 7px rgba(124, 92, 255, 0.13);
}
.video-play::after {
  content: "";
  width: 34%;
  height: auto;
  aspect-ratio: 0.82;
  margin-left: 12%;
  background: #07080c;
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}
/* These two are <span>, and a span is inline: vertical margin on an inline
   box is ignored and siblings share a line. The name and the description were
   rendering as one run-together sentence ("Eryk Lewandowski Targeted lead
   list built for email outreach") that then wrapped across the face of the
   video. display:block is the fix; the margin below was never the problem. */
.video-meta {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 16px;
  pointer-events: none;
  transition: transform 240ms var(--ease-out);
}
.video-client {
  display: block;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.25;
  margin: 0 0 3px;
}
.video-line {
  display: block;
  font-size: 12px;
  line-height: 1.4;
  color: var(--ink-soft);
  margin: 0;
}
.video-card:hover .video-meta { transform: translateY(-3px); }
.video-badge {
  position: absolute;
  top: 12px; left: 12px;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
  background: rgba(8, 9, 13, 0.7);
  border: 1px solid var(--hairline);
  border-radius: 999px;
  padding: 4px 9px;
  pointer-events: none;
}
.videos-note {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-micro);
  margin-top: 16px;
}

/* --- story --- */
.story-grid { display: grid; grid-template-columns: 1.15fr 0.85fr; gap: 44px; align-items: start; }
.story p { color: var(--ink-soft); margin: 0 0 16px; max-width: 58ch; }
.story p:last-of-type { margin-bottom: 24px; }
.story strong { color: var(--ink); font-weight: 600; }

.portrait-frame {
  position: relative;
  border-radius: var(--r-hero);
  overflow: hidden;
  border: 1px solid var(--hairline);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.05),
    0 22px 50px -18px rgba(0, 0, 0, 0.85);
}
.portrait-frame img { width: 100%; height: auto; }
/* Ties the photo into the deck so it does not read as a pasted-in stock shot. */
.portrait-frame::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(160deg, rgba(124, 92, 255, 0.10), transparent 45%, rgba(8, 9, 13, 0.55));
  pointer-events: none;
}

.social { display: flex; flex-wrap: wrap; gap: 10px; }
.social a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--white-soft);
  color: var(--ink-soft);
  font-size: 13px;
  font-weight: 500;
  transition: border-color 150ms var(--ease-out), color 150ms var(--ease-out), background 150ms var(--ease-out);
}
.social a:hover {
  color: var(--ink);
  border-color: rgba(124, 92, 255, 0.35);
  background: rgba(255, 255, 255, 0.06);
}
.social svg { width: 15px; height: 15px; flex: none; }

/* --- belief --- */
.belief { text-align: center; }
.belief-kicker { display: flex; align-items: center; gap: 16px; justify-content: center; margin-bottom: 26px; }
.belief-kicker::before,
.belief-kicker::after {
  content: "";
  width: 130px;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(124, 92, 255, 0.28), transparent);
}
.belief blockquote {
  font-family: var(--font-display);
  font-size: 46px;
  font-weight: 800;
  line-height: 1.12;
  letter-spacing: -0.02em;
  max-width: 22ch;
  margin: 0 auto 18px;
}
.belief-sub { color: var(--ink-soft); max-width: 62ch; margin: 0 auto 22px; font-size: 14px; }
.belief-by { display: inline-flex; align-items: center; gap: 11px; }
.belief-by img { width: 38px; height: 38px; border-radius: 50%; border: 1px solid rgba(255, 255, 255, 0.16); }
.belief-by span {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-micro);
}

/* --- 07 FAQ --- */
.faq { display: grid; grid-template-columns: 1fr 1fr; column-gap: 40px; }
.qa { border-top: 1px solid var(--hairline); padding: 20px 0; }
.qa h3 { font-size: 14.5px; font-weight: 600; font-family: var(--font-body); letter-spacing: 0; margin: 0 0 8px; }
.qa p { font-size: 13px; line-height: 1.65; color: var(--ink-soft); margin: 0; }

/* --- signature band: the one kinetic moment --- */
.signature { padding-top: 70px; }
.signature svg { width: 100%; height: auto; display: block; }
.sig-caps {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin-top: 6px;
}
.sig-caps .in { color: var(--ink-micro); }
.sig-caps .out { color: var(--cyan); }

/* --- CTA --- */
.cta {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-hero);
  padding: 52px 40px;
  text-align: center;
  margin-top: 70px;
}
.cta::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -170px;
  width: 460px;
  height: 300px;
  margin-left: -230px;
  background: radial-gradient(closest-side, rgba(124, 92, 255, 0.22), transparent);
  pointer-events: none;
}
.cta > * { position: relative; z-index: 1; }
.cta p { color: var(--ink-soft); max-width: 60ch; margin: 0 auto 24px; }
.cta-note {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-micro);
  margin-top: 18px;
}

/* --- footer --- */
.footer { border-top: 1px solid var(--hairline); margin-top: 70px; }
.footer .wrap { padding-top: 40px; padding-bottom: 30px; }
.foot-grid { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1fr; gap: 24px; }
.foot-blurb { font-size: 12.5px; color: var(--ink-micro); max-width: 34ch; margin: 12px 0 0; }
.foot-col h3 {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-micro);
  margin: 0 0 12px;
  font-weight: 400;
}
.foot-col ul { list-style: none; margin: 0; padding: 0; }
.foot-col li { margin-bottom: 2px; }
/* Footer links are touch targets, not just text. Padding brings them to the
   44px minimum without changing the visual rhythm much. */
.foot-col a {
  font-size: 13px;
  color: var(--ink-soft);
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.foot-col a { transition: color 160ms var(--ease-out); }
.foot-col a:hover { color: var(--ink); }
.foot-note {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-micro);
  margin-top: 10px;
}
.legal {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  border-top: 1px solid var(--hairline);
  margin-top: 30px;
  padding-top: 18px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-micro);
}

/* ------------------------------------------------------------ 404 page --- */
/* 404.html shares this stylesheet. When the v1 deck was replaced its
   .error-page / .error-copy / .error-back / .section rules went with it, so
   these exist to keep that page from rendering unstyled. */
.error-page {
  min-height: 78vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  max-width: var(--max-content);
  margin: 0 auto;
  padding: 80px var(--pad-page);
}
.error-page h1 {
  font-size: 52px;
  font-weight: 800;
  line-height: 1.06;
  max-width: 16ch;
  margin: 14px 0 18px;
}
.error-copy { color: var(--ink-soft); max-width: 48ch; margin: 0 0 26px; }
.error-back a { font-weight: 600; }

@media (max-width: 860px) {
  .error-page h1 { font-size: 34px; }
}

/* ------------------------------------------------------------- motion ---- */
/* Base state IS the finished state. Hidden only via `from`, so a paused,
   printed or reduced-motion render still shows everything. */
/* THE OVERTURE
   ------------
   One scored page load instead of a scattering of small effects. It is written
   to the content: this site sells a 17-agent operating system, so the hero
   boots the way that system does. Left column arrives in reading order, the
   console panel lands, the four agent rows report in one at a time with their
   border flashing violet as each comes online, and the answer chip swings in
   last because in the story it is the consequence, not the setup.

     0.05  avatar chip
     0.13  headline
     0.22  console panel
     0.27  sub-headline
     0.30  headline ignition sweep
     0.36  call to action pair
     0.44  proof line
     0.46  console title bar
     0.58  agent 01 reports in
     0.68  agent 02
     0.78  agent 03
     0.88  agent 04
     1.00  console footnote
     1.08  answer chip
     1.58  done

   THE RULE THAT MAKES THIS SAFE: every keyframe hides in `from` and fills
   `both`, so the FINISHED state is the element's base state. Kill animation
   (reduced motion, print, an old engine, a CSS parse failure) and the whole
   hero is simply present. Nothing here can leave content invisible, which is
   why it is built inside-out like this. The previous scroll system did the
   opposite and needed a 2.5s timeout to stop content vanishing.

   `.enter` no longer animates ITSELF, it animates its children. A parent and
   child both running `rise` compound their transforms and multiply their
   opacities, which reads as a slow smear rather than a cascade. */
.enter { animation: none; }

.hero-left > * { animation: rise 0.62s var(--ease-out) both; }
.hero-left > *:nth-child(1) { animation-delay: 0.05s; }
.hero-left > *:nth-child(2) { animation-delay: 0.13s; }
.hero-left > *:nth-child(3) { animation-delay: 0.27s; }
.hero-left > *:nth-child(4) { animation-delay: 0.36s; }
.hero-left > *:nth-child(5) { animation-delay: 0.44s; }

@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: none; }
}

/* The console materialises rather than slides. The scale is deliberately tiny
   (0.985); anything deeper distorts the child rows animating inside it. */
.hero-right > *:nth-child(1) { animation: panelIn 0.58s var(--ease-out) 0.22s both; }
.hero-right > *:nth-child(2) { animation: chipIn 0.62s var(--ease-out) 1.08s both; }

@keyframes panelIn {
  from { opacity: 0; transform: translate3d(0, 18px, 0) scale(0.985); }
  to { opacity: 1; transform: none; }
}
@keyframes chipIn {
  from { opacity: 0; transform: translate3d(20px, 12px, 0) rotate(1.6deg); }
  to { opacity: 1; transform: none; }
}

.hero .os-window > * { animation: rise 0.5s var(--ease-out) both; }
/* Agent rows get their own keyframe: the border flashes violet on arrival and
   settles back to the hairline, so each row reads as an agent coming online
   rather than as a list item fading in. Same specificity as the rule above, so
   this has to stay after it. */
.hero .os-window > .agent-row { animation: rowBoot 0.52s var(--ease-out) both; }

.hero .os-window > *:nth-child(1) { animation-delay: 0.46s; }
.hero .os-window > *:nth-child(2) { animation-delay: 0.58s; }
.hero .os-window > *:nth-child(3) { animation-delay: 0.68s; }
.hero .os-window > *:nth-child(4) { animation-delay: 0.78s; }
.hero .os-window > *:nth-child(5) { animation-delay: 0.88s; }
.hero .os-window > *:nth-child(6) { animation-delay: 1.00s; }

@keyframes rowBoot {
  from {
    opacity: 0;
    transform: translate3d(-10px, 0, 0);
    border-color: rgba(124, 92, 255, 0.55);
  }
  55% { border-color: rgba(124, 92, 255, 0.38); }
  to {
    opacity: 1;
    transform: none;
    border-color: var(--hairline);
  }
}

/* Headline ignition. Sweeps the bright band across "Be the answer." once, then
   parks it off-canvas again. Both background-position values are restated in
   both keyframes because the element carries two layers, and omitting the
   second would snap the colour ramp to 0. Scoped to .hero so the 404 page and
   the belief pull-quote keep the plain ramp. */
.hero .grad { animation: ignite 1.25s var(--ease-out) 0.30s both; }
@keyframes ignite {
  from { background-position: 130% 0, 0 0; }
  to { background-position: -70% 0, 0 0; }
}

@keyframes btnSheen {
  from { transform: translateX(-170%); opacity: 0; }
  22% { opacity: 1; }
  to { transform: translateX(420%); opacity: 0; }
}

/* REVEAL-DRIVEN, not load-driven. js/main.js adds .has-risen as a section
   enters view. Both of these animate FROM a hidden state TO the base state, so
   a section that never receives the class (already on screen at load, or JS
   off) shows the finished form. */
.kicker-row.has-risen::after { animation: ruleDraw 0.85s var(--ease-out) 0.08s both; }
@keyframes ruleDraw {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

/* The three proof numbers resolve into focus rather than counting up. A count
   up is the expected treatment and it fights the meaning: these are measured
   figures, not a scoreboard ticking. Blur costs nothing on three short strings
   and, unlike a clip reveal, cannot shave a glyph. */
.has-risen .stat-num { animation: numFocus 0.75s var(--ease-out) both; }
.stats .wrap > *:nth-child(1) .stat-num { animation-delay: 0.14s; }
.stats .wrap > *:nth-child(2) .stat-num { animation-delay: 0.22s; }
.stats .wrap > *:nth-child(3) .stat-num { animation-delay: 0.30s; }
@keyframes numFocus {
  from { opacity: 0; filter: blur(9px); transform: translateY(6px); }
  to { opacity: 1; filter: blur(0); transform: none; }
}

/* Read progress, welded to the bottom edge of the sticky header. Driven by a
   scroll timeline, so there is no scroll listener and nothing to throttle.
   Everything lives inside @supports: browsers without scroll timelines never
   get the pseudo-element at all, rather than getting a bar stuck at zero.
   .header is position: sticky, which is a positioned value, so it already
   establishes the containing block this needs. */
@supports (animation-timeline: scroll()) {
  .header::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -1px;
    height: 2px;
    background: var(--grad-primary);
    transform: scaleX(0);
    transform-origin: 0 50%;
    animation-name: readProgress;
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-timeline: scroll(root block);
  }
  @keyframes readProgress {
    from { transform: scaleX(0); }
    to { transform: scaleX(1); }
  }
}

.field span { animation: drift 26s ease-in-out infinite alternate; }
@keyframes drift {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(24px, -18px, 0); }
}

/* Signature draw-in. JS adds .draw once, on scroll into view. */
.sig-path { stroke-dasharray: 1; stroke-dashoffset: 0; }
.draw .sig-path { animation: sigDraw 1.3s var(--ease-out) both; }
.draw .p2 { animation-delay: 0.12s; }
.draw .p3 { animation-delay: 0.24s; }
.draw .p4 { animation-delay: 0.36s; }
.draw .p5 { animation-delay: 0.48s; }
@keyframes sigDraw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }

.draw .sig-node { animation: sigNode 0.5s ease both; animation-delay: 1.5s; }
@keyframes sigNode { from { opacity: 0; } to { opacity: 1; } }

.sig-ring {
  transform-box: fill-box;
  transform-origin: center;
  opacity: 0;
}
.draw .sig-ring { animation: sigRing 2.8s ease-out infinite; animation-delay: 1.9s; }
@keyframes sigRing {
  0% { transform: scale(0.5); opacity: 0.9; }
  75%, 100% { transform: scale(1.7); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }
  .lift:hover { transform: none; }
  .sig-ring { opacity: 0; }
}

/* --------------------------------------------------------- responsive ---- */
@media (max-width: 1040px) {
  .grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stack-grid,
  .testi-grid,
  .fit-grid { grid-template-columns: 1fr; }
  /* Single column: there is no second column to match height against, so the
     space-between that fills the gap on desktop would just push the channel
     link away from the text it belongs to. */
  .testi-copy { display: block; }
  .testi-foot { margin-top: 26px; }
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
  .kpis { grid-template-columns: repeat(2, 1fr); }
  .foot-grid { grid-template-columns: 1fr 1fr; }
}

/* THE HEADER'S REAL BREAKPOINT.
   The nav collapsed to a hamburger at 860px, but measured on the page the bar
   needs about 1034px before anything wraps: 609px of nav links at their
   natural width, a 167px brand, a 130px call to action, two 24px gaps and
   80px of page padding. Between 861px and 1034px the desktop nav was therefore
   shown at widths it could not fit, and it wrapped: the brand broke onto two
   lines and four of the eight links became two-line stacks. 1040px is that
   measurement plus a little slack.

   Only the nav moves to this breakpoint. The rest of the layout (hero
   stacking, page padding, hiding the header CTA) still changes at 860px,
   because those thresholds were correct. Splitting them is the point: one
   number was doing two jobs.

   js/main.js closes the open sheet at (min-width: 1041px) and must be kept in
   step with this value, or resizing out of the sheet leaves it stranded open. */
@media (max-width: 1040px) {
  .nav { display: none; }
  .nav.open {
    display: flex;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    gap: 0;
    background: var(--raised);
    border-bottom: 1px solid var(--hairline);
    padding: 8px var(--pad-page) 16px;
    margin: 0;
    overscroll-behavior: contain;
  }
  .nav.open a { padding: 12px 0; min-height: 44px; display: flex; align-items: center; }
  .nav-toggle { display: inline-flex; align-items: center; justify-content: center; }
}

@media (max-width: 860px) {
  :root { --pad-page: 22px; }

  .header .btn-primary { display: none; }

  .hero .wrap { grid-template-columns: 1fr; gap: 34px; padding-top: 44px; }
  .hero h1 { font-size: 40px; max-width: 18ch; }

  /* The chip stops overlapping and becomes a normal block. */
  /* `top` is reset too, not just `position`. A stale `top` on a static box is
     inert, but leaving it invites the next edit to reintroduce the overlap by
     flipping position back. */
  .answer-chip { position: static; top: auto; width: 100%; margin-top: 14px; }

  .stats .wrap { grid-template-columns: 1fr; }
  .stat { padding: 20px 0; }
  .stat + .stat { border-left: 0; border-top: 1px solid var(--hairline); padding-left: 0; }

  .grid-2,
  .grid-3,
  .grid-4,
  .kpis,
  .faq { grid-template-columns: 1fr; }

  /* Two vertical cards still fit side by side at 375px; they are the proof,
     so keep them paired rather than stacking into a long scroll. */
  .videos { gap: 10px; }

  .story-grid { grid-template-columns: 1fr; gap: 28px; }
  /* Photo leads on mobile: it is the fastest trust signal on a small screen. */
  .story-grid .portrait-frame { order: -1; max-width: 320px; }

  .cols { grid-template-columns: 1fr; }
  .col { padding: 20px 0; }
  .col + .col { border-left: 0; border-top: 1px solid var(--hairline); padding-left: 0; }

  .sec-head { font-size: 26px; }
  .belief blockquote { font-size: 30px; }
  .belief-kicker::before, .belief-kicker::after { width: 60px; }
  .cta { padding: 36px 22px; }
  .foot-grid { grid-template-columns: 1fr; }
  .legal { flex-direction: column; gap: 8px; }
  .utility .wrap { flex-direction: column; gap: 4px; }
  .matrix td.eng, .matrix th.eng { width: 34px; }
}

@media (max-width: 420px) {
  .hero h1 { font-size: 33px; }
  .belief blockquote { font-size: 25px; }
  .stat-num { font-size: 28px; }
  .engines .wrap { justify-content: flex-start; }
  .matrix th, .matrix td { padding: 8px 4px; font-size: 11.5px; }
}

/* ============================================================
   Blog / pillar pages (namespaced .post-*; added with the blog,
   nothing above this line changed)
   ============================================================ */
.post-hero { padding: 96px 0 40px; }
.post-hero h1 { font-family: 'Sora', sans-serif; font-size: clamp(30px, 5vw, 46px); line-height: 1.12; letter-spacing: -0.02em; margin: 14px 0 18px; }
.post-hero .post-sub { color: var(--ink-soft); font-size: 18px; line-height: 1.65; max-width: 62ch; }
.post-meta { display: flex; flex-wrap: wrap; gap: 10px 18px; margin-top: 22px; color: var(--ink-micro); font-family: 'Geist Mono', monospace; font-size: 12.5px; text-transform: uppercase; letter-spacing: 0.08em; }
.post-body { max-width: 72ch; padding-bottom: 64px; }
.post-body h2 { font-family: 'Sora', sans-serif; font-size: clamp(22px, 3.2vw, 30px); line-height: 1.2; margin: 52px 0 16px; letter-spacing: -0.015em; }
.post-body h3 { font-family: 'Sora', sans-serif; font-size: 19px; margin: 34px 0 10px; }
.post-body p, .post-body li { color: var(--ink-soft); font-size: 16.5px; line-height: 1.75; }
.post-body p { margin: 0 0 18px; }
.post-body ul, .post-body ol { margin: 0 0 20px; padding-left: 22px; }
.post-body li { margin-bottom: 8px; }
.post-body strong { color: var(--ink); }
.post-body a { text-decoration: underline; text-underline-offset: 3px; }
.post-callout { border: 1px solid rgba(34, 211, 238, 0.25); background: rgba(34, 211, 238, 0.05); border-radius: 12px; padding: 18px 20px; margin: 26px 0; }
.post-callout p { margin: 0; color: var(--ink); font-size: 15.5px; }
.post-proof-slot { border: 1px dashed rgba(154, 162, 180, 0.4); border-radius: 12px; padding: 16px 20px; margin: 26px 0; }
.post-proof-slot p { margin: 0; color: var(--ink-micro); font-size: 14.5px; }
.post-faq details { border-bottom: 1px solid rgba(154, 162, 180, 0.18); padding: 4px 0; }
.post-faq summary { cursor: pointer; color: var(--ink); font-weight: 600; font-size: 16px; padding: 12px 0; list-style: none; }
.post-faq summary::-webkit-details-marker { display: none; }
.post-faq details p { padding: 0 0 16px; margin: 0; }
.post-cta { border: 1px solid rgba(124, 92, 255, 0.35); background: rgba(124, 92, 255, 0.07); border-radius: 16px; padding: 28px; margin: 56px 0 0; }
.post-cta h2 { margin-top: 0; }
.blog-list { display: grid; gap: 22px; padding: 40px 0 80px; }
.blog-card { border: 1px solid rgba(154, 162, 180, 0.18); border-radius: 16px; padding: 26px 28px; display: block; transition: border-color 0.2s ease; }
.blog-card:hover { border-color: rgba(34, 211, 238, 0.45); }
.blog-card h2 { font-family: 'Sora', sans-serif; font-size: 22px; margin: 8px 0 10px; color: var(--ink); }
.blog-card p { color: var(--ink-soft); font-size: 15.5px; line-height: 1.65; margin: 0; }
.blog-card .card-kicker { font-family: 'Geist Mono', monospace; font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-micro); }


/* ==========================================================================
   SCROLL REVEAL + WORDMARK SHEEN
   Brand-kit motions 01 (rise-in) and 04 (sheen). Motion 05 (the signature
   draw-in) is the page's one kinetic moment and lives further up this file.

   .will-rise is added BY JAVASCRIPT. Without JS nothing is hidden, so the
   page is complete for crawlers, print and reduced-motion by default.
   ========================================================================== */

.will-rise {
  opacity: 0;
  transform: translateY(14px);
  /* Only transform and opacity animate: both are compositor-friendly and
     neither triggers layout. */
  transition:
    opacity 0.55s var(--ease-out),
    transform 0.55s var(--ease-out);
}
.has-risen { opacity: 1; transform: none; }

/* Staggered cascade within a section, so children arrive in reading order
   rather than all at once. Capped at six; past that the wait is noticeable. */
.will-rise:nth-child(2) { transition-delay: 0.05s; }
.will-rise:nth-child(3) { transition-delay: 0.10s; }
.will-rise:nth-child(4) { transition-delay: 0.15s; }
.will-rise:nth-child(5) { transition-delay: 0.20s; }
.will-rise:nth-child(n + 6) { transition-delay: 0.25s; }

/* Motion 04: a slow sheen across the wordmark. The only element allowed a
   looping highlight, and it is deliberately barely visible. */
.brand-name {
  background-image: linear-gradient(
    100deg,
    var(--ink) 0%, var(--ink) 42%,
    rgba(255, 255, 255, 0.92) 50%,
    var(--ink) 58%, var(--ink) 100%
  );
  background-size: 300% 100%;
  background-position: 100% 0;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: sheen 9s ease-in-out 2s infinite;
}
@keyframes sheen {
  0%, 72% { background-position: 100% 0; }
  92%, 100% { background-position: 0 0; }
}

@media (prefers-reduced-motion: reduce) {
  /* Kill the sheen and restore a plain painted wordmark. Without resetting
     text-fill-color the letters would stay transparent and vanish. */
  .brand-name {
    animation: none;
    background-image: none;
    -webkit-text-fill-color: var(--ink);
    color: var(--ink);
  }
  .will-rise { opacity: 1; transform: none; transition: none; }
  /* Belt and braces. JS already skips the reveal under reduced motion, so
     .will-rise should never land on .stack-grid here - but if it ever did,
     the coverage chips must not be left invisible. */
  .stack-grid.will-rise .chip,
  .stack-grid.has-risen .chip {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .chip { transition: none; }
  .chip:hover { transform: none; }
}
