/* ==========================================================================
   LITTLEOCEANS ◦ electric maximalist ◦ abyssal dark + bioluminescent acid
   ========================================================================== */

/* ---------- self-hosted webfonts ----------
   Served from our own origin rather than api.fontshare.com / fonts.googleapis.com.
   Two reasons: it removes two render-blocking third-party origins from the critical
   path, and it stops every visitor's IP being handed to Google before they've
   consented to anything. Files in /fonts, refreshed by tools/getfonts.sh.
   Clash Display + Cabinet Grotesk: Fontshare (ITF free licence, self-hosting allowed).
   Archivo Black + IBM Plex Mono: Google Fonts (SIL OFL 1.1). */
@font-face {
  font-family: "Clash Display"; font-style: normal; font-weight: 600; font-display: swap;
  src: url("/fonts/clash-display-600.woff2") format("woff2");
}
@font-face {
  font-family: "Clash Display"; font-style: normal; font-weight: 700; font-display: swap;
  src: url("/fonts/clash-display-700.woff2") format("woff2");
}
@font-face {
  font-family: "Cabinet Grotesk"; font-style: normal; font-weight: 500; font-display: swap;
  src: url("/fonts/cabinet-grotesk-500.woff2") format("woff2");
}
@font-face {
  font-family: "Cabinet Grotesk"; font-style: normal; font-weight: 700; font-display: swap;
  src: url("/fonts/cabinet-grotesk-700.woff2") format("woff2");
}
/* Archivo Black is used by exactly one element, the "Site by" credit, so it is
   latin-only and never preloaded. */
@font-face {
  font-family: "Archivo Black"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("/fonts/archivo-black-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2122, U+2212, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("/fonts/ibm-plex-mono-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("/fonts/ibm-plex-mono-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1E00-1E9F, U+1EF2-1EFF, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "IBM Plex Mono"; font-style: normal; font-weight: 500; font-display: swap;
  src: url("/fonts/ibm-plex-mono-500-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono"; font-style: normal; font-weight: 500; font-display: swap;
  src: url("/fonts/ibm-plex-mono-500-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1E00-1E9F, U+1EF2-1EFF, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --ink: #050810;
  --ink-2: #0a0f1e;
  /* accent trial 2026-07-14: acid ocean blue. Previous acid green:
     --acid: #c6ff3f; --acid-rgb: 198, 255, 63; --cyan: #4fd8ff; */
  --acid: #3ed6ff;
  --acid-rgb: 62, 214, 255;
  --cyan: #5b9dff;
  --spark: #c6ff3f; /* rare accent only: selection, occasional plankton. Never structural. */
  --foam: #f4f2ea;
  --mist: rgba(244, 242, 234, 0.74);
  --hairline: rgba(244, 242, 234, 0.12);
  --display: "Clash Display", "Arial Narrow", sans-serif;
  --body: "Cabinet Grotesk", -apple-system, sans-serif;
  --mono: "IBM Plex Mono", monospace;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  background: var(--ink);
  color: var(--foam);
  font-family: var(--body);
  font-weight: 500;
  font-size: 1.0625rem;
  line-height: 1.6;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--spark); color: var(--ink); }

/* height:auto is load-bearing now that every <img> carries width/height attributes:
   without it, max-width:100% shrinks the width while the attribute pins the height,
   and every screenshot renders squashed. The attributes are what reserve the space
   and stop the page reflowing as images decode. */
img { max-width: 100%; height: auto; display: block; }

.acid { color: var(--acid); }

.hollow {
  color: transparent;
  -webkit-text-stroke: 2px var(--foam);
}

/* ---------- keyboard focus ----------
   One visible ring everywhere, on our terms rather than the browser's. :focus-visible
   means mouse users never see it, so nothing about the look changes for them. */
:focus-visible {
  outline: 2px solid var(--acid);
  outline-offset: 3px;
  border-radius: 1px;
}
/* the acid ring is invisible against an acid-filled button, so invert those two */
.btn-acid:focus-visible, .flip-btn.is-active:focus-visible, .tier-flag:focus-visible {
  outline-color: var(--foam);
}
/* form fields already draw their own focus treatment (border + glow) */
.contact-form input:focus-visible, .contact-form textarea:focus-visible { outline: none; }

/* ---------- skip link ----------
   Every page pins a nav to the top, so without this a keyboard user tabs the header
   again on every single page load. */
.skip-link {
  position: absolute; top: 0; left: 0; z-index: 100;
  transform: translateY(-120%);
  background: var(--acid); color: var(--ink);
  font-family: var(--mono); font-size: 0.8rem; letter-spacing: 0.14em;
  text-transform: uppercase; text-decoration: none;
  padding: 0.9rem 1.4rem;
  transition: transform 0.2s ease;
}
.skip-link:focus { transform: translateY(0); }

/* ---------- grain ---------- */
.noise {
  position: fixed; inset: -50%; z-index: 40; pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  opacity: 0.05;
  animation: grain 8s steps(10) infinite;
}
@keyframes grain {
  0%, 100% { transform: translate(0, 0); }
  20% { transform: translate(-3%, 2%); }
  40% { transform: translate(2%, -3%); }
  60% { transform: translate(-2%, -2%); }
  80% { transform: translate(3%, 3%); }
}

/* ---------- custom cursor ----------
   The native cursor is only hidden once script.js has confirmed it is actually
   driving the replacement (it adds .has-custom-cursor to <body>). Previously this
   was an unconditional `body { cursor: none }` inside the pointer:fine query, which
   left anyone with prefers-reduced-motion — or anyone whose JS failed to load —
   with no pointer at all, since the JS half of the cursor is skipped in both cases.
   Text inputs keep their I-beam regardless: losing the caret affordance in the
   contact form costs more than the effect is worth. */
.cursor { display: none; }
@media (pointer: fine) {
  /* Top of the stack, above the nav (30), grain (40), consent bar (60) and skip
     link (100). It replaces the real pointer, so anything painting over it leaves
     the visitor with no cursor at all — which is exactly what happened over the
     consent banner when this sat at 50. If you add a new layer, keep it below. */
  .cursor { display: none; position: fixed; inset: 0; z-index: 200; pointer-events: none; }
  .has-custom-cursor .cursor { display: block; }
  .cursor-dot, .cursor-ring {
    position: absolute; top: 0; left: 0; border-radius: 50%;
    transform: translate(-50%, -50%);
  }
  .cursor-dot { width: 8px; height: 8px; background: var(--acid); box-shadow: 0 0 12px var(--acid); }
  .cursor-ring {
    width: 38px; height: 38px; border: 1.5px solid rgba(var(--acid-rgb), 0.5);
    transition: width 0.25s, height 0.25s, border-color 0.25s;
  }
  .cursor.is-hot .cursor-ring { width: 64px; height: 64px; border-color: var(--cyan); }
  body.has-custom-cursor { cursor: none; }
  .has-custom-cursor a, .has-custom-cursor button, .has-custom-cursor label { cursor: none; }
}

/* ---------- reveal on scroll ---------- */
.reveal {
  opacity: 0;
  transform: translateY(36px);
  transition: opacity 0.9s cubic-bezier(0.16, 1, 0.3, 1), transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal.in { opacity: 1; transform: none; }

/* ---------- nav ---------- */
.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 30;
  display: flex; align-items: center; justify-content: space-between;
  padding: 1.1rem clamp(1.25rem, 4vw, 3rem);
  background: linear-gradient(to bottom, rgba(5, 8, 16, 0.92), rgba(5, 8, 16, 0));
  transition: background 0.3s ease, border-color 0.3s ease;
  border-bottom: 1px solid transparent;
}
/* Once scrolled, the bar goes solid. The fade-to-transparent gradient is only
   honest over the hero, where the only thing behind it is the plankton canvas.
   Past that it sits at roughly 45% alpha exactly where the links are, so every
   section printed straight through them: the marquee ran across "WHAT WE DO",
   body copy crossed the phone number, the pricing pills landed inside the bar.
   Keep the gradient for the hero, keep the bar opaque for everything else. */
.nav.nav-shrunk {
  background: var(--ink);
  border-bottom-color: var(--hairline);
}
/* The mobile dropdown paints its own panel below the bar, so the bar has to be
   solid whenever it is open too, scrolled or not. */
.nav.nav-open {
  background: var(--ink);
  border-bottom-color: var(--hairline);
}
.nav-mark {
  font-family: var(--display); font-weight: 700; font-size: 1.35rem;
  letter-spacing: 0.02em; color: var(--foam); text-decoration: none;
  display: inline-flex; align-items: center; gap: 1px;
}
.mark-o {
  width: 0.78em; height: 0.78em; border: 0.14em solid var(--acid);
  border-radius: 50%; display: inline-block; position: relative; margin: 0 0.06em;
  flex: none;
  box-shadow: 0 0 14px rgba(var(--acid-rgb), 0.55);
}
.mark-o::after {
  content: ""; position: absolute; inset: -0.14em; border: 0.14em solid var(--acid);
  border-radius: 50%; animation: sonar 2.6s ease-out infinite;
}
@keyframes sonar {
  0% { transform: scale(1); opacity: 0.8; }
  100% { transform: scale(2.4); opacity: 0; }
}
.nav-right { display: flex; align-items: center; gap: 1.1rem; }
.nav-links { display: flex; align-items: center; gap: clamp(1rem, 2.5vw, 2rem); }
.nav-links a {
  color: var(--mist); text-decoration: none; font-family: var(--mono);
  font-size: 0.78rem; letter-spacing: 0.12em; text-transform: uppercase;
  opacity: 0; transform: translateX(18px); visibility: hidden; pointer-events: none;
  transition: color 0.2s, opacity 0.35s ease, transform 0.5s cubic-bezier(0.16, 1, 0.3, 1),
              visibility 0s 0.5s;
}
.nav-links a:hover { color: var(--acid); }
/* links bloom out when the wordmark collapses, or when the burger is opened */
.nav-shrunk .nav-links a, .nav-open .nav-links a {
  opacity: 1; transform: none; visibility: visible; pointer-events: auto;
  transition: color 0.2s, opacity 0.35s ease, transform 0.5s cubic-bezier(0.16, 1, 0.3, 1),
              visibility 0s;
}
/* dealt from the burger: rightmost first */
.nav-shrunk .nav-links a:nth-child(4), .nav-open .nav-links a:nth-child(4) { transition-delay: 0.05s; }
.nav-shrunk .nav-links a:nth-child(3), .nav-open .nav-links a:nth-child(3) { transition-delay: 0.13s; }
.nav-shrunk .nav-links a:nth-child(2), .nav-open .nav-links a:nth-child(2) { transition-delay: 0.21s; }
.nav-shrunk .nav-links a:nth-child(1), .nav-open .nav-links a:nth-child(1) { transition-delay: 0.29s; }

.nav-burger {
  width: 40px; height: 40px; flex: none;
  display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 5px;
  background: none; border: 1px solid var(--hairline); cursor: pointer;
  transition: opacity 0.3s, transform 0.4s cubic-bezier(0.16, 1, 0.3, 1), border-color 0.25s;
}
.nav-burger span {
  display: block; width: 16px; height: 2px; background: var(--foam);
  transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.2s, background 0.2s;
}
.nav-burger:hover { border-color: rgba(var(--acid-rgb), 0.6); }
.nav-burger:hover span { background: var(--acid); }
.nav-open .nav-burger span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-open .nav-burger span:nth-child(2) { opacity: 0; }
.nav-open .nav-burger span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
/* once scrolled, the links live in the bar; the burger bows out */
.nav-shrunk .nav-burger { opacity: 0; transform: scale(0.5); pointer-events: none; }

/* mobile: links become a dropdown panel; burger stays even when scrolled */
@media (max-width: 720px) {
  .nav-links {
    position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; align-items: stretch; gap: 0;
    background: rgba(5, 8, 16, 0.97);
    border-bottom: 1px solid var(--hairline);
    padding: 0.3rem clamp(1.25rem, 4vw, 3rem) 0.9rem;
    /* the whole panel hides until the burger opens it */
    opacity: 0; visibility: hidden; transform: translateY(-10px); pointer-events: none;
    transition: opacity 0.3s ease, transform 0.45s cubic-bezier(0.16, 1, 0.3, 1), visibility 0s 0.45s;
  }
  .nav-open .nav-links {
    opacity: 1; visibility: visible; transform: none; pointer-events: auto;
    transition: opacity 0.3s ease, transform 0.45s cubic-bezier(0.16, 1, 0.3, 1), visibility 0s;
  }
  .nav-links a {
    padding: 0.85rem 0; font-size: 0.8rem;
    border-top: 1px solid rgba(244, 242, 234, 0.06);
    transform: translateY(-10px);
  }
  /* scrolling alone doesn't open the panel on mobile; the burger does */
  .nav-shrunk:not(.nav-open) .nav-links a {
    opacity: 0; transform: translateY(-10px); visibility: hidden; pointer-events: none;
    transition-delay: 0s;
  }
  .nav-shrunk .nav-burger { opacity: 1; transform: none; pointer-events: auto; }
}

/* wordmark collapses into the O on scroll */
.mark-left, .mark-right {
  display: inline-block; overflow: hidden; white-space: nowrap;
  min-width: 0; flex: none;
  transition: width 0.55s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.3s ease,
              filter 0.4s ease, letter-spacing 0.55s cubic-bezier(0.16, 1, 0.3, 1);
}
.mark-left { width: var(--wl, auto); }
.mark-right { width: var(--wr, auto); }
.nav-measure .mark-left, .nav-measure .mark-right {
  width: auto !important; transition: none !important; letter-spacing: inherit !important;
}
.nav-shrunk .mark-left, .nav-shrunk .mark-right {
  width: 0; opacity: 0; filter: blur(6px); letter-spacing: -0.08em;
}
.mark-o { transition: transform 0.55s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.55s; }
.nav-shrunk .mark-o {
  transform: scale(1.35);
  box-shadow: 0 0 24px rgba(var(--acid-rgb), 0.85);
}
.nav-shrunk .mark-o::after { animation-duration: 1.6s; }

/* ---------- hero ---------- */
.hero {
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
  padding: clamp(4.5rem, 9svh, 6rem) clamp(1.25rem, 4vw, 3rem) 4.5rem;
  position: relative;
  overflow: clip;
}
#depth { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; }
/* The soft radial-gradient glow that used to sit here was removed 2026-07-28: it is
   the single most recognisable "AI built this" motif on a dark hero, and we sell
   craft. The plankton canvas and the grain carry the depth instead — both are ours.
   To restore, re-add .hero-glow + @keyframes drift and the markup in index.html. */
.hero > :not(canvas) { position: relative; z-index: 1; }

.hero-eyebrow {
  font-family: var(--mono); font-size: 0.8rem; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--mist);
  display: flex; align-items: center; gap: 0.7rem; margin-bottom: clamp(1.2rem, 2.6svh, 2.2rem);
}
.ping {
  width: 9px; height: 9px; background: var(--acid); border-radius: 50%;
  position: relative; box-shadow: 0 0 10px var(--acid);
}
.ping::after {
  content: ""; position: absolute; inset: -3px; border: 1.5px solid var(--acid);
  border-radius: 50%; animation: sonar 2s ease-out infinite;
}

.hero-title {
  font-family: var(--display); font-weight: 700;
  /* The svh cap went 11 -> 8.8 when the headline gained a fifth line ("YOUR BEDFORD"
     splitting off "BUSINESS"). That line is gone again — BEDFORD came out of the H1,
     see the note in index.html — so the cap goes back to 11 (2026-08-12). It matters
     more than it looks: on a 1440x900 laptop the cap is what sets the size, not the
     vw term, and 8.8svh was rendering the headline at 79px where 8.6vw asks for 124.
     The result was a hero with the type crammed into the left 41% and the right 59%
     left as bare canvas. 4 x 11 x 0.94 is the ratio this layout was drawn for.
     If a fifth line ever comes back, this has to come back down with it. */
  /* The 2.8rem floor became a 2.5rem floor at the same time: below ~465px the floor is
     what sets the size (8.6vw is smaller than it), so five lines at 2.8rem pushed the
     hero CTAs under the fold on a 375x667 phone. Only narrow viewports see this. */
  font-size: min(clamp(2.5rem, 8.6vw, 8.2rem), 11svh);
  line-height: 0.94; letter-spacing: -0.015em;
  text-transform: uppercase;
  margin-bottom: clamp(1.4rem, 3svh, 2.4rem);
}
.hero-title .line { display: block; overflow: hidden; padding-bottom: 0.06em; margin-bottom: -0.06em; }
/* The words are clipped out of view by .line's overflow until they animate, so this
   stagger is literally the page's LCP clock: the headline is the largest paint and
   it does not exist until the last word lands. Timings tightened 2026-07-28 (last
   word in at ~1.19s, was ~1.76s). The effect reads the same; the metric does not. */
.hero-title .word {
  display: inline-block;
  transform: translateY(110%) rotate(3deg);
  animation: surface 0.85s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.hero-title .line:nth-child(1) .word:nth-child(1) { animation-delay: 0.04s; }
.hero-title .line:nth-child(1) .word:nth-child(2) { animation-delay: 0.10s; }
.hero-title .line:nth-child(2) .word:nth-child(1) { animation-delay: 0.16s; }
.hero-title .line:nth-child(2) .word:nth-child(2) { animation-delay: 0.22s; }
.hero-title .line:nth-child(3) .word:nth-child(1) { animation-delay: 0.26s; }
.hero-title .line:nth-child(3) .word:nth-child(2) { animation-delay: 0.30s; }
/* Line 4 is the last one now that BEDFORD has gone from line 3 and the headline is
   four lines rather than five. The old nth-child(5) rule was dropped with it: it
   matched nothing, and left the next person to edit this expecting a line that is
   not in the markup. Last word now lands at 0.34s rather than 0.38s. */
.hero-title .line:nth-child(4) .word:nth-child(1) { animation-delay: 0.34s; }
@keyframes surface {
  to { transform: translateY(0) rotate(0); }
}
.hero-title .acid {
  color: var(--acid);
  text-shadow: 0 0 38px rgba(var(--acid-rgb), 0.45);
}

.hero-sub {
  max-width: 34rem; font-size: clamp(1.05rem, 1.6vw, 1.3rem);
  color: var(--mist); margin-bottom: clamp(1.4rem, 3svh, 2.6rem);
}

.hero-ctas { display: flex; gap: 1rem; flex-wrap: wrap; max-width: 34rem; }
.hero-ctas .btn { flex: 1 1 12rem; text-align: center; }

.btn {
  font-family: var(--mono); font-size: 0.85rem; letter-spacing: 0.14em;
  text-transform: uppercase; text-decoration: none;
  padding: 1.05rem 1.9rem; display: inline-block;
  transition: transform 0.2s, box-shadow 0.2s, background 0.2s, color 0.2s;
}
.btn:hover { transform: translateY(-3px); }
.btn-acid {
  background: var(--acid); color: var(--ink); font-weight: 500;
  box-shadow: 0 0 0 rgba(var(--acid-rgb), 0);
}
.btn-acid:hover { box-shadow: 0 8px 34px rgba(var(--acid-rgb), 0.4); }
.btn-ghost { border: 1px solid var(--hairline); color: var(--foam); }
.btn-ghost:hover { border-color: var(--cyan); color: var(--cyan); }
.btn-big { border: 0; font-size: 1rem; padding: 1.2rem 2.6rem; }

.scroll-hint {
  position: relative;
  display: block;
  align-self: center;
  margin-top: clamp(3rem, 9svh, 5.5rem);
  width: 26px; height: 30px;
  padding: 8px; box-sizing: content-box; /* generous tap target */
}
.scroll-hint span, .scroll-hint::after {
  content: ""; position: absolute; left: 50%; top: 4px;
  width: 12px; height: 12px; margin-left: -6px;
  border-right: 2px solid var(--acid); border-bottom: 2px solid var(--acid);
  transform: rotate(45deg);
  filter: drop-shadow(0 0 6px rgba(var(--acid-rgb), 0.8));
  animation: dive 1.9s ease-in-out infinite;
}
.scroll-hint::after { animation-delay: 0.35s; opacity: 0; }
@keyframes dive {
  0% { opacity: 0; transform: translateY(-6px) rotate(45deg); }
  40% { opacity: 1; }
  100% { opacity: 0; transform: translateY(12px) rotate(45deg); }
}

/* ---------- marquee ---------- */
.marquee-band { border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline); }
.marquee { overflow: hidden; padding: 1.05rem 0; }
.marquee.m2 { border-top: 1px solid var(--hairline); }
.marquee-track {
  display: flex; align-items: center; gap: 2.2rem; width: max-content;
  font-family: var(--display); font-weight: 600; font-size: 1.5rem;
  letter-spacing: 0.04em; white-space: nowrap;
}
.marquee-track i { color: var(--acid); font-style: normal; }
.m1 .marquee-track { animation: slide 52s linear infinite; }
.m2 .marquee-track { animation: slide 60s linear infinite reverse; color: transparent; -webkit-text-stroke: 1px var(--mist); }
.m2 .marquee-track i { color: var(--cyan); -webkit-text-stroke: 0; }
@keyframes slide {
  to { transform: translateX(calc(-50% - 1.1rem)); }
}

/* ---------- sections ----------
   The cap came down 9.5rem -> 7rem on 2026-08-12. At 1440 the old value put 152px
   above and below every section, so 304px of nothing between each pair, on top of
   the section-head margins — several screens of this page were empty top to bottom.
   The homepage was 15,816px on a 390 phone, about 19 full screens. The page is long
   because it has a lot to say; it does not need to be long because of padding. */
section { padding: clamp(4rem, 8vw, 7rem) clamp(1.25rem, 4vw, 3rem); }

.section-head { max-width: 72rem; margin: 0 auto 3.5rem; }
.kicker {
  font-family: var(--mono); font-size: 0.8rem; letter-spacing: 0.24em;
  color: var(--mist); margin-bottom: 1.3rem;
  display: flex; align-items: center; gap: 0.7rem;
}
.kicker .ping { flex: none; }
/* h1 is here because a page whose first section IS the page (/referrals/) needs its
   opening heading to be the h1, not an h2 under nothing. Same treatment either way. */
.section-head h1, .section-head h2, .contact h2 {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(2.6rem, 7vw, 6.2rem); line-height: 0.97;
  text-transform: uppercase; letter-spacing: -0.01em;
}
.section-intro {
  max-width: 38rem; color: var(--mist); margin-top: 1.8rem;
  font-size: clamp(1rem, 1.4vw, 1.2rem);
}

/* ---------- before/after flip ---------- */
/* width follows viewport height (shots are 16:10) so the whole frame + toggle fits on screen */
.flip { max-width: min(80rem, calc((100svh - 14rem) * 1.6)); margin: 0 auto; }
.flip-stage {
  position: relative; overflow: hidden;
  border: 1px solid var(--hairline);
  box-shadow: 0 40px 90px rgba(0, 0, 0, 0.55), 0 0 80px rgba(91, 157, 255, 0.07);
}
/* 16:10 matches the capture ratio. Without it the stage has zero height until the
   before-shot decodes, and everything below it jumps — the one uncontrolled layout
   shift left on the case study pages. */
.flip-img { width: 100%; height: auto; display: block; aspect-ratio: 16 / 10; object-fit: cover; object-position: top; }
.flip-after {
  position: absolute; inset: 0;
  opacity: 0; transform: scale(1.04);
  transition: opacity 0.55s cubic-bezier(0.16, 1, 0.3, 1), transform 0.55s cubic-bezier(0.16, 1, 0.3, 1);
}
.flip[data-state="after"] .flip-after { opacity: 1; transform: scale(1); }
.flip-controls {
  display: flex; justify-content: center; gap: 0;
  margin-top: -1.4rem; position: relative; z-index: 2;
}
.flip-btn {
  font-family: var(--mono); font-size: 0.78rem; letter-spacing: 0.14em;
  padding: 0.9rem 0; width: 9.5rem; white-space: nowrap; text-align: center;
  border: 1px solid var(--hairline);
  background: var(--ink-2); color: var(--mist); cursor: pointer;
  transition: background 0.25s, color 0.25s, border-color 0.25s, box-shadow 0.25s;
}
.flip-btn:first-child { border-right: 0; }
.flip-btn.is-active {
  background: var(--acid); color: var(--ink); border-color: var(--acid);
  font-weight: 500; box-shadow: 0 0 30px rgba(var(--acid-rgb), 0.35);
}
.flip-btn:not(.is-active):hover { color: var(--foam); border-color: rgba(var(--acid-rgb), 0.45); }

/* ---------- case study index cards ---------- */
.cs-index {
  max-width: 80rem; margin: 0 auto;
  padding: 0 clamp(1.25rem, 4vw, 3rem) clamp(4rem, 8vw, 6rem);
  display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.4rem;
}
.cs-card { display: block; text-decoration: none; color: inherit; }
.cs-card-frame {
  display: block; overflow: hidden; border: 1px solid var(--hairline);
  transition: border-color 0.4s;
}
.cs-card-frame img {
  width: 100%; aspect-ratio: 16 / 10; object-fit: cover; object-position: top;
  transition: transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
.cs-card:hover .cs-card-frame { border-color: rgba(var(--acid-rgb), 0.5); }
.cs-card:hover .cs-card-frame img { transform: scale(1.04); }
.cs-card-meta { display: block; padding: 1.1rem 0.2rem 0; }
.cs-card-kicker {
  display: block; font-family: var(--mono); font-size: 0.68rem;
  letter-spacing: 0.2em; color: var(--mist); margin-bottom: 0.5rem;
}
.cs-card-title {
  display: block; font-family: var(--display); font-weight: 700;
  font-size: clamp(1.5rem, 2.6vw, 2.1rem); text-transform: uppercase; line-height: 1;
}
.cs-card-go {
  display: inline-block; margin-top: 0.7rem; font-family: var(--mono);
  font-size: 0.72rem; letter-spacing: 0.16em; color: var(--acid);
  transition: transform 0.3s;
}
.cs-card:hover .cs-card-go { transform: translateX(6px); }

/* ---------- proof strip (homepage) ----------
   Reuses .cs-card / .cs-card-frame / .cs-card-meta from /work/ on purpose: the
   homepage teaser and the archive it links to should look like the same object,
   so a visitor who clicks through lands somewhere recognisable.

   Grid on desktop, scroll-snap strip on phones, one set of markup, no JS. It is
   deliberately not a carousel. Nothing auto-advances, there are no dots and no
   arrows, and no card is hidden behind an interaction: the question this section
   answers is "has he actually built anything", and that is answered by how many
   builds are visible at once, not by one at a time. Auto-rotation would also
   need a pause control to satisfy WCAG 2.2.2, and would be the third moving
   thing in the first screen and a half after the two marquee rows. */
.proof-strip { padding-top: clamp(3rem, 6vw, 4.5rem); padding-bottom: clamp(3rem, 6vw, 4.5rem); }
.proof-head {
  max-width: 72rem; margin: 0 auto clamp(1.6rem, 3vw, 2.2rem);
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
}
/* Both children must refuse to shrink. .kicker is itself a flex row (ping + mono
   text) and mono text with 0.24em tracking has no give in it, so when the head
   ran out of room at 390px flex shrank the kicker instead of wrapping the line,
   and the text slid back underneath the ping - "RECENT" rendered as "ECENT".
   flex: 0 0 auto forces the wrap that flex-wrap was there to provide. */
.proof-head > * { flex: 0 0 auto; }
.proof-head .kicker { margin-bottom: 0; }
.proof-all {
  font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.16em;
  color: var(--acid); text-decoration: none; transition: transform 0.3s;
}
.proof-all:hover, .proof-all:focus-visible { transform: translateX(4px); }

.proof-track {
  max-width: 72rem; margin: 0 auto;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.4rem;
}
/* .cs-card-title is sized for a 2-up archive grid and is far too loud at three
   across in a teaser. The name is a label here, not the headline. */
.proof-card .cs-card-title { font-size: clamp(1.05rem, 1.7vw, 1.35rem); }
.proof-card .cs-card-meta { padding-top: 0.9rem; }
.proof-live { color: var(--acid); font-weight: inherit; }
.proof-note {
  max-width: 72rem; margin: clamp(1.6rem, 3vw, 2.2rem) auto 0;
  color: var(--mist); font-size: 0.95rem;
}

@media (max-width: 1200px) { .proof-track { grid-template-columns: repeat(2, 1fr); } }

@media (max-width: 760px) {
  /* Cards at 85% so the next one peeks past the right edge. The peek is the
     whole mechanism - it is what says "there is more here" without needing
     arrows or dots to say it, and swipe is already the gesture people use.
     At 100% the strip looks like a single static image and nobody scrolls it.

     The track bleeds to both screen edges via matching negative margin and
     padding, so cards can run off the edge while the first one still lines up
     with the kicker above it. scroll-padding-inline repeats the same value so a
     snapped card lands on that line rather than flush to the glass.

     No JS and no tabindex: the cards are anchors, so tabbing through them
     scrolls the container natively, which is what keeps this usable by
     keyboard. Do not swap them for divs with click handlers. */
  .proof-track {
    display: flex; gap: 1rem;
    overflow-x: auto; overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    margin-inline: calc(-1 * clamp(1.25rem, 4vw, 3rem));
    padding-inline: clamp(1.25rem, 4vw, 3rem);
    scroll-padding-inline: clamp(1.25rem, 4vw, 3rem);
    scrollbar-width: none;
  }
  .proof-track::-webkit-scrollbar { display: none; }
  .proof-card { flex: 0 0 85%; scroll-snap-align: start; }
  /* The track is a scroll container, so overflow-y computes to auto rather than
     visible and any vertical overflow inside it becomes a real scroll. The
     cards carry .reveal, which holds them at translateY(36px) until the
     observer adds .in - and the observer's root is the viewport, so the cards
     parked off the right edge never intersect and never clear it. Result: the
     strip scrolled 36px vertically inside itself on a phone, which is not a
     gesture anything on this page asks for. Reported 20 Aug 2026.
     Cancel the reveal here rather than clipping with overflow-y: hidden, which
     would crop the transition and a focus ring. Per-card stagger is invisible
     at one card per screen anyway; the section still fades in as a whole. */
  .proof-track .reveal { opacity: 1; transform: none; transition: none; }
  .proof-card .cs-card-title { font-size: 1.25rem; }
}

/* ---------- case study stats ---------- */
.cs-stats { margin-top: clamp(2rem, 4vw, 3rem); }
.cs-stats dd b {
  display: block; font-family: var(--display); font-weight: 700;
  font-size: clamp(1.5rem, 3vw, 2.2rem); line-height: 1.1; margin-bottom: 0.4rem;
}
.cs-stats dd { font-weight: 400; color: var(--mist); font-size: 0.9rem; }

/* case study subpages: article provides its own top spacing */
.cs-page .cs { padding-top: clamp(9rem, 18svh, 12rem); border-top: 0; }

/* ---------- home work link band ---------- */
.work-band {
  display: block; text-decoration: none; color: inherit;
  border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline);
  padding: clamp(2.5rem, 6vw, 4.5rem) clamp(1.25rem, 4vw, 3rem);
}
.work-band-in { display: block; max-width: 72rem; margin: 0 auto; }
.work-band-kicker {
  display: block; font-family: var(--mono); font-size: 0.78rem;
  letter-spacing: 0.24em; color: var(--mist); margin-bottom: 1rem;
}
.work-band-title {
  display: block; font-family: var(--display); font-weight: 700;
  font-size: clamp(2.6rem, 7.5vw, 6.5rem); line-height: 0.95;
  text-transform: uppercase; letter-spacing: -0.01em;
  color: transparent; -webkit-text-stroke: 2px var(--foam);
  transition: color 0.35s, -webkit-text-stroke-color 0.35s;
}
/* Hover moved from .work-band to .work-band-link: the band is a <section> now,
   so hovering anywhere in the index below would have lit the headline too. */
.work-band-link { display: block; text-decoration: none; color: inherit; }
.work-band-link:hover .work-band-title,
.work-band-link:focus-visible .work-band-title { color: var(--acid); -webkit-text-stroke-color: var(--acid); }
.work-band-arrow { display: inline-block; transition: transform 0.35s; }
.work-band-link:hover .work-band-arrow,
.work-band-link:focus-visible .work-band-arrow { transform: translateX(0.25em); }

/* The .work-index rules lived here until v37 (25 Aug 2026). They styled a text
   list of every build under the SEE THE WORK headline; that list was cut from
   index.html because the proof strip above it already showed the same six names,
   labels and URLs as images within one scroll. Nothing else used the class -
   .svc-index only echoes the row treatment, it never shared it - so the rules
   went with the markup on the next version bump. The reasoning is kept in full
   in the WORK LINK BAND comment in index.html. */


/* ---------- phones comparison ---------- */
.phones { display: flex; justify-content: center; gap: 0; position: relative; }
.phone-col {
  width: min(46%, 240px);
  display: flex; flex-direction: column; align-items: center; gap: 1.1rem;
}
.phone {
  width: 100%; border: 1px solid var(--hairline); border-radius: 22px;
  overflow: hidden; background: var(--ink-2); position: relative;
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.phone img { border-radius: 21px; }
.phone-tag {
  font-family: var(--mono); font-size: 0.66rem; letter-spacing: 0.18em;
  background: rgba(5, 8, 16, 0.85); border: 1px solid var(--hairline);
  padding: 0.3rem 0.65rem; color: var(--mist);
}
.phone-tag.tag-hot { background: var(--acid); border-color: var(--acid); color: var(--ink); font-weight: 500; }
.phone-before { transform: rotate(-5deg) translateX(6%); filter: saturate(0.55) brightness(0.82); z-index: 1; }
.phone-col:last-child { z-index: 2; }
.phone-after {
  transform: rotate(4deg) translateX(-6%) scale(1.08);
  border-color: rgba(var(--acid-rgb), 0.5);
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.6), 0 0 44px rgba(var(--acid-rgb), 0.14);
}
.phones:hover .phone-before { transform: rotate(-7deg) translateX(8%); }
.phones:hover .phone-after { transform: rotate(2deg) translateX(-8%) scale(1.1); }

/* ---------- services ---------- */
.cards {
  max-width: 80rem; margin: 0 auto;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.2rem;
}
@media (max-width: 860px) { .cards { grid-template-columns: 1fr; } }
.card {
  border: 1px solid var(--hairline); padding: 2.6rem 2.1rem 2.4rem;
  position: relative; overflow: hidden; background: var(--ink);
  transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1), border-color 0.4s;
}
.card::before {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(420px circle at var(--mx, 50%) var(--my, 0%), rgba(var(--acid-rgb), 0.09), transparent 65%);
  opacity: 0; transition: opacity 0.4s;
}
.card:hover { transform: translateY(-8px); border-color: rgba(var(--acid-rgb), 0.45); }
.card:hover::before { opacity: 1; }
.card-num {
  font-family: var(--mono); font-size: 0.8rem; color: var(--acid);
  letter-spacing: 0.2em; display: block; margin-bottom: 2.6rem;
}
.card h3 {
  font-family: var(--display); font-weight: 600; font-size: 1.7rem;
  letter-spacing: 0.01em; margin-bottom: 0.9rem;
}
.card p { color: var(--mist); font-size: 1rem; position: relative; }

/* ---------- how it works ---------- */
.how { background: linear-gradient(to bottom, var(--ink), var(--ink-2) 60%, var(--ink)); }
.steps {
  max-width: 72rem; margin: 0 auto; list-style: none;
  display: grid; gap: 0;
}
.step {
  display: grid; grid-template-columns: minmax(90px, 220px) 1fr 1.4fr;
  gap: 2rem; align-items: center;
  padding: 3rem 0; border-top: 1px solid var(--hairline);
}
.step:last-child { border-bottom: 1px solid var(--hairline); }
@media (max-width: 720px) {
  .step { grid-template-columns: 1fr; gap: 0.8rem; padding: 2.4rem 0; }
}
.step-num {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(3.4rem, 8vw, 7rem); line-height: 1;
  color: transparent; -webkit-text-stroke: 2px rgba(var(--acid-rgb), 0.75);
  transition: color 0.4s;
}
.step:hover .step-num { color: var(--acid); -webkit-text-stroke-color: var(--acid); text-shadow: 0 0 40px rgba(var(--acid-rgb), 0.35); }
.step h3 {
  font-family: var(--display); font-weight: 600; font-size: clamp(1.5rem, 3vw, 2.2rem);
  letter-spacing: 0.01em; text-transform: uppercase;
}
.step p { color: var(--mist); max-width: 30rem; }

/* ---------- pricing ---------- */
.pricing { background: linear-gradient(to bottom, var(--ink), var(--ink-2) 60%, var(--ink)); }
.tier { display: flex; flex-direction: column; }
.tier .card-num { margin-bottom: 1.6rem; }
.tier-flag {
  position: absolute; top: 0; right: 0;
  font-family: var(--mono); font-size: 0.64rem; letter-spacing: 0.2em;
  background: var(--acid); color: var(--ink); font-weight: 500;
  padding: 0.38rem 0.85rem;
}
.tier-featured {
  border-color: rgba(var(--acid-rgb), 0.55);
  box-shadow: 0 0 44px rgba(var(--acid-rgb), 0.09);
}
.tier-price { display: flex; flex-direction: column; gap: 0.25rem; margin: 0.3rem 0 1.2rem; }
.tier-price b {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(1.9rem, 3vw, 2.4rem); line-height: 1; text-transform: uppercase;
}
.tier-price span {
  font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--acid);
}
.tier-desc { color: var(--mist); font-size: 1rem; position: relative; }
.tier-points {
  list-style: none; margin: 1.3rem 0 2rem; position: relative;
  display: grid; gap: 0.6rem;
}
.tier-points li { padding-left: 1.4rem; position: relative; color: var(--mist); font-size: 0.95rem; }
.tier-points li::before { content: "◦"; position: absolute; left: 0; color: var(--acid); font-weight: 700; }
.tier .btn { margin-top: auto; text-align: center; position: relative; }
.pricing-note {
  max-width: 80rem; margin: 2.4rem auto 0;
  color: var(--mist); font-size: 1rem;
}
/* The pricing section carries two notes now - why it costs what it costs, then which
   tier to pick. A second full 2.4rem gap read as two unrelated afterthoughts rather
   than one closing passage. */
.pricing-note + .pricing-note { margin-top: 1.1rem; }

/* The 80rem box is what keeps these left-aligned with the card grid above, but a
   four-line paragraph set that wide is a horrible measure to read. Padding rather
   than a narrower max-width, so the box still lines up with the cards while the
   text wraps at a sane 52rem. Collapses to zero once the viewport is the narrower
   of the two.

   The 100% MUST be wrapped in min() with the box width. Percentage padding
   resolves against the CONTAINING BLOCK, not the element, so a bare
   calc(100% - 52rem) measured the full-width .pricing section instead of this
   80rem box: at a 1920 viewport that is 992px of padding and the paragraph
   wraps in a 288px ribbon, and past ~2100px it has no room left at all.
   Reported 18 Aug 2026, present since 11 Aug. min(80rem, 100%) is the box's
   real width, so the subtraction is the one that was always intended. */
.pricing-note-why { padding-right: max(0px, calc(min(80rem, 100%) - 52rem)); }

/* ---------- what a bad site costs ----------
   Deliberately NOT built on .steps, even though the row rhythm is similar. A
   second big outlined 01/02/03 list on the same page as #how reads as a
   template repeating itself, and numbering these would imply a sequence. They
   are three simultaneous costs, not three stages.

   The layout is the argument. Left column is what the owner experiences and is
   set as quietly as it can be while staying readable - mist, small, no weight.
   Right column is what is actually happening, at full brightness with the
   measured figures in acid. The side that looks like nothing is the side that
   costs money, and the contrast between the columns says that before the words
   do.

   Sits directly above .audits, which has padding-top: 0 and its own hairline
   top border, so this section carries its own bottom spacing and stops short. */
.silent { padding-bottom: clamp(3rem, 6vw, 5rem); }
/* Sits in the same 72rem column as .section-head and the rows so its first
   character lines up under the H, then pads the right edge in to keep the
   measure at a readable 52rem. Same trick as .pricing-note-why. A plain
   max-width: 52rem here centres the paragraph inside the wider column and
   leaves it visibly indented against the heading. */
.silent-lead {
  max-width: 72rem; margin: 0 auto clamp(3rem, 6vw, 4.5rem);
  padding-right: max(0px, calc(min(72rem, 100%) - 52rem));
  color: var(--mist); font-size: clamp(1.05rem, 1.6vw, 1.25rem);
}
.silent-rows { max-width: 72rem; margin: 0 auto; list-style: none; }
.silent-row {
  display: grid; grid-template-columns: minmax(150px, 250px) 1fr;
  gap: clamp(1.5rem, 4vw, 3.5rem); align-items: start;
  padding: clamp(2.2rem, 4vw, 3rem) 0; border-top: 1px solid var(--hairline);
}
.silent-row:last-child { border-bottom: 1px solid var(--hairline); }

/* The quiet half. Small, mist, and no display face on purpose: the moment this
   column gets any typographic weight it stops reading as "nothing happened". */
.silent-label {
  display: block; font-family: var(--mono); font-size: 0.7rem;
  letter-spacing: 0.24em; color: rgba(244, 242, 234, 0.45);
  margin-bottom: 0.9rem;
}
.silent-seen p {
  color: var(--mist); font-size: clamp(1rem, 1.5vw, 1.15rem);
  line-height: 1.4; text-wrap: balance;
}
.silent-real h3 {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.3rem, 2.6vw, 1.9rem);
  letter-spacing: 0.01em; text-transform: uppercase;
  margin-bottom: 0.9rem;
}
.silent-real p { color: var(--mist); max-width: 44rem; }

.silent-tally {
  max-width: 72rem; margin: clamp(2.5rem, 5vw, 3.5rem) auto 0;
  font-family: var(--display); font-weight: 500;
  font-size: clamp(1.2rem, 2.4vw, 1.7rem); line-height: 1.35;
  text-wrap: balance;
}
/* tabular-nums is the whole reason this rule exists. Counting 0 -> 99 in a
   proportional face makes every digit a different width, so the sentence after
   the number shuffles left and right ~99 times. With tabular figures the glyphs
   share one advance width and the line is completely still while the number
   moves, which is the difference between a counter and a twitch.

   min-width reserves the space so the line does not reflow, and it has to hold
   the FINAL digit count, not two. tabular-nums only equalises digits against
   each other; it does nothing about the number of digits changing. Counting to
   155 goes 1 digit -> 2 -> 3, and at min-width: 2ch that measured four different
   widths (45.89, 47.53, 48.53, 48.89px) - the sentence shuffled twice on the way
   up. Raise this with the number: 3ch to 999.

   Right-aligned inside that reserved box on purpose. Left-aligned, the slack
   sits between the number and the word "faults" and reads like a typo while the
   count runs. Right-aligned, the slack sits after the previous sentence's full
   stop, where a word space already lives and nobody sees it. */
.tally-num {
  font-variant-numeric: tabular-nums;
  display: inline-block; min-width: 3ch; text-align: right;
}

@media (max-width: 720px) {
  /* Stacks with the quiet cell first so each row still reads "you see this,
     and this is why". A rule between the two halves does the work the column
     gap does on desktop, but at half the hairline's opacity: at full weight it
     matches the row divider above it and the row stops reading as one thing
     split in two, and starts reading as two unrelated blocks. */
  .silent-row { grid-template-columns: 1fr; gap: 1.4rem; }
  .silent-seen {
    padding-bottom: 1.4rem;
    border-bottom: 1px solid rgba(244, 242, 234, 0.06);
  }
}

/* ---------- audit findings (/website-audits/) ----------
   The evidenced version of the homepage's .silent rows, and deliberately not
   built out of them. Three findings appear in both places, so reusing the
   two-column "WHAT YOU SEE" grid would make this read as a reprint of a section
   the visitor has already scrolled past. Grouped by consequence instead, with
   the findings themselves as a numbered list.

   The numbers run 01-11 straight through all three groups rather than restarting
   per group, so the counter lives on .findings and not on .finding-list. That is
   the point of them: the page claims two sections down that report findings come
   numbered so you can forward them to whoever built the site, and this is what
   that looks like. Restarting at 01 three times would just be decoration. */
.findings { max-width: 72rem; margin: 0 auto; counter-reset: finding; }
.finding-group {
  border-top: 1px solid var(--hairline);
  padding: clamp(2rem, 4vw, 2.8rem) 0;
}
.finding-group:last-child { border-bottom: 1px solid var(--hairline); }
.finding-group h3 {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.15rem, 2.2vw, 1.6rem);
  letter-spacing: 0.01em; text-transform: uppercase;
  margin-bottom: clamp(1.2rem, 2.5vw, 1.6rem);
}
/* 26rem, not 21rem, and the number is doing real work. The groups hold four,
   three and four items. auto-fit at 21rem finds room for three columns in the
   72rem measure, which leaves the 4-item groups with a single item stranded
   under column one and drops the reading order to a stagger. 26rem caps it at
   two columns, so 4 sets as a clean 2x2 and only the 3-item group carries an
   orphan - one ragged block instead of two. It also keeps the line length sane:
   thirds of 72rem is a 22rem measure, which is too narrow for findings that run
   to three lines. */
.finding-list {
  list-style: none; display: grid; gap: 1.1rem 3rem;
  grid-template-columns: repeat(auto-fit, minmax(min(26rem, 100%), 1fr));
}
/* min(26rem, 100%) rather than a bare 26rem. A minmax() floor is a hard floor:
   grid will not shrink a track below it and pushes the page sideways instead.
   At 390px the column has 350px to live in, 26rem is 416px, and the whole
   document scrolled 46px wide. Caught at 390 and 768; the 720px query below
   does not save it because it only touches gap and padding. */
.finding-list li {
  position: relative; padding-left: 2.7rem;
  color: var(--mist); font-size: 0.98rem; line-height: 1.5;
}
/* Sits on the text's own baseline-ish (0.25em) rather than top: 0, which floats
   it above the cap height of the first line and reads as a footnote marker. */
.finding-list li::before {
  counter-increment: finding;
  content: counter(finding, decimal-leading-zero);
  position: absolute; left: 0; top: 0.25em;
  font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.1em;
  color: rgba(var(--acid-rgb), 0.75);
}
.findings-note {
  max-width: 72rem; margin: clamp(2.2rem, 4.5vw, 3rem) auto 0;
  padding-right: max(0px, calc(min(72rem, 100%) - 52rem));
  color: var(--mist); font-size: clamp(1rem, 1.5vw, 1.1rem);
}
@media (max-width: 720px) {
  .finding-list { gap: 1rem; }
  .finding-list li { padding-left: 2.3rem; }
}

/* ---------- audits ----------
   Deliberately not a fourth pricing tier: the three tiers are all builds, and
   an audit is a smaller, separate thing. Reuses .tier-price and .tier-points
   so the offer panel reads as part of the same pricing language. */
.audits { padding-top: 0; }
.audit-band {
  max-width: 80rem; margin: 0 auto;
  padding-top: clamp(3rem, 6vw, 4.5rem); border-top: 1px solid var(--hairline);
  display: grid; grid-template-columns: 1.1fr 0.9fr; gap: clamp(2rem, 5vw, 4rem);
  align-items: start;
}
@media (max-width: 860px) {
  .audit-band { grid-template-columns: 1fr; gap: 2.2rem; }
}
.audit-band h2 {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(2rem, 4.4vw, 3.4rem); line-height: 1;
  text-transform: uppercase; letter-spacing: -0.01em;
}
.audit-lead { color: var(--mist); max-width: 34rem; margin-top: 1.5rem; }
.audit-offer {
  border: 1px solid rgba(var(--acid-rgb), 0.4); background: var(--ink-2);
  padding: clamp(1.8rem, 3vw, 2.3rem);
  display: flex; flex-direction: column;
}
.audit-offer .tier-price { margin-top: 0; }
.audit-offer .btn { margin-top: auto; text-align: center; }
.audit-sample-link {
  margin-top: 0.9rem; text-align: center; text-decoration: none;
  font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--mist); transition: color 0.25s;
}
.audit-sample-link:hover, .audit-sample-link:focus-visible { color: var(--acid); }

/* ---------- about ---------- */
.about { position: relative; }
.about-inner { max-width: 72rem; margin: 0 auto; }
.about-big {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.7rem, 3.8vw, 3.2rem); line-height: 1.22;
  letter-spacing: 0.002em;
}
/* Deliberately quiet and deliberately small. .about-big is the argument; this is
   one line of evidence under it, and matching its size would set up a competing
   statement. Capped at 42rem so it stays a single readable measure against the
   72rem column rather than running the full width of the display type above. */
.about-cred {
  margin-top: clamp(1.6rem, 3vw, 2.4rem); max-width: 42rem;
  color: var(--mist); font-size: clamp(1.05rem, 1.6vw, 1.2rem);
}

/* ---------- contact ---------- */
.contact { background: linear-gradient(to bottom, var(--ink), #071224 65%, var(--ink)); }
.contact-form { max-width: 46rem; margin: 0 auto; display: grid; gap: 1.4rem; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1.4rem; }
@media (max-width: 640px) { .field-row { grid-template-columns: 1fr; } }
.contact-form label {
  display: grid; gap: 0.5rem; font-family: var(--mono); font-size: 0.74rem;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--mist);
}
.contact-form input, .contact-form textarea {
  background: rgba(244, 242, 234, 0.04); border: 1px solid var(--hairline);
  padding: 0.95rem 1.05rem; color: var(--foam); font-family: var(--body);
  font-size: 1.05rem; transition: border-color 0.25s, box-shadow 0.25s;
  border-radius: 0; resize: vertical;
}
.contact-form input:focus, .contact-form textarea:focus {
  outline: none; border-color: var(--acid);
  box-shadow: 0 0 0 1px var(--acid), 0 0 30px rgba(var(--acid-rgb), 0.12);
}
.form-status { text-align: center; font-size: 1rem; color: var(--mist); min-height: 1.4em; }
.form-status.is-ok { color: var(--spark); }
.form-alt { text-align: center; color: var(--mist); font-size: 0.95rem; }
.form-alt a { color: var(--acid); }

/* ---------- footer ---------- */
.footer { padding: 4rem clamp(1.25rem, 4vw, 3rem) 2.4rem; border-top: 1px solid var(--hairline); overflow: hidden; }
.footer-mark {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(2.6rem, 10vw, 9.5rem); line-height: 0.95;
  text-align: center; letter-spacing: 0.01em;
  color: transparent; -webkit-text-stroke: 2px rgba(244, 242, 234, 0.28);
  display: flex; align-items: center; justify-content: center;
  transition: -webkit-text-stroke-color 0.5s;
}
.footer-mark:hover { -webkit-text-stroke-color: var(--acid); }
.mark-o.big {
  border-width: 0.09em;
  box-shadow: 0 0 34px rgba(var(--acid-rgb), 0.35);
  margin: 0 0.05em;
}
.mark-o.big::after { animation: sonar 3.4s ease-out infinite; opacity: 0.5; }
/* Flex rather than a fixed 3-column grid: the row carries 3 items on the homepage
   and 4 on pages that also show the "Site by" credit, and the old
   `grid-template-columns: 1fr auto 1fr` silently wrapped once a 4th arrived. */
.footer-meta {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 0.6rem 2rem;
  margin-top: 3rem; font-family: var(--mono); font-size: 0.74rem;
  letter-spacing: 0.14em; color: var(--mist); text-transform: uppercase;
}
@media (max-width: 760px) {
  .footer-meta { justify-content: center; text-align: center; gap: 0.7rem 1.4rem; }
}

/* skip-link target: focusable so focus actually lands, but never draws a ring */
main:focus { outline: none; }

.footer-meta a { color: var(--mist); text-decoration: none; transition: color 0.25s; }
.footer-meta a:hover, .footer-meta a:focus-visible { color: var(--acid); }
/* The legal/nav run grew from 3 links to 7 when the service pages landed (11 Aug 2026).
   At that length it no longer fits beside the copyright and the studio line, so it takes
   its own full-width row under them rather than wrapping mid-list. */
.footer-legal {
  flex-basis: 100%; text-align: center;
  padding-top: 1.1rem; margin-top: 0.4rem;
  border-top: 1px solid var(--hairline);
  line-height: 2.1;
}

/* ---------- cookie consent ----------
   Deleted 13 Aug 2026 along with GA4. The banner, its two buttons and the
   fixed bottom bar are all gone: Cloudflare Web Analytics sets no cookies and
   writes nothing to the device, so there is nothing left to ask about.

   The rule the old banner followed, kept here because it applies to any banner
   that ever comes back: Accept and Decline were the same size, same weight and
   same prominence. A reject that is harder to find than accept is not consent,
   and the ICO has said so. */
/* The shrink rules that used to sit here went with the banner. Worth recording
   what they were for, because it is the strongest argument against ever adding
   another one: on a 360x640 phone the bar was 191px tall and started at y=449,
   which put both hero CTAs (499-628) behind it on first load. The first screen
   ended on a hero sentence cut mid-word with nothing to press, and no amount of
   padding tuning cleared it on a viewport that short. Measured 2026-08-12,
   deleted 13 Aug 2026 when the banner did. Phones got the whole hero back. */
/* Short viewports only: the hero block is vertically centred, so on a 640px-tall
   phone its own internal spacing is what pushes the CTAs down into the consent bar.
   Tightening the gaps rather than the type keeps the headline at full size. */
@media (max-height: 720px) {
  /* padding-top has to stay clear of the 76px nav on its own: the hero centres its
     content, so once the block gets shorter the centring lifts the eyebrow straight
     up behind the wordmark. 5.5rem is the floor that keeps it out. */
  .hero { padding-top: 5.5rem; padding-bottom: 2rem; }
  /* 0.22em wraps "A WEB DESIGN STUDIO IN BEDFORD" onto two lines at 360; 0.13em
     holds it on one. Twenty pixels, and the tracking still reads as a label. */
  .hero-eyebrow { margin-bottom: 0.9rem; letter-spacing: 0.13em; }
  .hero-title { margin-bottom: 0.8rem; }
  /* 1.6 -> 1.45 and a hair off the size: the sub is five lines at this width and
     leading is the cheapest 30px on the screen. */
  .hero-sub { margin-bottom: 0.85rem; font-size: 1rem; line-height: 1.42; }
  .hero-ctas { gap: 0.7rem; }
}

/* ---------- "Site by" credit (reusable — canonical copy in snippets/site-credit.html) ---------- */
.lo-credit{display:inline-flex;align-items:center;gap:.5em;text-decoration:none;color:inherit;}
.lo-siteby{font-size:.72rem;letter-spacing:.06em;color:inherit;opacity:.65;}
.lo-wordmark{font-family:'Archivo Black',Arial Black,sans-serif;font-size:.82rem;letter-spacing:.02em;line-height:1;color:inherit;opacity:.65;transition:opacity .25s ease;}
/* Ring-O: best-fit ellipse ring to Archivo Black's real "O", found by pixel-
   matching against the rendered glyph (glyph box 0.74x0.71em - nearly round).
   Outer 0.76x0.74em, borders .17em top/bottom .23em sides. Empty inline-block's
   baseline is its bottom edge, so it baseline-anchors like a letter; nudged with
   `top`, NOT transform (transforms fight the pulse animation's transform). */
.lo-ring{position:relative;display:inline-block;width:.76em;height:.74em;margin:0 .02em;vertical-align:baseline;top:.027em;}
.lo-ring i{position:absolute;inset:0;border-radius:50%;border-style:solid;border-color:currentColor;border-width:.17em .23em;display:block;}
.lo-ring em{position:absolute;inset:0;border-radius:50%;display:block;transform-origin:50% 50%;}
/* At rest: inherits the footer's muted text colour. On hover/focus: wakes up.
   The colour never changes on hover (guards against host `footer a:hover` rules);
   only opacity and the cyan ring do. */
.lo-credit:hover,.lo-credit:focus-visible{color:inherit;}
.lo-credit:hover .lo-wordmark,.lo-credit:focus-visible .lo-wordmark{opacity:1;}
.lo-credit:hover .lo-ring i,.lo-credit:focus-visible .lo-ring i{border-color:#3FD8FF;}
.lo-credit:hover .lo-ring em,.lo-credit:focus-visible .lo-ring em{border:.05em solid #3FD8FF;animation:lo-pulse 1.8s ease-out infinite;}
.lo-credit:focus-visible{outline:2px solid #3FD8FF;outline-offset:4px;border-radius:2px;}
@keyframes lo-pulse{0%{transform:scale(1);opacity:.7;}100%{transform:scale(2.1);opacity:0;}}
@media (prefers-reduced-motion: reduce){.lo-ring em{animation:none !important;}}

/* Host integration, this site only (never ships to client builds): the footer
   bar is uppercase, and the LO site gets the always-on pulse variant. */
.footer-meta .lo-siteby{text-transform:none;}
.lo-credit--live .lo-ring i{border-color:#3FD8FF;}
.lo-credit--live .lo-ring em{border:.05em solid #3FD8FF;animation:lo-pulse 1.8s ease-out infinite;}

/* ---------- case studies page ---------- */
.cs-hero {
  padding-top: clamp(9rem, 20svh, 13rem);
  max-width: 80rem; margin: 0 auto;
}
.cs-title {
  font-family: var(--display); font-weight: 700;
  font-size: min(clamp(3rem, 9.5vw, 9rem), 16svh);
  line-height: 0.95; text-transform: uppercase; letter-spacing: -0.01em;
}
.cs {
  max-width: 80rem; margin: 0 auto;
  padding: clamp(4rem, 9vw, 7rem) clamp(1.25rem, 4vw, 3rem);
  border-top: 1px solid var(--hairline);
}
/* h1 since 2026-07-28 — the case studies previously opened at h2 and had no h1 at
   all. h2 kept in the selector for the commented-out Insound block below. */
.cs-head h1, .cs-head h2 {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(2.6rem, 7vw, 6.2rem); line-height: 0.97;
  text-transform: uppercase; letter-spacing: -0.01em;
  margin-bottom: 2.5rem;
}
.cs-back {
  display: inline-flex; align-items: center; gap: 0.7rem;
  font-family: var(--mono); font-size: 0.8rem; letter-spacing: 0.24em;
  color: var(--mist); text-decoration: none;
  margin-bottom: 2rem;
  transition: color 0.3s;
}
.cs-back .cs-back-arrow { transition: transform 0.3s cubic-bezier(0.16,1,0.3,1); }
.cs-back:hover, .cs-back:focus-visible { color: var(--acid); }
.cs-back:hover .cs-back-arrow, .cs-back:focus-visible .cs-back-arrow { transform: translateX(-4px); }
.cs-meta {
  display: flex; flex-wrap: wrap;
  gap: 1px; background: var(--hairline);
  border: 1px solid var(--hairline); margin-bottom: clamp(3rem, 6vw, 5rem);
}
.cs-meta > div { flex: 1 1 150px; background: var(--ink); padding: 1.1rem 1.2rem; }
.cs-meta dt {
  font-family: var(--mono); font-size: 0.66rem; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--mist); margin-bottom: 0.4rem;
}
.cs-meta dd { font-weight: 600; font-size: 0.98rem; }
.cs-block { margin: clamp(3rem, 6vw, 5rem) 0; }
.cs-label {
  font-family: var(--mono); font-size: 0.8rem; letter-spacing: 0.24em;
  color: var(--acid); margin-bottom: 1.4rem;
}
.cs-prose { max-width: 44rem; }
.cs-prose p { color: var(--mist); margin-bottom: 1.2rem; font-size: clamp(1rem, 1.4vw, 1.15rem); }
.cs-prose p:last-child { margin-bottom: 0; }
.cs-list {
  list-style: none; margin-top: 2.2rem;
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.6rem 3rem;
}
@media (max-width: 760px) { .cs-list { grid-template-columns: 1fr; } }
.cs-list li { padding-left: 1.6rem; position: relative; color: var(--mist); }
.cs-list li::before { content: "◦"; position: absolute; left: 0; color: var(--acid); font-weight: 700; }
.cs-list strong { color: var(--foam); font-weight: 600; }
.cs-shots { display: grid; grid-template-columns: 1fr 1fr; gap: 1.2rem; margin: clamp(3rem, 6vw, 5rem) 0; }
@media (max-width: 760px) { .cs-shots { grid-template-columns: 1fr; } }
.shot { border: 1px solid var(--hairline); background: var(--ink-2); transition: border-color 0.4s, transform 0.4s cubic-bezier(0.16,1,0.3,1); }
.shot:hover { border-color: rgba(var(--acid-rgb), 0.45); transform: translateY(-6px); }
.shot { display: flex; flex-direction: column; }
.shot img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; object-position: left top; }
.cs-shots-tall .shot img { aspect-ratio: 5 / 4; object-position: center top; }
.shot figcaption { margin-top: auto; }
.shot figcaption {
  font-family: var(--mono); font-size: 0.72rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--mist); padding: 0.9rem 1.1rem;
  border-top: 1px solid var(--hairline);
}
.cs-split { display: grid; grid-template-columns: 1fr 1.2fr; gap: 4rem; align-items: center; }
@media (max-width: 860px) { .cs-split { grid-template-columns: 1fr; } }
.cs-next {
  max-width: 80rem; margin: 0 auto; text-align: left;
  border-top: 1px solid var(--hairline);
}
.cs-next-title {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(2.8rem, 8vw, 7rem); line-height: 0.95;
  text-transform: uppercase; margin-bottom: 1.6rem;
}
.cs-next .btn { margin-top: 1.4rem; }

/* ---------- 404 ----------
   Lifted out of an inline <style> block in 404.html so the site can ship a CSP
   without needing style-src 'unsafe-inline'. */
.lost {
  min-height: 100svh; display: flex; flex-direction: column;
  justify-content: center; align-items: flex-start;
  padding: clamp(1.25rem, 4vw, 3rem); position: relative; overflow: clip;
}
.lost h1 {
  font-family: var(--display); font-weight: 700; text-transform: uppercase;
  font-size: clamp(3rem, 10vw, 9rem); line-height: 0.94; margin-bottom: 1.6rem;
}
.lost p { color: var(--mist); max-width: 30rem; margin-bottom: 2.2rem; font-size: 1.15rem; }

/* honeypot field — must stay in the DOM and out of sight, for bots only */
.is-hidden { display: none; }

/* ---------- legal pages ---------- */
.legal {
  max-width: 48rem; margin: 0 auto;
  padding: clamp(9rem, 20svh, 13rem) clamp(1.25rem, 4vw, 3rem) clamp(5rem, 10vw, 8rem);
}
.legal h1 {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(2.6rem, 8vw, 5.5rem); line-height: 0.95;
  text-transform: uppercase; letter-spacing: -0.01em; margin-bottom: 1.2rem;
}
.legal .legal-updated {
  font-family: var(--mono); font-size: 0.74rem; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--mist); margin-bottom: 3rem;
}
.legal h2 {
  font-family: var(--display); font-weight: 600; font-size: clamp(1.4rem, 3vw, 1.9rem);
  text-transform: uppercase; letter-spacing: 0.01em;
  margin: 3rem 0 1rem; padding-top: 2rem; border-top: 1px solid var(--hairline);
}
.legal h2:first-of-type { border-top: 0; padding-top: 0; }
.legal p, .legal li { color: var(--mist); margin-bottom: 1.1rem; }
.legal ul { margin: 0 0 1.6rem; list-style: none; }
.legal li { padding-left: 1.4rem; position: relative; }
.legal li::before { content: "◦"; position: absolute; left: 0; color: var(--acid); font-weight: 700; }
.legal a { color: var(--acid); }
.legal strong { color: var(--foam); font-weight: 600; }

/* Who we legally are. Required on the site by the business names provisions of the
   Companies Act 2006, and by UK GDPR art.13 as the controller's identity. Same block
   answers both, which is why it lives here rather than on a page of its own. */
.legal .legal-id {
  font-style: normal; border: 1px solid var(--hairline);
  padding: clamp(1.1rem, 3vw, 1.5rem); margin: 0 0 1.6rem;
}
.legal .legal-id .legal-id-label {
  font-family: var(--mono); font-size: 0.7rem; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--acid); margin-bottom: 0.8rem;
}
.legal .legal-id p { margin-bottom: 0.45rem; }
.legal .legal-id p:last-child { margin-bottom: 0; }


/* ---------- work index groups ----------
   /work/ splits into CLIENT WORK and CONCEPTS. Both groups reuse .cs-index for the
   grid, so this only has to handle the headings and the spacing between the two.
   The section padding would otherwise stack to a chasm between groups. */
.cs-group { padding-top: 0; padding-bottom: clamp(2rem, 5vw, 4rem); }
.cs-group-head { max-width: 80rem; margin: 0 auto 2.6rem; }
.cs-group-head h2 {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(1.9rem, 4.4vw, 3.4rem); line-height: 1;
  text-transform: uppercase; letter-spacing: -0.01em;
}
.cs-group-head .section-intro { margin-top: 1.1rem; max-width: 44rem; }

/* A hairline above CONCEPTS does the separating work that a big gap would do less
   clearly, and keeps the two groups reading as one page rather than two. */
.cs-group-concepts { border-top: 1px solid var(--hairline); padding-top: clamp(3rem, 7vw, 5.5rem); }

/* ---------- referrals ---------- */
/* First section on the page sits under the fixed nav, so it needs the same top
   clearance the legal pages give themselves rather than the standard section padding. */
.referral-hero { padding-top: clamp(9rem, 20svh, 13rem); }

/* Two cards, not three. 72rem rather than the 80rem .cards uses, so the pair lines up
   with the left edge of .section-head above instead of floating inside it. */
.cards-two { grid-template-columns: repeat(2, 1fr); max-width: 72rem; }
@media (max-width: 860px) { .cards-two { grid-template-columns: 1fr; } }

/* The reward figures are the point of the card, so they lead rather than sit under a
   heading the way tier prices do. Foam, because .card p would otherwise grey them out. */
.cards-two .card h2 {
  font-family: var(--mono); font-size: 0.8rem; letter-spacing: 0.2em;
  color: var(--mist); margin-bottom: 0.6rem;
}
.cards-two .tier-price b { font-size: clamp(2.6rem, 6vw, 3.6rem); color: var(--foam); }

/* .legal owns its own page padding and centres itself at 48rem, which would put the
   small print on a different centre line to every other section. Match the 72rem
   gutter instead and hold the measure on the children. */
.referral-rules .legal-flush { padding: 0; max-width: 72rem; }
.referral-rules .legal-flush > * { max-width: 46rem; }

.referral-cta { margin-top: 2.4rem; }

/* ---------- phone in the nav ---------- */
/* Added 11 Aug 2026. The site sells to trades, who ring rather than fill in forms,
   and the copy already promised "the kind of words that turn a visit into a phone
   call" while giving nobody a number to call. Sits outside .nav-links so the
   scroll-shrink transition, which fades the links out into the burger, leaves it
   alone - the number is the one thing that should survive the shrink. */
.nav-call {
  font-family: var(--mono); font-size: 0.78rem; letter-spacing: 0.1em;
  color: var(--foam); text-decoration: none; white-space: nowrap;
  border: 1px solid var(--hairline); padding: 0.42rem 0.85rem;
  display: inline-flex; align-items: center; gap: 0.5rem;
  transition: color 0.3s, border-color 0.3s;
}
.nav-call:hover, .nav-call:focus-visible { color: var(--acid); border-color: rgba(var(--acid-rgb), 0.6); outline: none; }
.nav-call .ping { flex: none; }

/* On a 390px phone the wordmark, the number and the burger together leave about
   7px between the mark and the digits, which reads as a collision rather than a
   gap. Everything gives a little: the mark drops a step, the number loses its box
   and its ping, and .nav-right tightens. That buys back roughly 40px, which is a
   real gap. The number itself stays visible - it is the whole point of it being
   in the bar on the device people ring from. */
@media (max-width: 30rem) {
  .nav-mark { font-size: 1.15rem; }
  .nav-right { gap: 0.7rem; }
  .nav-call {
    border: 0; padding: 0.42rem 0; margin-left: 0.6rem;
    font-size: 0.68rem; letter-spacing: 0.05em;
  }
  .nav-call .ping { display: none; }
}

/* ---------- the facts strip ---------- */
/* Three things every enquiry asks before anything else: what it costs, what is due
   up front, and how long it takes. The third was missing from the site entirely
   until now, which left "how long?" as the one unanswered question in the pitch. */
.facts {
  max-width: 72rem; margin: 2.6rem auto 0;
  display: flex; flex-wrap: wrap; gap: 0.55rem;
  list-style: none;
}
.facts li {
  font-family: var(--mono); font-size: 0.74rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--mist);
  border: 1px solid var(--hairline); padding: 0.5rem 0.9rem;
}
.facts li b { color: var(--foam); font-weight: 500; }

/* ---------- client quotes ---------- */
/* Deliberately not built on .card. A fourth row of bordered boxes between the
   about band and the pricing tiers made the page read as one long card grid, and
   the quotes stopped registering as a different kind of thing. These borrow the
   .step row rhythm instead: hairline-separated full-width rows, oversized quote
   mark on the left, attribution held right. */
.quotes { background: linear-gradient(to bottom, var(--ink), var(--ink-2) 55%, var(--ink)); }
.quotes-list { max-width: 72rem; margin: 0 auto; list-style: none; }
.quote {
  display: grid; grid-template-columns: minmax(70px, 130px) 1fr minmax(auto, 15rem);
  gap: 1.5rem 2rem; align-items: start;
  padding: 3rem 0; border-top: 1px solid var(--hairline);
}
.quote:last-child { border-bottom: 1px solid var(--hairline); }
.quote-mark {
  font-family: var(--display); font-weight: 700; font-size: clamp(4rem, 9vw, 7rem);
  line-height: 0.7; color: transparent;
  -webkit-text-stroke: 2px rgba(var(--acid-rgb), 0.75);
  transition: color 0.4s;
}
.quote:hover .quote-mark { color: var(--acid); -webkit-text-stroke-color: var(--acid); text-shadow: 0 0 40px rgba(var(--acid-rgb), 0.35); }
.quote-text {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.25rem, 2.4vw, 1.9rem); line-height: 1.3;
  /* 34rem left a five-line quote sitting in a column half the width of the gap
     beside it, which read as a layout fault rather than a measure. 42rem lands a
     typical testimonial in three or four lines and closes the gap on the
     attribution without the line getting long - it is display type, so the
     comfortable measure is wider than it would be for body copy. */
  max-width: 42rem;
}
.quote-who { font-family: var(--mono); font-size: 0.76rem; line-height: 1.9; letter-spacing: 0.1em; }
.quote-name { display: block; color: var(--foam); text-transform: uppercase; letter-spacing: 0.14em; }
.quote-trade { display: block; color: var(--mist); text-transform: uppercase; letter-spacing: 0.14em; }
.quote-link { color: var(--acid); text-decoration: none; border-bottom: 1px solid rgba(var(--acid-rgb), 0.35); }
.quote-link:hover { border-bottom-color: var(--acid); }

@media (max-width: 860px) {
  .quote { grid-template-columns: 1fr; gap: 1rem; padding: 2.4rem 0; }
  .quote-mark { font-size: 3.4rem; line-height: 0.6; }
  .quote-who { text-align: left; }
}

/* ---------- direct contact ---------- */
/* Sits above the form rather than under it. Someone who wants to ring should not
   have to scroll past six fields to discover they could have. */
.contact-direct {
  max-width: 46rem; margin: 0 auto 2.6rem;
  display: flex; flex-wrap: wrap; gap: 0.9rem;
}
.contact-direct a {
  flex: 1 1 14rem;
  display: flex; flex-direction: column; gap: 0.3rem;
  border: 1px solid var(--hairline); padding: 1.1rem 1.3rem;
  text-decoration: none; color: var(--foam);
  transition: border-color 0.3s, background-color 0.3s;
}
.contact-direct a:hover, .contact-direct a:focus-visible {
  border-color: rgba(var(--acid-rgb), 0.5);
  background-color: rgba(var(--acid-rgb), 0.05); outline: none;
}
.contact-direct .cd-label {
  font-family: var(--mono); font-size: 0.7rem; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--mist);
}
.contact-direct .cd-value {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.05rem, 2vw, 1.35rem); letter-spacing: 0.01em;
}
.contact-direct a:hover .cd-value { color: var(--acid); }

/* ---------- FAQ page ---------- */
/* Real headings rather than <details>. Collapsed answers are text Google can see
   but a visitor cannot, and the whole point of the page is that the answers are
   the content. Nothing here is long enough to need hiding. */
.faq-list { max-width: 52rem; margin: 0 auto; }
.faq-item { padding: 2.2rem 0; border-top: 1px solid var(--hairline); }
.faq-item:last-child { border-bottom: 1px solid var(--hairline); }
.faq-item h3 {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.2rem, 2.4vw, 1.6rem); line-height: 1.25;
  margin-bottom: 0.9rem;
}
.faq-item p { color: var(--mist); max-width: 44rem; }
.faq-item p + p { margin-top: 0.9rem; }
.faq-item a { color: var(--acid); }
.faq-group + .faq-group { margin-top: clamp(3.5rem, 7vw, 6rem); }
.faq-group-title {
  font-family: var(--mono); font-size: 0.8rem; letter-spacing: 0.24em;
  text-transform: uppercase; color: var(--acid);
  max-width: 52rem; margin: 0 auto 0.6rem;
}

/* ---------- service page index ---------- */
/* The three service pages cross-link to each other from the foot of each one.
   Echoes the row treatment .work-index used to carry (removed in v37, see the
   note further up this file); a service with no thumbnail did not justify a
   second card grid. These rules are self-contained and were never shared with
   it, so the removal did not touch them. */
.svc-index { max-width: 72rem; margin: 0 auto; list-style: none; border-top: 1px solid var(--hairline); }
/* The list is its own section between the work band and the contact form now, so
   the standard section padding gives it the room it needs. It used to live inside
   .services and carried a hand-set top margin to fake that gap; both that rule and
   the extra margin on the kicker went with the move. The kicker keeps the 72rem
   column so its first character lines up with the list beneath it. */
.svc-index-kicker { max-width: 72rem; margin: 0 auto; }
.svc-index-kicker + .svc-index { margin-top: 1.3rem; }
.svc-index li { border-bottom: 1px solid var(--hairline); }
.svc-index a {
  display: grid; grid-template-columns: 1fr auto; align-items: baseline;
  column-gap: 1rem; padding: 1.35rem 0.75rem 1.35rem 0;
  text-decoration: none; color: inherit;
  transition: background-color 0.3s, padding-left 0.3s;
}
.svc-index a:hover, .svc-index a:focus-visible {
  background-color: rgba(var(--acid-rgb), 0.06); padding-left: 0.6rem; outline: none;
}
.svc-index-name {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.15rem, 2.2vw, 1.5rem); line-height: 1.15; transition: color 0.3s;
}
.svc-index a:hover .svc-index-name, .svc-index a:focus-visible .svc-index-name { color: var(--acid); }
.svc-index-note {
  font-family: var(--mono); font-size: 0.7rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--mist); text-align: right; white-space: nowrap;
}
@media (max-width: 44rem) {
  .svc-index a { grid-template-columns: 1fr; row-gap: 0.35rem; }
  .svc-index-note { text-align: left; white-space: normal; }
}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
  .hero-title .word { transform: none; }
  .reveal { opacity: 1; transform: none; }
  .noise { animation: none; }
  .quote:hover .quote-mark { text-shadow: none; }
}

/* The tiers use the plain 3-column .cards grid. There was briefly a 2-column override
   here when Set & Forget was retired; it went again when the capped entry tier was
   added on 4 Aug 2026. If a 2-wide ladder ever comes back, give it its own rule rather
   than reusing .cards-two - that belongs to the referrals page and restyles
   .tier-price b, which silently blows the tier prices up. */

/* NOTE (4 Aug 2026): this file is the MASTER. Pages link a versioned COPY,
   styles-vN.css, because Cloudflare ignores the ?v= query string on this zone -
   a ?v bump served the stale file from cache with cf-cache-status: HIT.
   To change styles: edit this file, copy to a NEW styles-vN+1.css, update the
   <link> in every page. Keep the previous version deployed so HTML already
   cached in a visitor's browser still finds its stylesheet. */
