/* ============================================================================
   YOUNG SEO — HOME v3. A complete stylesheet for home_v3() in build.py.
   Not layered on base.css.

   Why v3 exists, in Joe's words: "so many words and super flat and boring",
   and then, looking at v2 on his phone: "too much stuff on mobile."

   Three structural changes from v2, and they are the whole point:
   1. ALTERNATING PLATES. v1 and v2 put every section on one flat #191A1E, so
      the page read as one endless dark scroll with invisible section joins —
      which is what "flat and boring" actually described. Plates now alternate
      dark / light, and a light plate redefines the tokens locally so every
      component inside it inherits (the Instrument block-swap trick), instead
      of every component needing a light variant.
   2. MOBILE IS THE SOURCE, DESKTOP IS DERIVED. Every rule here is written for
      390px first. The hero is not 100svh any more — a full-height hero on a
      phone is one screen of headline before a stranger sees a single thing
      worth seeing.
   3. THE WORK SECTION. He sells websites and showed zero websites. Three real
      client sites, screenshotted on a phone, on a light plate.

   Ban list (client-set, still enforced): no purple gradient, no glassmorphism,
   no drop shadows on cards, no icon grid, no rounded-everything, no even
   three-column feature grid. One accent. Hairlines. Tabular numerals.
   ============================================================================ */
@font-face{font-family:'Questrial';src:url('/fonts/Questrial-400.woff2') format('woff2');font-weight:400;font-display:swap}
@font-face{font-family:'Plex';src:url('/fonts/IBMPlexSans-400.woff2') format('woff2');font-weight:400;font-display:swap}
@font-face{font-family:'Plex';src:url('/fonts/IBMPlexSans-500.woff2') format('woff2');font-weight:500;font-display:swap}
@font-face{font-family:'PlexMono';src:url('/fonts/IBMPlexMono-400.woff2') format('woff2');font-weight:400;font-display:swap}
@font-face{font-family:'PlexMono';src:url('/fonts/IBMPlexMono-500.woff2') format('woff2');font-weight:500;font-display:swap}

/* ---------------------------------------------------------------- tokens */
:root{
  --display:'Questrial',system-ui,sans-serif;
  --body:'Plex',system-ui,sans-serif;
  --mono:'PlexMono',ui-monospace,Menlo,monospace;
  --track:.2em;--radius:2px;--wrap:1240px;--gut:clamp(18px,4vw,48px);
  --ease:cubic-bezier(.2,.7,.2,1);
  /* The brand ground is #191A1E and is frozen (§4.1). So depth is NOT made by moving the
     ground — it is made by RECESSING the instrument into it. --panel is now darker than
     --bg, not lighter: the scanner reads as a well cut into the plate, with a hairline of
     light on its bottom edge. That is where "premium" comes from on a dark page: one
     value scale and one light source, not more decoration. */
  --bg:#191A1E;--bg-2:#131418;--panel:#101216;--panel-2:#1C1E24;--plate:#F0ECE1;
  --line:rgba(239,237,233,.10);--line-strong:rgba(239,237,233,.24);
  --edge-hi:rgba(255,255,255,.055);        /* the 1px of light on a top edge */
  --ink:#F2F0EC;--ink-2:#8F8C96;--ink-3:#5C5966;--ink-body:#BCB9C3;
  --accent:#C2163A;--accent-hover:#D9204A;--accent-ink:#FFFFFF;
  --green:#63C671;--green-deep:#7BD489;--green-dot:#2E8B3A;--amber:#8FBF45;
  /* THE BEFORE STATE IS NOT BRAND RED. 49 saturated #C7434F discs read as an alarm panel,
     they were 90% of the hero's visual weight, and they collided with the CTA — the site's
     own §6.5 has carried "the brand accent reads as the failure-state red" as an open item
     since 10 Sep. Cold, unlit oxblood says "not ranking" without shouting, and it hands the
     colour back to the button. The AFTER state keeps every bit of its brightness, so the
     reveal gets louder, not quieter. */
  --red:#1B1D23;--red-ring:rgba(239,237,233,.13);--red-ink:#E05A6E;
  --amber-before:#23222A;--amber-ring:rgba(216,166,96,.52);
  --grid:rgba(239,237,233,.04);
  --sec:clamp(62px,10vw,140px);           /* the section rhythm — one number. Was 44/104:
                                             Joe, 17 Sep: "space out the rest of the site
                                             more top to bottom ... too crammed in." */
}
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;background:#191A1E}
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--body);font-size:17px;line-height:1.7;-webkit-font-smoothing:antialiased;font-feature-settings:"tnum" 1;overflow-x:clip}
img,svg{max-width:100%;display:block}img{height:auto}
a{color:inherit;text-decoration:none}
p{margin:0 0 1.2em;color:var(--ink-body)}
h1,h2,h3{font-family:var(--display);font-weight:400;line-height:1.13;letter-spacing:-.022em;margin:0 0 .68em;text-wrap:balance;color:var(--ink)}
h1{font-size:clamp(2.2rem,7.4vw,4.1rem);letter-spacing:-.028em;line-height:1.08}
h2{font-size:clamp(1.85rem,5.4vw,3.1rem)}
h3{font-size:clamp(1.1rem,1.8vw,1.35rem);line-height:1.28}
b,strong{font-weight:500}
.wrap{max-width:var(--wrap);margin:0 auto;padding-inline:var(--gut)}
.skip{position:absolute;left:-999px;top:0;background:var(--accent);color:#fff;padding:8px 12px;z-index:99}
.skip:focus{left:8px;top:8px}

/* type roles */
.label{font-family:var(--display);text-transform:uppercase;letter-spacing:.2em;font-size:.63rem;color:var(--ink-3);display:block;margin-bottom:1.05rem;line-height:1.5}
.lbl-mk{display:flex;align-items:center;gap:9px}
.mk{width:13px;height:13px;color:var(--accent);flex:none}
.mono{font-family:var(--mono);font-variant-numeric:tabular-nums;font-size:.82rem;letter-spacing:.022em;line-height:1.6}
.lede{font-size:clamp(1.06rem,1.5vw,1.28rem);color:var(--ink-body);max-width:54ch}
.fine{font-size:.85rem;color:var(--ink-2);max-width:62ch}
.fine a{border-bottom:1px solid var(--line-strong)}
/* .punch was hardcoded #fff in v1/v2, which was fine while every section was dark.
   On a light plate it rendered the emphasised words invisible — cream on cream.
   Bound to the token: body copy is --ink-body, punch is --ink, on either ground. */
.punch{color:var(--ink)}
.punch-u{text-decoration:underline;text-decoration-color:var(--red-ink);text-underline-offset:.16em;text-decoration-thickness:2px}
/* was a chunky mint slab behind the words — it read as a highlighter pen on a page
   whose whole argument is precision. Ink plus one 2px rule underneath. */
mark.hi{background:none;color:#8FE39D;padding:0;box-shadow:inset 0 -2px 0 rgba(99,198,113,.55)}
s.lo{text-decoration:none;color:var(--red-ink)}
.link-mono{font-family:var(--mono);font-size:.82rem;color:var(--ink-2);border-bottom:1px solid var(--line-strong);padding-bottom:2px}
.link-mono:hover{color:var(--ink);border-color:var(--ink)}

.plate-light mark.hi{color:#14611F;box-shadow:inset 0 -2px 0 rgba(30,122,49,.5)}

/* ------------------------------------------------- THE PLATE SYSTEM
   A light plate redefines the tokens on itself. Everything inside inherits,
   so there is no light variant of any component to keep in sync. */
.plate{padding-block:var(--sec);background:var(--bg);color:var(--ink);position:relative;isolation:isolate}
/* One soft light, high and left, on the dark plates only. Not decoration — it is what
   stops a #191A1E page from reading as a flat swatch, and it is the cheapest possible
   depth cue: no shadows on cards, no glass, nothing on the ban list. */
.plate:not(.plate-light)::before{content:"";position:absolute;inset:0;pointer-events:none;z-index:-1;
  background:radial-gradient(120% 90% at 18% -10%,rgba(255,255,255,.045),rgba(255,255,255,.012) 38%,transparent 68%)}
/* THE RED GLOW. Joe: "I like that red glow on the top page. Make it more prominent."
   It was rgba(194,22,58,.055) — barely a rumour. It is now three stacked stops so it has a
   core and a long falloff instead of one flat wash: .17 at the hotspot, .085 through the
   middle, gone by 78%. The white light source stays where it is, top-left, so the red reads
   as a second light in the room rather than a tint on the whole plate. Anything past ~.20
   and the plate goes maroon, which is a different site. */
.plate:not(.plate-light)#top::before{background:
  radial-gradient(86% 66% at 10% -10%,rgba(255,255,255,.06),transparent 60%),
  radial-gradient(68% 52% at 92% 4%,rgba(194,22,58,.17),rgba(194,22,58,.085) 34%,transparent 78%),
  radial-gradient(44% 34% at 97% -4%,rgba(216,40,78,.14),transparent 62%)}
.plate-light{
  --bg:#F0ECE1;--bg-2:#E7E2D5;--panel:#FFFFFF;
  --ink:#1C1B1E;--ink-2:#6A6672;--ink-3:#948F9C;--ink-body:#3D3B44;
  --line:rgba(28,27,30,.14);--line-strong:rgba(28,27,30,.32);
  --grid:rgba(28,27,30,.05);
  --green-deep:#1E7A31;--red-ink:#A3132E;--accent-ink:#FFFFFF;
}
/* the seam between two plates is a hairline of the accent, never a gradient */
.plate+.plate{border-top:1px solid var(--accent)}
.plate-tight{padding-block:clamp(34px,5vw,56px)}

/* section head — number, label, H2, in that order, tight on a phone */
.sh{display:grid;gap:0;margin-bottom:clamp(22px,3.4vw,40px)}
.sh-n{font-family:var(--display);font-size:.72rem;letter-spacing:.22em;color:var(--ink-3);margin-bottom:.9rem;display:block}
.sh h2{margin-bottom:0}
.sh .label{margin-bottom:.7rem}
.sh-sub{margin:1rem 0 0;max-width:56ch}

/* buttons */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:.6em;font-family:var(--display);text-transform:uppercase;letter-spacing:var(--track);font-size:.76rem;padding:1em 1.5em .9em;border:1px solid var(--line-strong);border-radius:var(--radius);color:var(--ink);min-height:48px;text-align:center;
  transition:background .25s var(--ease),border-color .25s,color .25s,transform .25s var(--ease);-webkit-tap-highlight-color:transparent}
.btn:hover{border-color:var(--ink);transform:translateY(-1px)}
/* A 1px inner highlight on the top edge. This is an EDGE, not a drop shadow — the ban
   list forbids shadows on cards, and a lit top edge is how a physical button reads. */
.btn-primary{background:var(--accent);border-color:var(--accent);color:#fff;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.20)}
.btn-primary:hover{background:var(--accent-hover);border-color:var(--accent-hover);color:#fff}
.btn-ghost{box-shadow:inset 0 1px 0 var(--edge-hi)}
.btn-ghost{border-color:var(--line-strong);background:transparent}
.btn-big{padding:1.2em 1.7em 1.1em;font-size:.82rem}
.btn-head{padding:.8em 1.1em .7em;min-height:44px}
.ladder-row{display:flex;flex-wrap:wrap;gap:12px;margin-top:1.5rem}
@media (max-width:620px){.ladder-row .btn{width:100%}}

/* ---------------------------------------------------------------- header */
.site-head{position:sticky;top:0;z-index:50;background:color-mix(in srgb,#191A1E 96%,transparent);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border-bottom:1px solid var(--line);box-shadow:inset 0 2px 0 var(--accent)}
.head-row{display:flex;align-items:center;gap:32px;height:60px}
.logo{display:inline-flex;align-items:center;min-height:44px}
.logo svg{height:30px;width:auto}
.nav{display:flex;gap:26px;margin-left:auto}
.nav a{font-family:var(--display);text-transform:uppercase;letter-spacing:var(--track);font-size:.7rem;color:var(--ink-2)}
.nav a:hover{color:var(--ink)}
@media (max-width:860px){.nav{display:none}.btn-head{margin-left:auto}}
.progress{position:absolute;left:0;right:0;bottom:-1px;height:3px;display:block}
.progress i{display:block;height:100%;width:var(--p,0%);background:linear-gradient(90deg,var(--red-ink),var(--amber),var(--green));transition:width .15s linear}

/* ---------------------------------------------------------------- hero
   Mobile order: label, H1, one line, ONE button, then the scanner.
   No 100svh, no ticker, no second CTA, no call/text line competing with the
   button. The phone -> the hero fits in about a screen and a half and the
   next thing on screen is the instrument, not more prose. */
.hero{padding-top:clamp(26px,4.5vw,64px);padding-bottom:var(--sec)}
.hero-grid{display:grid;grid-template-columns:1fr;gap:clamp(22px,3vw,40px);align-items:start}
.hero h1{margin-bottom:0}
.hero .lede{margin:1.1rem 0 0}
/* THE BUTTON IS THE FIRST THING BELOW THE FOLD, and it is placed by measurement, not by a
   guessed margin. Joe chose "state the offer and the price" as the first screen's only job, so
   on a phone .hero-head is given exactly one screen minus the chrome above it — the headline,
   the price sentence and his cell, and the block ENDS at the fold. The CTA is the next sibling,
   so it starts one pixel past it and is the first thing you meet when you move.
   (A tuned margin-top was tried first and put the button at y=556 on an 844px phone, which is
   not below anything. A percentage of the viewport is a guess; svh minus the chrome is a fact.) */
.hero-cta{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:1.4rem}
@media (max-width:999px){
  .hero{padding-top:clamp(22px,4vw,40px)}
  /* Centred, not top-aligned. Top-aligned left ~500px of black under the last line, which
     reads as a page that failed to finish rather than as space. */
  .hero-head{min-height:calc(100svh - 61px - clamp(22px,4vw,40px));
    display:flex;flex-direction:column;justify-content:center}
  .hero-cta{margin-top:clamp(18px,3vh,30px)}
}
/* FIFTH same-specificity source-order collision this session. This rule used to declare the
   hero's grid areas here and the canonical desktop hero block 26 lines LOWER redeclared them,
   so the ez row never existed and the block auto-placed. The areas now live in ONE place — the
   block below — and this rule only carries what is unique to it. Rule 111: after a CSS change,
   grep the file for the selector again. */
@media (min-width:1000px){.hero-cta{grid-area:cta;align-self:start;margin-top:2rem}}
@media (max-width:620px){.hero-cta .btn{width:100%}}
.hero-alt{margin-top:1.15rem;font-size:.84rem}
.hero-alt a{border-bottom:1px solid var(--line-strong)}
/* One column, and a capped measure. The hero was a 6/6 split with the scanner in the
   right column; the instrument moved to the proof section (see v3_hero's docstring),
   so what is left is type, and type set across 1440px is not a hero, it is a banner.
   The cap is set on the H1's measure — ~19 words a line at the top size — and the lede
   sits a little narrower still so the two edges are not the same line twice. */
@media (min-width:760px){
  .hero{padding-bottom:clamp(52px,6vw,88px)}
  .hero-head h1{max-width:20ch}
  .hero-head .lede{max-width:36rem}
  .hero-head .hero-alt{max-width:36rem}
}

/* The second column is a list of three real businesses, not another instrument, and it
   exists only where the empty half of the screen would otherwise read as a broken grid.
   Below 1000px it is display:none, because the phone hero is type and one button. */
.hero-side{display:none}
@media (min-width:1000px){
  /* Explicit areas with row 1 at max-content. Without them the copy row was sized by the
     CLIENT LIST in column 2 (~490px) and the button landed 120px under the phone line instead
     of 34 — a gap nobody asked for, in the one place the button is supposed to be tight. */
  .hero-grid{grid-template-columns:7fr 4fr;gap:clamp(40px,5vw,84px);align-items:start;
    grid-template-areas:"head side" "cta side" "ez ez";
    grid-template-rows:max-content max-content auto;
    column-gap:clamp(40px,5vw,84px);row-gap:0}
  .hero-head{grid-column:1;grid-area:head}
  .ez{grid-area:ez}
  /* grid-row 1/span 2, not 1. The CTA is row 2 now, and with the side column pinned to row 1
     the row was as tall as the CLIENT LIST (~490px) — which pushed the button 135px below the
     phone line instead of 34. Spanning both rows lets row 1 be sized by the copy alone. */
  .hero-side{display:block;grid-area:side;padding-top:.7rem;
    border-left:1px solid var(--line);padding-left:clamp(20px,2.4vw,34px)}
  .hero-head h1{max-width:18ch}
}
.hs-list{list-style:none;margin:1.1rem 0 0;padding:0;display:grid;gap:1px;
  border-top:1px solid var(--line)}
.hs-list li{border-bottom:1px solid var(--line)}
.hs-list a{display:grid;gap:2px;padding:14px 0;text-decoration:none;color:var(--ink);
  transition:opacity .2s var(--ease)}
.hs-list a:hover{opacity:.72}
.hs-list b{font-family:var(--display);font-weight:400;font-size:1.02rem;letter-spacing:-.012em}
.hs-list .mono{font-family:var(--mono);font-size:.72rem;color:var(--ink-3)}
.hs-live{font-family:var(--display);text-transform:uppercase;letter-spacing:.16em;
  font-size:.52rem;color:var(--green-deep);display:inline-flex;align-items:center;gap:5px;margin-top:3px}
.hs-live i{width:5px;height:5px;border-radius:50%;background:var(--green-deep);
  animation:blink 1.8s ease-in-out infinite}
.hs-more{display:inline-block;margin-top:.95rem;font-family:var(--mono);font-size:.76rem;
  color:var(--ink-2);text-decoration:none;border-bottom:1px solid var(--line-strong);padding-bottom:2px}
.hs-more:hover{color:var(--ink)}

/* the scanner card */
/* RECESSED. --panel is darker than the ground, the top edge carries a shadow line and the
   bottom edge carries the light — so it reads as milled into the plate rather than pasted
   on top of it. The old version had a border AND a second inset border 6px in, which is
   the visual equivalent of saying the same thing twice. */
.scanner{margin:0;max-width:none;position:relative;border:1px solid rgba(0,0,0,.5);border-bottom-color:var(--edge-hi);
  background:var(--panel);border-radius:4px;padding:14px;overflow:hidden;
  box-shadow:inset 0 1px 0 rgba(0,0,0,.6), 0 1px 0 var(--edge-hi);
  background-image:linear-gradient(var(--grid) 1px,transparent 1px),linear-gradient(90deg,var(--grid) 1px,transparent 1px);background-size:26px 26px}
.scanner>*{position:relative;z-index:1}
/* one line at 390px, always — it used to wrap to two and the panel started with a mess */
.sc-head{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:2px 2px 10px;border-bottom:1px solid var(--line);margin-bottom:10px;color:var(--ink-2);white-space:nowrap}
.sc-head>*{min-width:0;overflow:hidden;text-overflow:ellipsis}
.sc-live{display:inline-flex;align-items:center;gap:8px;text-transform:uppercase;letter-spacing:.14em;font-size:.64rem;color:var(--ink);font-family:var(--display)}
.sc-live i{width:8px;height:8px;border-radius:50%;background:var(--red-ink);box-shadow:0 0 10px var(--red-ink);animation:blink 1.4s ease-in-out infinite}
.scanner.after .sc-live i{background:var(--green);box-shadow:0 0 10px var(--green)}
@keyframes blink{0%,100%{opacity:1}50%{opacity:.35}}
.sc-set{font-size:.68rem;text-align:right}
@media (max-width:420px){.sc-set{display:none}}
.sc-body{position:relative;aspect-ratio:1;max-width:520px;margin:0 auto}
.sc-body::before{content:"";position:absolute;inset:-4%;border-radius:50%;pointer-events:none;
  background:repeating-radial-gradient(circle at 50% 50%,transparent 0 46px,rgba(239,237,233,.07) 46px 47px)}
.sc-body::after{content:"";position:absolute;left:50%;top:50%;width:150%;aspect-ratio:1;transform:translate(-50%,-50%);pointer-events:none;border-radius:50%;
  background:conic-gradient(from 0deg,rgba(232,85,106,.28),rgba(232,85,106,.07) 14%,rgba(232,85,106,0) 30%);
  -webkit-mask-image:radial-gradient(closest-side,#000 30%,rgba(0,0,0,.5) 62%,transparent 80%);mask-image:radial-gradient(closest-side,#000 30%,rgba(0,0,0,.5) 62%,transparent 80%);
  animation:sweep 5s linear infinite}
.scanner.after .sc-body::after{background:conic-gradient(from 0deg,rgba(99,198,113,.28),rgba(99,198,113,.07) 14%,rgba(99,198,113,0) 30%)}
@keyframes sweep{to{transform:translate(-50%,-50%) rotate(360deg)}}
.sc-body .grid{position:relative;z-index:1}

/* the grid — the data */
/* ---------------------------------------------------------------- the 49 points
   Joe, 17 Sep: "everything in the dark area ... its really ugly and doesnt feel premium".
   He was looking at this. Forty-nine fully-saturated red discs, nearly touching, with 19px
   white numerals on them: it read as a bingo card, it was ~90% of the hero's visual weight,
   and it was the same red as the buy button.
   Fixed three ways and none of them is decoration:
     1. the unlit states are UNLIT — deep oxblood and deep bronze with a brighter ring, so a
        cold point reads as cold instead of as an alarm;
     2. every dot carries a 1px ring, which is what turns a flat disc into an instrument;
     3. the numerals drop to 15px and to --ink-2 on unlit points, so the NUMBER stops
        shouting and the COLOUR does the talking.
   The lit states are untouched. The whole point is that the after-state now arrives into a
   quiet grid, so the reveal is a bigger jump than it was. */
.grid{width:100%;height:auto;touch-action:pan-y}
.grid .d{fill:var(--red);stroke:var(--red-ring);stroke-width:1.5;
  transition:fill .5s var(--ease),stroke .5s var(--ease);transition-delay:calc(var(--i) * 14ms)}
.grid .d[data-b="a"]{fill:var(--amber-before);stroke:var(--amber-ring)}
.grid[data-state="before"] .d[data-b="a"]{fill:var(--amber-before);stroke:var(--amber-ring)}
.grid .d[data-b="g"]{fill:var(--green-dot);stroke:#3FA94D}
.grid[data-state="after"] .d[data-a="r"]{fill:var(--red);stroke:var(--red-ring)}
.grid[data-state="after"] .d[data-a="a"]{fill:var(--amber);stroke:#A6D45C}
.grid[data-state="after"] .d[data-a="g"]{fill:var(--green-dot);stroke:#54C163}
.grid-num .t{font-family:var(--mono);font-size:15px;font-weight:400;fill:var(--ink-3);text-anchor:middle;dominant-baseline:central;pointer-events:none;transition:opacity .3s,fill .5s var(--ease)}
.grid-num .d[data-b="g"]~.t,.grid-num[data-state="after"] .d[data-a="g"]~.t,.grid-num[data-state="after"] .d[data-a="a"]~.t{font-weight:500}
.grid-num .d[data-b="g"]~.t{fill:#EAF7EC}
.grid-num[data-state="after"] .d[data-a="g"]~.t{fill:#EAF7EC}
.grid-num .t-after{opacity:0}
.grid-num[data-state="after"] .t-before{opacity:0}
.grid-num[data-state="after"] .t-after{opacity:1}
.grid-num[data-state="after"] .d[data-a="a"]~.t{fill:#15200B}
.grid-num .pt{transform-box:fill-box;transform-origin:center;transition:transform .22s var(--ease)}
.grid-num .pt.near{transform:scale(1.38)}
.grid-num .pt.near2{transform:scale(1.14)}
.grid-num .pt.near .d{stroke:var(--ink);stroke-width:2}
.grid-num .probe{fill:none;stroke:var(--ink);stroke-width:1.6;stroke-dasharray:7 11;opacity:0;transition:opacity .3s var(--ease),r .3s var(--ease);pointer-events:none}
.grid-num .probe.on{opacity:.9;r:66}
.grid-num .probe-fill{fill:var(--ink);opacity:0;pointer-events:none;transition:opacity .3s var(--ease)}
.grid-num .probe-fill.on{opacity:.07}
.scanner .grid-num[data-state="after"]{filter:drop-shadow(0 0 10px rgba(99,198,113,.38))}
.scanner .pt{animation:pop .55s var(--ease) backwards;animation-delay:calc(60ms + var(--i) * 16ms)}
@keyframes pop{from{transform:scale(0);opacity:0}to{transform:scale(1);opacity:1}}

/* readout */
.sc-read{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:end;margin-top:12px;padding-top:12px;border-top:1px solid var(--line)}
.sc-read .label{margin-bottom:5px;font-size:.6rem}
/* the unrevealed side used to be a bare grey dash floating in space. It is a pending
   readout, so it should look like one: dimmed, monospaced, sitting on its own baseline. */
.scanner:not(.after) .sc-read>div:last-child{opacity:.5}
.scanner:not(.after) .big:not(.dim){font-family:var(--mono);font-size:1.5rem;letter-spacing:0;color:var(--ink-3)}
.big{font-family:var(--display);font-size:clamp(2rem,4vw,2.9rem);letter-spacing:-.03em;line-height:1;display:block;color:var(--green-deep);font-weight:400}
.big.dim{color:var(--red-ink);opacity:.85}
.scanner:not(.after) .big:not(.dim){color:var(--ink-3)}
.bar{display:block;height:5px;background:rgba(0,0,0,.45);box-shadow:inset 0 1px 0 rgba(0,0,0,.6),0 1px 0 var(--edge-hi);border-radius:3px;margin-top:9px;overflow:hidden}
.bar i{display:block;height:100%;width:0;background:var(--green);border-radius:3px;transition:width 1.1s var(--ease)}
.bar.dim i{background:var(--red-ink)}
.scanner.after .bar i,.scanner .bar.dim i{width:var(--w)}
.scanbtn{position:relative;display:flex;align-items:center;justify-content:space-between;gap:16px;width:100%;margin:12px 0 4px;padding:15px 18px;border-radius:3px;cursor:pointer;
  border:1px solid var(--accent);background:var(--accent);color:#fff;text-align:left;transition:background .3s var(--ease),border-color .3s var(--ease),transform .16s var(--ease);-webkit-tap-highlight-color:transparent;touch-action:manipulation}
.scanbtn:hover{transform:translateY(-2px)}.scanbtn:active{transform:translateY(1px)}
.scanbtn:focus-visible{outline:2px solid var(--ink);outline-offset:4px}
.sb-txt{display:grid;gap:3px;line-height:1.15;pointer-events:none}
.sb-txt i{font-style:normal;font-family:var(--display);text-transform:uppercase;letter-spacing:.18em;font-size:.55rem;opacity:.85}
.sb-txt b{font-family:var(--display);font-weight:400;text-transform:uppercase;letter-spacing:.09em;font-size:1.02rem;line-height:1.1}
.sb-b{display:none}
.sb-arrow{flex:none;font-size:1.4rem;line-height:1;pointer-events:none;animation:nudge 1.5s var(--ease) infinite}
@keyframes nudge{0%,100%{transform:translateX(0)}50%{transform:translateX(6px)}}
.scanbtn[aria-pressed="true"]{background:var(--green-dot);border-color:var(--green-dot)}
.scanbtn[aria-pressed="true"] .sb-a{display:none}.scanbtn[aria-pressed="true"] .sb-b{display:grid}
.scanner figcaption{display:grid;gap:7px;margin-top:10px;color:var(--ink-2)}
.state-cap{color:var(--red-ink)}
[data-cap-after]{display:none;color:var(--green-deep)}
.scanner.after [data-cap-before]{display:none}.scanner.after [data-cap-after]{display:block}
.legend{display:flex;flex-wrap:wrap;gap:7px 15px}
.lg{display:inline-flex;align-items:center;gap:6px;font-family:var(--mono);font-size:.7rem}
/* the legend is the key to the grid, so the swatches carry the same fill AND the same ring.
   Without the ring they went invisible the moment the unlit fills were darkened. */
.lg i{width:10px;height:10px;border-radius:50%;background:var(--red);box-shadow:inset 0 0 0 1.5px var(--red-ring)}
.lg i[data-b="g"]{background:var(--green-dot);box-shadow:inset 0 0 0 1.5px #3FA94D}
.lg i[data-b="a"]{background:var(--amber);box-shadow:inset 0 0 0 1.5px #A6D45C}
.scanner:not(.after) .lg i[data-b="a"]{background:var(--amber-before);box-shadow:inset 0 0 0 1.5px var(--amber-ring)}
.scanner figcaption .fine{font-size:.76rem}

/* ---------------------------------------------------------------- proof */
.pair{display:grid;grid-template-columns:1fr 1fr;gap:12px;align-items:start}
.panel{margin:0;border:1px solid rgba(0,0,0,.45);border-bottom-color:var(--edge-hi);background:var(--panel);border-radius:4px;padding:10px;display:flex;flex-direction:column;
  box-shadow:inset 0 1px 0 rgba(0,0,0,.5)}
.plate-light .panel{border-color:var(--line);box-shadow:none}
.panel .grid{margin-bottom:auto}
.panel figcaption{display:grid;gap:3px;margin-top:10px;color:var(--ink-2);font-size:.72rem}
.panel figcaption .solv{color:var(--ink);margin-top:3px}
.panel figcaption .solv b{color:var(--green-deep)}
.pair .panel:first-child figcaption .solv b{color:var(--red-ink)}
.panel-num .grid .d{transition-delay:0s}
/* v2 shipped a dot-by-dot "develop" on the after panel. At real scrolling speed
   the most important picture on the page was caught half-drawn — a row and a
   half of green against a full grid of red. The panel now arrives as a whole:
   one fade and one lift, both panels the same height, both always complete. */
.js .pair[data-reveal] .panel{opacity:0;transform:translateY(14px)}
.pair[data-reveal].in .panel{opacity:1;transform:none;transition:opacity .5s var(--ease),transform .5s var(--ease)}
.pair[data-reveal].in .panel:last-child{transition-delay:.14s}
@media (prefers-reduced-motion:reduce){.js .pair[data-reveal] .panel{opacity:1;transform:none}}
.pair-cap{display:flex;align-items:baseline;flex-wrap:wrap;gap:10px;margin:0 0 12px}  /* was justify-content:center + text-align:center, correct when this captioned a
     symmetric two-panel pair. It now captions one left-aligned instrument, and a
     centred line above a left-aligned figure reads as a typo. */
.pair-cap b{font-family:var(--display);font-size:clamp(1.15rem,4.6vw,1.9rem);color:var(--green-deep);letter-spacing:-.02em;line-height:1.05;white-space:nowrap}
.pair-cap .label{margin:0;font-size:.58rem;letter-spacing:.11em}
@media (max-width:420px){.pair{gap:7px}.pair .panel{padding:6px}.pair .panel figcaption{font-size:.6rem;gap:1px}}
.proof-line{margin:1.4rem 0 0;font-family:var(--display);font-size:clamp(1.08rem,2vw,1.45rem);color:var(--ink)}
.stats{list-style:none;margin:1.3rem 0 0;padding:0;display:grid;grid-template-columns:repeat(3,1fr);border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
.stats li{display:grid;gap:2px;padding:15px 10px 15px 0;border-left:1px solid var(--line);align-content:start}
.stats li:first-child{border-left:0}
.stats li+li{padding-left:12px}
.stat-n{font-family:var(--display);font-size:clamp(2.1rem,7vw,3.2rem);line-height:.95;letter-spacing:-.04em;color:var(--ink)}
.stats li:nth-child(2) .stat-n{color:var(--green-deep)}
.stats .label{margin:6px 0 0}
@media (max-width:479px){.stats .fine{display:none}.stats .label{font-size:.56rem;letter-spacing:.1em}}
/* align-items back to START. center was a patch for a short right column; the column now
   carries the CTA and the disclosure, so it fills and both columns share a top edge. */
@media (min-width:980px){.proof-grid{display:grid;grid-template-columns:6fr 5fr;column-gap:clamp(32px,4vw,64px);align-items:start}.proof-grid .stats{margin-top:1.4rem}.proof-grid .proof-line{margin-top:.2rem}.proof-say .ladder-row{margin-top:clamp(26px,3vw,44px)}.proof-say .disclosure{margin-top:clamp(20px,2.4vw,34px)}.proof-fig .raw{margin-top:clamp(20px,2.6vw,34px)}/* Cap the instrument. At 7fr of a 1240px wrap it was 830px wide and therefore ~1000px
   TALL, against a 470px copy column — the gap is what read as an island, and no amount
   of vertical alignment closes a 530px difference. A 49-dot grid does not need 830px. */.proof-fig .scanner{max-width:620px}/* THE STATS STACK. Three numbers squeezed 3-across into a 470px column was both cramped
   and the reason the column was short: 444px against a 1012px instrument. Stacked, each
   number gets its own row on a hairline, the column runs ~760px, and the two sides of the
   section are within ~250px of each other instead of 570. It also reads better — a spec
   column beside an instrument, which is the thing this section is pretending to be. */.proof-say .stats{grid-template-columns:1fr;border-bottom:0}.proof-say .stats li{border-left:0;border-bottom:1px solid var(--line);grid-template-columns:auto 1fr;column-gap:20px;align-items:baseline;padding:18px 0}.proof-say .stats li+li{padding-left:0}.proof-say .stats li .stat-n{grid-row:1/span 2;font-size:clamp(2.4rem,3.4vw,3.2rem);min-width:4.4ch}.proof-say .stats li .label{grid-column:2;margin:0}.proof-say .stats li .fine{grid-column:2;margin:2px 0 0}}
details.raw{margin-top:clamp(18px,3vw,30px);border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
details.raw summary{cursor:pointer;list-style:none;padding:14px 0;display:flex;align-items:center;gap:10px;min-height:48px}
details.raw summary::-webkit-details-marker{display:none}
details.raw summary .label{margin:0}
details.raw summary::before{content:"+";font-family:var(--mono);color:var(--accent);width:16px}
details.raw[open] summary::before{content:"\2013"}
details.raw .pair{padding-bottom:20px}
.panel-big{background:var(--plate);border-color:var(--line-strong)}
.panel-big figcaption{color:#5D5966}.panel-big figcaption .solv{color:#1C1B1E}
.panel-big img{border:1px solid rgba(28,27,30,.10);border-radius:2px}
.disclosure{margin-top:1.5rem;border-left:2px solid var(--accent);padding-left:14px}

/* ---------------------------------------------------------------- work
   NEW in v3. He sells websites and showed none. These are live client sites,
   screenshotted at phone width. The frame is a real phone shape so nobody
   mistakes it for a stock illustration. */
.work-strip{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(14px,2.4vw,34px)}
@media (max-width:760px){
  .work-strip{grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:72%;
    overflow-x:auto;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;gap:12px;
    margin-inline:calc(var(--gut) * -1);padding-inline:var(--gut);padding-bottom:8px;scrollbar-width:none}
  .work-strip::-webkit-scrollbar{display:none}
  .work-card{scroll-snap-align:start}
}
.work-card{display:grid;gap:0;min-width:0}
/* The card is a phone. A screenshot of a website is a picture of a picture; a page
   actually scrolling is the product. The poster is FRAME ZERO of the clip, so the still and
   the first painted video frame are the same pixels and the handoff is invisible, and every
   clip is trimmed to the first frame of movement — the originals held still for 4.5-6.3s
   while the recorded page painted, which is the pause Joe saw. app.js loads on approach. */
/* The frame is a BUTTON now, not a span inside a link: it plays and pauses its own reel,
   and the name underneath is the link to the client's site. A play badge inside an anchor
   that navigates instead of playing is a lie about what tapping does (rule 13) — and a
   <button> inside an <a> is invalid HTML, which is the same problem from the other side. */
.work-shot{display:block;width:100%;padding:0;margin:0;font:inherit;color:inherit;cursor:pointer;
  -webkit-appearance:none;appearance:none;
  position:relative;border:1px solid rgba(28,27,30,.28);border-radius:14px;overflow:hidden;background:#0C0D10;
  box-shadow:0 0 0 1px rgba(255,255,255,.5) inset}
.work-shot:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
/* NO object-fit on the video, deliberately, and it cost a round trip to learn why.
   A <video> inside an overflow-hidden, border-radiused box gets its own composited layer,
   and Chrome then paints the frame at its natural size in the top-left corner and ignores
   object-fit entirely — a grey box with a small picture in the corner. It reproduced in
   REAL Chrome on a real GPU, not just headless, so it is a bug and not a capture artifact.
   The fix is to not need object-fit: the clips are encoded 600x800 and the card is 3:4, so
   width:100% + height:auto gives a pixel-identical result with an ordinary paint path.
   The aspect-ratio on the frame is gone for the same reason — the video defines the height.
   If the clips are ever re-encoded at another ratio, this breaks silently. Keep them 3:4. */
.work-shot video{width:100%;height:auto;display:block;background:#0C0D10}
.work-shot>img{width:100%;height:auto;display:block;background:#0C0D10}
/* The chip used to float over the bottom-left of the frame, where it landed on top of the
   client's own buttons and read as part of THEIR site. It belongs in our meta row. */
.work-live{display:inline-flex;align-items:center;gap:6px;margin-right:9px;
  font-family:var(--display);text-transform:uppercase;letter-spacing:.16em;font-size:.56rem;color:var(--green-deep)}
.work-live i{width:6px;height:6px;border-radius:50%;background:var(--green-deep);animation:blink 1.8s ease-in-out infinite}
@media (prefers-reduced-motion:reduce){.work-live i{animation:none}}
.work-meta .wnum{display:flex;align-items:center}
.work-meta{display:grid;gap:2px;padding:14px 2px 0}
.work-meta b{font-family:var(--display);font-size:1.05rem;letter-spacing:-.01em;color:var(--ink);display:flex;align-items:baseline;gap:6px}
.work-meta .mono{color:var(--ink-2);font-size:.74rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.work-meta .wnum{color:var(--green-deep);font-family:var(--mono);font-size:.78rem;margin-top:3px}
.work-card:hover .work-shot,.work-shot:hover{border-color:var(--accent)}
.work-meta:hover b{color:var(--accent)}

/* THE PLAY BADGE. Joe: "put very obvious video play buttons on the other websites."
   It is driven off the video's own play/pause events, so it is showing the truth and not
   our intention — and it is gone the instant the reel runs, because a play button over
   playing video is the second-most confusing control on a page. Big enough to hit with a
   thumb through a work glove: 64px, over the 44px floor with room to spare. */
.work-play{position:absolute;inset:0;display:grid;place-content:center;gap:7px;
  justify-items:center;pointer-events:none;
  background:radial-gradient(60% 45% at 50% 50%,rgba(6,7,9,.52),rgba(6,7,9,.16) 70%,transparent);
  transition:opacity .22s var(--ease)}
.work-play svg{width:26px;height:26px;padding:18px;box-sizing:content-box;border-radius:50%;
  color:#fff;background:rgba(10,11,14,.72);border:1px solid rgba(255,255,255,.34);
  box-shadow:0 6px 22px rgba(0,0,0,.42);backdrop-filter:blur(2px)}
.work-play i{font-family:var(--display);font-style:normal;text-transform:uppercase;
  letter-spacing:.2em;font-size:.56rem;color:#fff;text-shadow:0 1px 6px rgba(0,0,0,.7)}
.work-shot.playing .work-play{opacity:0}
.work-shot:hover .work-play svg{background:var(--accent);border-color:var(--accent)}
.work-swipe{margin:12px 0 0}
/* .swipe.work-swipe, not .work-swipe. The generic .swipe rule is 24 lines FURTHER DOWN this
   file and sets display:flex at the same specificity, so source order won and "SWIPE FOR THE
   OTHERS" was showing at 1440px, where there is nothing to swipe. Third time this session
   that a later same-specificity rule quietly beat an earlier one (rules 17, 111). */
@media (min-width:761px){.swipe.work-swipe{display:none}}

/* ---------------------------------------------------------------- pricing */
.price-strip{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(14px,2vw,28px);align-items:stretch}
@media (max-width:820px){
  .price-strip{grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:82%;
    overflow-x:auto;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;gap:12px;
    margin-inline:calc(var(--gut) * -1);padding-inline:var(--gut);padding-bottom:8px;scrollbar-width:none}
  .price-strip::-webkit-scrollbar{display:none}
  .pcard{scroll-snap-align:start}
}
.pcard{background:var(--panel);border:1px solid var(--line);border-radius:4px;padding:clamp(20px,2.4vw,30px);display:flex;flex-direction:column;position:relative}
.plate-light .pcard{background:#FFFDF8}
.pcard.feat{border-color:var(--accent)}
.pcard .ptag{position:absolute;top:-1px;right:-1px;background:var(--accent);color:#fff;font-family:var(--display);text-transform:uppercase;letter-spacing:.16em;font-size:.58rem;padding:7px 11px 5px}
.pcard .pname{font-family:var(--display);text-transform:uppercase;letter-spacing:var(--track);font-size:.68rem;color:var(--ink-2);display:block;margin-bottom:.6rem}
.pcard .pnum{font-family:var(--display);font-size:clamp(2.6rem,8vw,3.5rem);line-height:1;letter-spacing:-.035em;color:var(--ink);display:block}
.pcard .punit{font-size:.84rem;color:var(--ink-2);margin:.5rem 0 0}
.pcard .padd{font-family:var(--mono);font-size:.78rem;color:var(--accent);margin:.35rem 0 0}
.pcard ul{list-style:none;margin:1.1rem 0 1.3rem;padding:1.1rem 0 0;display:grid;gap:.65rem;border-top:1px solid var(--line)}
.pcard li{display:grid;grid-template-columns:16px 1fr;gap:9px;font-size:.93rem;color:var(--ink-body);line-height:1.45}
.pcard li::before{content:"\2713";color:var(--green-deep);font-size:.85rem;line-height:1.5}
.pcard .pnot{font-size:.84rem;color:var(--ink-3);margin:-.7rem 0 1.2rem}
.pcard .btn{margin-top:auto;width:100%}
.swipe{font-family:var(--display);text-transform:uppercase;letter-spacing:.16em;font-size:.6rem;color:var(--ink-3);display:flex;align-items:center;gap:8px;margin:12px 0 0}
.swipe::after{content:"\2192";font-size:.9rem;animation:nudge 1.6s var(--ease) infinite}
@media (min-width:821px){.price-strip+.swipe{display:none}}
.price-note{margin-top:1.6rem;border-left:2px solid var(--accent);padding-left:14px;color:var(--ink-body);font-size:.95rem}
.price-fine{margin-top:1rem}

/* ---------------------------------------------------------------- how */
.how-grid{display:grid;gap:clamp(24px,3vw,48px)}
@media (min-width:900px){.how-grid{grid-template-columns:5fr 7fr;align-items:start}}
.quote{margin:0;border-left:2px solid var(--accent);padding-left:clamp(14px,2vw,22px)}
.quote p{font-family:var(--display);font-size:clamp(1.25rem,4.2vw,1.9rem);line-height:1.22;color:var(--ink);letter-spacing:-.015em}
.quote cite{font-style:normal;font-family:var(--mono);font-size:.78rem;color:var(--ink-2)}
.steps{list-style:none;margin:0;padding:0;display:grid}
.steps li{display:grid;grid-template-columns:auto 1fr;gap:14px;align-items:baseline;padding:14px 0;border-top:1px solid var(--line)}
.steps li:last-child{border-bottom:1px solid var(--line)}
.steps .sn{font-family:var(--mono);font-size:.9rem;color:var(--green-deep)}
.steps b{font-family:var(--display);font-size:1.05rem;color:var(--ink);display:block}
.steps span.sd{display:block;font-size:.88rem;color:var(--ink-2);margin-top:2px}

/* ---------------------------------------------------------------- trust + clients */
.trust{list-style:none;margin:clamp(28px,4vw,52px) 0 0;padding:0;display:grid;grid-template-columns:1fr 1fr;border-top:1px solid var(--line);border-left:1px solid var(--line)}
.trust li{display:grid;gap:2px;padding:15px 14px;border-right:1px solid var(--line);border-bottom:1px solid var(--line);background:var(--panel)}
.trust b{color:var(--green-deep);font-family:var(--display);font-size:1.02rem;letter-spacing:.01em}
.trust span{color:var(--ink-2);font-size:.79rem}
.trust a{display:grid;gap:2px}
@media (min-width:900px){.trust{grid-template-columns:repeat(4,1fr)}}

/* ---------------------------------------------------------------- vs */
.vs-list{list-style:none;margin:0;padding:0;display:grid;gap:12px}
.vsrow{border:1px solid var(--line);border-radius:4px;padding:16px 16px 14px;background:var(--panel)}
.plate-light .vsrow{background:#FFFDF8}
.vsrow .vq{font-family:var(--display);font-size:1.02rem;color:var(--ink);display:block;margin-bottom:.7rem}
.vsrow .va{display:grid;gap:8px}
.vsrow .va span{display:grid;grid-template-columns:auto 1fr;gap:9px;font-size:.92rem;line-height:1.45}
.vsrow .va .them{color:var(--ink-2)}
.vsrow .va .them::before{content:"\2715";color:var(--red-ink);font-size:.82rem;line-height:1.55}
.vsrow .va .us{color:var(--ink)}
.vsrow .va .us::before{content:"\2713";color:var(--green-deep);font-size:.85rem;line-height:1.5}
@media (min-width:900px){.vs-list{gap:0;border-top:1px solid var(--line)}.vsrow{border:0;border-bottom:1px solid var(--line);border-radius:0;background:none;display:grid;grid-template-columns:4fr 4fr 4fr;gap:24px;align-items:baseline;padding:18px 0}.plate-light .vsrow{background:none}.vsrow .vq{margin:0}.vsrow .va{display:contents}}
.vs-kicker{margin-top:1.5rem;font-family:var(--display);font-size:clamp(1.02rem,2vw,1.28rem);color:var(--ink);max-width:56ch}

/* ---------------------------------------------------------------- faq (inside the vs plate) */
.faq-block{margin-top:clamp(30px,4vw,54px);padding-top:clamp(24px,3vw,38px);border-top:1px solid var(--line)}
.faq-list{border-top:1px solid var(--line)}
.faq-list details{border-bottom:1px solid var(--line)}
.faq-list summary{cursor:pointer;list-style:none;padding:17px 34px 17px 0;position:relative;font-family:var(--display);font-size:1.04rem;color:var(--ink);min-height:48px}
.faq-list summary::-webkit-details-marker{display:none}
.faq-list summary::after{content:"+";position:absolute;right:4px;top:50%;transform:translateY(-50%);font-family:var(--mono);color:var(--accent);font-size:1.15rem}
.faq-list details[open] summary::after{content:"\2013"}
.faq-list p{margin:0 0 17px;max-width:66ch}
.faq-more{margin-top:1.4rem}

/* ---------------------------------------------------------------- joe */
.joe-grid{display:grid;gap:clamp(22px,3vw,44px)}
@media (min-width:820px){.joe-grid{grid-template-columns:4fr 8fr;align-items:start}}
.joe-photo{margin:0;max-width:320px;display:grid;grid-template-columns:118px 1fr;gap:14px;align-items:center}
@media (min-width:820px){.joe-photo{display:block;max-width:300px}}
.joe-photo img{border-radius:3px;border:1px solid var(--line-strong);width:100%}
.joe-photo figcaption{margin-top:0}
@media (min-width:820px){.joe-photo figcaption{margin-top:10px}}
.joe-photo figcaption b{font-family:var(--display);color:var(--ink);display:block}
.joe-photo figcaption span{font-size:.82rem;color:var(--ink-2)}

/* ---------------------------------------------------------------- book */
.book-grid{display:grid;gap:clamp(24px,3vw,48px)}
@media (min-width:920px){.book-grid{grid-template-columns:5fr 6fr;align-items:start}}
.form-card{border:1px solid rgba(0,0,0,.45);border-bottom-color:var(--edge-hi);background:var(--panel);border-radius:4px;padding:clamp(20px,2.6vw,30px);box-shadow:inset 0 1px 0 rgba(0,0,0,.5)}
.plate-light .form-card{border-color:var(--line-strong);box-shadow:none}
.plate-light .form-card{background:#FFFDF8}
.form-title{font-family:var(--display);font-size:clamp(1.2rem,2.2vw,1.5rem);color:var(--ink);margin:0 0 1.1rem;line-height:1.15}
.form-row{margin-bottom:12px}
.form-row-2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
@media (max-width:440px){.form-row-2{grid-template-columns:1fr}}
.form label{display:grid;gap:6px;font-family:var(--display);text-transform:uppercase;letter-spacing:.14em;font-size:.6rem;color:var(--ink-2)}
.form input{font:inherit;font-size:17px;padding:13px 14px;min-height:48px;background:#0C0D10;border:1px solid rgba(0,0,0,.5);border-bottom-color:var(--edge-hi);border-radius:3px;color:var(--ink);width:100%;box-shadow:inset 0 1px 0 rgba(0,0,0,.5)}
.plate-light .form input{background:#fff;border-color:var(--line-strong);box-shadow:none}
.plate-light .form input{background:#fff}
.form input:focus{outline:2px solid var(--accent);outline-offset:1px}
.hp{position:absolute;left:-9999px;width:1px;height:1px}
.btn-form{width:100%;margin-top:6px}
.form-msg{margin:10px 0 0;font-size:.86rem;color:var(--red-ink);min-height:1em}
.form-fine{margin:10px 0 0;font-size:.78rem}
.book-btns{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:1.4rem}
.book-btns .btn{flex-direction:column;gap:2px;align-items:flex-start;text-align:left;padding:14px 16px}
.book-btns .label{margin:0;font-size:.58rem}
.book-btns .phone{color:var(--ink);font-size:.92rem}
@media (max-width:520px){.book-btns{grid-template-columns:1fr}}

/* ---------------------------------------------------------------- footer
   NOTE: home_v3() calls the SAME footer() as every sub-page, so these rules must
   target footer()'s real markup — .foot-grid / .foot-col / .foot-note / .foot-base —
   and they must look identical to what base.css + instrument produce, because the
   sub-pages are still rendered on that stylesheet. The first draft of this file
   invented .foot-row / .foot-nav, which match nothing: the v3 home shipped with an
   unstyled footer for a day and nobody saw it because it is below the form. */
/* pinned to the literal, not to --bg-2: the token moved when the dark scale was reworked
   and the footer silently stopped matching the sub-pages an hour after it was matched.
   Rule 90 — bind to the token, EXCEPT where the value is a contract with another file. */
.site-foot{border-top:1px solid var(--line);background:#101114;padding-block:clamp(30px,4vw,52px);color:var(--ink-2)}
.foot-grid{display:grid;grid-template-columns:1fr 1fr;gap:22px}
.foot-grid>div:first-child{grid-column:1/3}
@media (min-width:760px){.foot-grid{grid-template-columns:6fr 3fr 3fr;gap:32px}.foot-grid>div:first-child{grid-column:auto}}
.logo-foot svg{height:28px;width:auto}
.foot-note{margin:14px 0 0;font-size:.85rem;color:var(--ink-2);max-width:42ch}
.foot-col{display:grid;gap:9px;align-content:start}
.foot-col .label{margin-bottom:2px}
.foot-col a{font-size:.9rem;color:var(--ink-2)}
.foot-col a:hover{color:var(--ink)}
.foot-base{display:flex;flex-wrap:wrap;gap:10px 24px;justify-content:space-between;margin-top:clamp(24px,3vw,38px);padding-top:16px;border-top:1px solid var(--line)}
.foot-base .mono{font-size:.74rem;color:var(--ink-3)}

/* ---------------------------------------------------------------- sticky mobile bar */
.stickybar{position:fixed;left:0;right:0;bottom:0;z-index:60;display:grid;grid-template-columns:44px 1fr 1fr;align-items:stretch;
  background:rgba(16,17,20,.96);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-top:1px solid var(--line-strong)}
.stickybar .sb-mk{display:grid;place-items:center;color:var(--accent)}
.stickybar .sb-mk .mk{width:16px;height:16px}
.stickybar a{display:grid;place-items:center;font-family:var(--display);text-transform:uppercase;letter-spacing:var(--track);font-size:.72rem;min-height:54px;color:#EFEDE9}
.stickybar .sb-call{background:var(--accent);color:#fff}
@media (min-width:861px){.stickybar{display:none}}
@media (max-width:860px){body{padding-bottom:54px}}

/* ---------------------------------------------------------------- reveal */
.js [data-reveal]{opacity:0;transform:translateY(16px)}
[data-reveal].in{opacity:1;transform:none;transition:opacity .55s var(--ease),transform .55s var(--ease)}
@media (prefers-reduced-motion:reduce){
  .js [data-reveal]{opacity:1;transform:none}
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}
}

/* The BEFORE state is a cold instrument with one live point on it, not a field of colour.
   It was brown-on-black with 49 brown numbers — Joe: "really ugly and doesn't feel
   premium", three times over. The fix was contrast, not saturation: --red and
   --amber-before now sit at the panel's own value with a hairline ring, so everything
   unlit recedes, the numbers drop to --ink-3, and the single #3 is the only thing on the
   board with a pulse in it. Pressing the button is then a room turning on. From brown
   there was nowhere to go. */
.grid[data-state="before"] .d[data-b="g"]{filter:drop-shadow(0 0 8px rgba(46,139,58,.6))}

/* The header's filled CTA is gone (see header() in build.py). What is left is a nav link that
   still reaches #book, sized to the tap-target floor, and visible on a phone where the rest of
   the nav is not — so removing the button did not remove the way to the form. */
.nav-book{color:var(--ink);border-bottom:1px solid var(--accent);padding-bottom:2px}
.nav-book:hover{color:var(--accent)}
@media (max-width:820px){
  .nav{display:flex;align-items:center;gap:0;margin-left:auto}
  .nav a:not(.nav-book){display:none}
  .nav-book{display:inline-flex;align-items:center;min-height:44px;padding:0 2px;
    font-family:var(--display);text-transform:uppercase;letter-spacing:.14em;font-size:.66rem}
}
/* The sticky bar is gone; nothing should still be paying for it. */
.stickybar{display:none}
body{padding-bottom:0}

/* ============================================================================
   PASS 6 — "the home page is a little too bare now. Looks like it's on an
   island I think some of the layout of things looks ai made."

   Both halves of that are one problem: the page is SIX IDENTICAL STACKS in one
   1240px column with a dead right margin. Every section opened with the same
   three-part stamp — grey number, red mark + caps eyebrow, h2 — and both card
   rows were perfectly even three-column grids with tick bullets, which is the
   single most recognisable template pattern on the web and is on the client's
   own ban list (§4.7: "Even three-column feature cards" BANNED, "Asymmetric
   editorial grid" REQUIRED). Nothing on the page had an edge, so the content
   floated: the island.

   Three fixes, all structural, no new content:
     1. THE SPINE. Section heads become a two-column editorial rail on desktop —
        number and eyebrow hung in a 172px margin column, headline in the wide
        one, a hairline between them. One vertical line down the page is what
        turns a stack of centred blocks into a set page.
     2. NO EVEN THREE-UP. Both card rows get asymmetric columns and the featured
        price card is lifted out of the row.
     3. PLATE SEAMS. Two adjacent plates were each paying full --sec, so every
        section boundary was ~280px of nothing. The second one pays 0.7.
   ============================================================================ */

@media (min-width:1000px){
  .sh{grid-template-columns:172px 1fr;column-gap:44px;align-items:start;
    background-image:linear-gradient(var(--line),var(--line));
    background-repeat:no-repeat;background-size:1px calc(100% - .3em);
    background-position:194px .3em}
  .sh-n{grid-column:1;grid-row:1;font-size:.86rem;letter-spacing:.24em;color:var(--ink-2);margin-bottom:.7rem}
  .sh .label{grid-column:1;grid-row:2;margin:0;align-items:flex-start;line-height:1.45}
  .sh h2{grid-column:2;grid-row:1/span 2;max-width:24ch}
  .sh-sub{grid-column:2;grid-row:3;margin:1.15rem 0 0}
}

/* Work: 1.3 / 1 / 1, and the third card drops off the baseline. Even thirds was
   the ban-list violation; a single offset is what makes three things read as a
   composition instead of a table row. */
@media (min-width:761px){
  .work-strip{grid-template-columns:1.3fr 1fr 1fr;align-items:start}
  .work-card:nth-child(3){margin-top:clamp(18px,3.4vw,52px)}
}

/* Pricing: the tier he wants sold is physically bigger and sits above the line
   the other two share. A price table where all three cards are the same size is
   a table; this is a recommendation. */
@media (min-width:821px){
  .price-strip{grid-template-columns:1fr 1fr 1.18fr;align-items:start}
  .pcard{height:100%}
  .pcard.feat{margin-top:calc(clamp(18px,2.6vw,38px) * -1);
    padding-top:calc(clamp(20px,2.4vw,30px) + clamp(18px,2.6vw,38px));
    box-shadow:0 0 0 1px var(--accent) inset}
}

/* Two plates meeting used to pay var(--sec) twice — ~280px of nothing at every
   seam once the section rhythm went up. The seam keeps the hairline; it does not
   need two full sections of air. */
.plate + .plate{padding-top:calc(var(--sec) * .7)}

/* ---------------------------------------------------------------- speed and ease
   Three facts on hairlines, sharing the section rail, on the hero's own ground so it reads as
   the hero's second paragraph rather than a ninth stop on the page. The number is display type
   at a size that competes with an h2 — that is the "big emphasis" Joe asked for, and it costs
   ~330px at 390px instead of a whole plate. */
/* It is a BLOCK ON THE HERO PLATE, not its own section. As a section it paid var(--sec) at
   both ends of a seam and added 1,200px to the page, which undid four passes of cutting. Here it
   costs ~400px, and it lands on the first scroll — the strongest position on the page for the
   answer to "what is this going to cost me in hassle". */
.ez{margin-top:clamp(34px,5vw,64px);padding-top:clamp(26px,3vw,40px);border-top:1px solid var(--line)}
.ez-list{list-style:none;margin:0;padding:0;display:grid;border-top:1px solid var(--line)}
.ez-list li{display:grid;gap:4px;padding:clamp(20px,2.6vw,30px) 0;border-bottom:1px solid var(--line)}
.ez-n{font-family:var(--display);font-weight:400;font-size:clamp(2rem,6.4vw,2.9rem);
  line-height:1;letter-spacing:-.035em;color:var(--ink);font-variant-numeric:tabular-nums}
.ez-list .label{margin:0;color:var(--accent);letter-spacing:.18em}
.ez-say{color:var(--ink-body);max-width:62ch;margin-top:4px}
.ez-fine{margin-top:clamp(18px,2.2vw,28px);max-width:64ch;color:var(--ink-2)}
@media (min-width:1000px){
  /* The numbers hang in the SAME 172px margin column the section rail uses, so the eye reads one
     vertical edge down the whole page instead of two competing ones. The 172px is therefore not
     adjustable, which sets the type size: "15 minutes" has to fit it on ONE line (at 2.7rem it
     broke to two and read as a mistake), so the number is capped at 2rem and nowrapped. A number
     that wraps is not emphasis. */
  .ez-list li{grid-template-columns:172px 1fr;column-gap:44px;align-items:start;gap:0}
  .ez-n{grid-column:1;grid-row:1/span 2;font-size:clamp(1.6rem,2vw,2rem);white-space:nowrap}
  .ez-list .label{grid-column:2;grid-row:1;margin-bottom:.5rem}
  .ez-say{grid-column:2;grid-row:2;margin:0}
  .ez-fine{margin-left:calc(172px + 44px)}
}


/* ============================================================
   8 Oct 2026 — HOME GOES LIGHT. Joe: "this is NOT how the site looks.
   it's supposed to have that white background like the other pages."
   Every home plate is now .plate-light (build.py). The ground is the
   exact one /pricing, /proof and /how-it-works paint: cream #F0ECE1,
   grain, topo contours, a soft vignette. Header + footer stay dark,
   same as the sub-pages, so a click never changes the room.
   ============================================================ */
:root{
  --grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.2 0 0 0 0 0.18 0 0 0 0 0.12 0 0 0 .05 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
body{
  background-color:#F0ECE1;
  background-image:var(--grain),url('/img/topo.svg');
  background-size:auto,1100px 1100px;
  background-position:0 0,center top;
  background-attachment:fixed,fixed;
}
body::before{content:"";position:fixed;inset:0;z-index:-2;pointer-events:none;
  background:radial-gradient(140% 120% at 50% 50%,transparent 55%,rgba(28,27,30,.09) 100%)}
/* let the contours run through every section instead of stopping at a flat panel */
.plate.plate-light{background:transparent}
/* the hero keeps a whisper of the brand red, top right — light-ground strength (≤8%, rule 56) */
.plate-light#top::before{content:"";position:absolute;inset:0;pointer-events:none;z-index:-1;
  background:radial-gradient(60% 55% at 94% 0%,rgba(194,22,58,.07),rgba(194,22,58,.03) 40%,transparent 75%)}
/* seams between light plates: a hairline, not a red rule across every section */
.plate-light+.plate-light{border-top:1px solid rgba(28,27,30,.10)}
/* the red "Book 15 minutes" tile: its text was --ink, which went dark with the light plate */
.plate-light .book-btns .btn-primary .phone{color:#fff}
.plate-light .book-btns .btn-primary .label{color:rgba(255,255,255,.78)}

/* ---------------------------------------------------------------- one plan (20 Oct 2026)
   The three-card strip became one card and the billing rules beside it. Unequal columns on
   purpose: the card is the thing, the rules are the reason it is safe to say yes. .pcard-one,
   not .feat: .feat carries the old raised-card offset, which only made sense next to two others. */
.plan-grid{display:grid;gap:clamp(22px,3vw,48px);align-items:start}
@media (min-width:821px){.plan-grid{grid-template-columns:5fr 6fr}}
.pcard.pcard-one{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent) inset}
.plan-grid .pcard{height:auto}
.plan-pay .lbl-mk{margin-bottom:.5rem}
.plan-pay .steps li{padding:12px 0}
.plan-pay .price-note{margin-top:1.4rem}
#packages .ladder-row{margin-top:clamp(22px,3vw,36px)}

/* footer: the NAP and the Kansas City area links (20 Oct 2026) */
@media (min-width:760px){.foot-grid{grid-template-columns:5fr 3fr 3fr 3fr}}
.foot-addr{font-style:normal;font-size:.86rem;line-height:1.5;color:var(--ink-2);margin-top:4px}

/* Work, five sites (20 Oct 2026): one row of five on a wide screen instead of three over two,
   with every other card dropped a step so it reads as a set of phones, not a table row. */
@media (min-width:1100px){
  .work-strip{grid-template-columns:repeat(5,1fr);gap:clamp(14px,1.6vw,24px)}
  .work-card:nth-child(3){margin-top:0}
  .work-card:nth-child(even){margin-top:clamp(18px,3vw,44px)}
}

/* generated from data/site.py TRADES -- do not hand-edit */
form:has(#tr-bath:checked) .pn-bath{display:block}
form:has(#tr-kitchen:checked) .pn-kitchen{display:block}
form:has(#tr-gc:checked) .pn-gc{display:block}
form:has(#tr-flooring:checked) .pn-flooring{display:block}
form:has(#tr-handyman:checked) .pn-handyman{display:block}
form:has(#tr-roofing:checked) .pn-roofing{display:block}
form:has(#tr-siding:checked) .pn-siding{display:block}
form:has(#tr-gutters:checked) .pn-gutters{display:block}
form:has(#tr-masonry:checked) .pn-masonry{display:block}
form:has(#tr-concrete:checked) .pn-concrete{display:block}
form:has(#tr-excavation:checked) .pn-excavation{display:block}
form:has(#tr-fence:checked) .pn-fence{display:block}
form:has(#tr-garage:checked) .pn-garage{display:block}
form:has(#tr-plumbing:checked) .pn-plumbing{display:block}
form:has(#tr-hvac:checked) .pn-hvac{display:block}
form:has(#tr-electrical:checked) .pn-electrical{display:block}
form:has(#tr-lawn:checked) .pn-lawn{display:block}
form:has(#tr-tree:checked) .pn-tree{display:block}
form:has(#tr-wash:checked) .pn-wash{display:block}
form:has(#tr-lights:checked) .pn-lights{display:block}
form:has(#tr-pool:checked) .pn-pool{display:block}
form:has(#tr-clean:checked) .pn-clean{display:block}
form:has(#tr-junk:checked) .pn-junk{display:block}
form:has(#tr-moving:checked) .pn-moving{display:block}
form:has(#tr-pest:checked) .pn-pest{display:block}
form:has(#tr-restore:checked) .pn-restore{display:block}
form:has(#tr-autodetail:checked) .pn-autodetail{display:block}
form:has(#tr-autorepair:checked) .pn-autorepair{display:block}
form:has(#tr-pro:checked) .pn-pro{display:block}
form:has(#tr-other:checked) .pn-other{display:block}
form:has(.tradio:checked) .pn-default{display:none}
