/* ============================================================================
   BALLY DOES AI — PREMIUM SHARED DESIGN SYSTEM  ·  premium-teal.css
   ----------------------------------------------------------------------------
   The single reusable stylesheet for every premium page. Locked to the
   Warm-Dark Teal system (TEAL-PALETTE-LOCKED.md · Set A default) with the
   Editorial Cream + Teal system (Set B) available as a `.band--cream` context.

   USAGE (per page):
     <link rel="preconnect" href="https://fonts.googleapis.com">
     <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
     <link rel="stylesheet" href="/assets/premium/premium-teal.css">
     <script src="/assets/premium/premium-motion.js" defer></script>
   Then compose from the components below. Nav + footer markup lives in
   /assets/premium/_nav-footer.html (paste-in snippet).

   RULES (do not fight these):
   · Components read tokens only — never raw hex in page CSS.
   · Deep teal fills large surfaces; --brand for CTAs/icons/keylines;
     --glow ONLY for links, focus rings, glows, italic accents.
   · Amber --accent is a spice (eyebrows, one keyline, badges) — max 10%.
   · Never pure #000/#FFF anywhere.
   · Light sections: add class="band--cream" — every token remaps to Set B.
   ============================================================================ */

@import url("https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@300..700&family=JetBrains+Mono:wght@400;500&display=swap");

/* ----------------------------------------------------------------------------
   1 · TOKENS — Set A "Warm-Dark Teal" (LOCKED DEFAULT)
   --------------------------------------------------------------------------- */
:root {
  /* neutrals (60%) — LIGHT-LED ground · Navy & Burgundy (LOCKED 2026-07-20) */
  --paper:      #F6F6F2;                    /* soft near-white canvas (default ground). NEVER #FFF fills */
  --paper-2:    #FCFCFA;                    /* tinted light panel */
  --surface:    #FFFFFF;                    /* crisp card */
  --wash:       #FCFCFA;                    /* elevated light surface (meaning inverted) */
  --wash-deep:  #EAEEF1;                    /* cool light field: behind media, quiet washes (was deep teal) */
  --ink:        #141A24;                    /* navy-black text. NEVER pure #000 */
  --muted:      #5A6270;                    /* secondary copy, captions */
  --muted-2:    #8A93A0;                    /* faint mono metadata (numbers, coords) — 2.87:1 on --paper, DECORATIVE ONLY */
  --muted-aa:   #5F6879;                    /* the same quiet grey that actually clears AA on BOTH light grounds:
                                               5.18:1 on --paper, 4.83:1 on --glass over paper. Use this the
                                               moment --muted-2 carries READABLE text (labels, categories,
                                               breadcrumbs, meta). See sharedFileRequests. */
  --black:      #0E1116;                    /* deep neutral for rare max-contrast */
  --line:       rgba(20,26,36,0.12);        /* hairlines, dividers on light */
  --line-soft:  rgba(20,26,36,0.06);
  /* PRIMARY structural anchor — NAVY */
  --navy:       #16263F;                    /* dark sections, footer, hero guest, deep headings */
  --navy-2:     #1F3253;                    /* lifted navy panel */
  --ink-inv:    #F3F5F8;                    /* text on navy/dark */
  /* PRIMARY rich accent — BURGUNDY */
  --burgundy:   #7C2D48;                    /* emphasis, one band, premium accent, keylines */
  --burgundy-2: #98354F;                    /* hover / lifted burgundy */
  /* SUPPORTING — TEAL */
  --teal:       #1C8B7C;                    /* links, secondary accents, icons */
  --teal-deep:  #146E62;                    /* small-text-safe teal (AA on light) */
  --brand:      #1C8B7C;                    /* alias: components read --brand = THE teal */
  --glow:       #1C8B7C;                    /* teal accents: links, focus, italics (no neon on paper) */
  /* ATTENTION — GREEN (sparing: primary CTAs, live dots, demo HUD) */
  --green:      #5FD433;                    /* synergy green — where the eye MUST go */
  --green-deep: #46B21F;                    /* green hover / green text on light (AA) */
  /* accent RETIRED → aliased to burgundy so brass usages become burgundy, nothing breaks */
  --accent:     var(--burgundy);
  --accent-body:var(--burgundy);
  /* derived — keep derivations here, never in page CSS */
  --brand-glow:  color-mix(in oklab, var(--brand) 22%, transparent);
  --brand-raise: color-mix(in oklab, var(--brand) 90%, #ffffff 10%); /* btn top-light */
  --accent-dim:  color-mix(in oklab, var(--accent) 12%, transparent);
  /* HERO EYEBROW LEGIBILITY (added 2026-07-22 — foundation pass)
     Nine pages hard-coded the mono hero eyebrow to rgba(243,245,248,0.7x).
     The ALPHA is the real defect: every premium hero here is a diagonal scrim
     that runs dark→light, so a 0.74-alpha near-white label dissolves the moment
     the ground crosses ~rgb(110). Use SOLID tokens, never alpha, and pair each
     with its halo — the halo is invisible on a flat ground and only earns its
     keep over a gradient or photograph (Primland's atmospheric-depth logic at
     glyph scale). Both colours clear 4.5:1 on their canonical ground; the halo
     carries the worst-case gradient midpoint over the line too.
       on-dark  #EDF1F7 vs --navy  #16263F = 13.9:1
       on-light #414B5A vs --paper #F6F6F2 =  7.8:1
     ⚠ A page agent flipping a hero light↔dark MUST swap BOTH declarations. */
  --eyebrow-on-dark:    #EDF1F7;
  --eyebrow-halo-dark:  0 0 2px rgba(11,17,27,0.55), 0 1px 12px rgba(11,17,27,0.42);
  --eyebrow-on-light:   #414B5A;
  --eyebrow-halo-light: 0 0 2px rgba(246,246,242,0.62), 0 1px 12px rgba(246,246,242,0.5);

  --glass:       rgba(20,26,36,0.035);      /* subtle dark film on the light ground */
  --glass-line:  rgba(20,26,36,0.10);
  --scrim:       20,26,36;                  /* navy rgb for rgba() scrims / recessed islands */
  --navy-grad:   linear-gradient(135deg,#16263F,#28406A);

  /* fluid type scale (minor-third → major-second blend) */
  --step--1: clamp(0.83rem, 0.80rem + 0.16vw, 0.94rem);
  --step-0:  clamp(1.00rem, 0.95rem + 0.25vw, 1.13rem);
  --step-1:  clamp(1.20rem, 1.10rem + 0.50vw, 1.50rem);
  --step-2:  clamp(1.44rem, 1.28rem + 0.80vw, 2.00rem);
  --step-3:  clamp(1.73rem, 1.48rem + 1.25vw, 2.67rem);
  --step-4:  clamp(2.07rem, 1.70rem + 1.85vw, 3.55rem);
  --step-5:  clamp(2.49rem, 1.95rem + 2.70vw, 4.74rem);
  --step-6:  clamp(2.99rem, 2.20rem + 3.90vw, 6.31rem);
  --step-7:  clamp(3.58rem, 2.45rem + 5.65vw, 8.42rem);
  --step-hero: clamp(2.85rem, 1.15rem + 7.4vw, 8.6rem);

  --font-display: "Instrument Serif", Georgia, serif;
  --font-body: "Geist", system-ui, -apple-system, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, monospace;

  /* easing — never bare linear/ease-in-out on UI */
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out-quint: cubic-bezier(0.83, 0, 0.17, 1);
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
  --dur-fast: 0.4s;
  --dur-base: 0.7s;
  --dur-slow: 1.1s;

  /* layout rhythm */
  --shell: 1360px;
  --gutter: clamp(20px, 4vw, 52px);
  --section-y: clamp(6.5rem, 10vw, 11rem);
  --r-lg: 2rem;
  --r-md: 1.25rem;
}

/* ----------------------------------------------------------------------------
   2 · RESET / BASE
   --------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* NAV-SAFE SCROLLPORT (2026-08-19) — the nav is `position: fixed`, so it is not
   in flow and nothing knows it is there. Every anchor jump, every :target, and
   every element.scrollIntoView() therefore parks its destination UNDER the pill
   and beheads it — reproduced on /faq/, where opening an accordion row scrolls
   the question to y=0 and the pill covers the top half of "What is the monthly
   actually for?".
   scroll-padding-top on the scrollport is the single lever that fixes all three
   at once. The pill's own box is top clamp(14,2.2vh,24) + ~54px tall, so 96px
   at the small end and 116px at the large end clears it with ~18px of air.
   scroll-margin-top on the likely destinations is belt-and-braces for scrollers
   that move the document themselves (Lenis) and ignore the scrollport's padding
   — it costs nothing and has no visual effect when unused.
   ⚠ This is the ONE chosen fix for the nav/heading overlap. Do not also add a
   hide-on-scroll-down nav: two mechanisms fighting over the same 100px is how
   a pill ends up flickering on every wheel event. */
:root { --nav-safe: clamp(96px, 11vh, 116px); }
html { scroll-behavior: auto; overflow-x: clip; scroll-padding-top: var(--nav-safe); }
:where(h1, h2, h3, h4, summary, [id]) { scroll-margin-top: var(--nav-safe); }
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--step-0);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}

::selection { background: rgba(28,139,124,0.20); color: var(--ink); }
img, video, canvas { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
* { font-optical-sizing: auto; }
:focus-visible { outline: 2px solid var(--glow); outline-offset: 3px; border-radius: 4px; }

h1, h2, h3, .display { text-wrap: balance; }
p, li { text-wrap: pretty; }

.shell { max-width: var(--shell); margin-inline: auto; padding-inline: var(--gutter); }
.section { padding-block: var(--section-y); position: relative; }
/* Two adjacent .section elements each apply the FULL --section-y (up to 176px)
   on their own touching edge, so a plain section-to-section boundary stacks
   to ~350px of pure background with nothing in it - the exact "falls off
   after the hero" complaint, confirmed 2026-08-10 by screenshotting every
   page's mid/bottom half rather than trusting a DOM element-presence check
   (which had wrongly reported zero dead zones earlier). This is a single,
   sitewide root cause, not a per-page content problem: shrink the INCOMING
   section's top padding whenever it follows another .section directly, since
   the outgoing section's bottom padding already did the work of separating
   them. A section that follows something else (a full-bleed hero, a marquee)
   is untouched, so hero->first-section spacing is unaffected. */
.section + .section { padding-top: calc(var(--section-y) * 0.18); }
/* The rule above only shrank the INCOMING half; the OUTGOING section's own
   full bottom padding was still stacking underneath it and stayed the
   dominant part of the gap (confirmed by screenshot, not assumed). Shrink
   that side too, on any .section directly followed by another .section, so
   the two halves split one section-y's worth of breathing room between them
   instead of each contributing a full one. :has() has full modern-browser
   support (Chrome 105+/Safari 15.4+/Firefox 121+, all well past this B2B
   site's bar). A section followed by something that ISN'T .section (a
   full-bleed CTA band, a footer) keeps its full bottom padding on purpose. */
.section:has(+ .section) { padding-bottom: calc(var(--section-y) * 0.35); }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--ink); color: var(--paper); padding: 10px 18px; border-radius: 999px;
}
.skip:focus { left: 16px; top: 16px; }

/* ----------------------------------------------------------------------------
   3 · GRAIN — one fixed layer per page, paints once, kills teal banding
   --------------------------------------------------------------------------- */
body::after {
  content: "";
  position: fixed; inset: 0; z-index: 90;
  pointer-events: none;
  opacity: 0.03;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-repeat: repeat;
  mix-blend-mode: overlay;
}

/* ----------------------------------------------------------------------------
   4 · TYPOGRAPHY HELPERS
   --------------------------------------------------------------------------- */
.eyebrow {
  font-family: var(--font-mono); font-weight: 500;
  font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--muted);
  display: inline-flex; align-items: center; gap: 10px;
}
.eyebrow .tick { width: 20px; height: 1px; background: var(--accent); display: inline-block; } /* the amber spice */
/* PHONE TRACKING STEP (2026-08-19). 0.18em of tracking on an 11px uppercase
   mono label is a desktop luxury: it adds ~1.98px to every character, so a
   30-character eyebrow needs ~60px it does not have inside a 350px content
   column. Measured at 390px, /about/'s "Why Ontario · and why it doesn't
   matter" ran 365px wide and broke into a 2-line block; several others sat one
   character from doing the same. Tracking is the thing to give up, not size —
   dropping to 0.10em buys back ~90% of the natural width and holds the line,
   where a 11→10px step would have cost legibility on the smallest screen for
   less. The tick shortens with it so the whole lockup scales as one object. */
@media (max-width: 640px) {
  .eyebrow { letter-spacing: 0.10em; gap: 8px; }
  .eyebrow .tick { width: 14px; }
  .link-plain { letter-spacing: 0.09em; }
}
/* One more step for the ~40-character HERO eyebrows, which are sentences rather
   than labels ("Websites for local business · Ontario, Canada",
   "Automation · custom builds for small business", "Honest comparison · written
   by the operator"). At 390px those still broke to two lines on 0.10em; 10.5px
   at 0.085em brings them back to one. 10.5px uppercase mono is not a new low
   for this file — .footer__rule, .trustline__row and .stack-card__tier all sit
   there already, and these are labels, never body copy.
   Genuinely longer strings (/work/'s 47-character "Demonstration builds · the
   ladder, rung by rung") still wrap, and should: past ~45 characters the answer
   is a shorter eyebrow, not a smaller one. */
@media (max-width: 430px) {
  .eyebrow { font-size: 10.5px; letter-spacing: 0.085em; }
}
.h-display {
  font-family: var(--font-display); font-weight: 400;
  font-size: var(--step-6); line-height: 1.02; letter-spacing: -0.015em;
  color: var(--ink);
}
.h-display em { font-style: italic; color: var(--glow); letter-spacing: 0.005em; }
.lede { color: var(--muted); font-size: var(--step-1); line-height: 1.55; max-inline-size: 56ch; font-weight: 300; }
/* a NOTE is read, not decoration → --muted-aa (ground-aware), not --muted-2 */
.small-note { color: var(--muted-aa); font-size: var(--step--1); }
.link-plain {
  font-family: var(--font-mono); font-size: 11.5px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--muted); display: inline-flex; align-items: center; gap: 8px;
  transition: color var(--dur-fast) var(--ease-out-quart);
}
.link-plain:hover { color: var(--glow); }

/* ----------------------------------------------------------------------------
   5 · NAV — floating detached glass pill (markup in _nav-footer.html)
   --------------------------------------------------------------------------- */
.nav {
  position: fixed; top: clamp(14px, 2.2vh, 24px); left: 50%;
  transform: translateX(-50%);
  z-index: 100;
  display: flex; align-items: center; gap: clamp(14px, 2vw, 30px);
  padding: 9px 10px 9px 20px;
  border-radius: 999px;
  border: 1px solid transparent;
  transition: background var(--dur-fast) var(--ease-out-quart),
              border-color var(--dur-fast) var(--ease-out-quart),
              box-shadow var(--dur-fast) var(--ease-out-quart);
  white-space: nowrap;
}
/* Scrolled over the LIGHT page → light glass pill + dark ink text.
   EDGE CONTRAST (2026-08-19): cream-on-cream. The pill's ground is
   rgba(246,246,242,0.93) and the page's ground is #F6F6F2 — the same colour —
   so a 12%-alpha hairline was the ONLY thing separating a floating object from
   the page behind it, and on /book/, /faq/ and mid-scroll /products/marketing/
   the pill visually dissolved into the ground. Three cheap layers instead of
   one heavier border (a heavy border reads as a box, not glass):
     · a tighter, darker hairline (0.18 vs 0.12)
     · a 0.5px contact ring immediately outside it — the edge the eye finds
     · a shorter, deeper drop so the pill sits ABOVE the page rather than in it
   All three are box-shadow/border only: no layout, no repaint cost on scroll. */
.nav.is-scrolled {
  background: rgba(246, 246, 242, 0.93);        /* light paper glass — 0.93 not 0.82 so headings don't bleed through mid-scroll */
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  backdrop-filter: blur(18px) saturate(140%);
  border-color: rgba(20,26,36,0.18);
  box-shadow: 0 0 0 0.5px rgba(20,26,36,0.07),
              0 2px 6px -2px rgba(20,26,36,0.14),
              0 16px 40px -18px rgba(20,26,36,0.30);
}
.nav__brand { display: inline-flex; align-items: baseline; gap: 9px; }
.nav__brand .nav__seal { display: block; flex: none; align-self: center; border-radius: 50%; }
.nav__brand .wm-serif {
  font-family: var(--font-display); font-style: italic; font-weight: 400;
  font-size: 20px; line-height: 1; color: var(--ink); letter-spacing: 0.01em;
}
.nav__brand .wm-mono {
  font-family: var(--font-mono); font-weight: 500; font-size: 9px;
  letter-spacing: 0.24em; text-transform: uppercase; color: var(--muted);
}
/* Stretching the whole chain — the <nav> flex item, its <ul>, its <li>s — makes
   `top: 100%` on the products panel resolve to the pill's inner bottom edge, so
   the panel's offset is a fixed 19px (9px pad + 1px border + 9px air) instead of
   a number that silently drifts with the link line-height. Stretch alone would
   also top-align the link text, so every level re-centres with align-items. */
.nav > nav { align-self: stretch; display: flex; align-items: center; }
.nav__links {
  display: flex; align-items: center; align-self: stretch;
  gap: clamp(12px, 1.6vw, 24px); list-style: none;
}
.nav__links > li { display: flex; align-items: center; }
.nav__links a {
  font-size: 13.5px; font-weight: 400; color: var(--muted);
  transition: color var(--dur-fast) var(--ease-out-quart);
}
.nav__links a:hover,
.nav__links a[aria-current="page"] { color: var(--ink); }
.nav__tel {
  font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.04em;
  color: var(--ink); opacity: 0.85;
}
.nav__tel:hover { color: var(--teal-deep); }
.nav__cta {
  position: relative;
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 13.5px; font-weight: 500; color: var(--ink-inv);
  background: var(--navy);
  padding: 8px 8px 8px 16px; border-radius: 999px;
  transition: transform var(--dur-fast) var(--ease-out-quart);
}
/* Living keyline — one signature "high-tech" moment, deliberately reserved for
   the single most-repeated conversion element (the nav's Book-a-call pill,
   always on screen) rather than sprayed across every button. Draws ONLY in
   the padding ring via mask-composite exclude, so it never spills past the
   button's own box regardless of what clips the nav pill around it. */
@property --nav-cta-angle { syntax: '<angle>'; inherits: false; initial-value: 0deg; }
.nav__cta::before {
  content: ""; position: absolute; inset: 0; padding: 1.4px; border-radius: inherit;
  background: conic-gradient(from var(--nav-cta-angle, 0deg), #1C8B7C, #5FD433, #7C2D48, #1C8B7C);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .nav__cta::before { animation: bd-nav-cta-spin 7s linear infinite; }
}
@keyframes bd-nav-cta-spin { to { --nav-cta-angle: 360deg; } }
.nav__cta .ic {
  width: 22px; height: 22px; border-radius: 999px; background: rgba(243,245,248,0.14);
  display: grid; place-items: center;
  transition: transform var(--dur-fast) var(--ease-out-quart);
}

/* AT TOP, over the navy hero dark-guest → LIGHT nav text (scoped to header only,
   so the footer's re-used .nav__brand keeps dark ink). Flips back when scrolled. */
/* …and the pill needs a GROUND to say it on. Nine pages open on a photographic
   or diagonally-scrimmed hero whose top-right corner — exactly where the phone
   number and CTA sit — runs LIGHT. With a transparent pill the white phone
   number measured 2.84–3.44:1 there (/work/, /products/*), i.e. the primary
   conversion action for a trades buyer was unreadable in daylight at scroll 0.
   `brightness()` inside backdrop-filter makes the scrim adaptive: it barely
   touches an already-dark sky (home) but firmly darkens a light corner. Scoped
   away from .nav--light, which has its own paper-glass pill, and away from
   .is-scrolled. Blur is legal here — .nav is fixed (see §16 guardrails).
   The tint is NAVY, not black: a black lozenge over the home hero's teal dawn
   sky reads as a slab pasted on the photograph, while --navy sits inside the
   palette and disappears into it. */
.nav:not(.is-scrolled):not(.nav--light) {
  background: rgba(22,38,63,0.34);
  -webkit-backdrop-filter: blur(16px) saturate(125%) brightness(0.52);
          backdrop-filter: blur(16px) saturate(125%) brightness(0.52);
  border-color: rgba(243,245,248,0.10);
  box-shadow: 0 20px 50px -26px rgba(11,17,27,0.55);
}
.nav:not(.is-scrolled) .nav__brand .wm-serif { color: var(--ink-inv); }
.nav:not(.is-scrolled) .nav__brand .nav__mark { background-color: var(--ink-inv); }
/* SOLID, never alpha — same lesson as the hero eyebrow above: a 0.82-alpha
   near-white dissolves the moment the ground lifts. #CFD7E2 keeps "DOES AI" and
   the link row deliberately quieter than the serif wordmark without going faint
   (7.9:1 on the scrimmed pill). Written as a literal, not a color-mix(): a
   computed color-mix() resolves to oklab() in getComputedStyle, which the
   rendered-pixel contrast auditor's rgb() parser skips — that would hide these
   two rules from the audit rather than fix them. */
.nav:not(.is-scrolled) .nav__brand .wm-mono,
.nav:not(.is-scrolled) .nav__links a { color: #CFD7E2; }
.nav:not(.is-scrolled) .nav__links a:hover,
.nav:not(.is-scrolled) .nav__links a[aria-current="page"] { color: var(--ink-inv); }
.nav:not(.is-scrolled) .nav__tel { color: var(--ink-inv); }
.nav:not(.is-scrolled) .nav__cta { background: var(--ink-inv); color: var(--navy); }
.nav:not(.is-scrolled) .nav__cta .ic { background: rgba(20,26,36,0.12); }
.nav__cta:hover .ic { transform: translate(2px, -1px); }
.nav__cta:active { transform: scale(0.97); }

/* Light-hero pages (about, faq, faq/*, pricing, vs, promote, trades, work,
   resources, products/*) — pin the paper-glass look at scroll=0 so the wordmark,
   links, phone, and CTA remain readable over the --paper ground. Ordered AFTER
   .nav:not(.is-scrolled) so the color overrides win at equal specificity. */
.nav.nav--light {
  background: rgba(246, 246, 242, 0.93);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
          backdrop-filter: blur(18px) saturate(140%);
  /* same three-layer edge as .is-scrolled above — these two are the same object
     on the same cream ground and must never disagree about how it reads */
  border-color: rgba(20,26,36,0.18);
  box-shadow: 0 0 0 0.5px rgba(20,26,36,0.07),
              0 2px 6px -2px rgba(20,26,36,0.14),
              0 16px 40px -18px rgba(20,26,36,0.30);
}
.nav.nav--light .nav__brand .wm-serif { color: var(--ink); }
.nav.nav--light .nav__brand .wm-mono { color: var(--muted); }
.nav.nav--light .nav__links a { color: var(--muted); }
.nav.nav--light .nav__links a:hover,
.nav.nav--light .nav__links a[aria-current="page"] { color: var(--ink); }
.nav.nav--light .nav__tel { color: var(--ink); opacity: 0.85; }
.nav.nav--light .nav__cta { background: var(--navy); color: var(--ink-inv); }
.nav.nav--light .nav__cta .ic { background: rgba(243,245,248,0.14); }

/* ----------------------------------------------------------------------------
   5a · PRODUCTS DISCLOSURE — the second lozenge
   ---------------------------------------------------------------------------
   The funnel defect this fixes: /products/website|ai-chat|automation|marketing
   were reachable ONLY from the footer, so a buyer whose first question is "what
   exactly do I get" had no path to the four pages that answer it.

   Why a detached panel and not a mega-menu: the nav's whole idiom is a floating
   lozenge with nothing touching it. A dropdown welded to the pill's underside
   would read as a corporate nav bar bolted onto a boutique site. So the panel is
   a SECOND lozenge of the same family — same radius language, same glass, same
   ground-awareness — that arrives 10px below the first and is obviously the same
   object's second thought. Inside it is an editorial index (mono ordinal, name,
   one plain line), not an icon grid.

   Ground-awareness: the panel is paper by default (used by .is-scrolled and by
   .nav--light) and flips to navy glass under
   .nav:not(.is-scrolled):not(.nav--light) — the exact selector the pill itself
   uses, so panel and pill can never disagree about which ground they're on.
   Every colour is written on the INNER spans, never on the <a>, because
   `.nav:not(.is-scrolled) .nav__links a` (0,3,1) would otherwise beat any rule
   here and paint the panel's body text #CFD7E2 on paper.
   --------------------------------------------------------------------------- */
.nav__item--disc { position: relative; }
.nav__disc {
  display: inline-flex; align-items: center; gap: 6px;
  margin: 0; padding: 0; border: 0; background: none; cursor: pointer;
  font-family: var(--font-body); font-size: 13.5px; font-weight: 400;
  line-height: inherit; letter-spacing: inherit; color: var(--muted);
  transition: color var(--dur-fast) var(--ease-out-quart);
}
.nav__disc:hover,
.nav__disc[aria-expanded="true"],
.nav__disc[aria-current="true"] { color: var(--ink); }
.nav__disc-caret {
  display: grid; place-items: center; width: 9px; height: 6px; opacity: 0.6;
  transition: transform 0.3s var(--ease-out-quart), opacity var(--dur-fast) var(--ease-out-quart);
}
.nav__disc[aria-expanded="true"] .nav__disc-caret { transform: rotate(180deg); opacity: 1; }

/* `top: 100%` is exact because the <nav>/<ul>/<li> chain is stretched — 100%
   lands on the pill's content-box bottom, and +28px clears the pill's own
   padding and border to leave a measured 10px of air. left:-14px puts the
   panel's first column under the "P" of Products (its 15px padding cancels).
   transform/opacity only; nothing here triggers layout. */
.nav__panel {
  position: absolute; top: calc(100% + 28px); left: -14px;
  z-index: 2; width: 366px; padding: 15px 15px 9px;
  border-radius: 20px; border: 1px solid var(--line);
  background: rgba(246, 246, 242, 0.97);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
          backdrop-filter: blur(18px) saturate(140%);
  box-shadow: 0 26px 60px -24px rgba(20,26,36,0.32), 0 2px 6px -3px rgba(20,26,36,0.14);
  white-space: normal;
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(-8px) scale(0.985); transform-origin: 30px -10px;
  transition: opacity 0.28s var(--ease-out-quart),
              transform 0.28s var(--ease-out-quart),
              visibility 0s linear 0.28s;
}
/* visibility (not [hidden] / display) is what keeps the four links out of the
   tab order while closed AND keeps the fade — same trick as .nav-menu. */
.nav__panel.is-open {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: none; transition-delay: 0s, 0s, 0s;
}
/* bridges the 10px air gap so a mouse travelling from "Products" down to the
   first row never leaves the group. Only live while open (the panel is
   pointer-events:none otherwise), so it can never eat a click on the hero. */
.nav__panel::before {
  content: ""; position: absolute; left: 0; right: 0; top: -22px; height: 22px;
}
.nav__panel-label {
  font-family: var(--font-mono); font-size: 9.5px; font-weight: 500;
  letter-spacing: 0.24em; text-transform: uppercase; color: var(--muted-aa);
  padding: 0 8px 9px; margin-bottom: 5px;
  border-bottom: 1px solid var(--line-soft);
}
.nav__panel-list { list-style: none; display: block; }
.nav__panel-list li {
  opacity: 0; transform: translateY(6px);
  transition: opacity 0.3s var(--ease-out-quart), transform 0.3s var(--ease-out-quart);
}
.nav__panel.is-open .nav__panel-list li { opacity: 1; transform: none; }
.nav__panel.is-open .nav__panel-list li:nth-child(1) { transition-delay: 0.05s; }
.nav__panel.is-open .nav__panel-list li:nth-child(2) { transition-delay: 0.09s; }
.nav__panel.is-open .nav__panel-list li:nth-child(3) { transition-delay: 0.13s; }
.nav__panel.is-open .nav__panel-list li:nth-child(4) { transition-delay: 0.17s; }
.nav__panel-list a {
  display: grid; grid-template-columns: auto 1fr; gap: 12px; align-items: start;
  padding: 8px; border-radius: 12px;
  transition: background var(--dur-fast) var(--ease-out-quart);
}
/* "you are here" has to survive being the same colour family as hover — a 10px
   teal ordinal alone was too quiet to find on a page you are already reading. */
.nav__panel-list a:hover,
.nav__panel-list a[aria-current="page"] { background: rgba(20,26,36,0.05); }
.nav__panel-n {
  font-family: var(--font-mono); font-size: 10px; font-weight: 500;
  letter-spacing: 0.1em; color: var(--muted-aa); padding-top: 4px;
  transition: color var(--dur-fast) var(--ease-out-quart);
}
.nav__panel-list a:hover .nav__panel-n,
.nav__panel-list a[aria-current="page"] .nav__panel-n { color: var(--teal-deep); }
.nav__panel-b { display: block; }
.nav__panel-b b {
  display: block; font-family: var(--font-body); font-weight: 500;
  font-size: 13.5px; line-height: 1.25; letter-spacing: -0.004em; color: var(--ink);
}
.nav__panel-b > span {
  display: block; margin-top: 2px;
  font-size: 12px; line-height: 1.4; color: var(--muted-aa);
}

/* dark ground — the pill is navy glass over a hero, so the panel is too */
.nav:not(.is-scrolled):not(.nav--light) .nav__disc { color: #CFD7E2; }
.nav:not(.is-scrolled):not(.nav--light) .nav__disc:hover,
.nav:not(.is-scrolled):not(.nav--light) .nav__disc[aria-expanded="true"],
.nav:not(.is-scrolled):not(.nav--light) .nav__disc[aria-current="true"] { color: var(--ink-inv); }
.nav:not(.is-scrolled):not(.nav--light) .nav__panel {
  background: rgba(16, 26, 41, 0.90);
  -webkit-backdrop-filter: blur(20px) saturate(130%) brightness(0.62);
          backdrop-filter: blur(20px) saturate(130%) brightness(0.62);
  border-color: rgba(243,245,248,0.14);
  box-shadow: 0 28px 64px -26px rgba(11,17,27,0.74);
}
.nav:not(.is-scrolled):not(.nav--light) .nav__panel-label {
  color: rgba(243,245,248,0.66); border-bottom-color: rgba(243,245,248,0.12);
}
.nav:not(.is-scrolled):not(.nav--light) .nav__panel-n { color: rgba(243,245,248,0.58); }
.nav:not(.is-scrolled):not(.nav--light) .nav__panel-b b { color: var(--ink-inv); }
.nav:not(.is-scrolled):not(.nav--light) .nav__panel-b > span { color: rgba(243,245,248,0.76); }
.nav:not(.is-scrolled):not(.nav--light) .nav__panel-list a:hover,
.nav:not(.is-scrolled):not(.nav--light) .nav__panel-list a[aria-current="page"] { background: rgba(243,245,248,0.07); }
/* the on-dark teal, written as a LITERAL: this is
   color-mix(in oklab, var(--glow) 62%, white 38%) resolved by hand, because a
   computed color-mix() serialises to oklab() and the rendered-pixel auditor's
   rgb() parser skips it — that hides the rule from the audit instead of
   passing it. 8.4:1 on the overlay ground. */
.nav:not(.is-scrolled):not(.nav--light) .nav__panel-list a:hover .nav__panel-n,
.nav:not(.is-scrolled):not(.nav--light) .nav__panel-list a[aria-current="page"] .nav__panel-n { color: #7FB6AC; }
/* --glow at 3px offset is only ~2.4:1 on navy; lift the ring on the dark pill */
.nav:not(.is-scrolled):not(.nav--light) .nav__disc:focus-visible,
.nav:not(.is-scrolled):not(.nav--light) .nav__links a:focus-visible,
.nav:not(.is-scrolled):not(.nav--light) .nav__brand:focus-visible,
.nav:not(.is-scrolled):not(.nav--light) .nav__tel:focus-visible,
.nav:not(.is-scrolled):not(.nav--light) .nav__panel-list a:focus-visible { outline-color: #7FB6AC; }

@media (prefers-reduced-motion: reduce) {
  .nav__panel {
    transform: none;
    transition: opacity 0.001s linear, visibility 0s linear 0.001s;
  }
  .nav__panel.is-open { transform: none; }
  .nav__panel-list li { opacity: 1; transform: none; transition: none; }
  .nav__panel.is-open .nav__panel-list li { transition-delay: 0s; }
  .nav__disc-caret { transition: none; }
}

/* ----------------------------------------------------------------------------
   5b · MOBILE NAV — toggle + full-screen overlay  (markup injected by
        .tools/inject-shared.mjs; behaviour in premium-motion.js)
   ---------------------------------------------------------------------------
   Trades trust furniture (trades-premium.md §1): the PHONE NUMBER never leaves
   the bar. Under 940px the link list collapses into the overlay; the number
   stays as literal text because ~60% of home-service buyers call rather than
   click. Under 640px "Book a call" moves into the overlay, where it is the
   largest green control on screen — the number wins the last 50px of bar.

   COLLAPSE BREAKPOINT — 940px, raised from 860px on 2026-08-19.
   The pill is a FLOATING DETACHED lozenge; that reading depends on visible
   ground either side of it. Measured (home and /faq/, identical to the pixel):

       vw 1024 → pill 856px, 84px clear      vw  900 → pill 839px, 31px clear
       vw  990 → pill 852px, 69px clear      vw  880 → pill 836px, 22px clear
       vw  960 → pill 847px, 56px clear      vw  861 → pill 834px, 14px clear
       vw  940 → pill 845px, 48px clear

   Adding "Free site check" for /teardown/ pushed the pill ~26px wider, which
   ate the bottom of that range: from 861–900px it sat 14–31px off each edge and
   stopped reading as detached — it read as a full-width bar with rounded ends.
   The label is the hook and stays; the breakpoint moves instead. 940 is the
   width where clearance comes back to ~48px, i.e. the ~40–60px it had before.
   ⚠ premium-motion.js still listens on (min-width: 861px) / (max-width: 860px)
   in two places — both are housekeeping only (close the overlay / close the
   products panel when you cross the line), never gating. The 941px block below
   neutralises the one state that could strand: a scroll lock left on after a
   resize. Those two matchMedia values should follow to 941/940 next time that
   file is open. See the Global agent's report, 2026-08-19.
   --------------------------------------------------------------------------- */

/* toggle is desktop-hidden; the overlay never exists above 940 */
.nav__toggle { display: none; }

@media (max-width: 940px) {
  .nav {
    left: 16px; right: 16px; transform: none;
    justify-content: space-between;
    gap: 8px; padding: 7px 7px 7px 16px;
  }
  .nav__links { display: none; }

  /* phone stays as text — compact mono, 44px tall tap target */
  .nav__tel {
    display: inline-flex; align-items: center; min-height: 44px;
    padding: 0 12px; border-radius: 999px;
    font-size: 12px; letter-spacing: 0.01em; opacity: 1;
    margin-left: auto;
  }
  .nav__cta { min-height: 44px; padding: 8px 8px 8px 14px; font-size: 13px; }
  .nav__cta .ic { width: 20px; height: 20px; }

  .nav__toggle {
    display: grid; place-items: center;
    width: 44px; height: 44px; flex: 0 0 44px;
    border: 1px solid var(--line); border-radius: 999px;
    background: transparent; color: var(--ink); cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out-quart),
                border-color var(--dur-fast) var(--ease-out-quart);
  }
  .nav__toggle:hover { background: var(--glass); }
  .nav__toggle-box { display: block; position: relative; width: 18px; height: 12px; }
  .nav__toggle-box i {
    position: absolute; left: 0; width: 100%; height: 1.5px; border-radius: 2px;
    background: currentColor;
    transition: transform var(--dur-fast) var(--ease-out-quart),
                opacity 0.2s linear;
  }
  .nav__toggle-box i:first-child { top: 0; }
  .nav__toggle-box i:last-child  { bottom: 0; }
  .nav__toggle[aria-expanded="true"] .nav__toggle-box i:first-child { transform: translateY(5.25px) rotate(45deg); }
  .nav__toggle[aria-expanded="true"] .nav__toggle-box i:last-child  { transform: translateY(-5.25px) rotate(-45deg); }

  /* dark-guest nav (at top, over a navy hero) — invert the toggle chrome */
  .nav:not(.is-scrolled):not(.nav--light) .nav__toggle {
    border-color: rgba(243,245,248,0.28); color: var(--ink-inv);
  }
  .nav:not(.is-scrolled):not(.nav--light) .nav__toggle:hover { background: rgba(243,245,248,0.10); }
}

@media (max-width: 640px) {
  .nav { padding: 7px 7px 7px 14px; }
  .nav__cta { display: none; }          /* promoted into the overlay instead */
  .nav__tel { padding: 0 10px; font-size: 11.5px; }
}
@media (max-width: 360px) {
  .nav__brand .wm-mono { display: none; }
}

/* ---- the overlay itself (sibling of <header>, never a child — .nav's
        backdrop-filter would otherwise become its containing block) ---- */
.nav-menu {
  position: fixed; inset: 0; z-index: 99;
  display: flex; flex-direction: column;
  padding: calc(env(safe-area-inset-top, 0px) + 92px) var(--gutter)
           calc(env(safe-area-inset-bottom, 0px) + 24px);
  background: rgba(16, 26, 41, 0.90);
  -webkit-backdrop-filter: blur(22px) saturate(130%);
          backdrop-filter: blur(22px) saturate(130%);
  color: var(--ink-inv);
  opacity: 0; visibility: hidden;
  transition: opacity 0.34s var(--ease-out-quart), visibility 0s linear 0.34s;
  overflow-y: auto; overscroll-behavior: contain;
}
.nav-menu[hidden] { display: none; }
.nav-menu.is-open { opacity: 1; visibility: visible; transition-delay: 0s, 0s; }
@media (min-width: 941px) { .nav-menu { display: none; } }

/* bottom-aligned via auto margin, NOT justify-content:flex-end — now that the
   products group makes the content taller than a 667px phone, flex-end would
   clip the top of an overflowing flex item and the "Menu" heading would become
   unreachable by scrolling. margin-top:auto degrades to plain top-alignment. */
.nav-menu__inner { width: 100%; max-width: 34rem; margin-inline: auto; margin-top: auto; }
.nav-menu__label {
  font-family: var(--font-mono); font-size: 10px; font-weight: 500;
  letter-spacing: 0.26em; text-transform: uppercase;
  color: rgba(243,245,248,0.62);
  padding-bottom: 0.55rem; margin-bottom: 0.75rem;
  border-bottom: 1px solid rgba(243,245,248,0.14);
}
.nav-menu__label + .nav-menu__label,
.nav-menu__list + .nav-menu__label { margin-top: 2rem; }
/* the lists live inside <nav> wrappers, so the label's real previous sibling is
   the <nav> — the two selectors above never actually matched. */
.nav-menu__inner > nav + .nav-menu__label { margin-top: 1.35rem; }

.nav-menu__list { list-style: none; display: block; }
.nav-menu__list li { overflow: hidden; }        /* the mask the letters rise out of */
/* Sized down from clamp(2.1rem, 11.5vw, 3.2rem) / 0.24em when the products
   group joined the overlay. At the old size the four serif words alone ate 272px
   of a 390x844 phone and pushed the phone number and "Book a call" off the
   bottom — the two conversion actions must never cost a scroll, so the headline
   list gives up the height rather than the Direct block. Still the largest type
   on the screen by a wide margin. */
.nav-menu__list a {
  display: block; padding: 0.16em 0;
  font-family: var(--font-display); font-style: italic; font-weight: 400;
  font-size: clamp(1.85rem, 8.6vw, 2.7rem); line-height: 1.06;
  letter-spacing: 0.004em; color: var(--ink-inv);
  transform: translateY(104%);
  transition: transform 0.62s var(--ease-out-expo), color var(--dur-fast) var(--ease-out-quart);
}
/* on-dark teal literal — see §5a: --glow (#1C8B7C) is 2.4:1 on this overlay and
   fails even the large-text 3:1 floor, so the current page was the least legible
   item in the menu. #7FB6AC is the same hue lifted to 8.4:1. */
.nav-menu__list a[aria-current="page"] { color: #7FB6AC; }
.nav-menu.is-open .nav-menu__list a { transform: translateY(0); }
.nav-menu.is-open .nav-menu__list li:nth-child(1) a { transition-delay: 0.04s; }
.nav-menu.is-open .nav-menu__list li:nth-child(2) a { transition-delay: 0.08s; }
.nav-menu.is-open .nav-menu__list li:nth-child(3) a { transition-delay: 0.12s; }
.nav-menu.is-open .nav-menu__list li:nth-child(4) a { transition-delay: 0.16s; }
.nav-menu.is-open .nav-menu__list li:nth-child(5) a { transition-delay: 0.20s; }

/* PRODUCTS on a phone — a labelled group, never a second tap. A thumb should
   not have to open a sub-menu to find out what it is buying, so the four sit in
   the open with their one-liners. Denser than the big serif list above on
   purpose: this is an index, not a headline, and the overlay still has to hold
   Direct below it on a 667px screen. Reveal is opacity + translateY only — no
   translateY(104%) mask, which at this row height would read as a glitch. */
.nav-menu__products { list-style: none; display: block; }
.nav-menu__products li + li { border-top: 1px solid rgba(243,245,248,0.10); }
.nav-menu__products a {
  display: grid; grid-template-columns: auto 1fr; column-gap: 0.8rem;
  padding: 0.55rem 0; min-height: 44px; align-content: center;
  opacity: 0; transform: translateY(10px);
  transition: opacity 0.38s var(--ease-out-quart), transform 0.38s var(--ease-out-quart);
}
.nav-menu.is-open .nav-menu__products a { opacity: 1; transform: none; }
.nav-menu.is-open .nav-menu__products li:nth-child(1) a { transition-delay: 0.20s; }
.nav-menu.is-open .nav-menu__products li:nth-child(2) a { transition-delay: 0.24s; }
.nav-menu.is-open .nav-menu__products li:nth-child(3) a { transition-delay: 0.28s; }
.nav-menu.is-open .nav-menu__products li:nth-child(4) a { transition-delay: 0.32s; }
.nav-menu__products i {
  grid-row: 1 / span 2; padding-top: 0.42rem;
  font-family: var(--font-mono); font-style: normal; font-size: 10px; font-weight: 500;
  letter-spacing: 0.14em; color: rgba(243,245,248,0.58);
}
.nav-menu__products b {
  display: block; font-family: var(--font-display); font-style: italic; font-weight: 400;
  font-size: 1.32rem; line-height: 1.12; letter-spacing: 0.004em; color: var(--ink-inv);
}
.nav-menu__products span {
  display: block; grid-column: 2; margin-top: 0.18rem;
  font-size: 12.5px; line-height: 1.42; color: rgba(243,245,248,0.76);
}
.nav-menu__products a[aria-current="page"] b,
.nav-menu__products a[aria-current="page"] i { color: #7FB6AC; }

.nav-menu__direct { display: grid; gap: 0.7rem; }
.nav-menu__tel {
  display: flex; flex-direction: column; gap: 0.3rem;
  min-height: 44px; padding: 0.85rem 1.1rem;
  border: 1px solid rgba(243,245,248,0.18); border-radius: var(--r-md);
  background: rgba(243,245,248,0.05);
  transition: background var(--dur-fast) var(--ease-out-quart);
}
.nav-menu__tel:hover { background: rgba(243,245,248,0.10); }
.nav-menu__tel b {
  font-family: var(--font-mono); font-weight: 500; font-size: 1.32rem;
  letter-spacing: 0.02em; color: var(--ink-inv);
}
.nav-menu__tel span {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-size: 12.5px; color: rgba(243,245,248,0.78);
}
.nav-menu__dot {
  width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--green); box-shadow: 0 0 0 0 rgba(95,212,51,0.5);
  animation: bd-livepulse 2.4s ease-in-out infinite;
}
.nav-menu__cta {
  display: flex; align-items: center; justify-content: space-between;
  min-height: 52px; padding: 0.9rem 1.1rem;
  border-radius: var(--r-md);
  background: var(--green); color: #0B1A08;
  font-weight: 600; font-size: 15.5px;
}
.nav-menu__cta:active { transform: scale(0.985); }
.nav-menu__meta {
  margin-top: 1.1rem;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.18em;
  text-transform: uppercase; color: rgba(243,245,248,0.5);
}

/* SHORT PHONES — an overlay holding four menu items, four products, the phone
   number and the CTA cannot fit a 667px screen at headline sizes. Rather than
   let "Book a call" drift below the fold, everything compresses once and the
   coordinate line (decoration) is the one thing that goes. The phone number in
   the bar above never moves at any height, so a call is always one tap. */
@media (max-width: 940px) and (max-height: 800px) {
  .nav-menu {
    padding-top: calc(env(safe-area-inset-top, 0px) + 88px);
    padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 16px);
  }
  .nav-menu__label { padding-bottom: 0.4rem; margin-bottom: 0.45rem; }
  .nav-menu__inner > nav + .nav-menu__label { margin-top: 0.75rem; }
  .nav-menu__list a { font-size: clamp(1.45rem, 6.7vw, 2rem); padding: 0.04em 0; }
  .nav-menu__products a { padding: 0.3rem 0; }
  .nav-menu__products b { font-size: 1.16rem; }
  .nav-menu__products span { font-size: 11.5px; line-height: 1.32; }
  .nav-menu__direct { gap: 0.55rem; }
  .nav-menu__tel { padding: 0.68rem 1rem; }
  .nav-menu__tel b { font-size: 1.12rem; }
  .nav-menu__cta { min-height: 48px; padding: 0.72rem 1.1rem; }
  .nav-menu__meta { display: none; }
}

/* body scroll lock while the overlay is open */
html.nav-open, html.nav-open body { overflow: hidden; touch-action: none; }
/* …and the one thing that could strand it. premium-motion.js releases the lock
   when the viewport crosses (min-width: 861px); the overlay itself now vanishes
   at 941px. Between those two numbers a resize could leave `.nav-open` set on a
   desktop layout with no overlay to close — a frozen page. Above the overlay's
   own breakpoint the lock has nothing left to protect, so cancel it in CSS and
   the gap cannot bite regardless of what the JS thinks. */
@media (min-width: 941px) {
  /* overflow-x stays `clip` (§2) — only the lock's vertical half is released,
     or the page loses its sitewide horizontal-overflow guard. */
  html.nav-open, html.nav-open body { overflow-x: clip; overflow-y: visible; touch-action: auto; }
  html.nav-open .bc { opacity: 1; pointer-events: auto; }
}

/* The chat launcher (bally-chat.js, z-index 9000) would otherwise float above
   the overlay and sit on top of the coordinate line. Kept as a scoped override
   so the widget stays a drop-in file with no page-level knowledge. */
html.nav-open .bc {
  opacity: 0; pointer-events: none;
  transition: opacity 0.2s var(--ease-out-quart);
}

@media (prefers-reduced-motion: reduce) {
  .nav-menu { transition: opacity 0.001s linear, visibility 0s; }
  .nav-menu__list a { transform: none; transition: color 0.001s linear; }
  .nav-menu.is-open .nav-menu__list li a { transition-delay: 0s; }
  .nav-menu__products a { opacity: 1; transform: none; transition: none; }
  .nav-menu.is-open .nav-menu__products li a { transition-delay: 0s; }
  .nav__toggle-box i { transition: none; }
  .nav-menu__dot { animation: none; }
}

/* ----------------------------------------------------------------------------
   6 · BUTTONS — pill + button-in-button (nested icon island)
   --------------------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; gap: 12px;
  border-radius: 999px;
  font-family: var(--font-body); font-weight: 500; font-size: 15px;
  padding: 0.85rem 0.85rem 0.85rem 1.5rem;
  transform: translate3d(0,0,0);
  transition: transform var(--dur-fast) var(--ease-out-quart),
              box-shadow var(--dur-fast) var(--ease-out-quart),
              background var(--dur-fast) var(--ease-out-quart);
}
.btn:active { transform: scale(0.98); }
.btn .ic {
  width: 2rem; height: 2rem; border-radius: 999px;
  display: grid; place-items: center;
  transition: transform var(--dur-fast) var(--ease-out-quart), background var(--dur-fast) var(--ease-out-quart);
}
.btn:hover .ic { transform: translate(3px, -1px) scale(1.06); }
/* PRIMARY — GREEN attention fill, navy-ink label (high AA), green bloom on hover.
   Green is reserved for primary CTAs only (see palette hierarchy).
   The label is --black, NOT --ink. --ink flips to #F3F5F8 inside .band--deep,
   but this button's green fill does NOT flip with the ground — so on the navy
   bands (e.g. /pricing/ + /trades/ "Start at rung one") the label was going
   light-on-green at 2.42:1 while every other instance sat at 7.6:1. A fixed
   fill has to carry a fixed label; --black is stable in both token sets. */
.btn--call {
  background: linear-gradient(180deg, var(--green), var(--green-deep));
  color: var(--black);
  box-shadow: 0 10px 34px -12px rgba(70,178,31,0.55);
}
.btn--call .ic { background: rgba(20,26,36,0.14); color: var(--black); }
.btn--call:hover { box-shadow: 0 0 40px rgba(95,212,51,0.38), 0 14px 44px -12px rgba(70,178,31,0.5); }
/* GHOST — glass outline (dark film + ink text on the light ground)
   ---------------------------------------------------------------------------
   SELF-CARRIED GROUND (2026-07-27, rendered-pixel contrast audit).
   The ghost is the secondary CTA that sits beside the green primary in every
   hero — and every premium hero here is a photograph or a diagonal scrim that
   runs dark→light. A 3.5%-alpha film has no ground of its own, so the label
   inherited whatever the photo happened to be doing underneath: measured 1.04:1
   on the home hero's fog band and 1.62:1 on /vs/. Both were literally invisible.

   The fill is derived from the button's OWN label colour and inverted in
   lightness, so one rule is correct on both grounds and stays correct on pages
   built later — including pages that flip the label to light WITHOUT flipping
   any token (which is what /, /pricing/, /products/ai-chat/ and
   /products/website/ actually do via `.<x>-hero .btn--ghost { color: … }`).
     dark label  (#141A24, L .21)  → frost L .92  → near-paper, reads as today
     light label (#F3F5F8, L .97)  → frost L .14  → smoked glass on the photo
   It is painted as an INSET box-shadow, not a background, for two reasons:
   inset shadows are clipped to the border radius, and pages already override
   `background` on their hero ghosts — so a background here would be discarded
   exactly where it is needed most. Nothing overrides box-shadow on .btn--ghost.
   Browsers without relative-colour-syntax simply drop the declaration and get
   today's rendering, so this can only improve, never break.

   ⚠ DO NOT "FIX" THIS BECAUSE contrast-audit.mjs STILL FLAGS IT. That auditor
   paints its background plate with `* { color: transparent !important }`, so
   during measurement currentColor is rgba(0,0,0,0) → l resolves to 0 → the
   clamp returns 0.96 and the plate shows a near-WHITE pill exactly where the
   browser paints a near-black one. Measured with the killer sheet on and off:
   plate fill rgb(204,206,210) vs real fill rgb(13,15,20). Sampling the real
   painted pill gives 8.9–17.5:1 on every route/viewport (home 12.9, ai-chat
   16.2, website 13.2, vs 9.9, work 10.9, automation 8.9, marketing 8.7,
   pricing 17.5). The ~6 remaining ghost rows in contrast.json are that
   artifact, not a defect. */
.btn--ghost {
  border: 1px solid var(--glass-line);
  background: var(--glass);
  color: var(--ink);
  box-shadow: inset 0 0 0 100px oklch(from currentColor clamp(0.08, 1.14 - l * 1.04, 0.96) calc(c * 0.5) h / 0.82);
}
.btn--ghost .ic { background: rgba(20,26,36,0.06); }
/* INK — navy fill (neutral emphasis, dark button on light ground) */
.btn--ink { background: var(--ink); color: var(--paper); }
.btn--ink .ic { background: rgba(243,245,248,0.14); }

/* ----------------------------------------------------------------------------
   7 · DOUBLE BEZEL (Doppelrand) — every premium card/image sits in a tray
   --------------------------------------------------------------------------- */
.bezel {
  padding: 0.5rem;
  border-radius: var(--r-lg);
  background: var(--glass);
  box-shadow: inset 0 0 0 1px var(--line);
}
.bezel__core {
  border-radius: calc(var(--r-lg) - 0.5rem);
  background: var(--surface);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.8), 0 30px 60px -28px rgba(20,26,36,0.20);
  overflow: hidden;
}

/* ----------------------------------------------------------------------------
   8 · TRUST ROW — slim honest trust-furniture band (phone · facts · promise)
   Markup: <div class="trustline"><div class="shell trustline__row">
             <a class="trustline__tel" href="tel:...">…</a>
             <span class="trustline__item">…</span> … </div></div>
   --------------------------------------------------------------------------- */
.trustline {
  border-top: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
  background: color-mix(in oklab, var(--wash) 55%, var(--paper));
}
.trustline__row {
  display: flex; align-items: center; justify-content: center;
  gap: clamp(12px, 2.2vw, 28px); flex-wrap: wrap;
  padding-block: 14px;
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--muted);
  text-align: center;
}
.trustline__item { display: inline-flex; align-items: center; gap: 10px; }
.trustline__item::before {
  content: ""; width: 4px; height: 4px; border-radius: 50%;
  background: var(--brand); flex: none;
}
.trustline__tel { color: var(--ink); letter-spacing: 0.08em; display: inline-flex; align-items: center; gap: 10px; }
.trustline__tel::before {
  content: ""; width: 4px; height: 4px; border-radius: 50%;
  background: var(--accent); flex: none;              /* one amber dot marks the phone */
}
.trustline__tel:hover { color: var(--glow); }
@media (max-width: 640px) {
  .trustline__row { flex-direction: column; gap: 9px; padding-block: 16px; }
  .trustline__item::before, .trustline__tel::before { display: none; }
  /* TAP TARGET (2026-08-19). This is a phone number, on a business that sells
     phone answering, rendering 17px tall on a phone — the single worst target
     on the site. It is a link, so it needs the full 44px; the surrounding
     .trustline__item spans are plain text and are deliberately left alone.
     Given a real target it may as well look like one: a quiet outlined chip
     that says "this is tappable" without competing with the green CTA. */
  .trustline__tel {
    min-height: 44px; padding: 0 16px; border-radius: 999px;
    border: 1px solid var(--line); background: var(--glass);
    font-size: 12px; letter-spacing: 0.09em;
  }
  .trustline__row { gap: 12px; }
}

/* ----------------------------------------------------------------------------
   9 · MARQUEE — CSS-driven, pauses on hover (no JS needed)
   --------------------------------------------------------------------------- */
.marquee-band {
  border-top: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
  padding-block: clamp(1.4rem, 2.6vh, 2.2rem);
  background: var(--paper);
}
.marquee { overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee__track {
  display: flex; align-items: baseline; gap: clamp(2.4rem, 4vw, 4.5rem);
  width: max-content;
  animation: marquee 30s linear infinite;   /* linear OK: continuous conveyor, not UI easing */
  will-change: transform;
}
.marquee__track:hover { animation-play-state: paused; }
.marquee__track span {
  font-family: var(--font-display); font-style: italic;
  font-size: var(--step-3); line-height: 1; color: var(--muted);
  white-space: nowrap;
}
.marquee__track i {
  font-style: normal; align-self: center;
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--brand); opacity: 0.8; flex: none;
}
@keyframes marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* ----------------------------------------------------------------------------
   10 · SEQ — pinned canvas image-sequence scrub (engine in premium-motion.js)
   Markup: <section class="seq is-static" data-seq data-seq-frames="61"
             data-seq-path="/assets/scrub/home/f-{i}.webp" data-seq-pad="3"
             data-seq-scroll="+=280%"> … see home page for full anatomy.
   Ship with class "is-static" — the engine removes it when it can scrub.
   --------------------------------------------------------------------------- */
.seq { position: relative; }
.seq__stage { position: relative; height: 100svh; overflow: hidden; isolation: isolate; }
.seq__canvas { position: absolute; inset: 0; width: 100%; height: 100%; z-index: -2; }
.seq__fallback { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: -2; display: none; }
.seq__scrim {
  position: absolute; inset: 0; z-index: -1;
  background:
    /* text-safe patch: darkens the zone directly behind seq__steps so headline/
       copy stay legible over bright or busy frames, independent of frame content */
    radial-gradient(48% 44% at 50% 50%, rgba(var(--scrim),0.60) 0%, rgba(var(--scrim),0.22) 60%, transparent 100%),
    radial-gradient(120% 90% at 50% 58%, rgba(var(--scrim),0.18) 0%, rgba(var(--scrim),0.62) 78%),
    linear-gradient(180deg, var(--paper) 0%, transparent 14%, transparent 86%, var(--paper) 100%);
}
.seq__eyebrow {
  position: absolute; top: clamp(84px, 12vh, 120px); left: 50%; transform: translateX(-50%);
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.3em;
  /* was rgba(243,236,224,0.55) — the last warm-cream (retired brass family) left
     in this file, and a 0.55 alpha over a scrub frame whose luminance is not
     knowable. Solid on-dark token + halo instead, same as every hero eyebrow. */
  text-transform: uppercase;
  color: var(--eyebrow-on-dark); text-shadow: var(--eyebrow-halo-dark);
}
.seq__steps { position: absolute; inset: 0; display: grid; place-items: center; text-align: center; padding-inline: var(--gutter); }
.seq__step { grid-area: 1 / 1; max-width: min(92vw, 1020px); }
.seq__step h2 {
  font-family: var(--font-display); font-weight: 400;
  font-size: var(--step-6); line-height: 1.02; letter-spacing: -0.01em;
  color: #F3ECE0; /* light — sits over dark scroll-sequence frames */
  text-shadow: 0 2px 30px rgba(8,10,14,0.72), 0 1px 8px rgba(8,10,14,0.6);
}
.seq__step h2 em {
  font-style: italic;
  /* lifted off pure --glow: a mid-teal reads too close in luminance to a bright
     photo frame to stay legible on its own — lighten it so it holds contrast
     against light AND dark portions of the scrub sequence */
  color: color-mix(in oklab, var(--glow) 62%, white 38%);
  text-shadow: 0 2px 22px rgba(8,10,14,0.85), 0 0 28px rgba(8,10,14,0.55);
}
.seq__step p {
  margin-top: 18px; color: rgba(243,236,224,0.78);
  font-weight: 300; font-size: var(--step-1); max-inline-size: 44ch; margin-inline: auto;
  text-shadow: 0 1px 18px rgba(var(--scrim),0.7);
}
.seq__bar {
  position: absolute; left: 50%; bottom: clamp(40px, 7vh, 64px); transform: translateX(-50%);
  width: min(280px, 40vw); height: 1px; background: var(--line);
}
.seq__bar i {
  position: absolute; inset: 0;
  background: var(--glow);
  transform: scaleX(0); transform-origin: left;
  display: block;
}
/* mobile / reduced: static scene, no pin */
.seq.is-static .seq__stage { height: auto; padding: var(--section-y) 0 0; overflow: visible; }
.seq.is-static .seq__canvas { display: none; }
.seq.is-static .seq__fallback {
  display: block; position: relative; inset: auto;
  aspect-ratio: 16 / 10; height: auto; border-radius: var(--r-md);
  width: calc(100% - var(--gutter) * 2); margin-inline: auto;
  border: 1px solid var(--line);
}
.seq.is-static .seq__scrim { display: none; }
.seq.is-static .seq__eyebrow { position: static; transform: none; display: block; text-align: center; margin: 30px 0 0; }
.seq.is-static .seq__eyebrow .when-scrub { display: none; }
.seq__eyebrow .when-static { display: none; }
.seq.is-static .seq__eyebrow .when-static { display: inline; }
/* CONTRAST FIX (axe, 47 serious) — in static mode the seq drops its dark canvas
   and sits on plain --paper, but .seq__eyebrow keeps rgba(243,236,224,0.55),
   which computes to ~rgb(240,236,230) on cream: 1.06:1, effectively invisible.
   Repoint the static variant at the on-light eyebrow token → 7.8:1. */
.seq.is-static .seq__eyebrow { color: var(--eyebrow-on-light); text-shadow: none; }
.seq.is-static .seq__steps { position: static; display: block; padding-block: 3rem 1rem; }
.seq.is-static .seq__step { max-width: 30ch; margin-inline: auto; padding-block: 1.6rem; }
.seq.is-static .seq__step h2 { font-size: var(--step-4); text-shadow: none; color: var(--ink); }
/* static fallback sits on plain --paper, not a photo — the lightened pastel teal
   (tuned for legibility over busy scrub frames) has weaker AA contrast on cream
   than --teal-deep, so pin it back to the small-text-safe token here */
.seq.is-static .seq__step h2 em { color: var(--teal-deep); text-shadow: none; }
.seq.is-static .seq__step p { text-shadow: none; color: var(--muted); }
.seq.is-static .seq__bar { display: none; }

/* ----------------------------------------------------------------------------
   11 · STICKY-STACK — stacking card deck (engine adds settle/dim on desktop)
   --------------------------------------------------------------------------- */
.stack { display: flex; flex-direction: column; gap: clamp(1.4rem, 3vh, 2.4rem); }
.stack-card { position: relative; --dim: 0; }
.stack-card .bezel { position: relative; }
.stack-card .bezel::after {
  content: ""; position: absolute; inset: 0; z-index: 2;
  border-radius: var(--r-lg);
  background: rgba(20,26,36,1);
  opacity: var(--dim);
  pointer-events: none;
}
@media (min-width: 768px) {
  .stack-card { position: sticky; top: calc(92px + var(--i, 0) * 14px); }
}
.stack-card .bezel__core {
  display: grid; grid-template-columns: 1fr;
  background: var(--wash);
}
@media (min-width: 880px) {
  .stack-card .bezel__core { grid-template-columns: 7fr 5fr; min-height: 58vh; }
}
.stack-card__shot { position: relative; overflow: hidden; background: var(--wash-deep); }
.stack-card__shot img {
  width: 100%; height: 100%; object-fit: cover; object-position: top center;
  aspect-ratio: 16 / 10;
}
.stack-card__shot::after {
  content: ""; position: absolute; inset: 0;
  box-shadow: inset 0 0 0 1px rgba(20,26,36,0.06);
  pointer-events: none;
}
.stack-card__body {
  padding: clamp(1.6rem, 3vw, 3rem);
  display: flex; flex-direction: column; justify-content: center; gap: 14px;
}
/* __num is the spec-sheet numbering ("01 02 03") — genuinely decorative, and the
   quiet rail is the point. Deliberately LEFT at --muted-2. */
.stack-card__num { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.2em; color: var(--muted-2); }
/* __tier is read ("Founder tier · one page") at 10.5px. --brand teal measured
   4.06:1 on the white card; --teal-deep is the file's small-text-safe teal
   (5.5:1 on paper, and band--deep already remaps it to the bright #3FBFAD). */
.stack-card__tier {
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--teal-deep);
}
.stack-card__title {
  font-family: var(--font-display); font-weight: 400;
  font-size: var(--step-4); line-height: 1; letter-spacing: -0.01em;
}
.stack-card__got { color: var(--muted); font-weight: 300; font-size: var(--step-0); line-height: 1.55; max-inline-size: 40ch; }
.stack-card__meta { font-size: var(--step--1); color: var(--muted-aa); }   /* read, not decoration */
.stack-card__cta {
  margin-top: 6px;
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--ink); font-weight: 500; font-size: 14.5px;
}
.stack-card__cta .arr { transition: transform var(--dur-fast) var(--ease-out-quart); }
.stack-card:hover .stack-card__cta { color: var(--glow); }
.stack-card:hover .stack-card__cta .arr { transform: translateX(4px); }
@media (max-width: 767px) { .stack-card .bezel__core { min-height: 0; } }

/* ----------------------------------------------------------------------------
   12 · CONTACT LEDGER — label/value rows with hairlines
   --------------------------------------------------------------------------- */
.contact-ledger .row {
  display: flex; align-items: baseline; gap: 22px;
  padding-block: 15px;
  border-top: 1px solid var(--line);
  font-size: var(--step-0);
}
.contact-ledger .row:last-child { border-bottom: 1px solid var(--line); }
/* PHONE / EMAIL / HOURS — these name the value sitting next to them, so they are
   read, not decoration. --muted-2 measured 2.90:1 on the home page's light
   ground; --muted-aa is the same quiet grey that actually clears AA (5.2:1). */
.contact-ledger .label {
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--muted-aa); min-width: 74px;
  transform: translateY(-2px);
}
.contact-ledger a {
  color: var(--ink); border-bottom: 1px solid var(--line);
  transition: border-color var(--dur-fast) var(--ease-out-quart), color var(--dur-fast) var(--ease-out-quart);
}
.contact-ledger a:hover { color: var(--glow); border-color: var(--brand); }
.contact-ledger .val { color: var(--muted); }

/* ----------------------------------------------------------------------------
   13 · FOOTER — the navy dark-guest closing band (markup in _nav-footer.html)
   .footer carries .band--deep itself: every shared token (ink/muted/line/
   brand/glow/burgundy) remaps to the navy context automatically, so this
   block only adds footer-specific layout — never raw hex.
   --------------------------------------------------------------------------- */
.footer {
  position: relative;
  overflow: hidden;
  padding-block: clamp(4.5rem, 8vh, 6.5rem) clamp(2rem, 4vh, 2.8rem);
}
/* faint navy-on-navy radial — the one restrained atmosphere cue, no photo, no noise beyond the global grain layer */
.footer::before {
  content: "";
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(60% 90% at 12% 0%, rgba(63,191,173,0.10), transparent 60%),
    radial-gradient(50% 70% at 100% 100%, rgba(192,85,118,0.09), transparent 60%);
}
.footer > .shell { position: relative; z-index: 1; }

/* FOOTER FACET — angular layered-panel accent lifted from the business
   card's visual language (navy/teal/burgundy diagonal facets), anchored
   bottom-right. SVG-based (see corner-facet comment above for why: CSS
   clip-path + filter:blur re-clip each other and produce a hard edge, SVG's
   own feGaussianBlur doesn't have that conflict). Markup:
   <div class="footer__facet" aria-hidden="true"><svg viewBox="0 0 500 400">…</svg></div> */
.footer__facet { position: absolute; right: 0; bottom: 0; z-index: 0; pointer-events: none;
  width: min(52vw, 620px); height: min(70%, 420px); overflow: hidden; }
.footer__facet svg { position: absolute; inset: 0; width: 100%; height: 100%; }
@media (max-width: 640px) { .footer__facet { width: 70vw; height: 45%; opacity: 0.7; } }

/* CORNER FACET (top-right variant) — same business-card facet language as
   .footer__facet, vertically mirrored for a section's TOP-right corner
   instead of the bottom. Reusable on any .band--cream/.band--burgundy
   section that has a large quiet corner (a headline + a few cards, lots of
   ground around them). Needs `position:relative` on the section (both band
   contexts already set it) and should sit as the section's first child so
   it paints under the .shell content (z-index 0 vs .shell's 1). */
/* Rebuilt as SVG (was CSS clip-path + filter:blur, which don't compose —
   a blur on a clipped element gets re-clipped at the same hard edge, so the
   "soft facet" rendered with a crisp diagonal line anyway). SVG's own
   feGaussianBlur has no such conflict. Markup carries the shapes directly:
   <div class="corner-facet--tr" aria-hidden="true"><svg viewBox="0 0 500 400">…</svg></div> */
.corner-facet--tr { position: absolute; top: 0; right: 0; z-index: 0; pointer-events: none;
  width: min(52vw, 620px); height: min(72%, 440px); overflow: hidden; }
.corner-facet--tr svg { position: absolute; inset: 0; width: 100%; height: 100%; }
@media (max-width: 640px) { .corner-facet--tr { width: 68vw; height: 44%; } }

/* CORNER FACET (bottom-left variant) — same SVG content, mirrored via CSS
   transform, for sections where the top-right is already busy (a photo, a
   card) and the empty ground is lower down instead. */
.corner-facet--bl { position: absolute; bottom: 0; left: 0; z-index: 0; pointer-events: none;
  width: min(52vw, 620px); height: min(72%, 440px); overflow: hidden; transform: scale(-1, -1); }
.corner-facet--bl svg { position: absolute; inset: 0; width: 100%; height: 100%; }
@media (max-width: 640px) { .corner-facet--bl { width: 68vw; height: 44%; } }

/* NETWORK FIELD — a loose constellation of nodes and connecting lines,
   hand-placed (not a grid) so it reads as a considered graphic rather than
   a repeating pattern. Second decorative language alongside the angular
   facet, for pages that want a more "connected/technical" feel (front
   desk, automation) or just visual variety so the site doesn't repeat one
   trick everywhere. Single inline SVG, no network request, no JS. */
.net-field { position: absolute; z-index: 0; pointer-events: none; overflow: hidden;
  width: min(54vw, 640px); height: min(68%, 440px); opacity: 1;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 500 400'%3E%3Cg stroke='%231C8B7C' stroke-width='1.6' opacity='0.55' fill='none'%3E%3Cline x1='60' y1='80' x2='150' y2='140'/%3E%3Cline x1='150' y1='140' x2='120' y2='230'/%3E%3Cline x1='150' y1='140' x2='260' y2='100'/%3E%3Cline x1='260' y1='100' x2='340' y2='160'/%3E%3Cline x1='260' y1='100' x2='230' y2='30'/%3E%3Cline x1='340' y1='160' x2='430' y2='120'/%3E%3Cline x1='340' y1='160' x2='380' y2='250'/%3E%3Cline x1='120' y1='230' x2='200' y2='290'/%3E%3Cline x1='200' y1='290' x2='300' y2='320'/%3E%3Cline x1='380' y1='250' x2='300' y2='320'/%3E%3Cline x1='60' y1='80' x2='30' y2='170'/%3E%3Cline x1='30' y1='170' x2='120' y2='230'/%3E%3C/g%3E%3Ccircle cx='60' cy='80' r='5' fill='%231C8B7C'/%3E%3Ccircle cx='150' cy='140' r='7' fill='%231C8B7C'/%3E%3Ccircle cx='120' cy='230' r='5' fill='%237C2D48'/%3E%3Ccircle cx='260' cy='100' r='6.5' fill='%231C8B7C'/%3E%3Ccircle cx='230' cy='30' r='4.5' fill='%235FD433'/%3E%3Ccircle cx='340' cy='160' r='8' fill='%231C8B7C'/%3E%3Ccircle cx='430' cy='120' r='5' fill='%237C2D48'/%3E%3Ccircle cx='380' cy='250' r='5' fill='%231C8B7C'/%3E%3Ccircle cx='200' cy='290' r='4.5' fill='%235FD433'/%3E%3Ccircle cx='300' cy='320' r='6' fill='%231C8B7C'/%3E%3Ccircle cx='30' cy='170' r='4' fill='%237C2D48'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-size: contain;
}
.net-field--tr { top: 0; right: 0; }
.net-field--br { bottom: 0; right: 0; transform: scaleY(-1); }
.net-field--bl { bottom: 0; left: 0; transform: scale(-1, -1); }
@media (max-width: 640px) { .net-field { width: 74vw; height: 42%; } }

/* BLOB WASH — a soft organic fluid shape (not a hard-edged polygon), the
   "liquid/silk" reference: irregular rounded blob in one bold hue with a
   second smaller blob overlapping in the accent hue. Reads as art, not a
   texture. */
.blob-wash { position: absolute; z-index: 0; pointer-events: none; overflow: hidden;
  width: min(48vw, 580px); height: min(60%, 400px); }
.blob-wash svg { position: absolute; width: 100%; height: 100%; }
.blob-wash--tr { top: 0; right: 0; }
.blob-wash--tl { top: 0; left: 0; transform: scaleX(-1); }
.blob-wash--br { bottom: 0; right: 0; transform: scaleY(-1); }
.blob-wash--bl { bottom: 0; left: 0; transform: scale(-1, -1); }
@media (max-width: 640px) { .blob-wash { width: 78vw; height: 40%; } }

/* WAVE LINES — a set of flowing ribbon-like curves, the ink-brush/silk
   reference. Thin, layered, medium-high contrast strokes so the motion
   reads clearly rather than disappearing into the page. */
.wave-lines { position: absolute; z-index: 0; pointer-events: none; overflow: hidden;
  width: min(56vw, 680px); height: min(58%, 380px); }
.wave-lines svg { position: absolute; width: 100%; height: 100%; }
.wave-lines--tr { top: 0; right: 0; }
.wave-lines--br { bottom: 0; right: 0; transform: scaleY(-1); }
.wave-lines--bl { bottom: 0; left: 0; transform: scale(-1, -1); }
@media (max-width: 640px) { .wave-lines { width: 82vw; height: 34%; } }

/* HALFTONE BURST — dots that shrink as they move away from the corner,
   the halftone-gradient reference. Denser/larger near the anchor point,
   thinning out, so it reads as a deliberate burst rather than a flat grid. */
.halftone-burst { position: absolute; z-index: 0; pointer-events: none; overflow: hidden;
  width: min(46vw, 520px); height: min(58%, 360px); }
.halftone-burst svg { position: absolute; width: 100%; height: 100%; }
.halftone-burst--tr { top: 0; right: 0; }
.halftone-burst--bl { bottom: 0; left: 0; transform: scale(-1, -1); }
@media (max-width: 640px) { .halftone-burst { width: 62vw; height: 36%; } }

/* ============================================================
   FULL-BLEED BACKGROUND ART -- 2026-08-20.
   Everything above this block is a CORNER accent: it decorates one edge of
   a section and leaves the rest of the ground bare. That is why so many
   sections still read as "a headline on white" -- the corner pieces were
   never meant to carry a whole section, and on sections without a big quiet
   corner (long body copy, a calendar grid, a plain closing band) nothing
   was applied at all.
   These four are full-bleed: `inset: 0`, sized to their section, meant to
   sit behind ENTIRE content rather than one corner of it. Same rule as
   every other decoration here -- z-index 0, pointer-events none, the
   section needs `position:relative` (band contexts already set it) and the
   layer must be the section's first child so it paints under `.shell`.
   Palette is the business-card language: layered navy/teal/burgundy facets
   with per-panel gradient shading (not flat opacity) and a fine dot-grid
   texture inside the facets -- never a generic scattered-dot field, which
   the owner explicitly moved away from on the card.
   Use ONE per section. They are a ground, not a confetti generator. */

.bg-facets { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.bg-facets svg { position: absolute; inset: 0; width: 100%; height: 100%; }
/* opacity lives on the layer, not baked into the SVG's own fills, so one
   component serves both a quiet body section and a louder hero without a
   second markup variant */
.bg-facets--quiet { opacity: 0.55; }
.bg-facets--bold { opacity: 1; }
@media (max-width: 640px) { .bg-facets--quiet { opacity: 0.4; } }

.bg-topo { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.bg-topo svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.bg-topo--quiet { opacity: 0.5; }

.bg-silk { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.bg-silk svg { position: absolute; inset: 0; width: 100%; height: 100%; }

/* Paper-cut waves run along the BOTTOM of a section -- masked to a ramp so
   they gather weight at the foot and clear near the top, which is what
   turns a dead page-ending band into a floor rather than flooding the
   whole section with colour. */
.bg-papercut { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden;
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 46%);
          mask-image: linear-gradient(180deg, transparent 0%, #000 46%); }
.bg-papercut svg { position: absolute; bottom: 0; left: 0; width: 100%; height: 100%; }
@media (max-width: 640px) {
  /* the wave crests are tuned for a wide viewBox; under 640px they compress
     into a busier ripple than intended, so trim the visible band instead */
  .bg-papercut { -webkit-mask-image: linear-gradient(180deg, transparent 30%, #000 62%);
                         mask-image: linear-gradient(180deg, transparent 30%, #000 62%); }
}


/* -- closing moment: one editorial line + a single quiet CTA -- */
.footer__close {
  display: flex; align-items: center; justify-content: space-between; gap: 2rem;
  padding-bottom: clamp(2.4rem, 4.5vh, 3.4rem);
  border-bottom: 1px solid var(--line-soft);
  margin-bottom: clamp(2.6rem, 5vh, 3.6rem);
}
.footer__line {
  font-family: var(--font-display); font-weight: 400; font-style: normal;
  font-size: var(--step-3); line-height: 1.15; letter-spacing: -0.005em;
  color: var(--ink); max-inline-size: 30ch;
}
.footer__line em { font-style: italic; color: var(--glow); }
.footer__cta { flex: none; }

/* -- nav grid -- */
.footer__grid { display: grid; grid-template-columns: 1fr; gap: 2.6rem; }
@media (min-width: 820px) { .footer__grid { grid-template-columns: 1.6fr 1fr 1fr 1fr; gap: 2rem; } }
.footer__brand p { color: var(--muted); font-size: var(--step--1); max-inline-size: 34ch; margin-top: 14px; font-weight: 300; }
.footer__contact {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 16px; font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.02em;
}
.footer__contact a { color: var(--muted); transition: color var(--dur-fast) var(--ease-out-quart); }
.footer__contact a:hover { color: var(--glow); }
.footer__contact span { color: var(--muted-2); }
.footer h4 {
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--muted-2); margin-bottom: 16px; font-weight: 500;
}
.footer ul { list-style: none; display: flex; flex-direction: column; gap: 10px; }
.footer ul a, .footer ul span { color: var(--muted); font-size: 14px; transition: color var(--dur-fast) var(--ease-out-quart); }
.footer ul a:hover { color: var(--ink); }

/* -- coordinates + status rule -- */
.footer__rule {
  margin-top: clamp(2.6rem, 5vh, 4rem); padding-top: 1.6rem;
  border-top: 1px solid var(--line-soft);
  display: flex; justify-content: space-between; align-items: center; gap: 1rem 2rem; flex-wrap: wrap;
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.1em;
  color: var(--muted-2); text-transform: uppercase;
}
.footer__status { display: inline-flex; align-items: center; gap: 9px; color: var(--muted-2); }
.footer__live-dot {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
  background: var(--green);
  box-shadow: 0 0 0 0 rgba(95,212,51,0.55);
  transition: background-color .3s ease;
}
/* Real open/closed state, set by premium-motion.js off the actual ET clock
   (Mon-Fri 8a-6p). Off-hours the dot goes quiet — no pulse, dimmer amber —
   rather than keep flashing "live" when nobody's actually picking up. */
.footer__status--closed .footer__live-dot {
  background: #C9974A;
  animation: none !important;
  box-shadow: none;
}

/* -- the rule yields to the floating chat launcher ---------------------------
   bally-chat.js pins `.bc` at right:22px / bottom:22px with a 60px circle, a
   pulse ring that scales to 1.32 (≈95px wide at peak → ~99px in from the right
   edge) and a soft drop shadow. MOVING the launcher sideways is the wrong
   direction: it is fixed to the viewport, so any offset that clears the footer
   would push it over content on some other page — measured at 390px, a mirrored
   bottom-LEFT slot lands on exactly the same elements. The footer yields
   instead. (bally-chat.js does now yield in TIME — it is absent over the first
   viewport and ducks while scrolling down — but this reserve must still hold
   for the at-rest state, so nothing here changes.)

   `.footer__rule` is the last line of every one of the 42 pages, and its right
   item — the "TAKING NEW BUILDS" live status — was landing inside that zone.
   Measured before the fix: the status text ends at the shell content edge, i.e.
   (100vw − ((100vw − --shell)/2 + --gutter)) — 10px clear of the circle at
   1440 (the pulse ring still swept over the final glyphs), and 31px UNDER the
   circle at 1280, which is where the /blog/ screenshot caught it.

   So reserve exactly the shortfall and no more: the natural gap between the
   shell content edge and the viewport edge already grows with the viewport, and
   past ~1480px it clears the launcher on its own — there the reserve resolves
   to 0px and the status keeps its original alignment. Padding is drawn INSIDE
   the border box, so the hairline still spans the full shell width; only the
   text moves. Below 641px the rule is a left-aligned column (see media query
   below) and the status already clears the launcher, so it is left untouched. */
:root { --fab-safe: 110px; }   /* launcher footprint incl. pulse ring + optical gap */
@media (min-width: 641px) {
  .footer__rule {
    padding-inline-end: max(0px, calc(var(--fab-safe)
                        - (100vw - min(100vw, var(--shell))) / 2
                        - var(--gutter)));
  }
}

/* ---- PHONE TAP TARGETS (2026-08-19) ------------------------------------------
   Measured on the live site at 390px, before this block:
     .footer ul a       18px tall   (14px type + a 10px column gap)
     .footer__contact a 21px tall
   Both are links, both are 26px under the 44px minimum, and there are thirteen
   of them stacked — the densest miss-tap zone on the site. The fix is real
   padding, not bigger type: the type is fine, the box is not.

   Scoped to the width where .footer__grid is already a single column (< 820px),
   so the four-column desktop footer keeps its tight 10px rhythm and does not
   balloon. Inside the column the row gap goes to 0 because each row now carries
   its own 44px of height — keeping the old 10px gap on top of that would add
   50px of dead air to the footer for nothing.
   ---------------------------------------------------------------------------- */
@media (max-width: 819px) {
  .footer ul { gap: 0; }
  .footer ul a, .footer ul span {
    display: flex; align-items: center; min-height: 44px;
  }
  .footer__contact { gap: 0 10px; }
  .footer__contact a { display: inline-flex; align-items: center; min-height: 44px; }
  /* the "·" separators are decoration, not targets — they keep their own height
     so the row does not turn into a 44px-tall wall of middots */
  .footer__contact span { align-self: center; }
}

@media (max-width: 640px) {
  .footer__close { flex-direction: column; align-items: flex-start; gap: 1.4rem; }
  .footer__line { font-size: var(--step-2); }
  .footer__rule { flex-direction: column; align-items: flex-start; gap: 10px; letter-spacing: 0.08em; }

  /* ---- THE LIVE STATUS PILL vs THE CHAT LAUNCHER (P0, 2026-08-19) ----------
     bally-chat.js pins `.bc` at right:12px / bottom:12px with a 48px circle on
     a phone. `.footer__rule` is the last line of every page, and its live
     status — "Taking new builds · back tomorrow at 8a ET", the newest feature
     on the site — was landing inside that square. Measured at 390x844 before
     this change: FAB x330–378 / y604–652, status x20–344 / y615–632. The FAB
     sat on the last quarter of the sentence, on every page, at rest.

     The launcher cannot move: it is fixed to the viewport, and a mirrored
     bottom-LEFT slot lands on exactly the same elements (that experiment is
     already documented above the --fab-safe block). So the FOOTER yields, the
     same way it already does on desktop — but on a phone the rule is a
     left-aligned COLUMN, so an inline reserve does nothing; the collision is
     vertical. Reserve the launcher's whole square at the bottom of the page
     instead, and the last line of the footer simply sits above it.

     84px = the 48px circle + its 12px inset + 24px of optical air, plus the
     home-indicator inset on the phones that have one.

     The status also moves to the TOP of the column (order: -1) and gets a real
     chip. It is the one live, changing thing in the footer and it was reading
     as the smallest, faintest, last item on the page. */
  .footer { padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 84px); }
  .footer__rule { padding-inline-end: 0; }
  .footer__status {
    order: -1;
    min-height: 30px; padding: 6px 12px 6px 11px; margin-bottom: 2px;
    border: 1px solid var(--line); border-radius: 999px;
    background: var(--glass);
    color: var(--ink);
    max-width: 100%;
  }
}

/* ----------------------------------------------------------------------------
   14 · REVEALS — initial hidden state is set by JS only (no-JS users see all)
   --------------------------------------------------------------------------- */
.reveal { will-change: transform, opacity; }

/* ----------------------------------------------------------------------------
   15 · .band--cream — Set B "Editorial Cream + Teal" context override
   Add to any <section> for the warm-light rest band. All shared components
   (eyebrow, h-display, bezel, buttons, ledger) restyle automatically.
   Rules: headlines --teal-deep · CTAs --brand w/ cream text ·
          brass --accent decorative-scale ONLY (use --accent-body at body size).
   --------------------------------------------------------------------------- */
/* Light-led now → .band--cream is a near-default light REST band (a hair warmer/
   raised than paper). Harmless: it no longer flips the ground dark. Emphasis em
   goes BURGUNDY here for the one premium accent moment. Seams retired. */
.band--cream {
  --paper:      #FCFCFA;                    /* elevated light rest ground */
  --wash:       #FFFFFF;                    /* crisp raised card */
  --wash-deep:  #EEF1F4;
  --ink:        #141A24;
  --muted:      #5A6270;
  --muted-2:    #8A93A0;
  --muted-aa:   #5F6879;   /* completes the ground contract. --muted-aa means "the quiet grey that clears AA
                              on THIS ground", so every ground context must state it — :root and .band--deep
                              already do. .band--cream is LIGHT, so the value matches :root and nothing changes
                              today (verified: 0 .band--cream-inside-.band--deep nestings across all 64 routes).
                              Declaring it anyway closes the trap: the first cream band nested in a deep band
                              would otherwise inherit the navy #8C96A8 and go faint on cream, which is exactly
                              the failure --muted-aa exists to prevent. 5.18:1 on #FCFCFA. */
  --line:       rgba(20,26,36,0.12);
  --line-soft:  rgba(20,26,36,0.06);
  background: var(--paper);
  color: var(--ink);
  position: relative;
}
/* ATMOSPHERE — two soft off-corner colour blooms plus a fine dot-grid, so a
   cream band reads as an engineered surface instead of a flat colour fill.
   One fixed recipe on ::before (z-index 0, below every .shell) — costs
   nothing extra to load, needs zero markup anywhere .band--cream is already
   used (64 routes), and stays under every card's own bezel/shadow. Ration:
   two blooms (teal + burgundy, ≤7% alpha) never a third colour. */
.band--cream::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    linear-gradient(122deg, rgba(28,139,124,0.07) 0%, transparent 42%, rgba(124,45,72,0.06) 100%),
    radial-gradient(46% 52% at 8% 2%, rgba(28,139,124,0.24), transparent 64%),
    radial-gradient(40% 46% at 96% 96%, rgba(124,45,72,0.20), transparent 66%),
    radial-gradient(rgba(20,26,36,0.11) 1px, transparent 1.4px);
  background-size: auto, auto, auto, 22px 22px;
}
.band--cream > .shell { position: relative; z-index: 1; }
.band--cream ::selection { background: rgba(28,139,124,0.20); color: var(--ink); }
.band--cream .h-display em { color: var(--burgundy); }   /* the deliberate burgundy emphasis moment */
.band--cream .bezel__core {
  background: var(--wash);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.9), 0 30px 60px -28px rgba(20,26,36,0.18);
}
/* chip — badge-scale accent, now BURGUNDY (brass retired) */
.chip-warm {
  color: var(--burgundy);
  border: 1px solid color-mix(in oklab, var(--burgundy) 40%, transparent);
  background: var(--accent-dim);
  padding: 4px 10px; border-radius: 999px;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.14em;
}

/* ----------------------------------------------------------------------------
   15a2 · .band--burgundy — warm-light accent band, a visibly tinted step up
   from .band--cream (which stayed intentionally near-default, see above) and
   lighter-touch than .band--deep (which stays capped at one per page). Use
   for a section that wants real color presence without going full dark —
   a pricing panel, a stat strip, a pull-quote. Ink stays the same dark
   value as cream so contrast is unaffected by the tint. Hex literals, not
   color-mix(), for the same reason .band--cream/.band--deep use them: a
   computed color-mix() resolves to oklab() in getComputedStyle, which the
   contrast auditor's rgb() parser skips.
   --------------------------------------------------------------------------- */
.band--burgundy {
  --paper:      #F7EEEF;
  --wash:       #FFFFFF;
  --wash-deep:  #F0E1E4;
  --ink:        #141A24;
  --muted:      #6B5560;
  --muted-2:    #9B838C;
  --muted-aa:   #6B5560;
  --line:       rgba(124,45,72,0.16);
  --line-soft:  rgba(124,45,72,0.07);
  --accent:     var(--burgundy);
  --accent-body: var(--burgundy);
  background: var(--paper);
  color: var(--ink);
  position: relative;
}
/* Same atmosphere idea as .band--cream, flipped to lead teal on a burgundy-
   tinted ground so the two light bands don't read identically. */
.band--burgundy::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    linear-gradient(122deg, rgba(124,45,72,0.08) 0%, transparent 44%, rgba(28,139,124,0.06) 100%),
    radial-gradient(44% 50% at 94% 4%, rgba(28,139,124,0.22), transparent 64%),
    radial-gradient(36% 42% at 4% 98%, rgba(124,45,72,0.19), transparent 62%),
    radial-gradient(rgba(124,45,72,0.13) 1px, transparent 1.4px);
  background-size: auto, auto, auto, 22px 22px;
}
.band--burgundy > .shell { position: relative; z-index: 1; }
.band--burgundy ::selection { background: rgba(124,45,72,0.20); color: var(--ink); }
.band--burgundy .h-display em { color: var(--burgundy); }
.band--burgundy .bezel__core {
  background: var(--wash);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.9), 0 30px 60px -28px rgba(124,45,72,0.16);
}

/* ============================================================================
   15a3 · THE CLOSING CTA — shared chassis  (2026-08-19 · the big one)
   ----------------------------------------------------------------------------
   THE COMPLAINT THIS ANSWERS, in the owner's words: every page ended with the
   same two screens. A filler section, then a `*-cta` band, then the navy
   footer — and every `*-cta` was the same object: texture div, halo blob, the
   512px seal marooned in an empty cream corner, a mono eyebrow reading
   "15 minutes, no slides" VERBATIM on 9 of 12 pages, an italic serif H2 with
   one teal accent word, one paragraph, the same green call button, "or text
   (519) 761-2251", and a pager. Measured heights before this block: 757px to
   1166px, for about five lines of content. The phone number then appeared
   three or four times inside the final 1500px, because the footer immediately
   repeated the line and a second Book-a-call pill.

   THE SPLIT: the bespoke half — what each closer actually SAYS, and why it
   only makes sense on that page — belongs to the page agents. This block owns
   the half that is genuinely shared: how much room the thing takes, what the
   seal does, and whether the footer repeats the ask underneath it.

   WHY THESE SELECTORS. The closers are page-scoped classes (.cta, .ab-cta,
   .pl-cta, .dm-cta, .wsp-cta, .rx-cta, .au-cta, .mk-cta, .vs-cta, .wk-cta,
   .bl-cta, .lg-cta, .pa-cta) declared in per-page <style> blocks the browser
   reads AFTER this sheet, so document order loses. `section.cta` and
   `section[class*="-cta"]` are both (0,1,1) and beat a bare (0,1,0) page rule
   on specificity — the same manoeuvre as §17/§18. Scoping to `section` is what
   keeps `.hero-cta` / `.hdr-cta` / `.final-cta` rows (all divs) out of it.
   ========================================================================== */

/* --- 1 · HEIGHT ------------------------------------------------------------
   Was `calc(var(--section-y) * 1.05)` (and *1.15 on home): 151–166px of padding
   on EACH side at 1440, 109–120px on each side at 390. A third of a screen of
   nothing, twice, on the way to a footer that says the same thing again.
   Roughly halved. Written through a `--cta-y` custom property so a page that
   genuinely needs a different rhythm can set ONE variable on its own section
   (`--cta-y: 7rem`) and keep it, instead of losing a `padding-block` fight it
   cannot win against this selector's specificity. */
section.cta,
section[class*="-cta"] { padding-block: var(--cta-y, clamp(3.4rem, 5.2vw, 5.6rem)); }

/* GROUND FOR THE CLOSER. Every page's closing section used to end on flat
   --paper (or a flat band colour) with nothing behind the seal and the
   headline -- the exact "too much white, no art" the owner flagged
   sitewide. A single ::before here reaches every closing section on every
   page with zero per-page markup: teal/deep-teal/burgundy wave bands (the
   business-card palette), masked to gather weight at the FOOT of the
   section and clear near the headline so it reads as a floor, not a wash
   over the copy. Sits at z-index 0, same tier as the seal and BELOW the
   `> .shell` guard above, so it can never land over readable text.
   `.band--deep`/`.band--burgundy` sections switch to a screen blend so the
   bands still read as colour-on-navy instead of navy-on-navy disappearing. */
section.cta::before,
section[class*="-cta"]::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 340' preserveAspectRatio='xMidYMax slice'><defs><linearGradient id='p1' x1='0' y1='0' x2='1' y2='1'><stop offset='0' stop-color='%231C8B7C' stop-opacity='.32'/><stop offset='1' stop-color='%231C8B7C' stop-opacity='.10'/></linearGradient><linearGradient id='p2' x1='0' y1='0' x2='1' y2='1'><stop offset='0' stop-color='%23146E62' stop-opacity='.34'/><stop offset='1' stop-color='%2316263F' stop-opacity='.12'/></linearGradient><linearGradient id='p3' x1='0' y1='0' x2='1' y2='1'><stop offset='0' stop-color='%237C2D48' stop-opacity='.30'/><stop offset='1' stop-color='%2398354F' stop-opacity='.10'/></linearGradient></defs><path d='M1200 40 C980 90,900 30,700 96 C520 154,420 110,240 168 C130 204,60 190,-20 220 L-20 340 L1200 340 Z' fill='url(%23p1)'/><path d='M1200 130 C1000 176,880 120,690 186 C520 244,400 200,220 254 C120 284,60 274,-20 296 L-20 340 L1200 340 Z' fill='url(%23p2)'/><path d='M1200 220 C1020 262,900 214,700 268 C540 312,400 286,220 316 C120 332,60 328,-20 340 L1200 340 Z' fill='url(%23p3)'/></svg>") bottom / cover no-repeat;
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 42%);
          mask-image: linear-gradient(180deg, transparent 0%, #000 42%);
}
.band--deep section.cta::before, .band--deep section[class*="-cta"]::before,
.band--burgundy section.cta::before, .band--burgundy section[class*="-cta"]::before,
section.cta.band--deep::before, section[class*="-cta"].band--deep::before,
section.cta.band--burgundy::before, section[class*="-cta"].band--burgundy::before {
  opacity: 0.6; mix-blend-mode: screen;
}
@media (max-width: 640px) {
  section.cta::before, section[class*="-cta"]::before {
    -webkit-mask-image: linear-gradient(180deg, transparent 24%, #000 58%);
            mask-image: linear-gradient(180deg, transparent 24%, #000 58%);
  }
}

/* The seal below is `position: absolute; z-index: 0`, which paints ABOVE
   non-positioned in-flow content. Lifting the shell out of the tie is what
   guarantees the mark can never land on top of a headline no matter how a page
   lays its closer out — the site has shipped that exact legibility bug before.
   Same pattern as `.band--cream > .shell`. */
section.cta > .shell,
section[class*="-cta"] > .shell { position: relative; z-index: 1; }

/* --- 2 · THE SEAL ----------------------------------------------------------
   `seal-full-512.png` is an OPAQUE dark navy disc, not a line mark. It was
   being drawn at min(220px, 24vw) at 70% opacity, floating in the middle of an
   empty cream corner — a lone object in a void, and on a phone it owned an
   entire otherwise-blank screen of its own between the CTA row and the footer.

   Re-treated as a stamped corner chip: small, tucked HALF OFF the section's
   trailing corner (every closer sets `overflow: hidden`, so it clips to a
   partial disc and reads as a mark pressed into the edge of the panel rather
   than a picture placed in a gap). The offset is derived from --gutter so it
   tracks the page's own margin instead of inventing a second rhythm.

   LEGIBILITY: it is now decoration in a corner, at a size where it cannot
   reach a text column, and the rule above puts every .shell above it anyway.
   Verified at 390 and 1440 on all 23 pages that carry it — zero overlap with
   any text box, so the "watermark behind the headline" contrast risk this
   component has had before cannot recur. */
.cta-seal {
  position: absolute;
  right: calc(var(--gutter) * -0.45);
  bottom: calc(var(--gutter) * -0.45);
  width: clamp(72px, 8vw, 124px); aspect-ratio: 1;
  opacity: 0.5;
  pointer-events: none; z-index: 0; user-select: none;
}
/* a dark disc on a navy ground has almost nothing to say at 50% — the same
   mark needs more presence on .band--deep to register at all */
.band--deep .cta-seal { opacity: 0.8; }

/* --- 3 · THE FOOTER'S DUPLICATE ASK ----------------------------------------
   The footer closes with "Built and answered by the person you actually talk
   to." and a second navy Book-a-call pill — a few hundred pixels under the
   closer's own primary button. Two identical asks that close together do not
   double the conversion, they halve the meaning of both.

   This is expressible in CSS after all, without a per-page modifier and
   without touching the canonical footer: `:has()` asks the document whether
   the page ALREADY ends with its own CTA section, and only then demotes the
   footer's pill to a plain underlined link. Pages with no closer of their own
   (/faq/, /book/, /teardown/, and any page whose bespoke closer does not carry
   a `-cta` section class) keep the full pill, which is the safe default.
   `body.has-closing-cta` is honoured as a manual override for a page agent
   whose new closer is named something else entirely.

   Specificity: :has() takes its most specific argument, so
   `body:has(section.cta)` is (0,1,2) and the whole selector (0,2,2) — enough
   to beat `.band--deep .btn--ink` (0,2,0) without !important.
   :has() is already load-bearing in this file (§2 `.section:has(+ .section)`);
   a browser too old for it simply keeps today's two pills. */
body:has(section.cta) .footer__cta,
body:has(section[class*="-cta"]) .footer__cta,
body.has-closing-cta .footer__cta {
  background: none;
  color: var(--ink);
  padding: 0.4rem 0;
  border-radius: 0;
  border-bottom: 1px solid var(--line);
  box-shadow: none;
  filter: none;                       /* the §16 AI-FEEL drop-shadow on .btn--ink */
  font-size: 14.5px; font-weight: 500;
  gap: 8px; min-height: 44px;
}
body:has(section.cta) .footer__cta .ic,
body:has(section[class*="-cta"]) .footer__cta .ic,
body.has-closing-cta .footer__cta .ic {
  width: auto; height: auto; background: none; border-radius: 0;
}
body:has(section.cta) .footer__cta:hover,
body:has(section[class*="-cta"]) .footer__cta:hover,
body.has-closing-cta .footer__cta:hover {
  color: var(--glow); border-bottom-color: var(--brand); filter: none;
}

/* 15a4 · .orb-field — fluid, water-like colour blobs behind a dark section.
   Two animations run on each orb at once: one morphs border-radius through
   organic blob shapes (the "fluid" read), the other drifts position/scale.
   transform + border-radius + opacity only (GPU-friendly, no layout thrash).
   Give the host element position:relative and overflow:hidden/clip so the
   blur doesn't bleed past the section edge. Bumped from a first pass that
   was too faint and too static to register as "moving" at a glance —
   opacity roughly doubled, blur pulled in so edges stay legible as blobs
   instead of dissolving into pure haze, cycles shortened so the motion is
   visible without watching for it. */
.orb-field { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; }
.orb-field .orb { position: absolute; filter: blur(42px); will-change: transform, border-radius; mix-blend-mode: screen; }
.orb-field .orb--burgundy {
  width: 44vw; height: 44vw; max-width: 600px; max-height: 600px; left: -14%; top: -12%;
  background: radial-gradient(circle at 35% 35%, var(--burgundy-2, var(--burgundy)) 0%, var(--burgundy) 45%, transparent 72%);
  opacity: 0.6; animation: orbDrift1 18s ease-in-out infinite, orbMorph1 11s ease-in-out infinite;
}
.orb-field .orb--teal {
  width: 38vw; height: 38vw; max-width: 520px; max-height: 520px; right: -10%; top: 18%;
  background: radial-gradient(circle at 60% 40%, #7FE0D0 0%, var(--glow) 45%, transparent 72%);
  opacity: 0.55; animation: orbDrift2 22s ease-in-out infinite, orbMorph2 13s ease-in-out infinite;
}
.orb-field .orb--green {
  width: 32vw; height: 32vw; max-width: 440px; max-height: 440px; left: 30%; bottom: -16%;
  background: radial-gradient(circle at 45% 55%, #8BEE5C 0%, #5FD433 45%, transparent 72%);
  opacity: 0.42; animation: orbDrift3 26s ease-in-out infinite, orbMorph3 15s ease-in-out infinite;
}
@keyframes orbDrift1 { 0%,100% { transform: translate(0,0) scale(1); } 50% { transform: translate(9%, 11%) scale(1.14); } }
@keyframes orbDrift2 { 0%,100% { transform: translate(0,0) scale(1); } 50% { transform: translate(-11%, -8%) scale(0.9); } }
@keyframes orbDrift3 { 0%,100% { transform: translate(0,0) scale(1); } 50% { transform: translate(7%, -10%) scale(1.16); } }
/* four organic blob silhouettes, cycled — the actual "fluid" motion */
@keyframes orbMorph1 {
  0%,100% { border-radius: 58% 42% 63% 37% / 41% 56% 44% 59%; }
  33%     { border-radius: 43% 57% 39% 61% / 62% 38% 65% 35%; }
  66%     { border-radius: 65% 35% 52% 48% / 35% 63% 40% 60%; }
}
@keyframes orbMorph2 {
  0%,100% { border-radius: 47% 53% 60% 40% / 60% 45% 55% 40%; }
  33%     { border-radius: 62% 38% 41% 59% / 38% 62% 35% 65%; }
  66%     { border-radius: 39% 61% 57% 43% / 65% 40% 60% 35%; }
}
@keyframes orbMorph3 {
  0%,100% { border-radius: 52% 48% 45% 55% / 55% 45% 60% 40%; }
  33%     { border-radius: 60% 40% 63% 37% / 40% 60% 38% 62%; }
  66%     { border-radius: 44% 56% 39% 61% / 63% 37% 55% 45%; }
}
@media (prefers-reduced-motion: reduce) { .orb-field .orb { animation: none !important; border-radius: 50% !important; } }

/* ----------------------------------------------------------------------------
   15b · .band--deep — NAVY dark-guest context (the one dark band per page:
   a hero band, stat strip, or footer). Remaps every token to the navy ground
   with brighter accents so shared components (eyebrow, h-display, bezel, buttons,
   ledger, footer) restyle automatically. Use SPARINGLY — one per page.
   --------------------------------------------------------------------------- */
.band--deep {
  --paper:      #16263F;                    /* navy ground */
  --paper-2:    #1F3253;
  --surface:    #1F3253;                    /* lifted navy card */
  --wash:       #1F3253;
  --wash-deep:  #142440;
  --ink:        #F3F5F8;                    /* light text on navy */
  --muted:      #AEB9CB;
  /* LIFTED AGAIN 2026-08-19, #8C96A8 → #A3ADBE (5.1:1 → 6.7:1 on --paper navy).
     5.1:1 passed the AA floor on paper but the design review still read every
     mono micro-label on a dark ground as "40–50% opacity greyed-out text" — and
     it was right in spirit: #8C96A8 is #F3F5F8 at an effective ~54% over navy,
     and these labels (the footer's TAKING NEW BUILDS status and coordinates,
     section h4s, the home shop band's "Priced separately…" foot, every
     .stack-card__meta and .next-hook .step inside a dark band) are read, not
     decoration. #A3ADBE is the same hue at ~65%, which is the ">= 60%" the
     review asked for, still visibly quieter than --muted (7.7:1) and far
     quieter than --ink (13.9:1) so the hierarchy is untouched.
     One token edit repairs every dark-band instance at once — which is the
     whole reason the ground contexts own these values instead of the pages. */
  --muted-2:    #A3ADBE;
  --muted-aa:   #A3ADBE;   /* --muted-aa means "the quiet grey that clears AA on THIS ground". On navy that is
                              #A3ADBE (6.7:1); the light-ground #5F6879 would be near-invisible here. Redefining
                              it once keeps every --muted-aa component ground-aware without per-rule overrides. */
  --line:       rgba(243,245,248,0.14);
  --line-soft:  rgba(243,245,248,0.07);
  /* brighter accents on the dark ground */
  --teal:       #3FBFAD;
  --teal-deep:  #3FBFAD;
  --brand:      #2FB0A0;
  --glow:       #4CCBB8;
  --burgundy:   #C05576;
  --burgundy-2: #D06A88;
  --accent:     #C05576;
  --accent-body:#C05576;
  --brand-glow:  color-mix(in oklab, var(--glow) 40%, transparent);
  --brand-raise: color-mix(in oklab, var(--brand) 86%, #ffffff 14%);
  --accent-dim:  color-mix(in oklab, var(--accent) 20%, transparent);
  --glass:       rgba(243,245,248,0.05);
  --glass-line:  rgba(243,245,248,0.14);
  --scrim:       11,17,27;                  /* navy-black for scrims on dark */
  /* ATMOSPHERE ON NAVY. Same ration the light bands follow -- two blooms
     (teal + burgundy) and a fine dot grid, never a third hue -- tuned down
     for a dark ground where the same alphas would read as smears. The
     burgundy bloom is the point: it is what stops long navy runs reading as
     one flat colour, and it puts warmth on pages (/faq/, /book/) that carry
     no .band--burgundy section at all. */
  background:
    radial-gradient(52% 46% at 88% 3%, rgba(63,191,173,0.11), transparent 62%),
    radial-gradient(46% 42% at 5% 97%, rgba(192,85,118,0.12), transparent 64%),
    radial-gradient(rgba(243,245,248,0.05) 1px, transparent 1.4px),
    var(--paper);
  background-size: auto, auto, 26px 26px, auto;
  color: var(--ink);
}
.band--deep ::selection { background: rgba(76,203,184,0.28); color: var(--ink); }
.band--deep .bezel__core {
  background: var(--surface);
  box-shadow: inset 0 1px 0 rgba(243,245,248,0.06), 0 30px 60px -24px rgba(0,0,0,0.5);
}
/* green stays the attention CTA on navy; navy .btn--ink flips to a light fill */
.band--deep .btn--ink { background: var(--ink-inv); color: var(--navy); }
.band--deep .btn--ink .ic { background: rgba(20,26,36,0.12); }

/* ----------------------------------------------------------------------------
   16 · PERFORMANCE + REDUCED-MOTION GUARDRAILS (do not remove)
   · animate transform/opacity/filter only · backdrop-blur on fixed/sticky only
   · grain is one fixed pointer-events-none layer · DPR capped in motion engine
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .marquee__track { animation: none; }
  .marquee { -webkit-mask-image: none; mask-image: none; overflow-x: auto; }
}

/* ============================================================
   AI-FEEL GLOW ACCENTS (additive · safe · all pages) — 2026-07-20
   Subtle premium glow signalling "the AI is live". Filter/text-shadow/
   hover only; no layout changes; reduced-motion safe.
   ============================================================ */
@keyframes bd-livepulse { 0%,100% { box-shadow: 0 0 0 0 rgba(63,191,173,.55); } 50% { box-shadow: 0 0 0 5px rgba(63,191,173,0); } }
@media (prefers-reduced-motion: no-preference) {
  .nav-pill__live-dot, [class*="live-dot"], [class*="__live"] { animation: bd-livepulse 2.4s ease-in-out infinite; }
}
.btn--call, .btn--ink, .btn--primary, .nav-pill__cta {
  filter: drop-shadow(0 6px 18px rgba(30,138,124,.28));
  transition: filter .25s cubic-bezier(.32,.72,0,1), transform .2s cubic-bezier(.32,.72,0,1);
}
.btn--call:hover, .btn--ink:hover, .btn--primary:hover, .nav-pill__cta:hover {
  filter: drop-shadow(0 10px 26px rgba(63,191,173,.5));
}
h1 em, h2 em, .hero-em, .accent, [class*="__accent"] { text-shadow: 0 0 28px rgba(63,191,173,.26); }
.bezel { transition: box-shadow .3s cubic-bezier(.32,.72,0,1), transform .3s cubic-bezier(.32,.72,0,1), border-color .3s ease; }
.bezel:hover { box-shadow: 0 0 0 1px rgba(63,191,173,.25), 0 24px 60px -24px rgba(30,138,124,.45); transform: translateY(-2px); }
@media (prefers-reduced-motion: reduce) { .bezel:hover { transform: none; } }

/* ============================================================================
   17 · BLOG POST HEADER — readable meta (repairs all 9 /blog/<slug>/ pages)
   ----------------------------------------------------------------------------
   The .pa-* header styles live in a <style> block inside each post, which the
   browser reads AFTER this stylesheet — so these overrides carry one extra
   class (.pa-hero, which wraps all of them) to win on specificity rather than
   order. Nine files, one defect each; fixing it here fixes all nine at once and
   keeps any post generated later correct by default.

   What was wrong (measured on painted pixels, not declared values):
     a.pa-back / .pa-meta   var(--muted-2) #8A93A0 → 2.87:1 on --paper
     span.pa-meta__cat      var(--brand)   #1C8B7C → 3.85:1 on --paper
   Both are the two failure patterns the token block already names: --muted-2 is
   DECORATIVE ONLY (faint mono numbering), and the teal is too light at small
   sizes. The repair is the token that exists for exactly this — --muted-aa
   (5.2:1 on paper) and --teal-deep (5.7:1), both of which are STILL quieter
   than the deck (--muted 5.7:1) and far quieter than the headline (--ink ~15:1).

   DELIBERATELY LEFT ALONE — these are decoration, not text:
     .pa-meta__dot   3px aria-hidden separator painted in --muted-2
     .pa-hero__halo  radial teal wash behind the headline
   The byline LINK ("Hunter Ballantyne") already sits at --muted (5.7:1) and is
   untouched: the hierarchy headline → deck → meta, and the "the name is the
   link" cue inside the meta line, both survive this change intact.
   ========================================================================== */
.pa-hero .pa-back          { color: var(--muted-aa); }
.pa-hero .pa-back:hover    { color: var(--teal-deep); }
.pa-hero .pa-meta          { color: var(--muted-aa); }
.pa-hero .pa-meta a:hover  { color: var(--teal-deep); }
.pa-hero .pa-meta__cat     { color: var(--teal-deep); }

/* ==========================================================================
   §18 · BLOG POST PROSE LINKS — the same too-light-teal defect as §17, one
   level down the page. .prose p a / .prose li a paint --glow #1C8B7C, which
   is 3.85:1 on --paper: under AA for body copy, and this is the most-read
   text on the site. --teal-deep #146E62 is the file's small-text-safe teal
   (5.7:1) and is already what §17 uses for the meta category and hovers, so
   the post reads as one system rather than two greens.

   Specificity note: the post styles live in a per-page <style> block the
   browser reads AFTER this sheet, so document order loses. Their rule is
   `.prose p a, .prose li a` = (0,1,2); `.pa-article .prose a` = (0,2,1)
   wins on class count. All 9 /blog/<slug>/ posts carry .pa-article.

   The underline is kept and its colour tracks the text at 55% so the link
   still reads as a link, just at a weight the eye can actually resolve.
   ========================================================================== */
.pa-article .prose a        { color: var(--teal-deep); text-decoration-color: rgba(20,110,98,0.55); }
.pa-article .prose a:hover  { text-decoration-color: var(--teal-deep); }

/* ==========================================================================
   CHAPTER HOOK — the page-to-page connective tissue.

   The site is meant to read as ONE numbered story: 01 home -> 02 the AI ->
   03 the website -> 04 pricing -> 05 the work -> 06 book -> 07 about, with
   the side chapters (demo, vs, faq, blog, marketing, automation) feeding
   back into the spine. Every page ends by handing the visitor forward.

   This component was authored four separate times as .next-hook (home),
   .wk-hook (work), .pl-hook (pricing) and .wsp-hook (website) — identical
   markup and near-identical rules, each scoped to one page. That is why the
   other seven pages simply had no hook: there was no shared component to
   reach for. It lives here now so any page can drop in the markup and get
   the chapter hook for free.

   Pure tokens, so it inverts correctly inside .band--deep (navy) without a
   second rule. The trailing arrow is the nested button-in-button treatment
   used across the site, not a naked glyph.
   ========================================================================== */
.next-hook__wrap { margin-top: clamp(2.4rem, 5vh, 3.4rem); display: flex; justify-content: center; }
.next-hook {
  display: inline-flex; align-items: center; gap: 16px;
  padding: 14px 16px 14px 22px; border-radius: 999px;
  background: var(--glass); border: 1px solid var(--line);
  transition: border-color var(--dur-fast) var(--ease-out-quart),
              background var(--dur-fast) var(--ease-out-quart),
              transform var(--dur-fast) var(--ease-out-quart);
}
.next-hook:hover { border-color: rgba(28,139,124,0.4); background: rgba(28,139,124,0.07); transform: translateY(-1px); }
.next-hook .step { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted-2); }
.next-hook .lbl  { font-size: 14.5px; color: var(--ink); font-weight: 500; }
.next-hook .lbl b { color: var(--teal-deep); font-weight: 600; }
.next-hook .arr {
  width: 2rem; height: 2rem; border-radius: 999px; flex: none; display: grid; place-items: center;
  background: rgba(28,139,124,0.12); color: var(--teal-deep);
  transition: transform var(--dur-fast) var(--ease-out-quart);
}
.next-hook:hover .arr { transform: translateX(3px); }

/* PHONE: ONE ROW, ALWAYS (2026-08-19).
   `flex-wrap: wrap` was the old mobile rule and it is what broke this pill.
   Once the label wrapped, the 32px arrow circle had nowhere to go but a third
   row of its own, and the capsule inflated into a tall, mostly-empty lozenge:
   measured at 390px before this change — /products/marketing/ 99px tall,
   /demo/ and /faq/ 125px, /book/ 148px, against 62px on desktop. A pager pill
   that is taller than a paragraph has stopped being a pill.

   So: never wrap. The three children hold one row — the mono step and the
   arrow are both `flex: none` (they have exactly one right size), and the
   label is the only flexible one, with `min-width: 0` so it is allowed to
   shrink and wrap its TEXT instead of pushing its siblings off the row.
   min-width:0 is the load-bearing declaration here: a flex item's automatic
   minimum size is its content, so without it the label refuses to go below its
   longest line and shoves the arrow out — the same class of bug as the
   `width: max-content` trap in the house rules.

   SPECIFICITY: five pages (book, demo, products/ai-chat, …) ship their own
   copy of `.next-hook` in a per-page <style> block the browser reads AFTER this
   sheet, including their own `flex-wrap: wrap`. `a.next-hook` (0,1,1) beats
   `.next-hook` (0,1,0) on specificity rather than order, so this holds without
   editing five files. Same trick as §17. */
@media (max-width: 767px) {
  a.next-hook {
    display: flex; flex-wrap: nowrap; align-items: center;
    gap: 12px; padding: 11px 11px 11px 16px;
    max-width: 100%;
  }
  a.next-hook .step { flex: none; font-size: 9.5px; letter-spacing: 0.12em; }
  a.next-hook .lbl  { flex: 1 1 auto; min-width: 0; font-size: 13.5px; line-height: 1.3; }
  a.next-hook .arr  { flex: none; width: 1.75rem; height: 1.75rem; }
}
/* The mono step STAYS on phones. It is the pill's whole identity — "NEXT · 06"
   is what makes it a pager rather than a second button — and at 9.5px/0.12em it
   costs ~66px of a 324px content row, which the label can afford. */
/* Motion is a nicety; the hook must never move for someone who asked it not to. */
@media (prefers-reduced-motion: reduce) {
  .next-hook, .next-hook .arr { transition: none; }
  .next-hook:hover { transform: none; }
  .next-hook:hover .arr { transform: none; }
}

/* ==========================================================================
   §19 · MONO MICROCOPY ON A DARK GROUND — the alpha trap, one more time
   --------------------------------------------------------------------------
   The site-wide lesson is already written at the top of this file, under
   --eyebrow-on-dark: on a photographic or diagonally-scrimmed hero the ALPHA
   is the defect, not the hue. A near-white at 0.44–0.48 alpha dissolves the
   moment the ground crosses ~rgb(110), and the home hero is a dawn video whose
   sky does exactly that. Measured on painted pixels at 1440: "Ontario · Canada"
   1.17:1 and "Scroll" 1.07:1 against the brightest ground they cross — i.e.
   invisible, not merely quiet.

   Repaired from here rather than in index.html for the same reason §17 repairs
   nine blog posts from here: this is the shared CONTRACT (solid token + halo)
   being applied, not a bespoke value, and the page's own <style> block is read
   after this sheet so `.hero .hero__meta` (0,2,0) has to win on specificity.
   A page agent who later moves these onto the tokens themselves lands on
   exactly the same rendering.
   ========================================================================== */
.hero .hero__meta,
.hero .hero__cue { color: var(--eyebrow-on-dark); text-shadow: var(--eyebrow-halo-dark); }

/* ==========================================================================
   §20 · CARD RAILS — the touch affordance
   --------------------------------------------------------------------------
   `.wsp-builds__rail` (/products/website/) and `.au-carousel__viewport`
   (/products/automation/) ARE horizontally swipeable. Neither said so. Measured
   at 390px before this block:

     .wsp-builds__rail  350px wide · card 335px (86vw) · 16px gap
                        → the next card began at x=371 on a 370px track, i.e.
                          ONE pixel of peek. The rail looked like a single
                          card that happened to be cut off — a layout bug.
     .au-carousel dots  present, but 6px circles painted in var(--line)
                        (12% alpha ink) and pushed to the far right edge:
                        rendered, and invisible.

   Two fixes, both CSS-only:
     1. PEEK. Shrink the card so ~26–32px of the next one is always showing at
        rest. A sliver of a second card is the only affordance a thumb reads as
        "there is more this way" without an instruction.
     2. DOTS. Make them visible where they matter — bigger, in a colour that
        actually paints on cream, and centred instead of hard right.

   MARKUP THIS CANNOT REACH — for the /products/website/ agent, not editable
   from here: `.wsp-builds` has NO dots element at all (its only pagination is
   `.wsp-builds__nav`, two chevron <button>s that are `display:none` below
   767px and `aria-hidden`). It needs the same `<div class="au-carousel__dots">`
   -style indicator that automation has, or the chevrons brought back at a
   44px touch size. The peek below is the half that CSS can deliver today.

   Specificity: page rules are (0,1,0); `.wsp-builds__rail .wsp-build` and
   `.au-carousel .au-carousel__dots span` are (0,2,0) and (0,3,0).
   ========================================================================== */
@media (max-width: 767px) {
  /* 78vw + a 16px gap inside a 350px track leaves ~30px of the next card */
  .wsp-builds__rail .wsp-build { flex-basis: 78vw; }
  /* the rail is a real scroller, so the peeking card must be allowed to leave
     the shell — it already is (overflow-x: auto); this only guarantees the
     last card can be scrolled fully into view rather than ending flush */
  .wsp-builds__rail { scroll-padding-inline: 0; }

  /* /products/automation/ already peeks ~32px of the next card (an 82vw card in
     a 390px viewport with a 17.6px gap) — measured, so its card width is left
     alone. Its miss was the indicator: 6px circles painted in var(--line), a
     12%-alpha ink that does not survive being six pixels across, and pushed to
     the far right edge of the row. Bigger, darker, centred.
     `.au-carousel__nav` is a SIBLING of `.au-carousel`, not a child (the nav
     sits in the shell while the viewport is full-bleed), so these are
     element-qualified rather than descended from `.au-carousel` — which is
     what an earlier pass got wrong, and the rules simply never matched. */
  div.au-carousel__nav { justify-content: center; }
  div.au-carousel__dots { gap: 9px; }
  div.au-carousel__dots span {
    width: 9px; height: 9px;
    background: color-mix(in oklab, var(--ink) 28%, transparent);
  }
  div.au-carousel__dots span.is-active { background: var(--teal-deep); transform: scale(1.3); }
}

/* ============================================================================
   20 · LAYERED DEPTH — bands as stacked paper, not abutting blocks
   ----------------------------------------------------------------------------
   Technique: depth from OVERLAP + SHADOW rather than from colour alone. Until
   now every ground change on this site was a hard seam — two flat fills meeting
   at a line, which reads as a slide transition instead of a composition. Giving
   each band a soft upward shadow and a hairline top edge makes it sit ON the
   section above it, so a page scrolls like layers of paper rather than a stack
   of rectangles.

   Why box-shadow and not a pseudo-element: shadows paint with the element's
   background layer, and non-positioned siblings paint in DOM order, so a band's
   upward shadow naturally falls OVER the section before it with no z-index or
   `position: relative` needed. That matters — .band--deep deliberately has no
   positioning context (adding one would re-anchor every absolutely-positioned
   child inside it across 13 pages).

   Deliberately excluded:
     · the FIRST band after a hero — a shadow there darkens the hero's own
       bottom fade, which several heroes hand-tune pixel by pixel.
     · .footer — it is the bottom of the page; nothing sits below it to cast on.
   ========================================================================== */
.band--cream,
.band--burgundy,
.band--deep {
  box-shadow: 0 -22px 48px -26px rgba(11, 17, 27, 0.30);
}
/* a 1px lit edge along the top of the incoming layer — the cut edge of the
   paper catching light, which is what sells it as a separate sheet */
.band--cream  { border-top: 1px solid rgba(255, 255, 255, 0.55); }
.band--burgundy { border-top: 1px solid rgba(255, 255, 255, 0.42); }
.band--deep   { border-top: 1px solid rgba(243, 245, 248, 0.07); }

/* A hero already ends in its own carefully-tuned fade to the ground below.
   Casting a shadow up onto that fade muddies it, so the first band after a
   hero opts out of both treatments. */
section[class*="hero"] + .band--cream,
section[class*="hero"] + .band--burgundy,
section[class*="hero"] + .band--deep,
.hero + .band--cream,
.hero + .band--burgundy,
.hero + .band--deep {
  box-shadow: none;
  border-top: 0;
}
/* Two consecutive bands of the SAME ground would show a seam where there is no
   real material change — that reads as a rendering artefact, not depth. */
.band--deep + .band--deep,
.band--cream + .band--cream,
.band--burgundy + .band--burgundy {
  box-shadow: none;
  border-top: 0;
}
