@charset "UTF-8";
/* ==========================================================================
   KEYSTONE SITES — v4
   A light, warm, lead-generation studio site. Built MOBILE FIRST: every base
   rule is the phone rule, and larger viewports add to it. v3 did the opposite
   and the phone experience suffered for it.

   WHY v4 REPLACED v3 ("Quarry"):
   v3 was near-black with a brass accent and a scroll-assembled stone arch. It
   was distinctive and it scored 100s, but the owner did not like looking at it,
   which is the only test that matters for a site he has to sell with. Checked
   against four real GTA competitors (Elevate, Umber, Dev Websites, WebLaunch):
     · not one of them is dark — v3 was the outlier, and that is most of why it
       felt heavy on a phone
     · none uses an abstract brand metaphor; all put the customer's problem,
       the city and the price in the headline
     · page length was never the issue — they run 17–27 phone screens, v3 ran 12

   WHAT SURVIVED FROM v3, deliberately:
     · the serif-italic accent word inside a grotesque headline (Dev Websites
       uses the same device, so the instinct was current)
     · published pricing, and only numbers we can actually verify
     · the keystone wedge, demoted to a logo glyph — no arch anywhere
     · the ownership positioning: the site is 100% the client's from day one

   TWO RULES:
     1. ONE accent colour, and it has two values that are NOT interchangeable —
        see §02. Getting this wrong on a single `a` selector broke AA across a
        whole band in v3.
     2. EXACTLY ONE dark section, and it is the closing CTA. This was "no dark
        sections, not the footer either" until v4.1, because v3 was dark
        throughout and read as heavy on a phone. The amendment is deliberate and
        narrow: six consecutive #FFF/#FAF7F5 bands are 1.03:1 apart, so the page
        had no ending. One inverted band at the close is rhythm; a second one
        starts rebuilding v3. Both new grounds are contrast-measured in §06.

   Reading order:
     01 Fonts        06 Layout/buttons  11 Tables
     02 Tokens       07 Reveals         12 FAQ / disclosure
     03 Reset        08 Hero            13 Forms
     04 Type         09 Cards / plans   14 Footer
     05 Header/nav   10 Facts / rows    15 Wider screens
                                        16 Reduced motion
   ========================================================================== */

/* 01 FONTS ================================================================= */
/* The display face is Archivo at wdth 100 (NORMAL width), wght 700–800. v3 used
   wdth 125 (expanded), which is what made its headlines read as unusual — every
   reference site uses a normal-width bold grotesque. Same file count, same
   budget; the expanded cut is parked in _legacy-img/v3-fonts/. */
@font-face {
  font-family: 'Display';
  src: url('/assets/fonts/archivo-display.woff2') format('woff2');
  font-weight: 700 800; font-style: normal; font-display: optional;
}
@font-face {
  font-family: 'Body';
  src: url('/assets/fonts/archivo-body.woff2') format('woff2');
  font-weight: 400 600; font-style: normal; font-display: optional;
}
@font-face {
  font-family: 'Accent';
  src: url('/assets/fonts/instrument-serif-italic.woff2') format('woff2');
  font-weight: 400; font-style: italic; font-display: optional;
}
@font-face {
  font-family: 'Accent';
  src: url('/assets/fonts/instrument-serif.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: optional;
}
/* Metric-matched fallback so the swap never reflows the page (CLS stays 0). */
@font-face {
  font-family: 'Fallback';
  src: local('Arial'), local('Helvetica Neue'), local('Liberation Sans'), local('Segoe UI'), local('Roboto');
  size-adjust: 101.43%; ascent-override: 87.80%; descent-override: 21.00%; line-gap-override: 0%;
}

/* 02 TOKENS ================================================================ */
:root {
  /* --- grounds. White, with a warm tint for the hero and alternating bands. */
  --bg:        #FFFFFF;
  --bg-2:      #FAF7F5;   /* alternating sections, footer */
  --bg-warm:   #FFF4EF;   /* the hero wash */
  --line:      #EDE6E1;
  --line-2:    #DCD2CB;

  /* --- ink. Every ratio below is MEASURED, not estimated. --- */
  --ink:       #14110F;   /* 18.80:1 on white */
  --body:      #4A453F;   /*  9.48:1 on white */
  --muted:     #6E6659;   /*  5.66:1 on white */

  /* --- THE ACCENT. Two values, NOT interchangeable:
           --red     3.77:1 on white → LARGE display text and decoration ONLY
           --red-ink 5.58:1 on white → body text, links, small labels
         v3 shipped one brass value used for both and failed AA on every dark
         section containing a link. Do not repeat that: if you colour text with
         --red it must be 24px+, or bold 18.66px+. --- */
  --red:       #F0451B;
  --red-ink:   #C42F0E;
  --red-wash:  #FFEDE7;

  /* Body text on the dark band. MEASURED 11.61:1 on --ink. White is 18.80:1 and
     reserved for headings, so the two read as a hierarchy rather than one flat
     slab of white. */
  --on-ink:    #D6C9BF;

  /* --- feedback --- */
  --danger:    #C42F0E;   /* the same red; this site has exactly one */
  --success:   #0E6B4A;   /*  5.94:1 on white */

  /* --- elevation: barely there. Light sites use hairlines, not shadows. --- */
  --sh-1: 0 1px 2px rgba(20,17,15,.04);
  --sh-2: 0 2px 4px rgba(20,17,15,.04), 0 10px 24px -12px rgba(20,17,15,.10);

  --r-1: 6px;
  --r-2: 10px;
  --r-3: 16px;
  --r-pill: 999px;

  --s0:.25rem; --s1:.5rem; --s2:.75rem; --s3:1rem;
  --s4:1.5rem; --s5:2rem;  --s6:3rem;   --s7:4rem;  --s8:5.5rem;

  /* --- type. These are the MOBILE values; §15 scales them up. --- */
  --t-h1:    clamp(2.35rem, 10.5vw, 3.1rem);
  --t-h2:    clamp(1.85rem, 8vw, 2.4rem);
  --t-h3:    1.1875rem;
  --t-lede:  1.0625rem;
  --t-body:  1.0625rem;
  --t-small: .9375rem;
  --t-micro: .75rem;

  --container: 1180px;
  --gutter: 1.25rem;
  --measure: 34rem;
  --section-y: 3.25rem;
  --header-h: 64px;

  --ease: cubic-bezier(.22,.61,.24,1);
  --ease-out: cubic-bezier(.16,1,.3,1);

  /* Drawn marks for glyphs the woff2 subsets do not carry. Anything typed as a
     character here would silently fall back to a system face and break the
     one-typeface rule — see §11. */
  --tick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M2.6 8.6l3.4 3.4 7.4-7.9' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* 03 RESET ================================================================= */
*,*::before,*::after { box-sizing:border-box; }
* { margin:0; }
html { -webkit-text-size-adjust:100%; scroll-padding-top:calc(var(--header-h) + 1rem); }
body {
  background:var(--bg); color:var(--body);
  font-family:'Body','Fallback',ui-sans-serif,system-ui,sans-serif;
  font-size:var(--t-body); line-height:1.6;
  -webkit-font-smoothing:antialiased; font-synthesis:none;
  overflow-x:hidden;
}
img,picture,svg,video { display:block; max-width:100%; }
img,video { height:auto; }
input,button,textarea,select { font:inherit; color:inherit; }
button { background:none; border:0; }
::selection { background:var(--red-wash); }

/* 04 TYPE ================================================================== */
h1,h2,h3 { color:var(--ink); font-weight:800; letter-spacing:-.03em; text-wrap:balance; }
h1 { font-family:'Display','Fallback',sans-serif; font-size:var(--t-h1); line-height:1.02; letter-spacing:-.038em; }
h2 { font-family:'Display','Fallback',sans-serif; font-size:var(--t-h2); line-height:1.06; letter-spacing:-.032em; }
h3 { font-size:var(--t-h3); font-weight:700; line-height:1.3; letter-spacing:-.018em; }
p { text-wrap:pretty; }
p,li { max-width:var(--measure); }
strong { font-weight:600; color:var(--ink); }

/* The one piece of v3 worth keeping: a serif italic phrase inside a grotesque
   headline. Uses --red, which is legal here because headline sizes clear AA's
   large-text bar of 3:1. */
.ac {
  font-family:'Accent',Georgia,serif; font-style:italic; font-weight:400;
  color:var(--red); letter-spacing:-.01em;
}

a { color:var(--red-ink); text-decoration:none; }
p a,.lede a {
  text-decoration:underline; text-decoration-thickness:1px;
  text-underline-offset:3px; text-decoration-color:var(--line-2);
}
p a:hover,.lede a:hover { text-decoration-color:currentColor; }

.lede { font-size:var(--t-lede); line-height:1.6; color:var(--muted); max-width:34rem; }
.small { font-size:var(--t-small); }
.muted { color:var(--muted); }

/* The pill eyebrow badge every reference site uses, with a live red dot. */
.badge {
  display:inline-flex; align-items:center; gap:.55rem;
  padding:.4rem .85rem .4rem .7rem; max-width:none;
  font-size:var(--t-micro); font-weight:600; letter-spacing:.04em;
  color:var(--ink); background:var(--bg);
  border:1px solid var(--line-2); border-radius:var(--r-pill);
}
.badge::before {
  content:''; width:7px; height:7px; border-radius:50%;
  background:var(--red); flex:none;
}

/* Section eyebrow — spaced caps. The pill is reserved for the hero. */
.eyebrow {
  display:block; margin-bottom:var(--s3);
  font-size:var(--t-micro); font-weight:600;
  letter-spacing:.14em; text-transform:uppercase; color:var(--muted);
}
/* The leading rule. Every reference site marks its eyebrows this way and it is
   the cheapest possible signal that a new section has started — it gives the
   label a left edge to hang off instead of floating above the heading.
   Decorative only, so the accent needs no contrast ratio here. */
.eyebrow::before {
  content:""; display:inline-block; vertical-align:middle;
  width:1.75rem; height:2px; margin-right:.6rem;
  background:var(--red); transform:translateY(-1px);
}
.head--center .eyebrow::before { display:none; }

.visually-hidden {
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0;
}
.skip-link {
  position:absolute; left:var(--s3); top:var(--s3); z-index:100;
  padding:var(--s2) var(--s3); background:var(--ink); color:#fff;
  border-radius:var(--r-1); transform:translateY(-180%);
}
.skip-link:focus { transform:translateY(0); }
:focus-visible { outline:2px solid var(--red-ink); outline-offset:3px; border-radius:3px; }

/* 05 HEADER + NAV ========================================================== */
.header {
  position:fixed; inset:0 0 auto; z-index:50; height:var(--header-h);
  display:flex; align-items:center;
  background:var(--bg);
  border-bottom:1px solid transparent;
  transition:border-color .3s var(--ease), box-shadow .3s var(--ease);
}
/* Opaque from the first frame. A translucent bar with no backdrop-filter (still
   banned — real compositing cost on mobile) lets copy scroll straight through
   it, which is exactly the bug v3 shipped for a while. */
.header.is-stuck { border-bottom-color:var(--line); box-shadow:var(--sh-1); }
.header__inner {
  width:100%; max-width:var(--container); margin-inline:auto;
  padding-inline:var(--gutter);
  display:flex; align-items:center; justify-content:space-between; gap:var(--s3);
}
.wordmark {
  display:inline-flex; align-items:center; gap:.5rem;
  font-family:'Display','Fallback',sans-serif;
  font-size:1.0625rem; font-weight:800; letter-spacing:-.03em;
  color:var(--ink); white-space:nowrap;
  /* The text is only ~27px tall; the block padding takes the home link to a
     full-height target without moving the wordmark optically. */
  padding-block:.55rem; margin-block:-.55rem;
}
/* All that remains of the arch: one wedge, wider at the top, as a logo glyph. */
.wordmark__key {
  width:14px; height:13px; background:var(--red); flex:none;
  clip-path:polygon(0 0,100% 0,82% 100%,18% 100%);
}

.nav__toggle {
  display:inline-flex; align-items:center; gap:.5rem;
  font-size:var(--t-small); font-weight:600; color:var(--ink);
  /* 44px minimum — this is the single most-used control on a phone and it was
     shipping at 40. The horizontal padding is deliberately wider than it looks
     it needs to be; the visible bars are only 18px across. */
  padding:.625rem .5rem; margin-inline-end:-.5rem; cursor:pointer;
}
.nav__bars,.nav__bars::before,.nav__bars::after {
  display:block; width:18px; height:2px; background:currentColor;
  transition:transform .3s var(--ease), opacity .2s var(--ease);
}
.nav__bars { position:relative; }
.nav__bars::before,.nav__bars::after { content:''; position:absolute; left:0; }
.nav__bars::before { top:-5px; }
.nav__bars::after { top:5px; }
.nav__toggle[aria-expanded="true"] .nav__bars { transform:rotate(45deg); }
.nav__toggle[aria-expanded="true"] .nav__bars::before { transform:translateY(5px); opacity:0; }
.nav__toggle[aria-expanded="true"] .nav__bars::after { transform:translateY(-5px) rotate(-90deg); }

.nav {
  position:fixed; inset:var(--header-h) 0 auto;
  display:flex; flex-direction:column; align-items:stretch;
  padding:var(--s2) var(--gutter) var(--s5);
  background:var(--bg); border-bottom:1px solid var(--line);
  box-shadow:var(--sh-2);
  max-height:calc(100dvh - var(--header-h)); overflow-y:auto;
}
/* Authored OPEN; main.js closes it on small screens. A script failure leaves
   the menu usable rather than sealed behind a dead button. */
.nav.nav--closed { display:none; }
.nav__list { display:flex; flex-direction:column; list-style:none; padding:0; }
.nav__list li { max-width:none; }
.nav__link {
  display:block; padding-block:var(--s3); font-size:1.0625rem; font-weight:500;
  color:var(--ink); border-bottom:1px solid var(--line);
}
.nav__link[aria-current="page"] { color:var(--red-ink); }
.nav .btn { margin-top:var(--s4); justify-content:center; }

/* 06 LAYOUT + BUTTONS ====================================================== */
.container { width:100%; max-width:var(--container); margin-inline:auto; padding-inline:var(--gutter); }
.section { padding-block:var(--section-y); }
.band-2 { background:var(--bg-2); }

/* --- THE TWO BREAK GROUNDS ------------------------------------------------
   v4 alternated #FFF and #FAF7F5 the whole way down. Those are 1.03:1 apart,
   which is why the page reads flat next to sites that drop into dark. These
   are the only two grounds allowed to break that rhythm, and BOTH are
   contrast-measured — do not eyeball a replacement:

     on --ink #14110F   white 18.80:1 · --on-ink 11.61:1 · --red 4.99:1
     on --red #F0451B   --ink 4.99:1  · white ONLY 3.77:1 → LARGE TEXT ONLY

   That second line is the trap, and it is why the accent band below is INK on
   orange rather than white on orange. Maple-Crete puts white labels on its red
   band and gets away with it because its red is #CE1F2D — materially darker
   than ours. Copying that pattern here fails AA on every label. */
.band-ink { background:var(--ink); color:var(--on-ink); }
.band-ink h1, .band-ink h2, .band-ink h3 { color:#fff; }
.band-ink p, .band-ink .lede { color:var(--on-ink); }
.band-ink .eyebrow { color:var(--on-ink); }
.band-ink .ac { color:var(--red); }           /* 4.99:1 — AA at body size too */
.band-ink .divider { background:rgba(255,255,255,.14); }
/* The solid button inverts: --ink on --ink is invisible. */
.band-ink .btn--solid { background:#fff; color:var(--ink); }
.band-ink .btn--solid:hover { background:var(--red); color:var(--ink); }
.band-ink .btn--line { background:transparent; color:#fff; border-color:rgba(255,255,255,.38); }
.band-ink .btn--line:hover { border-color:#fff; background:rgba(255,255,255,.08); }

/* --- the accent stat band ------------------------------------------------
   Full-bleed orange carrying the three measured proof numbers, sitting
   directly under the hero the way Maple-Crete's red band does. Ink type, per
   the contrast note above. */
.statband { background:var(--red); }
.statband .container { display:grid; grid-template-columns:1fr; gap:0; }
.statband .stat2 {
  position:relative; padding:var(--s4) var(--s3); text-align:center;
  color:var(--ink); max-width:none;
}
.statband b {
  display:block;
  font-family:'Display','Fallback',sans-serif; font-weight:800;
  font-size:clamp(2.1rem,7vw,3rem); line-height:1; letter-spacing:-.045em;
  font-variant-numeric:tabular-nums lining-nums;
}
/* MUST stay a child combinator. The counter markup is <b><span class="tick">…,
   so a bare `.statband span` also grabs the number itself and renders it at
   12px uppercase — leaving the unit behind at 3rem. Same trap the .stat rule
   documents at §08. */
.statband .stat2 > span {
  display:block; margin-top:.45rem;
  font-size:var(--t-micro); font-weight:600; letter-spacing:.14em;
  text-transform:uppercase;
}
.head { margin-bottom:var(--s5); }
.head .lede { margin-top:var(--s3); }
.head--center { text-align:center; }
.head--center .lede { margin-inline:auto; }
.grid { display:grid; gap:var(--s4); }
.divider { border:0; height:1px; background:var(--line); margin:0; }
.measure { max-width:var(--measure); }
.flow > * + * { margin-top:var(--s4); }

.btn {
  display:inline-flex; align-items:center; justify-content:center; gap:.55rem;
  padding:1rem 1.4rem; border-radius:var(--r-pill);
  font-size:1rem; font-weight:600; letter-spacing:-.01em;
  border:1px solid transparent; cursor:pointer; text-align:center;
  transition:background .25s var(--ease), color .25s var(--ease),
             border-color .25s var(--ease), transform .25s var(--ease);
}
/* Every arrow on the site is → (U+2192, present in the subset). The home hero
   used ↗ (U+2197), which is not — so the one button above the fold rendered its
   arrow in a system face while the other sixteen used Archivo. Horizontal
   travel on hover now matches the direction the glyph points. */
.btn__arrow { transition:transform .3s var(--ease-out); }
.btn:hover .btn__arrow { transform:translateX(4px); }
.btn--solid { background:var(--ink); color:#fff; }
.btn--solid:hover { background:var(--red-ink); transform:translateY(-1px); }
.btn--line { border-color:var(--line-2); color:var(--ink); background:var(--bg); }
.btn--line:hover { border-color:var(--ink); transform:translateY(-1px); }
/* A plain text action — what Dev Websites puts under its primary button. */
.btn--text { padding:.75rem 0; color:var(--ink); font-weight:600; }
.btn--text:hover { color:var(--red-ink); }

.actions { display:flex; flex-direction:column; gap:var(--s2); margin-top:var(--s5); }
/* justify-content is the one that matters once .actions flips to row at 600px.
   Without it the buttons sat hard left under a centred heading on every
   head--center block — invisible on white, obvious on the dark band. */
.actions--center { align-items:stretch; justify-content:center; }

/* 07 REVEALS =============================================================== */
/* Authored VISIBLE. `.js` arms them, so a no-JS document is never stuck hidden. */
.rise { opacity:1; transform:none; }
/* 14px was too short to register as motion — it read as a fade. The references
   travel 34px; 24px keeps the movement legible without the content visibly
   sliding into place on a fast scroll. */
.js .rise {
  opacity:0; transform:translateY(24px);
  transition:opacity .7s var(--ease-out), transform .7s var(--ease-out);
  transition-delay:calc(var(--i,0) * 80ms);
}
.js .rise.is-in { opacity:1; transform:none; }

/* 08 HERO ================================================================== */
.hero {
  position:relative; overflow:hidden;
  padding-top:calc(var(--header-h) + var(--s5));
  padding-bottom:var(--s6);
}
/* A warm wash plus a faint grid — what the reference sites use in place of a
   photograph, and what lets this hero work with no client imagery at all. */
.hero::before {
  content:''; position:absolute; inset:0; z-index:-2;
  background:
    radial-gradient(120% 80% at 12% 0%, var(--bg-warm) 0%, rgba(255,244,239,0) 62%),
    linear-gradient(180deg, var(--bg-warm) 0%, var(--bg) 64%);
}
.hero::after {
  content:''; position:absolute; inset:0; z-index:-1; pointer-events:none;
  background-image:
    linear-gradient(to right, rgba(20,17,15,.05) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(20,17,15,.05) 1px, transparent 1px);
  background-size:76px 76px;
  -webkit-mask-image:linear-gradient(180deg,#000 0%,transparent 76%);
          mask-image:linear-gradient(180deg,#000 0%,transparent 76%);
}
.hero h1 { margin-top:var(--s4); }
.hero .lede { margin-top:var(--s4); }

/* The honest stat row under the CTAs. */
.stats {
  margin-top:var(--s6); padding-top:var(--s4);
  border-top:1px solid var(--line);
  display:grid; gap:var(--s3);
}
.stat { display:flex; align-items:baseline; gap:.6rem; max-width:none; }
.stat b {
  font-family:'Display','Fallback',sans-serif; font-weight:800;
  font-size:1.5rem; letter-spacing:-.04em; color:var(--ink);
  font-variant-numeric:tabular-nums lining-nums;
  white-space:nowrap;
}
/* MUST stay a child combinator. The counter markup is <b><span data-count>…
   so a plain `.stat span` also matches the span INSIDE the <b> — same (0,1,1)
   specificity as `.stat b`, declared later, so it won. The figure rendered
   15px muted while its unit rendered 24px ink: "0.4s" came out as small grey
   "0.4" next to a big black "s". */
.stat > span { font-size:var(--t-small); color:var(--muted); }
.stat b span { font:inherit; color:inherit; letter-spacing:inherit; }

/* 09 CARDS + PLANS ========================================================= */
.card {
  display:block; padding:var(--s4);
  background:var(--bg); border:1px solid var(--line); border-radius:var(--r-3);
  transition:border-color .25s var(--ease), transform .25s var(--ease), box-shadow .25s var(--ease);
}
/* The lift is on .card, not a.card. Plan cards are divs that contain their own
   CTA link, so restricting this to anchors left the pricing grid completely
   inert on hover — the one grid where people are deciding something. */
.card:hover { border-color:var(--line-2); transform:translateY(-3px); box-shadow:var(--sh-2); }
.card > * + * { margin-top:var(--s2); }
.card__n { font-family:'Accent',Georgia,serif; font-style:italic; font-size:1.25rem; color:var(--red-ink); }
.card__meta { font-size:var(--t-small); color:var(--muted); }
.tag {
  display:inline-block; padding:.15rem .5rem;
  font-size:var(--t-micro); font-weight:600; letter-spacing:.1em;
  text-transform:uppercase; color:var(--red-ink);
  background:var(--red-wash); border-radius:var(--r-1);
}

.plan { display:flex; flex-direction:column; }
.plan__flag {
  font-size:var(--t-micro); font-weight:600; letter-spacing:.14em;
  text-transform:uppercase; color:var(--muted);
}
.plan__price {
  font-family:'Display','Fallback',sans-serif; font-weight:800;
  font-size:2.4rem; line-height:1; letter-spacing:-.045em; color:var(--ink);
  font-variant-numeric:tabular-nums lining-nums; margin-top:var(--s1);
}
.plan__period {
  font-family:'Accent',Georgia,serif; font-style:italic; font-weight:400;
  font-size:.4em; letter-spacing:0; color:var(--muted);
}
.plan__list { list-style:none; padding:0; margin-top:var(--s3); }
.plan__list li {
  position:relative; padding-left:1.4rem; margin-top:.5rem;
  font-size:var(--t-small); max-width:none;
}
/* The wedge as a bullet — the logo mark doing quiet work. */
.plan__list li::before {
  content:''; position:absolute; left:0; top:.5em;
  width:8px; height:7px; background:var(--red);
  clip-path:polygon(0 0,100% 0,82% 100%,18% 100%);
}
.plan--featured { border-color:var(--red); border-width:2px; }
.plan > p:last-child { margin-top:auto; padding-top:var(--s3); }

/* 10 FACTS + ROWS + STEPS ================================================== */
.facts { display:grid; gap:var(--s3); }
.fact {
  padding:var(--s4);
  background:var(--bg); border:1px solid var(--line); border-radius:var(--r-3);
}
.fact__stat {
  font-family:'Display','Fallback',sans-serif; font-weight:800;
  font-size:2.1rem; line-height:1; letter-spacing:-.045em; color:var(--red);
  font-variant-numeric:tabular-nums lining-nums;
}
.fact__stat em {
  font-family:'Accent',Georgia,serif; font-style:italic;
  font-size:.45em; letter-spacing:0; color:var(--muted);
}
/* Counting numbers — used by .fact__stat AND the hero .stat row, so this stays
   an unscoped class. inline-block so the reserved min-width set by the generator
   actually applies: an inline span ignores width entirely, which would put the
   layout shift straight back. text-align:right keeps the digits pinned to the
   unit as they fill, so the gap closes rather than the unit jumping. */
.tick { display:inline-block; text-align:right; }
.fact h3 { margin-top:var(--s2); }
/* :not([class]) is load-bearing. `.fact p` scores (0,1,1) and silently
   outranks .fact__stat — which is a <p> — turning the red stat black. This is
   the same specificity trap v3 hit with `.pane p`; do not remove the guard. */
.fact p:not([class]) { margin-top:var(--s1); font-size:var(--t-small); color:var(--muted); }
/* The keystone fact — ownership. The only element on the site that gets the
   wash, because it is the one thing the whole positioning rests on. */
.fact--key { border-color:var(--red); border-width:2px; background:var(--red-wash); }
.fact--key p:not([class]) { color:var(--body); }

.rows { border-top:1px solid var(--line); }
.row {
  display:flex; align-items:baseline; justify-content:space-between; gap:var(--s3);
  padding-block:var(--s4); border-bottom:1px solid var(--line);
  color:var(--body);
}
.row span { max-width:none; }
.row:hover { color:var(--ink); }
.row__meta {
  flex:none; font-size:var(--t-micro); letter-spacing:.1em;
  text-transform:uppercase; color:var(--muted);
}

.steps { list-style:none; padding:0; counter-reset:s; display:grid; gap:var(--s4); }
.steps > li { max-width:none; counter-increment:s; padding-top:var(--s3); border-top:1px solid var(--line); }
.steps > li::before {
  content:counter(s,decimal-leading-zero); display:block; margin-bottom:var(--s1);
  font-family:'Accent',Georgia,serif; font-style:italic;
  font-size:1.35rem; color:var(--red-ink);
}
.steps h3 { margin-bottom:var(--s1); }
.steps p:not([class]) { color:var(--muted); font-size:var(--t-small); }

.chips { display:flex; flex-wrap:wrap; gap:.4rem; list-style:none; padding:0; }
.chips li {
  max-width:none; padding:.4rem .8rem; font-size:var(--t-small);
  background:var(--bg-2); border:1px solid var(--line); border-radius:var(--r-pill);
  color:var(--body);
}
.chips li.is-you { border-color:var(--red); color:var(--red-ink); font-weight:600; }

.sticky-col { align-self:start; }

/* 11 TABLES ================================================================ */
/* position:relative is LOad-bearing, not decoration. The tick cells carry a
   .visually-hidden span, which is position:absolute; without a positioned
   ancestor those spans resolved against the page instead of this box, so the
   ones in off-screen columns dragged documentElement.scrollWidth to 472px on a
   390px viewport and the whole pricing page scrolled sideways. */
.table-scroll { position:relative; overflow-x:auto; border:1px solid var(--line); border-radius:var(--r-3); }
.table { width:100%; border-collapse:collapse; font-size:var(--t-small); min-width:34rem; }
.table caption { padding:var(--s3); text-align:left; color:var(--muted); }
.table th,.table td { padding:var(--s2) var(--s3); text-align:left; border-top:1px solid var(--line); }
.table thead th {
  background:var(--bg-2); color:var(--ink);
  font-size:var(--t-micro); letter-spacing:.08em; text-transform:uppercase; white-space:nowrap;
}
.table tbody th { color:var(--ink); font-weight:600; }
/* Generated ticks still need a visually-hidden word — screen readers do not
   announce ::before content reliably.

   The tick is DRAWN, not typed. U+2713 is absent from all four woff2 subsets,
   so `content:'✓'` fell through to a system face and every row of this table
   mixed Archivo with whatever the OS supplied. An inline mask has no such
   dependency and inherits the accent colour. */
.table td[data-yes]::before {
  content:''; display:inline-block; width:.8em; height:.8em;
  vertical-align:-.02em; background:var(--red-ink);
  -webkit-mask:var(--tick) center / contain no-repeat;
          mask:var(--tick) center / contain no-repeat;
}
.table td[data-no]::before { content:'—'; color:var(--line-2); }

/* 12 FAQ + DISCLOSURE ====================================================== */
.faq { border-top:1px solid var(--line); }
.faq details { border-bottom:1px solid var(--line); }
.faq summary {
  display:flex; align-items:flex-start; gap:var(--s3);
  padding-block:var(--s4); cursor:pointer; list-style:none;
  font-size:1.0625rem; font-weight:600; color:var(--ink);
}
.faq summary::-webkit-details-marker { display:none; }
.faq summary::after {
  content:''; flex:none; margin-left:auto; margin-top:.4em;
  width:9px; height:9px;
  border-right:2px solid var(--red-ink); border-bottom:2px solid var(--red-ink);
  transform:rotate(45deg); transition:transform .3s var(--ease);
}
.faq details[open] summary::after { transform:rotate(-135deg); }
.faq summary:hover { color:var(--red-ink); }
.faq details > div { padding-bottom:var(--s4); }
.faq details > div p:not([class]) { color:var(--muted); max-width:44rem; }

/* A .card that is also a <details>. Ships `open`; main.js closes it on phones
   and re-forces open above the breakpoint, so a keyboard user cannot collapse a
   card that has no chevron. */
.disclose { padding:0; }
.disclose > summary {
  display:flex; align-items:center; gap:var(--s2);
  padding:var(--s4); cursor:pointer; list-style:none;
}
.disclose > summary::-webkit-details-marker { display:none; }
.disclose > summary h3 { margin:0; }
.disclose__n {
  flex:none; font-size:var(--t-micro); letter-spacing:.1em;
  text-transform:uppercase; color:var(--muted); white-space:nowrap;
}
.disclose > summary::after {
  content:''; flex:none; margin-left:auto;
  width:9px; height:9px;
  border-right:2px solid var(--red-ink); border-bottom:2px solid var(--red-ink);
  transform:rotate(45deg); transition:transform .3s var(--ease);
}
.disclose[open] > summary::after { transform:rotate(-135deg); }
.disclose__body { padding:0 var(--s4) var(--s4); }
.disclose__body > * + * { margin-top:var(--s2); }

/* 13 FORMS ================================================================= */
.field { margin-top:var(--s4); }
.field > label { display:block; font-size:var(--t-small); font-weight:600; color:var(--ink); margin-bottom:.35rem; }
.hint { font-size:var(--t-small); color:var(--muted); margin-bottom:.35rem; }
.input,.select,.textarea {
  width:100%; padding:.85rem 1rem;
  background:var(--bg); color:var(--ink);
  border:1px solid var(--line-2); border-radius:var(--r-2);
  transition:border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.input:focus,.select:focus,.textarea:focus {
  outline:none; border-color:var(--red-ink); box-shadow:0 0 0 3px var(--red-wash);
}
.textarea { min-height:9rem; }
.select { appearance:none; }
.error { font-size:var(--t-small); color:var(--danger); margin-top:.3rem; }
.field--error .input,.field--error .select,.field--error .textarea { border-color:var(--danger); }
.form-status { font-size:var(--t-small); }
.form-status[data-state="error"] {
  color:var(--danger); padding:var(--s3);
  border:1px solid var(--danger); border-radius:var(--r-2); background:var(--red-wash);
}
/* Card-footer calls to action — a paragraph whose whole content is one link.
   As bare inline anchors these were ~19px tall, and on /pricing/ they are the
   primary conversion action on every plan and care card. inline-block plus
   block padding takes the anchor itself to 44px.

   Block padding ONLY. An earlier attempt added inline padding and a negative
   start margin; the two long links on /services/ then wrapped to the full
   column width and overhung their card by the padding. The anchors are
   130–220px wide already, so width was never the part that needed help. */
.cta-link > a { display:inline-block; padding-block:.55rem; }

/* HONEYPOT — hidden from sight AND from assistive tech AND from the tab order,
   all three together. Not display:none: some bots skip those. */
.hp { position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; opacity:0; pointer-events:none; }
[aria-disabled="true"] { opacity:.6; cursor:progress; }

/* 14 FOOTER ================================================================ */
/* Light, like everything else. v3's dark footer was the last thing making the
   page feel heavy on a phone. */
.footer { background:var(--bg-2); border-top:1px solid var(--line); padding-block:var(--s6) var(--s4); }
.footer__grid { display:grid; gap:var(--s5); }
.footer__title {
  font-size:var(--t-micro); font-weight:600; letter-spacing:.14em;
  text-transform:uppercase; color:var(--ink); margin-bottom:var(--s2);
}
.footer__list { list-style:none; padding:0; }
.footer__list li { max-width:none; font-size:var(--t-small); }
/* Padding rather than margin: a bare footer link is a ~16px tap target, well
   under the 44px guidance, and these are the links people reach for one-handed
   at the bottom of a long page. .625rem of block padding on 24px of line box
   lands the anchor itself — the thing that is actually tappable — at 44px. The
   negative inline margin keeps the text optically flush with the heading. */
.footer__list a {
  color:var(--body); display:inline-block;
  padding:.625rem .4rem; margin-inline-start:-.4rem;
}
/* Service Area is statements, not links, so it gets no anchor padding and would
   otherwise sit visibly tighter than the two columns beside it. */
.footer__list li:not(:has(a)) { padding-block:.625rem; }
.footer__list a:hover { color:var(--red-ink); }
.footer__legal {
  display:flex; flex-wrap:wrap; gap:var(--s2) var(--s4); justify-content:space-between;
  margin-top:var(--s6); padding-top:var(--s4);
  border-top:1px solid var(--line); font-size:var(--t-small); color:var(--muted);
}
.footer__legal p { max-width:none; }
/* Same tap-target reasoning as .footer__list — these sit at the very bottom of
   the page, which is exactly where a thumb is least accurate. */
.footer__legal a {
  color:var(--muted); display:inline-block;
  padding:.5rem .35rem; margin-inline:-.35rem;
}
.footer__legal a:hover { color:var(--red-ink); }

/* 15 WIDER SCREENS ========================================================= */
@media (min-width:600px) {
  :root { --gutter:2rem; --section-y:4.5rem; }
  .actions { flex-direction:row; align-items:center; }
  .stats { grid-template-columns:repeat(3,auto); gap:var(--s5); }
  .facts { grid-template-columns:repeat(2,1fr); }
  /* Three across, with hairline rules between rather than gaps — the band has
     to read as one solid slab, not three cards. */
  .statband .container { grid-template-columns:repeat(3,1fr); }
  .statband .stat2 + .stat2::before {
    content:""; position:absolute; left:0; top:22%; bottom:22%;
    width:1px; background:rgba(20,17,15,.25);
  }
  .footer__grid { grid-template-columns:repeat(2,1fr); }
}

@media (min-width:900px) {
  :root {
    --gutter:2.5rem; --section-y:6rem; --header-h:72px;
    --t-h1:clamp(3.1rem,5.6vw,4.4rem);
    --t-h2:clamp(2.2rem,3.6vw,3rem);
    --t-h3:1.3125rem;
    --t-lede:1.1875rem;
  }
  .nav__toggle { display:none; }
  .nav {
    position:static; inset:auto; flex-direction:row; align-items:center;
    gap:var(--s5); padding:0; background:none; border:0; box-shadow:none;
    max-height:none; overflow:visible;
  }
  .nav.nav--closed { display:flex; }
  .nav__list { flex-direction:row; gap:var(--s5); }
  .nav__link {
    padding-block:.35rem; font-size:var(--t-small); border-bottom:0;
    position:relative; color:var(--body);
  }
  .nav__link::after {
    content:''; position:absolute; left:0; right:0; bottom:0; height:1px;
    background:currentColor; transform:scaleX(0); transform-origin:left;
    transition:transform .3s var(--ease);
  }
  .nav__link:hover,.nav__link[aria-current="page"] { color:var(--ink); }
  .nav__link:hover::after,.nav__link[aria-current="page"]::after { transform:scaleX(1); }
  .nav .btn { margin-top:0; }

  /* The build demo becomes a pinned stage ONLY here. Two columns, the frame
     sticky beside its captions. --header-h keeps it clear of the fixed header;
     the extra --s5 is breathing room, not a magic number.
     align-items:start is load-bearing — a stretched grid item cannot stick. */
  .build__grid { grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr); gap:var(--s7); align-items:start; }
  .build__stage { top:calc(var(--header-h) + var(--s4)); }
  .build__steps { gap:var(--s8); padding-block:var(--s6); }
  .bw__page { padding:var(--s4); gap:var(--s4); }
  .wf__media { height:120px; }
  .wf__cards i { height:38px; }
  .wf__h { height:16px; }

  .hero { padding-top:calc(var(--header-h) + var(--s7)); padding-bottom:var(--s7); }
  .hero h1 { max-width:20ch; }
  .hero .lede { max-width:38rem; }
  /* Stats span the full width beneath the actions rather than sitting in a
     second column — with no imagery to balance it, a two-column hero just left
     half the screen empty. */
  .stats { grid-template-columns:repeat(3,minmax(0,max-content)); gap:var(--s7); margin-top:var(--s7); }
  .facts { grid-template-columns:repeat(3,1fr); }
  .grid--2 { grid-template-columns:repeat(2,1fr); }
  .grid--3 { grid-template-columns:repeat(3,1fr); }
  .grid--aside { grid-template-columns:minmax(0,4fr) minmax(0,8fr); }
  .grid--form { grid-template-columns:minmax(0,7fr) minmax(0,5fr); }
  .steps--row { grid-template-columns:repeat(4,1fr); }
  .footer__grid { grid-template-columns:1.4fr 1fr 1fr 1fr; }
  .sticky-col { position:sticky; top:calc(var(--header-h) + var(--s4)); }
  .card,.fact { padding:var(--s5); }
  .fact__stat { font-size:2.6rem; }
  .plan__price { font-size:2.9rem; }
  /* Above the phone breakpoint the disclosure cards are simply open cards:
     no chevron, no count, and a default cursor so nothing invites a click. */
  .disclose > summary { padding:var(--s5); cursor:default; }
  .disclose__body { padding:0 var(--s5) var(--s5); }
  .disclose > summary::after,.disclose__n { display:none; }
}

@media (min-width:1200px) { :root { --t-h1:4.6rem; } }

/* 15b THE BUILD DEMO ======================================================= */
/* A website assembling itself as you scroll. Rationale and the three rules it
   inherits from v3's cut arch live in tools/generate/build-demo.mjs.

   THE PHONE IS THE DEFAULT HERE, as everywhere else in this file, and STICKY IS
   THE BASE BEHAVIOUR rather than a desktop add-on. The first pass built the
   phone layout as a static frame with the captions stacked under it, and it was
   wrong for a reason worth recording: the frame scrolls out of view by caption
   two, so every step after that animates off-screen. A phone user got a large
   half-empty box and no payoff — which is v3's mistake wearing new clothes.

   Pinned, the frame stays on screen for the whole section and actually fills in
   as you read. It costs ~40vh of viewport while that section is in play, and
   nothing before or after it. The section is still exactly as tall as its own
   copy: there is no spacer anywhere in here. */
.build__grid { display:grid; gap:var(--s4); }

.build__stage {
  align-self:start;
  position:sticky; top:var(--header-h); z-index:1;
  /* Opaque and flush to the header, or the caption scrolling underneath shows
     through the gap above the frame and around its rounded corners. This
     assumes .build is a plain white .section — if it ever takes .band-2 this
     background has to move with it. */
  background:var(--bg); padding-block:var(--s2);
}

/* --- the browser frame ------------------------------------------------- */
.bw {
  border:1px solid var(--line-2); border-radius:var(--r-3);
  background:var(--bg); box-shadow:var(--sh-2); overflow:hidden;
}
.bw__bar {
  display:flex; align-items:center; gap:.4rem;
  padding:.6rem .8rem; border-bottom:1px solid var(--line);
  background:var(--bg-2);
}
.bw__dot { width:9px; height:9px; border-radius:50%; background:var(--line-2); flex:none; }
.bw__url {
  margin-left:.5rem; padding:.18rem .6rem; border-radius:var(--r-pill);
  background:var(--bg); border:1px solid var(--line);
  font-size:.72rem; color:var(--muted); letter-spacing:.01em;
}
/* Compact on phones — the pinned frame has to leave room for the caption it is
   illustrating. §15 opens it back up once there are two columns to fill. */
.bw__page { padding:var(--s2); display:grid; gap:var(--s2); }

/* --- the layers ---------------------------------------------------------
   Authored VISIBLE. `.js` arms them, so a no-JS page shows a finished site
   rather than an empty browser chrome. Same invariant as .rise. */
.wf { opacity:1; transform:none; }
.js .wf {
  opacity:0; transform:translateY(8px);
  transition:opacity .55s var(--ease-out), transform .55s var(--ease-out);
}
.js .wf.is-on { opacity:1; transform:none; }

.wf--nav { display:flex; align-items:center; gap:.6rem; }
.wf__mark { width:26px; height:10px; border-radius:3px; background:var(--ink); flex:none; }
.wf__links { display:flex; gap:.5rem; margin-left:auto; }
.wf__links i { width:30px; height:7px; border-radius:3px; background:var(--line-2); }
.wf__pill { width:52px; height:18px; border-radius:var(--r-pill); background:var(--ink); flex:none; }

.wf--head { display:grid; gap:.5rem; }
.wf__h { height:13px; border-radius:4px; background:var(--ink); }
.wf__h--1 { width:82%; }
.wf__h--2 { width:56%; }
.wf__p { height:7px; border-radius:3px; background:var(--line-2); margin-top:.25rem; }
.wf__p--short { width:64%; }

.wf--proof { display:grid; gap:.5rem; }
.wf__media { height:52px; border-radius:var(--r-2); background:var(--red-wash); border:1px solid var(--line); }
.wf__cards { display:grid; grid-template-columns:repeat(3,1fr); gap:.5rem; }
.wf__cards i { height:24px; border-radius:var(--r-1); background:var(--bg-2); border:1px solid var(--line); }

.wf--cta { display:flex; gap:.5rem; }
.wf__btn { width:84px; height:22px; border-radius:var(--r-pill); background:var(--red); }
.wf__btn--ghost { width:64px; background:transparent; border:1px solid var(--line-2); }

.wf--live { display:flex; gap:.4rem; flex-wrap:wrap; }
.wf__chip {
  padding:.16rem .5rem; border-radius:var(--r-1);
  font-size:.66rem; font-weight:600; letter-spacing:.06em;
  color:var(--red-ink); background:var(--red-wash); border:1px solid var(--line);
}

/* --- the captions -------------------------------------------------------- */
.build__steps { list-style:none; padding:0; margin:0; display:grid; gap:var(--s7); }
.build__step { max-width:var(--measure); }
.build__n {
  font-family:'Accent',Georgia,serif; font-style:italic;
  font-size:1.1rem; color:var(--red-ink); margin-bottom:.15rem;
}
.build__step h3 { margin-bottom:var(--s1); }
.build__step p:not([class]) { color:var(--muted); font-size:var(--t-small); }
/* The active caption gets a left rule rather than a colour change — colour on
   body copy would need re-measuring against §02, a rule needs nothing. */
.build__step { border-left:2px solid transparent; padding-left:var(--s3); transition:border-color .4s var(--ease); }
.js .build__step.is-at { border-left-color:var(--red); }

/* 16 REDUCED MOTION ======================================================== */
@media (prefers-reduced-motion:reduce) {
  .js .rise { transition:none !important; opacity:1 !important; transform:none !important; }
  /* The build demo arrives fully assembled and stops pinning. Sticky is not
     motion in the vestibular sense, but a stage that holds still while the text
     beside it moves is exactly the effect this media query exists to spare. */
  .js .wf { transition:none !important; opacity:1 !important; transform:none !important; }
  .build__stage { position:static !important; }
  .build__step { transition:none !important; }
  .btn,.nav__link::after,.header,.card,.faq summary::after,.disclose > summary::after,
  .nav__bars,.nav__bars::before,.nav__bars::after { transition:none !important; }
  .btn:hover,a.card:hover { transform:none !important; }
  html { scroll-behavior:auto !important; }
}
@media (prefers-reduced-motion:no-preference) { html { scroll-behavior:smooth; } }
