/* ==========================================================================
   Service subpages — the ONLY stylesheet a subpage links

       <link rel="stylesheet" href="../assets/css/subpage.css">

   The design system is COPIED IN below rather than @imported, so the whole
   sheet arrives in one request with no second round trip.

   GENERATED, IN PART. Everything from here down to the SUBPAGE STYLES marker
   is a copy of tokens.css, base.css, components.css and sections.css. If any
   of those change, this file must be rebuilt or the subpages will quietly
   drift away from the homepage. Rules below that marker are hand-authored
   and are preserved when it is rebuilt.
   ========================================================================== */

/* Hoisted from base.css: @import is only valid before any other rule, so
   it cannot stay where it was once that file is pasted mid-sheet. */
@import url("https://api.fontshare.com/v2/css?f[]=clash-display@600,700&f[]=satoshi@400,500,700&display=swap");

/* =====.field input=====================================================================
   TOKENS — copied from assets/css/tokens.css
   ========================================================================== */
/* ==========================================================================
   Living Fresh Cleaning — Design Tokens
   Concept: "Light Enters Clean Space"
   Palette anchored on the logo blue (#1F6FC4).
   ========================================================================== */

:root {
  /* ---- Ink (cinematic dark base) ------------------------------------- */
  --ink-900: #04101c;
  --ink-800: #071a2c;
  --ink-700: #0c2740;
  --ink-600: #12364f;

  /* ---- Brand blue ramp ----------------------------------------------- */
  --blue-700: #0f4380;
  --blue-600: #1554a0;
  --blue-500: #1f6fc4; /* logo blue — primary */
  --blue-400: #3d8fe0;
  --blue-300: #6fb0ee;

  /* ---- Light accents (the "light source") ---------------------------- */
  --cyan-400: #4fc3f7;
  --cyan-300: #7dd9ff;
  --mint-400: #3fd9a8;

  /* ---- Paper (light bands) ------------------------------------------- */
  --paper-50: #ffffff;
  --paper-100: #f4f8fc;
  --paper-200: #e4edf6;
  --paper-300: #cfdeed;

  /* ---- Text ----------------------------------------------------------- */
  --slate-700: #22394c; /* headings on light  — 10.9:1 on paper-100 */
  --slate-500: #4a6579; /* body on light      —  6.1:1 on paper-100 */
  --slate-400: #6d879b; /* muted on light     —  3.6:1 — large text only */

  --on-dark: #eef5fb; /* body on ink        — 15.2:1 on ink-800 */
  --on-dark-muted: #9db4c7; /* muted on ink —  7.1:1 on ink-800 */

  /* ---- RGB channels for alpha compositing ---------------------------- */
  --rgb-cyan: 79, 195, 247;
  --rgb-blue: 31, 111, 196;
  --rgb-mint: 63, 217, 168;
  --rgb-ink: 4, 16, 28;
  --rgb-white: 255, 255, 255;

  /* ---- Signature gradients -------------------------------------------- */
  --grad-hero: radial-gradient(120% 90% at 50% 0%, #1f6fc4 0%, #071a2c 55%, #04101c 100%);
  --grad-glow: radial-gradient(closest-side, rgba(var(--rgb-cyan), 0.55), transparent 70%);
  --grad-text: linear-gradient(105deg, #ffffff 0%, #7dd9ff 45%, #3d8fe0 100%);
  --grad-surface: linear-gradient(160deg, rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.02));
  --grad-fresh: linear-gradient(135deg, #4fc3f7, #3fd9a8);
  --grad-cta: linear-gradient(140deg, #1554a0 0%, #1f6fc4 45%, #0c2740 100%);

  /* ---- Typography ------------------------------------------------------ */
  --font-display: "Clash Display", "Outfit", "Segoe UI", system-ui, sans-serif;
  --font-body: "Satoshi", "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;

  --fs-display-xl: clamp(2.75rem, 8.2vw, 8rem);
  --fs-display-lg: clamp(2.25rem, 4.2vw, 3.5rem);
  --fs-display-md: clamp(1.85rem, 3.6vw, 3.15rem);
  --fs-heading-md: clamp(1.5rem, 2.6vw, 2.25rem);
  --fs-heading-sm: clamp(1.15rem, 1.7vw, 1.4rem);
  --fs-body-lg: clamp(1.05rem, 1.3vw, 1.2rem);
  --fs-body: 1rem;
  --fs-caption: 0.8125rem;
  --fs-eyebrow: 0.75rem;

  --lh-tight: 0.94;
  --lh-snug: 1.12;
  --lh-body: 1.7;

  --ls-display: -0.03em;
  --ls-eyebrow: 0.18em;

  /* ---- Spacing scale --------------------------------------------------- */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.5rem;
  --sp-6: 2rem;
  --sp-7: 3rem;
  --sp-8: 4rem;
  --sp-9: 6rem;
  --sp-10: 8rem;
  --section-y: clamp(4.5rem, 9vw, 6rem);

  /* ---- Layout ---------------------------------------------------------- */
  --container: 1280px;
  --container-wide: 1560px;
  --gutter: clamp(1.25rem, 4vw, 3rem);
  --measure: 68ch;
  --nav-h: 84px;

  /* ---- Radii ----------------------------------------------------------- */
  --r-sm: 8px;
  --r-md: 14px;
  --r-lg: 22px;
  --r-xl: 32px;
  --r-pill: 999px;

  /* ---- Elevation & glow ------------------------------------------------ */
  --shadow-sm: 0 2px 8px rgba(var(--rgb-ink), 0.08);
  --shadow-md: 0 18px 40px -18px rgba(var(--rgb-ink), 0.28);
  --shadow-lg: 0 40px 90px -30px rgba(var(--rgb-ink), 0.45);
  --glow-cyan: 0 0 0 1px rgba(var(--rgb-cyan), 0.35), 0 12px 44px -8px rgba(var(--rgb-cyan), 0.45);
  --glow-soft: 0 18px 60px -20px rgba(var(--rgb-cyan), 0.35);

  /* ---- Easing & duration ----------------------------------------------- */
  --e-out: cubic-bezier(0.22, 1, 0.36, 1); /* settle, slight authority */
  --e-inout: cubic-bezier(0.65, 0, 0.35, 1);
  --e-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* overshoot — "has weight" */
  --d-fast: 0.24s;
  --d-mid: 0.5s;
  --d-slow: 0.9s;

  /* ---- Z-index ---------------------------------------------------------- */
  --z-grain: 5;
  --z-nav: 100;
  --z-menu: 95;
  --z-progress: 110;
  --z-cursor: 200;
}

/* Reduced motion: collapse durations globally so any transition that slips
   through the JS gate is still near-instant. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --d-fast: 0.01s;
    --d-mid: 0.15s;
    --d-slow: 0.2s;
  }
}

/* ==========================================================================
   BASE — copied from assets/css/base.css
   ========================================================================== */
/* ==========================================================================
   Base — reset, typography, layout primitives, utilities
   ========================================================================== */

/* ---- Fonts (Fontshare CDN; swap to self-hosted woff2 in assets/fonts) ---- */


/* ---- Reset --------------------------------------------------------------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}

* {
  margin: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

/* Lenis drives scrolling; native smooth-scroll would fight it. */
html.lenis,
html.lenis body {
  height: auto;
}
.lenis.lenis-smooth {
  scroll-behavior: auto !important;
}

body {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--slate-500);
  background: var(--ink-900);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img,
picture,
svg,
video {
  display: block;
  max-width: 100%;
}

img {
  height: auto;
}

input,
button,
textarea,
select {
  font: inherit;
  color: inherit;
}

button {
  background: none;
  border: 0;
  cursor: pointer;
}

ul,
ol {
  list-style: none;
  padding: 0;
}

a {
  color: inherit;
  text-decoration: none;
}

/* ---- Typography ---------------------------------------------------------- */
h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-display);
  text-wrap: balance;
}

p {
  text-wrap: pretty;
}

.display-xl {
  font-size: var(--fs-display-xl);
  line-height: var(--lh-tight);
  font-weight: 600;
}

.display-lg {
  font-size: var(--fs-display-lg);
  line-height: 0.98;
}

.display-md {
  font-size: var(--fs-display-md);
}

.heading-md {
  font-size: var(--fs-heading-md);
}

.heading-sm {
  font-size: var(--fs-heading-sm);
  font-family: var(--font-display);
  font-weight: 600;
}

.body-lg {
  font-size: var(--fs-body-lg);
  line-height: 1.65;
}

.measure {
  max-width: var(--measure);
}

/* Eyebrow — small caps label above every section title */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  font-size: var(--fs-eyebrow);
  font-weight: 500;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--blue-500);
}

.eyebrow::before {
  content: "";
  width: 28px;
  height: 1px;
  background: currentColor;
  opacity: 0.6;
}

/* Gradient text — used only on hero and section titles over ink */
.grad-text {
  background: var(--grad-text);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ---- Layout -------------------------------------------------------------- */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.container-wide {
  width: 100%;
  max-width: var(--container-wide);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section {
  position: relative;
  padding-block: var(--section-y);
  overflow: clip;
}

/* Band colours — the dark/light rhythm */
.band-ink {
  background: var(--ink-900);
  color: var(--on-dark);
}
.band-ink-800 {
  background: var(--ink-800);
  color: var(--on-dark);
}
.band-paper {
  background: var(--paper-50);
  color: var(--slate-500);
}
.band-paper-100 {
  background: var(--paper-100);
  color: var(--slate-500);
}

.band-ink h1,
.band-ink h2,
.band-ink h3,
.band-ink-800 h1,
.band-ink-800 h2,
.band-ink-800 h3 {
  color: var(--paper-50);
}

.band-paper h1,
.band-paper h2,
.band-paper h3,
.band-paper-100 h1,
.band-paper-100 h2,
.band-paper-100 h3 {
  color: var(--slate-700);
}

.section-head {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  max-width: 780px;
  margin-bottom: clamp(2.5rem, 5vw, 4.5rem);
}

/* ---- Ambience: grain + glow orbs ---------------------------------------- */
/* Grain is the single largest contributor to the filmic feel. */
.grain::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: var(--z-grain);
  opacity: 0.045;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

.orb {
  position: absolute;
  border-radius: 50%;
  background: var(--grad-glow);
  filter: blur(28px);
  pointer-events: none;
  will-change: transform;
}

/* ---- Diagonal light-sweep band transition -------------------------------- */
/* Sits at the top of a band and cuts a soft diagonal into the band above. */
.sweep {
  position: absolute;
  inset-inline: -5%;
  top: -1px;
  height: clamp(60px, 7vw, 130px);
  pointer-events: none;
  background: inherit;
  clip-path: polygon(0 0, 100% 55%, 100% 100%, 0 100%);
}

.sweep::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 0%, rgba(var(--rgb-cyan), 0.5) 50%, transparent 100%);
  clip-path: polygon(0 0, 100% 55%, 100% calc(55% + 2px), 0 2px);
  opacity: 0.7;
}

/* ---- Accessibility ------------------------------------------------------- */
.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: -100px;
  z-index: 999;
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--r-pill);
  background: var(--paper-50);
  color: var(--ink-900);
  font-weight: 700;
  transition: top var(--d-fast) var(--e-out);
}

.skip-link:focus-visible {
  top: var(--sp-4);
}

:focus-visible {
  outline: 2px solid var(--cyan-300);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---- Reveal primitives ---------------------------------------------------
   Rest state IS the final state. JS adds .is-armed to hide elements before
   animating them in, so a JS failure or no-JS visit leaves the page readable.
   -------------------------------------------------------------------------- */
[data-reveal].is-armed {
  opacity: 0;
  transform: translateY(40px);
  filter: blur(8px);
}

[data-parallax] {
  will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  [data-reveal].is-armed {
    opacity: 1;
    transform: none;
    filter: none;
  }
  .orb {
    animation: none !important;
  }
}

/* ==========================================================================
   COMPONENTS — copied from assets/css/components.css
   ========================================================================== */
/* ==========================================================================
   Components — buttons, logo, nav, cards, form, footer, chrome
   ========================================================================== */

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  padding: 0.95rem 1.75rem;
  border-radius: var(--r-pill);
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: 0.01em;
  white-space: nowrap;
  cursor: pointer;
  overflow: hidden;
  transition: transform var(--d-mid) var(--e-out), box-shadow var(--d-mid) var(--e-out),
    background-color var(--d-mid) var(--e-out), color var(--d-mid) var(--e-out),
    border-color var(--d-mid) var(--e-out);
}

.btn:hover {
  transform: translateY(-3px);
}

.btn:active {
  transform: translateY(-1px);
}

.btn svg {
  flex: none;
  width: 18px;
  height: 18px;
}

/* Primary — the light source made into a control */
.btn-primary {
  background: var(--grad-fresh);
  color: var(--ink-900);
  box-shadow: var(--glow-soft);
}

.btn-primary:hover {
  box-shadow: var(--glow-cyan);
}

/* Sheen sweep on hover */
.btn-primary::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 20%, rgba(255, 255, 255, 0.55) 50%, transparent 80%);
  transform: translateX(-120%);
  transition: transform 0.7s var(--e-out);
}

.btn-primary:hover::after {
  transform: translateX(120%);
}

/* Ghost — on ink */
.btn-ghost {
  border: 1px solid rgba(var(--rgb-white), 0.22);
  color: var(--paper-50);
  background: rgba(var(--rgb-white), 0.04);
  backdrop-filter: blur(8px);
}

.btn-ghost:hover {
  border-color: rgba(var(--rgb-cyan), 0.6);
  background: rgba(var(--rgb-cyan), 0.1);
  color: var(--cyan-300);
}

/* Solid blue — on paper */
.btn-blue {
  background: var(--blue-500);
  color: var(--paper-50);
  box-shadow: 0 14px 34px -14px rgba(var(--rgb-blue), 0.75);
}

.btn-blue:hover {
  background: var(--blue-600);
  box-shadow: 0 20px 44px -14px rgba(var(--rgb-blue), 0.85);
}

/* Testimonials tail: the carousel shows four quotes, this is the way out to
   the rest of them on Google. */
.quotes__more {
  margin-top: var(--sp-7);
  text-align: center;
}

/* Text link with travelling underline */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--cyan-300);
}

.link-arrow span {
  position: relative;
}

.link-arrow span::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -3px;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform var(--d-mid) var(--e-out);
}

.link-arrow:hover span::after {
  transform: scaleX(1);
  transform-origin: left;
}

.link-arrow svg {
  width: 16px;
  height: 16px;
  transition: transform var(--d-mid) var(--e-out);
}

.link-arrow:hover svg {
  transform: translateX(5px);
}

/* --------------------------------------------------------------------------
   Logo
   -------------------------------------------------------------------------- */
.logo {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
}

.logo__mark {
  width: 52px;
  height: 52px;
  flex: none;
  /* The roundel is cut to a transparent circle, so nothing needs clipping
     here; object-fit only guards against a future re-export at another
     aspect ratio squashing it. */
  object-fit: contain;
  transition: transform var(--d-mid) var(--e-spring);
}

.logo:hover .logo__mark {
  transform: rotate(-8deg) scale(1.05);
}

.logo__text {
  display: flex;
  flex-direction: column;
  line-height: 1.1;
}

.logo__text b {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--paper-50);
}

.logo__text span {
  /* was 0.62rem = 9.92px, below the 12px floor for legible label text */
  font-size: 0.75rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--on-dark-muted);
}

/* --------------------------------------------------------------------------
   Navigation
   -------------------------------------------------------------------------- */
.nav {
  position: fixed;
  inset-inline: 0;
  top: 0;
  z-index: var(--z-nav);
  transition: background-color var(--d-mid) var(--e-out), box-shadow var(--d-mid) var(--e-out),
    backdrop-filter var(--d-mid) var(--e-out);
}

.nav__inner {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  height: var(--nav-h);
  transition: height var(--d-mid) var(--e-out);
}

.nav.is-stuck {
  background: rgba(4, 16, 28, 0.78);
  backdrop-filter: blur(18px) saturate(140%);
  box-shadow: 0 1px 0 rgba(var(--rgb-cyan), 0.28), 0 18px 40px -30px rgba(0, 0, 0, 0.9);
}

.nav.is-stuck .nav__inner {
  height: 68px;
}

.nav__links {
  display: flex;
  align-items: center;
  gap: clamp(0.5rem, 1.6vw, 1.6rem);
  margin-inline: auto;
}

.nav__link {
  position: relative;
  display: inline-flex;
  white-space: nowrap;
  align-items: center;
  gap: 6px;
  padding: 0.5rem 0.25rem;
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--on-dark);
  transition: color var(--d-fast) var(--e-out);
}

.nav__link:hover,
.nav__link[aria-expanded="true"] {
  color: var(--cyan-300);
}

.nav__link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: var(--cyan-300);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform var(--d-mid) var(--e-out);
}

.nav__link:hover::after,
.nav__link[aria-expanded="true"]::after {
  transform: scaleX(1);
  transform-origin: left;
}

.nav__link .chev {
  width: 12px;
  height: 12px;
  transition: transform var(--d-fast) var(--e-out);
}

.nav__link[aria-expanded="true"] .chev {
  transform: rotate(180deg);
}

.nav__actions {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

.nav__phone {
  display: inline-flex;
  white-space: nowrap;
  align-items: center;
  gap: var(--sp-2);
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--paper-50);
  transition: color var(--d-fast) var(--e-out);
}

.nav__phone:hover {
  color: var(--cyan-300);
}

.nav__phone svg {
  width: 16px;
  height: 16px;
  color: var(--cyan-400);
}

.nav__toggle {
  display: none;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-sm);
  color: var(--paper-50);
}

.nav__toggle span,
.nav__toggle span::before,
.nav__toggle span::after {
  content: "";
  display: block;
  width: 22px;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
  transition: transform var(--d-mid) var(--e-out), opacity var(--d-fast) linear;
}

.nav__toggle span::before {
  transform: translateY(-7px);
}
.nav__toggle span::after {
  transform: translateY(5px);
}

.nav__toggle[aria-expanded="true"] span {
  background: transparent;
}
.nav__toggle[aria-expanded="true"] span::before {
  transform: rotate(45deg);
}
.nav__toggle[aria-expanded="true"] span::after {
  transform: rotate(-45deg) translateY(-1px);
}

/* ---- Mega dropdown ------------------------------------------------------- */
.mega {
  position: absolute;
  inset-inline: 0;
  top: 100%;
  z-index: var(--z-menu);
  background: rgba(7, 26, 44, 0.96);
  backdrop-filter: blur(20px);
  border-top: 1px solid rgba(var(--rgb-cyan), 0.18);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-12px);
  transition: opacity var(--d-mid) var(--e-out), transform var(--d-mid) var(--e-out),
    visibility var(--d-mid);
}

.mega.is-open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.mega__grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 0.9fr;
  gap: var(--sp-6);
  padding-block: var(--sp-6) var(--sp-7);
}

.mega__title {
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--cyan-400);
  margin-bottom: var(--sp-4);
}

.mega__item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 0.6rem 0.75rem;
  border-radius: var(--r-md);
  font-size: 0.92rem;
  color: var(--on-dark);
  transition: background-color var(--d-fast) var(--e-out), color var(--d-fast) var(--e-out),
    transform var(--d-fast) var(--e-out);
}

.mega__item:hover {
  background: rgba(var(--rgb-cyan), 0.1);
  color: var(--cyan-300);
  transform: translateX(4px);
}

.mega__item svg {
  width: 16px;
  height: 16px;
  flex: none;
  color: var(--blue-400);
}

.mega__promo {
  position: relative;
  border-radius: var(--r-lg);
  overflow: hidden;
  min-height: 220px;
  display: flex;
  align-items: flex-end;
  padding: var(--sp-5);
  color: var(--paper-50);
}

.mega__promo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.85) brightness(0.62) contrast(1.06);
}

.mega__promo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(var(--rgb-blue), 0.15), rgba(var(--rgb-ink), 0.9));
}

.mega__promo > div {
  position: relative;
  z-index: 1;
}

.mega__promo strong {
  font-family: var(--font-display);
  font-size: 1.2rem;
  display: block;
}

.mega__promo p {
  font-size: 0.85rem;
  color: var(--on-dark-muted);
  margin-top: var(--sp-2);
}

/* --------------------------------------------------------------------------
   Chrome — scroll progress, cursor, scroll cue
   -------------------------------------------------------------------------- */
.progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 2px;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left;
  background: var(--grad-fresh);
  z-index: var(--z-progress);
  pointer-events: none;
}

.cursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: var(--z-cursor);
  pointer-events: none;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--cyan-400);
  mix-blend-mode: difference;
  opacity: 0;
  transition: width 0.3s var(--e-out), height 0.3s var(--e-out), background-color 0.3s var(--e-out),
    border-color 0.3s var(--e-out), opacity 0.3s linear;
}

.cursor.is-active {
  opacity: 1;
}

.cursor.is-hover {
  width: 46px;
  height: 46px;
  background: transparent;
  border: 1px solid var(--cyan-300);
}

.scroll-cue {
  position: absolute;
  left: 50%;
  bottom: clamp(1.5rem, 4vh, 3rem);
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  font-size: 0.7rem;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--on-dark-muted);
  z-index: 3;
}

.scroll-cue__rail {
  position: relative;
  width: 1px;
  height: 60px;
  background: rgba(var(--rgb-white), 0.18);
  overflow: hidden;
}

.scroll-cue__rail::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: -40%;
  height: 40%;
  background: linear-gradient(180deg, transparent, var(--cyan-300));
  animation: cue 2.4s var(--e-inout) infinite;
}

@keyframes cue {
  0% {
    transform: translateY(0);
  }
  100% {
    transform: translateY(350%);
  }
}

/* --------------------------------------------------------------------------
   Cards
   -------------------------------------------------------------------------- */
/* Service card — photo panel, floating icon badge, body beneath.
   Sits on a light band, so every colour here is the light-band half of the
   palette: white surface, slate text, blue-600 link. cyan-400 is 2.0:1 on
   white and blue-400 is 3.39:1 — neither can carry this link. */
.svc-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--paper-50);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--shadow-md);
  transition: transform var(--d-mid) var(--e-out), box-shadow var(--d-mid) var(--e-out);
}

.svc-card:hover {
  transform: translateY(-8px);
  box-shadow: var(--shadow-lg);
}

.svc-card__media {
  position: relative;
  margin: 0;
  aspect-ratio: 4 / 3;
  overflow: hidden;
}

.svc-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.9s var(--e-out);
}

.svc-card:hover .svc-card__media img {
  transform: scale(1.07);
}

/* Grounds the badge and keeps the photo from running bright into the body. */
.svc-card__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(0deg, rgba(var(--rgb-ink), 0.45), transparent 52%);
}

/* The badge straddles the seam between photo and body — that overlap is what
   gives the card its depth. It is anchored to the TOP of the body rather than
   the bottom of the media on purpose: .svc-card__media is overflow:hidden so
   the hover zoom stays in its frame, and that clip would slice the overhang
   off. The body clips nothing, so the badge hangs up into the photo intact. */
.svc-card__icon {
  position: absolute;
  left: var(--sp-5);
  top: -26px;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 54px;
  height: 54px;
  border-radius: var(--r-md);
  background: linear-gradient(135deg, var(--blue-700), var(--blue-500));
  border: 3px solid var(--paper-50);
  box-shadow: 0 12px 26px -8px rgba(var(--rgb-blue), 0.55);
  color: var(--paper-50);
  transition: transform var(--d-mid) var(--e-out);
}

.svc-card:hover .svc-card__icon {
  transform: rotate(-8deg) scale(1.07);
}

.svc-card__icon svg {
  width: 24px;
  height: 24px;
}

.svc-card__body {
  position: relative; /* the badge above is positioned against this */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: calc(var(--sp-6) + 8px) var(--sp-5) var(--sp-5);
}

.svc-card h3 {
  font-size: 1.15rem;
  color: var(--slate-700);
}

.svc-card p {
  font-size: 0.92rem;
  color: var(--slate-500);
  margin: 0;
}

/* The shared .link-arrow is cyan-300 for dark bands; on white it needs the
   deep blue instead. */
.svc-card .link-arrow {
  margin-top: var(--sp-2);
  color: var(--blue-600);
}

/* Glow tile — Why Choose Us; cyan follows the pointer */
.glow-tile {
  position: relative;
  padding: clamp(1.75rem, 3vw, 2.75rem);
  border-radius: var(--r-lg);
  background: var(--grad-surface);
  border: 1px solid rgba(var(--rgb-white), 0.09);
  overflow: hidden;
  transition: border-color var(--d-mid) var(--e-out), transform var(--d-mid) var(--e-out);
}

.glow-tile::before {
  content: "";
  position: absolute;
  width: 340px;
  height: 340px;
  border-radius: 50%;
  background: var(--grad-glow);
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, -50%);
  left: var(--mx, 50%);
  top: var(--my, 50%);
  transition: opacity var(--d-mid) linear;
}

.glow-tile:hover {
  border-color: rgba(var(--rgb-cyan), 0.32);
  transform: translateY(-4px);
}

.glow-tile:hover::before {
  opacity: 0.28;
}

.glow-tile > * {
  position: relative;
}

.glow-tile__icon {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  margin-bottom: var(--sp-5);
  border-radius: var(--r-md);
  background: rgba(var(--rgb-cyan), 0.12);
  border: 1px solid rgba(var(--rgb-cyan), 0.28);
  color: var(--cyan-300);
}

.glow-tile__icon svg {
  width: 26px;
  height: 26px;
}

.glow-tile h3 {
  font-size: var(--fs-heading-sm);
  margin-bottom: var(--sp-3);
}

.glow-tile p {
  color: var(--on-dark-muted);
  font-size: 0.95rem;
}

/* --------------------------------------------------------------------------
   Quote form
   -------------------------------------------------------------------------- */
.form {
  display: grid;
  gap: var(--sp-4);
  padding: clamp(1.5rem, 3vw, 2.5rem);
  border-radius: var(--r-lg);
  background: rgba(var(--rgb-ink), 0.42);
  border: 1px solid rgba(var(--rgb-white), 0.14);
  backdrop-filter: blur(16px);
}

.form__row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-4);
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.field label {
  font-size: var(--fs-caption);
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: rgba(var(--rgb-white), 0.72);
}

.field input,
.field select,
.field textarea {
  width: 100%;
  min-width: 0; /* form controls carry an intrinsic min-width; drop it */
  padding: 0.55rem 1rem;
  border-radius: var(--r-md);
  border: 1px solid rgba(var(--rgb-white), 0.18);
  background: rgba(var(--rgb-ink), 0.45);
  color: var(--paper-50);
  transition: border-color var(--d-fast) var(--e-out), box-shadow var(--d-fast) var(--e-out);
}

.field textarea {
  resize: vertical;
  min-height: 110px;
}

.field input::placeholder,
.field textarea::placeholder {
  color: rgba(var(--rgb-white), 0.42);
}

/* A select has no ::placeholder — its empty first option stands in for one.
   `required` keeps the control :invalid until a real choice is made, which is
   the only hook for showing that stand-in at placeholder weight rather than
   as though a service had already been picked. */
.field select:invalid {
  color: rgba(var(--rgb-white), 0.42);
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--cyan-400);
  box-shadow: 0 0 0 3px rgba(var(--rgb-cyan), 0.2);
}

.field select option {
  color: var(--slate-700);
}

.field__error {
  font-size: var(--fs-caption);
  color: #ffb4b4;
  min-height: 1.1em;
}

.form__note {
  font-size: var(--fs-caption);
  color: rgba(var(--rgb-white), 0.6);
}

.form__status {
  font-size: 0.9rem;
  color: var(--mint-400);
  min-height: 1.4em;
}

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */
.footer {
  position: relative;
  background: var(--ink-900);
  color: var(--on-dark-muted);
  padding-top: var(--sp-9);
  overflow: clip;
}

.footer__grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1fr;
  gap: var(--sp-6);
  padding-bottom: var(--sp-8);
}

.footer__grid > * {
  min-width: 0;
}

.footer a {
  overflow-wrap: anywhere; /* the email address must be allowed to break */
}

.footer h3 {
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--cyan-400);
  margin-bottom: var(--sp-4);
}

.footer li + li {
  margin-top: var(--sp-2);
}

/* Menu rows: chevron bullet, then the label. align-items:flex-start keeps the
   chevron on the first line when a label wraps to two. The hover shift is a
   transform, not padding, so it never reflows the column. */
.footer__link {
  display: inline-flex;
  align-items: flex-start;
  gap: var(--sp-2);
  font-size: 0.92rem;
  transition: color var(--d-fast) var(--e-out), transform var(--d-fast) var(--e-out);
}

.footer__link svg {
  width: 12px;
  height: 12px;
  flex: none;
  margin-top: 0.44em; /* optical centring against the first line of text */
  color: var(--blue-400);
  transition: color var(--d-fast) var(--e-out), transform var(--d-fast) var(--e-out);
}

.footer__link:hover {
  color: var(--cyan-300);
  transform: translateX(4px);
}

.footer__link:hover svg {
  color: var(--cyan-300);
  transform: translateX(2px);
}

/* Contact rows: real icons rather than a generic bullet. The last row is a
   <span>, not a link, so the class carries the layout instead of `a`. */
.footer__contact {
  display: inline-flex;
  align-items: flex-start;
  gap: var(--sp-3);
  font-size: 0.92rem;
  transition: color var(--d-fast) var(--e-out), transform var(--d-fast) var(--e-out);
}

.footer__contact svg {
  width: 15px;
  height: 15px;
  flex: none;
  margin-top: 0.24em;
  color: var(--cyan-400);
  transition: color var(--d-fast) var(--e-out);
}

a.footer__contact:hover {
  color: var(--cyan-300);
  transform: translateX(3px);
}

a.footer__contact:hover svg {
  color: var(--cyan-300);
}

/* Any footer link not covered above (e.g. legal rows added later). */
.footer a:not(.logo):not(.social__link):not(.footer__link):not(.footer__contact) {
  font-size: 0.92rem;
  transition: color var(--d-fast) var(--e-out);
}

.footer a:not(.logo):not(.social__link):not(.footer__link):not(.footer__contact):hover {
  color: var(--cyan-300);
}

.footer__blurb {
  margin-top: var(--sp-4);
  font-size: 0.92rem;
  max-width: 34ch;
}

.social {
  display: flex;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
}

/* The client's review poster in the footer's brand column, with a vector QR
   laid over its printed one. The artwork is a 200px JPEG, so its own code is
   too soft to decode at any display size, while an SVG of the same 41 modules
   stays sharp at every device pixel ratio - both encode the identical review
   URL. The percentages below are measured from the source file: the printed
   code occupies x 51-148, y 149-246 of a 200x283 image, and the overlay adds
   2.5 modules of quiet zone so its white ground covers the original cleanly.
   Re-measure them if the artwork is ever re-exported. */
.review-card {
  position: relative;
  display: block;
  width: 200px;
  max-width: 100%;
  margin-top: var(--sp-5);
  border-radius: var(--r-md);
  overflow: hidden;
  box-shadow: var(--shadow-md);
  transition: transform var(--d-mid) var(--e-out);
}

.review-card:hover,
.review-card:focus-visible {
  transform: translateY(-3px);
}

.review-card__art {
  display: block;
  width: 100%;
  height: auto;
}

.review-card__qr {
  position: absolute;
  left: 22.54%;
  top: 50.56%;
  width: 54.41%;
  height: auto;
  aspect-ratio: 1;
  background: #fff;
}

.social__link {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(var(--rgb-white), 0.14);
  color: var(--on-dark-muted);
  transition: transform var(--d-mid) var(--e-spring), color var(--d-fast) var(--e-out),
    border-color var(--d-fast) var(--e-out), background-color var(--d-fast) var(--e-out);
}

.social__link svg {
  width: 17px;
  height: 17px;
}

.social__link:hover {
  transform: translateY(-4px);
  color: var(--cyan-300);
  border-color: rgba(var(--rgb-cyan), 0.45);
  background: rgba(var(--rgb-cyan), 0.08);
}

.footer__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding-block: var(--sp-5);
  border-top: 1px solid rgba(var(--rgb-white), 0.08);
  font-size: var(--fs-caption);
}

/* The design credit in the legal row. The catch-all above already gives it a
   colour and a cyan hover, so this only corrects the two things that are
   wrong in this context: 0.92rem is larger than the 13px bar it sits in, and
   base.css leaves links with no underline, which makes a link in running copy
   indistinguishable from the copyright text around it.

   The :not() chain is repeated deliberately — it matches the specificity of
   the catch-all (0,5,1) so this rule wins on source order rather than on an
   !important. */
.footer__bar a:not(.logo):not(.social__link):not(.footer__link):not(.footer__contact) {
  font-size: inherit;
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(var(--rgb-white), 0.4);
  transition: color var(--d-fast) var(--e-out),
    text-decoration-color var(--d-fast) var(--e-out);
}

.footer__bar a:hover,
.footer__bar a:focus-visible {
  text-decoration-color: currentColor;
}

/* Oversized ghosted wordmark bleeding off the bottom edge */
.footer__ghost {
  position: absolute;
  left: 50%;
  bottom: -0.28em;
  transform: translateX(-50%);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(4rem, 15vw, 15rem);
  line-height: 1;
  white-space: nowrap;
  color: rgba(var(--rgb-white), 0.03);
  pointer-events: none;
  user-select: none;
}

/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */
/* Six links, the phone and the quote button need ~1290px on one line. Below
   1280 labels would wrap to two lines, so hand over to the drawer. */
@media (max-width: 1279px) {
  .nav__links,
  .nav__phone {
    display: none;
  }

  .nav__toggle {
    display: flex;
    order: 3;
  }

  .nav__actions {
    margin-left: auto;
  }
}

@media (max-width: 1080px) {
  .mega__grid {
    grid-template-columns: 1fr 1fr;
  }

  .mega__promo {
    display: none;
  }

  .footer__grid {
    grid-template-columns: 1fr 1fr;
  }
}

@media (max-width: 640px) {
  .btn {
    padding: 0.85rem 1.35rem;
    font-size: 0.9rem;
  }

  /* The nav bar can't hold the logo, CTA and toggle at this width. The drawer
     and the hero both carry a "Get Free Quote", so drop this one. */
  .nav__actions .btn {
    display: none;
  }

  .form__row {
    grid-template-columns: 1fr;
  }

  .footer__grid {
    grid-template-columns: 1fr;
  }

  .logo__text b {
    font-size: 0.95rem;
  }
}

/* --------------------------------------------------------------------------
   Mobile drawer (shares nav markup)
   -------------------------------------------------------------------------- */
.drawer {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(4, 16, 28, 0.97);
  backdrop-filter: blur(20px);
  padding: calc(var(--nav-h) + var(--sp-5)) var(--gutter) var(--sp-7);
  overflow-y: auto;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-2%);
  transition: opacity var(--d-mid) var(--e-out), transform var(--d-mid) var(--e-out),
    visibility var(--d-mid);
}

.drawer.is-open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

/* Group headings are buttons now. They keep the eyebrow treatment the old
   .drawer__title had, so the drawer reads the same when everything is shut. */
.drawer__group {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  width: 100%;
  margin-top: var(--sp-4);
  padding: 0.85rem 0;
  background: none;
  border: 0;
  border-bottom: 1px solid rgba(var(--rgb-white), 0.07);
  cursor: pointer;
  text-align: left;
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--cyan-400);
}

.drawer__group .chev {
  flex: none;
  width: 18px;
  height: 18px;
  transition: transform var(--d-mid) var(--e-out);
}

.drawer__group[aria-expanded="true"] .chev {
  transform: rotate(180deg);
}

/* Indented so the hierarchy is legible once a group is open. Needs the extra
   .drawer to outrank `.drawer a { padding: 0.7rem 0 }` below, which is the
   same specificity, comes later, and resets padding-left via the shorthand. */
.drawer .drawer__panel a {
  padding-left: var(--sp-4);
}

.drawer a {
  display: block;
  padding: 0.7rem 0;
  color: var(--on-dark);
  border-bottom: 1px solid rgba(var(--rgb-white), 0.07);
}

.drawer a:hover {
  color: var(--cyan-300);
}

/* `.drawer a` is (0,1,1) and outranks `.btn` (0,1,0), so without restating
   these the quote button inherits display:block, the drawer's vertical-only
   padding and its divider border — the label ends up jammed against the left
   edge with a stray line under the pill. */
.drawer .btn {
  display: inline-flex;
  width: 100%;
  margin-top: var(--sp-6);
  padding: 0.95rem 1.75rem;
  border-bottom: 0;
}


/* --------------------------------------------------------------------------
   Mobile touch targets

   Text links sit at 24-25px tall, which clears the 24px WCAG 2.5.8 floor but
   is uncomfortable on a phone. Vertical padding brings them to 44px without
   changing the visual rhythm — the list gaps shrink by the same amount, so the
   columns keep their spacing.
   -------------------------------------------------------------------------- */
@media (max-width: 900px) {
  .footer__link,
  .footer__contact {
    padding-block: 0.6rem;
  }

  .footer li + li {
    margin-top: 0; /* padding above now supplies the gap */
  }

  /* "View Service" inside the service cards. */
  .link-arrow {
    padding-block: 0.65rem;
  }
}

/* ==========================================================================
   SECTIONS — copied from assets/css/sections.css
   ========================================================================== */
/* ==========================================================================
   Sections — the twelve scenes
   ========================================================================== */

/* --------------------------------------------------------------------------
   02 · Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding-top: calc(var(--nav-h) + var(--sp-6));
  padding-bottom: var(--sp-9);
  background: var(--grad-hero);
  color: var(--on-dark);
  overflow: clip;
  /* Gives .hero__inner a 3D context so its scroll dolly (translateZ) reads as
     depth rather than a flat fade. .hero__stage nests its own for the layers. */
  perspective: 1200px;
}

/* ---- 3D stage ------------------------------------------------------------
   A single perspective wraps every background layer. Each layer declares its
   depth in px via data-depth; JS applies translateZ plus a compensating scale
   so the layer still fills the frame, then tilts the whole space toward the
   pointer. The result is genuine parallax between the footage, the glows and
   the copy — not a 2D fake.
   -------------------------------------------------------------------------- */
.hero__stage {
  position: absolute;
  inset: 0;
  z-index: 0;
  perspective: 1200px;
  perspective-origin: 50% 45%;
  overflow: hidden;
}

.hero__space {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  will-change: transform;
}

/* Composed transform: --py is the scroll parallax offset (GSAP), --z the depth,
   --zs the scale that cancels the size change translateZ would cause. Defaults
   make an untouched layer sit flat and correct, so no-JS is the neutral state. */
.hero__layer {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  backface-visibility: hidden;
  transform: translate3d(0, var(--py, 0px), var(--z, 0px)) scale(var(--zs, 1));
}

/* ---- Establishing shot ---------------------------------------------------- */
.hero__bg {
  overflow: hidden;
}

.hero__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Footage is warm golden-hour; pull it toward the brand blue and drop the
     exposure so the headline stays legible over it. */
  filter: saturate(0.66) contrast(1.08) brightness(0.6) hue-rotate(-12deg);
  transform: scale(1.08);
}

/* ---- Floating panels ------------------------------------------------------ */
.hero__panels {
  pointer-events: none;
}

.hero-panel {
  position: absolute;
  margin: 0;
  border-radius: var(--r-lg);
  overflow: hidden;
  transform: translate3d(0, var(--py, 0px), var(--z, 0px)) scale(var(--zs, 1)) rotate(var(--rot, 0deg));
  box-shadow: 0 40px 80px -30px rgba(0, 0, 0, 0.75), 0 0 0 1px rgba(var(--rgb-white), 0.09);
  backface-visibility: hidden;
}

.hero-panel img {
  width: 100%;
  height: auto;
  display: block;
  filter: saturate(0.82) contrast(1.05) brightness(0.86);
  border: 1px solid;
}

/* A cool rim so panels read as glass-edged rather than pasted on. */
.hero-panel::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(160deg, rgba(var(--rgb-cyan), 0.14), transparent 45%, rgba(var(--rgb-ink), 0.45));
  pointer-events: none;
}

.hero-panel figcaption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1;
  padding: var(--sp-5) var(--sp-4) var(--sp-4);
  font-size: var(--fs-caption);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--paper-50);
  background: linear-gradient(transparent, rgba(var(--rgb-ink), 0.85));
}

/* Cascading right-hand cluster. Depth order is set by data-depth in the
   markup; these are just the positions. */
.hero-panel--bathroom {
  top: 17%;
  right: 30%;
  width: 20vw;
  --rot: -3deg;
}

.hero-panel--kitchen {
  top: 31%;
  right: 12%;
  width: 24vw;
  --rot: 2deg;
}

.hero-panel--products {
  top: 67%;
  right: 36%;
  width: 15vw;
  --rot: -4deg;
}

/* The only frame with real Living Fresh branding — largest and frontmost. */
.hero-panel--cleaner {
  top: 53%;
  right: 14%;
  width: 30vw;
  --rot: 1.5deg;
}

/* Cinematic vignette — pulls the eye to centre and protects the headline. */
.hero__vignette {
  background: radial-gradient(75% 75% at 50% 45%, transparent 35%, rgba(var(--rgb-ink), 0.55) 78%, rgba(var(--rgb-ink), 0.88) 100%);
  pointer-events: none;
}

/* Copy scrim — the footage is bright golden-hour interiors, and its highlights
   (windows, sunlight) can hit 200+ luminance right behind the headline. This
   left-weighted wedge keeps the text safely readable on any frame, and doubles
   as composition: dark on the left where the copy sits, clear on the right
   where the footage is worth seeing. */
.hero__copyscrim {
  pointer-events: none;
  background: linear-gradient(100deg, rgb(4 16 28 / 62%) 0%, rgb(4 16 28 / 63%) 32%, rgba(var(--rgb-ink), 0.42) 58%, rgba(var(--rgb-ink), 0.06) 80%, transparent 100%);
}

/* Anamorphic light bloom drifting across the frame. */
.hero__bloom {
  pointer-events: none;
  background: linear-gradient(102deg, transparent 34%, rgba(var(--rgb-cyan), 0.16) 47%, rgba(255, 255, 255, 0.1) 50%, rgba(var(--rgb-cyan), 0.16) 53%, transparent 66%);
  mix-blend-mode: screen;
  opacity: 0.75;
  animation: bloom-drift 19s var(--e-inout) infinite alternate;
}

@keyframes bloom-drift {
  from {
    transform: translate3d(-14%, 0, 0) rotate(-1deg);
  }
  to {
    transform: translate3d(14%, 0, 0) rotate(1deg);
  }
}

/* Split layout: copy on the left, panel cluster on the right.
   Two classes, so this beats `.hero h1 { max-width: 16ch }` on specificity —
   without that the headline box extends past the panels and the two overlap.

   The headline must also come down in size. --fs-display-xl tops out at 8vw,
   which is ~102px at 1240 — in a 52% column that puts every single word on its
   own line and pushes the hero past 1100px tall. This scale keeps it to three
   lines with two words on most of them. */
@media (min-width: 1080px) {
  .hero .hero__inner > * {
    max-width: min(100%, 50%);
  }

  .hero h1 {
    font-size: clamp(3rem, 5.4vw, 5rem);
    max-width: min(100%, 50%);
  }

  /* The half-width column changes the vertical budget. At 56px the three trust
     items need 622px in a 592px row and wrap to three lines — 196px of height
     that pushes the CTA below the fold on a 840px-tall laptop. Tightening the
     gap and the block spacing keeps the whole hero inside one viewport. */
  /* Note the `.hero` prefix on both: the base .hero__trust / .hero__cta rules
     are declared later in this file, so at equal specificity they would win. */
  /* The trust strip sits at the bottom of the column, below where the panel
     cluster reaches, so it can run wider than the rest of the copy. It needs
     ~574px for one line; a straight 50% column is only 506px at 1100. */
  .hero .hero__trust {
    gap: clamp(1.25rem, 2.2vw, 2rem);
    max-width: min(100%, 58%);
  }

  .hero .hero__cta {
    margin-bottom: var(--sp-6);
  }

  .hero {
    padding-bottom: var(--sp-7);
  }
}

/* Below 1080px the copy runs full width, so any panel placement collides with
   the headline. The cluster is a wide-viewport flourish — drop it entirely and
   let the establishing shot carry the hero. The team photo still appears in
   the gallery, so nothing is lost from the page. */
@media (max-width: 1079px) {
  .hero__panels {
    display: none;
  }
}

.hero__orb-1 {
  inset: auto; /* .hero__layer sets inset:0; orbs position themselves */
  width: 46vw;
  height: 46vw;
  max-width: 640px;
  max-height: 640px;
  top: -12%;
  right: -6%;
  opacity: 0.5;
  animation: drift-a 38s var(--e-inout) infinite alternate;
}

.hero__orb-2 {
  inset: auto;
  width: 34vw;
  height: 34vw;
  max-width: 460px;
  max-height: 460px;
  bottom: -14%;
  left: -8%;
  opacity: 0.35;
  background: radial-gradient(closest-side, rgba(var(--rgb-mint), 0.45), transparent 70%);
  animation: drift-b 46s var(--e-inout) infinite alternate;
}

@keyframes drift-a {
  to {
    transform: translate3d(-7%, 9%, 0) scale(1.14);
  }
}

@keyframes drift-b {
  to {
    transform: translate3d(9%, -8%, 0) scale(1.2);
  }
}

.hero__inner {
  position: relative;
  z-index: 4;
  width: 100%;
  transform-style: preserve-3d;
}

/* The copy is a parallax layer too — slightly faster than the page, so it
   lifts away ahead of the imagery. Gated on .is-3d so the no-JS and
   reduced-motion states keep it perfectly flat. */
.hero.is-3d .hero__inner {
  transform: translate3d(0, var(--py, 0px), var(--z, 0px)) scale(var(--zs, 1));
  will-change: transform, opacity;
}

.hero__eyebrow {
  color: var(--cyan-300);
  margin-bottom: var(--sp-5);
}

/* Solid white is the safe default and the no-JS state. The gradient is applied
   per-word by animations.js — see applyHeadlineGradient() for why it can't
   live on the h1 itself. If that ever fails, the headline is still readable. */
.hero h1 {
  max-width: 16ch;
  margin-bottom: var(--sp-5);
  color: var(--paper-50);
}

/* Word-by-word masked reveal: each word sits in an overflow-hidden box */
.word {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding-bottom: 0.08em;
}

.word > span {
  display: inline-block;
}

.hero__sub {
  font-size: var(--fs-body-lg);
  font-weight: 500;
  color: var(--cyan-300);
  margin-bottom: var(--sp-3);
}

.hero__lede {
  max-width: 52ch;
  color: var(--on-dark);
  opacity: 0.86;
  margin-bottom: var(--sp-6);
}

.hero__cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  margin-bottom: var(--sp-8);
}

.hero__trust {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(1.5rem, 4vw, 3.5rem);
  padding-top: var(--sp-5);
  border-top: 1px solid rgba(var(--rgb-white), 0.12);
  max-width: 720px;
  padding: 30px;
  border-radius: 15px;
  background: radial-gradient(closest-side, rgba(var(--rgb-cyan), 0.55), #204b71e0 70%);
  
}

.trust-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.trust-item b {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.4vw, 2.1rem);
  font-weight: 700;
  color: var(--paper-50);
  line-height: 1;
}

.trust-item > span {
  /* Direct child only: a descendant selector also matches the counter span
     inside <b>, shrinking "13" to caption size beside a full-size "+". */
  font-size: var(--fs-caption);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--on-dark-muted);
}

/* --------------------------------------------------------------------------
   03 · Intro
   -------------------------------------------------------------------------- */
.intro__grid {
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  gap: clamp(2rem, 6vw, 5.5rem);
  align-items: center;
}

.intro__media {
  position: relative;
  min-height: clamp(360px, 46vw, 620px);
}

/* Oversized ghosted "13" sitting behind the image pair */
.intro__ghost {
  position: absolute;
  left: -4%;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(11rem, 26vw, 26rem);
  line-height: 0.8;
  color: rgba(var(--rgb-blue), 0.06);
  pointer-events: none;
  user-select: none;
  z-index: 0;
}

.intro__img {
  position: absolute;
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--shadow-lg);
  z-index: 1;
}

.intro__img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.9) contrast(1.04);
}

.intro__img--a {
  width: 62%;
  aspect-ratio: 4 / 5;
  left: 0;
  top: 0;
}

.intro__img--b {
  width: 52%;
  aspect-ratio: 1 / 1;
  right: 0;
  bottom: 0;
  border: 6px solid var(--paper-50);
}

.intro__badge {
  position: absolute;
  left: 44%;
  top: 8%;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 0.7rem 1.1rem;
  border-radius: var(--r-pill);
  background: var(--paper-50);
  box-shadow: var(--shadow-md);
  font-size: var(--fs-caption);
  font-weight: 700;
  color: var(--slate-700);
  white-space: nowrap;
}

.intro__badge i {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--mint-400);
  box-shadow: 0 0 0 4px rgba(var(--rgb-mint), 0.22);
}

.intro__copy h2 {
  margin-bottom: var(--sp-5);
}

.intro__copy p + p {
  margin-top: var(--sp-4);
}

.intro__highlight {
  display: block;
  margin-top: var(--sp-5);
  padding: var(--sp-4) var(--sp-5);
  border-left: 3px solid var(--blue-500);
  background: var(--paper-100);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  font-weight: 500;
  color: var(--slate-700);
}

.intro__list {
  display: grid;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
}

.intro__list li {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  font-size: 0.98rem;
}

.intro__list svg {
  flex: none;
  width: 20px;
  height: 20px;
  margin-top: 3px;
  color: var(--blue-500);
}

.intro__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  margin-top: var(--sp-6);
}

/* --------------------------------------------------------------------------
   04 · Services — card grid on a tinted light band

   Was a GSAP-pinned horizontal track. A pinned section is the most fragile
   thing on a page: it writes a spacer into layout, has to be measured before
   images settle, and competes with every other trigger for refresh order.
   A plain grid has none of that and shows all six services at once.
   -------------------------------------------------------------------------- */
.services {
  position: relative;
  /* Dark band, as before. It sits between band-paper (#fff) and
     band-paper-100 (#f4f8fc), so it is the section that keeps the page's
     light/dark rhythm from flattening into three pale bands in a row.
     The cards stay white and simply pop off it. */
  background: var(--ink-800);
  color: var(--on-dark-muted);
  padding-block: var(--section-y);
  overflow: clip;
}

.services__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-6);
  padding-bottom: clamp(2rem, 4vw, 3.5rem);
}

.services__head h2 {
  max-width: 16ch;
  color: var(--on-dark);
}

.services__grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(1rem, 1.8vw, 1.75rem);
}

@media (max-width: 1180px) {
  .services__grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 900px) {
  .services__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 560px) {
  .services__grid {
    grid-template-columns: 1fr;
  }

  .services__head {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sp-4);
  }
}

/* --------------------------------------------------------------------------
   05 · Working process — the light path
   -------------------------------------------------------------------------- */
.process {
  position: relative;
}

.process__grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1.5rem, 4vw, 3.5rem);
  margin-top: clamp(2rem, 4vw, 3rem);
}

.process__path {
  position: absolute;
  inset-inline: 8%;
  top: 3px;
  height: 90px;
  z-index: 0;
  pointer-events: none;
}

/* The SVG is stretched with preserveAspectRatio="none", so the horizontal and
   vertical scales differ and would squash the dots into ellipses at narrower
   widths. non-scaling-stroke renders the stroke in screen units instead, which
   keeps every dot round and evenly spaced at any container size. */
.process__path path {
  fill: none;
  stroke-width: 3;
  stroke-linecap: round;
  stroke-dasharray: 0.1 11; /* near-zero dash + round cap = a dot */
  vector-effect: non-scaling-stroke;
}

.process__path .track {
  stroke: var(--paper-300);
}

/* Dots travel left to right along the path. One full cycle is the dash
   period (0.1 + 11), so the loop is seamless. */
.process__path .draw {
  stroke: url(#processGrad);
  animation: dot-flow 1.1s linear infinite;
}

@keyframes dot-flow {
  to {
    stroke-dashoffset: -11.1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .process__path .draw {
    animation: none;
  }
}

.process__step {
  position: relative;
  z-index: 1;
  text-align: center;
}

.process__node {
  position: relative;
  display: grid;
  place-items: center;
  width: 92px;
  height: 92px;
  margin: 0 auto var(--sp-5);
  border-radius: 50%;
  background: var(--paper-50);
  border: 1px solid var(--paper-200);
  box-shadow: var(--shadow-md);
  color: var(--blue-500);
}

.process__node svg {
  width: 32px;
  height: 32px;
}

.process__node::after {
  content: "";
  position: absolute;
  inset: -8px;
  border-radius: 50%;
  border: 2px solid var(--mint-400);
  opacity: 0;
  transform: scale(0.85);
}

.process__step.is-lit .process__node::after {
  animation: ring 1.1s var(--e-out) forwards;
}

@keyframes ring {
  0% {
    opacity: 0.85;
    transform: scale(0.85);
  }
  100% {
    opacity: 0;
    transform: scale(1.35);
  }
}

/* ---- Node ambience -------------------------------------------------------
   Three layers, all decorative, all independent of the dotted path:

   .process__node::before   a dashed halo ring rotating OUTSIDE the disc. This
                            is the layer you actually notice. Being outside the
                            white circle, it cannot affect icon contrast at all,
                            so it can be as visible as it likes.
   .process__aura::before   a soft light sweeping around INSIDE the disc. Uses
                            cyan/mint only — both are high-luminance, so the
                            wash stays pale and the dark blue icon keeps its
                            contrast. Never blue, which would darken it.
   .process__aura::after    one slow drifting blob, for a bit of organic depth.

   Steps are offset with negative delays so the row never pulses in unison.
   All of it stops under prefers-reduced-motion.
   -------------------------------------------------------------------------- */
.process__node {
  transition: transform var(--d-mid) var(--e-out), box-shadow var(--d-mid) var(--e-out);
}

/* Beacon pulse. Replaces the rotating dashed halo, which read as the dotted
   path spinning around the disc — same motif twice. A ring that expands and
   fades is a different gesture entirely.

   The cycle is 2.8s (~0.36Hz), far below the 3Hz flash threshold, so it reads
   as a slow blink rather than anything strobing. */
.process__node::before {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  border: 2px solid var(--cyan-400);
  opacity: 0;
  animation: node-ping 2.8s var(--e-out) infinite;
}

@keyframes node-ping {
  0% {
    transform: scale(0.96);
    opacity: 0;
  }
  18% {
    opacity: 0.75;
  }
  100% {
    transform: scale(1.42);
    opacity: 0;
  }
}

/* Inner glow, blinking in sync with the ring. Still ring-masked so the light
   sits around the icon and the centre of the disc stays near-white. */
.process__aura {
  position: absolute;
  inset: 1px;
  border-radius: 50%;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}

.process__aura::before {
  content: "";
  position: absolute;
  inset: 6%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(var(--rgb-cyan), 0.55), transparent 74%);
  filter: blur(10px);
  -webkit-mask: radial-gradient(closest-side, transparent 40%, #000 72%);
  mask: radial-gradient(closest-side, transparent 40%, #000 72%);
  opacity: 0.25;
  animation: node-glow 2.8s var(--e-inout) infinite;
}

@keyframes node-glow {
  0%,
  100% {
    opacity: 0.22;
  }
  22% {
    opacity: 0.85;
  }
}

/* Slow drifting blob underneath the sweep. */
.process__aura::after {
  content: "";
  position: absolute;
  right: -14%;
  bottom: -16%;
  width: 76%;
  height: 76%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(var(--rgb-cyan), 0.32), transparent 70%);
  filter: blur(12px);
  animation: aura-drift 17s var(--e-inout) infinite alternate;
}

@keyframes aura-drift {
  to {
    transform: translate(-30%, -24%) scale(1.18);
  }
}

/* Icon rides above both layers and breathes very slightly. */
.process__node svg {
  position: relative;
  z-index: 1;
  animation: node-breathe 2.8s var(--e-inout) infinite;
}

@keyframes node-breathe {
  0%,
  100% {
    transform: scale(1);
  }
  22% {
    transform: scale(1.07);
  }
}

/* Offsets so the three steps never move together. :nth-of-type, not
   :nth-child — the .process__path <svg> is the grid's first child and would
   shift every index by one. */
.process__step:nth-of-type(2) .process__node::before {
  animation-delay: 0.9s;
}
.process__step:nth-of-type(3) .process__node::before {
  animation-delay: 1.8s;
}
.process__step:nth-of-type(2) .process__aura::before {
  animation-delay: 0.9s;
}
.process__step:nth-of-type(3) .process__aura::before {
  animation-delay: 1.8s;
}
.process__step:nth-of-type(2) .process__aura::after {
  animation-delay: -5s;
}
.process__step:nth-of-type(3) .process__aura::after {
  animation-delay: -10s;
}
.process__step:nth-of-type(2) .process__node svg {
  animation-delay: 0.9s;
}
.process__step:nth-of-type(3) .process__node svg {
  animation-delay: 1.8s;
}

/* Hover: lift the node and brighten the halo. */
.process__step:hover .process__node {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
}

.process__step:hover .process__node::before {
  border-color: var(--mint-400);
}

@media (prefers-reduced-motion: reduce) {
  .process__node::before,
  .process__aura::before,
  .process__aura::after,
  .process__node svg {
    animation: none;
  }
}


.process__num {
  display: block;
  font-family: var(--font-display);
  font-size: var(--fs-caption);
  letter-spacing: 0.18em;
  color: var(--blue-400);
  margin-bottom: var(--sp-2);
}

.process__step h3 {
  font-size: var(--fs-heading-sm);
  margin-bottom: var(--sp-3);
}

.process__step p {
  max-width: 34ch;
  margin-inline: auto;
  font-size: 0.95rem;
}

@media (max-width: 860px) {
  .process__grid {
    grid-template-columns: 1fr;
  }
  .process__path {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   06 · Why choose us — sticky scroll-scale reveal

   Borrowed from the sticky-video-scale pattern: a tall scroll region with a
   sticky stage inside it. As you scroll, an image frame opens from a small
   card to full bleed, and the four reasons surface on top of it — an aperture
   letting light into a clean room, which is the page's whole concept.

   The scale is driven by scroll progress, not time, so the user controls it.
   -------------------------------------------------------------------------- */
.why {
  /* This section drops the .section class, so it loses that rule's position and
     clipping. Both are needed: without `position: relative` the absolutely
     positioned .sweep escapes to an outer containing block and the clip below
     never applies to it. Clip the x axis only — `overflow: hidden` here would
     make .why a scroll container and break the sticky stage. */
  position: relative;
  padding-block: 0;
  overflow-x: clip;
}

/* Height is applied by JS (.is-scaling) so the no-JS and reduced-motion states
   stay an ordinary-length section rather than 260vh of dead space. */
.why__scroll {
  position: relative;
}

.why.is-scaling .why__scroll {
  height: 260vh;
}

.why__stage {
  display: grid;
  place-items: center;
  padding-block: var(--section-y);
}

.why.is-scaling .why__stage {
  position: sticky;
  top: 0;
  height: 100svh;
  padding-block: 0;
  overflow: hidden;
}

/* ---- The aperture --------------------------------------------------------- */
.why__frame {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  border-radius: var(--r-xl);
  /* --open: 0 = small card, 1 = full bleed. JS drives it; the default of 1
     means a static section still looks finished. */
  transform: scale(calc(0.46 + 0.54 * var(--open, 1)));
  will-change: transform;
}

/* Without the effect the frame is just a background layer. */
.why:not(.is-scaling) .why__frame {
  display: none;
}

.why__frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(calc(0.4 + 0.42 * var(--open, 1)))
          brightness(calc(0.34 + 0.26 * var(--open, 1)))
          contrast(1.06);
}

.why__frame-scrim {
  position: absolute;
  inset: 0;
  background: radial-gradient(78% 78% at 50% 42%, transparent 30%, rgba(var(--rgb-ink), 0.6) 76%, rgba(var(--rgb-ink), 0.92) 100%),
    linear-gradient(180deg, rgba(var(--rgb-ink), 0.55), rgba(var(--rgb-ink), 0.2) 40%, rgba(var(--rgb-ink), 0.85));
}

/* ---- Content on top -------------------------------------------------------- */
.why__content {
  position: relative;
  z-index: 1;
  width: 100%;
}

.why__head {
  margin-bottom: clamp(1.75rem, 3.5vw, 3rem);
}

.why__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(1rem, 2vw, 1.75rem);
}

/* Tiles rise in sequence once the frame is open. --lift is set per tile by JS. */
.why.is-scaling .why__tile {
  opacity: var(--lift, 1);
  transform: translateY(calc((1 - var(--lift, 1)) * 34px));
  will-change: transform, opacity;
}

/* Over a photograph the tiles need real glass, not a faint wash. */
.why.is-scaling .why__tile {
  background: linear-gradient(160deg, rgba(var(--rgb-ink), 0.72), rgba(var(--rgb-ink), 0.58));
  backdrop-filter: blur(14px) saturate(130%);
  border-color: rgba(var(--rgb-white), 0.14);
}

/* At four across the tiles get tight; drop the body copy to two lines of room. */
@media (min-width: 1200px) {
  .why__grid {
    grid-template-columns: repeat(4, 1fr);
  }

  .why.is-scaling .why__tile {
    padding: clamp(1.25rem, 1.8vw, 1.75rem);
  }

  .why.is-scaling .why__tile h3 {
    font-size: 1.05rem;
  }

  .why.is-scaling .why__tile p {
    font-size: 0.88rem;
  }

  .why.is-scaling .glow-tile__icon {
    width: 46px;
    height: 46px;
    margin-bottom: var(--sp-4);
  }
}

@media (max-width: 760px) {
  .why__grid {
    grid-template-columns: 1fr;
  }
}

/* Scroll-linked text-mask reveal: grey fills to white */
.mask-fill {
  background-image: linear-gradient(90deg, var(--paper-50) 50%, rgba(var(--rgb-white), 0.22) 50%);
  background-size: 200% 100%;
  /* Filled is the rest state, so without JS the heading is solid white rather
     than 22% grey. The tween starts it at 100% and scrubs back to 0. */
  background-position: 0 0;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* --------------------------------------------------------------------------
   07 · Before / after
   -------------------------------------------------------------------------- */
.ba {
  position: relative;
  background: var(--ink-900);
  color: var(--on-dark);
}

.ba__stage {
  position: relative;
  aspect-ratio: 16 / 9;
  max-height: 78vh;
  border-radius: var(--r-xl);
  overflow: hidden;
  box-shadow: var(--shadow-lg);
  touch-action: pan-y;
  user-select: none;
  cursor: ew-resize;
}

.ba__layer {
  position: absolute;
  inset: 0;
}

.ba__layer img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* "After" — the clean, graded state */
.ba__after img {
  filter: saturate(1.02) contrast(1.05) brightness(1.04);
}

/* "Before" — same frame, degraded: hazy, dull, cooler */
.ba__before {
  clip-path: inset(0 calc(100% - var(--split, 50%)) 0 0);
}

.ba__before img {
  filter: saturate(0.42) contrast(0.88) brightness(0.72) sepia(0.18) blur(0.4px);
}

.ba__before::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(60, 55, 45, 0.35), rgba(30, 30, 28, 0.45));
  mix-blend-mode: multiply;
}

.ba__tag {
  position: absolute;
  top: var(--sp-5);
  z-index: 3;
  padding: 0.4rem 0.9rem;
  border-radius: var(--r-pill);
  font-size: var(--fs-caption);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  backdrop-filter: blur(8px);
}

.ba__tag--before {
  left: var(--sp-5);
  background: rgba(var(--rgb-ink), 0.6);
  color: var(--paper-50);
  border: 1px solid rgba(var(--rgb-white), 0.2);
}

.ba__tag--after {
  right: var(--sp-5);
  background: rgba(var(--rgb-mint), 0.16);
  color: var(--mint-400);
  border: 1px solid rgba(var(--rgb-mint), 0.4);
}

.ba__handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--split, 50%);
  z-index: 4;
  width: 2px;
  background: var(--cyan-300);
  box-shadow: 0 0 22px rgba(var(--rgb-cyan), 0.8);
  transform: translateX(-1px);
}

.ba__grip {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: rgba(var(--rgb-ink), 0.7);
  border: 1px solid var(--cyan-300);
  color: var(--cyan-300);
  backdrop-filter: blur(10px);
  cursor: ew-resize;
  transition: transform var(--d-mid) var(--e-spring), box-shadow var(--d-mid) var(--e-out);
}

.ba__grip:hover,
.ba__stage:focus-within .ba__grip {
  transform: translate(-50%, -50%) scale(1.1);
  box-shadow: var(--glow-cyan);
}

.ba__grip svg {
  width: 22px;
  height: 22px;
}

.ba__caption {
  margin-top: var(--sp-4);
  font-size: var(--fs-caption);
  color: var(--on-dark-muted);
  text-align: center;
}

/* --------------------------------------------------------------------------
   08 · Stats band — lit stage

   Four figures standing in their own columns of light. The band is deliberately
   the darkest thing on the page so the light does the work: a shaft rises
   behind each figure, hairlines frame the band top and bottom, and a beam
   drifts across the whole width. No photograph here on purpose — this sits
   between two image-heavy sections and works better as a cut between them.
   -------------------------------------------------------------------------- */
.stats {
    position: relative;
    background: radial-gradient(120% 140% at 50% 120%, #0a2138 0%, #061627 45%, #24405d 100%);
    padding-block: clamp(2.5rem, 7vw, 0rem);
    overflow: clip;
}

/* Hairlines, brightest at centre — frames the band like a title card. */
.stats::before,
.stats::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(var(--rgb-cyan), 0.5) 50%, transparent);
  pointer-events: none;
  z-index: 2;
}

.stats::before {
  top: 0;
}

.stats::after {
  bottom: 0;
}

/* Ambient key light from below, as if the figures are lit from the floor. */
.stats__glow {
  position: absolute;
  inset-inline: -10%;
  bottom: -50%;
  height: 130%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(var(--rgb-cyan), 0.09), transparent 70%);
  pointer-events: none;
}

/* Anamorphic beam drifting across the band. */
.stats__beam {
  position: absolute;
  inset-block: 0;
  width: 42%;
  background: linear-gradient(100deg, transparent, rgba(var(--rgb-white), 0.05) 45%, rgba(var(--rgb-cyan), 0.1) 50%, rgba(var(--rgb-white), 0.05) 55%, transparent);
  filter: blur(8px);
  pointer-events: none;
  animation: stats-beam 19s var(--e-inout) infinite alternate;
}

@keyframes stats-beam {
  from {
    transform: translateX(-40%);
  }
  to {
    transform: translateX(260%);
  }
}

.stats__grid {
  position: relative;
  z-index: 1;
  display: grid;
  /* minmax(0, 1fr): the default min-content floor lets the widest word
     ("Certified") stretch its column and knock the dividers off rhythm. */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0;
  text-align: center;
}

.stat {
  position: relative;
  padding-inline: clamp(0.5rem, 1.6vw, 1.5rem);
  padding-block: var(--sp-4);
  transition: transform var(--d-mid) var(--e-out);
}

/* ---- The column of light -------------------------------------------------
   A shaft rising behind each figure. Widest and softest at the bottom, so it
   reads as a beam hitting the floor rather than a flat rectangle.
   -------------------------------------------------------------------------- */
/* Deliberately an <i>, not a <span>: `.stat > span` below styles the label,
   and a span here would match it — picking up position:relative and dropping
   into the flow as a 246px block that shoves the figure out of the band. */
.stat__beam {
  position: absolute;
  left: 50%;
  bottom: -30%;
  width: 78%;
  height: 190%;
  transform: translateX(-50%);
  background: linear-gradient(
    to top,
    rgba(var(--rgb-cyan), 0.13) 0%,
    rgba(var(--rgb-blue), 0.06) 38%,
    transparent 74%
  );
  -webkit-mask: radial-gradient(60% 70% at 50% 100%, #000 20%, transparent 75%);
  mask: radial-gradient(60% 70% at 50% 100%, #000 20%, transparent 75%);
  filter: blur(10px);
  opacity: 0.9;
  pointer-events: none;
  animation: shaft-breathe 7s var(--e-inout) infinite alternate;
}

@keyframes shaft-breathe {
  to {
    opacity: 0.45;
    transform: translateX(-50%) scaleY(1.08);
  }
}

/* Offset so the four shafts never pulse together. */
.stat:nth-child(2) .stat__beam {
  animation-delay: -1.8s;
}
.stat:nth-child(3) .stat__beam {
  animation-delay: -3.5s;
}
.stat:nth-child(4) .stat__beam {
  animation-delay: -5.2s;
}

/* Divider rules run the full height and fade at both ends, so they read as
   shafts of light between the columns rather than table borders. */
.stat + .stat::before {
  content: "";
  position: absolute;
  left: 0;
  top: 6%;
  bottom: 6%;
  width: 1px;
  background: linear-gradient(180deg, transparent, rgba(var(--rgb-cyan), 0.3) 50%, transparent);
}

.stat:hover {
  transform: translateY(-4px);
}

.stat:hover .stat__beam {
  opacity: 1;
  animation-play-state: paused;
}

.stat b {
  position: relative;
  display: block;
  font-family: var(--font-display);
  /* Capped so the longest word stat fits one equal column without wrapping. */
  font-size: clamp(1.9rem, 3.6vw, 3.4rem);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.025em;
  /* Counters must not reflow the layout as digits change width. */
  font-variant-numeric: tabular-nums;
  /* Not --grad-text: its blue end drops to 2.3:1 over the lit shafts. This ramp
     stays light the whole way, so the darkest stop still clears 4.9:1. */
  background: linear-gradient(105deg, #ffffff 0%, #cdeeff 45%, #7dd9ff 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* A little bloom under the figure, as if it is emitting light. */
  filter: drop-shadow(0 6px 22px rgba(var(--rgb-cyan), 0.28));
}

/* Direct child only — a descendant selector also matches the counter span
   inside <b>, which shrank "13" to caption size and, with display:block,
   pushed the "+" onto its own line. */
.stat > span {
  position: relative;
  display: block;
  margin-top: var(--sp-4);
  font-size: var(--fs-caption);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #b9cbda; /* brighter than --on-dark-muted: the shafts lift the bg */
  transition: color var(--d-mid) var(--e-out);
}

/* Small rule under the label, echoing the section hairlines. */
.stat > span::after {
  content: "";
  display: block;
  width: 22px;
  height: 1px;
  margin: var(--sp-4) auto 0;
  background: rgba(var(--rgb-cyan), 0.35);
  transition: width var(--d-mid) var(--e-out), background-color var(--d-mid) var(--e-out);
}

.stat:hover > span {
  color: var(--cyan-300);
}

.stat:hover > span::after {
  width: 44px;
  background: var(--cyan-300);
}

@media (max-width: 860px) {
  .stats__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    row-gap: clamp(2.5rem, 6vw, 3.5rem);
  }

  /* With two columns the divider belongs on the left of every second cell. */
  .stat + .stat::before {
    display: none;
  }

  .stat:nth-child(even)::before {
    content: "";
    /* The rule above hides every divider; this one must switch it back on.
       Same specificity, so declaring `content` alone would not have. */
    display: block;
    position: absolute;
    left: 0;
    top: 6%;
    bottom: 6%;
    width: 1px;
    background: linear-gradient(180deg, transparent, rgba(var(--rgb-cyan), 0.3) 50%, transparent);
  }
}

@media (max-width: 480px) {
  .stats__grid {
    grid-template-columns: 1fr;
  }

  .stat:nth-child(even)::before {
    display: none;
  }

  .stat__beam {
    width: 60%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .stats__beam,
  .stat__beam {
    animation: none;
  }
}

/* --------------------------------------------------------------------------
   09 · Testimonials
   -------------------------------------------------------------------------- */
.quotes {
  text-align: center;
}

/* ---- Interactive reveal background ---------------------------------------
   Two photographs of the same room — empty, and being cleaned. The lower one
   (reveal) is a plain <img>; the upper one (no-reveal) is painted into a canvas
   on top and erased away under the pointer, so wiping the background performs
   the service the section is about.

   Both images are exported at identical pixel dimensions and the canvas uses
   the same cover maths as object-fit: cover, so the two stay locked together
   at any viewport size.

   The section's own layout, cards and spacing are untouched — this sits behind
   .container, which is raised above it below.
   -------------------------------------------------------------------------- */
.reveal-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

/* Swap the OS cursor for the drawn one, but only where a pointer exists. */
@media (hover: hover) and (pointer: fine) {
  .quotes.has-reveal {
    cursor: none;
  }
}

.reveal-bg__under,
.reveal-bg__cover {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

.reveal-bg__under {
  object-fit: cover;
  object-position: center;
  /* The revealed photograph is untouched — full colour, full brightness. It is
     the payoff inside the circle, so nothing is laid over it. */
  filter: none;
}

/* The cover is a wrapper, not an image, so the mask cuts through the
   photograph and its white tint together. Tinting the section instead would
   also tint what the circle reveals. */
.reveal-bg__cover {
  --x: 50%;
  --y: 50%;
  --r: 0px;
  -webkit-mask-image: radial-gradient(circle var(--r) at var(--x) var(--y), transparent 0%, transparent 58%, #000 100%);
  mask-image: radial-gradient(circle var(--r) at var(--x) var(--y), transparent 0%, transparent 58%, #000 100%);
  will-change: mask-image;
}

.reveal-bg__cover img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  filter: saturate(0.28) contrast(0.92);
}

/* White wash over the covering photograph only — never over the revealed one.
   Measured sweep against the darkest 5% of the copy band: 0.70 leaves the
   customer names at 4.86:1, 0.76 at 5.36:1, 0.90 at 6.64:1. 0.76 is the
   lightest setting with real headroom, so the covering photograph stays as
   visible as legibility allows. */
.reveal-bg__tint {
  position: absolute;
  inset: 0;
  background: rgba(255, 255, 255, 0.76);
}

/* ---- Wiping-hand cursor --------------------------------------------------
   Desktop only: there is no cursor to replace on touch, and the finger already
   marks the spot. Sits above the wash so it stays legible over the photograph.
   -------------------------------------------------------------------------- */
.reveal-cursor {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  width: 54px;
  height: 54px;
  margin: -27px 0 0 -27px;
  color: var(--ink-800);
  pointer-events: none;
  opacity: 0;
  transform: translate3d(-100px, -100px, 0) scale(0.8);
  transition: opacity var(--d-mid) var(--e-out), scale var(--d-mid) var(--e-spring);
  filter: drop-shadow(0 3px 10px rgba(255, 255, 255, 0.9));
}

.reveal-cursor svg {
  width: 100%;
  height: 100%;
}

.reveal-bg.is-live .reveal-cursor {
  opacity: 1;
}

/* No pointer: no custom cursor, and the default one stays as it is. */
@media (hover: none), (pointer: coarse) {
  .reveal-cursor {
    display: none;
  }
}

/* Raise the existing content above the background. Nothing else about it
   changes — no new spacing, no new box. */
.quotes > .container {
  position: relative;
  z-index: 1;
}

/* With the overlay gone, the copy sits directly on photographs that run from
   near-black to near-white within the same band — no single text colour clears
   4.5:1 against both. A layered dark halo travels with the glyphs instead, so
   the picture keeps its own colour everywhere except the few pixels hugging
   the letterforms. White type + dark halo is the pairing that survives both
   ends of that range. */
/* The copy is dark on a white-washed ground. The halo is therefore light, not
   dark: it only has to hold the letterforms together for the second or so the
   circle passes underneath and exposes the untinted photograph. */
.quotes .eyebrow,
.quotes h2,
.quote blockquote,
.quote cite {
  text-shadow: 0 1px 3px rgba(255, 255, 255, 0.9), 0 2px 12px rgba(255, 255, 255, 0.85),
    0 0 26px rgba(255, 255, 255, 0.7);
}

.quote .stars {
  filter: drop-shadow(0 1px 4px rgba(255, 255, 255, 0.9)) drop-shadow(0 0 12px rgba(255, 255, 255, 0.7));
}

.quotes__dot {
  box-shadow: 0 6px 20px rgba(var(--rgb-ink), 0.22);
}

.quotes__stage {
  position: relative;
  min-height: clamp(230px, 26vw, 300px);
  max-width: 900px;
  margin-inline: auto;
}

.quote {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-5);
  opacity: 0;
  visibility: hidden;
  transform: translateY(14px);
  filter: blur(6px);
  transition: opacity var(--d-slow) var(--e-out), transform var(--d-slow) var(--e-out),
    filter var(--d-slow) var(--e-out), visibility var(--d-slow);
}

.quote.is-active {
  opacity: 1;
  visibility: visible;
  transform: none;
  filter: none;
}

.quote blockquote {
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 3vw, 2.35rem);
  font-weight: 600;
  font-style: italic;
  line-height: 1.3;
  letter-spacing: -0.02em;
  color: var(--slate-700);
  max-width: 22ch;
}

.quote cite {
  font-style: normal;
  font-weight: 700;
  color: var(--blue-600);
  letter-spacing: 0.02em;
}

.stars {
  display: flex;
  gap: 4px;
  color: var(--blue-500);
}

.stars svg {
  width: 18px;
  height: 18px;
}

.quotes__rail {
  display: flex;
  justify-content: center;
  gap: var(--sp-4);
  margin-top: clamp(2rem, 4vw, 3rem);
}

.quotes__dot {
  position: relative;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  padding: 0;
  overflow: hidden;
  filter: grayscale(1);
  opacity: 0.5;
  transition: transform var(--d-mid) var(--e-spring), opacity var(--d-mid) var(--e-out),
    filter var(--d-mid) var(--e-out), box-shadow var(--d-mid) var(--e-out);
}

.quotes__dot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.quotes__dot:hover {
  opacity: 0.85;
  filter: grayscale(0.3);
}

.quotes__dot[aria-selected="true"] {
  transform: scale(1.16);
  opacity: 1;
  filter: none;
  box-shadow: 0 0 0 2px var(--paper-50), 0 0 0 4px var(--blue-500);
}

/* --------------------------------------------------------------------------
   10 · Founder
   -------------------------------------------------------------------------- */
.founder__grid {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: clamp(2rem, 6vw, 5rem);
  align-items: center;
}

.founder__media {
  position: relative;
  isolation: isolate;
}

.founder__media img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--r-xl);
  filter: saturate(0.85) contrast(1.06) brightness(0.95);
}

/* Cyan rim light behind the portrait */
.founder__media::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: -8% -8% -8% 14%;
  border-radius: var(--r-xl);
  background: var(--grad-glow);
  opacity: 0.5;
  filter: blur(10px);
}

.founder__badge {
  position: absolute;
  right: -6%;
  bottom: 8%;
  padding: var(--sp-4) var(--sp-5);
  border-radius: var(--r-lg);
  background: rgba(var(--rgb-ink), 0.72);
  border: 1px solid rgba(var(--rgb-cyan), 0.32);
  backdrop-filter: blur(14px);
  box-shadow: var(--shadow-lg);
}

.founder__badge b {
  display: block;
  font-family: var(--font-display);
  font-size: 1.5rem;
  color: var(--paper-50);
}

.founder__badge span {
  font-size: var(--fs-caption);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--cyan-300);
}

@media(max-width:1500px){
h2.display-lg {
    font-size:3.4rem;
}
}
.founder__copy p {
  color: var(--on-dark);
  opacity: 0.86;
}

.founder__copy p + p {
  margin-top: var(--sp-4);
}

.founder__sign {
  margin-top: var(--sp-6);
  padding-top: var(--sp-5);
  border-top: 1px solid rgba(var(--rgb-white), 0.12);
}

.founder__sign b {
  display: block;
  font-family: var(--font-display);
  font-size: 1.15rem;
  color: var(--paper-50);
}

.founder__sign span {
  font-size: var(--fs-caption);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--on-dark-muted);
}

/* --------------------------------------------------------------------------
   11 · Gallery
   -------------------------------------------------------------------------- */
.gallery__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(0.75rem, 1.6vw, 1.25rem);
}

.gallery__item {
  position: relative;
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--ink-700);
}

.gallery__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.78) contrast(1.06) brightness(0.85);
  transition: transform 0.9s var(--e-out), filter var(--d-mid) var(--e-out);
}

.gallery__item:hover img {
  transform: scale(1.07);
  filter: saturate(1) contrast(1.02) brightness(1);
}

/* Rows are sized by items 2/3 (row 1), 4 (row 2) and 6 (row 3). Items 1 and 5
   carry no aspect-ratio so they stretch to fill, leaving no gaps in the grid. */
.gallery__item:nth-child(2),
.gallery__item:nth-child(3) {
  aspect-ratio: 4 / 3;
}
.gallery__item:nth-child(4),
.gallery__item:nth-child(6) {
  grid-column: span 2;
  aspect-ratio: 16 / 7;
}
.gallery__item:nth-child(1) {
  grid-row: span 2;
}

.gallery__cap {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  padding: var(--sp-5);
  font-size: var(--fs-caption);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--paper-50);
  background: linear-gradient(transparent, rgba(var(--rgb-ink), 0.85));
  opacity: 0;
  transform: translateY(8px);
  transition: opacity var(--d-mid) var(--e-out), transform var(--d-mid) var(--e-out);
}

.gallery__item:hover .gallery__cap {
  opacity: 1;
  transform: none;
}

/* On touch there is no hover, so a caption revealed only on hover is content
   the visitor can never reach. Show them permanently wherever hover is absent. */
@media (hover: none), (max-width: 760px) {
  .gallery__cap {
    opacity: 1;
    transform: none;
  }
}

@media (max-width: 760px) {
  .gallery__grid {
    grid-template-columns: repeat(2, 1fr);
  }
  .gallery__item {
    aspect-ratio: 4 / 3;
  }
  .gallery__item:nth-child(1) {
    grid-row: auto;
  }
  .gallery__item:nth-child(4),
  .gallery__item:nth-child(6) {
    grid-column: span 2;
    aspect-ratio: 16 / 7;
  }
}

/* --------------------------------------------------------------------------
   12 · Quote CTA
   -------------------------------------------------------------------------- */
.cta {
  position: relative;
  background: var(--grad-cta);
  color: var(--paper-50);
  overflow: clip;
}

.cta__aurora {
  position: absolute;
  inset: -20%;
  background: radial-gradient(38% 44% at 22% 30%, rgba(var(--rgb-cyan), 0.4), transparent 65%),
    radial-gradient(36% 40% at 78% 66%, rgba(var(--rgb-mint), 0.3), transparent 65%),
    radial-gradient(50% 50% at 50% 100%, rgba(var(--rgb-ink), 0.75), transparent 70%);
  filter: blur(38px);
  animation: aurora 26s var(--e-inout) infinite alternate;
  pointer-events: none;
}

@keyframes aurora {
  to {
    transform: translate3d(4%, -5%, 0) scale(1.12);
  }
}

.cta__grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 0.95fr 1.05fr;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
}

.cta h2 {
  font-size: var(--fs-display-md);
  text-transform: uppercase;
  margin-bottom: var(--sp-5);
}

.cta__lede {
  color: rgba(var(--rgb-white), 0.82);
  max-width: 44ch;
}

.cta__contacts {
  display: grid;
  gap: var(--sp-4);
  margin-top: var(--sp-7);
}

.cta__contact {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  transition: transform var(--d-fast) var(--e-out);
}

.cta__contact:hover {
  transform: translateX(4px);
}

.cta__contact i {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  flex: none;
  border-radius: 50%;
  background: rgba(var(--rgb-white), 0.1);
  border: 1px solid rgba(var(--rgb-white), 0.2);
  color: var(--cyan-300);
}

.cta__contact i svg {
  width: 18px;
  height: 18px;
}

.cta__contact span {
  display: block;
  font-size: var(--fs-caption);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(var(--rgb-white), 0.62);
}

.cta__contact b {
  /* The label above is a block, uppercase and muted; the value has to leave
     that flow and reset all three, or it sets on the same line in the same
     small caps and reads as one run-on string ("CALL US0409 151 743"). */
  display: block;
  font-size: 1.02rem;
  font-weight: 700;
  letter-spacing: normal;
  text-transform: none;
  color: var(--paper-50);
  /* The email address is one unbreakable token; without this it sets a
     ~500px min-content width and blows the grid out of the viewport. */
  overflow-wrap: anywhere;
}

/* Grid items default to min-width:auto, which lets long content push past
   the container. Reset it wherever a grid holds free text. */
.cta__grid > *,
.intro__grid > *,
.founder__grid > *,
.why__grid > *,
.process__grid > * {
  min-width: 0;
}

/* --------------------------------------------------------------------------
   Shared responsive collapses
   -------------------------------------------------------------------------- */
@media (max-width: 960px) {
  .intro__grid,
  .founder__grid,
  .cta__grid {
    grid-template-columns: 1fr;
  }

  .founder__media {
    max-width: 460px;
  }

  .founder__badge {
    right: 4%;
  }

  .services__head {
    flex-direction: column;
    align-items: flex-start;
  }
}

@media (max-width: 560px) {
  .intro__media {
    min-height: 420px;
  }

  .intro__img--a {
    width: 72%;
  }

  .intro__img--b {
    width: 56%;
  }

  .intro__badge {
    left: auto;
    right: 0;
    top: 2%;
  }

  .ba__stage {
    aspect-ratio: 4 / 3;
  }
}

/* ---- Lightbox ----------------------------------------------------------- */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: clamp(1rem, 4vw, 3rem);
  background: rgba(var(--rgb-ink), 0.94);
}

.lightbox[hidden] { display: none !important; }

.lightbox__img {
  /* --cap is set per photo by the script to twice the source width, so a small
     photo is never stretched across a big screen. min() folds it together with
     the viewport limits instead of the inline style overriding them. */
  width: 100%;
  max-width: min(96vw, 1200px, var(--cap, 1200px));
  max-height: 84vh;
  height: auto;
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
}

.lightbox__btn {
  position: absolute;
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 1px solid rgba(var(--rgb-white), 0.18);
  background: rgba(var(--rgb-white), 0.10);
  color: var(--on-dark);
  cursor: pointer;
}

.lightbox__btn:hover { background: rgba(var(--rgb-white), 0.20); }

.lightbox__btn:focus-visible {
  outline: 3px solid var(--cyan-400);
  outline-offset: 2px;
}

.lightbox__btn svg { width: 22px; height: 22px; }

.lightbox__close { top: clamp(0.75rem, 2vw, 1.5rem); right: clamp(0.75rem, 2vw, 1.5rem); }
.lightbox__prev  { left: clamp(0.75rem, 2vw, 1.5rem); }
.lightbox__next  { right: clamp(0.75rem, 2vw, 1.5rem); }
.lightbox__prev, .lightbox__next { top: 50%; transform: translateY(-50%); }
.lightbox__prev svg { transform: rotate(180deg); }

.lightbox__count {
  position: absolute;
  left: 50%;
  bottom: clamp(0.75rem, 2vw, 1.5rem);
  transform: translateX(-50%);
  margin: 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--on-dark-muted);   /* 6.1:1 on the backdrop */
}

/* Below the arrows' reach on a phone: move them to the bottom corners so they
   are not sitting on top of the photo. */
@media (max-width: 560px) {
  .lightbox__prev, .lightbox__next {
    top: auto;
    bottom: clamp(0.75rem, 2vw, 1.5rem);
    transform: none;
  }
  .lightbox__count { bottom: calc(clamp(0.75rem, 2vw, 1.5rem) + 62px); }
}


/* ==== SUBPAGE STYLES — hand-authored; everything above is generated ==== */

.band-ink,
.band-ink-800 {
  --sp-body: var(--on-dark-muted);   /* 7.1:1 on ink-800 */
  --sp-strong: var(--on-dark);       /* 15.2:1 on ink-800 */
  --sp-accent: var(--cyan-400);      /* 8.8:1 on ink-800 */
  --sp-line: rgba(var(--rgb-white), 0.09);
  --sp-line-strong: rgba(var(--rgb-cyan), 0.32);
  --sp-surface: var(--grad-surface);
  --sp-shadow: var(--shadow-lg);
  /* The homepage grades its photography cool and stopped down on dark bands. */
  --sp-photo: saturate(0.78) contrast(1.06) brightness(0.86);
  --sp-photo-hover: saturate(0.95) contrast(1.03) brightness(0.95);
  --sp-aura-a: rgba(var(--rgb-blue), 0.32);
  --sp-aura-b: rgba(var(--rgb-cyan), 0.20);
  --sp-dot: rgba(var(--rgb-white), 0.16);
}

.band-paper,
.band-paper-100 {
  --sp-body: var(--slate-500);       /* 6.1:1 on paper-100 */
  --sp-strong: var(--slate-700);     /* 10.9:1 on paper-100 */
  /* blue-600, not cyan-400: cyan is 2.0:1 on white and cannot carry text. */
  --sp-accent: var(--blue-600);      /* 7.5:1 on paper-50 */
  --sp-line: var(--paper-300);
  --sp-line-strong: var(--blue-400);
  --sp-surface: var(--paper-50);
  --sp-shadow: var(--shadow-md);
  /* On a light band the same photograph is left bright — a stopped-down image
     on white reads as a mistake rather than as a grade. */
  --sp-photo: saturate(1) contrast(1.02) brightness(1);
  --sp-photo-hover: saturate(1.06) contrast(1.02) brightness(1.03);
  /* Ceilings measured against the body copy at each blob's centre, which is
     the strongest point of the wash: blue fails --slate-500 past 0.22, cyan
     survives to 0.40 because it is so much lighter. Sitting just under both. */
  --sp-aura-a: rgba(var(--rgb-blue), 0.20);   /* body 4.7:1 at the centre */
  --sp-aura-b: rgba(var(--rgb-cyan), 0.34);   /* body 4.8:1 at the centre */
  --sp-dot: rgba(var(--rgb-blue), 0.30);
}

/* Eyebrows follow the band instead of carrying an inline colour per instance. */
.band-ink .eyebrow,
.band-ink-800 .eyebrow,
.band-paper .eyebrow,
.band-paper-100 .eyebrow {
  color: var(--sp-accent);
}

/* --------------------------------------------------------------------------
   Page hero
   Shorter than the homepage hero on purpose: a service page should reach the
   copy quickly, so this is a banner, not a full viewport.
   -------------------------------------------------------------------------- */
.sub-hero {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: clamp(460px, 66vh, 720px);
  padding-top: calc(var(--nav-h, 88px) + clamp(2.5rem, 6vw, 5rem));
  padding-bottom: clamp(2.5rem, 6vw, 5rem);
  background: var(--ink-900);
  color: var(--on-dark);
  overflow: clip;
}

.sub-hero__bg {
  position: absolute;
  inset: -8% 0;
  z-index: 0;
}

.sub-hero__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.72) contrast(1.1) brightness(0.5);
}

.sub-hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    radial-gradient(120% 90% at 20% 30%, rgba(var(--rgb-blue), 0.28), transparent 60%),
    linear-gradient(180deg, rgba(var(--rgb-ink), 0.72) 0%, rgba(var(--rgb-ink), 0.55) 40%, rgba(var(--rgb-ink), 0.96) 100%);
}

.sub-hero__inner {
  position: relative;
  z-index: 2;
  width: 100%;
}

/* Banner copy left, quote form right. The form is second in the DOM so it
   stacks *after* the copy when this collapses — no `order` juggling, and the
   reading order a screen reader gets is the same at every width. */
.sub-hero__grid {
  display: grid;
  grid-template-columns: 1fr minmax(360px, 0.82fr);
  gap: clamp(2rem, 4vw, 4rem);
  align-items: center;
}

/* The banner is no longer height-driven — the form decides how tall it is —
   so the copy no longer needs to hug the bottom edge. */
.sub-hero__copy {
  min-width: 0;
}

/* Inside the banner the panel is a sidebar, not a full section, so it loses
   some of its padding and the intro line that only earns its place at width. */
.sub-hero .form--fresh {
  padding: clamp(1.4rem, 2.2vw, 1.9rem);
}

.sub-hero .eyebrow {
  color: var(--cyan-400);
}

.sub-hero h1 {
  margin: var(--sp-4) 0 var(--sp-5);
  max-width: 18ch;
}

.sub-hero__lede {
  color: var(--on-dark-muted);
  max-width: 52ch;
  margin-bottom: var(--sp-6);
}

.sub-hero__cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  margin-bottom: var(--sp-7);
}

.sub-hero__cta .btn svg {
  width: 17px;
  height: 17px;
}

/* --------------------------------------------------------------------------
   Breadcrumb
   -------------------------------------------------------------------------- */
.breadcrumb ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 0.82rem;
  color: var(--on-dark-muted);
}

.breadcrumb li + li::before {
  content: "/";
  margin-right: var(--sp-3);
  opacity: 0.45;
}

.breadcrumb a {
  display: inline-block;
  /* Padded to a 40px+ tap target; as bare inline text the link is 17px tall. */
  padding-block: 0.7rem;
  margin-block: -0.7rem;
  color: var(--on-dark-muted);
  transition: color var(--d-fast, 0.25s) var(--e-out);
}

.breadcrumb a:hover {
  color: var(--cyan-300);
}

.breadcrumb [aria-current="page"] {
  color: var(--on-dark);
}

/* --------------------------------------------------------------------------
   Trust chips
   -------------------------------------------------------------------------- */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  list-style: none;
  margin: 0;
  padding: 0;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 0.55rem 1rem;
  border-radius: var(--r-pill);
  background: rgba(var(--rgb-white), 0.06);
  border: 1px solid rgba(var(--rgb-white), 0.14);
  backdrop-filter: blur(8px);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--on-dark);
}

.chip svg {
  width: 16px;
  height: 16px;
  color: var(--cyan-400);
}

/* --------------------------------------------------------------------------
   Overview
   -------------------------------------------------------------------------- */
/* The section title takes the full row and centres, so the two columns below
   read as one block under one heading rather than as two unrelated halves.

   Width is in rem, NOT ch. `ch` resolves against the element's own font-size,
   and this is a plain div at 16px — 34ch measured ~272px, which wrapped a
   three-word heading set at 46px onto three lines. */
.intro-block__head {
  max-width: 44rem;
  margin: 0 auto clamp(2.5rem, 5vw, 4rem);
  text-align: center;
}

.intro-block__head h2 {
  text-wrap: balance;
}

/* .eyebrow is inline-flex with a leading rule, so text-align cannot centre it
   — it has to be centred as a flex item. */
.intro-block__head .eyebrow {
  display: inline-flex;
  margin-bottom: var(--sp-3);
}

.intro-block__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}

.intro-block p {
  color: var(--sp-body);
}

.intro-block p + p {
  margin-top: var(--sp-4);
}

/* --------------------------------------------------------------------------
   Alternating content blocks
   -------------------------------------------------------------------------- */
.vblock__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
}

/* Flipping with `order` rather than a reversed DOM keeps the reading order —
   figure, then copy — identical for a screen reader on every block, whichever
   side the picture lands on. */
.vblock--flip .vblock__media,
.vblock--flip .vblock__panel {
  order: 2;
}

/* ---- Drifting light wash ------------------------------------------------
   Two soft radial washes per block, easing across the frame on a long loop so
   the band is never quite static. Deliberately cheap: a radial-gradient and a
   transform, with no blur filter, no blend mode and no will-change — those
   three together are what stalled the compositor earlier in this project.
   ------------------------------------------------------------------------ */
.vblock {
  position: relative;
  overflow: clip;
}

/* Dot-grid patch. It used to tile across the whole band, which was the wrong
   instinct: measured, body copy crossing a dot reads 3.23:1 on the light bands
   and 3.6:1 on the dark ones. Texture behind text cannot be made safe at any
   alpha that is also visible — so it lives on the MEDIA side instead, peeking
   out from behind the photograph, where no text ever sits and where the eye
   is already looking.
   ------------------------------------------------------------------------ */
@keyframes grid-drift {
  to {
    transform: translate3d(30px, 30px, 0);
  }
}

.vblock__aura {
  position: absolute;
  z-index: 0;

  position: absolute;
  z-index: 0;
  pointer-events: none;
}

/* The wash. Soft and large, so it has no hard edge to catch text against —
   which is why it can sit anywhere. Alpha is capped by the body copy at the
   blob's centre: 4.64:1 on paper, 5.8:1 on ink. */
.vblock__aura--a {
  top: -24%;
  left: -14%;
  width: min(64vw, 880px);
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(closest-side, var(--sp-aura-a), transparent 70%);
  animation: aura-drift 17s ease-in-out infinite alternate;
}

/* The patch, anchored to whichever side the photograph is on. The figure comes
   first in the DOM, so a DEFAULT block puts the media LEFT and the copy right;
   .vblock--flip reverses it. Getting this backwards parks the patch straight
   on top of the body copy, which is exactly what it exists to avoid. */
.vblock__aura--b {
  left: 1%;
  bottom: -7%;
  width: min(34vw, 360px);
  aspect-ratio: 1.15;
  background-image: radial-gradient(circle, var(--sp-dot) 1.7px, transparent 1.8px);
  background-size: 30px 30px;
  /* One full tile of travel per loop, so it cycles with no visible jump. */
  animation: grid-drift 14s linear infinite;
}

.vblock--flip .vblock__aura--b {
  left: auto;
  right: 1%;
}

@media (max-width: 980px) {
  /* Stacked, the copy runs full width and the patch would end up behind it. */
  .vblock__aura--b {
    display: none;
  }
}

@keyframes aura-drift {
  to {
    transform: translate3d(16%, 20%, 0) scale(1.28);
  }
}

.vblock > .container {
  position: relative;
  z-index: 1;
}

.vblock__media {
  position: relative;
  margin: 0;
  aspect-ratio: 4 / 3;
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--sp-shadow);
}

/* Overscaled and pulled up, so the scroll parallax below has room to travel
   inside the frame without ever exposing an edge. */
.vblock__media img {
  position: absolute;
  inset: -9% 0;
  display: block;
  width: 100%;
  height: 118%;
  object-fit: cover;
  filter: var(--sp-photo);
  transition: filter var(--d-mid) var(--e-out);
}

/* No hover scale here any more: the image carries a scroll-driven transform,
   and a second transform on the same element would overwrite it mid-scrub. */
.vblock__media:hover img {
  filter: var(--sp-photo-hover);
}

.vblock__index {
  font-family: var(--font-display);
  font-size: 0.85rem;
  letter-spacing: 0.22em;
  color: var(--sp-accent);
  margin-bottom: var(--sp-3);
}

.vblock__copy h2 {
  font-size: clamp(1.6rem, 2.6vw, 2.4rem);
  margin-bottom: var(--sp-4);
  max-width: 22ch;
}

.vblock__copy p {
  color: var(--sp-body);
}

.vblock__copy p + p {
  margin-top: var(--sp-4);
}

/* --------------------------------------------------------------------------
   Tick list
   -------------------------------------------------------------------------- */
.ticklist {
  display: grid;
  gap: var(--sp-3);
  list-style: none;
  margin: var(--sp-5) 0 0;
  padding: 0;
}

.ticklist li {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  font-size: 0.95rem;
  color: var(--sp-strong);
}

.ticklist svg {
  flex: none;
  width: 18px;
  height: 18px;
  margin-top: 4px;
  color: var(--sp-accent);
}

/* --------------------------------------------------------------------------
   Three-step process
   -------------------------------------------------------------------------- */
/* The head is measure-capped, which left the right half of this band empty.
   Pairing it with a photograph in a two-column intro fills it without
   stretching the copy past a readable line length. */
.process-block {
  position: relative;
  overflow: clip;
}

.process-block__intro {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
  margin-bottom: clamp(2.5rem, 5vw, 4rem);
}

.process-block__head {
  max-width: 60ch;
}

.process-block > .container {
  position: relative;
  z-index: 1;
}

@media (max-width: 980px) {
  .process-block__intro {
    grid-template-columns: 1fr;
  }
}

.process-block__head h2 {
  margin: var(--sp-4) 0 var(--sp-4);
}

.process-block__head p {
  margin-bottom: 0;
}

.process-block__head p,
.process-block__note {
  color: var(--sp-body);
}

.steps {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1rem, 2.5vw, 2rem);
  list-style: none;
  margin: 0;
  padding: 0;
}

.step {
  position: relative;
  padding: clamp(1.5rem, 2.6vw, 2.25rem);
  border-radius: var(--r-lg);
  background: var(--sp-surface);
  border: 1px solid var(--sp-line);
  overflow: hidden;
  transition: border-color var(--d-mid) var(--e-out), transform var(--d-mid) var(--e-out);
}

.step:hover {
  border-color: var(--sp-line-strong);
  transform: translateY(-4px);
}

.step__n {
  display: block;
  font-family: var(--font-display);
  font-size: 2.2rem;
  line-height: 1;
  color: var(--sp-accent);
  opacity: 0.75;
  margin-bottom: var(--sp-4);
}

.step h3 {
  font-size: var(--fs-heading-sm, 1.15rem);
  margin-bottom: var(--sp-3);
}

.step p {
  font-size: 0.92rem;
  color: var(--sp-body);
  margin: 0;
}

.process-block__note {
  margin-top: clamp(2rem, 4vw, 3rem);
  max-width: 60ch;
}

/* --------------------------------------------------------------------------
   FAQ
   -------------------------------------------------------------------------- */
/* .band-ink paints a flat --ink-900, which is exactly the footer's colour, so
   the two ran together as one slab with no seam.

   The gradient runs LIGHT-END-DOWN on purpose. Lightening the top would have
   separated it from the pale band above — which it did not need, that edge is
   already 10.4:1 — while leaving the bottom identical to the footer. Running
   it the other way puts the tonal step exactly where the problem was, and
   reads as light rising from below, which is the site's whole premise.

   Ceiling on the light end is a contrast limit, not taste: the answer text is
   --on-dark-muted over a 9%-white glass card, and at --ink-700 plus a 0.16
   blue wash that lands on 4.6:1. Any lighter and the answers fail 4.5.
   Overriding .band-ink needs the extra class — same specificity otherwise. */
/* Closing dark band for pages that do not end on an FAQ, so the step down into
   the footer reads the same on every subpage. */
.band-ink.is-closing,
.faq.band-ink {
  background:
    radial-gradient(120% 80% at 50% 100%, rgba(var(--rgb-blue), 0.16), transparent 64%),
    linear-gradient(180deg, var(--ink-900) 0%, var(--ink-800) 45%, var(--ink-700) 100%);
}

.faq__grid {
  display: grid;
  grid-template-columns: 0.8fr 1.2fr;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}

.faq__head p {
  color: var(--sp-body);
  margin-top: var(--sp-4);
}

.faq__head a {
  color: var(--sp-accent);
  font-weight: 600;
}

.faq__list {
  display: grid;
  gap: var(--sp-3);
}

.faq__item {
  border: 1px solid var(--sp-line);
  border-radius: var(--r-md);
  background: var(--sp-surface);
  overflow: hidden;
  transition: border-color var(--d-fast, 0.25s) var(--e-out), box-shadow var(--d-mid) var(--e-out);
}

.faq__item:has(.faq__q[aria-expanded="true"]) {
  border-color: var(--sp-line-strong);
  box-shadow: var(--sp-shadow);
}

/* The heading carries no styling of its own — it exists so the questions are
   real headings in the document outline rather than a stack of buttons. */
.faq__h {
  margin: 0;
  font: inherit;
}

.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  width: 100%;
  padding: clamp(1rem, 1.8vw, 1.35rem) clamp(1rem, 2vw, 1.5rem);
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 1.02rem;
  font-weight: 600;
  color: var(--sp-strong);
}

.faq__q svg {
  flex: none;
  width: 18px;
  height: 18px;
  color: var(--sp-accent);
  transition: transform var(--d-mid) var(--e-out);
}

.faq__q[aria-expanded="true"] svg {
  transform: rotate(180deg);
}

.faq__a {
  padding: 0 clamp(1rem, 2vw, 1.5rem) clamp(1rem, 1.8vw, 1.35rem);
}

.faq__a p {
  color: var(--sp-body);
  font-size: 0.95rem;
  margin: 0;
}

/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */
@media (max-width: 980px) {
  .intro-block__grid,
  .vblock__grid,
  .faq__grid {
    grid-template-columns: 1fr;
  }

  /* Stacked, the picture belongs above its copy on every block. */
  .vblock--flip .vblock__media,
  .vblock--flip .vblock__panel {
    order: 0;
  }

  .steps {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 980px) {
  /* One column: banner copy, then the form beneath it. */
  .sub-hero__grid {
    grid-template-columns: 1fr;
  }

  /* Stacked, the banner is content-height. Keeping align-items:flex-end here
     would push the whole column to the bottom of a 66vh box and leave a gap
     above the breadcrumb. */
  .sub-hero {
    align-items: stretch;
    min-height: 0;
  }
}

@media (max-width: 560px) {
  .sub-hero {
    min-height: 0;
  }

  .sub-hero__cta .btn {
    flex: 1 1 100%;
    justify-content: center;
  }
}

/* --------------------------------------------------------------------------
   Inline lead form — the "fresh" gradient panel

   Sits in the light Overview band, where the section's left column would
   otherwise be a large empty space under the heading. Painted in --grad-fresh,
   the same cyan-to-mint the primary button uses, so it reads as the page's
   one call to action rather than as another content card.

   Contrast, measured against the LIGHTER stop of the gradient (the worst case):
     ink-900 headings and labels  9.56:1
     slate-700 helper text        5.96:1
     white text on the ink button 19.16:1
   slate-500 is only 3.06:1 here, so nothing on this panel uses it.
   -------------------------------------------------------------------------- */
.form--fresh {
  position: relative;
  background: var(--grad-fresh);
  border: 0;
  backdrop-filter: none;
  box-shadow: var(--shadow-lg);
  color: var(--ink-900);
  border-radius: var(--r-xl);
  padding: clamp(1.6rem, 3vw, 2.4rem);
  overflow: hidden;
}

/* A soft light in the top corner. Without it a large flat gradient panel reads
   as a solid colour swatch rather than as a surface. */
.form--fresh::before {
  content: "";
  position: absolute;
  top: -32%;
  right: -18%;
  width: 72%;
  aspect-ratio: 1;
  background: radial-gradient(closest-side, rgba(var(--rgb-white), 0.5), transparent 72%);
  pointer-events: none;
}

.form--fresh > * {
  position: relative;
}

.form--fresh .form__title {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--ink-900);
  margin: 0;
}

.form--fresh .form__intro {
  font-size: 0.9rem;
  color: var(--slate-700);
  margin: calc(var(--sp-2) * -1) 0 var(--sp-2);
  max-width: 40ch;
}

/* A hairline under the header separates it from the fields without adding a
   second surface colour to the panel. */
.form--fresh .form__intro::after {
  content: "";
  display: block;
  height: 1px;
  margin-top: var(--sp-5);
  background: rgba(var(--rgb-ink), 0.18);
}

.form--fresh .btn {
  margin-top: var(--sp-2);
  justify-content: center;
}

.form--fresh label {
  color: var(--ink-900);
  font-weight: 600;
}

/* White fields are only 2.0:1 against the gradient, under the 3:1 a control
   boundary needs, so the border is what defines each field. It has to carry
   3:1 against BOTH neighbours — the white fill inside and the cyan panel
   outside. A translucent ink border fails the outside test (1.26:1); solid
   slate-700 clears both at 11:1 inside and 5.7:1 outside. */
.form--fresh input,
.form--fresh select,
.form--fresh textarea {
  background: var(--paper-50);
  border: 1px solid var(--slate-700);
  color: var(--ink-900);
}

.form--fresh input::placeholder,
.form--fresh textarea::placeholder {
  color: var(--slate-500);
}

/* Same stand-in placeholder, against the white field this variant uses. */
.form--fresh select:invalid {
  color: var(--slate-500);
}

.form--fresh input:focus-visible,
.form--fresh select:focus-visible,
.form--fresh textarea:focus-visible {
  outline: 2px solid var(--ink-900);
  outline-offset: 2px;
  border-color: var(--ink-900);
}

/* Inverted: on a light gradient panel the action has to be the dark element,
   or the button disappears into its own background. */
.form--fresh .btn {
  background: var(--ink-900);
  color: var(--paper-50);
  box-shadow: none;
}

.form--fresh .btn:hover {
  background: var(--ink-700);
}

.form--fresh .field__error {
  color: #7a1710;   /* 5.35:1 on the cyan stop */
}

.form--fresh .form__status {
  color: var(--ink-900);
  font-weight: 600;
}

.form--fresh .form__note {
  color: var(--slate-700);
}


/* ==========================================================================
   Panel — an alternative to a photograph in the .vblock media slot

   The image library holds one genuine warehouse photograph. Rather than
   caption a stock classroom or a domestic lounge as an industrial site, the
   sections with no honest photograph get this panel instead: it carries real
   points lifted from that section's own copy, so the slot holds content rather
   than filler, and the two-column rhythm of the page is never broken.

   Every colour resolves through the --sp-* custom properties, so one rule
   serves both the dark and the light bands.
   ========================================================================== */
.vblock__panel {
  position: relative;
  align-self: center;
  border-radius: var(--r-lg);
  padding: clamp(1.6rem, 3vw, 2.4rem);
  background: var(--sp-surface);
  border: 1px solid var(--sp-line);
  box-shadow: var(--sp-shadow);
  overflow: hidden;
}

/* Same aura language as the photo blocks, kept inside the card so the panel
   reads as part of the section rather than a bolted-on box. */
.vblock__panel::after {
  content: "";
  position: absolute;
  right: -18%;
  bottom: -34%;
  width: 68%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(closest-side, var(--sp-aura-b), transparent 70%);
  pointer-events: none;
}

.vblock__panel > * { position: relative; }

.panel__icon {
  display: grid;
  place-items: center;
  width: 54px;
  height: 54px;
  border-radius: var(--r-md);
  background: var(--grad-fresh);
  color: var(--ink-900);          /* 11.4:1 on the lighter cyan stop */
  margin-bottom: var(--sp-4);
}

.panel__icon svg { width: 26px; height: 26px; }

.panel__eyebrow {
  margin: 0 0 var(--sp-3);
  font-family: var(--font-display);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sp-accent);
}

.vblock__panel .ticklist { margin: 0; }

/* The panel is already a surface, so a second one for the copy would stack two
   cards side by side. Keep the copy plain. */
.vblock__panel + .vblock__copy { padding-block: 0; }


/* ==========================================================================
   Duo — two sections that share one band as a pair of cards

   Used where consecutive sections are short enough to sit together and neither
   has a photograph of its own. Keeps the page from running to a dozen
   full-height alternating blocks.
   ========================================================================== */
.duo { position: relative; overflow: clip; }
.duo > .container { position: relative; z-index: 1; }

.duo__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(1.5rem, 3vw, 2.5rem);
  /* Stretch, not start: the two heads are the same height, so letting the white
     bodies run to a common baseline keeps the pair reading as one unit. */
  align-items: stretch;
}

/* A card, not a box. The head is a dark gradient panel carrying the index,
   the icon and the title; the copy sits on white below it. On a light band that
   inversion is what gives the pair some weight — two white rectangles of prose
   read as a wall no matter how the type is set. */
.duo__card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--paper-50);
  border: 1px solid var(--paper-300);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}

.duo__card__head {
  position: relative;
  padding: clamp(1.5rem, 2.6vw, 2rem) clamp(1.6rem, 3vw, 2.2rem) clamp(1.4rem, 2.4vw, 1.8rem);
  background:
    radial-gradient(120% 140% at 88% 0%, rgba(var(--rgb-cyan), 0.30), transparent 62%),
    linear-gradient(135deg, var(--ink-800), var(--blue-700));
  color: var(--on-dark);
  overflow: hidden;
}

/* The same cyan-to-mint rule that edges the quote panel, so the two brightest
   moments on the page speak the same language. */
.duo__card__head::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 3px;
  background: var(--grad-fresh);
}

.duo__card__n {
  position: absolute;
  top: clamp(0.6rem, 1.4vw, 1rem);
  right: clamp(1rem, 2vw, 1.4rem);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.6rem, 5vw, 3.6rem);
  line-height: 1;
  letter-spacing: -0.02em;
  color: rgba(var(--rgb-white), 0.10);
}

.duo__card__head .panel__icon {
  margin-bottom: var(--sp-4);
}

.duo__card h2 {
  position: relative;
  margin: 0;
  max-width: 22ch;
  font-size: clamp(1.3rem, 2.1vw, 1.6rem);
  line-height: 1.2;
  color: var(--on-dark);
}

.duo__card__body {
  flex: 1;
  padding: clamp(1.6rem, 3vw, 2.2rem);
}

.duo__card__body p {
  margin: 0;
}

.duo__card__body p + p {
  margin-top: var(--sp-4);
}

@media (max-width: 980px) {
  .duo__grid { grid-template-columns: 1fr; }
}


/* A cloud of 30+ chips. The default .chip carries backdrop-filter: blur(8px);
   one blurred layer per chip is precisely the compositing cost that stalled
   this project earlier, so the plain variant drops it and lifts the fill
   slightly to compensate. */
.chips--plain .chip {
  backdrop-filter: none;
  background: rgba(var(--rgb-white), 0.07);
}


/* Copy that follows a ticklist inside a block. `p + p` gives paragraphs their
   rhythm, but a paragraph after a <ul> matches neither selector and ended up
   flush against the last bullet. */
.vblock__copy .ticklist + p,
.intro-block__grid .ticklist + p {
  margin-top: var(--sp-4);
}


/* ==========================================================================
   Photo wall — masonry grid + lightbox

   Namespaced .photowall, NOT .gallery: the homepage already owns .gallery__grid
   as a three-column CSS grid with nth-child spans, and display:grid there beats
   column-count here no matter which rule comes last.

   The source photos run from 240px to 1034px wide and from 3:4 portrait to
   3:2 landscape, so a fixed-ratio grid would crop most of them badly. CSS
   columns keep every photo at its own aspect ratio and let the shorter ones
   close the gaps.
   ========================================================================== */
.photowall { position: relative; overflow: clip; }
.photowall > .container { position: relative; z-index: 1; }

.photowall__grid {
  column-count: 4;
  column-gap: clamp(0.75rem, 1.4vw, 1.25rem);
}

@media (max-width: 1180px) { .photowall__grid { column-count: 3; } }
@media (max-width: 820px)  { .photowall__grid { column-count: 2; } }
@media (max-width: 460px)  { .photowall__grid { column-count: 1; } }

.photowall__item {
  break-inside: avoid;
  margin: 0 0 clamp(0.75rem, 1.4vw, 1.25rem);
}

/* A button, not a bare image: opening a lightbox is an action, and this way it
   is reachable and operable from the keyboard for free. */
.photowall__btn {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  border-radius: var(--r-md);
  overflow: hidden;
  cursor: zoom-in;
  box-shadow: var(--sp-shadow);
}

.photowall__btn img {
  display: block;
  width: 100%;
  height: auto;
  filter: var(--sp-photo);
  transition: filter var(--d-mid) var(--e-out), transform var(--d-mid) var(--e-out);
}

.photowall__btn:hover img,
.photowall__btn:focus-visible img {
  filter: var(--sp-photo-hover);
  transform: scale(1.04);
}

.photowall__btn:focus-visible {
  outline: 3px solid var(--sp-line-strong);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .photowall__btn img { transition: none; }
  .photowall__btn:hover img,
  .photowall__btn:focus-visible img { transform: none; }
}



/* ==========================================================================
   Video cards — click-to-load YouTube facade

   Nothing from YouTube loads until someone presses play: no player script, no
   cookie, no third-party request. The poster is a local WebP and the <iframe>
   is created on click. On a page already carrying 154 photos, two embedded
   players would have been the heaviest thing on it.

   The frames are 4:3 because both videos are vertical and YouTube's own
   thumbnail is a 4:3 canvas with a blurred side-fill — matching it means the
   poster shows exactly as generated, with no crop.
   ========================================================================== */
.videos__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(1.25rem, 2.5vw, 2rem);
}

@media (max-width: 820px) {
  .videos__grid { grid-template-columns: 1fr; }
}

.video-card {
  position: relative;
  background: var(--sp-surface);
  border: 1px solid var(--sp-line);
  border-radius: var(--r-lg);
  box-shadow: var(--sp-shadow);
  overflow: hidden;
}

.video-card__frame {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  aspect-ratio: 4 / 3;
  background: var(--ink-900);
  cursor: pointer;
  overflow: hidden;
}

.video-card__frame img,
.video-card__frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

.video-card__frame img { object-fit: cover; }

.video-card__play {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  width: 76px;
  height: 76px;
  border-radius: 50%;
  background: var(--grad-fresh);
  color: var(--ink-900);          /* 11.4:1 on the lighter stop */
  box-shadow: var(--shadow-lg);
  transition: transform var(--d-mid) var(--e-out);
  pointer-events: none;
}

.video-card__frame:hover .video-card__play,
.video-card__frame:focus-visible .video-card__play {
  transform: translate(-50%, -50%) scale(1.08);
}

.video-card__frame:focus-visible {
  outline: 3px solid var(--sp-line-strong);
  outline-offset: -3px;
}

.video-card__play svg { width: 30px; height: 30px; margin-left: 4px; }

.video-card__title {
  margin: 0;
  padding: clamp(1rem, 2vw, 1.35rem) clamp(1.1rem, 2.2vw, 1.5rem);
  font-family: var(--font-display);
  font-size: clamp(1.02rem, 1.5vw, 1.15rem);
  font-weight: 700;
  line-height: 1.3;
  color: var(--sp-strong);
}

@media (prefers-reduced-motion: reduce) {
  .video-card__play { transition: none; }
  .video-card__frame:hover .video-card__play,
  .video-card__frame:focus-visible .video-card__play { transform: translate(-50%, -50%); }
}


/* ==========================================================================
   Video facade

   A poster and a play button, not a YouTube iframe. Two embeds on load would
   pull in a large amount of third-party script and set cookies for every
   visitor, including those who never press play; the iframe is swapped in on
   click instead, and the poster is served from our own assets.
   ========================================================================== */
/* Every video on this channel is a vertical phone clip, several re-posted
   from TikTok. YouTube pads them into a 16:9 thumbnail with a blurred copy of
   the frame down each side, so a wide card would be mostly blur — the cards
   are 9:16 and sit four across instead, and the posters are cropped to the
   real content column. */
.videos__grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(1rem, 2vw, 1.5rem);
}

@media (max-width: 1100px) {
  .videos__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (max-width: 760px) {
  .videos__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.video {
  position: relative;
  margin: 0;
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--ink-900);
  box-shadow: var(--sp-shadow);
}

.video__frame {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  aspect-ratio: 9 / 16;
  background: none;
  cursor: pointer;
  color: var(--on-dark);
}

.video__frame img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.9) brightness(0.82);
  transition: filter var(--d-mid) var(--e-out), transform var(--d-mid) var(--e-out);
}

.video__frame:hover img,
.video__frame:focus-visible img {
  filter: saturate(1) brightness(0.95);
  transform: scale(1.03);
}

.video__frame:focus-visible {
  outline: 3px solid var(--cyan-400);
  outline-offset: 3px;
}

/* The play badge. Sized past 44px so it is a comfortable touch target, and it
   carries a real triangle rather than a glyph so it scales cleanly. */
.video__play {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 2;
  translate: -50% -50%;
  display: grid;
  place-items: center;
  width: 58px;
  height: 58px;
  border-radius: 50%;
  background: var(--grad-fresh);
  box-shadow: var(--shadow-lg);
  transition: transform var(--d-mid) var(--e-out);
}

.video__frame:hover .video__play,
.video__frame:focus-visible .video__play { transform: scale(1.08); }

.video__play svg {
  width: 26px;
  height: 26px;
  margin-left: 4px;          /* optical centring of the triangle */
  color: var(--ink-900);     /* 11.4:1 on the lighter gradient stop */
}

/* Channel titles run long — one is 100 characters. Unclamped, the caption
   grows up the card and collides with the centred play badge. The clamp goes
   on an inner span, not on this element: -webkit-line-clamp limits line boxes
   but overflow still clips at the padding box, so a padded clamped element
   leaks its last line past the card edge. */
.video__cap {
  position: absolute;
  inset: auto 0 0;
  z-index: 1;
  padding: clamp(2rem, 5vw, 2.75rem) clamp(0.7rem, 1.4vw, 1rem) clamp(0.7rem, 1.4vw, 0.95rem);
  margin: 0;
  background: linear-gradient(180deg, transparent, rgba(var(--rgb-ink), 0.88));
  color: var(--on-dark);     /* 12.6:1 on the gradient's solid end */
  font-weight: 600;
  /* Was 0.95rem for a half-width landscape card; these cards are a quarter of
     the row, so the title needs to be smaller to fit three lines. */
  font-size: 0.8rem;
  line-height: 1.3;
  text-align: left;
  pointer-events: none;
}

.video__cap span {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}


/* Matches the poster, so a vertical clip fills the player instead of being
   pillarboxed inside a landscape frame. */
.video iframe {
  display: block;
  width: 100%;
  aspect-ratio: 9 / 16;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  .video__frame img,
  .video__play { transition: none; }
  .video__frame:hover img,
  .video__frame:focus-visible img { transform: none; }
  .video__frame:hover .video__play,
  .video__frame:focus-visible .video__play { transform: none; }
}

/* ==========================================================================
   Contact page — a centred banner, and the contact rows on a light band
   ========================================================================== */

/* ---- Centred banner ------------------------------------------------------
   The default banner is copy-left / form-right. A page that moves the form
   out of the hero has a single column left, and left-aligned copy against an
   empty right half reads as a layout that lost a piece. This modifier centres
   the column instead.

   Every child has to be told separately: the breadcrumb, the CTA row and the
   chips are all flex containers, so `text-align` alone does nothing to them,
   and the h1 and lede carry their own max-widths, so they need an auto inline
   margin to sit in the middle of the band rather than at its left edge. */
.sub-hero--center {
  align-items: center;
}

.sub-hero--center .sub-hero__copy {
  text-align: center;
}

/* Every flex child of the banner has to be centred on its own: text-align
   does nothing to a flex container's items. Miss one — .article-meta was the
   one missed — and it sits hard left while everything above and below it is
   centred. */
.sub-hero--center .breadcrumb ol,
.sub-hero--center .article-meta,
.sub-hero--center .sub-hero__cta,
.sub-hero--center .chips {
  justify-content: center;
}

.sub-hero--center h1,
.sub-hero--center .sub-hero__lede {
  margin-inline: auto;
}

/* ---- Contact rows and socials on a light band ----------------------------
   .cta__contact and .social__link are the homepage's components and are
   written for ink: their fills, borders and text are all white alphas, which
   vanish on paper, and the cyan they use for icons is 2.0:1 on white. On a
   light band restate them against that band's own --sp-* tokens, which are
   already picked for contrast there (--sp-accent is blue-600, not cyan). */
.band-paper .cta__contact i,
.band-paper-100 .cta__contact i {
  /* Not --sp-surface: on .band-paper that resolves to the same white as the
     band, leaving an outline with nothing in it. A blue wash at 7% keeps the
     circle readable on both paper tones without competing with the icon. */
  background: rgba(var(--rgb-blue), 0.07);
  border-color: var(--sp-line);
  color: var(--sp-accent);
}

.band-paper .cta__contact span,
.band-paper-100 .cta__contact span {
  color: var(--sp-body);
}

.band-paper .cta__contact b,
.band-paper-100 .cta__contact b {
  color: var(--sp-strong);
}

.band-paper .social__link,
.band-paper-100 .social__link {
  border-color: var(--sp-line);
  color: var(--sp-body);
}

.band-paper .social__link:hover,
.band-paper-100 .social__link:hover {
  color: var(--sp-accent);
  border-color: var(--sp-line-strong);
  background: rgba(var(--rgb-blue), 0.06);
}

/* ==========================================================================
   Service areas — suburb chips that are links, on either band
   ========================================================================== */

/* .chip was built as a static label sitting on ink: one flat fill, no hover,
   white alphas throughout. The service-area page repeats a ~39-chip cloud
   four times on alternating bands and every chip is a link, so it needs both
   an interactive state and a palette that survives on paper. */
.chip[href] {
  text-decoration: none;
  transition: background-color var(--d-fast) var(--e-out),
    border-color var(--d-fast) var(--e-out),
    color var(--d-fast) var(--e-out),
    transform var(--d-fast) var(--e-out);
}

.chip[href]:hover,
.chip[href]:focus-visible {
  transform: translateY(-2px);
  color: var(--cyan-300);
  border-color: rgba(var(--rgb-cyan), 0.45);
  background: rgba(var(--rgb-cyan), 0.10);
}

/* On paper the white fill and border disappear and --on-dark is unreadable,
   so restate all three against the band's own tokens. --sp-accent is
   blue-600 there, not cyan, which is 2.0:1 on white and cannot carry text. */
.band-paper .chip,
.band-paper-100 .chip {
  background: rgba(var(--rgb-blue), 0.05);
  border-color: var(--sp-line);
  color: var(--sp-strong);
}

.band-paper .chip svg,
.band-paper-100 .chip svg {
  color: var(--sp-accent);
}

.band-paper .chip[href]:hover,
.band-paper .chip[href]:focus-visible,
.band-paper-100 .chip[href]:hover,
.band-paper-100 .chip[href]:focus-visible {
  color: var(--sp-accent);
  border-color: var(--sp-line-strong);
  background: rgba(var(--rgb-blue), 0.10);
}

@media (prefers-reduced-motion: reduce) {
  .chip[href] { transition: none; }
  .chip[href]:hover,
  .chip[href]:focus-visible { transform: none; }
}

/* Inline links inside a section intro. base.css sets `a { color: inherit;
   text-decoration: none }` for the nav and card links, which leaves a real
   link in running copy indistinguishable from the text around it. Restore
   both signals here, against the band's own accent so it carries on paper
   (blue-600) and on ink (cyan-400) alike. */
.intro-block__head p a {
  color: var(--sp-accent);
  text-decoration: underline;
  text-underline-offset: 0.18em;
  text-decoration-thickness: 1px;
}

.intro-block__head p a:hover,
.intro-block__head p a:focus-visible {
  text-decoration-thickness: 2px;
}

/* ==========================================================================
   Blog index — post cards
   ========================================================================== */

/* Three columns, not the services grid's four: a post card carries a date, a
   full headline and a couple of lines of excerpt, and four-up the headlines
   wrapped to four lines each. */
.posts__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1.25rem, 2.2vw, 2rem);
}

@media (max-width: 1100px) {
  .posts__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 700px) {
  .posts__grid { grid-template-columns: 1fr; }
}

/* One link wrapping the whole card, so the click target is the card and the
   markup still has a single anchor per post. .svc-card is the flex column;
   this has to become one too or the body stops filling the card. */
.post-card__link {
  display: flex;
  flex-direction: column;
  height: 100%;
  color: inherit;
}

/* .svc-card__body pads its top to clear the icon badge that straddles the
   photo seam. A post card has no badge, so that space is just a hole. */
.post-card .svc-card__body {
  padding-top: var(--sp-5);
}

/* .svc-card p is (0,1,1) and sets the body grey on every <p> in the card, so
   these two need a second class to outrank it rather than sit under it. */
.post-card .post-card__meta {
  margin: 0;
  font-size: var(--fs-caption);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  /* Fixed, not --sp-accent: the card is white whatever band it sits on, and
     on a dark band that token resolves to cyan, which is 1.8:1 on white. */
  color: var(--blue-600);
}

.post-card .post-card__excerpt {
  margin: 0;
  color: var(--slate-500);
  font-size: 0.94rem;
  /* Cards sit in a stretch grid, so an over-long excerpt would push one card
     taller than its row-mates for no gain. Clamp to a consistent depth. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}

/* .link-arrow is an <a> on the homepage; here the anchor is the card wrapper,
   so it renders as a span and needs the inline-flex back. */
span.link-arrow {
  display: inline-flex;
  margin-top: auto;
  padding-top: var(--sp-3);
}

/* ==========================================================================
   Article prose — the blog post body
   ========================================================================== */

/* A three-column grid: gutter, measure, gutter. Everything sits in the middle
   column; figures and tables opt into the full width. That keeps the text at a
   readable line length without also shrinking the pictures.

   The measure is set from the line length, not chosen by eye. At 46rem and
   16.3px the body ran to 93 characters a line — far past the 60-75 where the
   eye reliably finds the start of the next line, which is what makes a long
   article tiring. 39rem at 1.12rem lands at ~70. */
.prose {
  --measure: 39rem;
  --wide: 46rem;
  display: grid;
  grid-template-columns: 1fr min(var(--measure), 100%) 1fr;
}

.prose > * {
  grid-column: 2;
}

/* Pictures and tables are not prose and do not want the prose measure. */
.prose > figure,
.prose > .prose__scroll {
  grid-column: 1 / -1;
  width: min(var(--wide), 100%);
  justify-self: center;
}

/* Reset every child's own margin FIRST, then set the flow gap. Doing it the
   other way round is a specificity trap: `.prose p { margin: 0 }` is (0,1,1)
   and silently beats the (0,1,0) `.prose > * + *`, which collapses the gap
   between consecutive paragraphs. */
.prose > * {
  margin: 0;
}

.prose > * + * {
  margin-top: var(--sp-5);
}

.prose p {
  color: var(--sp-body);
  font-size: 1.12rem;
  line-height: 1.7;
}

.prose h2,
.prose h3 {
  color: var(--sp-strong);
  letter-spacing: -0.01em;
  scroll-margin-top: calc(var(--nav-h, 88px) + 1rem);
}

.prose h2 {
  font-size: clamp(1.45rem, 2.6vw, 1.9rem);
  line-height: 1.2;
}

.prose h3 {
  font-size: clamp(1.15rem, 1.9vw, 1.35rem);
  line-height: 1.25;
}

/* A heading needs more air above it than below: the gap belongs to the
   section it opens, not to the paragraph it follows. */
.prose > h2 + *,
.prose > h3 + * {
  margin-top: var(--sp-4);
}

.prose > * + h2 {
  margin-top: var(--sp-8);
}

.prose > * + h3 {
  margin-top: var(--sp-6);
}

.prose strong {
  color: var(--sp-strong);
  font-weight: 700;
}

/* base.css sets `a { color: inherit; text-decoration: none }` for the nav and
   the cards. In running prose a link has to look like one. */
.prose a {
  color: var(--sp-accent);
  text-decoration: underline;
  text-underline-offset: 0.18em;
  text-decoration-thickness: 1px;
}

.prose a:hover,
.prose a:focus-visible {
  text-decoration-thickness: 2px;
}

/* base.css strips list markers globally (`ul, ol { list-style: none }`) for
   the nav, chips and card lists. Prose is the one place that actually wants
   them back, so restore the marker here — an ordered list of steps is useless
   without its numbers. */
.prose ul,
.prose ol {
  padding-left: 1.5rem;
  display: grid;
  gap: var(--sp-3);
  color: var(--sp-body);
  font-size: 1.08rem;
  line-height: 1.65;
}

.prose ul:not(.ticklist) {
  list-style: disc;
}

.prose ol {
  list-style: decimal;
}

.prose li::marker {
  color: var(--sp-accent);
}

/* The live posts stack ✅-prefixed lines inside one <p> using <br>. They are
   lists, so they are rebuilt as .ticklist — the same component the service
   pages use — rather than kept as a run-on paragraph. */
.prose .ticklist {
  margin-top: var(--sp-4);
}

.prose figure {
  margin-inline: 0;
}

.prose figure img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--r-lg);
  box-shadow: var(--sp-shadow);
}

.prose figcaption {
  margin-top: var(--sp-3);
  font-size: var(--fs-caption);
  color: var(--sp-body);
  opacity: 0.85;
}

/* Tables carry more columns than a phone has room for, so the table scrolls
   inside its own box rather than pushing the page sideways. */
.prose__scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--sp-line);
  border-radius: var(--r-lg);
}

.prose table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.94rem;
  min-width: 34rem;
}

.prose th,
.prose td {
  padding: var(--sp-3) var(--sp-4);
  text-align: left;
  border-bottom: 1px solid var(--sp-line);
  color: var(--sp-body);
}

.prose thead th {
  background: rgba(var(--rgb-blue), 0.06);
  color: var(--sp-strong);
  font-weight: 700;
  white-space: nowrap;
}

.prose tbody tr:last-child td {
  border-bottom: 0;
}

/* ---- Article furniture --------------------------------------------------- */

/* Date and reading time under the banner headline. */
.article-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-4);
  margin-bottom: var(--sp-5);
  font-size: var(--fs-caption);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--on-dark-muted);
}

.article-meta span + span::before {
  content: "·";
  margin-right: var(--sp-4);
  opacity: 0.6;
}

/* ==========================================================================
   Article layout — prose left, sticky contents right
   ========================================================================== */

/* Capped as a pair, not per column: the sidebar is a fixed 15rem, so letting
   the whole grid run to the full container width would stretch the prose
   column past a readable measure again. */
.article__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 15rem;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
  max-width: 62rem;
  margin-inline: auto;
}

/* The contents box comes FIRST in the DOM so that on a phone, where the grid
   collapses, it lands above the article where a table of contents is useful —
   rather than at the bottom of a 1100-word read, where it is not. Grid
   placement puts it back on the right at desktop widths. */
.toc {
  grid-column: 2;
  grid-row: 1;
  position: sticky;
  top: calc(var(--nav-h, 88px) + 1.5rem);
  /* Some posts have 18 headings. Cap the box to the viewport and let it
     scroll on its own rather than pushing past the fold. */
  max-height: calc(100vh - var(--nav-h, 88px) - 3rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--sp-5);
  border: 1px solid var(--sp-line);
  border-radius: var(--r-lg);
  background: var(--sp-surface);
  box-shadow: var(--sp-shadow);
}

.article__grid > .prose {
  grid-column: 1;
  grid-row: 1;
}

.toc__title {
  margin: 0 0 var(--sp-4);
  font-family: var(--font-display);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--sp-strong);
}

.toc__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
}

.toc__list a {
  display: block;
  padding: 0.4rem 0 0.4rem 0.8rem;
  border-left: 2px solid var(--sp-line);
  font-size: 0.88rem;
  line-height: 1.35;
  color: var(--sp-body);
  transition: color var(--d-fast) var(--e-out),
    border-color var(--d-fast) var(--e-out);
}

.toc__list a:hover,
.toc__list a:focus-visible {
  color: var(--sp-accent);
  border-left-color: var(--sp-accent);
}

/* Sub-headings sit under their parent, indented and a shade quieter. */
.toc__item--sub a {
  padding-left: 1.6rem;
  font-size: 0.82rem;
  opacity: 0.85;
}

/* Set by subpage.js as the reader moves through the article. */
.toc__list a.is-current {
  color: var(--sp-accent);
  border-left-color: var(--sp-accent);
  font-weight: 600;
  opacity: 1;
}

@media (max-width: 1000px) {
  .article__grid {
    grid-template-columns: 1fr;
  }

  /* Back into the flow, above the article, and no longer height-capped. */
  .toc {
    grid-column: 1;
    grid-row: auto;
    position: static;
    max-height: none;
    overflow: visible;
    margin-bottom: var(--sp-7);
  }

  .article__grid > .prose {
    grid-column: 1;
    grid-row: auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  .toc__list a { transition: none; }
}


/* The logo and phone icon must not be what gives way when the nav is tight:
   flex-shrink squeezed "Living Fresh" onto two lines and the icon to a dot. */
.nav .logo {
  flex-shrink: 0;
}

.nav .logo__text {
  white-space: nowrap;
}

.nav__phone svg {
  flex: none;
}

/* Just above the drawer breakpoint the links, phone number and quote button
   need ~11px more than there is (full row fits from ~1291px). The phone number
   steps out in that sliver; the hero call button and the drawer still carry it. */
@media (min-width: 1280px) and (max-width: 1319px) {
  .nav__phone {
    display: none;
  }
}



/* ---- Heading spacing -------------------------------------------------------
   Every heading and display title: 0.1px tracking, 6px between words.
   ":root :is(...)" scores (0,2,0), so it outranks the narrower heading rules
   (.footer h3, .prose h2) without !important. Split headlines keep real
   spaces between their .word spans, so word-spacing still applies to them. */
:root :is(h1, h2, h3, h4, h5, h6, .display-xl, .display-lg, .display-md) {
  letter-spacing: 0.1px;
  word-spacing: 6px;
}