/* ============================================================
   Liftli — "The Paper Atelier" design system
   Warm drafting paper · iron-gall ink · clay · red chalk · gold leaf
   ============================================================ */

/* ---------- Fonts (self-hosted, subset latin) ----------
   Both faces are ones ramp.com itself loads. Ramp's display face is TWK
   Lausanne (commercial WELTKERN — not legally self-hostable), so we use the
   two Plex faces Ramp also ships: IBM Plex Sans and IBM Plex Mono, both OFL
   and free. Ramp's rules are ported: 400 for display, 500 as the only
   emphasis, tracking 0, display leading ~1.0. */
@font-face {
  font-family: 'IBM Plex Sans';
  src: url('/fonts/plex-sans-400.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Sans';
  src: url('/fonts/plex-sans-500.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Sans';
  src: url('/fonts/plex-sans-600.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Sans';
  src: url('/fonts/plex-sans-400-i.woff2') format('woff2');
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Mono';
  src: url('/fonts/ibm-plex-mono-400.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
/* metric-matched fallbacks to kill font-swap CLS */
@font-face {
  font-family: 'IBM Plex Sans Fallback';
  src: local('Arial'), local('Helvetica Neue');
  size-adjust: 104%; ascent-override: 98%; descent-override: 26%; line-gap-override: 0%;
}
@font-face {
  font-family: 'IBM Plex Mono Fallback';
  src: local('Courier New'), local('Courier');
  size-adjust: 100%; ascent-override: 102%; descent-override: 30%; line-gap-override: 0%;
}

/* ---------- Tokens ---------- */
:root {
  /* paper */
  --canvas: #f6f2e8;
  --sheet: #fdfaf3;
  --sheet-2: #efe9db;
  /* ink */
  --ink: #221a10;
  --ink-soft: #463a2b;
  --ink-mute: #6d5f4b;
  /* accents */
  --clay: #cf6548;
  --clay-deep: #b25e49;
  /* link-safe clay: --clay-deep sits at ~4.6:1 on paper, too close to the
     floor once antialiasing is factored in. Links use this instead. */
  --clay-link: #96422f;
  --clay-pale: #ecb6a3;
  --clay-wash: #f6e3da;
  --sanguine: #a4432f;
  --gold: #8a6a1f;
  --gold-glint: #d8b357;
  --green: #3c6a48;
  /* night workshop (dark section) */
  --walnut: #251c11;
  --walnut-2: #2e2417;
  --cream-on-dark: #f2ecdd;
  --mute-on-dark: #b3a68e;
  /* lines */
  --hairline: rgba(34, 26, 16, 0.12);
  --hairline-strong: rgba(34, 26, 16, 0.2);
  /* CTA: ink, not clay — see .btn-primary */
  --cta: #1f1a13;
  --cta-hover: #33291d;
  --cta-ink: #fbf7ee;
  /* type */
  --sans: 'IBM Plex Sans', 'IBM Plex Sans Fallback', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  /* one voice — Ramp runs a single face site-wide. --serif is kept as an
     alias so older pages that reference it keep working. */
  --serif: 'IBM Plex Sans', 'IBM Plex Sans Fallback', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --mono: 'IBM Plex Mono', 'IBM Plex Mono Fallback', ui-monospace, 'SF Mono', Menlo, monospace;
  /* fluid scale */
  --step--3: clamp(0.7rem, 0.68rem + 0.05vw, 0.72rem);
  --step--2: clamp(0.79rem, 0.77rem + 0.06vw, 0.825rem);
  --step--1: clamp(0.92rem, 0.89rem + 0.09vw, 0.96rem);
  --step-0: clamp(1rem, 0.96rem + 0.22vw, 1.13rem);
  --step-1: clamp(1.19rem, 1.12rem + 0.35vw, 1.38rem);
  --step-2: clamp(1.5rem, 1.38rem + 0.62vw, 1.85rem);
  --step-3: clamp(1.9rem, 1.68rem + 1.08vw, 2.5rem);
  --step-4: clamp(2.35rem, 2rem + 1.75vw, 3.35rem);
  --step-5: clamp(2.9rem, 2.35rem + 2.75vw, 4.45rem);
  --step-6: clamp(3.4rem, 2.6rem + 4vw, 5.7rem);
  /* motion */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-soft: cubic-bezier(0.25, 1, 0.5, 1);
  --ease-move: cubic-bezier(0.65, 0, 0.35, 1);
  --t-micro: 180ms;
  --t-enter: 550ms;
  /* geometry */
  --radius: 14px;
  --radius-sm: 8px;
  --radius-lg: 22px;
  --radius-pill: 999px;
  /* leading + spacing ladders. Before these, the page rendered 14 line-height
     ratios (six inside 0.15) and 21 gaps (eight inside 7px) with no tokens at
     all — the same bypass pattern the font-size scale had. */
  --lh-tight: 1.06;
  --lh-snug: 1.2;
  --lh-head: 1.4;
  --lh-mid: 1.5;
  --lh-body: 1.6;
  /* the ladder as first declared (4/8/12/20/32/56) matched almost nothing the
     page actually drew, which is why it sat at zero consumers. These are the
     real clusters, measured from the sheet. */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 10px;
  --sp-4: 14px;
  --sp-5: 20px;
  --sp-6: 36px;
  --sp-section: clamp(28px, 4vw, 64px);
  --maxw: 1200px;
  --shadow-lift: 0 1px 2px rgba(34, 26, 16, 0.08), 0 12px 32px -12px rgba(34, 26, 16, 0.18);
  --shadow-rest: 0 1px 2px rgba(34, 26, 16, 0.05);
}

/* ---------- Reset & base ---------- */
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
[id] { scroll-margin-top: 92px; }
html { overflow-x: clip; }
body {
  font-family: var(--sans);
  font-size: var(--step-0);
  background: var(--canvas);
  color: var(--ink-soft);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
  text-rendering: optimizeLegibility;
}
/* paper tooth — 2KB inline noise, multiply so it warms not greys */
body::before {
  content: '';
  position: fixed; inset: 0; z-index: 1; pointer-events: none;
  opacity: 0.5; mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.55 0 0 0 0 0.5 0 0 0 0 0.42 0 0 0 0.045 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
main, header.site, footer.site { position: relative; z-index: 2; }
::selection { background: var(--clay-pale); color: var(--ink); }
a { color: inherit; text-decoration: none; }
img { max-width: 100%; height: auto; display: block; }
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 clamp(20px, 4vw, 40px); }

/* Ramp's two load-bearing rules: everything is weight 400 — hierarchy comes
   from size and colour, never from bold — and tracking stays at zero even at
   display sizes, with leading near 1.0. */
h1, h2, h3 {
  font-family: var(--sans);
  color: var(--ink);
  font-weight: 400;
  letter-spacing: -0.018em;
  line-height: 1.04;
  text-wrap: balance;
}
h2 { line-height: var(--lh-tight); }
h3 { line-height: var(--lh-snug); }
p { text-wrap: pretty; }
h1 em, h2 em, h3 em { font-style: normal; color: var(--clay-deep); }

/* ---------- Utility type ---------- */
/* Ramp sets its eyebrows as plain grey sans in sentence case. Mono + uppercase
   + wide tracking is the "AI-generated / futuristic" tell we are avoiding. */
.eyebrow {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  font-family: var(--sans);
  font-size: var(--step--1);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-mute);
}
.eyebrow .tick { color: var(--sanguine); font-weight: 500; }
.chalk { position: relative; white-space: nowrap; }
.chalk svg { position: absolute; left: -1%; bottom: -0.14em; width: 102%; height: 0.28em; overflow: visible; }
.chalk svg path { fill: none; stroke: var(--sanguine); stroke-width: 3.2; stroke-linecap: round; opacity: 0.75; }
.accent-i { font-style: italic; color: var(--clay-link); }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-3);
  font-family: var(--sans);
  font-size: var(--step--1); font-weight: 600;
  padding: 15px 28px;
  border-radius: 12px;
  border: 1px solid transparent;
  white-space: nowrap; cursor: pointer;
  transition: transform var(--t-micro) var(--ease-soft), box-shadow var(--t-micro) var(--ease-soft), background-color var(--t-micro) var(--ease-soft), border-color var(--t-micro) var(--ease-soft);
}
.btn svg { transition: transform var(--t-micro) var(--ease-soft); }
.btn:hover svg { transform: translateX(3px); }
.btn[data-dir="down"]:hover svg { transform: translateY(3px); }
/* Clay sits within a few degrees of Claude's own #D97757, so an orange CTA
   made visitors read the whole site as Claude-generated. Ink is the highest
   contrast available on warm paper — and it is what Ramp itself uses. */
.btn-primary {
  background: var(--cta);
  color: var(--cta-ink);
  box-shadow: 0 1px 2px rgba(34,26,16,.22), 0 10px 26px -12px rgba(34,26,16,.55);
}
.btn-primary:hover { background: var(--cta-hover); color: var(--cta-ink); transform: translateY(-2px); box-shadow: 0 2px 3px rgba(34,26,16,.2), 0 16px 34px -12px rgba(34,26,16,.6); }
.btn-primary:active { transform: translateY(0); }
.btn-ghost {
  background: transparent; color: var(--ink);
  border-color: var(--hairline-strong);
}
.btn-ghost:hover { border-color: var(--ink-mute); background: var(--sheet); transform: translateY(-2px); }
.btn:focus-visible, a:focus-visible, button:focus-visible { outline: 2.5px solid var(--clay-deep); outline-offset: 3px; border-radius: 6px; }

/* underline-draw links */
.ulink { position: relative; font-weight: 500; color: var(--ink); }
.ulink::after {
  content: ''; position: absolute; left: 0; bottom: -3px; height: 1.5px; width: 100%;
  background: var(--sanguine); transform: scaleX(0); transform-origin: right;
  transition: transform 280ms var(--ease-soft);
}
.ulink:hover::after { transform: scaleX(1); transform-origin: left; }

/* ---------- Header ---------- */
header.site {
  position: sticky; top: 0; z-index: 60;
  /* 82% let a NARROW dark slab (the featured pricing card, 328px) print its
     silhouette through as a hard-edged grey rectangle behind the nav — the
     .on-dark inversion never fires for it, because that only triggers on
     bands ≥55% of the viewport. 93% keeps the frost legible as depth while
     dropping a walnut slab's bleed-through from Δ38 to Δ17 luminance. */
  background: color-mix(in srgb, var(--canvas) 93%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(1.4); backdrop-filter: blur(14px) saturate(1.4);
  border-bottom: 1px solid var(--hairline);
}
.nav { display: flex; align-items: center; justify-content: space-between; height: 68px; max-width: var(--maxw); margin: 0 auto; padding: 0 clamp(20px, 4vw, 40px); }
.brand { display: inline-flex; align-items: center; gap: var(--sp-3); }
.brand-word { font-family: var(--sans); font-style: italic; font-weight: 400; font-size: var(--step-1); color: var(--ink); letter-spacing: 0; }
.brand-plane { width: 27px; height: 27px; }
.nav-links { display: flex; align-items: center; gap: clamp(14px, 2vw, 26px); }
.navlink { font-size: var(--step--1); font-weight: 500; color: var(--ink-soft); position: relative; }
.navlink::after { content: ''; position: absolute; left: 0; bottom: -5px; height: 1.5px; width: 100%; background: var(--sanguine); transform: scaleX(0); transform-origin: right; transition: transform 260ms var(--ease-soft); }
.navlink:hover { color: var(--ink); }
.navlink:hover::after { transform: scaleX(1); transform-origin: left; }
.nav .btn { padding: 11px 20px; font-size: var(--step--1); }
.menu-toggle { display: none; background: none; border: none; cursor: pointer; padding: 12px; margin-right: -12px; color: var(--ink); }
/* stroke via currentColor so the dark-ground override actually reaches it */
.menu-toggle svg { stroke: currentColor; }

/* ---------- Hero ---------- */
.hero { position: relative; overflow: clip; }
/* centred hero (post-bridge structure): logos → headline → sub → CTA → proof */
.hero-inner {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  padding-top: clamp(40px, 6vh, 76px); padding-bottom: clamp(28px, 4vh, 48px);
}
.hero-platforms { display: flex; align-items: center; justify-content: center; gap: var(--sp-4); margin-bottom: clamp(26px, 4vh, 40px); }
.hero-platforms .plat { display: block; border-radius: 12px; transition: transform var(--t-micro) var(--ease-soft), box-shadow var(--t-micro) var(--ease-soft); }
.hero-platforms .plat img { width: clamp(44px, 6vw, 54px); height: auto; display: block; border-radius: 12px; }
.hero-platforms .plat:hover { transform: translateY(-4px); box-shadow: 0 10px 22px -10px rgba(34,26,16,.4); }
.hero h1 {
  font-size: clamp(2.5rem, 1.7rem + 3.4vw, 4rem);
  margin: 0 0 22px;
  letter-spacing: -0.024em;
  line-height: 1;
  max-width: 20ch;
}
.hero h1 .knows { display: block; font-style: italic; color: var(--clay-deep); }
.hero .sub { font-size: var(--step-1); line-height: var(--lh-body); max-width: 40ch; color: var(--ink-mute); margin: 0 auto; }
.hero .sub b { color: var(--ink); font-weight: 500; }
.hero-cta { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-4); margin-top: 32px; }
.hero-proof {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--sp-3) 26px;
  margin-top: 34px; padding-top: 22px; border-top: 1px solid var(--hairline);
  font-size: var(--step--1); color: var(--ink-mute); max-width: 760px;
}

/* founder video — a deliberately compact slot until the real film exists,
   so an empty frame never owns a whole viewport */
.hero-video { margin: clamp(4px, 1.5vh, 14px) auto clamp(24px, 4vh, 44px); max-width: 560px; }
.hero-video .vframe {
  position: relative; aspect-ratio: 16 / 9; border-radius: var(--radius-lg);
  background: var(--sheet); border: 1px dashed var(--hairline-strong);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-3);
  overflow: hidden;
}
.hero-video .vframe .vplay {
  width: 46px; height: 46px; border-radius: 50%;
  background: var(--clay-wash); border: 1px solid rgba(178,94,73,.3);
  display: grid; place-items: center; pointer-events: none;
}
.hero-video .vframe .vplay::after {
  content: ''; margin-left: 3px;
  border-left: 13px solid var(--clay); border-top: 8px solid transparent; border-bottom: 8px solid transparent;
}
.hero-video .vghost { display: none; }
.hero-video .vlabel { font-family: var(--sans); font-weight: 500; font-size: var(--step--1); color: var(--ink-soft); text-align: center; padding: 0 20px; }
.hero-video .vnote { font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-mute); }
.hero-proof .hero-note { flex-basis: 100%; width: 100%; text-align: center; margin-top: 6px; font-size: var(--step--2); }
.hero-proof b { font-family: var(--sans); font-weight: 500; color: var(--ink); font-size: var(--step-0); font-variant-numeric: tabular-nums; }

/* ---------- Sections ---------- */
.sec { padding: clamp(72px, 11vh, 130px) 0; }
.sec-tight { padding: clamp(48px, 7vh, 84px) 0; }
.sec-head { max-width: 780px; margin-bottom: clamp(36px, 5vh, 64px); }
.sec-head.center { margin-left: auto; margin-right: auto; text-align: center; }
.sec-head h2 { font-size: var(--step-4); margin: 18px 0 16px; }
.sec-head p { font-size: var(--step-1); color: var(--ink-mute); }
/* content-visibility:auto was removed here: with rough intrinsic-size
   estimates Chrome's scroll anchoring makes the page jump on scroll. */

/* poster statement */

.chip { font-family: var(--mono); font-size: var(--step--3); font-weight: 500; padding: 4px 10px; border-radius: 999px; background: var(--clay-wash); color: var(--clay-link); border: 1px solid rgba(178,94,73,.25); }
.postmini {
  background: #fff; border: 1px solid var(--hairline); border-radius: 12px; padding: 13px 15px; margin-top: 10px;
  font-size: var(--step--1); color: var(--ink-soft); box-shadow: var(--shadow-rest);
}
.postmini .ph { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: 8px; }
.postmini .av { width: 30px; height: 30px; border-radius: 50%; background: var(--sanguine); color: #fff8f0; font-weight: 500; font-size: var(--step--2); display: grid; place-items: center; }
.postmini .pn { display: block; font-weight: 500; color: var(--ink); font-size: var(--step--1); line-height: var(--lh-snug); }
.postmini .pt { display: block; font-size: var(--step--3); color: var(--ink-mute); }

/* ---------- Night workshop (Keep Claude) ---------- */
.night { background: var(--walnut); color: var(--mute-on-dark); position: relative; }
.night::after { content: ''; position: absolute; inset: 0; pointer-events: none; background: radial-gradient(90% 60% at 50% 0%, rgba(216,179,87,0.07), transparent 70%); }
.night .wrap { position: relative; z-index: 1; }
.night h2 { color: var(--cream-on-dark); font-size: var(--step-4); }
.night h2 em { color: var(--gold-glint); }
.night .sec-head p { color: var(--mute-on-dark); }
.night .eyebrow { color: var(--mute-on-dark); }
.night .eyebrow .tick { color: var(--gold-glint); }
/* weighted, not a spreadsheet: the strongest claims span wider */
.gap-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px; background: rgba(242,236,221,0.14); border: 1px solid rgba(242,236,221,0.14); border-radius: var(--radius-lg); overflow: hidden; }
.gap-cell { background: var(--walnut-2); padding: 30px 26px 34px; position: relative; overflow: hidden; }

.gap-cell .n { font-family: var(--sans); font-size: var(--step-3); font-weight: 400; line-height: 1; color: var(--gold-glint); opacity: 0.45; position: absolute; right: 20px; top: 14px; font-variant-numeric: tabular-nums; }
.gap-cell h3 { color: var(--cream-on-dark); font-family: var(--sans); font-weight: 400; font-size: var(--step-0); letter-spacing: 0; margin: 12px 0 8px; line-height: 1.3; }
.gap-cell p { font-size: var(--step--1); line-height: var(--lh-body); }
/* the blunt line is the dark act's full-width poster moment */
.night .blunt { letter-spacing: -0.022em; text-align: center; margin-top: clamp(48px, 8vh, 80px); font-family: var(--sans); font-size: var(--step-5); line-height: var(--lh-tight); color: var(--cream-on-dark); font-style: normal; font-weight: 400; text-wrap: balance; max-width: none; }
.night .blunt b { color: var(--gold-glint); display: block; font-weight: 400; }

/* ---------- Receipts ---------- */
.receipts-grid { display: grid; grid-template-columns: minmax(0, 6fr) minmax(0, 5fr); gap: var(--sp-section); align-items: center; }
.proof-shot { position: relative; border-radius: var(--radius-lg); overflow: visible; margin: 0; }
.proof-shot .mount {
  border-radius: var(--radius-lg); overflow: hidden; border: 1px solid var(--hairline);
  box-shadow: var(--shadow-lift); background: #fff; aspect-ratio: 900 / 1208;
  transform: rotate(-1.4deg);
}
.proof-shot img { width: 100%; }
/* red-chalk annotation ring over the headline number — positioned INSIDE
   .mount so its coordinates track the image box exactly */
.proof-shot .mount { position: relative; }
.proof-shot .ring { position: absolute; left: 1.5%; top: 15.4%; width: 44%; height: 3.4%; pointer-events: none; }
.proof-shot .ring path { fill: none; stroke: var(--sanguine); stroke-width: 2.5; stroke-linecap: round; opacity: 0.7; vector-effect: non-scaling-stroke; }
.proof-shot figcaption {
  font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-mute); margin-top: 18px; text-align: left; transform: rotate(-1.4deg);
}
@media (min-width: 901px) { .proof-shot { margin-left: calc(-1 * clamp(0px, 3vw, 44px)); } }
.receipts-copy h2 { font-size: var(--step-5); margin: 14px 0 18px; }
.pmore { display: inline-block; font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.06em; color: var(--clay-link); margin: -10px 0 18px; padding: 6px 0; }
.pmore:hover { text-decoration: underline; text-underline-offset: 3px; }
.pcard.featured .pmore { color: var(--clay-pale); }
.receipts-copy p { color: var(--ink-mute); font-size: var(--step-0); margin-bottom: 14px; }
.stat b { display: block; font-family: var(--sans); font-size: var(--step-3); font-weight: 400; color: var(--ink); letter-spacing: 0; font-variant-numeric: tabular-nums; }
.stat span { font-size: var(--step--2); color: var(--ink-mute); }

/* ---------- Pricing ---------- */
.price-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; align-items: stretch; }
.pcard {
  background: var(--sheet); border: 1px solid var(--hairline); border-radius: var(--radius-lg);
  padding: 28px 24px; display: flex; flex-direction: column; position: relative;
  transition: transform 300ms var(--ease-soft), box-shadow 300ms var(--ease-soft);
}
.pcard:hover { transform: translateY(-4px); box-shadow: var(--shadow-lift); }
.pcard.featured { background: var(--walnut); border-color: var(--walnut); color: var(--mute-on-dark); }
.pcard.featured .ptier, .pcard.featured .amt, .pcard.featured li { color: var(--cream-on-dark); }
.pcard.featured .picp, .pcard.featured .pbilled, .pcard.featured .pnote { color: var(--mute-on-dark); }
.pcard .badge { position: absolute; top: -12px; left: 24px; font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.1em; text-transform: uppercase; background: var(--clay); color: #fff8f0; padding: 5px 12px; border-radius: 999px; }
.ptier { font-family: var(--sans); font-style: normal; font-size: var(--step-1); color: var(--ink); }
.picp { font-size: var(--step--1); color: var(--ink-mute); margin: 8px 0 16px; min-height: 4.4em; line-height: var(--lh-head); }
.pprice .amt { font-family: var(--sans); font-size: var(--step-3); font-weight: 500; color: var(--ink); letter-spacing: 0; }
.pprice small { font-size: var(--step--1); color: var(--ink-mute); }
.pbilled { font-size: var(--step--2); color: var(--ink-mute); margin-top: 2px; }
.pnote { font-size: var(--step--2); color: var(--clay-link); margin: 8px 0 4px; }
.pcard.featured .pnote { color: var(--clay-pale); }
.pcard .plus { font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.09em; text-transform: uppercase; color: var(--ink-mute); margin: 12px 0 2px; }
.pcard ul { list-style: none; margin: 12px 0 22px; flex: 1; display: flex; flex-direction: column; gap: var(--sp-3); }
.pcard li { display: flex; gap: var(--sp-3); font-size: var(--step--1); line-height: var(--lh-head); color: var(--ink-soft); }
.pcard li svg { flex: none; margin-top: 3px; color: var(--green); }
.pcard.featured li svg { color: var(--gold-glint); }
.pcard .btn { width: 100%; }
.pcard.featured .btn-primary { box-shadow: 0 1px 2px rgba(0,0,0,.3), 0 10px 26px -10px rgba(207,101,72,.6); }
.billing-toggle { display: flex; align-items: center; justify-content: center; gap: var(--sp-4); margin-top: 22px; }
.billing-toggle .seg { display: inline-flex; background: var(--sheet-2); border: 1px solid var(--hairline); border-radius: 999px; padding: 4px; }
.billing-toggle button { font-family: var(--sans); font-size: var(--step--1); font-weight: 500; color: var(--ink-mute); background: none; border: none; border-radius: 999px; padding: 12px 20px; min-height: 44px; cursor: pointer; transition: background-color var(--t-micro), color var(--t-micro); }
.billing-toggle button[aria-pressed="true"] { background: var(--sheet); color: var(--ink); box-shadow: var(--shadow-rest); }
.save-pill { font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.06em; color: var(--green); background: rgba(60,106,72,0.1); border: 1px solid rgba(60,106,72,0.25); padding: 5px 11px; border-radius: 999px; }
/* mobile: non-featured cards tuck their bullets behind a toggle (JS adds it;
   no-JS keeps everything visible) */
.pfeat-toggle { display: none; }
@media (max-width: 760px) {
  .js .pcard.collapsible ul { display: none; }
  .js .pcard.collapsible.open ul { display: flex; }
  .js .pcard.collapsible .pfeat-toggle {
    display: inline-flex; align-items: center; gap: var(--sp-2); align-self: flex-start;
    font-family: var(--mono); font-size: var(--step--2); letter-spacing: 0.06em;
    color: var(--ink-soft); background: none; border: none; padding: 12px 0; margin: 2px 0 6px; cursor: pointer;
  }
  .js .pcard.collapsible .pfeat-toggle::after { content: '+'; color: var(--sanguine); font-weight: 500; }
  .js .pcard.collapsible.open .pfeat-toggle::after { content: '–'; }
}
.plan-caption { text-align: center; font-size: var(--step--2); color: var(--ink-mute); margin-top: 26px; }
.plan-caption a { color: var(--clay-link); font-weight: 500; }
.li-sub { display: block; font-size: var(--step--2); color: var(--ink-mute); line-height: var(--lh-head); margin-top: 2px; }

/* ---------- FAQ ---------- */
.faq-list { max-width: 780px; margin: 0 auto; }
.faq-item { border-bottom: 1px solid var(--hairline); }
.faq-item summary {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-5);
  cursor: pointer; list-style: none; padding: 24px 4px;
  font-family: var(--sans); font-weight: 500; font-size: var(--step-1); letter-spacing: 0; color: var(--ink);
  transition: color var(--t-micro);
}
.faq-item summary:hover { color: var(--clay-link); }
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary .x { flex: none; width: 22px; height: 22px; position: relative; align-self: center; }
.faq-item summary .x { color: var(--sanguine); }
.faq-item summary .x::before, .faq-item summary .x::after { content: ''; position: absolute; inset: 0; margin: auto; background: currentColor; border-radius: 2px; transition: transform 260ms var(--ease-soft); }
.faq-item summary .x::before { width: 15px; height: 2.5px; transform: rotate(-3deg); }
.faq-item summary .x::after { width: 2.5px; height: 15px; transform: rotate(-3deg); }
.faq-item[open] summary .x::before { transform: rotate(2deg); }
.faq-item[open] summary .x::after { transform: rotate(87deg); }
.faq-item .a { padding: 0 4px 24px; color: var(--ink-mute); max-width: 62ch; line-height: var(--lh-body); }
.faq-item .a a { color: var(--clay-link); font-weight: 500; }
.faq-item .a code { font-family: var(--mono); font-size: 0.85em; background: var(--sheet-2); padding: 2px 6px; border-radius: 6px; }

/* ---------- CTA band ---------- */
.sec.cta-band { position: relative; }
.cta-grid { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 4fr); gap: var(--sp-section); align-items: center; }
.cta-grid h2 { font-size: var(--step-5); letter-spacing: -0.022em; margin-bottom: 20px; }
.cta-grid h2 em { color: var(--clay-deep); }
.cta-grid .cta-sub { font-size: var(--step-1); color: var(--ink-mute); max-width: 30em; }
.cta-art { border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow-lift); transform: rotate(-1.4deg); }

/* ============================================================
   CAPABILITIES — one real in-app scene per capability.
   Alternating editorial rows, not a grid of identical cards.
   ============================================================ */
.cap {
  display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: var(--sp-section); align-items: center;
  padding: clamp(38px, 6vh, 76px) 0;
}
.cap + .cap { border-top: 1px solid var(--hairline); }
.cap.flip { grid-template-columns: minmax(0, 6fr) minmax(0, 5fr); }
.cap.flip .cap-copy { order: 2; }
.cap-n { font-family: var(--sans); font-size: var(--step--1); font-weight: 400; letter-spacing: 0; text-transform: none; color: var(--ink-mute); }
.cap-copy h3 { font-size: var(--step-3); margin: 14px 0 16px; }
.cap-copy p { font-size: var(--step-0); color: var(--ink-mute); line-height: var(--lh-body); max-width: 40ch; }

/* the scene: a soft app window, no heavy chrome */
.cap-scene { background: var(--sheet); border: 1px solid var(--hairline); border-radius: var(--radius-lg); box-shadow: var(--shadow-lift); overflow: hidden; }
.scene-bar { display: flex; align-items: center; gap: var(--sp-3); padding: 12px 16px; border-bottom: 1px solid var(--hairline); background: var(--canvas); }
.scene-bar .dots { display: flex; gap: 5px; }
.scene-bar .dots i { width: 9px; height: 9px; border-radius: 50%; background: var(--sheet-2); border: 1px solid var(--hairline); }
.scene-bar .who { display: flex; align-items: center; gap: var(--sp-2); font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-mute); }
.scene-body { padding: clamp(16px, 2.2vw, 24px); display: flex; flex-direction: column; gap: var(--sp-4); }
.scene-body .u { align-self: flex-end; max-width: 88%; background: var(--walnut); color: var(--cream-on-dark); border-radius: 16px 16px 4px 16px; padding: 11px 15px; font-size: var(--step--1); line-height: var(--lh-mid); }
.scene-body .a { align-self: flex-start; max-width: 94%; font-size: var(--step--1); line-height: var(--lh-body); color: var(--ink-soft); }
.scene-body .a b { color: var(--ink); }

/* ICP checkbox answers */
.choices { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.choice {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  font-size: var(--step--2); color: var(--ink-mute);
  border: 1px solid var(--hairline); border-radius: 999px; padding: 7px 13px 7px 9px; background: var(--canvas);
}
.choice .box { width: 15px; height: 15px; border-radius: 6px; border: 1.5px solid var(--hairline-strong); display: grid; place-items: center; font-size: var(--step--3); color: transparent; }
.choice.on { color: var(--ink); border-color: rgba(178,94,73,.4); background: var(--clay-wash); }
.choice.on .box { background: var(--clay); border-color: var(--clay); color: #2b1509; font-weight: 500; }

/* idea list */
.idealist { list-style: none; display: flex; flex-direction: column; gap: var(--sp-3); }
.idealist li { display: flex; flex-direction: column; gap: 2px; padding-left: 14px; border-left: 2px solid var(--clay-pale); }
.idealist b { font-size: var(--step--1); color: var(--ink); font-weight: 500; line-height: var(--lh-head); }
.idealist span { font-size: var(--step--2); color: var(--ink-mute); line-height: var(--lh-head); }

/* critique pass */
.critique { background: var(--canvas); border: 1px solid var(--hairline); border-radius: 12px; padding: 13px 15px; display: flex; flex-direction: column; gap: var(--sp-2); }
.crit-h { font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-mute); }
.crit-line { display: flex; gap: var(--sp-3); align-items: flex-start; font-size: var(--step--2); line-height: var(--lh-head); color: var(--ink-soft); }
.crit-line .m { flex: none; width: 17px; height: 17px; border-radius: 50%; display: grid; place-items: center; font-size: var(--step--3); font-weight: 500; margin-top: 1px; }
.crit-line.ok .m { background: rgba(60,106,72,.14); color: var(--green); }
/* --sanguine on its own 14% wash landed at exactly 4.5:1 — on the line, not over
   it. --clay-link is the same hue one step darker and clears at 5.24:1. */
.crit-line.warn .m { background: rgba(164,67,47,.14); color: var(--clay-link); }

@media (max-width: 900px) {
  .cap, .cap.flip { grid-template-columns: minmax(0, 1fr); gap: 26px; }
  .cap.flip .cap-copy { order: 0; }
}

/* ============================================================
   CONNECT — pick your AI, three steps, see the result.
   Simplicity model: post-bridge. Skin: Paper Atelier.
   ============================================================ */
.connect-sec { background: var(--sheet); border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline); }

.agent-tabs {
  display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-3);
  margin: 0 auto clamp(28px, 4vh, 44px);
}
.agent-tab {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  font-family: var(--sans); font-size: var(--step--1); font-weight: 500;
  color: var(--ink-mute); background: var(--canvas);
  border: 1px solid var(--hairline); border-radius: 999px;
  padding: 11px 20px; min-height: 46px; cursor: pointer;
  transition: color var(--t-micro), border-color var(--t-micro), background-color var(--t-micro), transform var(--t-micro);
}
.agent-tab:hover { color: var(--ink); border-color: var(--hairline-strong); transform: translateY(-1px); }
.agent-tab[aria-selected="true"] {
  color: var(--ink); background: var(--sheet); border-color: var(--ink-mute);
  box-shadow: 0 1px 2px rgba(34,26,16,.06), 0 8px 20px -12px rgba(34,26,16,.3);
}
.agent-tab .logo { width: 19px; height: 19px; flex: none; display: block; }
.agent-tab .soon { font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.08em; text-transform: uppercase; color: var(--gold); background: rgba(216,179,87,0.16); padding: 3px 7px; border-radius: 999px; }

.connect-grid { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); gap: clamp(22px, 3vw, 40px); align-items: start; }

.connect-card {
  background: var(--canvas); border: 1px solid var(--hairline); border-radius: var(--radius-lg);
  padding: clamp(24px, 3vw, 34px);
}
.connect-card .fastest { font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.13em; text-transform: uppercase; color: var(--sanguine); }
.connect-card h3 { font-size: var(--step-2); margin: 12px 0 20px; }
.connect-steps { list-style: none; display: flex; flex-direction: column; gap: var(--sp-4); margin-bottom: 24px; }
.connect-steps li { display: flex; gap: var(--sp-4); align-items: flex-start; font-size: var(--step--1); line-height: var(--lh-head); color: var(--ink-soft); }
.connect-steps .num {
  flex: none; width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center; margin-top: 1px;
  font-family: var(--mono); font-size: var(--step--2); font-weight: 500;
  color: var(--sanguine); background: var(--clay-wash); border: 1px solid rgba(178,94,73,.22);
}
.connect-steps code { font-family: var(--mono); font-size: 0.86em; background: var(--sheet-2); padding: 2px 6px; border-radius: 6px; word-break: break-all; }
.connect-card .fine { font-size: var(--step--2); color: var(--ink-mute); margin-top: 14px; }
.connect-card .fine a { color: var(--clay-link); font-weight: 500; }

/* copyable command / URL row */
.copyrow {
  display: flex; align-items: center; gap: var(--sp-3); max-width: 100%;
  background: var(--walnut); border-radius: 12px; padding: 12px 12px 12px 15px; margin-bottom: 20px;
}
.copyrow code { min-width: 0; font-family: var(--mono); font-size: var(--step--2); color: var(--cream-on-dark); background: none; padding: 0; overflow-x: auto; white-space: nowrap; scrollbar-width: none; flex: 1; }
.copyrow code::-webkit-scrollbar { display: none; }
.copybtn {
  flex: none; font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--walnut); background: var(--gold-glint); border: none; border-radius: 8px;
  padding: 9px 12px; min-height: 40px; cursor: pointer; font-weight: 500;
  transition: background-color var(--t-micro), transform var(--t-micro);
}
.copybtn:hover { background: #e6c775; transform: translateY(-1px); }
.copybtn.done { background: var(--green); color: var(--cream-on-dark); }

/* the "what it looks like once connected" mock */
.connect-mock {
  background: var(--canvas); border: 1px solid var(--hairline); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift); overflow: hidden;
}
.mock-bar { display: flex; align-items: center; gap: var(--sp-3); padding: 13px 16px; border-bottom: 1px solid var(--hairline); background: var(--sheet); }
.mock-bar .dots { display: flex; gap: 5px; }
.mock-bar .dots i { width: 9px; height: 9px; border-radius: 50%; background: var(--sheet-2); border: 1px solid var(--hairline); }
.mock-bar .who { font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-mute); display: flex; align-items: center; gap: var(--sp-2); }
.mock-bar .who img, .mock-bar .who svg { width: 15px; height: 15px; }
.mock-body { padding: clamp(18px, 2.4vw, 26px); display: flex; flex-direction: column; gap: var(--sp-4); min-height: 300px; }
.mock-body .u { align-self: flex-end; max-width: 88%; background: var(--walnut); color: var(--cream-on-dark); border-radius: 16px 16px 4px 16px; padding: 12px 16px; font-size: var(--step--1); line-height: var(--lh-mid); }
.mock-body .a { align-self: flex-start; max-width: 92%; font-size: var(--step--1); line-height: var(--lh-body); color: var(--ink-soft); }
.toolchip {
  display: inline-flex; align-items: center; gap: var(--sp-3); align-self: flex-start;
  background: var(--sheet); border: 1px solid var(--hairline); border-radius: 999px;
  padding: 7px 14px 7px 10px; font-family: var(--mono); font-size: var(--step--2); color: var(--ink-soft);
}
.toolchip .ic { width: 15px; height: 15px; flex: none; }
.toolchip .meta { color: var(--ink-mute); }
.toolchip .ok { color: var(--green); font-weight: 500; }

/* ============================================================
   ON THE GO — the phone is the capture device (Telegram).
   Visualization model: getstanley.ai. Content: Liftli's real flow.
   ============================================================ */
.togo-copy h2 { font-size: var(--step-4); margin: 16px 0 18px; }
.togo-copy > p { font-size: var(--step-1); color: var(--ink-mute); max-width: 30em; }
.togo-note { font-size: var(--step--2); color: var(--ink-mute); border-top: 1px solid var(--hairline); padding-top: 16px; }
.togo-note b { color: var(--ink); font-weight: 500; }

/* phone */
.phone {
  position: relative; width: min(330px, 82vw);
  background: var(--walnut); border-radius: 42px; padding: 11px;
  box-shadow: 0 2px 4px rgba(34,26,16,.14), 0 40px 70px -30px rgba(34,26,16,.5);
  transform: rotate(-1.2deg);
}
.phone::before { /* notch */
  content: ''; position: absolute; top: 20px; left: 50%; transform: translateX(-50%);
  width: 96px; height: 22px; background: var(--walnut); border-radius: 999px; z-index: 3;
}
.pm { max-width: 86%; font-size: var(--step--1); line-height: var(--lh-head); border-radius: 16px; padding: 10px 13px; }
.pm.out { align-self: flex-end; background: var(--clay); color: #2b1509; border-bottom-right-radius: 5px; }
.pm.in { align-self: flex-start; background: var(--canvas); color: var(--ink-soft); border: 1px solid var(--hairline); border-bottom-left-radius: 5px; }
.pm .tstamp { display: block; font-family: var(--mono); font-size: var(--step--3); opacity: .6; margin-top: 5px; }
.pm.system {
  align-self: center; max-width: 92%; text-align: center;
  font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.04em;
  color: var(--green); background: rgba(60,106,72,0.1); border: 1px solid rgba(60,106,72,0.22);
  border-radius: 999px; padding: 8px 14px;
}
.js .pm { opacity: 0; transform: translateY(10px); }
@media (prefers-reduced-motion: reduce) { .js .pm { opacity: 1; transform: none; } }

@media (max-width: 900px) {
  .connect-grid { grid-template-columns: minmax(0, 1fr); }
  /* heading frames the visual — never the other way round */
  .togo-copy { order: 1; }
  .phone { transform: rotate(0deg); width: min(300px, 78vw); }
}
@media (max-width: 760px) {
  .agent-tabs { flex-wrap: nowrap; overflow-x: auto; justify-content: flex-start; scrollbar-width: none; padding-bottom: 4px; scroll-snap-type: x proximity; }
  .agent-tabs::-webkit-scrollbar { display: none; }
  .agent-tab { flex: none; scroll-snap-align: start; }
  .mock-body { min-height: 0; }
}

/* ---------- Agents note ---------- */
.agents-note { border-top: 1px solid var(--hairline); background: var(--sheet); }
.agents-note .inner { padding: 30px 0; font-size: var(--step--1); color: var(--ink-mute); max-width: 72ch; }
.agents-note h2 { font-family: var(--sans); font-size: var(--step--1); font-weight: 500; letter-spacing: 0; color: var(--ink-soft); margin-bottom: 8px; }
.agents-note a { color: var(--clay-link); font-weight: 500; }
.agents-note code { font-family: var(--mono); font-size: 0.82em; background: var(--sheet-2); padding: 2px 6px; border-radius: 6px; }

/* ---------- Footer ---------- */
footer.site { background: var(--walnut); color: var(--mute-on-dark); }
.foot-close { position: relative; padding: clamp(56px, 9vh, 100px) clamp(20px, 4vw, 40px) clamp(24px, 4vh, 48px); }
.foot-close::after { content: ''; position: absolute; left: clamp(20px, 4vw, 40px); right: clamp(20px, 4vw, 40px); bottom: 0; height: 1px; background: rgba(242,236,221,0.12); }
.foot-close .close-line { font-family: var(--sans); font-style: normal; font-weight: 400; color: var(--cream-on-dark); font-size: var(--step-4); letter-spacing: -0.02em; max-width: 14em; line-height: 1.1; text-wrap: balance; }
.foot-close .close-line .gl { color: var(--gold-glint); }
.foot-top { display: grid; grid-template-columns: minmax(220px, 1.5fr) repeat(4, minmax(0, 1fr)); gap: var(--sp-6) 28px; padding: clamp(36px, 6vh, 60px) 0; }
.foot-brand p { font-size: var(--step--1); line-height: var(--lh-body); margin-top: 14px; max-width: 26em; }
.foot-brand .brand-word { color: var(--cream-on-dark); }
.foot-col .foot-h { font-family: var(--mono); font-size: var(--step--3); font-weight: 500; letter-spacing: 0.13em; text-transform: uppercase; color: var(--gold-glint); margin-bottom: 12px; }
.foot-col a { display: block; font-size: var(--step--1); padding: 8px 0; color: var(--mute-on-dark); transition: color var(--t-micro); }
.foot-col a:hover { color: var(--cream-on-dark); }
.foot-meta { padding: 18px 0 0; font-size: var(--step--2); color: var(--mute-on-dark); line-height: 2; }
.foot-meta a { color: var(--mute-on-dark); text-decoration: underline; text-decoration-color: rgba(242,236,221,0.25); text-underline-offset: 3px; padding: 6px 2px; display: inline-block; }
.foot-meta a:hover { color: var(--cream-on-dark); }
.foot-meta .lbl { font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.1em; text-transform: uppercase; margin-right: 8px; }
.foot-bottom { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: var(--sp-4); padding: 22px 0 30px; border-top: 1px solid rgba(242,236,221,0.12); font-size: var(--step--2); }
.foot-social { display: flex; gap: var(--sp-5); }
.foot-social a { color: var(--mute-on-dark); transition: color var(--t-micro), transform var(--t-micro); }
.foot-social a:hover { color: var(--cream-on-dark); transform: translateY(-2px); }

/* ---------- Reveal animations ----------
   Default (no JS / unsupported / reduced motion): content simply visible. */
.js .reveal { opacity: 0; transform: translateY(22px); }
.js .reveal.in { opacity: 1; transform: none; transition: opacity var(--t-enter) var(--ease-out), transform var(--t-enter) var(--ease-out); }
.js .reveal.in.d1 { transition-delay: 70ms; } .js .reveal.in.d2 { transition-delay: 140ms; } .js .reveal.in.d3 { transition-delay: 210ms; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
  .js .reveal { opacity: 1; transform: none; }
}

/* ---------- Responsive ---------- */
@media (max-width: 1080px) {
  .price-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .gap-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .foot-top { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  .hero-inner { grid-template-columns: 1fr; }
      .receipts-grid, .cta-grid { grid-template-columns: 1fr; }
  .cta-art { max-width: 380px; }
    .receipts-grid .proof-shot { order: 2; }
}
@media (max-width: 760px) {
  .nav-links {
    display: none; position: absolute; top: 68px; left: 0; right: 0;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--canvas); border-bottom: 1px solid var(--hairline);
    padding: 10px 20px 20px; box-shadow: var(--shadow-lift);
  }
  .nav-links.open { display: flex; }
  .navlink { padding: 13px 4px; font-size: var(--step-0); border-bottom: 1px solid var(--hairline); }
  .navlink::after { display: none; }
  .nav-links .btn { margin-top: 14px; }
  .menu-toggle { display: block; }
  .hero h1 { font-size: var(--step-4); }
  .price-grid, .gap-grid { grid-template-columns: 1fr; }
  .foot-top { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .gap-cell:nth-child(1), .gap-cell:nth-child(4) { grid-column: auto; }
  .foot-col a { padding: 11px 0; }
  .foot-meta a { padding: 10px 2px; }
  .hero-proof { gap: var(--sp-2) 18px; }
  .btn { width: 100%; }
  .nav .btn, .billing-toggle button { width: auto; }
}

/* ============================================================
   WALL OF LOVE — real reviews, and an open invitation to add one
   ============================================================ */
.wall-sec { background: var(--sheet); border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline); }
.wall-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--sp-5); align-items: stretch; }
@media (min-width: 1080px) { .wall-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.love {
  background: var(--canvas); border: 1px solid var(--hairline); border-radius: var(--radius-lg);
  padding: 26px 26px 24px; display: flex; flex-direction: column; gap: var(--sp-4);
}
.love .stars { color: var(--gold-glint); font-size: var(--step-0); letter-spacing: 2px; line-height: 1; }
.love blockquote { font-size: var(--step--1); line-height: var(--lh-body); color: var(--ink-soft); }
.love figcaption { display: flex; flex-direction: column; gap: 2px; margin-top: auto; }
.love .who { font-weight: 500; color: var(--ink); font-size: var(--step--1); }
.love .role { font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.06em; color: var(--ink-mute); }
.love-add { background: var(--walnut); border-color: var(--walnut); justify-content: center; }
.love-add .add-eyebrow { font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.13em; text-transform: uppercase; color: var(--gold-glint); }
.love-add p { color: var(--mute-on-dark); font-size: var(--step--1); line-height: var(--lh-body); }
.love-add .btn { width: 100%; }

/* review dialog */
.review-modal {
  border: none; border-radius: var(--radius-lg); padding: 0; max-width: 520px; width: calc(100vw - 40px);
  margin: auto;  /* keep it centred — the reset's `margin: 0` breaks dialog centring */
  max-height: calc(100dvh - 40px); overflow-y: auto;
  background: var(--canvas); color: var(--ink-soft); box-shadow: 0 40px 90px -30px rgba(34,26,16,.6);
}
.review-modal::backdrop { background: rgba(34,26,16,.55); backdrop-filter: blur(3px); }
.review-form { padding: clamp(22px, 3vw, 32px); display: flex; flex-direction: column; gap: var(--sp-2); }
.rm-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; position: sticky; top: 0; background: var(--canvas); z-index: 1; padding-top: 2px; }
.rm-head h3 { font-size: var(--step-2); }
.rm-close { background: none; border: none; font-size: var(--step-2); line-height: 1; color: var(--ink-mute); cursor: pointer; padding: 4px 10px; border-radius: 8px; min-height: 44px; }
.rm-close:hover { color: var(--ink); background: var(--sheet-2); }
.rm-label { font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-mute); margin-top: 12px; }
.rm-stars { display: flex; gap: 4px; }
.rm-stars .star { background: none; border: none; font-size: var(--step-2); line-height: 1; color: var(--sheet-2); cursor: pointer; padding: 4px 6px; min-height: 44px; transition: color var(--t-micro), transform var(--t-micro); }
.rm-stars .star:hover { transform: scale(1.12); }
.rm-stars .star.on { color: var(--gold-glint); }
.review-form textarea, .review-form input[type="url"] {
  font-family: var(--sans); font-size: var(--step--1); color: var(--ink);
  background: var(--sheet); border: 1px solid var(--hairline-strong); border-radius: 12px;
  padding: 13px 15px; width: 100%; resize: vertical;
}
.review-form textarea:focus, .review-form input:focus { outline: 2.5px solid var(--clay-deep); outline-offset: 2px; }
.rm-hint { font-size: var(--step--2); color: var(--ink-mute); }
.rm-consent { display: flex; gap: var(--sp-3); align-items: flex-start; margin-top: 16px; font-size: var(--step--1); line-height: var(--lh-head); color: var(--ink-soft); cursor: pointer; }
.rm-consent input { margin-top: 3px; width: 18px; height: 18px; accent-color: var(--clay); flex: none; }
.rm-actions { display: flex; gap: var(--sp-3); justify-content: flex-end; margin-top: 20px; }
.rm-note { font-size: var(--step--2); margin-top: 10px; min-height: 1.3em; }
.rm-note.ok { color: var(--green); }
.rm-note.err { color: var(--sanguine); }

/* mid-page CTA */
.cta-mid .center { text-align: center; }
.cta-mid h2 { font-size: var(--step-3); margin-bottom: 22px; }

/* ============================================================
   BUILDER
   ============================================================ */
.builder-grid { display: grid; grid-template-columns: minmax(0, 4fr) minmax(0, 7fr); gap: var(--sp-section); align-items: center; }
.builder-photo { margin: 0; }
.bphoto {
  aspect-ratio: 4 / 5; border-radius: var(--radius-lg); background: var(--sheet-2);
  border: 1px solid var(--hairline); box-shadow: var(--shadow-lift);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-4);
  transform: rotate(-1.4deg);
}
.bphoto .bp-initial { font-size: var(--step-4); color: var(--clay); font-weight: 500; }
.bphoto .bp-note { font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.11em; text-transform: uppercase; color: var(--ink-mute); }
.builder-photo figcaption { font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.07em; color: var(--ink-mute); margin-top: 16px; text-align: center; }
.builder-copy h2 { font-size: var(--step-4); margin: 14px 0 18px; }
.builder-copy p { color: var(--ink-mute); line-height: var(--lh-body); margin-bottom: 14px; }
.builder-copy .lede { font-size: var(--step-1); color: var(--ink-soft); }
.builder-sign { font-size: var(--step--1); margin-top: 20px; }

/* ============================================================
   SUPPORTED PLATFORMS (clickable → per-platform pages)
   ============================================================ */
.plat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; }
.plat-card {
  display: flex; flex-direction: column; gap: var(--sp-4);
  background: var(--sheet); border: 1px solid var(--hairline); border-radius: var(--radius-lg);
  padding: 26px 24px 24px; color: var(--ink-soft);
  transition: transform 300ms var(--ease-soft), box-shadow 300ms var(--ease-soft), border-color 300ms var(--ease-soft);
}
.plat-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lift); border-color: var(--hairline-strong); }
.plat-card img { width: 42px; height: 42px; border-radius: 12px; }
.plat-card h3 { font-size: var(--step-2); }
.plat-card p { font-size: var(--step--1); color: var(--ink-mute); line-height: var(--lh-mid); }
.plat-card .go { font-family: var(--mono); font-size: var(--step--3); white-space: nowrap; letter-spacing: 0.07em; color: var(--clay-link); margin-top: auto; }
.plat-card.soon { background: var(--canvas); border-style: dashed; }
.plat-card.soon .badge-soon { min-height: 42px; display: inline-flex; align-items: center;
  /* keeps its heading on the same baseline as the three logo cards */
 font-family: var(--mono); font-size: var(--step--3); letter-spacing: 0.1em; text-transform: uppercase; color: var(--gold); background: rgba(216,179,87,.16); padding: 4px 9px; border-radius: 999px; align-self: flex-start; }

@media (max-width: 900px) {
  .builder-grid { grid-template-columns: 1fr; }
  .builder-photo { max-width: 320px; margin: 0 auto; }
  .bphoto { transform: none; }
}

/* ============================================================
   PRODUCT SCREENSHOTS — real captures, framed as evidence
   ============================================================ */
.cap-shot {
  position: relative; margin: 0; width: 100%;
  border-radius: var(--radius-lg); overflow: hidden;
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-lift);
  background: var(--sheet-2);
  max-width: 460px; justify-self: end;
}
.cap-shot img {
  width: 100%; height: auto; display: block;
  max-height: 560px; object-fit: cover; object-position: top center;
}
/* per-shot ratios: reserves the box for lazy-load without cropping the proof */
.cap-shot--wide img { aspect-ratio: 720 / 669; }
.cap-shot--tall img { aspect-ratio: 3 / 4; }
/* the raw captures end on a hard crop; fade them out so the truncation
   reads as a designed edge rather than a broken image */
/* the fade and the colour wash belong to the IMAGE, not the figure: the
   <figcaption> is the figure's last child, so anchoring them to .cap-shot
   washed the caption out and left the crop edge itself untouched. */
.cap-shot picture { position: relative; display: block; }
.cap-shot picture::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 112px;
  background: linear-gradient(to bottom, rgba(246,242,232,0) 0%, var(--canvas) 66%);
  pointer-events: none;
}
/* reaches full opacity at 66% so the crop never ends on a half-readable line */
.togo-sec .cap-shot picture::after { background: linear-gradient(to bottom, rgba(239,233,219,0) 0%, var(--sheet-2) 66%); }

/* the published-post receipt: the one third-party-verifiable claim on the
   page, so it gets to look like a physical artifact, not a sentence */
.proof-line { margin-top: 18px; }
.proof-chip {
  display: inline-flex; align-items: center; gap: var(--sp-3);
  background: var(--canvas); border: 1px solid rgba(178,94,73,.35); border-radius: 999px;
  padding: 10px 18px 10px 12px; font-size: var(--step--1); color: var(--ink); font-weight: 500;
  box-shadow: var(--shadow-rest);
  transition: transform var(--t-micro) var(--ease-soft), box-shadow var(--t-micro) var(--ease-soft), border-color var(--t-micro);
}
.proof-chip:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); border-color: var(--clay); }
.proof-chip img { width: 20px; height: 20px; border-radius: 6px; flex: none; }
.proof-chip .arw { color: var(--clay-link); font-weight: 500; }

/* second tonal ground: the Telegram section gets its own paper so the green
   captures sit in a field that flatters them instead of fighting the cream */
.togo-sec { background: var(--sheet-2); border-top: 1px solid var(--hairline-strong); border-bottom: 1px solid var(--hairline-strong); }

/* close the doubled vertical padding where .sec and .cap stack */
.caps-sec .cap:first-of-type, .togo-sec .cap:first-of-type { padding-top: 0; }
.caps-sec .cap:last-of-type, .togo-sec .cap:last-of-type { padding-bottom: 0; }

@media (max-width: 900px) {
  .cap-shot, .cap.flip .cap-shot { justify-self: stretch; max-width: none; }
  .cap-shot img { max-height: 460px; }
}

/* the tab strip scrolls horizontally on small screens — without an edge fade
   the off-screen tabs have no affordance at all */
@media (max-width: 760px) {
  .agent-tabs {
    -webkit-mask-image: linear-gradient(to right, #000 0, #000 86%, transparent 100%);
    mask-image: linear-gradient(to right, #000 0, #000 86%, transparent 100%);
  }
  /* fade only while there is more to scroll to — no listener needed */
  @supports (animation-timeline: scroll()) {
    .agent-tabs { animation: tabfade linear both; animation-timeline: scroll(self inline); }
    @keyframes tabfade {
      to { mask-image: linear-gradient(to right, #000 0, #000 100%); -webkit-mask-image: linear-gradient(to right, #000 0, #000 100%); }
    }
  }
}

/* 44px minimums on the last few small targets */
.rm-close { min-width: 44px; }
.rm-stars .star { min-width: 44px; }
.pfeat-toggle { min-height: 44px; }
.foot-social a { min-width: 44px; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }

/* hero entrance — the markup has carried these hooks all along with no rule
   behind them, so the fold never moved. Transform-only on the H1 so the fade
   can't push LCP to the end of the animation. */
.js .h-enter { opacity: 0; transform: translateY(14px); animation: hEnter 620ms var(--ease-out) forwards; }
.js .h-enter.e1 { animation-delay: 0ms; }
.js .h-enter.e2 { opacity: 1; transform: translateY(18px); animation-name: hEnterMove; animation-delay: 60ms; }
.js .h-enter.e3 { animation-delay: 150ms; }
.js .h-enter.e4 { animation-delay: 230ms; }
.js .h-enter.e5 { animation-delay: 310ms; }
@keyframes hEnter { to { opacity: 1; transform: none; } }
@keyframes hEnterMove { to { transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .js .h-enter { opacity: 1; transform: none; animation: none; }
}

.cap.flip .cap-shot { justify-self: start; }

@media (max-width: 760px) { .foot-meta a { padding: 10px 6px; } }

/* not every section opens the same way: two run their header left, hung on the
   same column the content below uses */
.caps-sec .sec-head, .plat-sec .sec-head { text-align: left; margin-left: 0; }
.caps-sec .sec-head h2 { font-size: var(--step-5); }
.caps-sec .sec-head p, .plat-sec .sec-head p { margin-left: 0; }

/* alignment + measure corrections */
.cap-copy p + p { margin-top: 14px; }
.builder-copy p { max-width: 62ch; }
.love .role { min-height: 3em; }
.stat-row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px 32px; margin-bottom: 28px; }
/* step-3 tabular numerals measure ~155px against a 145px track inside the
   483px receipts column: "137,903" and "11x" collided into one figure. */
.receipts-sec .stat b { font-size: var(--step-2); }

/* the H1 must be the largest type on the page — it was capped below the
   section H2s, so section three was out-shouting the headline */
.hero h1 { font-size: clamp(2.9rem, 2rem + 4.4vw, 5.2rem); }
.caps-sec .sec-head h2 { font-size: var(--step-4); max-width: 16ch; }
.receipts-copy h2 { font-size: var(--step-4); }

/* Removed: a `.cap:nth-of-type(3)` block meant to widen the critique scene.
   It was written when the critique WAS the third article; inserting the
   machine pushed it to fourth, so for a while the rule had been silently
   widening the wrong section, and cap 03 is now a full-width block with no
   .scene-body at all — every declaration in it was dead. The critique scene
   was measured and pinned at its current width, so this is a deletion, not a
   move: re-widening it would invalidate those pins. Positional selectors do
   not survive reordering; anything section-specific belongs on a class. */

/* a second act before the peak: the capabilities section gets its own ground
   so the run to the walnut band isn't one unbroken cream field */
.caps-sec { background: var(--sheet); border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline); }

/* signature motion — the chalk mark draws itself once on load. It is the only
   device that states the brand, so it is the only thing that animates. */
.js .chalk svg path {
  stroke-dasharray: 420; stroke-dashoffset: 420;
  animation: chalkDraw 900ms var(--ease-out) 620ms forwards;
}
@keyframes chalkDraw { to { stroke-dashoffset: 0; } }
@media (prefers-reduced-motion: reduce) {
  .js .chalk svg path { stroke-dashoffset: 0; animation: none; }
}

/* reveal children, not slabs, in the grid sections */
.js .agent-tabs.in .agent-tab,
.js .wall-grid.in .love,
.js .plat-grid.in .plat-card { animation: slabIn 520ms var(--ease-out) backwards; }
.js .agent-tabs.in .agent-tab:nth-child(2),
.js .wall-grid.in .love:nth-child(2),
.js .plat-grid.in .plat-card:nth-child(2) { animation-delay: 70ms; }
.js .agent-tabs.in .agent-tab:nth-child(3),
.js .wall-grid.in .love:nth-child(3),
.js .plat-grid.in .plat-card:nth-child(3) { animation-delay: 140ms; }
.js .agent-tabs.in .agent-tab:nth-child(4),
.js .wall-grid.in .love:nth-child(4),
.js .plat-grid.in .plat-card:nth-child(4) { animation-delay: 210ms; }
.js .agent-tabs.in .agent-tab:nth-child(n+5) { animation-delay: 280ms; }
@keyframes slabIn { from { opacity: 0; transform: translateY(14px); } }
@media (prefers-reduced-motion: reduce) {
  .js .agent-tabs.in .agent-tab, .js .wall-grid.in .love, .js .plat-grid.in .plat-card { animation: none; }
}

/* A3 — reserve the varying note/plus rows so all four feature lists start level */
.pcard .pnote { min-height: 2.9em; }
.pcard .plus { min-height: 3.2em; }

/* A4 — the spanned cells ran ~78ch against ~42ch in the narrow ones, and the
   numeral floated 455px from its own heading. Cap the measure, move the
   numeral inline so its relationship to the heading is constant. */
.gap-cell p { max-width: 46ch; }
.gap-cell .n { position: static; display: block; margin-bottom: 6px; right: auto; top: auto; font-size: var(--step-2); }

/* A5 — "here's the setup / here's the result" left a 114px hole under the mock */
.connect-grid { align-items: stretch; }
.connect-mock { display: flex; flex-direction: column; }
.mock-body { flex: 1; }

/* A7b — the toggle was centred as a group with the savings pill, pushing it
   73px left of the page centre */
.billing-toggle { position: relative; }
@media (min-width: 761px) {
  .billing-toggle .save-pill { position: absolute; left: calc(50% + 116px); top: 50%; transform: translateY(-50%); }
}

/* B3 — promote the chalk stroke from a one-off flourish to a recurring device */
.js .chalk-mark { position: relative; white-space: nowrap; }
.js .chalk-mark svg { position: absolute; left: -1%; bottom: -0.16em; width: 102%; height: 0.3em; overflow: visible; }
.js .chalk-mark svg path {
  fill: none; stroke: var(--sanguine); stroke-width: 3.2; stroke-linecap: round; opacity: .75;
  stroke-dasharray: 420; stroke-dashoffset: 420;
}
.js .reveal.in .chalk-mark svg path { animation: chalkDraw 900ms var(--ease-out) 180ms forwards; }
.night .chalk-mark svg path { stroke: var(--gold-glint); }
/* the footer sits on walnut too, so clay would draw near-invisible there */
.foot-close .chalk-mark svg path { stroke: var(--gold-glint); }
@media (prefers-reduced-motion: reduce) { .js .chalk-mark svg path { stroke-dashoffset: 0; animation: none; } }

/* B2 — the page's thesis line says it argues with itself; make the scene do it */
.js .critique .crit-line { opacity: 0; }
.js .cap.in.playing .critique .crit-line { animation: critIn 420ms var(--ease-out) forwards; }
.js .cap.in .critique .crit-line:nth-of-type(1) { animation-delay: 640ms; }
.js .cap.in .critique .crit-line:nth-of-type(2) { animation-delay: 780ms; }
.js .cap.in .critique .crit-line:nth-of-type(3) { animation-delay: 920ms; }
@keyframes critIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.crit-line .struck { text-decoration: line-through; text-decoration-color: var(--sanguine); text-decoration-thickness: 1.5px; }
@media (prefers-reduced-motion: reduce) { .js .critique .crit-line { opacity: 1; animation: none; } }

/* B1 — the only falsifiable object on the page was a 0.92rem pill */
.proof-chip { font-size: var(--step-1); padding: 14px 24px 14px 16px; }
.proof-chip img { width: 26px; height: 26px; }

/* A4 — equalising the panels moved the hole inside; a composer row closes it
   and completes the chat metaphor rather than padding it */
.mock-composer {
  margin-top: auto; display: flex; align-items: center; gap: var(--sp-3);
  border-top: 1px solid var(--hairline); padding: 13px 2px 2px;
  font-size: var(--step--1); color: var(--ink-mute);
}
.mock-composer .send {
  margin-left: auto; width: 28px; height: 28px; border-radius: 50%;
  background: var(--clay-wash); display: grid; place-items: center; flex: none;
}
.mock-composer .send svg { width: 13px; height: 13px; }

/* A5 — land the crop on empty chat ground so the fade reads as an exhibit edge,
   and mount the captures the way the analytics screenshot is mounted */
.cap-shot img { filter: saturate(0.62); object-position: top center; }
.cap-shot picture::before {
  content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 1;
  background: var(--clay-wash); mix-blend-mode: multiply; opacity: .14;
}
/* .cap-shot figcaption removed - no markup carries .cap-shot any more */

/* B1 — the strategy and ideation scenes were dead beside an animated third */
.js .cap-scene .u, .js .cap-scene .toolchip, .js .cap-scene .a,
.js .cap-scene .choices, .js .cap-scene .idealist, .js .cap-scene .postmini { opacity: 0; }
.js .cap.in.playing .cap-scene .u,
.js .cap.in .cap-scene .toolchip,
.js .cap.in .cap-scene .a,
.js .cap.in .cap-scene .choices,
.js .cap.in .cap-scene .idealist,
.js .cap.in .cap-scene .postmini { animation: critIn 420ms var(--ease-out) forwards; }
/* the messages live in .scene-body, not .cap-scene — .cap-scene has exactly two
   children (.scene-bar, .scene-body), so these delays were inert and every
   message in all three scenes faded in together at 0ms */
.js .cap.in .scene-body > *:nth-child(1) { animation-delay: 60ms; }
.js .cap.in .scene-body > *:nth-child(2) { animation-delay: 200ms; }
.js .cap.in .scene-body > *:nth-child(3) { animation-delay: 340ms; }
.js .cap.in .scene-body > *:nth-child(4) { animation-delay: 480ms; }
.js .cap.in .scene-body > *:nth-child(5) { animation-delay: 620ms; }
@media (prefers-reduced-motion: reduce) {
  .js .cap-scene .u, .js .cap-scene .toolchip, .js .cap-scene .a,
  .js .cap-scene .choices, .js .cap-scene .idealist, .js .cap-scene .postmini { opacity: 1; animation: none; }
}

/* B3 — a second peak: the only falsifiable claim on the page gets the walnut
   ground rather than reading quieter than the pricing table */
.receipts-sec { background: var(--walnut); color: var(--mute-on-dark); }
.receipts-sec .receipts-copy h2,
.receipts-sec .stat b { color: var(--cream-on-dark); }
.receipts-sec .receipts-copy p { color: var(--mute-on-dark); }
.receipts-sec .eyebrow { color: var(--mute-on-dark); }
.receipts-sec .stat span { color: var(--mute-on-dark); }
.receipts-sec .proof-shot figcaption { color: var(--mute-on-dark); }
.receipts-sec .chalk-mark svg path { stroke: var(--gold-glint); }

/* ============================================================
   ROUND 8 — object families, the revision gesture, one column break
   ============================================================ */

/* B2 — every reveal on the page was opacity+translateY. The critique pass now
   performs the edit instead of narrating it: the unsupported phrase is struck
   with a drawn rule, then rolls out of a clipped box as the sourced line rolls
   in. Clipped, paired and horizontal — nothing else on the page moves this way. */
.swap { display: inline-grid; vertical-align: bottom; overflow: hidden; }
.swap > * { grid-area: 1 / 1; }
.swap .was { position: relative; }
/* at rest this is just a revised sentence — a bolded phrase mid-line would
   read as arbitrary emphasis to anyone arriving after the animation */
.swap .now { color: var(--ink); }
/* without JS the scene is a still, so it shows the finished sentence */
.swap .was { display: none; }
.js .swap .was { display: block; }
.js .swap .now { opacity: 0; transform: translateY(100%); }
/* R31 #5: the rule spanned the grid cell, which is pinned to the WIDER
   phrase — striking 180px of empty air past the period when the incoming
   line is longer. Draw it the width of the words: home.js sets --was-w. */
.js .swap .was::after {
  width: var(--was-w, 100%); right: auto;
  content: ''; position: absolute; left: 0; right: 0; top: calc(50% - 0.75px); height: 1.5px;
  background: var(--sanguine); transform: scaleX(0); transform-origin: left center;
}
.js .cap.in.playing .swap .was::after { animation: strikeDraw 460ms var(--ease-out) 1180ms forwards; }
.js .cap.in.playing .swap .was { animation: swapOut 560ms var(--ease-out) 1760ms forwards; }
.js .cap.in.playing .swap .now { animation: swapIn 560ms var(--ease-out) 1760ms forwards; }
@keyframes strikeDraw { to { transform: scaleX(1); } }
@keyframes swapOut { to { opacity: 0; transform: translateY(-100%); } }
@keyframes swapIn { to { opacity: 1; transform: none; } }
/* the same drawn rule in the critique list, so the device reads as one idea */
.crit-line .struck { position: relative; text-decoration: none; }
.js .crit-line .struck::after {
  content: ''; position: absolute; left: 0; right: 0; top: calc(50% - 0.75px); height: 1.5px;
  background: var(--sanguine); transform: scaleX(0); transform-origin: left center;
}
.js .cap.in.playing .crit-line .struck::after { animation: strikeDraw 340ms var(--ease-out) 960ms forwards; }
@media (prefers-reduced-motion: reduce) {
  .js .swap .was { display: none; }
  .js .swap .now { opacity: 1; transform: none; }
  .js .crit-line .struck { text-decoration: line-through; text-decoration-color: var(--sanguine); text-decoration-thickness: 1.5px; }
  .js .crit-line .struck::after { display: none; }
}

/* A4 — eleven object types shared one recipe (22px radius + hairline + lift).
   Three families now read differently by construction, not by content. */

/* chat scenes: illustrative transcripts, not product chrome. The one literal
   product mock on the page (.connect-mock) keeps the window treatment. */
.cap-scene { background: var(--sheet-2); border: 0; box-shadow: none; border-radius: 14px; }
.cap-scene .scene-bar { background: transparent; border-bottom: 1px solid var(--hairline); }

/* testimonials: quote slips on the open page, not boxes */
.love { background: transparent; border: 0; border-left: 2px solid var(--clay); border-radius: 0; padding: 4px 0 4px 20px; }
/* the one card in the row is the CTA — the contrast is the point */
.love-add {
  background: var(--walnut); border: 1px solid var(--walnut); border-radius: var(--radius-lg);
  padding: 26px 26px 24px;
}

/* platform index: rules, not cards */
.plat-card {
  background: transparent; border: 0; border-top: 1px solid var(--hairline-strong); border-radius: 0;
  padding: 22px 0 24px;
}
.plat-card:hover { transform: none; box-shadow: none; border-top-color: var(--clay); }
.plat-card.soon { background: transparent; border-style: none; border-top: 1px dashed var(--hairline-strong); }

/* A5 — 14,322px of page and nothing ever left the 1120px wrap. The strongest
   composition breaks it: shifted, not widened, so the analytics stay uncropped. */
@media (min-width: 1400px) {
  .receipts-sec .proof-shot { margin-left: -120px; margin-right: 120px; }
}
/* inner panels stay raised now that the scene ground is recessed */
.cap-scene .critique, .cap-scene .choice { background: var(--sheet); }
/* three narrow tracks make long labels ragged; balance them and top-align */
.stat-row { align-items: start; }
.stat span { text-wrap: balance; }

/* three stats in a 350px column gives 95px tracks — the labels shredded into
   four lines each. Stack them as an aligned figure/label list instead. */
@media (max-width: 620px) {
  .stat-row { grid-template-columns: 1fr; gap: var(--sp-4); }
  .stat { display: flex; align-items: baseline; gap: var(--sp-4); }
  .stat b { flex: none; min-width: 4.6em; }
  .stat span { text-wrap: initial; }
}

/* ============================================================
   ROUND 9 — swap sizing, motion vocabulary, hero entrance, duotone
   ============================================================ */

/* A1 — .hero-cta is centred for the hero; the CTA band reuses it flush-left,
   which pushed the buttons 150px off the section's axis */
.sec.cta-band .hero-cta { justify-content: flex-start; }

/* A2 — the numerals were measured last round, the LABELS were not: a 140px
   track wraps "impressions in 28 days" to two lines and the row loses its
   baseline. Reserve two lines for all three so the row stays level. */
/* 3.2em == two line-heights at this size; 2.5em fell short and cell 1 ran 10px tall */
.receipts-sec .stat span { display: block; min-height: 3.2em; }

/* A3 — saturate(0.62) still left Telegram green reading as Telegram green
   against warm paper. Map the captures onto the palette properly. */
.cap-shot img { filter: grayscale(1) sepia(0.30) saturate(1.5) hue-rotate(-12deg) contrast(1.18) brightness(1.04); }

/* A4 — hang the opening quote so the headline starts on the section axis */
.hang { display: inline-block; margin-left: -0.42em; }

/* A6 — two labelled link groups ran together mid-wrap ("Supergrow FOR ROBOTS") */
.foot-meta .mgroup { margin: 0; }
.foot-meta .mgroup + .mgroup { margin-top: 6px; }

/* B1 — both phrases share one grid cell, so nothing may wrap while the box is
   pinned to the outgoing width (see the width handoff in home.js) */
/* min-width:auto would hold the track at the WIDER child's min-content, so the
   pinned width would be ignored and the strike would span the incoming phrase */
.swap > * { white-space: nowrap; min-width: 0; }

/* B2 — one entrance verb, 35 times. Capability rows now wipe along the
   reading direction, and the quote slips hinge off their clay rule. */
/* insets run negative on the idle edges so the wipe never crops a box-shadow */
.js .cap.reveal { opacity: 0; transform: none; clip-path: inset(-100px 38% -100px -100px); }
.js .cap.reveal.in {
  opacity: 1; clip-path: inset(-100px);
  transition: opacity var(--t-enter) var(--ease-out), clip-path 780ms var(--ease-out);
}
.js .cap.flip.reveal, .js .cap-daily.reveal { clip-path: inset(-100px -100px -100px 38%); }
/* .js .cap.flip.reveal ties .js .cap.reveal.in on specificity and comes later,
   so without this the flipped row would stay clipped forever */
.js .cap.flip.reveal.in, .js .cap-daily.reveal.in { clip-path: inset(-100px); }
@keyframes slipIn { from { opacity: 0; transform: translateY(9px) rotate(-1.1deg); } to { opacity: 1; transform: none; } }
.js .wall-grid.in .love { animation: slipIn 560ms var(--ease-out) backwards; transform-origin: 0% 50%; }

/* B3 — the hero was a single fade. Deal the platform tiles in, then draw the
   stroke only once the H1 has settled (e2 finishes at ~680ms). */
@keyframes dealIn { from { opacity: 0; transform: translateY(15px) rotate(-8deg) scale(0.9); } to { opacity: 1; transform: none; } }
.js .hero-platforms .plat { animation: dealIn 560ms var(--ease-out) backwards; transform-origin: 50% 120%; }
.js .hero-platforms .plat:nth-child(1) { animation-delay: 140ms; }
.js .hero-platforms .plat:nth-child(2) { animation-delay: 225ms; }
.js .hero-platforms .plat:nth-child(3) { animation-delay: 310ms; }
.js .chalk svg path { animation-delay: 800ms; }

/* B5 — the briefing asks, then the answers tick on one at a time */
.js .choices .choice.on { color: var(--ink-mute); border-color: var(--hairline); background: var(--sheet); }
.js .choices .choice.on .box { background: transparent; border-color: var(--hairline-strong); color: transparent; }
@keyframes chipOn { to { color: var(--ink); border-color: rgba(178,94,73,.4); background: var(--clay-wash); } }
@keyframes boxOn { to { background: var(--clay); border-color: var(--clay); color: #2b1509; } }
.js .cap.in.playing .choices .choice.on { animation: chipOn 340ms var(--ease-out) forwards; }
.js .cap.in.playing .choices .choice.on .box { animation: boxOn 280ms var(--ease-out) forwards; }
.js .cap.in .choices .choice:nth-child(1).on,
.js .cap.in .choices .choice:nth-child(1).on .box { animation-delay: 760ms; }
.js .cap.in .choices .choice:nth-child(2).on,
.js .cap.in .choices .choice:nth-child(2).on .box { animation-delay: 980ms; }
.js .cap.in .choices .choice:nth-child(4).on,
.js .cap.in .choices .choice:nth-child(4).on .box { animation-delay: 1200ms; }

/* B4 — the one element that breaks the grid should earn it on scroll */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .receipts-sec .proof-shot {
      animation: proofRise linear both;
      animation-timeline: view();
      animation-range: entry 8% cover 40%;
    }
    @keyframes proofRise {
      from { transform: translateY(52px) scale(0.975); }
      to   { transform: translateY(0) scale(1); }
    }
  }
}

@media (prefers-reduced-motion: reduce) {
  .js .cap.reveal, .js .cap.flip.reveal { opacity: 1; clip-path: none; }
  .js .hero-platforms .plat, .js .wall-grid.in .love { animation: none; }
  .js .choices .choice.on { color: var(--ink); border-color: rgba(178,94,73,.4); background: var(--clay-wash); }
  .js .choices .choice.on .box { background: var(--clay); border-color: var(--clay); color: #2b1509; }
  .js .cap.in.playing .choices .choice.on, .js .cap.in .choices .choice.on .box { animation: none; }
}

/* ============================================================
   ROUND 10 — scene choreography, section tempo, one broken band
   ============================================================ */

/* the writing scene's closing line claims the draft was revised, so it must
   arrive AFTER the swap (1520ms) rather than on the base stagger */
.js .cap-writing.in .scene-body > .a:nth-last-child(2) { animation-delay: 2420ms; }

/* the tool calls resolve instead of shipping pre-ticked */
.js .toolchip .ok { opacity: 0; transform: scale(0.6); display: inline-block; }
@keyframes okLand { to { opacity: 1; transform: none; } }
.js .cap.in.playing .toolchip .ok { animation: okLand 300ms var(--ease-out) 520ms forwards; }

/* the idea list builds a line at a time under its own message */
.js .idealist li { opacity: 0; }
@keyframes ideaIn { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: none; } }
.js .cap.in.playing .idealist li { animation: ideaIn 380ms var(--ease-out) forwards; }
.js .cap.in .idealist li:nth-child(1) { animation-delay: 640ms; }
.js .cap.in .idealist li:nth-child(2) { animation-delay: 780ms; }
.js .cap.in .idealist li:nth-child(3) { animation-delay: 920ms; }
.js .cap.in .idealist li:nth-child(4) { animation-delay: 1060ms; }

/* a live caret, so the three chat panels read as running rather than captured */
.mock-composer::after {
  content: ''; width: 1.5px; height: 1.05em; background: var(--clay);
  display: inline-block; vertical-align: text-bottom; margin-left: -6px;
}
.js .mock-composer::after { animation: caret 1.2s steps(1, end) infinite; }
@keyframes caret { 0%, 55% { opacity: 1; } 56%, 100% { opacity: 0; } }

/* SECTION TEMPO — one heading size and one padding governed eight bands.
   Peaks are the argument moments; the index bands compress. */
.night .sec-head h2, .sec.cta-band h2 { font-size: var(--step-5); }
.night, .sec.cta-band { padding: clamp(104px, 15vh, 172px) 0; }
.wall-sec .sec-head h2, .plat-sec .sec-head h2 { font-size: var(--step-3); }
.wall-sec, .plat-sec { padding: clamp(56px, 8vh, 88px) 0; }
/* caps was already at step-5 and competed with the peaks */
.caps-sec .sec-head h2 { font-size: var(--step-4); }

/* ONE BROKEN BAND — nine consecutive [text | card] units. The writing scene
   is the longest and the critique panel reads better wide, so it runs the
   full wrap with its copy set once above. */
@media (min-width: 900px) {
  .cap-writing { display: block; padding-bottom: clamp(48px, 7vh, 88px); }
  .cap-writing .cap-copy { max-width: 62ch; margin-bottom: clamp(26px, 3.4vh, 40px); }
  .cap-writing .cap-copy p { max-width: 58ch; }
  .cap-writing .cap-scene { width: 100%; }
  /* the section is "it argues with itself", so set the draft against its own
     critique instead of stacking them down a 1120px column */
  .cap-writing .scene-body {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 16px 28px; align-items: start;
    padding: clamp(22px, 2.6vw, 34px) clamp(24px, 3vw, 40px);
  }
  .cap-writing .scene-body > .u { grid-column: 1 / -1; justify-self: end; }
  .cap-writing .scene-body > .toolchip { grid-column: 1 / -1; }
  .cap-writing .postmini { grid-column: 1; margin-top: 0; }
  .cap-writing .critique { grid-column: 2; }
  .cap-writing .scene-body > .a:nth-last-child(2) { grid-column: 1 / -1; }
}

/* the shared-element indicator behind the agent tabs (built in home.js) */
.agent-tabs { position: relative; }
.tab-ind {
  position: absolute; left: 0; top: 0; border-radius: 999px; z-index: 0;
  background: var(--sheet); border: 1px solid var(--ink-mute);
  box-shadow: 0 1px 2px rgba(34,26,16,.06), 0 8px 20px -12px rgba(34,26,16,.3);
  transition: transform 420ms var(--ease-out), width 420ms var(--ease-out), height 420ms var(--ease-out);
  pointer-events: none;
}
.agent-tab { position: relative; z-index: 1; }
/* the pill now draws the selected state, so the tab itself steps aside —
   guarded on .js because without it there would be no indicator at all */
.js .agent-tab[aria-selected="true"] { background: transparent; border-color: transparent; box-shadow: none; }

/* ============================================================
   ROUND 11 — owner feedback: CTA colour, plain eyebrows, agent cycle
   ============================================================ */

/* an ink button vanishes on the walnut cards, so invert it there */
.love-add .btn-primary,
.pcard.featured .btn-primary {
  background: var(--cta-ink); color: var(--cta);
  box-shadow: 0 1px 2px rgba(0,0,0,.35), 0 12px 28px -14px rgba(0,0,0,.7);
}
.love-add .btn-primary:hover,
.pcard.featured .btn-primary:hover { background: #fff; color: var(--cta); }

/* the eyebrows are sans now, so the leading tick needs its own weight back */
.eyebrow .tick { color: var(--sanguine); font-weight: 500; }

/* the hero H1's cycling agent mark, replacing the clay chalk stroke */
.agent-cycle {
  display: inline-block; position: relative;
  width: 0.92em; height: 0.92em; vertical-align: -0.06em; margin-left: 0.16em;
}
.agent-cycle img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  border-radius: 0.2em; opacity: 0;
}
.agent-cycle img:first-child { opacity: 1; }   /* no-JS resting state */
/* seven agents now, not four: one slot is 100/7 = 14.286% of the loop, and each
   logo fades in over the first ~1/6 of its slot and out over the last ~1/6. */
@media (prefers-reduced-motion: reduce) {
  .js .agent-cycle img { animation: none; opacity: 0; }
  .js .agent-cycle img:first-child { opacity: 1; }
}

/* the video moved inside the hero column now that the proof row is gone */
.hero-video { margin: clamp(30px, 5vh, 54px) auto 0; max-width: 620px; }

/* the ✓ glyph sits below its own optical centre in the box, so the tick is an
   SVG now — geometrically centred and it inherits the box's animated colour */
.choice .box svg {
  width: 9px; height: 9px; display: block;
  fill: none; stroke: currentColor; stroke-width: 2.2;
  stroke-linecap: round; stroke-linejoin: round;
}

/* multi-line snippets (Codex TOML) */
.copyrow--block { align-items: flex-start; }
.copyrow--block code { white-space: pre; line-height: var(--lh-body); }
.copyrow--block .copybtn { margin-top: 2px; }

/* consent is the first question high-context users ask about ideation */
.cap-consent {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  font-size: var(--step--1); line-height: var(--lh-mid); color: var(--ink-mute);
  background: var(--sheet); border: 1px solid var(--hairline);
  border-radius: 12px; padding: 12px 14px; margin-top: 16px; max-width: 46ch;
}
.cap-consent .lock { flex: none; font-size: var(--step--2); line-height: var(--lh-body); filter: grayscale(1); opacity: .75; }

/* TYPE RULE: mono means machine text — tool names, commands, fig. numbers.
   Every other label is plain sans in sentence case. Uppercase + wide-tracked
   mono on ordinary labels is the "futuristic / AI-generated" tell. */
.hero-video .vnote,
.pcard .badge,
.pcard .plus,
.scene-bar .who,
.crit-h,
.agent-tab .soon,
.connect-card .fastest,
.mock-bar .who,
.foot-col .foot-h,
.foot-meta .lbl,
.love-add .add-eyebrow,
.rm-label,
.bphoto .bp-note,
.js .pcard.collapsible .pfeat-toggle,
.copybtn,
.save-pill,
.pmore {
  font-family: var(--sans);
  letter-spacing: 0;
  text-transform: none;
}
/* they were sized for uppercase mono; sentence-case sans needs a touch more */
.hero-video .vnote, .crit-h, .agent-tab .soon, .connect-card .fastest,
.foot-meta .lbl, .love-add .add-eyebrow, .rm-label, .bphoto .bp-note,
.pcard .plus, .pcard .badge { font-size: var(--step--2); }
.scene-bar .who, .mock-bar .who, .foot-col .foot-h { font-size: var(--step--2); }
.copybtn { font-size: var(--step--2); }
/* the analytics caption shouted in uppercase; match the fig. NN captions */
.proof-shot figcaption { text-transform: none; letter-spacing: 0.02em; }

/* the play affordance sat in clay directly under an ink CTA — it read as a
   second, competing button colour in the hero */
.hero-video .vframe .vplay { background: rgba(31,26,19,.07); border-color: rgba(31,26,19,.18); }
.hero-video .vframe .vplay::after { border-left-color: var(--cta); }

/* ============================================================
   ROUND 12 — caret slot, cycle cross-fade, the last mono orphans
   ============================================================ */

/* .mock-composer is a flex row and .send has margin-left:auto, so the ::after
   caret landed in the LAST flex slot — blinking to the right of the send arrow
   and pushing it 8px off the panel edge. inline-block/vertical-align were inert
   on a flex item. Order puts it back beside the placeholder text. */
.mock-composer::after { order: 0; display: block; align-self: center; }
.mock-composer .send { order: 1; }

/* the cross-fade had a hole: the outgoing mark hit 0 at 25% but the incoming
   did not reach 1 until 29%, so the H1 lost its focal mark four times a loop */
/* bare marks on the page ground, post-bridge style — no plate, no border.
   codex.svg's baked-in white 24x24 plate stays deleted so it matches. */
.agent-cycle img { background: none; box-shadow: none; padding: 0; }

/* the sans sweep missed the card-footer label family — .badge-soon in tracked
   gold mono directly contradicted .agent-tab .soon, its own counterpart */
.plat-card.soon .badge-soon,
.plat-card .go,
.love .role,
.builder-photo figcaption {
  font-family: var(--sans);
  letter-spacing: 0;
  text-transform: none;
  font-size: var(--step--2);
}
.plat-card .go { font-size: var(--step--1); }
.love .role { font-size: var(--step--2); }

/* the padlock is an inline SVG now, not a platform-rendered emoji */
.cap-consent .lock {
  width: 15px; height: 15px; flex: none; margin-top: 2px;
  fill: none; stroke: var(--ink-mute); stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
  filter: none; opacity: 1;
}

/* "Features" as a 64px display H2 was the one template-sounding moment on an
   editorial page. Same word, demoted to the label voice used everywhere else,
   so the band opens on "01 · Strategy" and its real headline. */
/* Oded 08-28: the section opener was a whisper (step--1, muted) heading six
   full scenes. It now carries real weight, with the chalk mark the other
   section headers already earn. */
.caps-sec .sec-head.caps-label { margin: 0 0 clamp(26px, 4vh, 44px); text-align: left; }
.caps-sec .sec-head.caps-label h2 {
  font-family: var(--sans); font-size: var(--step-5); font-weight: 400;
  letter-spacing: -0.022em; color: var(--ink); margin: 0; line-height: 1.05;
}

/* B3 — the strike-and-replace is the only motion that dramatises the actual
   claim ("you see the version that survived"), but it played small and fast
   inside the panel's entrance. In the full-wrap layout the draft is the hero
   object of the band, so set it larger and give the swap its own beat. */
@media (min-width: 900px) {
  .cap-writing .postmini { font-size: var(--step-0); }
  .cap-writing .postmini .body { line-height: var(--lh-body); }
  .cap-writing .swap .was::after { height: 2px; top: calc(50% - 1px); }
}

/* ============================================================
   ROUND 13 — pricing joins the container system, one scale change
   ============================================================ */

/* A2 — the same label reads 5.94:1 in three tiers and 2.70:1 in the featured
   one, because there was no dark-ground override next to its siblings.
   Measured; Lighthouse did not catch it. */
.pcard.featured .plus { color: var(--mute-on-dark); }

/* A1/B1 — pricing was a fourth, generic container family: four bordered
   radius cards that could sit on any SaaS site. The rules-only idiom is
   already the page's own (.plat-card); pricing now uses it, and "chosen" is
   carried by material — a walnut slab breaking the rule line — not a pill. */
/* stretch, not start: the column rules ARE the card borders, so uneven card
   heights leave the rules with ragged bottoms */
.price-grid { gap: 0; align-items: stretch; }
.pcard {
  background: transparent; border: 0; border-radius: 0;
  border-top: 1px solid var(--hairline-strong);
  padding: 26px 22px 26px 0;
}
.pcard + .pcard { border-left: 1px solid var(--hairline); padding-left: 22px; }
.pcard:hover { transform: none; box-shadow: none; }
.pcard.featured {
  background: var(--walnut); border-radius: 14px; border-top-color: transparent;
  margin: -22px 0; padding: 48px 24px 34px; border-left: 0;
}
.pcard.featured + .pcard { border-left: 0; }
/* the badge was an overlapping tab; it is a plain line above the tier now */
.pcard .badge {
  position: static; display: block; background: none; border-radius: 0;
  padding: 0 0 7px; color: var(--clay-link); font-weight: 500;
}
.pcard.featured .badge { color: var(--gold-glint); }
.pcard.featured .btn-primary { box-shadow: 0 1px 2px rgba(0,0,0,.3), 0 10px 26px -12px rgba(0,0,0,.7); }
@media (max-width: 900px) {
  .price-grid { gap: 0; }
  .pcard + .pcard { border-left: 0; padding-left: 0; }
  .pcard.featured { margin: 8px 0; padding: 30px 24px; }
}

/* B2 — --maxw:1200 was the ONLY layout container across a 14,000px scroll, so
   every band resolved to the same column and the middle third read as one
   tempo. The strongest artifact now breaks to full width on its own ground. */
@media (min-width: 900px) {
  .cap-writing {
    background: var(--sheet-2);
    border-top: 1px solid var(--hairline);
    border-bottom: 1px solid var(--hairline);
    margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw);
    padding-left: calc(50vw - 50%); padding-right: calc(50vw - 50%);
    padding-top: clamp(52px, 7vh, 88px);
  }
  .cap-writing .cap-scene { background: var(--sheet); }
  .cap-writing .cap-scene .critique, .cap-writing .cap-scene .choice { background: var(--sheet-2); }
}

/* ============================================================
   ROUND 14 — hang the mark, align the table, make the scale real
   ============================================================ */

/* 1 — the trailing mark sat INSIDE the centred line box, so the words paid for
   half its width and line 2 rendered 45px left of the page axis. Hang it. */
.agent-cycle { margin-right: -1.08em; }

/* 2 — the four marks had incompatible optical weight: an airy clay asterisk in
   the same slot as a solid gradient tile. Full colour stays (owner's call), so
   equalise optical AREA instead — inset the dense marks, hold the airy ones.
   The blanket radius existed only to tame the solid tile; it goes with it. */
.agent-cycle img { border-radius: 0; box-sizing: border-box; }
.agent-cycle img[src*="codex"] { padding: 0.10em; }
.agent-cycle img[src*="claude-code"] { padding: 0.06em; }

/* 3 — the price table did not align under its own rule. The featured slab
   carries an in-flow badge the siblings lack, and its bottom padding left the
   CTAs 14px out. Reserve the badge slot; re-seat the CTA row. */
/* derived from the badge's own box (0.8rem x 1.6 line-height + 7px pad), not
   guessed in em — an em here resolves against .pcard and overshot by 3.6px */
.pcard:not(.featured)::before { content: ''; display: block; height: calc(1.28rem + 7px); }
.pcard.featured { padding-bottom: 48px; }
/* the page's own rules-only idiom (.plat-card) uses a top rule and no column
   rules; with a slab in the middle only ONE tier adjacency was left to rule,
   which read as arbitrary. Match the idiom exactly.
   Scoped to desktop: unscoped, this sat AFTER the max-width:900 reset and
   silently re-applied a 22px indent to every card but Free, so $0 hung 22px
   left of $29/$79/Custom on the one section built to be metronomic. */
@media (min-width: 901px) {
  /* the 22px only ever cleared the column rules, which are gone — it left three
     of four columns 22px off their own cell edges */
  .pcard + .pcard { border-left: 0; padding-left: 0; }
  .pcard.featured { padding-left: 24px; }
}

/* 5 — (retired 2026-09-10) the free card's button used to be ink-filled, "the
   free tier is the primary path". Owner's call: Business is the path we want
   chosen, so Free and Pro share the light button and Business alone is filled. */

/* 4 — the "scale change" was only a tint, and it landed on the SAME ground as
   .togo-sec directly below it (2,600px of one colour with a 100px sliver).
   Give the band its own ground AND actually widen the artifact. */
@media (min-width: 900px) {
  .cap-writing { background: var(--canvas); }
  .cap-writing .cap-scene .critique, .cap-writing .cap-scene .choice { background: var(--canvas); }
  .cap-writing .cap-scene {
    width: min(1320px, 92vw);
    margin-left: calc(50% - min(660px, 46vw));
  }
}

/* .pnote's min-height was 4px under a real two-line box, so the single-line
   Free tier clamped short and pushed its list row out of the shared grid */
.pcard .pnote { min-height: 3.2em; }

/* ============================================================
   ROUND 15 — the user's own post gets fixed, on ChatGPT
   ============================================================ */

/* explicit rows: the critique tool-call now sits between the draft and the
   verdict in the DOM, which would otherwise break the side-by-side pairing */
@media (min-width: 900px) {
  .cap-writing .scene-body > .u        { grid-column: 1 / -1; grid-row: 1; justify-self: end; }
  .cap-writing .scene-body > .toolchip { grid-column: 1 / -1; grid-row: 2; }
  .cap-writing .scene-body > .postmini { grid-column: 1;      grid-row: 3; margin-top: 0; }
  .cap-writing .scene-body > .critique { grid-column: 2;      grid-row: 3; }
  .cap-writing .scene-body > .a:nth-last-child(2) { grid-column: 1 / -1; grid-row: 4; }
}

/* two revisions now, on their own beats: the unverifiable statistic is struck
   then replaced; the throat-clearing hook is simply replaced (nothing to
   strike — it was weak writing, not a false claim) */
/* R30 #15: the panel strikes both phrases; the draft struck only one. The
   exception that lived here made the device mean two different things. Removed. */
.js .cap.in.playing .swap-b .was::after { animation: strikeDraw 460ms var(--ease-out) 1100ms forwards; }
.js .cap.in.playing .swap-b .was { animation: swapOut 560ms var(--ease-out) 5600ms forwards; }
.js .cap.in.playing .swap-b .now { animation: swapIn  560ms var(--ease-out) 5600ms forwards; }
.js .cap.in.playing .swap:not(.swap-b) .was { animation: swapOut 560ms var(--ease-out) 6100ms forwards; }
.js .cap.in.playing .swap:not(.swap-b) .now { animation: swapIn  560ms var(--ease-out) 6100ms forwards; }
.js .cap-writing.in .scene-body > .a:nth-last-child(2) { animation-delay: 2700ms; }

/* A2 (critic, measured) — the inset went on the two marks nearest the median
   and left the real outlier at full size, widening optical spread 1.48x -> 2.42x.
   openai is 39% above the mean; it is the one that needs pulling back. */
.agent-cycle img[src*="codex"] { padding: 0; }
.agent-cycle img[src*="claude-code"] { padding: 0; }
.agent-cycle img[src*="openai"] { padding: 0.07em; box-sizing: border-box; }

/* the price numerals were the last display type still at 500 — the H1, every
   section H2 and every capability headline are 400. Weight, not family, is
   what read as "a different font" here. */
.pprice .amt { font-weight: 400; }

/* A3 (critic) — the row-locking that makes the desktop table metronomic is
   dead space once the cards stack; there is no cross-card row to align to. */
@media (max-width: 900px) {
  .pcard:not(.featured)::before { display: none; }
  .pcard .plus, .pcard .pnote { min-height: 0; }
  .pcard .picp { min-height: 0; }
}

/* receipts: live links to the posts behind the numbers. Swap the hrefs and the
   titles in index.html — three slots, styled for the walnut ground. */
.receipt-posts { margin-top: 26px; }
.receipt-posts .rp-h {
  display: block; font-size: var(--step--1); color: var(--mute-on-dark); margin-bottom: 12px;
}
.rp-list { list-style: none; display: flex; flex-direction: column; gap: 1px;
  background: rgba(242,236,221,0.12); border: 1px solid rgba(242,236,221,0.12);
  border-radius: 12px; overflow: hidden; }
.rp-list a {
  display: flex; align-items: center; gap: var(--sp-3); padding: 13px 15px;
  background: var(--walnut-2); color: var(--cream-on-dark);
  font-size: var(--step--1); line-height: var(--lh-head); text-decoration: none;
  transition: background-color var(--t-micro) var(--ease-soft);
}
.rp-list a:hover { background: #3a2f1e; }
.rp-list img { flex: none; border-radius: 6px; }
.rp-t { flex: 1; min-width: 0; }
.rp-go { flex: none; color: var(--gold-glint); font-size: var(--step--2); white-space: nowrap; }
@media (max-width: 560px) { .rp-go { display: none; } }

/* B1 (critic, measured) — from the receipts band to the footer the page ran
   ~3,900px (28%) on one unchanging ground: pricing, FAQ, the emotional close
   and the channel list, no contrast event. The close already holds the best
   photograph and the strongest line on the page and was doing neither on the
   same cream as the FAQ above it. It becomes the fourth peak, and the walnut
   now runs straight into the walnut footer. */
.sec.cta-band { background: var(--walnut); color: var(--mute-on-dark); }
.sec.cta-band h2 { color: var(--cream-on-dark); }
.sec.cta-band h2 em { color: var(--gold-glint); font-style: normal; }
.sec.cta-band .cta-sub { color: var(--mute-on-dark); }
.sec.cta-band .btn-primary {
  background: var(--cta-ink); color: var(--cta);
  box-shadow: 0 1px 2px rgba(0,0,0,.35), 0 12px 28px -14px rgba(0,0,0,.7);
}
.sec.cta-band .btn-primary:hover { background: #fff; color: var(--cta); }
.sec.cta-band .btn-ghost { color: var(--cream-on-dark); border-color: rgba(242,236,221,0.3); }
.sec.cta-band .btn-ghost:hover { background: rgba(242,236,221,0.08); border-color: rgba(242,236,221,0.5); }
/* the origami was a rounded card inset to the wrap; let it run off the edge */
@media (min-width: 900px) {
  .sec.cta-band .cta-art { border-radius: 16px 0 0 18px; margin-right: calc(50% - 50vw); }
}
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .sec.cta-band .cta-art { animation: proofRise linear both; animation-timeline: view(); animation-range: entry 8% cover 46%; }
  }
}

/* A4 (critic, measured) — -120px landed the figure's left edge at x=40, a
   fourth left edge matching none of the page's others (0 / 60 / 160). -100px
   lands it on 60, rhyming with the full-bleed writing scene above it. */
@media (min-width: 1400px) {
  .receipts-sec .proof-shot { margin-left: -100px; margin-right: 100px; }
}

/* ============================================================
   ROUND 16 — mobile truncation, header over walnut, the close
   ============================================================ */

/* 🔴 the hook swap holds a whole sentence; .swap > * is nowrap (needed for the
   width handoff), so at 390 it forced a 336px unit into a 286px column and
   silently cut "...Nobody wants the cleanup. T". Below 900 the hook simply
   shows its revised state and wraps. The FACT-CHECK swap (.swap-b, 161px)
   still fits and still animates, so the strike — the best moment on the page
   — survives on phones. */
/* 2026-09-11: the owner saw the phone skip the strike and asked for it back.
   The sentence WRAPS below 900 instead of being skipped: the swap stays a
   stacked grid, its two lines go white-space: normal, and because a single
   drawn rule cannot cross two lines of text the strike inks in through
   text-decoration-color instead - same timing as the desktop rule. */
@media (max-width: 900px) {
  .swap:not(.swap-b) { width: auto !important; vertical-align: top; }
  .swap:not(.swap-b) > * { white-space: normal; }
  .js .swap:not(.swap-b) .was::after { display: none; }
  .js .swap:not(.swap-b) .was {
    text-decoration: line-through; text-decoration-color: transparent; text-decoration-thickness: 1.5px;
  }
  .js .cap.in.playing .swap:not(.swap-b) .was {
    animation: strikeInk 460ms var(--ease-out) 1180ms forwards, swapOut 560ms var(--ease-out) 6100ms forwards;
  }
  @keyframes strikeInk { to { text-decoration-color: var(--sanguine); } }
}

/* 🔴 the sticky header is a light slab pinned over every walnut band, and
   making the close walnut increased that exposure. It inverts now. */
.site.on-dark {
  background: color-mix(in srgb, var(--walnut) 93%, transparent);
  border-bottom-color: rgba(253,250,243,0.14);
}
.site.on-dark .brand-word { color: var(--cream-on-dark); }
.site.on-dark .navlink { color: var(--mute-on-dark); }
.site.on-dark .navlink:hover { color: var(--cream-on-dark); }
.site.on-dark .menu-toggle { color: var(--cream-on-dark); }
.site.on-dark .btn-primary { background: var(--cta-ink); color: var(--cta); }
.site.on-dark .btn-primary:hover { background: #fff; color: var(--cta); }

/* 🟠 a 155px cream sliver sat between the walnut close and the walnut footer,
   breaking the ending the page's own vocabulary asks for. It is machine/SEO
   text, so it joins the dark ground rather than interrupting it. */
.agents-note { background: var(--walnut); color: var(--mute-on-dark); }
.agents-note h2, .agents-note b { color: var(--cream-on-dark); }
.agents-note a { color: var(--gold-glint); }
.agents-note code { background: rgba(242,236,221,0.1); color: var(--cream-on-dark); border-color: rgba(242,236,221,0.18); }

/* 🟠 two cream-on-walnut display lines with a gold accent phrase 300px apart —
   the second diluted the first. The CTA band keeps the gold; the close is cream. */
.foot-close .close-line .gl { color: var(--cream-on-dark); }
.foot-close .chalk-mark svg { display: none; }

/* 🟠 the openai inset is wrong — two critics measured it opposite ways, so the
   safest reading is that no mark is a clear outlier. Back to raw. */
.agent-cycle img[src*="openai"] { padding: 0; }

/* 🟡 ~6 viewport-heights of unbroken cream in the mobile tail; the platform
   index takes the oat ground already used by the on-the-go band. */
.plat-sec { background: var(--sheet-2); border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline); }
/* a more specific rule kept this paragraph at --ink-mute (2.7:1 on walnut) */
.agents-note p, .agents-note li { color: var(--mute-on-dark); }

/* B (critic, measured) — the FAQ is the flattest block on the page (nine
   collapsed rows, hairline rules, no tonal event) sitting between the pricing
   decision and the emotional close. It was the middle of the page's longest
   dead run: 1800px desktop / 3100px mobile. On walnut it splits that run and
   gives the tail a dark -> cream -> dark -> oat -> dark cadence into the close. */
section.sec#faq { background: var(--walnut); color: var(--mute-on-dark); }
section.sec#faq .sec-head h2 { color: var(--cream-on-dark); }
section.sec#faq .eyebrow { color: var(--mute-on-dark); }
section.sec#faq .faq-item { border-color: rgba(242,236,221,0.16); }
section.sec#faq .faq-item summary { color: var(--cream-on-dark); }
section.sec#faq .faq-item .a { color: var(--mute-on-dark); }
section.sec#faq .faq-item .x { color: var(--gold-glint); }
section.sec#faq .faq-item a { color: var(--gold-glint); }
section.sec#faq .faq-item[open] summary { color: var(--cream-on-dark); }
/* on mobile the feature list collapses behind a "What's included" toggle, so
   the .plus label added for desktop row-alignment is a second label for the
   same list. The desktop grid still needs it; the collapsed card does not. */
@media (max-width: 900px) {
  .js .pcard.collapsible .plus { display: none; }
}

/* 3 — the FAQ code chips carry the connector URL and the install command, the
   most load-bearing strings on the page; on walnut they measured 1.98:1. They
   sit inside a closed <details>, so no audit sees them. */
section.sec#faq .faq-item .a code { background: rgba(242,236,221,0.10); color: var(--cream-on-dark); }
section.sec#faq .faq-item .a a { color: var(--gold-glint); }
/* 5 — #faq's summary rule (1,1,1) outranked .faq-item summary:hover (0,2,1),
   so ten accordion rows lost hover feedback entirely */
section.sec#faq .faq-item summary:hover { color: var(--gold-glint); }
section.sec#faq .faq-item summary:hover .x { color: var(--gold-glint); }

/* ============================================================
   ROUND 18 — contrast failures Lighthouse scores 100 on
   Found by compositing every translucent layer down to the real
   painted ground, and by opening every <details> first. axe only
   samples rendered text on solid parents, so it saw none of these.
   ============================================================ */

/* gold-on-gold-wash badges: 4.13:1 and 3.85:1 */
.agent-tab .soon, .plat-card.soon .badge-soon { color: #6f5518; }

/* the draft avatar was cream on clay at 3.56:1 */
.postmini .av { background: var(--sanguine); }

/* a five-star rating is information, not decoration — 1.92:1 in glint gold */
.love .stars { color: #8a6a1f; }

/* --clay-deep measures 4.09:1 on canvas; --clay-link is the body-copy clay */
.pnote { color: var(--clay-link); }

/* the featured slab never got a dark-ground override for its price suffix */
.pcard.featured .pprice small { color: var(--mute-on-dark); }

/* ============================================================
   ROUND 19 — ship the art that was already on disk
   hero_squadron (1400x1738, the highest-res asset, literally named
   for the hero) and aerial_screw were used on ZERO pages. The first
   photograph on the homepage arrived at 32% scroll depth; the only
   signature composition sat at 84%. The centred hero is the owner's
   explicit instruction, so the art sits ADJACENT to it rather than
   restructuring the hero into two columns.
   ============================================================ */
.squadron-band {
  /* R31 #11: squadronDrift's scale(1.06) pushed the img 94px past the
     viewport; only overflow-x:clip on body hid it. Clip at the band. */
  overflow: hidden;
  position: relative; isolation: isolate;
  border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline);
  background: var(--sheet-2);
}
.squadron-band picture, .squadron-band img { display: block; width: 100%; height: 100%; }
.squadron-band img { object-fit: cover; object-position: 32% center; }
/* the line sits in the empty right third the photograph was composed to leave */
.squadron-band .sq-line {
  position: absolute; inset: 0 clamp(20px, 6vw, 88px) 0 auto;
  display: flex; align-items: center; justify-content: flex-end; text-align: right;
  max-width: 30ch; margin: 0;
  font-family: var(--sans); font-size: var(--step-2); font-weight: 400;
  line-height: 1.24; letter-spacing: -0.014em; color: var(--ink);
  text-wrap: balance; pointer-events: none;
}
@media (max-width: 900px) {
  .squadron-band { display: grid; }
  .squadron-band img { object-position: 38% center; min-height: 210px; }
  .squadron-band .sq-line {
    position: static; inset: auto; max-width: none; text-align: center;
    justify-content: center; padding: 20px clamp(20px, 6vw, 32px) 26px;
    font-size: var(--step-1); background: var(--sheet-2);
  }
}

/* B1 — the signature asset had the same entrance verb as 30 other elements.
   Give it the scroll-linked grammar already proven on .proof-shot. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .squadron-band img {
      animation: squadronDrift linear both;
      animation-timeline: view();
      animation-range: entry 5% cover 48%;
    }
    @keyframes squadronDrift {
      from { transform: scale(1.06) translateX(1.4%) rotate(0.7deg); }
      to   { transform: scale(1) translateX(0) rotate(0deg); }
    }
  }
}

/* aerial_screw grounds the capability band, which ran ~3,000px of cream
   with no photography at all */
.caps-sec { position: relative; }
.caps-sec::before {
  /* R31 #11: flush right:0 bisected the helicopter at the viewport edge and
     its box overlapped the first scene card. Smaller, fully inside, quieter. */
  content: ''; position: absolute; right: clamp(16px, 3vw, 56px); top: clamp(16px, 3vh, 44px);
  width: min(240px, 17vw); aspect-ratio: 1120 / 836;
  background: url('/art/aerial_screw-560.webp') no-repeat right top / contain;
  /* multiply DARKENED this one (its ground is darker than the cream) and left
     three hard edges. It is atmosphere, so feather it instead of mounting it —
     the plate treatment 15 lines below is for figures like .squadron. */
  mix-blend-mode: normal; opacity: .22; pointer-events: none;
  -webkit-mask-image: radial-gradient(closest-side, #000 52%, transparent 100%);
  mask-image: radial-gradient(closest-side, #000 52%, transparent 100%);
}
@media (max-width: 900px) { .caps-sec::before { display: none; } }

/* standalone link rows were 31px / 37px / 19px tall — clears WCAG 2.5.8 AA
   (24px) but not the 44px bar a studio holds for a primary tap target */
@media (max-width: 900px) {
  .pmore, .ulink, .brand, .rp-list a, .plat-card .go {
    min-height: 44px; display: inline-flex; align-items: center;
  }
  .foot-meta a { min-height: 44px; display: inline-flex; align-items: center; }
}
/* the photo's own cream is lighter than the oat band, so multiply left a
   visible rectangle. Mount it as a plate, like every other figure here. */
.squadron img {
  mix-blend-mode: normal;
  border-radius: 14px;
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-lift);
}

/* blog prose links used --clay-deep (4.09:1 on the post ground); --clay-link
   is the accessible body clay used everywhere else on the site */
.post-body a, .faq .a a, .post-body .related a { color: var(--clay-link); }

/* the featured slab sat ON its cell edge with 24px of interior padding, so its
   content hung right of the other three columns' text. The first fix widened
   the slab past its cell with -24px inline margins — but .price-grid runs at
   `gap: 0` (the column rules ARE the borders), so a negative margin does not
   bleed into a gutter, it bleeds ONTO the neighbouring card and painted dark
   ink over the first 24px of every line in the Enterprise column (1.02:1).
   Align by matching the siblings' 22px text inset instead; nothing overlaps. */
@media (min-width: 901px) {
  .pcard.featured { padding-left: 22px; padding-right: 22px; }
}

/* Centre the mark on the LAST LETTER's ink, not on the line box. Measured from
   the glyph itself: 't' at 83.2px has an ink ascent of 54.83px and no descender,
   so its optical centre is 27.41px above the baseline. The mark is 0.92em tall,
   so its box must sit 0.131em below the baseline for the two centres to meet. */
.agent-cycle { vertical-align: -0.131em; }

/* ============================================================
   SCENE CHROME — three surfaces, three looks.
   Oded: "All of the claude screenshots are designed the same —
   rectangulars with round corners. It's hard to differentiate."
   He was right: every scene was one .cap-scene shell with three
   traffic-light dots and a mono label, so the only thing that
   changed between Claude, Claude Code and ChatGPT was a 14px
   logo. Each surface now carries its own ground, radius, type
   and message shape — the terminal most of all.
   ============================================================ */

/* --- shared: the bar is a product label, not machine text --- */
.cap-scene .scene-bar .who {
  font-family: var(--sans); font-size: var(--step--2);
  letter-spacing: 0; text-transform: none; color: var(--ink-soft); font-weight: 500;
}
.cap-scene .scene-bar { padding: 11px 16px; gap: var(--sp-3); }

/* ---------- 1. Claude: warm paper, soft bubbles ---------- */
/* sheet-2, not sheet: .caps-sec IS var(--sheet), so a sheet-coloured scene on it
   has no edge at all and the window stops reading as a window. */
.cap-scene.sc-claude { background: var(--sheet-2); border-radius: 16px; }
.sc-claude .scene-bar { background: var(--sheet-2); border-bottom: 1px solid var(--hairline); }

/* ---------- 2. ChatGPT: flat white, full-round bubble, no pill ---------- */
.cap-scene.sc-gpt { background: #fff; border-radius: 12px; }
.sc-gpt .scene-bar { background: #fff; border-bottom: 1px solid rgba(20,16,10,.07); }
.sc-gpt .scene-body { background: #fff; }
.sc-gpt .scene-body .u {
  background: #f1efec; color: var(--walnut); border-radius: 18px; padding: 10px 16px;
}
.sc-gpt .postmini, .sc-gpt .critique, .sc-gpt .choice { background: #faf9f8; }
.sc-gpt .toolchip {
  background: none; border: 0; padding: 0; border-radius: 0;
  font-family: var(--sans); font-size: var(--step--2); color: var(--ink-mute);
}

/* ---------- 3. Claude Code: an actual terminal ---------- */
.cap-scene.sc-term {
  background: #17110a; border: 1px solid #2f2417; border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,.25), 0 22px 50px -22px rgba(0,0,0,.7);
  font-family: var(--mono);
}
.sc-term .term-bar {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 9px 13px; background: #201810; border-bottom: 1px solid #2f2417;
}
.sc-term .term-bar .dots { display: flex; gap: 6px; }
.sc-term .term-bar .dots i { width: 9px; height: 9px; border-radius: 50%; background: #3b2f20; }
.sc-term .term-bar .who {
  display: flex; align-items: center; gap: var(--sp-2); margin: 0 auto;
  font-family: var(--mono); font-size: var(--step--3); color: #a3927a; letter-spacing: 0.02em;
}
.sc-term .term-bar .who img { opacity: .85; }
.sc-term .term-body {
  padding: clamp(15px, 2vw, 21px) clamp(14px, 1.9vw, 20px) 8px;
  display: flex; flex-direction: column; gap: 5px;
}
/* flex here let the un-wrappable mono line set the column's min-content width,
   which blew the terminal ~95px past its grid cell and into the gutter.
   Hanging indent instead: wraps like a real terminal, shrinks to its cell. */
.sc-term .tl {
  margin: 0; font-size: var(--step--2); line-height: 1.62; color: #cbbda6;
  padding-left: 19px; text-indent: -19px; overflow-wrap: anywhere;
}
.sc-term .tl .car, .sc-term .tl .bul, .sc-term .tl .elb {
  display: inline-block; width: 11px; text-indent: 0; margin-right: 8px;
}
.sc-term .tprompt { color: #f0e7d6; margin-bottom: 5px; }
.sc-term .tprompt .car, .sc-term .term-input .car { color: var(--clay); flex: none; }
.sc-term .trun { color: #a3927a; }
.sc-term .trun .bul { color: var(--clay); font-size: .74em; flex: none; }
/* padding-left here overrode .tl's 19px while text-indent stayed at -19px, so
   the ⊦ marker hung 16px outside the terminal's padding. Indent, don't reset. */
.sc-term .tout { color: #988567; padding-left: 32px; }
.sc-term .tout .elb { color: #8f7d5f; }
.sc-term .tsay { color: #f0e7d6; margin-top: 9px; display: block; }
.sc-term .tlist {
  list-style: none; margin: 4px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 9px; counter-reset: t;
}
.sc-term .tlist li {
  counter-increment: t; position: relative; padding-left: 22px;
  font-size: var(--step--2); line-height: 1.55;
}
.sc-term .tlist li::before {
  content: counter(t) "."; position: absolute; left: 0; top: 0; color: #988567;
}
.sc-term .tlist b { display: block; color: #f0e7d6; font-weight: 400; }
.sc-term .tlist span { display: block; color: #988567; margin-top: 2px; }
.sc-term .term-input {
  display: flex; align-items: center; gap: 9px;
  margin: 11px clamp(14px, 1.9vw, 20px) clamp(14px, 1.9vw, 18px);
  padding: 9px 12px; border: 1px solid #322619; border-radius: 6px;
  font-size: var(--step--2); color: #cbbda6;
}
.sc-term .term-input .blink {
  width: 7px; height: 1.05em; background: #cbbda6; border-radius: 1px;
  animation: termBlink 1.15s steps(1, end) infinite;
}
@keyframes termBlink { 0%, 55% { opacity: 1; } 56%, 100% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .sc-term .term-input .blink { animation: none; } }

/* ---------- 4. Results: the chart card ---------- */
.chartcard {
  margin: 0; padding: 14px 15px 10px;
  background: var(--canvas); border: 1px solid var(--hairline); border-radius: 12px;
}
.chartcard figcaption {
  font-family: var(--sans); font-size: var(--step--2); color: var(--ink-soft);
  margin-bottom: 10px;
}
.spark { display: block; width: 100%; height: auto; overflow: visible; }
.spark .grid line { stroke: var(--hairline); stroke-width: 1; }
.spark .ax text, .spark .hrs text {
  font-family: var(--mono); font-size: 11px; fill: var(--ink-mute);
}
.spark .ax text { text-anchor: end; }
.spark .hrs text { text-anchor: middle; }
.spark .trace {
  fill: none; stroke: var(--clay); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
.spark .pts circle { fill: var(--clay); }
.spark .pts circle.last { fill: var(--sanguine); stroke: var(--canvas); stroke-width: 1.6; }
/* the trace draws itself when the capability scrolls in — same chalkDraw verb
   the hero mark and the receipts rule already use */
/* the path's true length is 289.16, not 340 — a hardcoded 340 left ~15% of the
   1.1s draw (about 165ms) running after the line was already complete.
   pathLength="1" normalises it so the draw ends exactly when the line does. */
.js .spark .trace { stroke-dasharray: 1; stroke-dashoffset: 1; }
.js .spark .pts circle { opacity: 0; }
.js .cap.in .spark .pts circle:nth-child(1) { animation-delay: 300ms; }
.js .cap.in .spark .pts circle:nth-child(2) { animation-delay: 440ms; }
.js .cap.in .spark .pts circle:nth-child(3) { animation-delay: 570ms; }
.js .cap.in .spark .pts circle:nth-child(4) { animation-delay: 700ms; }
.js .cap.in .spark .pts circle:nth-child(5) { animation-delay: 840ms; }
.js .cap.in .spark .pts circle:nth-child(6) { animation-delay: 990ms; }
@keyframes ptPop { from { opacity: 0; transform: scale(.3); } to { opacity: 1; transform: scale(1); } }
.spark .pts circle { transform-box: fill-box; transform-origin: center; }
@media (prefers-reduced-motion: reduce) {
  .js .spark .trace { stroke-dashoffset: 0; animation: none; }
  .js .spark .pts circle { opacity: 1; animation: none; }
}

/* ============================================================
   DARK-GROUND OVERRIDES — key on the GROUND, not on one class.
   `.night` was the original dark section, so every light-ink
   override was written as `.night …`. Three more sections went
   walnut later by different routes (section.sec#faq, .sec.cta-band,
   .receipts-sec), and each one silently re-derived most of the
   overrides but not all — step "08" on /how-it-works.html shipped
   as --sanguine on walnut at 2.73:1 because `.night .eyebrow .tick`
   never matched it. Same failure mode as the pricing slab: the
   guard was on one door instead of in the rule.
   ============================================================ */
.night .eyebrow .tick,
section.sec#faq .eyebrow .tick,
.sec.cta-band .eyebrow .tick,
.receipts-sec .eyebrow .tick,
.agents-note .eyebrow .tick,
footer.site .eyebrow .tick { color: var(--gold-glint); }

.night .eyebrow,
section.sec#faq .eyebrow,
.sec.cta-band .eyebrow,
.receipts-sec .eyebrow,
.agents-note .eyebrow { color: var(--mute-on-dark); }

.night .chalk-mark svg path,
section.sec#faq .chalk-mark svg path,
.sec.cta-band .chalk-mark svg path,
.receipts-sec .chalk-mark svg path { stroke: var(--gold-glint); }

/* ============================================================
   PRICING COLUMNS — one inset for all four tiers.
   Measured at 1280/1440/1728: every cell is 280px, but Free/Pro/
   Enterprise rendered padding-inline 0/22 while Business (the
   featured slab) rendered 22/22. So the tier we most want read
   had a 236px text column against everyone else's 258px, and its
   text started 22px right of where the other three started —
   which is what reads as "not well spaced". Same inset for all.
   ============================================================ */
@media (min-width: 901px) {
  .price-grid > .pcard { padding-left: 22px; padding-right: 22px; }
  /* the outer two would now sit 22px inside the wrap on each side; pull the
     table back out so the first and last columns still align to the measure */
  .price-grid { margin-left: -22px; margin-right: -22px; }
}

/* ============================================================
   ROUND-21 REGRESSION FIXES (both verified by measurement first)
   ============================================================ */

/* A7 — the featured slab keeps a -22px BLOCK bleed. At >=1081px the grid is
   4-up and the bleed only eats its own row's whitespace. Below that the grid
   collapses to 2x2, Business lands directly under Free, and the opaque walnut
   slab painted 494x19px over Free's "Start free" button. Measured at 1024:
   Free bottom 1610, Business top 1591. The bleed is decoration; drop it
   wherever the grid is not 4-up. */
@media (max-width: 1080px) {
  .pcard.featured { margin-top: 0; margin-bottom: 0; }
}

/* A4 — .cap-writing's scene was pinned to `min(1320px, 92vw)` centred on the
   viewport, so at 1440 it spanned 60->1380 while its own headline started at
   160. It aligned to nothing: not the page edge, not the gutter, not the text
   column. Let it run wider than the reading column (it is a two-pane scene)
   but land on the same measure everything else uses. */
@media (min-width: 901px) {
  .cap-writing .cap-scene {
    width: 100%;
    max-width: var(--maxw);
    margin-left: auto;
    margin-right: auto;
  }
}

/* A6 — the frosted header printed the silhouette of whatever dark slab passed
   under it: a hard-edged grey rectangle across the nav, ending mid-word. 82%
   was obvious, 93% was still visible (confirmed by screenshot at 1440 with the
   .sc-term scene behind the nav — the rectangle stops right before "Features").
   Translucency cannot be tuned out of this: any alpha < 1 composites the slab.
   The header is opaque now. The blur was only ever selling depth the hairline
   already provides. */
header.site {
  background: var(--canvas);
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
.site.on-dark { background: var(--walnut); }

/* the headline number belongs on the chart, not only in the prose beneath it */
.spark .peak text {
  font-family: var(--sans); font-size: 15px; font-weight: 500;
  fill: var(--sanguine); text-anchor: end;
}
.js .spark .peak { opacity: 0; }
@media (prefers-reduced-motion: reduce) { .js .spark .peak { opacity: 1; animation: none; } }

/* ============================================================
   BUILDER PHOTOS — two prints, not one empty well.
   The slot used to be a dashed 4:5 placeholder reading "Photo
   coming shortly". Now: the stage shot (him presenting his own
   Liftli post, 300 reactions / 175 comments on the slide behind
   him) as the primary print, with the seaside portrait pinned
   over its corner. The stage shot IS the section's argument —
   building in public — and the portrait is the face.
   ============================================================ */
/* The 5fr/6fr widening was added to "give the photo pair room", but measured
   with a real ch probe it starved the copy at every width: 41-44ch against the
   base 4fr/7fr rule's 45-62ch. The photos do not need it — 4fr is ~407px at
   1440, ample for a 3:2 print with an inset. The base rule at :785 stands, and
   the stack below 901px stands. Nothing overrides either. */
.bphotos { position: relative; padding-bottom: 46px; }
.bphotos picture { display: block; }
.bphotos img { display: block; width: 100%; height: auto; }

.bp-stage img {
  border-radius: var(--radius); border: 1px solid var(--hairline);
  box-shadow: var(--shadow-lift);
  transform: rotate(-1.2deg);
}
/* The portrait must not cover the thing the stage shot is FOR. Oded is on the
   right of that frame at roughly x 72-95%, y 28-55%; the first placement
   (right:-10px, bottom:0, 41%) sat straight on top of him. Hang the print off
   the bottom-right corner instead, so it overlaps only floor. */
.bp-portrait {
  position: absolute; right: -16px; bottom: -22px; width: 34%;
}
.bp-portrait img {
  border-radius: 10px;
  /* a physical print: white margin, not a hairline */
  border: 5px solid var(--sheet);
  box-shadow: 0 2px 4px rgba(34,26,16,.14), 0 16px 34px -14px rgba(34,26,16,.42);
  transform: rotate(3.4deg);
}
@media (max-width: 900px) {
  .builder-photo { max-width: 460px; }
  .bp-portrait { width: 33%; right: -6px; bottom: -14px; }
}
/* the caption is a label, not machine text */
.builder-photo figcaption {
  font-family: var(--sans); letter-spacing: 0; text-transform: none;
  font-size: var(--step--1); margin-top: 20px;
}

/* ============================================================
   TELEGRAM FIGURES — a live chat, not a screenshot.
   These were raw phone screenshots (messenger doodle wallpaper,
   system Helvetica, a soft fade that dissolved mid-sentence) on a
   page of commissioned photography. Rebuilt as a real Telegram
   surface inside a device frame, replaying the exchange on a
   loop: message -> typing -> reply. Telegram's own light-theme
   wallpaper is a warm tan, which sits inside the paper palette
   rather than fighting it.
   ============================================================ */
.tgfig { margin: 0; display: flex; flex-direction: column; align-items: center; }
.tgphone {
  width: min(310px, 100%);
  background: #1c1712;
  border-radius: 40px;
  padding: 9px;
  box-shadow:
    0 0 0 1px rgba(34,26,16,.30),
    0 2px 5px rgba(34,26,16,.20),
    0 26px 56px -22px rgba(34,26,16,.55);
}
.tg-screen {
  position: relative; overflow: hidden;
  border-radius: 32px;
  background: #e9dccb;
  /* Telegram's tan wallpaper, approximated as a soft repeating wash */
  background-image:
    radial-gradient(circle at 22% 18%, rgba(255,255,255,.42) 0 12px, transparent 13px),
    radial-gradient(circle at 74% 44%, rgba(198,168,132,.30) 0 15px, transparent 16px),
    radial-gradient(circle at 42% 78%, rgba(255,255,255,.30) 0 11px, transparent 12px);
  background-size: 116px 116px, 148px 148px, 132px 132px;
  display: flex; flex-direction: column;
  min-height: 430px;
}
.tg-island {
  position: absolute; top: 7px; left: 50%; transform: translateX(-50%);
  width: 74px; height: 19px; border-radius: 999px; background: #1c1712; z-index: 4;
}

/* header */
.tg-top {
  display: flex; align-items: center; gap: 9px;
  padding: 30px 13px 9px;
  background: rgba(252,250,247,.96);
  border-bottom: 1px solid rgba(34,26,16,.10);
  position: relative; z-index: 3;
}
.tg-back {
  width: 8px; height: 8px; flex: none; margin-right: 1px;
  border-left: 1.8px solid #3d90d8; border-bottom: 1.8px solid #3d90d8;
  transform: rotate(45deg);
}
.tg-av {
  width: 29px; height: 29px; flex: none; border-radius: 50%;
  background: var(--clay-wash); display: grid; place-items: center;
}
.tg-who { display: flex; flex-direction: column; line-height: 1.18; min-width: 0; }
.tg-who b { font-size: 12.5px; font-weight: 600; color: var(--ink); }
.tg-who i { font-style: normal; font-size: 10.5px; color: #7d8b96; }

/* message list */
.tg-body {
  flex: 1; display: flex; flex-direction: column; gap: 7px;
  padding: 12px 10px 8px; overflow: hidden;
}
.tg-date {
  align-self: center; font-size: 10px; color: #6c5f4e;
  background: rgba(252,250,247,.72); border-radius: 999px; padding: 2px 10px; margin-bottom: 2px;
}
.tg-msg {
  position: relative; max-width: 84%;
  padding: 7px 10px 15px; border-radius: 12px;
  font-size: 12.3px; line-height: 1.42; color: #1d1a16;
  box-shadow: 0 1px 1px rgba(34,26,16,.12);
}
.tg-msg b { font-weight: 600; }
.tg-msg.in  { align-self: flex-start; background: #fffdf9; border-bottom-left-radius: 4px; }
.tg-msg.out { align-self: flex-end;  background: #e6f6cf; border-bottom-right-radius: 4px; }
.tg-meta {
  position: absolute; right: 9px; bottom: 4px;
  font-size: 9.2px; color: #8a9aa6; display: inline-flex; align-items: center; gap: 3px;
}
.tg-msg.out .tg-meta { color: #6aa84f; }

/* voice note bubble */
.tg-voice { display: flex; align-items: center; gap: 8px; padding-right: 12px; }
.tg-play {
  width: 27px; height: 27px; flex: none; border-radius: 50%; background: #5eb257;
  position: relative;
}
.tg-play::after {
  content: ''; position: absolute; top: 50%; left: 53%; transform: translate(-50%,-50%);
  border-left: 8px solid #fff; border-top: 5px solid transparent; border-bottom: 5px solid transparent;
}
.tg-wave { display: flex; align-items: center; gap: 1.6px; height: 20px; flex: 1; }
.tg-wave i { flex: 1; background: #9dc98e; border-radius: 1px; height: 30%; }
.tg-wave i:nth-child(3n)   { height: 74%; }
.tg-wave i:nth-child(4n+1) { height: 52%; }
.tg-wave i:nth-child(5n+2) { height: 92%; }
.tg-wave i:nth-child(7n)   { height: 40%; }
.tg-dur { font-size: 10.5px; color: #5c6b58; flex: none; }

/* typing indicator */
.tg-typing {
  align-self: flex-start; display: flex; align-items: center; gap: 4px;
  background: #fffdf9; border-radius: 12px; border-bottom-left-radius: 4px;
  padding: 11px 12px; box-shadow: 0 1px 1px rgba(34,26,16,.12);
}
.tg-typing i { width: 5px; height: 5px; border-radius: 50%; background: #a9b6bf; }

/* news card message */
.tg-card { display: flex; flex-direction: column; gap: 5px; max-width: 92%; padding-bottom: 16px; }
.tgc-kicker { font-size: 9.4px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: #3d90d8; }
.tgc-head { font-size: 12.8px; font-weight: 600; line-height: 1.3; color: var(--ink); }
.tgc-why { font-size: 11.4px; line-height: 1.4; color: #4a4234; }
.tgc-src { font-size: 9.6px; color: #8a9aa6; }
.tgc-btn {
  margin-top: 4px; text-align: center; font-size: 11.6px; font-weight: 600; color: #3d90d8;
  border-top: 1px solid rgba(34,26,16,.10); padding-top: 8px;
}

/* composer */
.tg-compose {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 11px 14px;
  background: rgba(252,250,247,.96);
  border-top: 1px solid rgba(34,26,16,.10);
}
.tg-clip, .tg-mic { width: 15px; height: 15px; flex: none; opacity: .5; }
.tg-clip { border: 1.7px solid #6c7a85; border-radius: 4px 8px 8px 4px; }
.tg-mic  { border: 1.7px solid #6c7a85; border-radius: 999px 999px 5px 5px; }
.tg-field { flex: 1; font-size: 12px; color: #8a9aa6; }

/* .tgfig figcaption removed with the fig. 01-03 captions (Oded, 30 Aug) */

/* the two figures are phones now, so the old shot sizing does not apply */
.togo-sec .cap { align-items: center; }
@media (max-width: 900px) { .tgphone { width: min(290px, 86%); } }

/* the 28 waveform bars were `flex: 1` inside a ~150px track with 1.6px gaps,
   which left each bar under a pixel wide and rendered nothing at all */
.tg-wave { gap: 2px; }
.tg-wave i { flex: none; width: 2px; border-radius: 1px; }
/* the played portion of a Telegram voice note is darker than the rest */
.tg-wave i:nth-child(-n+11) { background: #5eb257; }

/* the composer glyphs were bare bordered boxes that read as blobs; draw them */
.tg-clip, .tg-mic { border: 0; position: relative; opacity: .55; }
.tg-clip::before {
  content: ''; position: absolute; inset: 1px 3px;
  border: 1.7px solid #6c7a85; border-radius: 999px;
  transform: rotate(-38deg); border-top-color: transparent;
}
.tg-mic::before {
  content: ''; position: absolute; left: 4px; top: 0; width: 7px; height: 9px;
  background: #6c7a85; border-radius: 999px;
}
.tg-mic::after {
  content: ''; position: absolute; left: 1.5px; top: 7px; width: 12px; height: 6px;
  border: 1.7px solid #6c7a85; border-top: 0; border-radius: 0 0 999px 999px;
}

/* Telegram's own greys and blues sit at 2.3-3.4:1. Reproducing a UI faithfully
   is not a licence to ship unreadable text, so every label here is the same hue
   pushed to >=4.6:1 on its actual bubble. Still reads as Telegram. */
.tg-field { color: #5a646c; }
.tg-who i { color: #5b656e; }
.tg-msg.in .tg-meta, .tgc-src { color: #69757e; }
.tg-msg.out .tg-meta { color: #4b7738; }
.tgc-kicker, .tgc-btn { color: #3378b3; }

/* ============================================================
   PRICING GRID — land on the page measure, keep the tiers aligned.
   Two defects, both from the previous fix:
   A3 the `padding-inline:22px` + `margin-inline:-22px` pair landed
      the TEXT on 160/1280 but the border-top and the featured slab
      paint on the BOX, so the longest horizontal rule on the page
      ran 138->1302 — 22px past the measure every other band uses.
   A2 the -22px BLOCK bleed was silently compensating for featured's
      larger padding; zeroing it below 1081px left the Business
      column's first ink 22px lower than its row (measured 1706 vs
      1685 at 1024) and its CTA 23px the other way.
   Real gutter instead: no card padding, a 44px column-gap, and the
   bleed on the slab alone — which is what the gap is there to absorb.
   ============================================================ */
@media (min-width: 901px) {
  .price-grid { margin-left: 0; margin-right: 0; column-gap: 44px; }
  .price-grid > .pcard { padding-left: 0; padding-right: 0; }
  .price-grid > .pcard.featured { padding-left: 22px; padding-right: 22px; }
}
/* below 1081px the block bleed is off, so featured must match its siblings'
   vertical insets too, or it sits 22px low in its own row */
@media (max-width: 1080px) and (min-width: 901px) {
  .pcard.featured { padding-top: 26px; padding-bottom: 26px; }
}

/* ============================================================
   ONE MEASURE — derive the right-hand gutter instead of guessing.
   `.wrap` is `max-width: var(--maxw); margin: 0 auto; padding: 0
   clamp(20px,4vw,40px)`, so the distance from the wrap's CONTENT
   right edge to the viewport right edge is exactly:
       (100vw - min(100vw, --maxw)) / 2  +  that padding
   Two rules were guessing at it with `calc(50% - 50vw)`, where the
   50% resolves against the element's own containing block rather
   than the viewport. Measured cost: the closing CTA artwork ran
   355px past the viewport at 1440 — 40% of the image thrown away
   (45% at 1024) — and the squadron caption landed 74px past the
   measure every other band uses.
   ============================================================ */
:root {
  --wrap-pad: clamp(20px, 4vw, 40px);
  --bleed-r: calc((100vw - min(100vw, var(--maxw))) / 2 + var(--wrap-pad));
}
@media (min-width: 901px) {
  .sec.cta-band .cta-art { margin-right: calc(-1 * var(--bleed-r)); }
  .squadron-band .sq-line { right: var(--bleed-r); }
}

/* B1 — the writing band's ground was rgb(246,242,232) against a
   rgb(253,250,243) neighbour: a 3.2% luminance step, under the ~5%
   floor where a ground change reads as deliberate. It still carried
   two edge-to-edge hairlines, so they read as rules dividing nothing.
   Commit the ground: --sheet-2 is a step you can actually see. */
.cap-writing { background: var(--sheet-2); }
.cap-writing .cap-scene .critique, .cap-writing .cap-scene .choice { background: var(--sheet); }

/* B3 — "Posts from this window" (plural) over a one-row list read as an
   under-delivered promise: a visitor counts one. Header is singular now, and
   the one real receipt gets the weight of a card instead of a 47px row. */
.rp-list li a { align-items: flex-start; padding: 15px 16px; }
.rp-list .rp-t { display: flex; flex-direction: column; gap: 4px; }
.rp-list .rp-t b { font-weight: 500; color: var(--cream-on-dark); }
.rp-list .rp-t i {
  font-style: normal; font-size: var(--step--2); line-height: var(--lh-mid);
  color: var(--mute-on-dark); max-width: 46ch;
}
.rp-list .rp-go { align-self: center; flex: none; }

/* the slab may only bleed into the gutter where it HAS a gutter on both
   sides — that is the 4-up range. Below 1081px it sits in column 1. */
@media (min-width: 1081px) {
  .price-grid > .pcard.featured { margin-left: -22px; margin-right: -22px; }
}

/* ============================================================
   SCENE REPLAY — one clock, synchronised exits.
   The first looping build gave every child the SAME 15s animation
   with a different `animation-delay`. Infinite animations with
   different delays can never resynchronise, so each element also
   EXITED on its own offset: for ~2.7s of every cycle the user's own
   voice note had vanished from the top of the Telegram thread while
   both of Liftli's replies were still on screen, and the typing
   indicator re-fired underneath a message it had already sent. A
   chat transcript that deletes its own history is worse than a
   static screenshot.
   Every element now runs delay:0 on the same 15s clock and encodes
   its entrance beat as a percentage, so the conversation builds in
   order, holds whole, and clears in one piece.
   ============================================================ */
@keyframes sceneStep1 {
  0% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; padding-block: 0; overflow: hidden; }
  1.36% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; padding-block: 0; overflow: hidden; }
  4.09% { opacity: 1; transform: none; max-height: 30em; margin-block: var(--bmt, 0px) 0; padding-block: var(--bpt, 0px) var(--bpb, 0px); }
  96% { opacity: 1; transform: none; max-height: 30em; }
  100% { opacity: 0; transform: translateY(-4px); max-height: 30em; }
}

@keyframes sceneStep2 {
  0% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; padding-block: 0; overflow: hidden; }
  6.37% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; padding-block: 0; overflow: hidden; }
  9.1% { opacity: 1; transform: none; max-height: 30em; margin-block: var(--bmt, 0px) 0; padding-block: var(--bpt, 0px) var(--bpb, 0px); }
  96% { opacity: 1; transform: none; max-height: 30em; }
  100% { opacity: 0; transform: translateY(-4px); max-height: 30em; }
}

@keyframes sceneStep3 {
  0% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; padding-block: 0; overflow: hidden; }
  11.36% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; padding-block: 0; overflow: hidden; }
  14.09% { opacity: 1; transform: none; max-height: 30em; margin-block: var(--bmt, 0px) 0; padding-block: var(--bpt, 0px) var(--bpb, 0px); }
  96% { opacity: 1; transform: none; max-height: 30em; }
  100% { opacity: 0; transform: translateY(-4px); max-height: 30em; }
}

@keyframes sceneStep4 {
  0% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; padding-block: 0; overflow: hidden; }
  16.36% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; padding-block: 0; overflow: hidden; }
  19.09% { opacity: 1; transform: none; max-height: 30em; margin-block: var(--bmt, 0px) 0; padding-block: var(--bpt, 0px) var(--bpb, 0px); }
  96% { opacity: 1; transform: none; max-height: 30em; }
  100% { opacity: 0; transform: translateY(-4px); max-height: 30em; }
}

@keyframes sceneStep5 {
  0% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; padding-block: 0; overflow: hidden; }
  21.37% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; padding-block: 0; overflow: hidden; }
  24.1% { opacity: 1; transform: none; max-height: 30em; margin-block: var(--bmt, 0px) 0; padding-block: var(--bpt, 0px) var(--bpb, 0px); }
  96% { opacity: 1; transform: none; max-height: 30em; }
  100% { opacity: 0; transform: translateY(-4px); max-height: 30em; }
}

@keyframes sceneStep6 {
  0% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; padding-block: 0; overflow: hidden; }
  26.36% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; padding-block: 0; overflow: hidden; }
  29.09% { opacity: 1; transform: none; max-height: 30em; margin-block: var(--bmt, 0px) 0; padding-block: var(--bpt, 0px) var(--bpb, 0px); }
  96% { opacity: 1; transform: none; max-height: 30em; }
  100% { opacity: 0; transform: translateY(-4px); max-height: 30em; }
}

@keyframes sceneStep7 {
  0% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; padding-block: 0; overflow: hidden; }
  31.36% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; padding-block: 0; overflow: hidden; }
  34.09% { opacity: 1; transform: none; max-height: 30em; margin-block: var(--bmt, 0px) 0; padding-block: var(--bpt, 0px) var(--bpb, 0px); }
  96% { opacity: 1; transform: none; max-height: 30em; }
  100% { opacity: 0; transform: translateY(-4px); max-height: 30em; }
}

.js .cap-scene .u, .js .cap-scene .toolchip, .js .cap-scene .a,
.js .cap-scene .choices, .js .cap-scene .idealist, .js .cap-scene .postmini,
.js .cap-scene .critique, .js .cap-scene .chartcard { opacity: 0; }
.js .cap.in.playing .scene-body > *:nth-child(1) { animation: sceneStep1 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .scene-body > *:nth-child(2) { animation: sceneStep2 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .scene-body > *:nth-child(3) { animation: sceneStep3 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .scene-body > *:nth-child(4) { animation: sceneStep4 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .scene-body > *:nth-child(5) { animation: sceneStep5 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .scene-body > *:nth-child(6) { animation: sceneStep6 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .scene-body > *:nth-child(7) { animation: sceneStep7 11s var(--ease-out) 0s infinite both; }
@keyframes termLine1 {
  0% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  1.36% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  4.09% { opacity: 1; transform: none; max-height: 6em; margin-block: var(--bmt, 0px) 0; }
  96% { opacity: 1; transform: none; max-height: 6em; }
  100% { opacity: 0; transform: translateY(-4px); max-height: 6em; }
}

@keyframes termLine2 {
  0% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  7.27% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  10% { opacity: 1; transform: none; max-height: 6em; margin-block: var(--bmt, 0px) 0; }
  96% { opacity: 1; transform: none; max-height: 6em; }
  100% { opacity: 0; transform: translateY(-4px); max-height: 6em; }
}

@keyframes termLine3 {
  0% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  10.91% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  13.64% { opacity: 1; transform: none; max-height: 6em; margin-block: var(--bmt, 0px) 0; }
  96% { opacity: 1; transform: none; max-height: 6em; }
  100% { opacity: 0; transform: translateY(-4px); max-height: 6em; }
}

@keyframes termLine4 {
  0% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  15.91% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  18.64% { opacity: 1; transform: none; max-height: 6em; margin-block: var(--bmt, 0px) 0; }
  96% { opacity: 1; transform: none; max-height: 6em; }
  100% { opacity: 0; transform: translateY(-4px); max-height: 6em; }
}

@keyframes termLine5 {
  0% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  19.54% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  22.27% { opacity: 1; transform: none; max-height: 6em; margin-block: var(--bmt, 0px) 0; }
  96% { opacity: 1; transform: none; max-height: 6em; }
  100% { opacity: 0; transform: translateY(-4px); max-height: 6em; }
}

@keyframes termLine6 {
  0% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  24.55% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  27.27% { opacity: 1; transform: none; max-height: 6em; margin-block: var(--bmt, 0px) 0; }
  96% { opacity: 1; transform: none; max-height: 6em; }
  100% { opacity: 0; transform: translateY(-4px); max-height: 6em; }
}

@keyframes termItem1 {
  0% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  28.19% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  30.91% { opacity: 1; transform: none; max-height: 6em; margin-block: var(--bmt, 0px) 0; }
  96% { opacity: 1; transform: none; max-height: 6em; }
  100% { opacity: 0; transform: translateY(-4px); max-height: 6em; }
}

@keyframes termItem2 {
  0% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  32.28% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  35% { opacity: 1; transform: none; max-height: 6em; margin-block: var(--bmt, 0px) 0; }
  96% { opacity: 1; transform: none; max-height: 6em; }
  100% { opacity: 0; transform: translateY(-4px); max-height: 6em; }
}

@keyframes termItem3 {
  0% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  36.37% { opacity: 0; transform: translateY(9px); max-height: 0; margin-block: 0; overflow: hidden; }
  39.1% { opacity: 1; transform: none; max-height: 6em; margin-block: var(--bmt, 0px) 0; }
  96% { opacity: 1; transform: none; max-height: 6em; }
  100% { opacity: 0; transform: translateY(-4px); max-height: 6em; }
}

.js .sc-term .tl, .js .sc-term .tlist li { opacity: 0; }
.js .cap.in.playing .sc-term .tl:nth-of-type(1) { animation: termLine1 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .sc-term .tl:nth-of-type(2) { animation: termLine2 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .sc-term .tl:nth-of-type(3) { animation: termLine3 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .sc-term .tl:nth-of-type(4) { animation: termLine4 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .sc-term .tl:nth-of-type(5) { animation: termLine5 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .sc-term .tl:nth-of-type(6) { animation: termLine6 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .sc-term .tlist li:nth-child(1) { animation: termItem1 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .sc-term .tlist li:nth-child(2) { animation: termItem2 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .sc-term .tlist li:nth-child(3) { animation: termItem3 11s var(--ease-out) 0s infinite both; }
@keyframes tgStep1 {
  0% { opacity: 0; transform: translateY(9px); }
  3.64% { opacity: 0; transform: translateY(9px); }
  6.37% { opacity: 1; transform: none; }
  96% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(-4px); }
}

@keyframes tgTyping {
  0% { opacity: 0; transform: translateY(9px); }
  13.64% { opacity: 0; transform: translateY(9px); }
  16.36% { opacity: 1; transform: none; }
  37.46% { opacity: 1; transform: none; }
  39.1%, 100% { opacity: 0; transform: none; }
}

@keyframes tgStep3 {
  0% { opacity: 0; transform: translateY(9px); }
  39.1% { opacity: 0; transform: translateY(9px); }
  41.82% { opacity: 1; transform: none; }
  96% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(-4px); }
}

@keyframes tgStep4 {
  0% { opacity: 0; transform: translateY(9px); }
  50.9% { opacity: 0; transform: translateY(9px); }
  53.63% { opacity: 1; transform: none; }
  96% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(-4px); }
}

.js .tg-msg, .js .tg-typing { opacity: 0; }
.js .togo-sec .cap.in.playing .tg-msg[data-step="1"] { animation: tgStep1 11s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap.in.playing .tg-typing[data-step="2"] { animation: tgTyping 11s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap.in.playing .tg-msg[data-step="3"] { animation: tgStep3 11s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap.in.playing .tg-msg[data-step="4"] { animation: tgStep4 11s var(--ease-out) 0s infinite both; }
@keyframes tgDot { 0%, 60%, 100% { transform: none; opacity: .55; } 30% { transform: translateY(-3.5px); opacity: 1; } }
.js .togo-sec .cap.in.playing .tg-typing i { animation: tgDot 1.05s ease-in-out infinite; }
.js .togo-sec .cap.in .tg-typing i:nth-child(2) { animation-delay: .16s; }
.js .togo-sec .cap.in .tg-typing i:nth-child(3) { animation-delay: .32s; }
@media (prefers-reduced-motion: reduce) {
  .js .cap-scene .u, .js .cap-scene .toolchip, .js .cap-scene .a,
  .js .cap-scene .choices, .js .cap-scene .idealist, .js .cap-scene .postmini,
  .js .cap-scene .critique, .js .cap-scene .chartcard,
  .js .sc-term .tl, .js .sc-term .tlist li, .js .tg-msg { opacity: 1; animation: none; }
  .js .tg-typing { display: none; }
  .js .cap.in.playing .spark .trace, .js .cap.in .spark .pts circle { animation: none; }
}

/* B4 — a real chat is anchored to the composer and fills upward; ours started
   at the top of a fixed-height frame and left ~250px of bare wallpaper under
   the last message. */
.tg-body { justify-content: flex-end; }

/* ============================================================
   HERO AGENT CYCLE — one clock, one window per logo.
   The `.playing` visibility gate raised this rule's specificity to
   (0,4,1), which put the `animation` SHORTHAND above the seven
   `animation-delay` rules at (0,3,1) — and the shorthand resets
   delay to 0s. Every logo then ran in unison: measured blank for
   88.5% of frames, with all seven marks stacked in a 45px box the
   rest of the time. Same root cause as the scene loops: a stagger
   expressed as `animation-delay` is fragile under specificity and
   cannot survive an infinite loop. Each logo now owns a window on
   a single 11.2s clock, so no delay is involved at all.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .js .agent-cycle img { animation: none; opacity: 0; }
  .js .agent-cycle img:first-child { opacity: 1; }
}

/* A-3 — the chart ran two sub-clocks inside a 15s card: chalkDraw on 1400ms
   and six ptPop dots on 300ms, all `infinite`. Six data points popped 200
   times a minute, forever, inside a container fading on a 15s cycle — a
   3.3Hz strobe. Folded onto the same clock as everything else. */
/* Declare opacity at EVERY stop. Leaving it out of the early stops meant the
   property had no 0% value to return to on each iteration, and the trace decayed
   monotonically across cycles (0.62 -> 0.17 -> 0.05 -> 0) instead of resetting. */
.js .cap.in.playing .spark .trace { animation: traceDraw15 11s var(--ease-out) 0s infinite both; }
@keyframes dotPop1 {
  0%, 8.59% { opacity: 0; transform: scale(.3); }
  10.77%, 100% { opacity: 1; transform: scale(1); }
}
@keyframes dotPop2 {
  0%, 9.41% { opacity: 0; transform: scale(.3); }
  11.59%, 100% { opacity: 1; transform: scale(1); }
}
@keyframes dotPop3 {
  0%, 10.23% { opacity: 0; transform: scale(.3); }
  12.41%, 100% { opacity: 1; transform: scale(1); }
}
@keyframes dotPop4 {
  0%, 11.05% { opacity: 0; transform: scale(.3); }
  13.23%, 100% { opacity: 1; transform: scale(1); }
}
@keyframes dotPop5 {
  0%, 11.86% { opacity: 0; transform: scale(.3); }
  14.05%, 100% { opacity: 1; transform: scale(1); }
}
@keyframes dotPop6 {
  0%, 12.68% { opacity: 0; transform: scale(.3); }
  14.86%, 100% { opacity: 1; transform: scale(1); }
}
.js .cap.in.playing .spark .pts circle:nth-child(1) { animation: dotPop1 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .spark .pts circle:nth-child(2) { animation: dotPop2 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .spark .pts circle:nth-child(3) { animation: dotPop3 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .spark .pts circle:nth-child(4) { animation: dotPop4 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .spark .pts circle:nth-child(5) { animation: dotPop5 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .spark .pts circle:nth-child(6) { animation: dotPop6 11s var(--ease-out) 0s infinite both; }
@media (prefers-reduced-motion: reduce) {
  .js .spark .trace { stroke-dashoffset: 0; animation: none; }
  .js .spark .pts circle { opacity: 1; animation: none; }
}

/* the 2,919 callout lands just after the last dot, on the same clock */
@keyframes peakIn15 {
  0%, 12.27% { opacity: 0; transform: scale(.3); }
  15%, 100% { opacity: 1; transform: scale(1); }
}
.js .cap.in.playing .spark .peak { animation: peakIn15 11s var(--ease-out) 0s infinite both; }
@media (prefers-reduced-motion: reduce) { .js .spark .peak { opacity: 1; animation: none; } }

/* The 4fr/7fr split only clears a 45ch measure from ~1000px up; between 901
   and 999 it lands at 40ch, worse than the 62ch it gets stacked. Stay stacked
   through that band. */
@media (max-width: 999px) {
  .builder-grid { grid-template-columns: 1fr; }
  .builder-photo { max-width: 460px; margin: 0 auto; }
}

/* B-5 — the terminal caret was the one infinite animation still running at the
   very bottom of a 14,000px page; it is gated on visibility like everything else. */
.sc-term .term-input .blink { animation: none; }
.js .cap.in.playing .sc-term .term-input .blink { animation: termBlink 1.15s steps(1, end) infinite; }

/* B-4 — the blog pull quote rendered at 29.6px, IDENTICAL to the h2 that
   follows it, in the same family and the same ink, with symmetric 45/44px
   margins so it belonged to neither section. Two same-size display objects
   44px apart is not a punctuation mark. Drop it below the heading, give it the
   softer ink, and close the section it quotes with asymmetric space. */
.post-pull { margin-block: 30px 58px; }
.post-pull blockquote {
  font-size: var(--step-1);
  color: var(--ink-soft);
  line-height: var(--lh-snug);
}
@media (max-width: 1099px) { .post-pull { margin-left: -12px; } }

/* Nine windows on one 14.4s clock, COMPLEMENTARY crossfade: logo N fades
   out over exactly the window logo N+1 fades in, so opacities always sum to
   ~1 and the mark never loses ink. All marks in full colour is the OWNER'S
   DECISION (2026-08-28). Nine slots because the markup carries nine logos
   (Hermes and OpenClaw joined 2026-09-10); a slot count below the img count
   leaves the extra logos permanently invisible. */
@keyframes agentCycle1 {
  0%, 9.51% { opacity: 1; }
  11.11%, 98.40% { opacity: 0; }
  100% { opacity: 1; }
}
@keyframes agentCycle2 {
  0%, 9.51% { opacity: 0; }
  11.11%, 20.62% { opacity: 1; }
  22.22%, 100% { opacity: 0; }
}
@keyframes agentCycle3 {
  0%, 20.62% { opacity: 0; }
  22.22%, 31.73% { opacity: 1; }
  33.33%, 100% { opacity: 0; }
}
@keyframes agentCycle4 {
  0%, 31.73% { opacity: 0; }
  33.33%, 42.84% { opacity: 1; }
  44.44%, 100% { opacity: 0; }
}
@keyframes agentCycle5 {
  0%, 42.84% { opacity: 0; }
  44.44%, 53.96% { opacity: 1; }
  55.56%, 100% { opacity: 0; }
}
@keyframes agentCycle6 {
  0%, 53.96% { opacity: 0; }
  55.56%, 65.07% { opacity: 1; }
  66.67%, 100% { opacity: 0; }
}
@keyframes agentCycle7 {
  0%, 65.07% { opacity: 0; }
  66.67%, 76.18% { opacity: 1; }
  77.78%, 100% { opacity: 0; }
}
@keyframes agentCycle8 {
  0%, 76.18% { opacity: 0; }
  77.78%, 87.29% { opacity: 1; }
  88.89%, 100% { opacity: 0; }
}
@keyframes agentCycle9 {
  0%, 87.29% { opacity: 0; }
  88.89%, 98.40% { opacity: 1; }
  100% { opacity: 0; }
}
.js .hero.playing .agent-cycle img:nth-child(1) { animation: agentCycle1 14.4s linear 0s infinite both; }
.js .hero.playing .agent-cycle img:nth-child(2) { animation: agentCycle2 14.4s linear 0s infinite both; }
.js .hero.playing .agent-cycle img:nth-child(3) { animation: agentCycle3 14.4s linear 0s infinite both; }
.js .hero.playing .agent-cycle img:nth-child(4) { animation: agentCycle4 14.4s linear 0s infinite both; }
.js .hero.playing .agent-cycle img:nth-child(5) { animation: agentCycle5 14.4s linear 0s infinite both; }
.js .hero.playing .agent-cycle img:nth-child(6) { animation: agentCycle6 14.4s linear 0s infinite both; }
.js .hero.playing .agent-cycle img:nth-child(7) { animation: agentCycle7 14.4s linear 0s infinite both; }
.js .hero.playing .agent-cycle img:nth-child(8) { animation: agentCycle8 14.4s linear 0s infinite both; }
.js .hero.playing .agent-cycle img:nth-child(9) { animation: agentCycle9 14.4s linear 0s infinite both; }

/* at full bleed the bar kept its 16px inset while the body moved to 72px, so
   the "Claude" label sat left of everything under it */

/* Draw immediately and STAY drawn. The old timing held the line undrawn for
   the first 2,500ms and erased it again every cycle, so the widest element on
   the page showed an empty axis grid 23% of the time — and on arrival. */
@keyframes traceDraw15 {
  0% { stroke-dashoffset: 1; opacity: 1; }
  12.27%, 100% { stroke-dashoffset: 0; opacity: 1; }
}
.js .cap.in.playing .spark .trace { animation: traceDraw15 11s var(--ease-out) 0s infinite both; }
@keyframes dotPop1 {
  0%, 8.59% { opacity: 0; transform: scale(.3); }
  10.77%, 100% { opacity: 1; transform: scale(1); }
}
@keyframes dotPop2 {
  0%, 9.41% { opacity: 0; transform: scale(.3); }
  11.59%, 100% { opacity: 1; transform: scale(1); }
}
@keyframes dotPop3 {
  0%, 10.23% { opacity: 0; transform: scale(.3); }
  12.41%, 100% { opacity: 1; transform: scale(1); }
}
@keyframes dotPop4 {
  0%, 11.05% { opacity: 0; transform: scale(.3); }
  13.23%, 100% { opacity: 1; transform: scale(1); }
}
@keyframes dotPop5 {
  0%, 11.86% { opacity: 0; transform: scale(.3); }
  14.05%, 100% { opacity: 1; transform: scale(1); }
}
@keyframes dotPop6 {
  0%, 12.68% { opacity: 0; transform: scale(.3); }
  14.86%, 100% { opacity: 1; transform: scale(1); }
}
.js .cap.in.playing .spark .pts circle:nth-child(1) { animation: dotPop1 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .spark .pts circle:nth-child(2) { animation: dotPop2 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .spark .pts circle:nth-child(3) { animation: dotPop3 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .spark .pts circle:nth-child(4) { animation: dotPop4 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .spark .pts circle:nth-child(5) { animation: dotPop5 11s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .spark .pts circle:nth-child(6) { animation: dotPop6 11s var(--ease-out) 0s infinite both; }
@media (prefers-reduced-motion: reduce) {
  .js .spark .trace { stroke-dashoffset: 0; animation: none; }
  .js .spark .pts circle { opacity: 1; animation: none; }
}

/* the 2,919 callout lands just after the last dot, on the same clock */
@keyframes peakIn15 {
  0%, 12.27% { opacity: 0; transform: scale(.3); }
  15%, 100% { opacity: 1; transform: scale(1); }
}
.js .cap.in.playing .spark .peak { animation: peakIn15 11s var(--ease-out) 0s infinite both; }
@media (prefers-reduced-motion: reduce) { .js .spark .peak { opacity: 1; animation: none; } }

/* The 4fr/7fr split only clears a 45ch measure from ~1000px up; between 901
   and 999 it lands at 40ch, worse than the 62ch it gets stacked. Stay stacked
   through that band. */
@media (max-width: 999px) {
  .builder-grid { grid-template-columns: 1fr; }
  .builder-photo { max-width: 460px; margin: 0 auto; }
}

/* B-5 — the terminal caret was the one infinite animation still running at the
   very bottom of a 14,000px page; it is gated on visibility like everything else. */
.sc-term .term-input .blink { animation: none; }
.js .cap.in.playing .sc-term .term-input .blink { animation: termBlink 1.15s steps(1, end) infinite; }

/* B-4 — the blog pull quote rendered at 29.6px, IDENTICAL to the h2 that
   follows it, in the same family and the same ink, with symmetric 45/44px
   margins so it belonged to neither section. Two same-size display objects
   44px apart is not a punctuation mark. Drop it below the heading, give it the
   softer ink, and close the section it quotes with asymmetric space. */
.post-pull { margin-block: 30px 58px; }
.post-pull blockquote {
  font-size: var(--step-1);
  color: var(--ink-soft);
  line-height: var(--lh-snug);
}
@media (max-width: 1099px) { .post-pull { margin-left: -12px; } }

/* Seven windows on one 11.2s clock, CROSSFADED. The first build made them
   strictly sequential: logo N reached 0 before logo N+1 began, so the glyph
   was measurably empty for ~285ms at every handoff — seven holes per cycle in
   the mark that carries the whole "inside your AI agent" claim. Each logo now
   fades in across its predecessor's fade-out, so something is always on. */
.js .mock-composer::after { animation: none; }
.js .connect-sec.playing .mock-composer::after { animation: caret 1.2s steps(1, end) infinite; }
@media (prefers-reduced-motion: reduce) {
  .js .cap *, .js .cap-scene *, .js .tgphone *, .js .agent-cycle img,
  .js .mock-composer::after, .js .spark * {
    animation: none !important;
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .js .agent-cycle img { opacity: 0 !important; }
  .js .agent-cycle img:first-child { opacity: 1 !important; }
  .js .tg-typing { display: none !important; }
  .js .spark .trace { stroke-dashoffset: 0 !important; }
}

/* B-5 — `.tab-ind` is only built when JS runs AND motion is allowed
   (`js/home.js`: `if (cxTabs.length && !reduce)`), but the CSS at :1363 strips
   the selected tab's chip unconditionally on the `.js` class. Under
   prefers-reduced-motion that left the ACTIVE agent looking like the disabled
   one and the inactive tabs looking chosen — the state read inverted. Restore
   a static selected treatment when there is no indicator to do the job. */
@media (prefers-reduced-motion: reduce) {
  .js .agent-tab[aria-selected="true"] {
    background: var(--sheet);
    border-color: var(--clay);
    box-shadow: inset 0 -2px 0 var(--clay);
  }
}

/* B-4 — the CTA band, the agents note and the footer were all --walnut, so the
   final ask had no edge and merged into the footer as one 2,900px slab. Step
   the tone down twice so the close reads as its own panel and the footer sits
   beneath it. */
.agents-note { background: #1c1813; }
footer.site { background: #17140f; }
.sec.cta-band { border-bottom: 1px solid rgba(242,236,221,0.10); }

/* Seven windows on one 11.2s clock. The overlap is deliberately short: a 2.79%
   crossfade left both marks near half opacity for ~160ms at each handoff, which
   reads as the glyph flickering rather than dissolving. 1.1% is ~123ms. */

/* A-3 — every surface on this site is warm (#fdfaf3 / #f6f2e8 / #efe9db); the
   ChatGPT scene was pure #ffffff, which read as a pasted screenshot rather than
   a designed surface — and it is the widest object in the middle third. */
.cap-scene.sc-gpt, .sc-gpt .scene-bar, .sc-gpt .scene-body { background: var(--sheet); }
.sc-gpt .postmini, .sc-gpt .critique, .sc-gpt .choice { background: var(--canvas); }

/* B-2 — below 1100 the outdent was a flat 12px, invisible against a 674px
   column. Make it read as deliberate at every width. */
@media (max-width: 1099px) { .post-pull { margin-left: -26px; } }
@media (max-width: 640px)  { .post-pull { margin-left: -14px; } }

/* R31 #1: a full seven-slot duplicate of the agentCycle block lived here
   and, being later, WON the cascade over the six-slot set above — so with
   six logos the headline ended in an empty square for 12.7%% of every loop.
   The six-window 9.6s set higher in the file is the only one now. */

/* at full bleed the bar kept its 16px inset while the body moved to 72px, so
   the "Claude" label sat left of everything under it */

/* A2/B1 — the erase was moved, not fixed. `stroke-dashoffset` now holds, but
   the PARENT sceneStep keyframes still drive the chartcard to opacity 0 from
   88% to the next cycle's 10.33% — 2.45s of the page's largest element being
   literally empty, every 15s. A proof number must never blink out. The Results
   scene plays ONCE per entry and holds; `js/home.js` re-arms it on re-entry by
   toggling .playing, so it replays when you scroll back rather than on a timer
   you can arrive in the middle of. */

/* A3 — SVG type and stroke scale with the viewBox, so the chart had no fixed
   optical size: axis labels measured 3.93px at 390 (unreadable) and 22.9px at
   1920 (larger than the body copy beside them); the trace was a 0.86px hairline
   on a phone. Widening the box 320->800 made mobile 2.5x worse. Pin the stroke,
   and set the type per breakpoint so it lands at a constant ~11-13px rendered. */
/* The trace CANNOT use non-scaling-stroke: it is drawn with a normalised dash
   (pathLength="1" + stroke-dasharray:1), and Chrome resolves the dash in user
   space while stroking in screen space. The two disagree by exactly the viewport
   scale factor, so at 1440px (scale 1.561) the line stopped at 1/1.561 = 64% of
   its length and the last two data points floated unconnected — on the one chart
   whose whole job is to show a number climbing. Verified: forcing dashoffset to
   0 did NOT complete it; removing the dash did. So pin the optical stroke the
   same way the labels below are pinned — per breakpoint, in user units, back-
   solved from the measured scale (0.894 at 390, ~1.30-1.58 desktop, 2.36 at 900). */
.spark .trace { stroke-width: 1.3; }
@media (max-width: 619px)  { .spark .trace { stroke-width: 2.2; } }
@media (min-width: 620px) and (max-width: 900px) { .spark .trace { stroke-width: 1; } }
.spark .pts circle { vector-effect: non-scaling-stroke; }
/* font-size inside a viewBox is in USER UNITS, so it scales with the box —
   which is why a single value rendered 9.3px at 390 and 20.8px at 1920. These
   values are back-solved from the measured scale factor at each breakpoint so
   the labels land at a constant ~12-13px rendered. */

@media (min-width: 620px) and (max-width: 900px) {
  .spark .ax text, .spark .hrs text { font-size: 15px; }
  .spark .peak text { font-size: 20px; }
}

/* A scroll-scrubbed draw was built here and REMOVED. The intent was right —
   progress tied to scroll position can never be arrived at mid-cycle — but the
   named view-timeline would not drive in testing: the trace froze at 99% drawn
   and the "2,919" callout sat at 0.34 opacity at every scroll position. Play-
   once-and-hold is verified working (9 blank frames in 2037 sampled, and it
   re-arms on re-entry via .playing), so it stays. Shipping animation code whose
   behaviour I could not verify, in place of behaviour I could, is the wrong
   trade — especially when the failure mode leaves the number half-transparent.
*/

/* ============================================================
   STRATEGY SCENE — the sequence a real session actually has.
   Before: the three ICP checkboxes were already ticked when the
   scene faded in, which cannot happen — Claude asks the question,
   THEN the user picks. And the whole thing ran at a 550ms stagger,
   too fast to read a message before the next arrived.
   Now, on one 17s clock:
     ask -> pointer travels -> click -> tick (x3) -> user replies
     -> thinking -> the strategy lands, and holds.
   Percentages are seconds/17 so the beats are legible as timings.
   ============================================================ */
.strategy-flow .choices { position: relative; }
.strategy-flow .ptr {
  position: absolute; left: 0; top: 0; width: 17px; height: 22px; z-index: 3;
  opacity: 0; pointer-events: none; will-change: transform;
  /* a real macOS-ish arrow, drawn so it needs no asset */
  background: var(--ink);
  clip-path: polygon(0 0, 0 78%, 26% 60%, 44% 100%, 62% 91%, 45% 54%, 78% 52%);
  filter: drop-shadow(0 1px 2px rgba(34,26,16,.45));
}
/* the chips start OFF; each turns on only as the pointer reaches it */
.strategy-flow .choice.pick { color: var(--ink-mute); border-color: var(--hairline); background: var(--canvas); }
.strategy-flow .choice.pick .box { background: none; border-color: var(--hairline-strong); color: transparent; }

@keyframes ptrRun {
  0%, 18.86%   { opacity: 0; transform: translate(var(--p0x, 49.41%), var(--p0y, 120%)); }
  20.84%       { opacity: 1; transform: translate(var(--p0x, 49.41%), var(--p0y, 120%)); }
  24.71%       { opacity: 1; transform: translate(var(--p1x), var(--p1y)); }
  26.19%       { opacity: 1; transform: translate(var(--p1x), var(--p1y)) scale(.82); }  /* click */
  27.59%       { opacity: 1; transform: translate(var(--p1x), var(--p1y)); }
  31.95%       { opacity: 1; transform: translate(var(--p2x), var(--p2y)); }
  33.44%       { opacity: 1; transform: translate(var(--p2x), var(--p2y)) scale(.82); }
  34.92%       { opacity: 1; transform: translate(var(--p2x), var(--p2y)); }
  39.2%       { opacity: 1; transform: translate(var(--p3x), var(--p3y)); }
  40.68%       { opacity: 1; transform: translate(var(--p3x), var(--p3y)) scale(.82); }
  42.16%       { opacity: 1; transform: translate(var(--p3x), var(--p3y)); }
  46.04%       { opacity: 0; transform: translate(var(--p3x), calc(var(--p3y) + 26px)); }
  100%        { opacity: 0; transform: translate(var(--p0x, 49.41%), var(--p0y, 120%)); }
}
/* each chip flips at the frame the pointer presses it */
@keyframes chip1 { 0%, 26.11% { color: var(--ink-mute); border-color: var(--hairline); background: var(--canvas); } 27.18%, 100% { color: var(--ink); border-color: rgba(178,94,73,.4); background: var(--clay-wash); } }
@keyframes chip2 { 0%, 33.35% { color: var(--ink-mute); border-color: var(--hairline); background: var(--canvas); } 34.42%, 100% { color: var(--ink); border-color: rgba(178,94,73,.4); background: var(--clay-wash); } }
@keyframes chip3 { 0%, 40.6% { color: var(--ink-mute); border-color: var(--hairline); background: var(--canvas); } 41.67%, 100% { color: var(--ink); border-color: rgba(178,94,73,.4); background: var(--clay-wash); } }
@keyframes box1 { 0%, 26.11% { background: none; border-color: var(--hairline-strong); color: transparent; transform: scale(1); } 26.68% { transform: scale(1.28); } 27.18%, 100% { background: var(--clay); border-color: var(--clay); color: #2b1509; transform: scale(1); } }
@keyframes box2 { 0%, 33.35% { background: none; border-color: var(--hairline-strong); color: transparent; transform: scale(1); } 33.93% { transform: scale(1.28); } 34.42%, 100% { background: var(--clay); border-color: var(--clay); color: #2b1509; transform: scale(1); } }
@keyframes box3 { 0%, 40.6% { background: none; border-color: var(--hairline-strong); color: transparent; transform: scale(1); } 41.18% { transform: scale(1.28); } 41.67%, 100% { background: var(--clay); border-color: var(--clay); color: #2b1509; transform: scale(1); } }

/* the beats, in seconds of a 17s cycle */
@keyframes beatIn {
  0% { opacity: 0; transform: translateY(9px); }
  100% { opacity: 1; transform: none; }
}
.js .cap.in.playing .strategy-flow > [data-beat] { opacity: 0; }
.js .cap.in.playing .strategy-flow > [data-beat="1"] { animation: sBeat1 17s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .strategy-flow > [data-beat="2"] { animation: sBeat2 17s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .strategy-flow > [data-beat="3"] { animation: sBeat3 17s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .strategy-flow > [data-beat="4"] { animation: sBeat4 17s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .strategy-flow > [data-beat="5"] { animation: sBeat5 17s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .strategy-flow > [data-beat="6"] { animation: sBeat6 17s linear 0s infinite both; }
.js .cap.in.playing .strategy-flow > [data-beat="7"] { animation: sBeat7 17s var(--ease-out) 0s infinite both; }
@keyframes sBeat1 { 0% { opacity:0; transform:translateY(9px); max-height:0; margin-block:0; overflow:hidden; } 1.98%, 92% { opacity:1; transform:none; max-height: 9em; margin-block: var(--bmt, 0px) 0; padding-block: var(--bpt, 0px) var(--bpb, 0px); } 96%,100% { opacity:0; max-height:9em; } }
@keyframes sBeat2 { 0%,4.86% { opacity:0; transform:translateY(9px); max-height:0; margin-block:0; overflow:hidden; } 6.75%, 92% { opacity:1; transform:none; max-height: 9em; margin-block: var(--bmt, 0px) 0; padding-block: var(--bpt, 0px) var(--bpb, 0px); } 96%,100% { opacity:0; max-height:9em; } }
@keyframes sBeat3 { 0%,10.62% { opacity:0; transform:translateY(9px); max-height:0; margin-block:0; overflow:hidden; } 12.6%, 92% { opacity:1; transform:none; max-height: 9em; margin-block: var(--bmt, 0px) 0; padding-block: var(--bpt, 0px) var(--bpb, 0px); } 96%,100% { opacity:0; max-height:9em; } }
@keyframes sBeat4 { 0%,15.48% { opacity:0; transform:translateY(9px); max-height:0; margin-block:0; overflow:hidden; } 17.46%, 92% { opacity:1; transform:none; max-height: 12em; margin-block: var(--bmt, 0px) 0; padding-block: var(--bpt, 0px) var(--bpb, 0px); } 96%,100% { opacity:0; max-height:12em; } }
@keyframes sBeat5 { 0%,47.44% { opacity:0; transform:translateY(9px); max-height:0; margin-block:0; overflow:hidden; } 49.41%, 92% { opacity:1; transform:none; max-height: 9em; margin-block: var(--bmt, 0px) 0; padding-block: var(--bpt, 0px) var(--bpb, 0px); } 96%,100% { opacity:0; max-height:9em; } }
@keyframes sBeat6 { 0%,52.29% { opacity:0; max-height:0; margin-block:0; overflow:hidden; } 54.27%, 61.02% { opacity:1; max-height: 4em; margin-block: var(--bmt, 0px) 0; padding-block: var(--bpt, 0px) var(--bpb, 0px); } 64.24%,100% { opacity:0; max-height:0; margin-block:0; overflow:hidden; } }
@keyframes sBeat7 { 0%,63% { opacity:0; transform:translateY(9px); max-height:0; margin-block:0; overflow:hidden; } 65.06%, 97% { opacity:1; transform:none; max-height: 12em; margin-block: var(--bmt, 0px) 0; padding-block: var(--bpt, 0px) var(--bpb, 0px); } 100% { opacity:0; max-height:12em; } }

.js .cap.in.playing .strategy-flow .ptr { animation: ptrRun 17s var(--ease-soft) 0s infinite both; }
.js .cap.in.playing .strategy-flow .choice.pick:nth-of-type(1) { animation: chip1 17s linear 0s infinite both; }
.js .cap.in.playing .strategy-flow .choice.pick:nth-of-type(2) { animation: chip2 17s linear 0s infinite both; }
.js .cap.in.playing .strategy-flow .choice.pick:nth-of-type(4) { animation: chip3 17s linear 0s infinite both; }
.js .cap.in.playing .strategy-flow .choice.pick:nth-of-type(1) .box { animation: box1 17s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .strategy-flow .choice.pick:nth-of-type(2) .box { animation: box2 17s var(--ease-out) 0s infinite both; }
.js .cap.in.playing .strategy-flow .choice.pick:nth-of-type(4) .box { animation: box3 17s var(--ease-out) 0s infinite both; }

/* the thinking dots between the user's reply and the strategy */
.strategy-flow .thinking {
  align-self: flex-start; display: inline-flex; align-items: center; gap: 4px;
  background: var(--canvas); border: 1px solid var(--hairline); border-radius: 12px;
  padding: 10px 13px; width: fit-content;
}
.strategy-flow .thinking i { width: 5px; height: 5px; border-radius: 50%; background: var(--ink-mute); }
.js .cap.in.playing .strategy-flow .thinking i { animation: tgDot 1.05s ease-in-out infinite; }
.js .cap.in.playing .strategy-flow .thinking i:nth-child(2) { animation-delay: .16s; }
.js .cap.in.playing .strategy-flow .thinking i:nth-child(3) { animation-delay: .32s; }

@media (prefers-reduced-motion: reduce) {
  .js .strategy-flow > [data-beat] { opacity: 1 !important; animation: none !important; transform: none !important; }
  /* R30 #5: hiding the pointer here deleted the scene's whole idea on
     phones. placePointer measures live rects, so it works at any width. */
  .js .strategy-flow .thinking { display: none; }
  .js .strategy-flow .choice.pick { color: var(--ink); border-color: rgba(178,94,73,.4); background: var(--clay-wash); }
  .js .strategy-flow .choice.pick .box { background: var(--clay); border-color: var(--clay); color: #2b1509; }
}

/* ============================================================
   PHONE GEOMETRY — a real iPhone 17 Pro, not a shrunk rectangle.
   The mock was 310 x 430+ (about 1.4:1), which reads as a squat
   toy. A 6.3" Pro is 1206 x 2622 device px = 2.174:1, so at 300px
   wide the screen must be 652px tall. Titanium rail, thin uniform
   bezel, 48px screen radius, Dynamic Island cut INTO the screen
   rather than floating on the app's header.
   ============================================================ */
.tgphone {
  width: min(300px, 100%);
  padding: 10px;
  border-radius: 54px;
  background: linear-gradient(150deg, #4a4238 0%, #211b13 28%, #171209 55%, #3a332a 100%);
  box-shadow:
    0 0 0 1px rgba(20,15,8,.55),
    inset 0 0 0 1px rgba(255,244,224,.10),
    0 2px 6px rgba(34,26,16,.24),
    0 34px 66px -26px rgba(34,26,16,.62);
}
.tg-screen {
  border-radius: 46px;
  aspect-ratio: 1206 / 2622;
  min-height: 0;
  overflow: hidden;
}
/* the island sits over the screen's own top inset, as on the device */
.tg-island {
  top: 12px; width: 88px; height: 26px; border-radius: 999px;
  background: #0a0806;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.05);
}
.tg-top { padding: 44px 13px 9px; }
/* a real phone fills its screen; the thread grows from the composer up */
.tg-body { justify-content: flex-end; padding-bottom: 10px; }
.tg-compose { padding: 9px 11px 22px; }
/* home indicator */
.tg-screen::after {
  content: ''; position: absolute; left: 50%; bottom: 8px; transform: translateX(-50%);
  width: 108px; height: 4px; border-radius: 999px; background: rgba(20,15,8,.30);
}
@media (max-width: 900px) { .tgphone { width: min(272px, 84%); } }

/* Oded picked the ink option: the dark bands drop their brown cast for a
   warm-tinted near-black, so the clay accent carries the colour instead. */
.sec.cta-band { background: #241f18; }
.receipts-sec { background: #241f18; }
section.sec#faq { background: #241f18; }
.pcard.featured { background: #241f18; border-color: #241f18; }
.scene-body .u, .mock-body .u { background: #241f18; }
.sc-term { background: #14110d; border-color: #2b2620; }
.sc-term .term-bar { background: #1c1813; border-bottom-color: #2b2620; }
.love-add { background: #241f18; border-color: #241f18; }

/* ============================================================
   TELEGRAM — rebuilt from Oded's actual screenshots.
   Corrections against what was mocked before:
     - the wallpaper is Telegram's GREEN doodle, not tan
     - the news radar is one bubble: newspaper emoji + headline,
       a "→ why this matters" line, the raw URL, "Tap below to add
       your take", then a link-preview card with a purple rule
     - the Add-take button is a separate olive inline-keyboard bar
       UNDER the message, not part of the bubble
     - a saved seed echoes the opening of the transcript and cuts
       off MID-WORD — the detail Oded specifically liked
     - the loop ends with the published post coming back as a link
   ============================================================ */
.tg-screen {
  background: linear-gradient(165deg, #cfe0a8 0%, #b9d492 42%, #a9c98a 100%);
  background-image:
    radial-gradient(circle at 18% 12%, rgba(255,255,255,.20) 0 9px, transparent 10px),
    radial-gradient(circle at 68% 30%, rgba(120,150,90,.16) 0 11px, transparent 12px),
    radial-gradient(circle at 38% 62%, rgba(255,255,255,.16) 0 8px, transparent 9px),
    radial-gradient(circle at 84% 78%, rgba(120,150,90,.14) 0 10px, transparent 11px),
    linear-gradient(165deg, #cfe0a8 0%, #b9d492 42%, #a9c98a 100%);
  background-size: 104px 104px, 132px 132px, 118px 118px, 126px 126px, 100% 100%;
}
.tg-msg.in  { background: #fff; }
.tg-msg.out { background: #e6f7d2; }
.tg-date { background: rgba(255,255,255,.55); color: #46503a; }

/* the news bubble */
.tg-news { max-width: 94%; display: flex; flex-direction: column; gap: 5px; padding-bottom: 17px; }
.tgn-emoji { margin-right: 5px; }
.tgn-head { font-weight: 500; color: #1d1a16; line-height: 1.34; }
.tgn-why  { color: #2f2b25; line-height: 1.4; }
.tgn-link { color: #2a6fa8; text-decoration: underline; word-break: break-all; line-height: 1.35; }
.tgn-tap  { color: #2f2b25; }

/* the link preview: a purple rule, source, title, description */
.tg-preview {
  display: flex; flex-direction: column; gap: 3px;
  border-left: 3px solid #8b7fd4; background: #f4f1fd;
  border-radius: 0 6px 6px 0; padding: 7px 9px; margin-top: 3px;
}
.tgp-src   { color: #5f52a8; font-weight: 600; font-size: 11.4px; }
.tgp-title { font-weight: 600; color: #1d1a16; line-height: 1.32; font-size: 12px; }
.tgp-desc  { color: #3a352d; line-height: 1.38; font-size: 11.4px; }

/* the inline-keyboard button under the message */
.tg-btn {
  align-self: flex-start; width: 94%;
  background: #5f7a2b; color: #fff;
  border-radius: 9px; padding: 9px 11px;
  font-size: 12px; font-weight: 500; line-height: 1.3;
  display: flex; align-items: center; gap: 7px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  box-shadow: 0 1px 1px rgba(34,26,16,.16);
}
.tgb-ico { flex: none; }

/* the echoed transcript opening, cut off mid-word */
.tg-echo {
  display: block; margin-top: 9px; color: #3a352d; line-height: 1.42;
}

/* voice note: duration + unplayed dot, as Telegram draws it */
.tg-vmeta { display: inline-flex; align-items: center; gap: 5px; flex: none; }
.tg-unplayed { width: 5px; height: 5px; border-radius: 50%; background: #5eb257; }
.tg-play { background: #4fae4e; }
.tg-wave i { background: #79b96a; }
.tg-wave i:nth-child(-n+11) { background: #3f9c3e; }

/* the news thread is taller than the seed thread — let each phone size itself */
.togo-sec .cap:nth-of-type(2) .tg-body { justify-content: flex-start; padding-top: 10px; }

/* ============================================================
   TELEGRAM CHROME — rebuilt against Oded's two reference photos
   (IMG_8E5DA234 header, IMG_C8FDF93B composer). What was wrong:
   the header was a SOLID opaque bar with the title left-aligned
   beside the avatar, which is the Android/desktop layout. Telegram
   on current iOS floats three separate translucent capsules over
   the wallpaper — back+unread on the left, a wide centred
   name/type pill, and the avatar as its own circle on the right.
   The composer is the same idea: a round attach button, a wide
   field pill with the sticker glyph inside its right edge, and a
   round mic button — all floating, all showing wallpaper between
   them. The old flat bar also clipped the mic against the screen
   edge, which is what read as "cut off".
   ============================================================ */
.tg-screen { padding-top: 0; }

/* --- iOS status bar. The island is real estate the OS flanks. --- */
.tg-status {
  position: absolute; top: 13px; left: 0; right: 0; z-index: 5;
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 22px; pointer-events: none;
  font-family: var(--sans); font-size: 11px; font-weight: 600;
  color: #22261c; letter-spacing: .01em;
}
.tg-status .tg-batt {
  display: inline-flex; align-items: center; gap: 3px;
}
.tg-status .tg-bars { display: inline-flex; align-items: flex-end; gap: 1.5px; height: 8px; }
.tg-status .tg-bars i { width: 2.5px; background: currentColor; border-radius: .5px; }
.tg-status .tg-bars i:nth-child(1) { height: 3px; }
.tg-status .tg-bars i:nth-child(2) { height: 4.5px; }
.tg-status .tg-bars i:nth-child(3) { height: 6px; }
.tg-status .tg-bars i:nth-child(4) { height: 8px; }
.tg-status .tg-cell {
  width: 15px; height: 8px; border: 1.2px solid currentColor; border-radius: 2.5px;
  position: relative;
}
.tg-status .tg-cell::before {
  content: ''; position: absolute; inset: 1.4px; right: 4.5px;
  background: currentColor; border-radius: 1px;
}
.tg-status .tg-cell::after {
  content: ''; position: absolute; right: -2.6px; top: 2.4px;
  width: 1.4px; height: 3px; border-radius: 0 1px 1px 0; background: currentColor; opacity: .55;
}

/* --- header: three floating capsules, not a bar --- */
.tg-top {
  background: none; border-bottom: 0;
  padding: 40px 9px 6px;
  gap: 6px; align-items: center;
}
.tg-cap {
  background: rgba(252,253,250,.82);
  -webkit-backdrop-filter: blur(9px); backdrop-filter: blur(9px);
  box-shadow: 0 1px 3px rgba(28,40,18,.14), inset 0 0 0 .5px rgba(255,255,255,.6);
  border-radius: 999px;
}
.tg-back {
  /* was a bare chevron; the real one is a capsule holding the chevron
     AND the unread-count badge from the chat list you came from */
  width: auto; height: auto; transform: none; border: 0; margin: 0;
  display: inline-flex; align-items: center; gap: 4px;
  padding: 4px 7px 4px 6px; flex: none;
}
.tg-back .chev {
  width: 6px; height: 6px; flex: none;
  border-left: 1.7px solid #2b2f26; border-bottom: 1.7px solid #2b2f26;
  transform: rotate(45deg);
}
.tg-back .badge {
  background: #22261c; color: #fff; border-radius: 999px;
  font-size: 9.5px; font-weight: 600; line-height: 1; padding: 3px 6px;
}
.tg-who {
  flex: 1; min-width: 0; align-items: center; text-align: center;
  padding: 3px 10px 4px; gap: 0;
}
.tg-who b { font-size: 12.5px; font-weight: 700; color: #1a1d16; }
.tg-who i { font-size: 10px; color: #6b7365; }
.tg-av {
  width: 30px; height: 30px; padding: 0;
  box-shadow: 0 1px 3px rgba(28,40,18,.16), inset 0 0 0 1px rgba(196,110,84,.5);
  background: rgba(252,246,242,.9);
}

/* --- bubble tails. Telegram's most recognisable trait. --- */
.tg-msg.in, .tg-msg.out { border-radius: 13px; }
.tg-msg.in  { border-bottom-left-radius: 4px; }
.tg-msg.out { border-bottom-right-radius: 4px; }
.tg-msg.in::after, .tg-msg.out::after {
  content: ''; position: absolute; bottom: 0; width: 9px; height: 13px;
}
.tg-msg.in::after {
  left: -6px;
  background: #fff;
  -webkit-mask: radial-gradient(circle at 100% 0, transparent 9px, #000 9.5px);
          mask: radial-gradient(circle at 100% 0, transparent 9px, #000 9.5px);
  border-bottom-left-radius: 9px;
}
.tg-msg.out::after {
  right: -6px;
  background: #e6f7d2;
  -webkit-mask: radial-gradient(circle at 0 0, transparent 9px, #000 9.5px);
          mask: radial-gradient(circle at 0 0, transparent 9px, #000 9.5px);
  border-bottom-right-radius: 9px;
}

/* --- composer: two round glass buttons flanking a field pill --- */
.tg-compose {
  background: none; border-top: 0;
  padding: 8px 10px 20px; gap: 7px; align-items: center;
}
.tg-clip, .tg-mic {
  width: 30px; height: 30px; flex: none; opacity: 1; border: 0; border-radius: 999px;
  background: rgba(252,253,250,.82);
  -webkit-backdrop-filter: blur(9px); backdrop-filter: blur(9px);
  box-shadow: 0 1px 3px rgba(28,40,18,.14), inset 0 0 0 .5px rgba(255,255,255,.6);
  position: relative;
}
/* paperclip */
.tg-clip::before {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 8px; height: 14px; margin: -7px 0 0 -4px;
  border: 1.5px solid #4a5145; border-radius: 999px;
  transform: rotate(38deg);
}
.tg-clip::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 4px; height: 9px; margin: -5px 0 0 -2px;
  border: 1.5px solid #4a5145; border-top: 0; border-radius: 0 0 999px 999px;
  transform: rotate(38deg);
}
/* microphone */
.tg-mic::before {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 6px; height: 9px; margin: -7px 0 0 -3px;
  background: #4a5145; border-radius: 999px;
}
.tg-mic::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 11px; height: 8px; margin: -1px 0 0 -5.5px;
  border: 1.5px solid #4a5145; border-top: 0; border-radius: 0 0 999px 999px;
}
.tg-field {
  flex: 1; min-width: 0; display: flex; align-items: center; justify-content: space-between;
  padding: 7px 11px; border-radius: 999px;
  font-size: 12px; color: #6b7365;
  background: rgba(252,253,250,.72);
  -webkit-backdrop-filter: blur(9px); backdrop-filter: blur(9px);
  box-shadow: inset 0 0 0 .5px rgba(255,255,255,.6), 0 1px 3px rgba(28,40,18,.12);
}
.tg-sticker {
  width: 13px; height: 13px; flex: none; border-radius: 50%;
  border: 1.4px solid #7b8375; position: relative; overflow: hidden;
}
.tg-sticker::after {
  content: ''; position: absolute; right: -3px; bottom: -3px;
  width: 8px; height: 8px; border-radius: 2px 0 0 0; background: #7b8375; opacity: .55;
}
/* the home indicator is drawn on ::after of the screen; keep it clear of the pills */
.tg-screen::after { bottom: 7px; background: rgba(28,40,18,.42); }

/* The squadron caption collided with the lead plane's nose: the crop
   (object-position 32%) pushes the formation right as the band widens,
   and the caption is anchored to the right gutter, so the two closed on
   each other. Pull the crop left at desktop widths so the empty third the
   photograph was composed to leave actually stays empty, and hold the
   caption off the artwork with its own guaranteed gutter. */
@media (min-width: 901px) {
  .squadron-band img { object-position: 22% center; }
  .squadron-band .sq-line { max-width: 26ch; }
}
@media (min-width: 1400px) {
  .squadron-band img { object-position: 16% center; }
}

/* ============================================================
   PHOTO-SEED THREAD (fig. 03) + a real step clock for every
   child of a thread, not only .tg-msg. The inline-keyboard bar
   was never in the animation selector, so in the news thread the
   "Add take" button was on screen BEFORE the message it belongs
   to — a button for a message that had not arrived.
   ============================================================ */
.js .tg-body > [data-step] { opacity: 0; }
@keyframes tgStep2 {
  0% { opacity: 0; transform: translateY(9px); }
  19.09% { opacity: 0; transform: translateY(9px); }
  21.82% { opacity: 1; transform: none; }
  96% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(-4px); }
}
@keyframes tgStep5 {
  0% { opacity: 0; transform: translateY(9px); }
  68.18% { opacity: 0; transform: translateY(9px); }
  52% { opacity: 1; transform: none; }
  96% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(-4px); }
}
.js .togo-sec .cap.in.playing .tg-body > [data-step="1"] { animation: tgStep1 11s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap.in.playing .tg-body > [data-step="2"] { animation: tgStep2 11s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap.in.playing .tg-body > [data-step="3"] { animation: tgStep3 11s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap.in.playing .tg-body > [data-step="4"] { animation: tgStep4 11s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap.in.playing .tg-body > [data-step="5"] { animation: tgStep5 11s var(--ease-out) 0s infinite both; }
/* the typing indicator keeps its own shorter window — it must DISAPPEAR when
   the reply lands, which none of the tgStep curves do */
.js .togo-sec .cap.in.playing .tg-typing[data-step="2"] { animation: tgTyping 11s var(--ease-out) 0s infinite both; }
@media (prefers-reduced-motion: reduce) {
  .js .tg-body > [data-step] { opacity: 1; animation: none; }
}

/* The shared screenshot. This was an abstracted grey skeleton card, which
   Oded correctly read as not looking like an uploaded image at all. It is now
   the actual screenshot from his thread, rendered the size Telegram renders a
   photo in a phone-width bubble — recognisably a LinkedIn post, with the body
   far too small to function as a republication of anyone's writing. The author
   row is softened so no third party is identifiable at any zoom. */
.tg-photo { padding: 4px 4px 15px; max-width: 62%; }
.tg-photo img {
  display: block; width: 100%; height: auto; border-radius: 10px;
  max-height: 132px; object-fit: cover; object-position: top center;
}
/* .tg-msg.out .tg-meta (0,3,0) was beating .tg-photo .tg-meta (0,2,0), so the
   "white" timestamp actually rendered GREEN on the dark chip at 3.2:1. Match
   the specificity; the alpha bump alone fixed nothing. */
.tg-msg.out.tg-photo .tg-meta {
  right: 10px; bottom: 4px; color: #fff;
  /* .42 alpha measured 3.20:1 against the photo's light areas - under the 4.5
     floor. Telegram's own chip is this dark too; .68 measures ~7:1. */
  background: rgba(20,26,14,.68); border-radius: 999px; padding: 2px 7px;
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
}

.tg-ok { font-size: 11px; }
.tg-sub { display: block; margin-top: 4px; color: #3a352d; line-height: 1.4; }
.tg-live { max-width: 92%; }
.tg-live .tgn-link { display: block; margin-top: 2px; }
.tg-preview--x { border-left-color: #7a6fd0; background: #f5f2fd; }

/* three phones now, so the thread heights differ per scene — let each one
   bottom-anchor except the news radar, which is a single tall push */
.togo-sec .cap:nth-of-type(3) .tg-body { justify-content: flex-start; padding-top: 8px; }

/* ============================================================
   PROOF LINK — was a pill with two arrows in it (one typed into
   the sentence, one as the affordance), a logo sitting on the
   text baseline rather than optically centred, and a step-1 type
   size that forced the label to wrap inside a rounded pill, so
   the second line hung under the icon. Rebuild as a proper link
   card: fixed icon tile, two-line label, one arrow that moves.
   ============================================================ */
.proof-line { margin-top: 22px; }
.proof-chip {
  display: inline-grid; grid-template-columns: auto 1fr auto; align-items: center;
  gap: 13px; max-width: 100%;
  border-radius: 14px; padding: 12px 16px 12px 12px;
  font-size: var(--step--1); font-weight: 400;
  background: var(--sheet); border: 1px solid rgba(178,94,73,.28);
  text-decoration: none;
}
.proof-chip .pc-ic {
  width: 38px; height: 38px; border-radius: 10px; flex: none;
  display: grid; place-items: center;
  background: var(--canvas); box-shadow: inset 0 0 0 1px rgba(34,26,16,.07);
}
.proof-chip .pc-ic img { width: 22px; height: 22px; border-radius: 5px; display: block; }
.proof-chip .pc-t { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.proof-chip .pc-t b {
  font-weight: 500; font-size: var(--step-0); color: var(--ink); line-height: 1.25;
  letter-spacing: -0.008em;
}
.proof-chip .pc-t i {
  font-style: normal; font-family: var(--mono); font-size: 11.5px;
  color: var(--ink-mute); letter-spacing: .01em;
}
.proof-chip .arw {
  width: 18px; height: 18px; flex: none; color: var(--clay-link);
  transition: transform var(--t-micro) var(--ease-soft);
}
.proof-chip .arw svg { width: 100%; height: 100%; display: block; }
.proof-chip:hover .arw { transform: translateX(3px); }

/* ============================================================
   INK BANDS — Oded: "everything is dark and there is no contrast
   between them, and the text is crowded as well."
   Three surfaces stacked (CTA band -> agent strip -> footer) sat
   within ~7 points of each other, so the eye read one long black
   slab with faint seams rather than three sections. Widen the
   steps to a legible interval and alternate them, so the agent
   strip is a DARKER inset rail between two lighter grounds — the
   same read as the terminal scene, which is what it is: a strip
   addressed to machines, not to people.
   ============================================================ */
.sec.cta-band, .receipts-sec, section.sec#faq { background: #2a241b; }
.pcard.featured { background: #2a241b; border-color: #2a241b; }
footer.site { background: #1d1913; }
.agents-note { background: #100e0a; border-top: 0; }

/* the strip: more air, real contrast, and a machine rule to open it */
.agents-note { position: relative; }
.agents-note::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px;
  background: linear-gradient(90deg, rgba(207,101,72,.55), rgba(207,101,72,.10) 42%, transparent 78%);
}
.agents-note .inner {
  padding: 40px 0 42px; max-width: 74ch; line-height: 1.72;
  color: rgba(242,236,221,.72);
}
.agents-note h2 {
  color: #f4eee0; margin-bottom: 12px; letter-spacing: -0.004em;
}
.agents-note code {
  background: rgba(242,236,221,.09); color: #f0e9db;
  box-shadow: inset 0 0 0 1px rgba(242,236,221,.14);
  padding: 3px 8px; margin: 0 2px;
}
.agents-note a { color: #dfbc63; }
.agents-note a:hover { color: #f0d489; }

/* the band's own bottom padding was stacking on top of the strip's, so ~150px
   of unclaimed dark sat between the artwork and the next section */
@media (min-width: 901px) { .sec.cta-band { padding-bottom: clamp(72px, 9vh, 108px); } }

/* A chat transcript grows UP off the composer; it does not hang from the top of
   an empty card. The scenes reserved their full end-state height and then played
   into the top of it, so 40-60% of every card was bare ground for most of the
   loop. Bottom-anchor the flex scenes (the writing scene is a grid and keeps its
   own placement). */
/* Oded: real chats start at the TOP and advance downward - the transcript
   fills from the first message, it does not rise off the composer. The
   collapse machinery stays; only the anchor flips. */
.sc-claude .scene-body, .sc-gpt .scene-body:not(.cap-writing .scene-body) {
  justify-content: flex-start;
}
.strategy-flow { justify-content: flex-start; }

/* Telegram's own placeholder and status line sit at ~4.1:1, which is fine on a
   real phone and not fine on a web page that has to clear AA. Darken both just
   past the threshold; at 12px and 10px the difference is invisible against the
   reference photo but the text is legible to everyone. */
.tg-field { color: #565d50; }
.tg-who i { color: #575e51; }

/* The strip was one run-on sentence carrying a URL, three links and two labels,
   wrapping mid-phrase ("Agent / skills:"). Same content, laid out as a labelled
   row list, so a human scans it and an agent still reads it in order. */
.agents-note .inner { max-width: 78ch; }
.agents-note .inner > p { margin-bottom: 18px; }
.an-rows { display: flex; flex-direction: column; gap: 9px; margin: 0; }
.an-rows > div {
  display: grid; grid-template-columns: 92px 1fr; align-items: baseline; gap: 14px;
}
.an-rows dt {
  font-family: var(--mono); font-size: 11px; letter-spacing: .09em; text-transform: uppercase;
  color: rgba(242,236,221,.62);
}
.an-rows dd { margin: 0; display: flex; flex-wrap: wrap; gap: 14px; align-items: baseline; }
.an-rows dd a { font-family: var(--mono); font-size: var(--step--2); }
@media (max-width: 560px) {
  .an-rows > div { grid-template-columns: 1fr; gap: 3px; }
}

/* ============================================================
   RECEIPTS — the aggregate number and the post that produced it,
   alternating. Oded: "put a small animation showing the 137K
   impressions and then showing the lovable post that blew up, on
   repeat." The analytics page proves the scale; a single post
   proves it is a real feed and not a dashboard. Neither works
   alone, and side by side they would each be too small to read.
   ============================================================ */
.proof-alt .mount { position: relative; }
/* scoped to .mount: the figcaption uses the same pf-a/pf-b hooks for its two
   lines, and an unscoped rule painted the caption with the frame's ground */
.proof-alt .mount > .pf { position: absolute; inset: 0; }
.proof-alt .mount > .pf-a { position: relative; }   /* frame A sizes the mount */
.proof-alt .mount > .pf img { display: block; width: 100%; }
.proof-alt .mount > .pf-b {
  display: grid; place-items: center; background: #eee6d8; padding: 3% 0;
  /* an AUTO row is sized by its item, which makes the item's max-height:100%
     cyclic and therefore ignored — the screenshot overflowed and its engagement
     bar, the whole point of showing it, was clipped off the bottom */
  grid-template-rows: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr);
}
.proof-alt .mount > .pf-b img {
  width: auto; height: auto; max-width: 94%; max-height: 100%; object-fit: contain;
  border-radius: 4px; box-shadow: 0 6px 22px -8px rgba(34,26,16,.42);
}
.proof-alt .ring { position: absolute; left: 1.5%; top: 15.4%; width: 44%; height: 3.4%; }
.pf-caps { position: relative; }
.pf-caps > span { display: block; }
.pf-caps > .pf-b { position: absolute; inset: 0; }

/* 16s: 6s on the number, 6s on the post, 2s of cross-dissolve either way.
   Both frames declare opacity at EVERY stop so nothing decays across loops. */
@keyframes proofAltA {
  0%, 37.5% { opacity: 1; }
  50%, 87.5% { opacity: 0; }
  100% { opacity: 1; }
}
@keyframes proofAltB {
  0%, 37.5% { opacity: 0; }
  50%, 87.5% { opacity: 1; }
  100% { opacity: 0; }
}
.js .proof-alt .mount > .pf-b, .js .pf-caps > .pf-b { opacity: 0; }
.js .receipts-sec .proof-alt.in .mount > .pf-a { animation: proofAltA 16s var(--ease-soft) 0s infinite both; }
.js .receipts-sec .proof-alt.in .mount > .pf-b { animation: proofAltB 16s var(--ease-soft) 0s infinite both; }
/* R30 #8: dissolving one caption through another left both legible at once -
   ghost words superimposed on the same line. Images may dissolve; TYPE cuts.
   The cuts land at the midpoints of the image crossfades (43.75% / 93.75%). */
@keyframes proofCapA {
  0%, 43.74% { opacity: 1; }
  43.75%, 93.74% { opacity: 0; }
  93.75%, 100% { opacity: 1; }
}
@keyframes proofCapB {
  0%, 43.74% { opacity: 0; }
  43.75%, 93.74% { opacity: 1; }
  93.75%, 100% { opacity: 0; }
}
.js .receipts-sec .reveal.in .pf-caps > .pf-a { animation: proofCapA 16s linear 0s infinite both; }
.js .receipts-sec .reveal.in .pf-caps > .pf-b { animation: proofCapB 16s linear 0s infinite both; }
@media (prefers-reduced-motion: reduce) {
  .js .proof-alt .pf-a, .js .proof-alt .pf-b,
  .js .pf-caps > .pf-a, .js .pf-caps > .pf-b { animation: none; }
  .js .proof-alt .mount > .pf-b, .js .pf-caps > .pf-b { opacity: 0; }
}

/* Mobile footer: a 2-column grid over five blocks of very different heights
   left a whole empty quadrant beside "Follow Oded Tsamir" and a long void under
   the brand blurb. CSS columns balance by height instead of by count, so the
   groups pack tight and no quadrant is left stranded. */
@media (max-width: 719px) {
  .foot-top {
    display: block; columns: 2; column-gap: 26px; padding: 34px 0 30px;
  }
  .foot-brand, .foot-col { break-inside: avoid; margin: 0 0 26px; display: block; }
  .foot-brand { column-span: all; margin-bottom: 30px; }
  .foot-brand p { max-width: 46ch; }
  .foot-col a { padding: 7px 0; }
}

/* The inline-keyboard bar ran past the bubble and off the screen edge with its
   label cut mid-word: text-overflow cannot apply to a flex container's own text,
   so the declared ellipsis never rendered. Match the bubble's width and centre
   the label the way Telegram does. */
.tg-btn { align-self: stretch; width: auto; justify-content: center;
  /* the ellipsis-era overflow:hidden was clipping the pointer that now lives
     inside the button; the label is short, nothing needs truncating */
  overflow: visible; white-space: normal; }

/* ============================================================
   SQUADRON CAPTION — third attempt, and this one is structural.
   The first two tuned the crop (object-position) and the measure
   (max-width). Both are conditional: when the band's aspect ratio
   happens to match the photograph's 1584x672, object-fit:cover
   crops NOTHING and object-position has no effect at all, so the
   lead plane's nose lands wherever the photo puts it and the
   caption has to get out of its way on its own.
   So: reserve the right zone in layout, not by tuning. The photo
   still bleeds the full width; its right edge dissolves into the
   band's own ground, and the caption sits in that cleared air.
   ============================================================ */
@media (min-width: 901px) {
  /* The cleared zone is DERIVED from where the caption actually sits, not from
     a share of the viewport. The caption is anchored to the content measure via
     --bleed-r, so above --maxw it moves INWARD as the window grows while a
     viewport-share gutter moves outward — they crossed over at ~1430px and the
     line landed back on the artwork. One variable now drives both. */
  /* PIXEL-MEASURED, not reasoned: the lead plane's painted right edge sits at a
     constant ~62% of the band's width (it is a full-bleed photograph, so it
     scales with the viewport). Anchoring the caption to the CONTENT measure
     instead pins it to a fixed 1200px column, so above ~1400px the plane
     outruns the text and they collide again — which is exactly what happened
     twice. Both edges are now percentages of the same box, so the gap is
     constant by construction. */
  .squadron-band { --sq-gutter: calc(34% + 20px); }
  .squadron-band img {
    object-position: left center;
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - var(--sq-gutter)),
                                        rgba(0,0,0,.35) calc(100% - var(--sq-gutter) * .45),
                                        transparent 100%);
            mask-image: linear-gradient(90deg, #000 calc(100% - var(--sq-gutter)),
                                        rgba(0,0,0,.35) calc(100% - var(--sq-gutter) * .45),
                                        transparent 100%);
  }
  .squadron-band .sq-line {
    inset: 0 clamp(24px, 4vw, 72px) 0 66%;
    max-width: none;
  }
}
/* at narrow desktop the reserved gutter is tighter than the measure the line
   needs at step-2, so it broke to three lines with "it." orphaned */
@media (min-width: 901px) and (max-width: 1430px) {
  .squadron-band .sq-line { font-size: var(--step-1); }
}

/* ============================================================
   03 · WRITING & SCHEDULING — the missing half of the story.
   Every scene before this one showed Liftli reacting to text the
   user had already written, so a visitor could reasonably conclude
   it does not write. It does, and the same breath schedules it.
   Content is Oded's real X thread (`p_aeb3450e4594e7`), which was
   genuinely written by Liftli and genuinely queued.
   ============================================================ */
.threadcard {
  display: flex; flex-direction: column; gap: 0;
  background: var(--canvas); border: 1px solid var(--hairline);
  border-radius: 12px; overflow: hidden;
}
.threadcard .tw {
  display: grid; grid-template-columns: 30px 1fr; gap: 2px 10px;
  padding: 12px 14px; font-size: var(--step--1); line-height: var(--lh-body);
  color: var(--ink-soft);
}
.threadcard .tw + .tw { border-top: 1px solid var(--hairline); }
/* the thread's spine — the vertical rule X draws between replies */
.threadcard .tw { position: relative; }
.threadcard .tw:not(:last-child)::after {
  content: ''; position: absolute; left: 28px; top: 34px; bottom: -1px;
  width: 1.5px; border-radius: 2px; background: var(--hairline-strong); opacity: .55;
}
.threadcard .tn {
  font-family: var(--mono); font-size: var(--step--2); color: var(--clay-link);
  font-weight: 500; padding-top: 1px;
}
@media (max-width: 520px) {
  .threadcard .tw { grid-template-columns: 24px 1fr; padding: 10px 11px; }
  .threadcard .tw:not(:last-child)::after { left: 22px; }
}

/* the aside in the fact-check copy — a wink, not a typo */
.wink { font-family: var(--mono); color: var(--clay-link); letter-spacing: -0.04em; }

/* The sub carries two jobs now: the promise, then the scope. The old version
   spent the highest-attention line on a six-noun feature list and a restatement
   of the headline. Demote the scope so it is available without competing. */
.sub .sub-scope {
  display: block; margin-top: 9px;
  font-size: 0.86em; color: var(--ink-mute);
}

/* ============================================================
   MOBILE HERO — one screen, video included (the post-bridge
   pattern Oded asked for). Measured before: at 390x844 the video
   ended 905px down a 844px viewport, so the whole proof element
   sat below the fold and the CTAs were the last thing standing.
   Every reduction below is measured, not guessed.
   ============================================================ */
@media (max-width: 600px) {
  .hero-platforms { margin-bottom: 16px; gap: 12px; }
  .hero-platforms .plat img { width: 40px; }
  .hero h1 { font-size: clamp(34px, 11vw, 46px); line-height: 1.1; }
  .hero .sub { font-size: var(--step--1); line-height: 1.45; margin-top: 12px; max-width: 34ch; }

  /* one full-width primary, secondary demoted to a text link — the hero has
     room for exactly one decision on a phone */
  .hero .hero-cta { flex-direction: column; gap: 10px; margin-top: 18px; align-items: stretch; }
  .hero .hero-cta .btn-primary { width: 100%; justify-content: center; padding-block: 15px; border-radius: 999px; }
  .hero .hero-cta .btn-ghost {
    border: 0; background: none; box-shadow: none; padding: 4px 0; min-height: 0;
    color: var(--ink-mute); font-size: var(--step--1); text-decoration: underline;
    text-underline-offset: 3px; text-decoration-color: var(--hairline-strong);
  }
  .hero .hero-cta .btn-ghost:hover { background: none; color: var(--ink); }

  .hero-video { margin-top: 16px; margin-bottom: 20px; }
  .hero-video .vframe { gap: 8px; }
  .hero-video .vframe .vplay { width: 38px; height: 38px; }
  .hero-video .vframe .vplay::after { border-left-width: 11px; border-top-width: 7px; border-bottom-width: 7px; }
  .hero-video .vlabel { font-size: var(--step--2); padding: 0 24px; }
}
/* The trailing mark HANGS (negative margin) so the centred line box ignores its
   width. At phone widths the last line is already near the full measure, so the
   hang pushed the mark off the screen edge and it rendered clipped. Give it back
   its box on mobile — a slightly off-axis line is much cheaper than a sliced logo. */
@media (max-width: 600px) {
  .agent-cycle { margin-right: 0; width: 0.86em; height: 0.86em; }
}

/* The Editing band paints its own ground (--sheet-2), but the FULL-BLEED
   geometry that makes that ground read as a band was scoped to >=900px while
   the background itself was not. Below 900px the tint therefore stopped 20px
   short of both screen edges with square corners — which reads as a rendering
   fault, not a section. Same bleed maths works at every width: 50% resolves
   against the wrap's content box, 50vw against the screen. */
@media (max-width: 899px) {
  .cap-writing {
    border-top: 1px solid var(--hairline);
    border-bottom: 1px solid var(--hairline);
    margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw);
    padding-left: calc(50vw - 50%); padding-right: calc(50vw - 50%);
  }
}

/* ============================================================
   05 · REPURPOSING — and the prototype for getting OUT of the
   chat-rectangle rut. Four of five capability scenes were the
   same rounded window with the same title bar, so by the third
   one the eye stops reading. This scene has NO frame: two
   documents sit directly on the page ground with the reasoning
   between them, because the artifact here is a comparison, not
   a conversation.
   ============================================================ */
/* The two-document comparison is gone. Oded wanted the IDEA, not four
   paragraphs of evidence for it: one thing in, many out; many in, one out;
   across all three platforms, in both directions. That is a diagram, and a
   diagram is the one thing a wall of text cannot be skimmed into.

   The clip is a single Gemini/Veo generation - the platform logos and the
   Liftli dart are all rendered in-shot, nothing composited on afterwards.
   Its cut jumped 13.5 mean-abs at the wrap against a 1.7 baseline, so the
   loop is baked the same way the machine's is (ffmpeg xfade), bringing the
   wrap to 3.4. */
.rp-stage {
  position: relative; margin: 0; max-width: 1200px;
  background: #dde3e5;
  border-radius: var(--radius-lg); overflow: hidden;
  box-shadow: 0 1px 2px rgba(34,26,16,.15), 0 22px 50px -24px rgba(34,26,16,.4);
}
.rpvid, .rp-stage img.rpstill { display: block; width: 100%; height: auto; }
.rp-stage img.rpstill { display: none; }
@media (prefers-reduced-motion: reduce) {
  .rpvid { display: none; }
  .rp-stage img.rpstill { display: block; }
}
.cap-repurpose { display: block; }
.cap-repurpose .cap-copy { max-width: 60ch; margin-bottom: clamp(22px, 3vh, 34px); }
.cap-repurpose .cap-copy p { max-width: 56ch; }

/* ============================================================
   TELEGRAM PHONES — a slight Y-axis rotation so the section reads
   as an object on a desk rather than three flat rectangles.
   The rotation is on a wrapper, NOT on .tgphone itself: .tg-screen
   uses position:absolute children and an overflow:hidden mask, and
   a transform on that element would establish a new containing
   block and re-anchor the island, the status bar and the composer.
   Perspective lives on the figure so each phone converges on its
   own vanishing point instead of sharing one across the row.
   ============================================================ */
@media (min-width: 900px) and (prefers-reduced-motion: no-preference) {
  .tgfig { perspective: 1400px; perspective-origin: 50% 45%; }
  .tgfig .tgphone {
    transform: rotateY(-15deg) rotateX(2deg);
    transform-style: preserve-3d;
    transition: transform 600ms var(--ease-soft);
    /* the light was baked for a flat phone; deepen the far edge so the
       tilt does not read as a skewed image */
    box-shadow:
      0 0 0 1px rgba(20,15,8,.55),
      inset 0 0 0 1px rgba(255,244,224,.10),
      18px 10px 26px -14px rgba(34,26,16,.34),
      44px 40px 74px -30px rgba(34,26,16,.62);
  }
  /* mirror the tilt on the flipped rows so the phones face INTO the page,
     not off the edge of it */
  .togo-sec .cap.flip .tgphone { transform: rotateY(15deg) rotateX(2deg); }
  .togo-sec .cap.flip .tgphone {
    box-shadow:
      0 0 0 1px rgba(20,15,8,.55),
      inset 0 0 0 1px rgba(255,244,224,.10),
      -18px 10px 26px -14px rgba(34,26,16,.34),
      -44px 40px 74px -30px rgba(34,26,16,.62);
  }
  /* straighten on hover: the content becomes readable when you attend to it */
  .tgfig:hover .tgphone,
  .togo-sec .cap.flip .tgfig:hover .tgphone { transform: rotateY(0) rotateX(0); }
}

/* ============================================================
   HERO CTA — one decision, at a size that matches its weight.
   The secondary "See how it connects" is gone (the section it
   pointed at is the next thing you scroll into anyway), so the
   primary can stop sharing the row and grow into it.
   ============================================================ */
.hero .hero-cta .btn-primary {
  font-size: var(--step-1);
  padding: 19px 40px;
  border-radius: 999px;
  letter-spacing: -0.008em;
}
.hero .hero-cta .btn-primary svg { width: 18px; height: 18px; }
@media (max-width: 600px) {
  .hero .hero-cta .btn-primary { font-size: var(--step-0); padding-block: 17px; }
}

/* ============================================================
   FEATURE GROUNDS — six capabilities on one cream field read as
   one long stretch, which is the other half of why the section
   felt monotonous. Give each its own tinted band so scrolling
   through them registers as movement between rooms.

   The tints are deliberately WEAK (2-5% of a hue over the paper).
   The palette's whole argument is warm paper and one clay accent;
   saturated section grounds would win a fight the content should
   be winning. Each band is full-bleed with the same maths the
   editing band uses, so nothing stops short of the screen edge.
   ============================================================ */
.caps-sec .cap {
  position: relative;
  /* Bleed the ELEMENT, do not fake it with a pseudo-element. The ::before
     version (absolute + negative insets, then width:100vw + translate) left a
     60px margin of section ground on both sides in two different forms, so the
     inset maths was not the problem - the pseudo-element was. This is the exact
     technique .cap-writing already uses successfully at every width: push the
     box out to the screen with margins, then put the measure back with padding
     so the CONTENT never moves. */
  margin-inline: calc(50% - 50vw);
  padding-inline: calc(50vw - 50%);
}
/* 01 strategy - the paper itself, so the first one is the baseline */
/* 02 ideation - a cool graphite wash under the terminal */
.caps-sec .cap:nth-of-type(2) { background: #e9ede8; }
/* 03 writing & scheduling - clay, the brand's own warmth */
.caps-sec .cap:nth-of-type(3) { background: #f7ebe1; }
/* 04 editing keeps the sheet-2 band it already had */
/* 05 repurposing - matched to the clip, which is the only band on this page
   chosen by MEASUREMENT rather than palette. Oded's replacement render sits
   on a cool grey-blue ground sampled at rgb(221,227,229) around its edges;
   this is that hue held at the same lightness as the sage band the other
   sections use, so the video reads as a panel resting ON the page instead of
   a foreign rectangle pasted onto it. Targeted by class, not position. */
.caps-sec .cap.cap-repurpose { background: #e8edef; }
/* 06 results - sand, warmest, closing the section */
.caps-sec .cap:nth-of-type(6) { background: #f4ecda; }

/* the scene cards were tuned against ONE ground; on a tinted band a
   --sheet card floats slightly. Lift them to plain paper so they read
   as objects placed ON the band rather than holes cut out of it. */
.caps-sec .cap:nth-of-type(2) .cap-scene,
.caps-sec .cap:nth-of-type(3) .cap-scene,
.caps-sec .cap:nth-of-type(6) .cap-scene { box-shadow: 0 18px 44px -22px rgba(34,26,16,.42); }

/* ============================================================
   FLOATING BUBBLES — the writing scene loses its window entirely.
   No title bar, no card: the messages sit on the band and drift,
   which is the one place on the page where motion is decoration
   rather than explanation, so it is deliberately tiny (3-5px) and
   slow, and it stops for anyone who asked motion to stop.
   ============================================================ */
.caps-sec .cap:nth-of-type(3) .cap-scene {
  background: none; border: 0; box-shadow: none; overflow: visible;
}
.caps-sec .cap:nth-of-type(3) .scene-bar { display: none; }
.caps-sec .cap:nth-of-type(3) .scene-body { padding: 0; gap: var(--sp-4); }
.caps-sec .cap:nth-of-type(3) .scene-body > .a,
.caps-sec .cap:nth-of-type(3) .threadcard,
.caps-sec .cap:nth-of-type(3) .toolchip {
  box-shadow: 0 10px 26px -14px rgba(34,26,16,.34);
}
.caps-sec .cap:nth-of-type(3) .scene-body > .a {
  background: var(--sheet); border-radius: 16px 16px 16px 4px;
  padding: 13px 17px; max-width: 88%;
}
@media (prefers-reduced-motion: no-preference) {
  @keyframes bubbleDrift {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-5px); }
  }
  .caps-sec .cap:nth-of-type(3).playing .scene-body > * {
    animation-name: sceneStep1, bubbleDrift;
    animation-duration: 15s, 7s;
    animation-timing-function: var(--ease-out), ease-in-out;
    animation-iteration-count: infinite, infinite;
    animation-fill-mode: both, none;
  }
  .caps-sec .cap:nth-of-type(3).playing .scene-body > *:nth-child(2) { animation-name: sceneStep2, bubbleDrift; animation-delay: 0s, -1.4s; }
  .caps-sec .cap:nth-of-type(3).playing .scene-body > *:nth-child(3) { animation-name: sceneStep3, bubbleDrift; animation-delay: 0s, -2.8s; }
  .caps-sec .cap:nth-of-type(3).playing .scene-body > *:nth-child(4) { animation-name: sceneStep4, bubbleDrift; animation-delay: 0s, -4.2s; }
  .caps-sec .cap:nth-of-type(3).playing .scene-body > *:nth-child(5) { animation-name: sceneStep5, bubbleDrift; animation-delay: 0s, -5.6s; }
}

/* ============================================================
   VIDEO POSTER — the dashed "coming shortly" box is gone. The
   slot now holds a real photographic frame in the same paper
   world as the squadron band, with the play affordance and the
   label composited over it. When the recording exists, only the
   click handler changes; the composition stays.
   ============================================================ */
.hero-video .vframe--poster {
  border: 0; background: none; padding: 0; gap: 0;
  display: block; position: relative;
  box-shadow: 0 1px 2px rgba(34,26,16,.18), 0 24px 54px -22px rgba(34,26,16,.5);
}
.hero-video .vframe--poster picture,
.hero-video .vframe--poster img { display: block; width: 100%; height: 100%; object-fit: cover; }
.hero-video .vframe--poster .vplay {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 62px; height: 62px;
  background: rgba(253,250,243,.92); border: 0;
  box-shadow: 0 2px 5px rgba(34,26,16,.25), 0 14px 34px -10px rgba(34,26,16,.45);
  transition: transform var(--t-micro) var(--ease-soft);
}
.hero-video .vframe--poster .vplay::after {
  border-left-color: var(--sanguine);
  border-left-width: 16px; border-top-width: 10px; border-bottom-width: 10px;
  margin-left: 4px;
}
.hero-video .vframe--poster:hover .vplay { transform: translate(-50%, -50%) scale(1.07); }
.hero-video .vframe--poster .vlabel--overlay {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 26px 18px 13px; text-align: center; color: #fff;
  font-size: var(--step--2);
  /* .62 measured 1.97:1 where the scrim thins over the light paper photo -
     the caption zone must be dark enough on its own, not borrowed from the
     image. Verified >=4.5 against the brightest pixels after this change. */
  background: linear-gradient(180deg, transparent 0%, rgba(26,20,12,.58) 40%, rgba(26,20,12,.85) 100%);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}

/* R30 #4: the terminal reserved its end-state height and filled from the top,
   so the first seconds of every loop were two-thirds empty black. A real
   terminal pins its content to the prompt line and grows upward. The first
   attempt put justify-content on .term-body alone, which did nothing: the body
   is auto-height, so the void sat BETWEEN it and the input bar. The card
   itself must be the column and the body must own the spare height. */
.cap-scene.sc-term { display: flex; flex-direction: column; }
.sc-term .term-body {
  flex: 1; display: flex; flex-direction: column; justify-content: flex-start;
}

/* The courier scroll plane lived here; Oded: "childish". Removed. */
/* stray brace removed (was breaking the next rule) */

/* ============================================================
   HOVER GRAMMAR — one rule: only things you can ACT on respond
   to the cursor. Decorative cards staying still is what makes
   the interactive ones read as interactive. Lifts are 2-3px,
   150ms, transform+shadow only.
   ============================================================ */
/* .pcard hover lift removed - four cards of different heights jumping
   independently read as misalignment, not affordance (Oded, 08-27). */

.rp-list li a, .love-add .btn { transition: transform var(--t-micro) var(--ease-soft); }
.rp-list li a:hover { transform: translateX(3px); }

/* nav links: a hairline that draws in from the left — quieter than a colour swap */
header.site nav a:not(.btn) { position: relative; }
header.site nav a:not(.btn)::after {
  content: ''; position: absolute; left: 0; right: 100%; bottom: 2px; height: 1.5px;
  background: currentColor; opacity: .55;
  transition: right 180ms var(--ease-soft);
}
header.site nav a:not(.btn):hover::after { right: 0; }

/* the platform tiles above the H1 already lift; give the agent tabs the same verb */
.agent-tab { transition: transform var(--t-micro) var(--ease-soft), border-color var(--t-micro), background-color var(--t-micro); }
.agent-tab:hover { transform: translateY(-2px); }
@media (prefers-reduced-motion: reduce) {
  .pcard, .rp-list li a, .agent-tab, header.site nav a:not(.btn)::after { transition: none; }
  .pcard:hover, .rp-list li a:hover, .agent-tab:hover { transform: none; }
}

/* R31 #7: below 640px the mark wraps onto its own centred line under the H1 —
   40px of air around a logo that (with any timing slip) can be an empty box.
   The device is a desktop grace note; phones get the sentence. */
@media (max-width: 640px) { .agent-cycle { display: none; } }

/* ============================================================
   R31 #3/#4 — the growth illusion without the reflow. Collapsed
   beats make the chat/terminal grow off their input line, but a
   card with no reserved height then pumps the WHOLE DOCUMENT
   (measured: body scrollHeight cycled 17,290 -> 17,452px on a
   15s timer — the page breathing under the reader). Pin each
   card to its measured end-state height per breakpoint; content
   is bottom-anchored, so it fills upward inside a fixed frame.
   Values are MEASURED end states, not guesses (1440/1024/768/390:
   strategy 535/529/490/668, terminal 502/536/443/558).
   ============================================================ */
#capabilities .cap:nth-of-type(1) .cap-scene { min-height: 593px; } /* re-measured after the composer landed */
.cap-scene.sc-term { min-height: 502px; }
@media (max-width: 1099px) {
  #capabilities .cap:nth-of-type(1) .cap-scene { min-height: 587px; }
  .cap-scene.sc-term { min-height: 536px; }
}
@media (max-width: 900px) {
  #capabilities .cap:nth-of-type(1) .cap-scene { min-height: 548px; }
  .cap-scene.sc-term { min-height: 443px; }
}
@media (max-width: 640px) {
  #capabilities .cap:nth-of-type(1) .cap-scene { min-height: 726px; }
  .cap-scene.sc-term { min-height: 558px; }
}
/* strategy card becomes the flex frame its transcript bottom-anchors inside */
#capabilities .cap:nth-of-type(1) .cap-scene { display: flex; flex-direction: column; }
#capabilities .cap:nth-of-type(1) .scene-body { flex: 1; }

/* R31 #4 follow-through: flex `gap` still spaces COLLAPSED items — seven 20px
   gaps held ~140px open under the transcript, so it floated mid-card. Gaps
   move onto the children as margins, which the collapse keyframes already
   zero (`margin-block: 0`), so hidden beats now contribute nothing at all. */
#capabilities .cap:nth-of-type(1) .scene-body { gap: 0; }
#capabilities .cap:nth-of-type(1) .scene-body > * { margin-block-start: var(--sp-4); }
#capabilities .cap:nth-of-type(1) .scene-body > *:first-child { margin-block-start: 0; }
.sc-term .term-body { gap: 0; }
.sc-term .term-body > * { margin-block-start: 5px; } /* = the gap it replaces */
.sc-term .term-body > *:first-child { margin-block-start: 0; }

/* R31 #9: two cards left-aligned in a four-slot grid left ~700px of empty page.
   Centre the pair and level their top edges until the wall has more residents. */
.wall-grid { justify-content: center; grid-template-columns: repeat(auto-fit, minmax(280px, 400px)); align-items: stretch; }
.love-add { display: flex; flex-direction: column; justify-content: center; }

/* ============================================================
   BAND NUMERALS — R31's counter-proposal instead of spot art:
   per-band identity from a mark already in the system. The tiny
   "01 ·" eyebrow becomes a large chalk numeral in the band's own
   air. CSS counters so the HTML can reorder without renumbering;
   empty alt-text silences assistive tech; generated content, so
   the contrast gate's text-node walk correctly ignores what is
   ornament, and .12 opacity keeps it under the copy, not over.
   Only where the gutter exists.
   ============================================================ */
@media (min-width: 1420px) {
  .caps-sec { counter-reset: feat; }
  .caps-sec .cap { counter-increment: feat; }
  .caps-sec .cap::after {
    content: counter(feat, decimal-leading-zero) / "";
    position: absolute; top: clamp(20px, 4vh, 44px); left: clamp(10px, 1.6vw, 30px);
    font-family: var(--sans); font-weight: 300; font-size: clamp(96px, 9vw, 148px);
    line-height: 1; letter-spacing: -0.04em;
    color: var(--sanguine); opacity: .12; pointer-events: none;
  }
}


/* ============================================================
   R32 B3 — the growth fix landed on ONE card of four. Class
   rule now: every flex chat scene is a fixed frame whose
   transcript bottom-anchors and grows upward (collapse lives in
   the shared sceneStep keyframes above). The editing scene is a
   GRID >=900 and is explicitly excluded: collapsing one grid
   column's items against the other would shear the row.
   ============================================================ */
.cap-scene.sc-claude, .cap-scene.sc-gpt:not(.cap-writing .cap-scene) {
  display: flex; flex-direction: column;
}
.sc-claude .scene-body,
.sc-gpt .scene-body:not(.cap-writing .scene-body) { flex: 1; }
/* gap spaces collapsed items; margins collapse with them */
.sc-claude .scene-body,
.sc-gpt .scene-body:not(.cap-writing .scene-body) { gap: 0; }
.sc-claude .scene-body > *,
.sc-gpt .scene-body:not(.cap-writing .scene-body) > * { margin-block-start: var(--sp-4); }
.sc-claude .scene-body > *:first-child,
.sc-gpt .scene-body:not(.cap-writing .scene-body) > *:first-child { margin-block-start: 0; }
/* the editing grid keeps reserved height but anchors its rows down */
@media (min-width: 900px) { .cap-writing .scene-body { align-content: start; } }

/* R32 B8 — a chat with no input line reads as a mis-positioned element, not an
   empty conversation. The composer the connect mock already ships, pinned as
   the floor every transcript grows off. (Static: outside the beat clocks.) */

/* R32 B2/B6 — the numeral was cut by the scene card on flip rows (8px overlap
   with an opaque fill) and collided with its own eyebrow. Smaller so it fits
   the true gutter (right edge < 160px at 1440), mirrored on flip rows. */
/* R33 #2: at 1320-1412px the numeral overlapped the copy column by up to
   36px (collision holds wherever 0.1034W > W/2 - 560). Gate on the width
   where the gutter genuinely exists. */
@media (min-width: 1420px) {
  .caps-sec .cap::after {
    font-size: clamp(78px, 7.6vw, 112px);
    left: clamp(12px, 1.8vw, 34px);
  }
  .caps-sec .cap.flip::after { left: auto; right: clamp(12px, 1.8vw, 34px); }
  .cap-repurpose::after { display: none; } /* full-width scene: no gutter to sit in */
  .caps-sec .cap.cap-repurpose { counter-increment: feat 0; } /* (0,3,0) - the (0,1,0) version lost to .caps-sec .cap and the rail read 01..04,06 */
}

/* R32 B5 — images now cut at the SAME instants as the captions. The dissolve
   put two dense screenshots on screen for ~1.3s and let the caption name the
   losing figure for 19 of 300 frames. One clock, two hard cuts. */
@keyframes proofAltA {
  0%, 43.74% { opacity: 1; }
  43.75%, 93.74% { opacity: 0; }
  93.75%, 100% { opacity: 1; }
}
@keyframes proofAltB {
  0%, 43.74% { opacity: 0; }
  43.75%, 93.74% { opacity: 1; }
  93.75%, 100% { opacity: 0; }
}
.js .receipts-sec .proof-alt.in .mount > .pf-a { animation: proofAltA 16s linear 0s infinite both; }
.js .receipts-sec .proof-alt.in .mount > .pf-b { animation: proofAltB 16s linear 0s infinite both; }

/* R32 B7 — two same-ground adjacencies flattened a third of the page. The
   caps section drops to the canvas so `connect` (sheet) reads as its own
   room; the CTA band lifts off the FAQ's walnut. */
/* canvas measured a 3.2% step against connect's sheet - under the 5% floor
   this same file establishes. sheet-2 is a step you can actually see. */
.caps-sec { background: var(--sheet-2); }
.sec.cta-band { background: #33291c; border-top: 1px solid rgba(216,179,87,.30); }
section.sec#faq { padding-bottom: clamp(56px, 8vh, 96px); }

/* R32 B3 follow-through: the shared keyframes now collapse beats in EVERY
   scene, so every animated card needs its measured end-state floor or the
   document pump returns through the unpinned ones. Measured, not guessed
   (1440/1024/768/390): c3 530/550/522/647 · c4 433/414/518/652 · c6 704/686/692/682. */
#capabilities .cap:nth-of-type(3) .cap-scene { min-height: 602px; }
#capabilities .cap:nth-of-type(4) .cap-scene { min-height: 457px; }
#capabilities .cap:nth-of-type(6) .cap-scene { min-height: 762px; }
@media (max-width: 1099px) {
  #capabilities .cap:nth-of-type(3) .cap-scene { min-height: 622px; }
  #capabilities .cap:nth-of-type(4) .cap-scene { min-height: 438px; }
  #capabilities .cap:nth-of-type(6) .cap-scene { min-height: 744px; }
}
@media (max-width: 900px) {
  #capabilities .cap:nth-of-type(3) .cap-scene { min-height: 594px; }
  #capabilities .cap:nth-of-type(4) .cap-scene { min-height: 576px; }
  #capabilities .cap:nth-of-type(6) .cap-scene { min-height: 750px; }
}
@media (max-width: 640px) {
  #capabilities .cap:nth-of-type(3) .cap-scene { min-height: 719px; }
  #capabilities .cap:nth-of-type(4) .cap-scene { min-height: 710px; }
  #capabilities .cap:nth-of-type(6) .cap-scene { min-height: 740px; }
}

/* With top-fill, the composer needs its own anchor to stay the card's floor. */
#capabilities .cap:nth-of-type(1) .scene-body .mock-composer { margin-top: auto; }

/* fetched wall reviews: the avatar chip (image when Oded cropped one from a
   Slack screenshot, initials on clay wash otherwise) */
.love figcaption { display: grid; grid-template-columns: auto 1fr; column-gap: 10px; align-items: center; }
.love figcaption .who { grid-column: 2; }
.love figcaption .role { grid-column: 2; }
.love-av { grid-row: 1 / 3; width: 34px; height: 34px; border-radius: 50%; object-fit: cover; }
.love-av--txt {
  display: grid; place-items: center;
  background: var(--clay-wash); color: var(--clay-link);
  font-family: var(--sans); font-size: 13px; font-weight: 600; letter-spacing: .02em;
}
/* the static founder card has no avatar element; its caption keeps one column */
.love figcaption:not(:has(.love-av)) { grid-template-columns: 1fr; }

/* fetched and static review cards must speak one alignment */
.wall-grid .love figcaption { text-align: left; justify-items: start; }

/* R34 #1: the composer is the card's FLOOR, not the last message - nth-child
   beat rules were animating it in and wiping it with the transcript. Same
   specificity as the beat rules, later in the cascade, so it wins. */
.js .cap.in.playing .scene-body > .mock-composer { animation: none; opacity: 1; }

/* R34 #2: with the composer as lastElementChild, the answer is nth-last-child(2)
   (rewritten above) and the composer takes its own full-width final row. */
@media (min-width: 900px) {
  .cap-writing .scene-body > .mock-composer { grid-column: 1 / -1; }
}


/* ============================================================
   THE SLOW SLIDE, root-caused. The collapse stops declared
   margin-block:0 / padding-block:0 only at the HIDDEN keyframes.
   Per spec, a property missing from later keyframes interpolates
   toward an implicit endpoint at its base value - so every
   revealed message's box metrics crept from 0 back to natural
   across the entire remaining loop, sliding all text below it
   downward for ~10s. The keyframes now declare the metrics at
   every stop; these vars carry each element's NATURAL values
   (measured from computed styles, not guessed).
   ============================================================ */
.scene-body > * { --bmt: var(--sp-4); }
.scene-body > *:first-child { --bmt: 0px; }
.scene-body > .u { --bpt: 11px; --bpb: 11px; }
.scene-body > .toolchip { --bpt: 7px; --bpb: 7px; }
.scene-body > .thinking { --bpt: 10px; --bpb: 10px; }
.scene-body > .postmini,
.scene-body > .critique { --bpt: 13px; --bpb: 13px; }
.scene-body > .chartcard { --bpt: 14px; --bpb: 10px; }
.sc-term .term-body > * { --bmt: 5px; }
.sc-term .term-body > *:first-child { --bmt: 0px; }

/* ============================================================
   NEWS PHONE CHOREOGRAPHY — the story arrives, a cursor taps
   Add take, and the real exchange from Oded's own thread plays:
   ask -> the user's take -> saved-as-seed. The news bubble and
   button then scroll away exactly as a live chat would push
   them up. One 11s clock, scoped to this phone only (these
   rules sit later than the shared tgStep set, same specificity
   family, so they win here without touching the other phones).
   ============================================================ */
.togo-sec .cap.flip .tg-btn { position: relative; }
.tg-ptr {
  position: absolute; right: 22%; top: 120%;
  width: 17px; height: 22px; z-index: 3;
  opacity: 0; pointer-events: none; will-change: transform;
  background: var(--ink);
  clip-path: polygon(0 0, 0 78%, 26% 60%, 44% 100%, 62% 91%, 45% 54%, 78% 52%);
  filter: drop-shadow(0 1px 2px rgba(34,26,16,.45));
}
@keyframes tgnPtr {
  0%, 23%    { opacity: 0; transform: translate(26px, 30px); }
  26%        { opacity: 1; transform: translate(26px, 30px); }
  34%        { opacity: 1; transform: translate(0, -14px); }      /* on the button */
  37%        { opacity: 1; transform: translate(0, -14px) scale(.82); }  /* press */
  38.5%      { opacity: 1; transform: translate(0, -14px) scale(1); }
  43%, 100%  { opacity: 0; transform: translate(0, -14px); }
}
@keyframes tgnBtnPress {
  0%, 36%   { filter: none; }
  37%, 38%  { filter: brightness(1.25); }
  39%, 100% { filter: none; }
}
/* news bubble + button: visible from their entrances, then scroll away after
   the click. Metrics declared at EVERY stop (the slow-slide lesson). */
/* ============================================================
   NEWS PHONE, take 2 — Oded: too fast, and the article must not
   be cut away; it should read like someone slowly scrolling
   inside Telegram. So: NO collapse anywhere. The full exchange
   exists in the layout from frame one (later messages sit below
   the screen's fold, exactly as unread messages do), and the
   WHOLE THREAD slides up by measured offsets as each message
   arrives — a real chat scroll. 18s clock, everything at its
   natural height, so the metric-creep class of bug cannot exist
   here: the only animated properties are opacity and the body's
   translate.
   Offsets are MEASURED (phone is a fixed-size artifact):
   296px phone / 481px body: 41 / 195 / 284 · 272px / 407px:
   135 / 286 / 385.
   ============================================================ */
.togo-sec .cap.flip .tg-scroll { --s1: -41px; --s2: -195px; --s3: -284px; }
@media (max-width: 900px) {
  .togo-sec .cap.flip .tg-scroll { --s1: -135px; --s2: -286px; --s3: -385px; }
}
@keyframes tgnScroll {
  0%, 32%   { transform: translateY(0); }
  37%       { transform: translateY(var(--s1)); }
  46%       { transform: translateY(var(--s1)); }
  51%       { transform: translateY(var(--s2)); }
  60%       { transform: translateY(var(--s2)); }
  65%, 100% { transform: translateY(var(--s3)); }
}
@keyframes tgnIn {
  0% { opacity: 0; transform: translateY(9px); }
  2.8% { opacity: 0; transform: translateY(9px); }
  5% { opacity: 1; transform: none; }
  96% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: none; }
}
@keyframes tgnBtnIn {
  0%, 12% { opacity: 0; transform: translateY(9px); }
  15% { opacity: 1; transform: none; }
  96% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: none; }
}
@keyframes tgnPtr {
  0%, 16%  { opacity: 0; transform: translate(26px, 30px); }
  19%      { opacity: 1; transform: translate(26px, 30px); }
  26%      { opacity: 1; transform: translate(0, -14px); }
  27.5%    { opacity: 1; transform: translate(0, -14px) scale(.82); }
  29%      { opacity: 1; transform: translate(0, -14px) scale(1); }
  33%, 100%{ opacity: 0; transform: translate(0, -14px); }
}
@keyframes tgnBtnPress {
  0%, 26.5% { filter: none; }
  27.5%, 28.5% { filter: brightness(1.25); }
  29.5%, 100% { filter: none; }
}
@keyframes tgnM3 {
  0%, 31% { opacity: 0; transform: translateY(9px); }
  34% { opacity: 1; transform: none; }
  96% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: none; }
}
@keyframes tgnM4 {
  0%, 45% { opacity: 0; transform: translateY(9px); }
  48% { opacity: 1; transform: none; }
  96% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: none; }
}
@keyframes tgnM5 {
  0%, 59% { opacity: 0; transform: translateY(9px); }
  62% { opacity: 1; transform: none; }
  96% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: none; }
}
/* The clip must NOT ride the scroll: transforming .tg-body moved its own
   overflow box, spilling the article above the header and chopping the new
   messages below (computed opacity 1, zero pixels painted). The carriage
   moves; the body stands still and clips. */
.togo-sec .cap.flip .tg-body { display: block; }
.togo-sec .cap.flip .tg-scroll { display: flex; flex-direction: column; gap: 7px; min-height: 100%; justify-content: flex-start; }
.js .togo-sec .cap.flip.in.playing .tg-scroll { animation: tgnScroll 18s var(--ease-soft) 0s infinite both; }
.js .togo-sec .cap.flip.in.playing .tg-scroll > [data-step="1"] { animation: tgnIn 18s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap.flip.in.playing .tg-scroll > [data-step="2"] { animation: tgnBtnIn 18s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap.flip.in.playing .tg-btn .tg-ptr { animation: tgnPtr 18s var(--ease-soft) 0s infinite both; opacity: 0; }
.js .togo-sec .cap.flip.in.playing .tg-btn[data-step="2"] { animation-name: tgnBtnIn, tgnBtnPress; animation-duration: 18s, 18s; animation-timing-function: var(--ease-out), linear; animation-iteration-count: infinite, infinite; animation-fill-mode: both, both; }
.js .togo-sec .cap.flip.in.playing .tg-scroll > [data-step="3"] { animation: tgnM3 18s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap.flip.in.playing .tg-scroll > [data-step="4"] { animation: tgnM4 18s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap.flip.in.playing .tg-scroll > [data-step="5"] { animation: tgnM5 18s var(--ease-out) 0s infinite both; }
@media (prefers-reduced-motion: reduce) {
  .js .togo-sec .tg-body, .js .togo-sec .tg-body > [data-step], .js .tg-btn .tg-ptr { animation: none !important; }
  .js .togo-sec .tg-body > [data-step] { opacity: 1; }
  .js .tg-btn .tg-ptr { opacity: 0 !important; }  /* loses to `.js .cap *` without it */
  /* static: the story card and button, exactly what a screenshot would show */
}

.js .tg-scroll > [data-step] { opacity: 0; }
@media (prefers-reduced-motion: reduce) { .js .tg-scroll, .js .tg-scroll > [data-step] { animation: none !important; } .js .tg-scroll > [data-step] { opacity: 1; } }

/* ============================================================
   THE MACHINE — cap 02, replacing the terminal. One authored
   video and nothing else: a da Vinci contraption that carries
   Fathom, GitHub and Claude down the ramp as raw material,
   through the gearworks, and out of the press as finished
   LinkedIn, X and Substack posts.

   NO overlays. Every sprite and keyframe that used to sit on
   top of this frame is gone on purpose — the footage already
   tells the whole story, and anything layered over it had to be
   re-synced by hand every time the footage changed.

   The loop is seamless because it was BAKED that way (ffmpeg
   xfade — the closing wide shot dissolves into the opening one),
   not because CSS hides the seam. Measured at the wrap: 5.9
   mean-abs frame step, against a 4.4 baseline for any two
   consecutive frames mid-shot. The loop point is arithmetically
   indistinguishable from ordinary motion. The source cut jumped
   17.4 — four times a normal step, which is the jolt Oded saw.
   ============================================================ */
.cap-machine { display: block; }
.cap-machine .cap-copy { max-width: 60ch; margin-bottom: clamp(20px, 3vh, 32px); }
.machine-stage {
  position: relative; margin: 0; max-width: 1200px;
  background: var(--sheet-2);
  border-radius: var(--radius-lg); overflow: hidden;
  box-shadow: 0 1px 2px rgba(34,26,16,.15), 0 22px 50px -24px rgba(34,26,16,.4);
}
.mvid, .machine-stage img.mstill { display: block; width: 100%; height: auto; }
/* must out-rank any bare `.machine-stage img` rule - a bare `.mstill`
   lost that fight once and the fallback rendered BELOW the video */
.machine-stage img.mstill { display: none; }
/* .machine-stage figcaption removed with the machine caption itself */
@media (prefers-reduced-motion: reduce) {
  .mvid { display: none; }
  .machine-stage img.mstill { display: block; }
}

/* ============================================================
   PUBLISHING & SCHEDULING — cap 03. Two claims, one picture:
   you can publish from the chat, and you can fill a week at
   once. So the calendar is the surface and the chat window
   lands ON it, rather than two screenshots side by side — the
   publish that fires in the chat is the same post that turns
   green on the 10th.

   Everything animates on opacity/transform ONLY, and every chip
   holds its box at all times (opacity 0, not display:none), so
   the grid's height is constant and the document cannot pump.
   ============================================================ */
.cap-publish { display: block; }
.cap-publish .cap-copy { max-width: 62ch; margin-bottom: clamp(22px, 3.4vh, 36px); }
/* the card hangs past the calendar's bottom edge - floating IN FRONT of the
   surface rather than sitting inside it, which also keeps the queued week
   (row 3) fully visible. The stage reserves the overhang so nothing clips. Re-measured at 80px once the 12th
   started carrying two posts and row 3 grew 24px; 86 leaves a little slack. */
.pub-stage { position: relative; max-width: 1200px; padding-bottom: 86px; }

/* ---- the calendar ---- */
.pub-cal {
  position: relative;
  background: var(--canvas); border: 1px solid var(--hairline);
  border-radius: var(--radius-lg); overflow: hidden;
  box-shadow: 0 1px 2px rgba(34,26,16,.10), 0 22px 50px -28px rgba(34,26,16,.34);
}
.pcal-bar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px clamp(14px, 1.6vw, 20px) 12px;
}
.pcal-bar b { font-size: var(--step-0); font-weight: 500; color: var(--ink); }
.pcal-nav { display: flex; align-items: center; gap: 14px; color: var(--ink-mute); }
.pcal-nav i { font-style: normal; font-size: var(--step-0); }
.pcal-nav em { font-style: normal; font-size: var(--step--1); }
.pcal-dow {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline);
  background: var(--sheet-2);
}
.pcal-dow i {
  font-style: normal; text-align: center; padding: 7px 0;
  font-family: var(--mono); font-size: var(--step--3); letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-mute);
}
/* minmax(0, 1fr), NOT 1fr: a bare 1fr track is minmax(auto, 1fr), so every
   column's floor became the min-content width of its widest nowrap chip and
   the seven columns came out visibly unequal (215px Sun against 370px Thu),
   with the chips overrunning their cells because nothing could ever shrink.
   Zeroing the floor lets the chips' own ellipsis do the work. */
.pcal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); background: var(--hairline); gap: 1px; }
.pd {
  position: relative; background: var(--sheet); min-height: 68px;
  padding: 6px 7px 8px;
  display: flex; flex-direction: column; align-items: stretch; gap: 3px;
}
.pd.out { background: var(--canvas); }
.pd u {
  display: block; text-decoration: none;
  font-size: var(--step--2); color: var(--ink-mute); margin-bottom: 5px;
}
.pd.today u {
  color: #fff8f0; background: var(--sanguine);
  width: 1.65em; height: 1.65em; border-radius: 50%;
  display: grid; place-items: center; margin-bottom: 4px;
}

/* ---- a queued post ---- */
.chip {
  display: flex; align-items: center; gap: 5px;
  /* The real console sets scheduled-post titles in the UI sans, not mono
     (Oded's screenshot). They were inheriting Plex Mono from the calendar
     chrome, which read as code rather than as a post title - and mono is
     wider per character, so it truncated titles that now fit. */
  font-family: var(--sans);
  font-weight: 400; font-size: var(--step--3); line-height: 1.3;
  color: var(--ink-soft); background: var(--canvas);
  border: 1px solid var(--hairline); border-left: 2px solid var(--clay-pale);
  border-radius: 5px; padding: 3px 5px; overflow: hidden;
}
/* the title carries its own truncation: text-overflow does nothing on a flex
   CONTAINER, so a bare text node would have overrun the cell at 1024px */
.chip span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chip i {
  flex: none; width: 11px; height: 11px; border-radius: 2px;
  background-position: center; background-size: contain; background-repeat: no-repeat;
}
.chip[data-p="in"]  i { background-image: url(/logos/linkedin.svg); }
.chip[data-p="x"]   i { background-image: url(/logos/x.svg); }
.chip[data-p="sub"] i { background-image: url(/logos/substack.svg); }
.chip.done { opacity: .55; border-left-color: var(--hairline); }
.chip.live { border-left-color: var(--clay); background: var(--clay-wash); color: var(--ink); }

/* the one annotation that carries "optimal time" without a sentence of copy */
.pcal-slot {
  position: absolute; left: 16%; top: 55.6%; z-index: 4;
  font-family: var(--mono); font-size: var(--step--3); letter-spacing: .04em;
  color: #fff8f0; background: var(--walnut);
  padding: 5px 9px; border-radius: 6px; white-space: nowrap;
  box-shadow: 0 6px 16px -6px rgba(34,26,16,.5);
  opacity: 0;
}
/* a caret so the tag reads as an annotation ON the 10th, not a floating label */
.pcal-slot::before {
  content: ''; position: absolute; left: 16px; top: -4px;
  width: 8px; height: 8px; background: var(--walnut); transform: rotate(45deg);
}

/* ---- the chat window, sitting on the calendar ---- */
.pub-chat {
  position: absolute; right: 1.5%; bottom: 0; width: 40%; max-width: 430px; margin: 0;
  background: var(--sheet); border: 1px solid var(--hairline);
  border-radius: 14px; overflow: hidden; z-index: 5;
  box-shadow: 0 2px 6px rgba(34,26,16,.14), 0 26px 54px -18px rgba(34,26,16,.46);
}
.pchat-bar {
  display: flex; align-items: center; gap: 7px;
  padding: 9px 13px; background: var(--canvas); border-bottom: 1px solid var(--hairline);
  font-family: var(--mono); font-size: var(--step--3); letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-mute);
}
.pchat-body { padding: 13px 14px 14px; display: flex; flex-direction: column; gap: 11px; }
.pchat-draft {
  display: flex; flex-direction: column; gap: 7px;
  font-family: var(--mono); font-size: var(--step--2); line-height: var(--lh-mid);
  color: var(--ink-soft); background: var(--sheet-2);
  border-radius: 9px; padding: 10px 11px;
}
/* Oded: there is no Publish button in the product - you TYPE it. So this is
   an ordinary sent message, in the same dark bubble the chat scenes use for
   the user's own turn, and the confirmation answers it. */
.pchat-say {
  align-self: flex-end; max-width: 88%;
  background: var(--walnut); color: var(--cream-on-dark);
  border-radius: 16px 16px 4px 16px; padding: 9px 14px;
  font-size: var(--step--1); line-height: var(--lh-mid);
}
.pchat-res { display: flex; flex-direction: column; gap: 6px; opacity: 0; }
.pchat-pill {
  align-self: flex-start; display: flex; align-items: center; gap: 6px;
  font-size: var(--step--2); color: var(--green);
  background: #eaf1ea; border-radius: 999px; padding: 4px 11px 4px 8px;
}
.pchat-link {
  font-family: var(--mono); font-size: var(--step--3);
  color: var(--clay-link); word-break: break-all; line-height: var(--lh-mid);
}

/* ============================================================
   The 14s loop. Beats: the week lands (0.5-3.6s), the best-hour
   tag surfaces, the chat rises, the pointer presses Publish,
   the label turns over, the live link comes back, and the 10th
   goes green — the same post, in both places. Then the stage
   clears BEFORE the wrap, so the loop has an ending rather than
   a cut. Every property is declared at every stop.
   ============================================================ */
@keyframes pubChip {
  0%           { opacity: 0; transform: translateY(6px) scale(.94); }
  4%           { opacity: 1; transform: translateY(0) scale(1); }
  88%          { opacity: 1; transform: translateY(0) scale(1); }
  94%, 100%    { opacity: 0; transform: translateY(0) scale(1); }
}
@keyframes pubSlot {
  0%, 20%      { opacity: 0; transform: translateY(4px); }
  25%          { opacity: 1; transform: translateY(0); }
  86%          { opacity: 1; transform: translateY(0); }
  92%, 100%    { opacity: 0; transform: translateY(0); }
}
@keyframes pubChat {
  0%, 28%      { opacity: 0; transform: translateY(16px) scale(.985); }
  34%          { opacity: 1; transform: translateY(0) scale(1); }
  88%          { opacity: 1; transform: translateY(0) scale(1); }
  94%, 100%    { opacity: 0; transform: translateY(0) scale(1); }
}
@keyframes pubSay {
  0%, 44%    { opacity: 0; transform: translateY(8px); }
  48%        { opacity: 1; transform: translateY(0); }
  88%        { opacity: 1; transform: translateY(0); }
  94%, 100%  { opacity: 0; transform: translateY(0); }
}
@keyframes pubRes {
  0%, 56%      { opacity: 0; transform: translateY(5px); }
  61%          { opacity: 1; transform: translateY(0); }
  88%          { opacity: 1; transform: translateY(0); }
  94%, 100%    { opacity: 0; transform: translateY(0); }
}
/* the 10th flips from "going out" to "went out" the moment the link returns */
@keyframes pubLive {
  0%, 59%      { border-left-color: #cf6548; background-color: #f6e3da; }
  63%          { border-left-color: #3c6a48; background-color: #eaf1ea; }
  88%          { border-left-color: #3c6a48; background-color: #eaf1ea; }
  94%, 100%    { border-left-color: #3c6a48; background-color: #eaf1ea; }
}

.js .cap-publish .chip.q, .js .cap-publish .pub-chat, .js .cap-publish .pchat-say,
.js .cap-publish .pchat-res, .js .cap-publish .pcal-slot { opacity: 0; }
.js .cap-publish.in.playing .chip[data-q="1"] { animation: pubChip 14s var(--ease-out) .40s infinite both; }
.js .cap-publish.in.playing .chip[data-q="2"] { animation: pubChip 14s var(--ease-out) .68s infinite both; }
.js .cap-publish.in.playing .chip[data-q="3"] { animation: pubChip 14s var(--ease-out) .96s infinite both; }
.js .cap-publish.in.playing .chip[data-q="4"] { animation: pubChip 14s var(--ease-out) 1.24s infinite both; }
.js .cap-publish.in.playing .chip[data-q="5"] { animation: pubChip 14s var(--ease-out) 1.52s infinite both; }
.js .cap-publish.in.playing .chip[data-q="6"] { animation: pubChip 14s var(--ease-out) 1.84s infinite both; }
.js .cap-publish.in.playing .chip[data-q="7"] { animation: pubChip 14s var(--ease-out) 2.12s infinite both; }
.js .cap-publish.in.playing .chip[data-q="8"] { animation: pubChip 14s var(--ease-out) 2.40s infinite both; }
.js .cap-publish.in.playing .chip[data-q="9"] { animation: pubChip 14s var(--ease-out) 2.68s infinite both; }
.js .cap-publish.in.playing .pcal-slot   { animation: pubSlot 14s var(--ease-out) 0s infinite both; }
.js .cap-publish.in.playing .pub-chat    { animation: pubChat 14s var(--ease-out) 0s infinite both; }
.js .cap-publish.in.playing .pchat-say   { animation: pubSay  14s var(--ease-out) 0s infinite both; }
.js .cap-publish.in.playing .pchat-res   { animation: pubRes  14s var(--ease-out) 0s infinite both; }
.js .cap-publish.in.playing .chip.live   { animation: pubLive 14s linear 0s infinite both; }

/* Reduced motion shows the finished state - message sent, link back. Nothing
   needs force-hiding any more now that the pointer and the button's spare
   labels are gone; the blanket `.js .cap * { opacity: 1 !important }` further
   up already reveals every beat, which for this scene is the right still. */

/* ---- narrower ---- */
@media (max-width: 1099px) {
  /* the card stops floating HERE, not at 900. Measured: above 1100 it needs
     73px of overhang to clear the queued week, but below it the calendar
     loses 38px of row height while the card gains height as it narrows, so
     the requirement jumps to 90px and reaches 108px at 901. That much dead
     air under the month costs more than the overlap buys. */
  .pub-stage { padding-bottom: 0; }
  .pub-chat { position: static; width: 100%; max-width: none; margin-top: 16px; }
  .pd { min-height: 60px; }
  /* ...and once it is static it owns 296px of layout rather than floating over
     the month, so the rise-in leaves a hole that size. Measured on a 390px
     viewport: the card is invisible from 88% through 34% of the next cycle -
     6.5s of every 14s staring at a gap. Off the overlay it should simply BE
     there; the beats inside it (the typed message, the confirmation) still
     play, and they cannot shift layout because both keep their box. */
  /* must match the driver's specificity (.js .cap-publish.in.playing .pub-chat,
     0-4-0) - the plainer selector lost the cascade and the hole stayed */
  .js .cap-publish.in.playing .pub-chat { opacity: 1; animation: none; }
}
@media (max-width: 640px) {
  /* seven columns cannot hold a title, so the chip becomes its own marker:
     the platform glyph and the clay rule, which is all the density needs */
  .pd { min-height: 44px; padding: 4px 3px 5px; }
  .pd u { font-size: 10px; text-align: center; margin-bottom: 3px; }
  .pd.today u { margin: 0 auto 3px; }
  .chip { font-size: 0; gap: 0; padding: 2px; justify-content: center; border-left-width: 2px; }
  .chip i { width: 12px; height: 12px; }
  .pcal-dow i { font-size: 9px; letter-spacing: .02em; }
  .pcal-bar b { font-size: var(--step--1); }
  /* under the 10th there is no room left once cells are 50px, so the tag
     moves into the month's genuinely empty first row */
  .pcal-slot { left: 3%; top: 25%; font-size: 10px; padding: 4px 7px; }
  .pcal-slot::before { display: none; }
}

/* ============================================================
   PHOTO-SEED PHONE — the cursor taps "Make it a seed too",
   matching the Add-take tap on the newsjacking phone. Oded:
   the two buttons should behave the same way.

   The tap needed room that did not exist: the button lands at
   41.8% of the 11s loop and the live-post reply landed at 50.9%,
   a 1.3s window for approach, press and release. The reply is
   therefore pushed to 64% HERE ONLY — scoped to .cap-seed, not
   to a positional selector, because the shared tgStep4 also
   drives the voice-memo phone's fourth message.
   ============================================================ */
.togo-sec .cap-seed .tg-btn { position: relative; }
@keyframes tgsPtr {
  0%, 45%   { opacity: 0; transform: translate(26px, 30px) scale(1); }
  48%       { opacity: 1; transform: translate(26px, 30px) scale(1); }
  55%       { opacity: 1; transform: translate(0, -14px) scale(1); }
  57%       { opacity: 1; transform: translate(0, -14px) scale(.82); }
  59%       { opacity: 1; transform: translate(0, -14px) scale(1); }
  63%, 100% { opacity: 0; transform: translate(0, -14px) scale(1); }
}
@keyframes tgsBtnPress {
  0%, 56%     { filter: none; }
  57%, 58.5%  { filter: brightness(1.25); }
  60%, 100%   { filter: none; }
}
/* same curve as tgStep4, slid later so it lands AFTER the press */
@keyframes tgsStep4 {
  0%      { opacity: 0; transform: translateY(9px); }
  64%     { opacity: 0; transform: translateY(9px); }
  66.7%   { opacity: 1; transform: none; }
  96%     { opacity: 1; transform: none; }
  100%    { opacity: 0; transform: translateY(-4px); }
}
.js .togo-sec .cap-seed.in.playing .tg-btn .tg-ptr { animation: tgsPtr 11s var(--ease-soft) 0s infinite both; }
.js .togo-sec .cap-seed.in.playing .tg-btn[data-step="3"] { animation-name: tgStep3, tgsBtnPress; animation-duration: 11s, 11s; animation-timing-function: var(--ease-out), linear; animation-iteration-count: infinite, infinite; animation-fill-mode: both, both; }
.js .togo-sec .cap-seed.in.playing .tg-body > [data-step="4"] { animation: tgsStep4 11s var(--ease-out) 0s infinite both; }

/* ============================================================
   HERO PROOF — two additions under the fold-line, both asked
   for by Oded (30 Aug).

   1. The count line under the first CTA. The avatars are an
      illustrative cluster, NOT identified customers: no names,
      no roles, aria-hidden, so nothing on the page claims these
      five people said or did anything.
   2. Shachar Abir's quote under the video. That one IS real -
      his words, his name, his own photograph.
   ============================================================ */
.social-proof {
  display: flex; align-items: center; justify-content: center;
  gap: 13px; flex-wrap: nowrap;
  /* Measured: faces 142 + gap 13 + the line at 417 = 572, against a 62ch cap
     that resolved to 571.4 - one pixel short, which orphaned "using Liftli"
     onto a second line. 70ch leaves room for the copy to grow a little. */
  max-width: 70ch; margin: clamp(14px, 2.2vh, 24px) auto 0;
  font-size: var(--step--1); line-height: var(--lh-mid); color: var(--ink-mute);
}
/* the sentence needs to BE a flex item. As a bare text node it became an
   anonymous one that took the full row, pushing the faces onto a line of
   their own - the opposite of the reference, where they sit side by side. */
.sp-text { text-align: left; min-width: 0; }
.sp-faces { display: inline-flex; flex: none; }
.sp-faces img {
  width: 38px; height: 38px; border-radius: 50%; object-fit: cover;
  /* the ring is the hero's own ground, so the stack reads as cut-outs
     rather than as five bordered thumbnails */
  border: 2px solid var(--canvas);
  box-shadow: 0 1px 2px rgba(34,26,16,.16);
}
.sp-faces img + img { margin-left: -12px; }

.hq-who { display: flex; flex-direction: column; text-align: left; }
.hq-who b { font-weight: 500; font-size: var(--step--1); color: var(--ink); line-height: 1.25; }
.hq-who i { font-style: normal; font-size: var(--step--2); color: var(--ink-mute); }

/* ---- proof row: three customer quotes directly under the hero. Replaced the
   founder-video poster on 2026-09-10 (parked in snippets/hero-video-poster.html).
   Each card opens with a full-bleed square portrait whose foot dissolves into
   the paper along a soft curve; the words sit BELOW it on solid paper, never
   over the face. The box is square (not 5:4) because Shachar's chin sits at
   ~69% of his frame and the fade must start under it. Colours untouched. ---- */
.proof-row {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px;
  width: 100%; max-width: 900px; margin: clamp(22px, 3.5vh, 36px) auto 0;
  text-align: left;
}
.proof-card {
  display: flex; flex-direction: column; margin: 0; overflow: hidden;
  background: var(--sheet); border: 1px solid var(--hairline); border-radius: var(--radius);
  box-shadow: var(--shadow-rest);
}
.proof-photo { display: block; position: relative; width: 100%; aspect-ratio: 1 / 1; }
.proof-photo img {
  /* absolutely positioned so the box's height comes from aspect-ratio alone; as a
     flow child the square image forced the box square whatever the ratio said */
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% 0;
}
/* the dissolve: clear to ~74% of the box at the centre, curving higher at the
   sides, fully paper at the foot */
.proof-photo::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(140% 100% at 50% 0%, rgba(251,248,242,0) 74%, var(--sheet) 100%);
}
/* position + z-index: the words tuck 6px up into the portrait's dissolve, and the
   dissolve is an absolutely positioned ::after that would otherwise paint paper over
   the first line's ascenders (seen on a real phone, 09-11) */
.proof-body { display: flex; flex-direction: column; flex: 1; gap: 10px; padding: 0 20px 18px; margin-top: -6px; position: relative; z-index: 1; }
.proof-card blockquote {
  margin: 0; flex: 1; font-size: var(--step--1); line-height: var(--lh-body);
  color: var(--ink-soft); text-wrap: pretty;
}
.proof-card blockquote b { color: var(--ink); font-weight: 600; }
.proof-card .hq-who { margin-top: 4px; }
@media (max-width: 900px) {
  /* phones: one swipeable row, not a stack. Three full-height cards one under
     the other pushed the product a whole screen further down (owner, 09-11);
     a snap carousel keeps the proof to one row and the faces to a glance. */
  .proof-row {
    display: flex; gap: 12px; max-width: none; margin-top: 12px;
    overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory;
    /* a horizontal scroller clips vertically too: give the card's top edge and shadow
       real headroom (the 2px it had shaved the border on a real phone, 09-11) */
    scroll-padding: 0 7vw; padding: 12px 7vw 14px; scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .proof-row::-webkit-scrollbar { display: none; }
  .proof-card { flex: 0 0 min(72vw, 290px); scroll-snap-align: center; }
  /* 5:4 keeps Shachar's chin (69% down his square frame) above the dissolve, which
     starts at 86% here; a 4:3 box put it inside the fade */
  .proof-photo { aspect-ratio: 5 / 4; }
  .proof-photo::after { background: radial-gradient(140% 100% at 50% 0%, rgba(251,248,242,0) 86%, var(--sheet) 100%); }
  .proof-body { padding: 0 16px 16px; }
  .proof-card blockquote { font-size: var(--step--2); }
}

/* the two new blocks join the hero's staggered entrance */
.js .h-enter.e4b { animation-delay: 270ms; }

@media (max-width: 640px) {
  /* below 640 the row cannot hold both, so the cluster goes back on top
     and the sentence centres under it */
  .social-proof { gap: 10px; font-size: var(--step--2); flex-wrap: wrap; }
  .sp-text { text-align: center; }
  .sp-faces img { width: 32px; height: 32px; }
  .sp-faces img + img { margin-left: -10px; }
}

/* ============================================================
   LAUNCH BANNER — "works with Grok Bot like a charm".
   Sits ABOVE the header in the flow, so it pushes the page down
   instead of covering the nav; the sticky header keeps working
   because the bar scrolls away with the rest of the document.
   Dismissible and remembered, so it never nags twice.
   ============================================================ */
.topbar {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  position: relative; z-index: 60;
  padding: 9px 44px 9px 18px;
  background: var(--walnut); color: var(--cream-on-dark);
  font-size: var(--step--1); line-height: 1.35; text-align: center;
}
html.topbar-hidden .topbar { display: none; }
.tb-new {
  flex: none;
  font-family: var(--mono); font-size: var(--step--3); letter-spacing: .1em;
  text-transform: uppercase; color: var(--walnut);
  background: var(--gold-glint); border-radius: 5px; padding: 2px 7px;
}
.tb-txt { min-width: 0; }
.tb-x {
  /* 44px, not the 28 it shipped at - the icon stays 14px, the TARGET grows */
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  display: grid; place-items: center; width: 44px; height: 44px;
  background: none; border: 0; border-radius: 50%; cursor: pointer;
  color: var(--mute-on-dark);
  transition: color var(--t-micro) var(--ease-soft), background-color var(--t-micro) var(--ease-soft);
}
.tb-x svg { width: 14px; height: 14px; }
.tb-x:hover { color: var(--cream-on-dark); }
.tb-x:hover svg { background: rgba(255,255,255,.09); border-radius: 50%; }
@media (max-width: 640px) {
  .topbar { font-size: var(--step--2); padding: 8px 40px 8px 12px; gap: 8px; }
  .tb-new { font-size: 9.5px; padding: 2px 6px; }
}
@media (prefers-reduced-motion: reduce) { .tb-x { transition: none; } }


/* Keyboard users and screen readers need a way past the nav on all 151 pages.
   Off-canvas until focused - never visible to a mouse, always first in tab
   order. */
.skip-link {
  position: absolute; left: 8px; top: -60px; z-index: 200;
  background: var(--walnut); color: var(--cream-on-dark);
  padding: 10px 16px; border-radius: 0 0 10px 10px;
  font-size: var(--step--1); text-decoration: none;
  transition: top 140ms var(--ease-soft);
}
.skip-link:focus { top: 0; outline: 2px solid var(--clay); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .skip-link { transition: none; } }

/* ============================================================
   DAILY POST PHONE (togo D, 2026-09-10). Same carriage-scroll model as
   the news phone: the whole thread exists from frame one, later messages
   sit below the fold, and the .tg-scroll carriage slides up by MEASURED
   offsets as each message lands. Its own clock (16s) and its own class,
   so the news phone's .cap.flip timings never touch it.
   ============================================================ */
@media (min-width: 901px) {
  .cap.cap-daily { grid-template-columns: minmax(0, 6fr) minmax(0, 5fr); }
  .cap.cap-daily .cap-copy { order: 2; }
}
.tg-draft .tgd-head { display: block; font-weight: 600; margin-bottom: 7px; }
.tg-para { display: block; margin-top: 8px; }
/* Telegram's inline keyboard: two per row, a full-width row when asked */
.tg-kb { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; align-self: stretch; }
.tg-kb .tg-btn { position: relative; width: auto; align-self: auto; justify-content: center; padding: 8px 9px; font-size: 11.5px; overflow: visible; white-space: normal; }
.tg-kb .tg-btn--full { grid-column: 1 / -1; }
.togo-sec .cap-daily .tg-body { display: block; }
.togo-sec .cap-daily .tg-scroll { display: flex; flex-direction: column; gap: 7px; min-height: 100%; justify-content: flex-start; }
/* offsets MEASURED in the browser 2026-09-10 (phone is a fixed-size artifact):
   296px phone / 468px body: 128 / 192 / 230 · 272px / 407px: 242 / 324 / 376 */
.togo-sec .cap-daily .tg-scroll { --d1: -128px; --d2: -192px; --d3: -230px; }
@media (max-width: 900px) { .togo-sec .cap-daily .tg-scroll { --d1: -242px; --d2: -324px; --d3: -376px; } }
@keyframes tgdScroll {
  0%, 27%   { transform: translateY(0); }
  32%       { transform: translateY(var(--d1)); }
  49%       { transform: translateY(var(--d1)); }
  54%       { transform: translateY(var(--d2)); }
  64%       { transform: translateY(var(--d2)); }
  69%, 100% { transform: translateY(var(--d3)); }
}
@keyframes tgdIn1 { 0%, 2% { opacity: 0; transform: translateY(9px); } 5% { opacity: 1; transform: none; } 96% { opacity: 1; transform: none; } 100% { opacity: 0; transform: none; } }
@keyframes tgdIn2 { 0%, 9% { opacity: 0; transform: translateY(9px); } 12% { opacity: 1; transform: none; } 96% { opacity: 1; transform: none; } 100% { opacity: 0; transform: none; } }
@keyframes tgdIn3 { 0%, 28% { opacity: 0; transform: translateY(9px); } 31% { opacity: 1; transform: none; } 96% { opacity: 1; transform: none; } 100% { opacity: 0; transform: none; } }
@keyframes tgdIn4 { 0%, 35% { opacity: 0; transform: translateY(9px); } 38% { opacity: 1; transform: none; } 96% { opacity: 1; transform: none; } 100% { opacity: 0; transform: none; } }
@keyframes tgdIn5 { 0%, 40% { opacity: 0; transform: translateY(9px); } 43% { opacity: 1; transform: none; } 96% { opacity: 1; transform: none; } 100% { opacity: 0; transform: none; } }
@keyframes tgdIn6 { 0%, 60% { opacity: 0; transform: translateY(9px); } 63% { opacity: 1; transform: none; } 96% { opacity: 1; transform: none; } 100% { opacity: 0; transform: none; } }
@keyframes tgdIn7 { 0%, 66% { opacity: 0; transform: translateY(9px); } 69% { opacity: 1; transform: none; } 96% { opacity: 1; transform: none; } 100% { opacity: 0; transform: none; } }
/* two taps: Approve (~23%) and In 2 minutes (~54%) */
@keyframes tgdPtr1 {
  0%, 15%  { opacity: 0; transform: translate(26px, 30px); }
  18%      { opacity: 1; transform: translate(26px, 30px); }
  22%      { opacity: 1; transform: translate(0, -14px); }
  23.5%    { opacity: 1; transform: translate(0, -14px) scale(.82); }
  25%      { opacity: 1; transform: translate(0, -14px) scale(1); }
  28%, 100%{ opacity: 0; transform: translate(0, -14px); }
}
@keyframes tgdPress1 { 0%, 22.5% { filter: none; } 23.5%, 24.5% { filter: brightness(1.25); } 25.5%, 100% { filter: none; } }
@keyframes tgdPtr2 {
  0%, 46%  { opacity: 0; transform: translate(26px, 30px); }
  49%      { opacity: 1; transform: translate(26px, 30px); }
  53%      { opacity: 1; transform: translate(0, -14px); }
  54.5%    { opacity: 1; transform: translate(0, -14px) scale(.82); }
  56%      { opacity: 1; transform: translate(0, -14px) scale(1); }
  59%, 100%{ opacity: 0; transform: translate(0, -14px); }
}
@keyframes tgdPress2 { 0%, 53.5% { filter: none; } 54.5%, 55.5% { filter: brightness(1.25); } 56.5%, 100% { filter: none; } }
.js .togo-sec .cap-daily.in.playing .tg-scroll { animation: tgdScroll 16s var(--ease-soft) 0s infinite both; }
.js .togo-sec .cap-daily.in.playing .tg-scroll > [data-step="1"] { animation: tgdIn1 16s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap-daily.in.playing .tg-scroll > [data-step="2"] { animation: tgdIn2 16s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap-daily.in.playing .tg-scroll > [data-step="3"] { animation: tgdIn3 16s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap-daily.in.playing .tg-scroll > [data-step="4"] { animation: tgdIn4 16s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap-daily.in.playing .tg-scroll > [data-step="5"] { animation: tgdIn5 16s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap-daily.in.playing .tg-scroll > [data-step="6"] { animation: tgdIn6 16s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap-daily.in.playing .tg-scroll > [data-step="7"] { animation: tgdIn7 16s var(--ease-out) 0s infinite both; }
.js .togo-sec .cap-daily.in.playing [data-step="2"] .tg-btn:first-child { animation: tgdPress1 16s linear 0s infinite both; }
.js .togo-sec .cap-daily.in.playing [data-step="2"] .tg-btn:first-child .tg-ptr { animation: tgdPtr1 16s var(--ease-soft) 0s infinite both; }
.js .togo-sec .cap-daily.in.playing [data-step="5"] .tg-btn:first-child { animation: tgdPress2 16s linear 0s infinite both; }
.js .togo-sec .cap-daily.in.playing [data-step="5"] .tg-btn:first-child .tg-ptr { animation: tgdPtr2 16s var(--ease-soft) 0s infinite both; }
@media (prefers-reduced-motion: reduce) {
  .js .cap-daily .tg-scroll, .js .cap-daily .tg-scroll > [data-step], .js .cap-daily .tg-btn, .js .cap-daily .tg-ptr { animation: none !important; }
  .js .cap-daily .tg-scroll > [data-step] { opacity: 1; }
  /* !important: the site-wide reduced-motion reset (.js .cap * { transform: none !important })
     would otherwise park the carriage at the top and hide the Scheduled beat */
  .js .cap-daily .tg-scroll { transform: translateY(var(--d3)) !important; }
}
/* the daily phone sits on a flipped row too: mirror the tilt like .cap.flip */
@media (min-width: 900px) and (prefers-reduced-motion: no-preference) {
  .togo-sec .cap-daily .tgphone {
    transform: rotateY(15deg) rotateX(2deg);
    box-shadow:
      0 0 0 1px rgba(20,15,8,.55),
      inset 0 0 0 1px rgba(255,244,224,.10),
      -18px 10px 26px -14px rgba(34,26,16,.34),
      -44px 40px 74px -30px rgba(34,26,16,.62);
  }
  .togo-sec .cap-daily .tgfig:hover .tgphone { transform: rotateY(0) rotateX(0); }
}

/* ============================================================
   RESULTS, second screen (2026-09-10): the Liftli app's Analytics tab
   under the Claude scene. Built from the 2026-09-04 analytics mockup (Paper
   Atelier); the current-period figures and the daily series are Oded's real
   28-day numbers, the previous-period figures are illustrative (owner's call,
   every delta reads up). Static SVG, no chart library.
   ============================================================ */
.scene-stack { display: flex; flex-direction: column; gap: 18px; min-width: 0; }
.dash-shot { margin: 0; }
.dash {
  background: var(--sheet); border: 1px solid var(--hairline); border-radius: var(--radius);
  box-shadow: var(--shadow-rest); padding: 0 0 12px; overflow: hidden; font-size: 11.5px; color: var(--ink-soft);
}
.dash-bar { display: flex; align-items: center; justify-content: space-between; padding: 9px 14px; border-bottom: 1px solid var(--hairline); background: color-mix(in srgb, var(--sheet) 70%, var(--canvas)); }
.dash-brand { display: inline-flex; align-items: center; gap: 6px; font-family: var(--serif); font-style: italic; font-size: 13px; color: var(--ink); }
.dash-tabs { display: inline-flex; gap: 12px; font-size: 11px; color: var(--ink-mute); }
.dash-tabs i, .dash-seg i { font-style: normal; }
.dash-tabs .on { color: var(--ink); font-weight: 600; }
.dash-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; padding: 12px 14px 8px; }
.dash-title { font-family: var(--serif); font-size: 17px; color: var(--ink); display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.dash-title small { font-family: var(--sans); font-size: 10.5px; color: var(--ink-mute); display: inline-flex; align-items: center; gap: 5px; }
.dash-pulse { width: 6px; height: 6px; border-radius: 50%; background: #3c6a48; box-shadow: 0 0 0 3px rgba(60,106,72,.14); }
.dash-seg { display: inline-flex; background: var(--canvas); border: 1px solid var(--hairline); border-radius: 7px; padding: 2px; font-size: 10.5px; color: var(--ink-mute); }
.dash-seg i { padding: 2px 8px; border-radius: 5px; }
.dash-seg .on { background: var(--sheet); color: var(--ink); box-shadow: var(--shadow-rest); }
.dash-kpis { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; padding: 0 14px; }
.dk { display: flex; flex-direction: column; gap: 1px; padding: 9px 10px 6px; background: var(--canvas); border: 1px solid var(--hairline); border-radius: 9px; min-width: 0; }
.dk-l { font-size: 9.5px; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-mute); font-weight: 500; }
.dk-v { font-size: 19px; font-weight: 600; letter-spacing: -.02em; color: var(--ink); line-height: 1.2; margin-top: 2px; font-variant-numeric: tabular-nums; }
.dk-p { font-size: 9.5px; color: var(--ink-mute); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dk-d { display: inline-block; width: fit-content; margin-top: 3px; padding: 1px 5px; border-radius: 4px; font-size: 9.5px; font-weight: 500; font-variant-numeric: tabular-nums; }
.dk-d.down { color: var(--sanguine); background: rgba(164,67,47,.10); }
.dk-d.up { color: #3c6a48; background: rgba(60,106,72,.12); }
.dk svg { width: 100%; height: 22px; margin-top: 6px; }
.dash-chart { padding: 10px 14px 0; }
.dash-legend { display: flex; align-items: center; gap: 6px; font-size: 10px; color: var(--ink-mute); margin-bottom: 4px; flex-wrap: wrap; }
.dash-legend i { width: 10px; height: 3px; border-radius: 2px; display: inline-block; }
.dash-legend i + * { margin-left: 2px; }
.dash-legend .imp { background: var(--clay); } .dash-legend .eng { background: var(--ink-soft); margin-left: 8px; }
.dash-legend em { font-style: normal; margin-left: auto; }
.dash-chart svg { display: block; width: 100%; height: auto; }
@media (max-width: 640px) {
  .dash-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dash-tabs { display: none; }
}
