/*
 * Self-hosted faces, subset to what this page can render.
 *
 * Source: the Google Fonts css2 response for Space Grotesk 600/700, Inter
 * 400/500/600 and JetBrains Mono 400/500. Their latin woff2 files were then cut
 * down with fontTools to ASCII plus the punctuation this voice uses (the em
 * dash above all), a small set of Western-European accented letters for names,
 * and the layout features the page depends on -- kern, liga, clig, calt, and
 * tnum, which is what `font-variant-numeric:tabular-nums` needs to work.
 *
 * 246KB to 156KB across the seven. The regeneration command lives in
 * docs/fonts.md; the files are named -subset because they are cached immutable
 * for a year, so changed bytes have to mean a changed name.
 *
 * This exists so the critical path holds no third-party stylesheet. The Google
 * stylesheet was render-blocking at 598-663ms because it is a request to
 * another origin that then names the font files, so nothing could start
 * downloading a face until it came back.
 */

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/inter-400-subset.woff2) format('woff2');
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(fonts/inter-500-subset.woff2) format('woff2');
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(fonts/inter-600-subset.woff2) format('woff2');
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/jetbrains-mono-400-subset.woff2) format('woff2');
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(fonts/jetbrains-mono-500-subset.woff2) format('woff2');
}
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(fonts/space-grotesk-600-subset.woff2) format('woff2');
}
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/space-grotesk-700-subset.woff2) format('woff2');
}

:root{
  /* ---------- ground ---------- */
  --bg:#0D1210;
  --ground-line:#1E2724;      /* decorative separators only */
  --line-control:#566660;     /* the visible boundary of a control: 3.1:1 on --bg */
  --line-strong:#2E3A36;      /* hover state of a control boundary */
  --scroll-thumb:#2A3532;

  --surface-thread:#111715;   /* the one surface doing real work */
  --bubble-in:#2B3A36;        /* their reply, forward */
  --bubble-out:#1C2422;       /* our message, receding */

  /* ---------- ink ---------- */
  --cold:#5B7480;             /* dormant traces and large display text */
  --cold-text:#7E96A1;        /* the same idea at small sizes, contrast-safe */

  --amber:#FFB020;
  --amber-hi:#FFC352;         /* hover only */
  --amber-soft:#E5CD97;
  --amber-ink:#8A6A12;        /* amber that survives the paper band, 4.4:1 */
  --amber-link:#7E6010;       /* the same at body size, 5.2:1 */
  --btn-ink:#150F00;
  --red:#FF7A6B;

  --text:#EDF1EE;
  --text-dim:#94A8A2;

  /* The calculator inverts. See the band comment further down for why. */
  --paper:#F2F0EB;
  --paper-well:#FFFFFF;
  --ink:#131A18;
  --ink-dim:#4E5B57;

  /* ---------- alpha ---------- */
  --amber-a25:rgba(255,176,32,.25);   /* selection, focus rings */
  --amber-a45:rgba(255,176,32,.45);   /* borders and hairlines that must read */
  --red-a50:rgba(255,122,107,.5);
  --white-a04:rgba(255,255,255,.04);
  --ink-a12:rgba(19,26,24,.12);       /* paper-band separators */
  --ink-a16:rgba(19,26,24,.16);       /* paper-band control boundaries */
  --scrim:rgba(3,6,5,.86);            /* behind the screenshot viewer, below --bg */

  /* ---------- type ---------- */
  --font-display:'Space Grotesk', system-ui, sans-serif;
  --font-body:'Inter', system-ui, -apple-system, sans-serif;
  --font-mono:'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  /*
   * The scale is in rem so a visitor who has raised their browser's default
   * text size gets a page that answers. Browser *zoom* scales px fine, which is
   * why this read as satisfied for so long — but the buyer this page is written
   * for is reading on a phone between jobs, and the setting that person is most
   * likely to have changed is the one px ignored. `html` sets no font-size, so
   * 1rem is whatever they chose. Every value below is its old pixel size over
   * 16, so nothing moves at the default.
   */
  --text-xs:0.6875rem;        /* 11px · mono metadata */
  --text-sm:0.8125rem;        /* 13px · every small explanatory line */
  --text-md:0.90625rem;       /* 14.5px · transcript, ledger rows, chips */
  --text-ui:0.96875rem;       /* 15.5px · the button */
  --text-base:1rem;           /* 16px */
  --text-lg:1.1875rem;        /* 19px */
  --text-xl:1.5rem;           /* 24px */

  /*
   * The fluid steps keep their viewport term and take rem bounds. The vw in the
   * middle is deliberately left alone: solving these for a rem component while
   * holding both anchor widths gives a coefficient of zero, so adding one would
   * change what every visitor sees in order to serve a few. Bounds in rem are
   * enough — raise the default text size and the floor rises with it, which is
   * what pins the display type larger on the widths where it used to shrink.
   */
  --lede:clamp(1.0625rem,1.9vw,1.25rem);        /* 17 → 20px */
  --display-3:clamp(1.625rem,3.4vw,2.25rem);    /* 26 → 36px */
  --display-2:clamp(1.75rem,4.4vw,2.875rem);    /* 28 → 46px */
  --display-1:clamp(2.5rem,7.2vw,5.25rem);      /* 40 → 84px */
  --figure:clamp(2.5rem,6vw,4rem);              /* 40 → 64px */

  /* ---------- space ---------- */
  --s-0:2px;
  --s-1:4px;
  --s-2:8px;
  --s-3:12px;
  --s-4:16px;
  --s-5:24px;
  --s-6:36px;
  --s-7:48px;
  --s-8:64px;
  --s-9:88px;
  --s-10:120px;

  /* ---------- geometry ---------- */
  --r-xs:4px;
  --r-sm:6px;
  --r-md:14px;
  --r-pill:999px;

  --wrap:1080px;
  --wrap-wide:1240px;

  --ease:cubic-bezier(.16,1,.3,1);
}

*{
  box-sizing:border-box; margin:0; padding:0;
  scrollbar-width:thin; scrollbar-color:var(--scroll-thumb) transparent;
}

html{scroll-behavior:smooth;}
body{
  background:var(--bg);
  color:var(--text);
  font-family:var(--font-body);
  font-size:var(--text-base);
  line-height:1.55;
  overflow-x:hidden;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}

/* The surfaces the browser draws for you still belong to the design. */
::selection{background:var(--amber-a25); color:var(--text);}

/*
 * Eleven viewports of single-column scroll with no navigation: a keyboard or
 * screen-reader visitor who wants the booking section should not have to walk
 * the demo, the qualifier and the ledger to reach it.
 */
.skip{
  position:absolute; left:var(--s-4); top:var(--s-4); z-index:10;
  transform:translateY(-200%);
  padding:var(--s-3) var(--s-4); border-radius:var(--r-sm);
  background:var(--amber); color:var(--btn-ink);
  font-size:var(--text-sm); font-weight:600; text-decoration:none;
}
.skip:focus-visible{transform:translateY(0);}
:focus-visible{outline:2px solid var(--amber); outline-offset:var(--s-1); border-radius:var(--r-xs);}
*::-webkit-scrollbar{width:9px; height:9px;}
*::-webkit-scrollbar-thumb{background:var(--scroll-thumb); border-radius:var(--r-pill);}
*::-webkit-scrollbar-track{background:transparent;}

a{color:var(--amber); text-decoration-thickness:1px; text-underline-offset:3px;}

.wrap{max-width:var(--wrap); margin:0 auto; padding:0 var(--s-5);}

/*
 * The page alternates ground rather than ruling lines between sections: dark
 * for the argument, paper for the arithmetic, dark again for the decision. The
 * change of ground is the separator, so there is no hairline to maintain.
 */
section{padding:var(--s-10) 0;}
@media (max-width:720px){section{padding:var(--s-8) 0;}}

/* ---------- type ---------- */

h1{
  font-family:var(--font-display); font-weight:700;
  font-size:var(--display-1); line-height:1.02; letter-spacing:-.03em;
  max-width:14ch; text-wrap:balance;
}
h2{
  font-family:var(--font-display); font-weight:700;
  font-size:var(--display-2); line-height:1.08; letter-spacing:-.025em;
  max-width:20ch; text-wrap:balance;
}
p{max-width:66ch;}

.lede{font-size:var(--lede); color:var(--text-dim); margin-top:var(--s-5); max-width:52ch;}
.note{font-size:var(--text-sm); color:var(--text-dim); line-height:1.65;}
.note--spaced{margin-top:var(--s-6);}
.data{font-family:var(--font-mono); font-variant-numeric:tabular-nums;}

/* ---------- the one button ---------- */

.btn{
  display:inline-flex; align-items:center; gap:var(--s-3);
  font-family:var(--font-body); font-size:var(--text-ui); font-weight:600; letter-spacing:-.01em;
  background:var(--amber); color:var(--btn-ink); border:none;
  padding:var(--s-4) var(--s-5); border-radius:var(--r-sm); cursor:pointer; text-decoration:none;
  min-height:48px;
  transition:background .18s var(--ease), transform .18s var(--ease),
             color .18s var(--ease), border-color .18s var(--ease);
}
.btn:hover{background:var(--amber-hi); transform:translateY(-1px);}
.btn:active{transform:translateY(0);}
.btn--quiet{
  background:transparent; color:var(--text); border:1px solid var(--line-control);
  font-weight:500;
}
.btn--quiet:hover{background:var(--white-a04); border-color:var(--line-strong); transform:none;}

/* ---------- hero ---------- */

.hero{padding:clamp(88px,17vh,180px) 0 clamp(64px,14vh,140px);}
.hero h1 em{font-style:normal; color:var(--cold);}

/* A live monitor under the headline: the whole premise in one line. */
.hero-trace{margin:var(--s-6) 0; max-width:640px;}
.hero-trace svg{width:100%; height:auto; display:block;}
.hero-trace .wave{
  stroke:var(--amber); stroke-width:1.75; fill:none;
  stroke-linecap:round; stroke-linejoin:round; vector-effect:non-scaling-stroke;
}
/*
 * Translate by exactly one waveform pitch (320 units). Because the geometry
 * repeats at that pitch, the end state is pixel-identical to the start and the
 * loop is invisible — a monitor running, not a shape sliding back to its mark.
 */
.hero-trace .sweep{animation:sweep 3.4s linear infinite;}
/* Paused once the hero is off screen, the way the strip already is. Twelve
   animating SVGs below the fold was the reason that rule exists; this one was
   simply missed. */
.hero[data-visible="false"] .sweep{animation-play-state:paused;}
@keyframes sweep{
  from{transform:translateX(0);}
  to{transform:translateX(-320px);}
}

.hero-actions{display:flex; gap:var(--s-3); flex-wrap:wrap; align-items:center; margin-top:var(--s-6);}
.hero-meta{margin-top:var(--s-6); font-size:var(--text-sm); color:var(--text-dim);}

/* ---------- demo ---------- */

.demo-lede{display:flex; justify-content:space-between; align-items:flex-end; gap:var(--s-6); flex-wrap:wrap;}
.demo-lede .note{max-width:34ch;}
.demo-lede h2 em{font-style:normal; color:var(--cold);}

/*
 * Choice and trigger in one place: the chips run the demo, and the transport
 * control sits with them rather than below a 340px transcript where it is
 * off-screen exactly while it is useful.
 */
.controls{
  display:flex; align-items:center; gap:var(--s-3); flex-wrap:wrap;
  margin:var(--s-7) 0 var(--s-2);
}
.trades{display:flex; gap:var(--s-2); flex-wrap:wrap;}
.trade{
  font-family:var(--font-body); font-size:var(--text-md); font-weight:500;
  padding:0 var(--s-4); min-height:44px;
  background:transparent; color:var(--text-dim);
  border:1px solid var(--line-control); border-radius:var(--r-pill); cursor:pointer;
  transition:color .18s var(--ease), border-color .18s var(--ease);
}
.trade:hover{color:var(--text); border-color:var(--line-strong);}
.trade[aria-pressed="true"]{color:var(--amber); border-color:var(--amber-a45);}
.control-hint{font-size:var(--text-sm); color:var(--text-dim); margin-bottom:var(--s-6);}

/* The strip: bare traces on the ground, no cards. */
.strip{display:grid; grid-template-columns:repeat(6,1fr); gap:var(--s-5); margin-bottom:var(--s-3);}
.trace svg{width:100%; height:26px; display:block;}
/*
 * Stacked, not spread. Spread across a narrow column put each trace's state
 * next to the following trace's id, so the row read as one run-on string.
 */
.trace-id{
  display:flex; flex-direction:column; gap:var(--s-0); margin-top:var(--s-2);
  font-family:var(--font-mono); font-size:var(--text-xs); color:var(--text-dim);
  font-variant-numeric:tabular-nums;
}
.trace-state{color:var(--cold-text);}
.trace[data-state="alive"] .trace-state{color:var(--amber);}
.trace[data-state="suppressed"] .trace-state{color:var(--red);}

@media (max-width:720px){
  /* Only the two that change state — six flat lines on a phone say nothing. */
  .strip{grid-template-columns:1fr 1fr; gap:var(--s-4);}
  .strip .trace:not([data-role]){display:none;}
}

.line{fill:none; stroke-width:1.6; vector-effect:non-scaling-stroke;}
.line.flat{stroke:var(--cold); opacity:1; transition:opacity .6s var(--ease);}
.line.pulse{stroke:var(--amber); opacity:0; transition:opacity .6s var(--ease);}
[data-state="alive"] > svg .flat{opacity:0;}
[data-state="alive"] > svg .pulse{opacity:1;}
[data-state="suppressed"] > svg .flat{stroke:var(--red); opacity:.5;}

.ecg-track{animation:scrollECG 2.8s linear infinite;}
@keyframes scrollECG{from{transform:translateX(0);} to{transform:translateX(-200px);}}
[data-demo="root"][data-visible="false"] .ecg-track{animation-play-state:paused;}

.stage{display:grid; grid-template-columns:200px 1fr; gap:var(--s-7); align-items:start; margin-top:var(--s-7);}
@media (max-width:820px){.stage{grid-template-columns:1fr; gap:var(--s-5);}}

.subject{position:sticky; top:var(--s-6);}
@media (max-width:820px){.subject{position:static;}}
.subject svg{width:100%; height:44px; display:block;}
.subject-name{font-family:var(--font-display); font-size:var(--text-lg); font-weight:600; margin-top:var(--s-4); letter-spacing:-.01em;}
.subject-meta{font-family:var(--font-mono); font-size:var(--text-xs); color:var(--text-dim); margin-top:var(--s-2); line-height:1.6;}
.subject-state{
  display:inline-block; margin-top:var(--s-4);
  font-family:var(--font-mono); font-size:var(--text-xs); color:var(--cold-text);
}
.subject[data-state="alive"] .subject-state{color:var(--amber);}
.subject[data-state="suppressed"] .subject-state{color:var(--red);}

/*
 * The transcript keeps a container. Stripped bare it read as loose text on the
 * page rather than a conversation — this is the one place on the page where a
 * surface is doing real work, so it is the one surface that stays.
 */
/*
 * Capped and scrolled, so the run resolves inside one viewport.
 *
 * The transcript used to grow to its full length: at 1440x900 it began around
 * y=477 and the outcome figures and the ask — the entire payoff — landed some
 * 700px below the fold, so a visitor who tapped a chip watched the opening of
 * a conversation and then had to go looking for how it ended. On a phone it
 * was four thumb-swipes. The section's own spectacle was off-screen.
 *
 * The height holds nine seconds of transcript with the newest message pinned
 * in view (demo.js scrolls it), which keeps the composition — monitor, thread,
 * outcome, ask — inside a single screen at every width.
 */
.thread{
  display:flex; flex-direction:column; gap:var(--s-3);
  min-height:340px; max-height:min(46vh, 420px); overflow-y:auto;
  background:var(--surface-thread); border-radius:var(--r-md); padding:var(--s-5);
  scroll-behavior:smooth;
}
@media (prefers-reduced-motion: reduce){.thread{scroll-behavior:auto;}}
@media (max-width:640px){.thread{padding:var(--s-4);}}
.msg{
  max-width:78%; padding:var(--s-3) var(--s-4); border-radius:var(--r-md);
  font-size:var(--text-md); line-height:1.5;
  opacity:0; transform:translateY(8px); animation:arrive .42s var(--ease) forwards;
}
@keyframes arrive{to{opacity:1; transform:translateY(0);}}
.msg--out{align-self:flex-end; background:var(--bubble-out); border-bottom-right-radius:var(--r-xs);}
.msg--in{align-self:flex-start; background:var(--bubble-in); border-bottom-left-radius:var(--r-xs);}

.beat{
  align-self:center; font-family:var(--font-mono); font-size:var(--text-xs); color:var(--text-dim);
  opacity:0; animation:arrive .42s var(--ease) forwards;
}

/* The classification is the most differentiating claim on the page, so it is
   the widest thing in the thread, not the smallest. */
.reading{
  align-self:stretch; max-width:none;
  border-left:1px solid var(--amber-a45); padding:var(--s-0) 0 var(--s-0) var(--s-4);
  display:flex; flex-direction:column; gap:var(--s-2);
  opacity:0; animation:arrive .42s var(--ease) forwards;
}
.reading-head{font-family:var(--font-mono); font-size:var(--text-sm); color:var(--amber);}
.reading-head span{color:var(--text-dim);}
.reading-source{font-size:var(--text-sm); color:var(--amber-soft); line-height:1.5;}

.suppressed{
  align-self:stretch; max-width:none;
  border-left:1px solid var(--red-a50); padding:var(--s-0) 0 var(--s-0) var(--s-4);
  font-family:var(--font-mono); font-size:var(--text-xs); color:var(--red); line-height:1.6;
  opacity:0; animation:arrive .42s var(--ease) forwards;
}

.queue{
  align-self:stretch; margin:var(--s-4) 0 var(--s-0); padding-top:var(--s-4);
  border-top:1px solid var(--ground-line);
  font-family:var(--font-mono); font-size:var(--text-xs); letter-spacing:.1em;
  text-transform:uppercase; color:var(--text-dim); text-align:center;
  opacity:0; animation:arrive .42s var(--ease) forwards;
}
.thread-idle{margin:auto; font-size:var(--text-md); color:var(--text-dim);}

/* The outcome, resolved in place. */
.outcome{
  display:grid; grid-template-columns:repeat(2,1fr); gap:var(--s-6);
  max-width:520px;
  margin-top:var(--s-8); padding-top:var(--s-6); border-top:1px solid var(--ground-line);
}
.outcome-figure{
  font-family:var(--font-display); font-size:var(--display-3); font-weight:700;
  letter-spacing:-.02em; color:var(--text); font-variant-numeric:tabular-nums;
  transition:color .5s var(--ease);
}
.outcome-label{font-size:var(--text-sm); color:var(--text-dim); margin-top:var(--s-2);}
.outcome[data-state="resolved"] .outcome-item[data-key="converted"] .outcome-figure{color:var(--amber);}
.outcome-note{margin-top:var(--s-5); font-size:var(--text-sm); color:var(--text-dim); max-width:64ch; line-height:1.65;}

/*
 * The ask. One component, used at the end of the demo and again at the end of
 * the calculator, and present from first paint in both — a visitor who never
 * interacts still has somewhere to go. Finishing a run, or touching a slider,
 * promotes it rather than revealing it.
 */
.ask{margin-top:var(--s-7); display:flex; align-items:center; gap:var(--s-5); flex-wrap:wrap;}
.ask p{font-size:var(--text-base); max-width:38ch;}

/* ---------- calculator ---------- */

.calc-context{
  font-family:var(--font-mono); font-size:var(--text-xs); color:var(--text-dim);
  margin-top:var(--s-6); letter-spacing:.02em;
}

.sliders{
  display:grid; grid-template-columns:repeat(3,1fr); gap:var(--s-6);
  /*
   * The 9px is the same 9px the range rule below spends: each slider's box now
   * ends 9px lower than it used to, so the block gives 9px back here and the
   * rule under the sliders sits where it always did. Stacked, that height lands
   * three times over, so the gap comes down a step to hold the same rhythm.
   */
  margin:var(--s-5) 0 calc(var(--s-8) - 9px);
}
@media (max-width:820px){.sliders{grid-template-columns:1fr; gap:var(--s-5);}}

/*
 * Value first, label under it. Spreading label and value to opposite ends of a
 * narrow column put each slider's number beside the next slider's label.
 */
.slider-head{display:flex; flex-direction:column-reverse; gap:var(--s-0);}
.slider-label{font-size:var(--text-sm); color:var(--text-dim);}
/*
 * Written by the qualifier a band above. Amber, then back to its own colour:
 * the visitor sees their own answer arrive rather than finding it already here.
 */
.slider-value.is-seeded{animation:seeded 1.6s var(--ease) both;}
@keyframes seeded{
  0%  {color:var(--amber); transform:translateY(-2px);}
  18% {color:var(--amber); transform:translateY(0);}
  100%{color:var(--text); transform:translateY(0);}
}
.band--paper .slider-value.is-seeded{animation-name:seeded-ink;}
@keyframes seeded-ink{
  0%  {color:var(--amber-ink); transform:translateY(-2px);}
  18% {color:var(--amber-ink); transform:translateY(0);}
  100%{color:var(--ink); transform:translateY(0);}
}

.slider-value{
  font-family:var(--font-mono); font-size:var(--text-xl); color:var(--text);
  font-variant-numeric:tabular-nums; letter-spacing:-.03em; line-height:1.2;
}

/*
 * 44px tall, and the track still sits exactly where it did. The input's own box
 * is the hit area on a range, so the only honest way to reach the 44px guideline
 * is to make the box 44px — not to draw a bigger thumb. The track centres in the
 * box, so growing 26px to 44px would have pushed it down 9px; the top margin
 * comes off by the same 9px to put it back. The extra height lands in the gap
 * below, which is --s-6 and has it to spare.
 *
 * This is not behind `pointer:coarse`. The buyer this page is written for reads
 * it on a phone between jobs, and a laptop with a touchscreen reports a fine
 * pointer while being dragged with a thumb.
 */
input[type="range"]{
  -webkit-appearance:none; appearance:none;
  width:100%; height:44px; margin-top:var(--s-0); background:transparent; cursor:pointer;
}
input[type="range"]::-webkit-slider-runnable-track{
  height:2px; background:var(--line-control); border-radius:var(--r-pill);
}
input[type="range"]::-moz-range-track{height:2px; background:var(--line-control); border-radius:var(--r-pill);}
input[type="range"]::-webkit-slider-thumb{
  -webkit-appearance:none; appearance:none;
  width:16px; height:16px; margin-top:-7px; border-radius:50%;
  background:var(--amber); border:none;
  transition:transform .16s var(--ease);
}
input[type="range"]::-moz-range-thumb{
  width:16px; height:16px; border-radius:50%; background:var(--amber); border:none;
}
input[type="range"]:hover::-webkit-slider-thumb{transform:scale(1.18);}
input[type="range"]:active::-webkit-slider-thumb{transform:scale(1.05);}
/*
 * The same focus ring as everything else on the page, bent into a circle: a
 * solid 2px amber stroke held --s-1 off the thumb by a band of the ground
 * colour, which is `outline:2px solid var(--amber); outline-offset:var(--s-1)`
 * written as a shadow because a real outline would trace the input's 44px box
 * rather than the thing that moves. It used to be a 45%-opacity halo, and it
 * was the one indicator on the page you had to look for.
 */
input[type="range"]:focus-visible{outline:none;}
input[type="range"]:focus-visible::-webkit-slider-thumb{
  box-shadow:0 0 0 var(--s-1) var(--bg), 0 0 0 calc(var(--s-1) + 2px) var(--amber);
}
input[type="range"]:focus-visible::-moz-range-thumb{
  box-shadow:0 0 0 var(--s-1) var(--bg), 0 0 0 calc(var(--s-1) + 2px) var(--amber);
}

.results{
  display:grid; grid-template-columns:1fr 1fr; gap:var(--s-7);
  padding-top:var(--s-7); border-top:1px solid var(--ground-line);
}
@media (max-width:640px){.results{grid-template-columns:1fr; gap:var(--s-6);}}
/*
 * Each result is its own query container so the figure below can be sized
 * against the column it actually sits in rather than against the viewport,
 * which is a different number on the two-column desktop layout.
 */
.result{container-type:inline-size;}
.result-label{font-size:var(--text-md); color:var(--text-dim);}
/*
 * The one role on this page whose content cannot wrap, so the one role that has
 * to be allowed to shrink. A currency figure has no break opportunity, and a
 * grid track will not go below its own min-content — so at a doubled default
 * text size the figure grew past its column and `overflow-x:hidden` ate the
 * last digit. The number the page is built to argue was the number being cut.
 *
 * The cap is measured, not guessed: in the display face at this weight and
 * tracking, the widest figure the calculator can produce -- $600,067,458, from
 * every slider and every assumption at its maximum -- renders 6.43x its own
 * font size. 15cqi therefore fills 96% of the column in the worst case the
 * model allows, and `min()` means the type scale still wins everywhere else:
 * on a 1280 desktop the cap computes to 84px against a 64px ceiling.
 */
.result-figure{
  font-family:var(--font-display); font-size:min(var(--figure), 15cqi); font-weight:700;
  letter-spacing:-.03em; margin-top:var(--s-2); font-variant-numeric:tabular-nums;
}
.result--cost .result-figure{color:var(--text);}
.result--return .result-figure{color:var(--amber);}
.result-sub{font-size:var(--text-sm); color:var(--text-dim); margin-top:var(--s-3); line-height:1.6;}

.maths{margin-top:var(--s-7);}
/*
 * min-height, not extra padding: these three controls sat at 36-38px while the
 * buttons and chips on the same page were already 44-48px. Padding would have
 * pushed the text off its baseline in a band where the rhythm is the argument;
 * a min-height grows only the hit area.
 */
.maths > summary{
  cursor:pointer; list-style:none; font-size:var(--text-md); color:var(--text-dim);
  display:inline-flex; align-items:center; gap:var(--s-2); padding:var(--s-2) 0;
  min-height:44px;
}
.maths > summary::-webkit-details-marker{display:none;}
.maths > summary::before{content:''; width:14px; height:1px; background:var(--text-dim);}
.maths[open] > summary{color:var(--text); margin-bottom:var(--s-5);}

/* A disclosure whose label still says "Show" after it has shown is the
   control lying about its own state. */
.summary-open{display:none;}
details[open] > summary .summary-closed{display:none;}
details[open] > summary .summary-open{display:inline;}
.maths-grid{display:grid; grid-template-columns:1fr 1fr; gap:var(--s-7);}
@media (max-width:820px){.maths-grid{grid-template-columns:1fr; gap:var(--s-6);}}

.ledger{display:flex; flex-direction:column;}
.ledger h3{
  font-family:var(--font-body); font-size:var(--text-sm); font-weight:500; color:var(--text-dim);
  letter-spacing:.02em; margin-bottom:var(--s-4);
}
/*
 * The value is allowed to drop under its own label. At a doubled default text
 * size on a 320px phone the label's min-content plus a `nowrap` currency value
 * are wider than the column, and a flex row will not shrink either side below
 * that — so the ledger ran off the page. Wrapping costs a line in a case almost
 * nobody hits; not wrapping loses the figure entirely.
 */
.row{
  display:flex; flex-wrap:wrap; justify-content:space-between; align-items:baseline;
  gap:var(--s-1) var(--s-5);
  padding:var(--s-3) 0; border-bottom:1px solid var(--ground-line); font-size:var(--text-md);
}
.row > :first-child{min-width:0;}
.row > .row-value{margin-left:auto;}
.row:last-child{border-bottom:none;}
.row-detail{display:block; font-family:var(--font-mono); font-size:var(--text-xs); color:var(--text-dim); margin-top:var(--s-1); line-height:1.5;}
.row-value{font-family:var(--font-mono); font-size:var(--text-sm); white-space:nowrap; font-variant-numeric:tabular-nums;}
.row--muted, .row--muted .row-value{color:var(--text-dim);}
.row--total{border-bottom:none; border-top:1px solid var(--ground-line); margin-top:var(--s-1); padding-top:var(--s-4); font-weight:600;}

.assumptions{margin-top:var(--s-5);}
.assumptions > summary{
  cursor:pointer; list-style:none; font-size:var(--text-sm); color:var(--text-dim);
  display:inline-flex; align-items:center; gap:var(--s-2); padding:var(--s-2) 0;
  min-height:44px;
}
.assumptions > summary::-webkit-details-marker{display:none;}
.assumptions > summary::before{content:''; width:14px; height:1px; background:var(--text-dim);}
.assumptions-grid{display:grid; grid-template-columns:1fr 1fr; gap:var(--s-4) var(--s-5); margin-top:var(--s-4);}
@media (max-width:640px){.assumptions-grid{grid-template-columns:1fr;}}
.assumption label{display:block; font-size:var(--text-sm); color:var(--text-dim); margin-bottom:var(--s-2);}
/*
 * `min-height` rather than more padding: these six carry mono numerals that are
 * read as a column, so the text stays where it is and the field grows around it.
 * Every other control on this page already clears 44px.
 */
.assumption input[type="number"]{
  width:100%; min-height:44px; background:transparent; color:var(--text);
  border:1px solid var(--line-control); border-radius:var(--r-sm);
  padding:var(--s-2) var(--s-3); font-family:var(--font-mono); font-size:var(--text-sm);
  caret-color:var(--amber);
}
.assumption input:focus-visible{border-color:var(--amber);}
.reset-row{display:flex; align-items:center; gap:var(--s-3); flex-wrap:wrap; margin-top:var(--s-5);}
.reset{
  background:none; border:none; cursor:pointer; padding:var(--s-2) 0;
  font-family:var(--font-body); font-size:var(--text-sm); color:var(--text-dim);
  text-decoration:underline; text-underline-offset:3px;
  display:inline-flex; align-items:center; min-height:44px;
}
.reset:hover{color:var(--text);}
/*
 * A returning visitor's own numbers, announced where they can see them and
 * undo them. It used to be an inline span two disclosures down, which meant
 * restored values arrived with no explanation anywhere near them.
 */
.restore{
  margin-top:var(--s-4); font-size:var(--text-sm); color:var(--ink-dim);
  display:flex; align-items:center; gap:var(--s-3); flex-wrap:wrap;
}
.restore[hidden]{display:none;}

/*
 * The paper band is the calculator on purpose: a printed quote is the thing it
 * most resembles, and putting the ledger on paper says "this is the invoice,
 * check it" more plainly than another dark panel would. It is also what makes
 * the page alternate, so the closing section reads as a return rather than as
 * one abrupt flip at the end.
 */
.band--paper{background:var(--paper); color:var(--ink);}
.band--paper h2{color:var(--ink);}
.band--paper .lede,
.band--paper .note,
.band--paper .calc-context,
.band--paper .slider-label,
.band--paper .result-label,
.band--paper .result-sub,
.band--paper .ledger h3,
.band--paper .row--muted,
.band--paper .row--muted .row-value,
.band--paper .row-detail,
.band--paper .maths > summary,
.band--paper .assumptions > summary,
.band--paper .assumption label,
.band--paper .restore,
.band--paper .reset{color:var(--ink-dim);}
.band--paper .slider-value,
.band--paper .result--cost .result-figure,
.band--paper .row,
.band--paper .ask p,
.band--paper .maths[open] > summary,
.band--paper .reset:hover{color:var(--ink);}
.band--paper .row{border-bottom-color:var(--ink-a12);}
.band--paper .row--total{border-top-color:var(--ink-a16);}
.band--paper .results{border-top-color:var(--ink-a12);}
.band--paper .maths > summary::before,
.band--paper .assumptions > summary::before{background:var(--ink-dim);}
.band--paper input[type="range"]::-webkit-slider-runnable-track{background:var(--ink-a16);}
.band--paper input[type="range"]::-moz-range-track{background:var(--ink-a16);}
/*
 * Amber on paper is 1.6:1 — invisible, and on the sliders it is the only thing
 * showing the visitor where the value sits. The burnt tone clears 4.4:1 there
 * and on the display figure; links need the darker one at body size.
 */
.band--paper input[type="range"]::-webkit-slider-thumb{background:var(--amber-ink);}
.band--paper input[type="range"]::-moz-range-thumb{background:var(--amber-ink);}
/* The ring inverts with the band: paper for the gap, burnt amber for the stroke. */
.band--paper input[type="range"]:focus-visible::-webkit-slider-thumb{
  box-shadow:0 0 0 var(--s-1) var(--paper), 0 0 0 calc(var(--s-1) + 2px) var(--amber-ink);
}
.band--paper input[type="range"]:focus-visible::-moz-range-thumb{
  box-shadow:0 0 0 var(--s-1) var(--paper), 0 0 0 calc(var(--s-1) + 2px) var(--amber-ink);
}
.band--paper .assumption input[type="number"]{
  background:var(--paper-well); color:var(--ink); border-color:var(--ink-a16);
}
.band--paper .assumption input:focus-visible{border-color:var(--amber-ink);}
.band--paper .assumptions{border-top:1px solid var(--ink-a12); padding-top:var(--s-4);}
.band--paper ::selection{background:var(--amber-a25); color:var(--ink);}
.band--paper .result--return .result-figure{color:var(--amber-ink);}
.band--paper a{color:var(--amber-link);}
.band--paper .btn{color:var(--btn-ink);}
.band--paper .btn--quiet{color:var(--ink); border-color:var(--ink-a16);}
.band--paper .btn--quiet:hover{background:var(--ink-a12); border-color:var(--ink-a16);}
.band--paper :focus-visible{outline-color:var(--amber-ink);}

/* ---------- qualify ---------- */

#qualify{border-top:1px solid var(--ground-line);}
#qualify h2 em{font-style:normal; color:var(--amber);}

.qualify{margin-top:var(--s-8); display:grid; gap:var(--s-7);}

/*
 * A fieldset with the browser's own border and margin reset to nothing. The
 * three questions are held apart by rhythm, like every other group on this
 * page, rather than by three more boxes.
 */
.q{border:none; padding:0; margin:0; min-width:0;}
.q-label{
  display:flex; align-items:baseline; gap:var(--s-4); padding:0;
  font-family:var(--font-display); font-size:var(--text-lg); font-weight:600;
  letter-spacing:-.01em; color:var(--text);
}
.q-n{
  font-family:var(--font-mono); font-size:var(--text-sm); font-weight:400;
  color:var(--amber); font-variant-numeric:tabular-nums;
  transition:color .2s var(--ease);
}

/*
 * An answered question dims its own number. Nothing is added to the page — the
 * marker that was already there just stops asking, so the one still lit is the
 * one still waiting. The verdict names it in words as well, because colour
 * alone must not carry the message.
 */
.q[data-answered="true"] .q-n{color:var(--cold-text);}

.q-options{
  margin-top:var(--s-5);
  display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:var(--s-3);
}

/*
 * The radio itself is hidden and the label carries the state, so the target is
 * the whole card rather than a 13px circle — this question is answered on a
 * phone, one-handed, by someone standing in a practice.
 */
.q-opt{position:relative; display:block; cursor:pointer;}
/*
 * The input is the control; the card is the target and carries every visible
 * state. Suppressing the input's own ring matters: at 0x0 it painted a 2px
 * amber sliver at the card's edge, which reads as a rendering artefact rather
 * than as focus, directly next to the card ring that is doing the real work.
 */
.q-opt input{position:absolute; opacity:0; width:0; height:0;}
.q-opt input:focus-visible{outline:none;}
.q-opt-body{
  display:flex; flex-direction:column; gap:var(--s-1);
  min-height:76px; padding:var(--s-4);
  border:1px solid var(--line-control); border-radius:var(--r-md);
  background:var(--surface-thread);
  transition:border-color .18s var(--ease), background .18s var(--ease);
}
.q-opt:hover .q-opt-body{border-color:var(--line-strong);}
.q-opt-title{font-size:var(--text-md); color:var(--text); font-weight:500; line-height:1.4;}
.q-opt-note{font-family:var(--font-mono); font-size:var(--text-xs); color:var(--text-dim);}
.q-opt input:checked + .q-opt-body{
  border-color:var(--amber); background:var(--bubble-out);
}
.q-opt input:checked + .q-opt-body .q-opt-title{color:var(--amber);}
.q-opt input:focus-visible + .q-opt-body{
  outline:2px solid var(--amber); outline-offset:2px;
}

/* The card is the target, so it should feel like one under a thumb. */
.q-opt:active .q-opt-body{transform:translateY(1px);}
.q-opt-body{transition:border-color .18s var(--ease), background .18s var(--ease), transform .12s var(--ease);}

.q-slider{margin-top:var(--s-5); display:flex; align-items:center; gap:var(--s-5); flex-wrap:wrap;}
.q-slider input[type="range"]{flex:1 1 260px; min-width:200px;}
.q-slider-value{
  font-family:var(--font-mono); font-size:var(--text-xl); color:var(--amber);
  font-variant-numeric:tabular-nums; min-width:5ch;
}

/*
 * Before the question is answered the readout is still showing the slider's
 * default, and in the accent colour it read as a settled answer — so a visitor
 * whose real number was already the default saw a lit figure and a page still
 * asking for it. Dormant until it is theirs, which is the same grammar the
 * question numbers use above. --cold-text is contrast-safe at this size, and
 * the idle line names the missing question in words regardless.
 */
.q:not([data-answered="true"]) .q-slider-value{color:var(--cold-text);}

.q-trades{margin-top:var(--s-5); display:flex; gap:var(--s-2); flex-wrap:wrap;}

/*
 * One element, three states. The idle copy holds the vertical space the answer
 * will occupy, so arriving at a verdict never shoves the page under the
 * visitor's thumb mid-read.
 */
/*
 * The verdict is the one authored moment on this page, and it belongs to the
 * instrument the rest of the page already uses: a check running, then a state
 * resolving. It does not fade in. A hairline crosses the panel the way the
 * traces above it scroll, the record line settles, and only then does the
 * outcome resolve — amber for a list that clears, red for one that does not.
 *
 * It runs once, on the visitor's third answer. Nothing here loops, nothing is
 * scroll-driven, and the whole sequence is transform and opacity on four
 * elements inside one bounded panel.
 */
.verdict{
  position:relative; overflow:hidden;
  margin-top:var(--s-8); padding:var(--s-6);
  border:1px solid var(--ground-line); border-radius:var(--r-md);
  background:var(--surface-thread);
  min-height:200px; display:flex; flex-direction:column; justify-content:center;
  transition:border-color .5s var(--ease);
}
.verdict[data-state="qualified"]{border-color:var(--amber-a45);}
.verdict[data-state="blocked"]{border-color:var(--red-a50);}

/* The gate itself: a single pass of the panel, in the colour of the answer. */
.verdict::after{
  content:''; position:absolute; inset:0; pointer-events:none; opacity:0;
  /*
   * Full width, translated by its own width, so the pass takes the same time
   * to cross a 1,000px panel and a 320px one. A fixed bar translated by 100vw
   * spends most of its duration outside the panel on a wide screen, which is
   * how a sweep ends up reading as a stutter.
   */
  background:linear-gradient(90deg, transparent 0 48.4%, var(--amber) 50%, transparent 51.6% 100%);
  -webkit-mask-image:linear-gradient(180deg, transparent, #000 22%, #000 78%, transparent);
          mask-image:linear-gradient(180deg, transparent, #000 22%, #000 78%, transparent);
}
.verdict[data-state="blocked"]::after{
  background:linear-gradient(90deg, transparent 0 48.4%, var(--red) 50%, transparent 51.6% 100%);
}
/*
 * Linear, like the hero trace and the strip. An ease-out curve put the band
 * across the panel in the first 150ms of its own duration and spent the rest
 * of the time off-screen: an arrival curve applied to something that is not
 * arriving. A scan is a process, and it runs at a constant rate.
 */
.verdict.is-resolving::after{animation:gate .6s linear forwards;}
@keyframes gate{
  0%   {opacity:0; transform:translateX(-100%);}
  10%  {opacity:1;}
  85%  {opacity:1;}
  100% {opacity:0; transform:translateX(100%);}
}

/*
 * The parts of the answer, in the order they are read: what the engine
 * recorded, what it decided, then what that is worth. Three steps, capped at
 * 260ms of total delay — the sequence has to finish inside the beat where the
 * visitor is still looking at it.
 */
.verdict.is-resolving .verdict-record{animation:settle .4s var(--ease) both;}
.verdict.is-resolving .verdict-body h3{animation:settle .46s var(--ease) .1s both;}
.verdict.is-resolving .verdict-line{animation:settle .46s var(--ease) .18s both;}
.verdict.is-resolving .verdict-figures,
.verdict.is-resolving .verdict-fail .note{animation:settle .5s var(--ease) .26s both;}
@keyframes settle{
  from{opacity:0; transform:translateY(6px);}
  to  {opacity:1; transform:translateY(0);}
}
@media (max-width:640px){.verdict{padding:var(--s-5);}}

.verdict-idle{font-size:var(--text-md); color:var(--text-dim); text-align:center;}
.verdict-body{display:none;}
.verdict[data-state="idle"] .verdict-idle{display:block;}
.verdict[data-state="qualified"] .verdict-idle,
.verdict[data-state="blocked"] .verdict-idle{display:none;}
.verdict[data-state="qualified"] .verdict-body,
.verdict[data-state="blocked"] .verdict-body{display:block;}
.verdict-pass, .verdict-fail{display:none;}
.verdict[data-state="qualified"] .verdict-pass{display:block;}
.verdict[data-state="blocked"] .verdict-fail{display:block;}

/* The record line reads back what the engine would store, in the engine's own
   vocabulary. It is the tell that this is a gate, not a marketing form. */
.verdict-record{
  font-family:var(--font-mono); font-size:var(--text-xs); color:var(--text-dim);
  padding-bottom:var(--s-4); border-bottom:1px solid var(--ground-line);
  letter-spacing:.02em;
}
.verdict-body h3{
  margin-top:var(--s-5);
  font-family:var(--font-display); font-size:var(--display-3); font-weight:600;
  letter-spacing:-.02em; line-height:1.15; color:var(--text);
}
.verdict[data-state="qualified"] .verdict-body h3{color:var(--amber);}
.verdict[data-state="blocked"] .verdict-body h3{color:var(--red);}
.verdict-line{
  margin-top:var(--s-4); font-size:var(--text-base); color:var(--text-dim);
  line-height:1.7; max-width:62ch;
}
.verdict-line em{font-style:normal; color:var(--text);}
.verdict-fail .note{margin-top:var(--s-5); max-width:62ch;}

.verdict-figures{
  margin-top:var(--s-6);
  display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:var(--s-5);
}
.vf-value{
  font-family:var(--font-mono); font-size:var(--text-xl); color:var(--text);
  font-variant-numeric:tabular-nums;
}
.vf-value--gain{color:var(--amber);}
.vf-label{margin-top:var(--s-2); font-size:var(--text-sm); color:var(--text-dim); line-height:1.6;}
.verdict-pass .note{margin-top:var(--s-5); max-width:62ch;}


/*
 * Capture. One field, and the button beside it rather than under it, because
 * the whole ask is meant to read as smaller than the booking ask directly
 * below it. The checkbox is unchecked in the markup and stays that way: a page
 * arguing for documented consent cannot help itself to an implied opt-in.
 */
.capture{margin-top:var(--s-6); padding-top:var(--s-5); border-top:1px solid var(--ground-line);}
.capture-label{
  display:block; font-size:var(--text-md); color:var(--text); font-weight:500;
}
.capture-row{
  margin-top:var(--s-3); display:flex; gap:var(--s-3); flex-wrap:wrap; align-items:stretch;
}
.capture-row input[type="email"]{
  flex:1 1 260px; min-width:0; min-height:48px; padding:0 var(--s-4);
  background:var(--bg); color:var(--text);
  border:1px solid var(--line-control); border-radius:var(--r-sm);
  font-family:var(--font-body); font-size:var(--text-base);
}
.capture-row input[type="email"]::placeholder{color:var(--text-dim);}
.capture-row input[type="email"]:focus-visible{border-color:var(--amber);}
.capture-row .btn{flex:0 0 auto;}
.capture-row .btn[disabled]{opacity:.6; cursor:default;}

/* Sending is the only moment on this page with real latency in it, so it is
   the only one that gets a loop — and it stops the instant the answer lands. */
.capture[data-state="sending"] .btn{animation:pulse-send 1.1s ease-in-out infinite;}
@keyframes pulse-send{50%{opacity:.55;}}
.capture-done{animation:settle .4s var(--ease) both;}

.capture-opt{
  margin-top:var(--s-4); display:flex; gap:var(--s-3); align-items:flex-start;
  font-size:var(--text-sm); color:var(--text-dim); line-height:1.6; cursor:pointer;
  max-width:56ch;
}
.capture-opt input{margin-top:3px; accent-color:var(--amber); width:16px; height:16px; flex:0 0 auto;}

.capture-note{margin-top:var(--s-4); font-size:var(--text-sm); color:var(--text-dim); line-height:1.65; max-width:60ch;}
.capture-done, .capture-error{
  font-size:var(--text-base); line-height:1.7; max-width:60ch;
}
.capture-done{color:var(--amber);}
.capture-error{color:var(--red);}
.capture-error a{color:var(--red);}
.capture-done[hidden], .capture-error[hidden]{display:none;}
.capture-form[hidden]{display:none;}

/* ---------- how it works ---------- */

/*
 * Two dark bands now run back to back — the demo, then this. A hairline is
 * enough to separate them: a second surface colour here would compete with the
 * paper band, which is the page's only real change of ground.
 */
#how{border-top:1px solid var(--ground-line);}

/*
 * The step number is a marker, not a heading, so it is set in mono at metadata
 * size and held in its own column. Numbering with an ordered list rather than
 * typed digits keeps the sequence readable to a screen reader in the order it
 * is meant to be read.
 */
.steps{list-style:none; margin-top:var(--s-8); display:grid; gap:var(--s-6);}
.step{display:grid; grid-template-columns:48px 1fr; gap:var(--s-5); align-items:start;}
@media (max-width:640px){
  .step{grid-template-columns:1fr; gap:var(--s-2);}
}
.step-n{
  font-family:var(--font-mono); font-size:var(--text-sm); color:var(--amber);
  padding-top:3px; font-variant-numeric:tabular-nums;
}
.step-body h3{
  font-family:var(--font-display); font-size:var(--text-lg); font-weight:600;
  letter-spacing:-.01em; color:var(--text);
}
.step-body p{
  margin-top:var(--s-3); font-size:var(--text-base); color:var(--text-dim);
  line-height:1.7; max-width:62ch;
}
.step-body em{font-style:normal; color:var(--text);}

/*
 * The gates are the answer to the objection the demo raises, so they get a
 * panel of their own rather than a sixth paragraph. Same surface as the
 * transcript: it is the other place on the page where the machine's own record
 * is being shown rather than described.
 */
.gates{
  margin-top:var(--s-8); padding:var(--s-6);
  background:var(--surface-thread); border:1px solid var(--ground-line);
  border-radius:var(--r-md);
}
@media (max-width:640px){.gates{padding:var(--s-5);}}
.gates h3{
  font-family:var(--font-mono); font-size:var(--text-xs); font-weight:500;
  text-transform:uppercase; letter-spacing:.08em; color:var(--text-dim);
}
.gate-list{list-style:none; margin-top:var(--s-5); display:grid; gap:var(--s-4);}
.gate-list li{
  display:grid; grid-template-columns:180px 1fr; gap:var(--s-4);
  padding-bottom:var(--s-4); border-bottom:1px solid var(--ground-line);
  font-size:var(--text-md); line-height:1.6;
}
.gate-list li:last-child{padding-bottom:0; border-bottom:none;}
@media (max-width:640px){
  .gate-list li{grid-template-columns:1fr; gap:var(--s-1);}
}
.gate-name{color:var(--text); font-weight:500;}
.gate-rule{color:var(--text-dim);}

/* ---------- services ---------- */

#stack{border-top:1px solid var(--ground-line);}

/*
 * A list, not a card grid. Six bordered rectangles of eighty words each was the
 * one place this page reverted to the pattern its own stylesheet opens by
 * rejecting — and it sat after the conversion event, which is the worst place
 * on the page to spend four hundred words.
 *
 * Three columns: who, what for, what it costs. The cost column is the reason
 * anyone reads this section, so it is set in mono and right-aligned into its
 * own channel where it can be scanned down without reading a word of prose.
 */
.svc-list{list-style:none; margin-top:var(--s-7); display:grid;}
.svc{
  display:grid; grid-template-columns:180px minmax(0,1fr) 250px; gap:var(--s-5);
  align-items:baseline;
  padding:var(--s-4) 0; border-bottom:1px solid var(--ground-line);
}
.svc:last-child{border-bottom:none;}
@media (max-width:820px){
  .svc{grid-template-columns:1fr; gap:var(--s-1); padding:var(--s-4) 0;}
}
.svc-name{
  font-family:var(--font-display); font-size:var(--text-lg); font-weight:600;
  letter-spacing:-.01em; color:var(--text);
}
.svc-role{font-size:var(--text-md); color:var(--text-dim); line-height:1.65;}
.svc-cost{
  font-family:var(--font-mono); font-size:var(--text-sm); color:var(--amber);
  line-height:1.6; text-align:right;
}
@media (max-width:820px){.svc-cost{text-align:left; margin-top:var(--s-2);}}

/* Shipped-off, and greyed so it can never read as something being sold. */
.svc--off .svc-name, .svc--off .svc-role{color:var(--cold-text);}
.svc--off .svc-cost{color:var(--cold-text);}

/* ---------- proof ---------- */

#proof{border-top:1px solid var(--ground-line);}
#proof h2 em{font-style:normal; color:var(--amber);}

/*
 * Full width, caption underneath. These used to sit in a 1.6fr column beside
 * their captions, which rendered them about 480px wide — and the captions cite
 * a specific red row and a specific badge. A caption pointing at evidence the
 * reader cannot physically read is worse than no caption at all.
 */
.proof-grid{margin-top:var(--s-8); display:grid; gap:var(--s-9);}
.proof{margin:0;}

/*
 * The screenshot is a button because it does something. Zero chrome: no border,
 * no padding, no background — the image is the control, and the affordance is
 * the cursor plus a border that warms on hover.
 */
.proof-shot{
  display:block; width:100%; padding:0; border:0; background:none; cursor:zoom-in;
  border-radius:var(--r-md); overflow:hidden;
}
/*
 * The `sizes` attribute on these three restates the arithmetic below: .wrap is
 * border-box at 1080px with 24px of padding a side, so the slot is 1032px once
 * the viewport reaches 1080px and `100vw - 48px` before that. A phone therefore
 * takes the 700px source rather than the 1980px one it used to downscale by six.
 * Changing --wrap or this padding means changing that attribute too.
 */
.proof-shot picture{display:block;}
.proof-shot img{
  display:block; width:100%; height:auto;
  border:1px solid var(--ground-line); border-radius:var(--r-md);
  transition:border-color .2s var(--ease);
}
.proof-shot:hover img{border-color:var(--amber-a45);}
.proof-shot:focus-visible{outline:2px solid var(--amber); outline-offset:var(--s-2);}

.proof figcaption{
  margin-top:var(--s-4); font-size:var(--text-md); color:var(--text-dim);
  line-height:1.7; max-width:70ch;
}
.proof figcaption code{
  font-family:var(--font-mono); font-size:var(--text-sm); color:var(--text);
}
.proof figcaption em{font-style:normal; color:var(--text);}

/*
 * The viewer. `dialog` brings the top layer, the backdrop, Escape and focus
 * trapping with it, so there is nothing here to reimplement badly.
 */
.zoom{
  border:none; padding:0; background:none; max-width:96vw; max-height:96vh;
  color:var(--text);
}
.zoom::backdrop{background:var(--scrim);}
.zoom img{
  display:block; max-width:96vw; max-height:86vh; width:auto; height:auto;
  border:1px solid var(--line-strong); border-radius:var(--r-md);
}

/*
 * On a phone, fitting a 1,980px-wide dashboard capture into 374px of screen is
 * the same unreadable picture the visitor just tapped to escape. Below the
 * point where fit-to-width stops being legible, the viewer becomes a pannable
 * window onto the capture at a size where the type can actually be read.
 */
@media (max-width:900px){
  .zoom{overflow:auto; max-width:96vw; max-height:90vh; -webkit-overflow-scrolling:touch;}
  .zoom img{max-width:none; max-height:none; width:1100px;}
  .zoom-close{position:sticky; left:0; bottom:0; margin-inline:auto;}
}
.zoom-close{
  display:block; margin:var(--s-4) auto 0;
  background:none; border:1px solid var(--line-control); border-radius:var(--r-pill);
  color:var(--text); font-family:var(--font-body); font-size:var(--text-sm);
  padding:var(--s-2) var(--s-5); min-height:44px; cursor:pointer;
}
.zoom-close:hover{border-color:var(--line-strong);}

/* ---------- booking ---------- */

/*
 * The closing section centres. Everywhere else is left-aligned because it is
 * being read; this is the one place the visitor is deciding rather than
 * reading, and a centred column with the calendar under it reads as a single
 * deliberate object instead of a centred box beside left-set text.
 */
#book{text-align:center;}
#book h2, #book .lede, #book .note, #book .identity, #book .booking-fallback{
  margin-left:auto; margin-right:auto;
}
#book .lede{text-wrap:balance;}

/*
 * Wide enough that Calendly lays the calendar and the time list side by side.
 * Below roughly 1000px of frame it stacks them into two rows, which doubles the
 * height of the booking step for no reason.
 */
#book .wrap{max-width:var(--wrap-wide);}

/*
 * Who is on the other end of the call. The page spends three bands arguing and
 * then asks a stranger for thirty minutes; the answer to "who am I booking
 * with, and what will this cost me" belongs above the calendar, not below it.
 */
.identity{
  display:flex; gap:var(--s-4); align-items:baseline; justify-content:center;
  flex-wrap:wrap; margin-top:var(--s-6); max-width:74ch;
  font-size:var(--text-sm); color:var(--text-dim);
}
.identity strong{color:var(--text); font-weight:600;}
.identity span{max-width:46ch; text-align:left;}
@media (max-width:640px){.identity span{text-align:center;}}

.booking{
  position:relative;
  margin:var(--s-7) auto 0;
  border-radius:var(--r-md); overflow:hidden;
  background:var(--bg);
}
/*
 * No fixed height. Calendly's iframe posts its own content height as it moves
 * between steps, and the loader applies it — so the frame grows and shrinks
 * with the flow instead of trapping it behind a scrollbar. The min-height is
 * only a placeholder, set to the measured settled height at each breakpoint so
 * the page neither jumps nor shrinks when the real measurement lands.
 */
.booking .calendly-inline-widget{
  /*
   * The vendor's 280px floor only applies while there is 280px to give. At a
   * 320px viewport the wrap's content box is 272px, and a fixed floor there put
   * 8px of the frame under `overflow:hidden` on the smallest width this page
   * supports.
   */
  min-width:min(280px, 100%); width:100%; display:block;
  min-height:700px;
  transition:height .28s var(--ease);
}
@media (max-width:1100px){.booking .calendly-inline-widget{min-height:1120px;}}
@media (max-width:640px){.booking .calendly-inline-widget{min-height:1160px;}}

/* Held only for the seconds between the frame arriving and its content
   rendering, so the reservation never reads as a broken page. */
.booking-loading{
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  font-family:var(--font-mono); font-size:var(--text-xs); color:var(--text-dim);
  pointer-events:none;
}
.booking-loading[hidden]{display:none;}

/* Always in the markup, never JS-gated: if the embed is blocked, this is the
   only way left to reach anyone. */
/*
 * The address is one unbreakable token, and at a doubled text size it is wider
 * than a phone. It breaks mid-word rather than running under the edge, because
 * this line exists precisely for the visitor whose calendar did not load.
 */
.booking-fallback{margin-top:var(--s-5); font-size:var(--text-sm); color:var(--text-dim);}
.booking-fallback a{overflow-wrap:anywhere;}
.booking-fallback-lead{display:none;}

/*
 * The embed failed. The reserved frame collapses, the loading label goes, and
 * the email path stops being a footnote and becomes the booking section: full
 * body size, its own explanation, and the amber the primary action would have
 * had. A dead 700px rectangle is worse than no rectangle.
 */
#book[data-booking="unavailable"] .booking{min-height:0; margin-top:0;}
#book[data-booking="unavailable"] .booking-fallback{
  font-size:var(--text-base); color:var(--text); margin-top:var(--s-7);
  max-width:52ch;
}
#book[data-booking="unavailable"] .booking-fallback-lead{display:block; color:var(--text-dim); margin-bottom:var(--s-2);}
#book[data-booking="unavailable"] .booking-fallback a{color:var(--amber);}


/*
 * Revealed only by a confirmed booking. Amber-edged because it is the one
 * moment on the page where the visitor has already said yes and the page is
 * answering rather than persuading.
 */
.booked{display:none;}
#book[data-booked="true"] .booked{
  display:block; text-align:left;
  margin:var(--s-7) auto 0; max-width:64ch; padding:var(--s-6);
  border:1px solid var(--amber-a45); border-radius:var(--r-md);
  background:var(--surface-thread);
}
#book[data-booked="true"] .booking,
#book[data-booked="true"] .booking-fallback,
#book[data-booked="true"] .terms{display:none;}
.booked h3{
  font-family:var(--font-display); font-size:var(--text-xl); font-weight:600;
  letter-spacing:-.01em; color:var(--amber);
}
.booked-list{margin:var(--s-5) 0 0; padding-left:var(--s-5); display:grid; gap:var(--s-4);}
.booked-list li{font-size:var(--text-md); color:var(--text-dim); line-height:1.7;}
.booked-list strong{color:var(--text); font-weight:500;}
.booked .note{margin-top:var(--s-5);}


/* ---------- what a refusal changes ---------- */

/*
 * The verdict is a page state, not a panel state. A visitor who has just been
 * told the product will not run on their list should not then be sold to for
 * five more viewports: the calculator says whose numbers those are, the
 * calendar is replaced by the condition under which booking is worth it, and
 * the closing ask stops asking.
 *
 * Everything here is inert until qualify.js writes the attribute, so a visitor
 * who never answers the three questions sees the page exactly as before.
 */
.band-note, .booking-blocked{display:none;}

:root[data-verdict="blocked"] .band-note{
  display:block; color:var(--ink-dim); max-width:56ch;
  border-left:1px solid var(--amber-ink); padding-left:var(--s-4);
}
:root[data-verdict="blocked"] #calculator .ask{display:none;}

:root[data-verdict="blocked"] .booking{display:none;}
:root[data-verdict="blocked"] .booking-fallback{display:none;}
:root[data-verdict="blocked"] .terms{display:none;}
:root[data-verdict="blocked"] .booking-blocked{
  display:block; text-align:left;
  margin:var(--s-7) auto 0; max-width:64ch; padding:var(--s-6);
  border:1px solid var(--red-a50); border-radius:var(--r-md);
  background:var(--surface-thread);
}
.booking-blocked h3{
  font-family:var(--font-display); font-size:var(--text-xl); font-weight:600;
  letter-spacing:-.01em; color:var(--red);
}
.booking-blocked p{
  margin-top:var(--s-4); font-size:var(--text-md); color:var(--text-dim); line-height:1.7;
}
.booking-blocked .booked-list{margin-top:var(--s-5);}
.booking-blocked a{color:var(--red);}

/* ---------- footer ---------- */

footer{
  background:var(--bg); color:var(--text-dim);
  border-top:1px solid var(--ground-line);
  padding:var(--s-7) 0 var(--s-8);
}
footer p{font-size:var(--text-sm); color:var(--text-dim); line-height:1.7; max-width:76ch;}
footer p + p{margin-top:var(--s-3);}
footer a{color:var(--text-dim);}

/* ---------- motion ---------- */

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto;}
  .ecg-track, .hero-trace .sweep{animation:none;}
  /* The verdict still resolves — colour and opacity carry it — but nothing
     crosses the panel and nothing rises into place. */
  .verdict.is-resolving::after{animation:none;}
  .verdict.is-resolving .verdict-record,
  .verdict.is-resolving .verdict-body h3,
  .verdict.is-resolving .verdict-line,
  .verdict.is-resolving .verdict-figures,
  .verdict.is-resolving .verdict-fail .note{animation:fade .3s var(--ease) both;}
  @keyframes fade{from{opacity:0;} to{opacity:1;}}
  /* The seeded value still changes colour; it just does not move. The
     shortened duration was not enough — the keyframe itself still translated. */
  .slider-value.is-seeded{animation:seeded-flat 1.2s var(--ease) both;}
  .band--paper .slider-value.is-seeded{animation-name:seeded-flat-ink;}
  @keyframes seeded-flat{0%,18%{color:var(--amber);} 100%{color:var(--text);}}
  @keyframes seeded-flat-ink{0%,18%{color:var(--amber-ink);} 100%{color:var(--ink);}}
  /* The capture's success message used `settle`, which nothing here covered. */
  .capture-done{animation:fade .3s var(--ease) both;}
  .q-opt:active .q-opt-body{transform:none;}
  .capture[data-state="sending"] .btn{animation:none; opacity:.6;}
  .msg, .beat, .reading, .suppressed, .queue{animation:none; opacity:1; transform:none;}
  .btn, .trade, .line.flat, .line.pulse, .outcome-figure,
  .booking .calendly-inline-widget{transition:none;}
  .btn:hover{transform:none;}
  input[type="range"]::-webkit-slider-thumb{transition:none;}
  input[type="range"]:hover::-webkit-slider-thumb,
  input[type="range"]:active::-webkit-slider-thumb{transform:none;}
}
