/* ==========================================================================
   PURESHINE CLEANING — SHARED STYLESHEET
   Used by every page. Sections are numbered; jump with your editor's search.

    1. Design tokens        11. Services (cards)     21. Contact page
    2. Reset & base         12. Services (detail)    22. Footer
    3. Typography           13. Why choose us        23. Scroll reveal
    4. Layout               14. How it works (14b)   24. Responsive
    5. Buttons              15. Testimonials         25. Reduced motion
    6. Header & nav         16. Stats
    7. Image placeholders   17. CTA band
    8. Hero (8b. map)       18. Pricing page
    9. Page hero (inner)    19. FAQ  (19b. FAQ page)
   10. Trust bar            20. About page
   ========================================================================== */


/* ==========================================================================
   1. DESIGN TOKENS
   Everything themeable lives here. Re-skin the whole site from this block.
   ========================================================================== */
:root {
  /* — Colour: cool neutrals —
     One ground for the whole site. --c-bg and --c-tint are deliberately the
     SAME value: every band that used to alternate between white and blue now
     melts into the page, and the header matches it too. Cards lift off it
     with --c-surface, which is a soft off-white rather than pure #FFF —
     full white against this much blue is glaring. */
  --c-bg:           #E9F1FB;   /* page base — every page, every section */
  --c-tint:         #E9F1FB;   /* kept as an alias so bands stay seamless */
  --c-tint-deep:    #DDE9F8;   /* only for gradients and hover states */
  --c-surface:      #F8FBFF;   /* cards and panels */

  /* — Colour: deep navy (primary) — */
  --c-navy:         #0B2545;
  --c-navy-mid:     #14396B;
  --c-blue:         #1D4ED8;
  --c-blue-mist:    #E4EDFB;   /* pale icon-tile blue */

  /* — Colour: bright blue accent — */
  --c-accent:       #2563EB;
  --c-accent-light: #60A5FA;
  --c-accent-pale:  #D2E2FA;

  /* — Colour: type — */
  --c-ink:          #0B2545;
  --c-body:         #56637A;
  /* Darkened with the new ground: the page base went from #F7FAFE to a
     deeper blue, which cost every text colour a little contrast. This keeps
     the small print at 4.6:1 instead of dropping it to 3.8:1. */
  --c-muted:        #5F6D85;   /* darkened to clear WCAG AA on the light ground */
  --c-on-dark:      #EAF2FF;
  --c-on-dark-dim:  rgba(234, 242, 255, .72);

  /* — Lines & surfaces — */
  --line:           rgba(11, 37, 69, .12);
  --line-soft:      rgba(11, 37, 69, .07);
  --line-dark:      rgba(234, 242, 255, .20);

  /* — Typography — */
  --font-display: 'Cormorant Garamond', 'Times New Roman', Georgia, serif;
  --font-body:    'Jost', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;

  --fs-eyebrow:  clamp(.66rem, .62rem + .12vw, .74rem);
  --fs-body:     clamp(.98rem, .95rem + .18vw, 1.06rem);
  --fs-lead:     clamp(1.06rem, 1rem + .4vw, 1.24rem);
  --fs-h3:       clamp(1.35rem, 1.2rem + .7vw, 1.75rem);
  --fs-h2:       clamp(2.1rem, 1.6rem + 2.2vw, 3.6rem);
  --fs-h1:       clamp(2.9rem, 1.9rem + 4.6vw, 6rem);
  --fs-h1-inner: clamp(2.4rem, 1.7rem + 3vw, 4.4rem);

  --track-wide:  .28em;   /* letter-spacing for small uppercase labels */
  --track-mid:   .16em;

  /* — Rhythm — */
  --container:   1240px;
  --gutter:      clamp(1.25rem, 4vw, 3rem);
  --section-y:   clamp(3.25rem, 6.5vw, 6rem);

  /* — Form — */
  --radius-sm:   6px;
  --radius:      14px;
  --radius-lg:   22px;
  --radius-pill: 999px;

  --shadow-sm:   0 2px 10px rgba(11, 37, 69, .05);
  --shadow:      0 18px 50px -28px rgba(11, 37, 69, .26);
  --shadow-lg:   0 40px 90px -50px rgba(11, 37, 69, .42);

  /* — Motion — */
  --ease:        cubic-bezier(.22, 1, .36, 1);
  --t-fast:      .28s var(--ease);
  --t-med:       .5s  var(--ease);
  --t-slow:      .95s var(--ease);

  /* Header height — also drives anchor scroll-padding. Went from 50px to 66px
     as the logo grew: the lockup carries its own wordmark, so it has to be read
     rather than just recognised, and a 52px logo needs about 7px of air above
     and below or the bar reads as crammed. */
  --header-h:    66px;
  /* What the header actually measures once you've scrolled, which is the only
     time anything needs to stick beneath it. Anchoring to --header-h instead
     leaves a gap the width of the difference, and page content shows through
     it. Kept in sync with .header.is-solid, which reads it. */
  --header-h-solid: 62px;

  /* — Texture: inline SVG grain, no network request — */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23g)' opacity='.42'/%3E%3C/svg%3E");

  /* — Blue tick, used by every inclusion list — */
  --tick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%232563EB' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 8.5l3.2 3.2L13 4.5'/%3E%3C/svg%3E");

  /* — Select chevron — */
  --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%2356637A' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5l4 4 4-4'/%3E%3C/svg%3E");
}


/* ==========================================================================
   2. RESET & BASE
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html {
  scroll-behavior: smooth;
  /* Clears the sticky header for every in-page jump. --header-h is the tall,
     unscrolled height (58px); the header is 48px once scrolled, so this
     already carries a 10px buffer — no extra padding needed on targets. */
  scroll-padding-top: calc(var(--header-h) + 4px);
  -webkit-text-size-adjust: 100%;
}

body {
  position: relative;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: 300;
  line-height: 1.75;
  color: var(--c-body);
  background: var(--c-bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

/* Whole-page grain veil — sits above backgrounds, below content */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  background-image: var(--grain);
  opacity: .035;
  mix-blend-mode: multiply;
  pointer-events: none;
  z-index: 9998;
}

img, svg { display: block; max-width: 100%; }
/* Every <img> carries width/height attributes so the browser can reserve space
   before the file loads. Those attributes also set a real used height, which
   silently beats any `aspect-ratio` we set in CSS — the hero rendered at its
   full 1024px instead of the ratio it was given. `height: auto` hands the
   sizing back to CSS while keeping the attributes doing their job.

   Deliberately a bare element selector: an attribute selector such as
   img[width][height] would outrank .svc-hero__media's `height: 100%` and
   break the pinned desktop hero. A presentational hint loses to any author
   rule regardless of specificity, so 0-0-1 is enough here. */
img { height: auto; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
ul { list-style: none; padding: 0; }

/* The `hidden` attribute is only a UA-stylesheet `display: none`, so any author
   rule that sets a display — the FAQ search's `display: grid` clear button, for
   one — silently outranks it and the element stays on screen. */
[hidden] { display: none !important; }

::selection { background: var(--c-navy); color: var(--c-bg); }

:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 3px;
  border-radius: 2px;
}

.skip-link {
  position: absolute;
  top: -100px; left: var(--gutter);
  z-index: 10000;
  padding: .8rem 1.4rem;
  background: var(--c-navy);
  color: var(--c-on-dark);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  transition: top var(--t-fast);
}
.skip-link:focus { top: 0; }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}


/* ==========================================================================
   3. TYPOGRAPHY PRIMITIVES
   ========================================================================== */
h1, h2, h3, h4, .display {
  font-family: var(--font-display);
  font-weight: 300;
  line-height: 1.06;
  letter-spacing: -.012em;
  color: var(--c-ink);
  text-wrap: balance;
}

h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); line-height: 1.2; }

/* Small uppercase label with a hairline rule */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: .9rem;
  font-size: var(--fs-eyebrow);
  font-weight: 400;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--c-accent);
}
.eyebrow::before {
  content: "";
  width: clamp(24px, 4vw, 44px);
  height: 1px;
  background: currentColor;
  opacity: .55;
}
.eyebrow--center { justify-content: center; }
.eyebrow--center::after {
  content: "";
  width: clamp(24px, 4vw, 44px);
  height: 1px;
  background: currentColor;
  opacity: .55;
}
.eyebrow--light { color: var(--c-accent-light); }

.lead {
  font-size: var(--fs-lead);
  line-height: 1.7;
  color: var(--c-body);
  text-wrap: pretty;
}

.prose > * + * { margin-top: 1.35rem; }
.prose p { max-width: 68ch; }


/* ==========================================================================
   4. LAYOUT
   ========================================================================== */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.container--narrow { max-width: 860px; }

.section { padding-block: var(--section-y); }
/* Both band modifiers are now no-ops — the whole site sits on one ground.
   Kept (rather than stripped from five HTML files) so the hooks stay
   available if a band is ever wanted again. */
.section--tint  { background: transparent; }
.section--paper { background: transparent; }
.section--tight { padding-block: clamp(3rem, 6vw, 5rem); }
/* Sits directly under the overlapping card row, so it needs less headroom.
   No bottom padding — the section that follows supplies its own spacing. */
.section--after-cards { padding-block: clamp(1.6rem, 3.2vw, 2.5rem) 0; }
/* Pairs with the above so two paddings don't stack into a large empty band */
/* Used once, on the services detail list. padding-bottom is trimmed too:
   the full --section-y stacked with the last block's own bottom padding. */
.section--flush-top { padding-block: 0 clamp(2rem, 4vw, 3.25rem); }
/* Utility: remove vertical spacing for a tight band (no gap above or below) */
.section--no-gap { padding-block: 0; }

.section-head { max-width: 640px; }
.section-head--center {
  max-width: 660px;
  margin-inline: auto;
  text-align: center;
}
.section-head .eyebrow { margin-bottom: 1.5rem; }
.section-head p { margin-top: 1.4rem; }

/* Opt-in inline header: eyebrow and h2 on one line at larger widths */
@media (min-width: 720px) {
  .section-head--inline {
    display: flex;
    align-items: center;
    gap: clamp(.6rem, 1.2vw, 1rem);
  }
  .section-head--inline .eyebrow {
    margin-bottom: 0;
    flex: none;
  }
  .section-head--inline h2 {
    margin: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: var(--fs-h3);
    line-height: 1.08;
  }
}

.rule { height: 1px; background: var(--line); border: 0; }


/* ==========================================================================
   5. BUTTONS
   ========================================================================== */
.btn {
  --btn-bg: var(--c-navy);
  --btn-fg: var(--c-bg);
  --btn-bd: var(--c-navy);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .7rem;
  padding: 1.05rem 2.2rem;
  font-size: .78rem;
  font-weight: 400;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  text-align: center;
  color: var(--btn-fg);
  background: var(--btn-bg);
  border: 1px solid var(--btn-bd);
  border-radius: var(--radius-pill);
  overflow: hidden;
  isolation: isolate;
  transition: color var(--t-fast), border-color var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast);
}
/* Wipe fill on hover */
.btn::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--c-accent);
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform .55s var(--ease);
}
.btn:hover {
  color: #fff;
  border-color: var(--c-accent);
  transform: translateY(-2px);
  box-shadow: 0 16px 34px -20px rgba(37, 99, 235, .55);
}
.btn:hover::after { transform: scaleX(1); transform-origin: left center; }
.btn:active { transform: translateY(0); }

.btn--ghost       { --btn-bg: transparent; --btn-fg: var(--c-navy);  --btn-bd: var(--line); }
.btn--ghost-light { --btn-bg: transparent; --btn-fg: var(--c-on-dark); --btn-bd: var(--line-dark); }
.btn--light       { --btn-bg: var(--c-bg); --btn-fg: var(--c-navy); --btn-bd: var(--c-bg); }
.btn--block       { width: 100%; }
/* One size up, for the single most important button on a page */
.btn--lg          { padding: 1.2rem 2.9rem; font-size: .82rem; }

/* Understated text link with an animated underline */
.link {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  font-size: .74rem;
  font-weight: 400;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  color: var(--c-navy);
}
.link__line {
  display: block;
  width: 26px; height: 1px;
  background: currentColor;
  transition: width var(--t-med);
}
.link:hover { color: var(--c-accent); }
.link:hover .link__line { width: 42px; }
.link--light { color: var(--c-on-dark); }


/* ==========================================================================
   6. HEADER & NAV
   ========================================================================== */
.header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 900;
  height: var(--header-h);
  display: flex;
  align-items: center;
  border-bottom: 1px solid transparent;
  transition: background var(--t-med), border-color var(--t-med),
              box-shadow var(--t-med), height var(--t-med);
}
.header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  width: 100%;
}

/* Lockup — the house mark plus the wordmark */
.logo {
  display: flex;
  align-items: center;
  gap: clamp(.45rem, 1.2vw, .65rem);
  font-family: var(--font-display);
  /* Both halves scale together, because on a 320px screen the mark, the
     wordmark and the burger have to share one 50px bar. */
  font-size: clamp(1.18rem, .95rem + .95vw, 1.5rem);
  font-weight: 700;      /* the wordmark carries the brand — it reads bold */
  line-height: 1.1;      /* the inherited 1.75 makes a 42px line box — too tall now */
  letter-spacing: .01em;
  color: var(--c-on-dark);
  transition: color var(--t-med);
  white-space: nowrap;
  /* A long business name would otherwise push the phone number and the button
     out of the bar. The lockup is the one thing here allowed to give way:
     min-width:0 lets it shrink below its text width, and the wordmark trails
     off rather than shoving its neighbours around. */
  min-width: 0;
  flex: 0 1 auto;
}
.header .logo > span {
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The footer column is narrow and has height to spare, so down there a long
   name wraps onto a second line rather than being cut short. */
.footer__about .logo { white-space: normal; align-items: flex-start; }
/* The logo carries its own wordmark, so this one image IS the lockup — there is
   no text beside it to balance against. Sized off the header bar instead: 30px
   inside the 50px mobile bar, up to 38px on a desktop, which leaves the same
   breathing room above and below as the old mark-plus-text pair did. width:auto
   keeps the artwork's 2.17:1 proportion, and the width/height attributes on the
   <img> reserve the box before it decodes. */
.logo__mark {
  height: clamp(42px, 1.4vw + 36px, 52px);
  width: auto;
  flex: none;
  transition: height var(--t-med);
}
/* Shrinks with the bar when the header goes solid, so the two move as one. */
.header.is-solid .logo__mark { height: clamp(39px, 1.4vw + 33px, 48px); }

/* Two cuts of the same artwork. The wordmark is near-black, which all but
   disappears on the navy header, so midnight swaps in the copy whose greys are
   lifted to the on-dark ink. The leaves are identical in both. Setting `content`
   on the <img> keeps one element, one alt and one box in the layout. */
html[data-theme="midnight"] .logo__mark { content: url("../images/logo-light.png"); }

.nav { display: none; }
.nav__list { display: flex; align-items: center; gap: clamp(1.4rem, 2.6vw, 2.6rem); }
.nav__link {
  position: relative;
  font-size: .76rem;
  /* 600, not 700: Jost's semibold is the heaviest weight the page loads, and
     asking for a weight that isn't there gets you the browser's smeared
     synthetic bold instead of the real cut. */
  font-weight: 600;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  color: var(--c-on-dark-dim);
  padding-block: .4rem;
  transition: color var(--t-fast);
  white-space: nowrap;
}
.nav__link::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: var(--c-accent-light);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform var(--t-med);
}
.nav__link:hover { color: var(--c-on-dark); }
.nav__link:hover::after { transform: scaleX(1); transform-origin: left; }

/* Current page — set with class="nav__link is-active" + aria-current="page" */
.nav__link.is-active { color: var(--c-on-dark); }
.nav__link.is-active::after { transform: scaleX(1); }

/* Phone + Book Now sit together at the right end of the header */
.header__end { display: flex; align-items: center; gap: clamp(.9rem, 2vw, 1.7rem); flex: none; }

.header__cta {
  display: none;
  padding: .6rem 1.35rem;
  font-size: .71rem;
  line-height: 1.3;      /* keeps the button inside the 48px scrolled header */
  white-space: nowrap;
}
/* On the home page the header floats over a dark image, where a navy button
   would disappear — so it flips to a light one until the header goes solid. */
body:not(.light-hero) .header:not(.is-solid) .header__cta {
  --btn-bg: var(--c-surface);
  --btn-fg: var(--c-navy);
  --btn-bd: var(--c-surface);
}

.header__phone {
  display: none;
  align-items: center;
  gap: .6rem;
  font-size: .82rem;
  letter-spacing: .06em;
  color: var(--c-on-dark);
  transition: color var(--t-fast);
  /* The number is one unit — never break "0412 345 678" across two lines,
     however tight the bar gets. */
  white-space: nowrap;
  flex: none;
}
.header__phone svg { width: 15px; height: 15px; stroke: var(--c-accent-light); flex: none; }
.header__phone:hover { color: var(--c-accent-light); }
.header__phone-label {
  display: block;
  font-size: .58rem;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--c-on-dark-dim);
  margin-bottom: -2px;
}

/* Scrolled state — the page's own ground, so the bar reads as part of the page
   rather than a white strip laid over it. Fully opaque: it was a frosted white
   at 92%, and the blur only existed to soften what showed through. With a solid
   fill nothing shows through, so the filter is dropped — one less compositing
   layer on every scroll frame. The hairline and the faint shadow are all that
   separate it now, which is the point. */
.header.is-solid {
  height: var(--header-h-solid);
  background: var(--c-bg);
  border-bottom-color: var(--line-soft);
  box-shadow: var(--shadow-sm);
}
.header.is-solid .logo,
.header.is-solid .header__phone { color: var(--c-navy); }
.header.is-solid .nav__link { color: var(--c-body); }
.header.is-solid .nav__link:hover,
.header.is-solid .nav__link.is-active { color: var(--c-navy); }
.header.is-solid .nav__link.is-active::after { background: var(--c-accent); }
.header.is-solid .header__phone-label { color: var(--c-muted); }
.header.is-solid .header__phone svg { stroke: var(--c-accent); }
.header.is-solid .burger__bar { background: var(--c-navy); }
.header.is-solid .burger[aria-expanded="true"] {
  background: linear-gradient(155deg, rgba(37,99,235,.16), rgba(37,99,235,.05));
  border-color: rgba(37,99,235,.35);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.6), 0 8px 20px rgba(11,37,69,.14);
}

/* Burger — bare icon, no tile; the bars carry it on their own */
.burger {
  position: relative;
  z-index: 1000;
  display: grid;
  place-items: center;
  /* Without this the three grid rows stretch to fill the 36px box and the bars
     float in the middle of a ~11px row each — which is where the spacing came
     from, not from `gap`. Centred rows shrink to the bars, so `gap` below is
     what you actually see between the lines. */
  align-content: center;
  gap: 3px;
  width: 46px;
  height: 36px;
  margin-right: -8px;
  border-radius: 9px;
  background: transparent;
  border: 1px solid transparent;   /* kept so the box never resizes between states */
  transition: background var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast), border-color var(--t-fast);
}
.burger:hover { transform: translateY(-1px); }
.burger:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px rgba(37,99,235,.12);
  border-color: rgba(96,165,250,.18);
}

.burger__bar {
  width: 26px;
  height: 4px;
  border-radius: 2px;
  /* No tile behind it any more, so the bars take the header's own colour:
     light over the dark hero, navy once it goes solid (rules further up). */
  background: var(--c-on-dark);
  transition: transform .36s cubic-bezier(.34, 1.56, .64, 1), opacity var(--t-fast), background var(--t-fast), width var(--t-fast), height var(--t-fast);
  transform-origin: center;
}

/* Open state — glassy accent halo behind a bold, centred cross */
.burger[aria-expanded="true"] {
  background: var(--c-accent);
  border-color: rgba(37,99,235,.35);
  box-shadow: 0 10px 26px rgba(11,37,69,.18);
  transform: none;
}
.burger[aria-expanded="true"] .burger__bar { background: #fff; }
.burger[aria-expanded="true"] .burger__bar:nth-child(1) { width: 26px; height: 4px; transform: translateY(7px) rotate(45deg); }
.burger[aria-expanded="true"] .burger__bar:nth-child(2) { opacity: 0; transform: scale(0); }
.burger[aria-expanded="true"] .burger__bar:nth-child(3) { width: 26px; height: 4px; transform: translateY(-7px) rotate(-45deg); }

@media (prefers-reduced-motion: reduce) {
  .burger, .burger__bar { transition: none; transform: none; }
}

/* Dedicated close button, pinned to the drawer's own top-right corner */
.mobile-nav__close {
  position: absolute;
  top: .85rem;
  right: .85rem;
  z-index: 5;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 10px;
  background: linear-gradient(155deg, rgba(255,255,255,.16), rgba(255,255,255,.04));
  border: 1px solid rgba(255,255,255,.18);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.2);
  transition: background var(--t-fast), transform var(--t-fast), border-color var(--t-fast);
}
.mobile-nav__close:hover { background: rgba(255,255,255,.24); transform: translateY(-1px); }
.mobile-nav__close:focus-visible { outline: none; box-shadow: 0 0 0 4px rgba(37,99,235,.25); border-color: rgba(96,165,250,.45); }
.mobile-nav__close span {
  position: absolute;
  width: 17px;
  height: 2px;
  border-radius: 999px;
  background: var(--c-on-dark);
}
.mobile-nav__close span:first-child { transform: rotate(45deg); }
.mobile-nav__close span:last-child  { transform: rotate(-45deg); }

/* Mobile drawer — a compact glass panel sized to its own content,
   floating near the top of the page rather than stretching to the bottom */
.mobile-nav {
  position: fixed;
  top: .7rem;
  right: .7rem;
  z-index: 950;
  width: min(21rem, calc(100vw - 1.4rem));
  max-height: calc(100svh - 1.4rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 3.3rem 1.15rem 1.3rem;
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  background:
    radial-gradient(120% 70% at 100% 0%, rgba(37, 99, 235, .32), transparent 55%),
    linear-gradient(160deg, rgba(13, 44, 84, .68) 0%, rgba(10, 33, 65, .74) 60%, rgba(8, 26, 51, .82) 100%);
  backdrop-filter: blur(22px) saturate(180%);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-10px) scale(.97);
  transform-origin: top right;
  transition: opacity var(--t-fast), transform var(--t-fast), visibility var(--t-fast);
}
.mobile-nav.is-open { opacity: 1; visibility: visible; transform: none; }


/* Dim the page behind the panel; below the header so the burger stays live */
body.is-locked::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 890;
  background: rgba(7, 22, 45, .45);
  backdrop-filter: blur(3px);
  animation: backdrop-in .3s var(--ease);
}
@keyframes backdrop-in { from { opacity: 0; } }
/* The quote dialog brings its own backdrop and locks the page the same way.
   Without this the drawer's dim stacks underneath it — twice the blur, twice
   the tint, and a hairline of the wrong colour around the dialog. */
body.is-dialog::after { display: none; }

.mobile-nav__list { counter-reset: mnav; }
.mobile-nav__list li + li { border-top: 1px solid var(--line-dark); }
.mobile-nav__link {
  position: relative;
  display: flex;
  align-items: center;
  gap: .8rem;
  padding: .78rem .1rem;
  font-size: .78rem;
  font-weight: 600;   /* matches the desktop nav — it's the same navigation */
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  color: var(--c-on-dark);
  transition: color var(--t-fast), padding-left var(--t-fast);
}
/* Tiny auto numerals — 01, 02, … */
.mobile-nav__link::before {
  counter-increment: mnav;
  content: counter(mnav, decimal-leading-zero);
  font-size: .6rem;
  letter-spacing: .08em;
  color: var(--c-accent-light);
  opacity: .75;
}
/* Slim arrow that slides in on the current / pressed item */
.mobile-nav__link::after {
  content: "→";
  margin-left: auto;
  font-size: .8rem;
  color: var(--c-accent-light);
  opacity: 0;
  transform: translateX(-6px);
  transition: opacity var(--t-fast), transform var(--t-fast);
}
.mobile-nav__link:hover,
.mobile-nav__link.is-active { color: var(--c-accent-light); padding-left: .25rem; }
.mobile-nav__link:hover::after,
.mobile-nav__link.is-active::after { opacity: 1; transform: none; }

/* Quick, subtle stagger as the panel opens */
.mobile-nav__list li,
.mobile-nav__cta,
.mobile-nav__foot { opacity: 0; transform: translateY(6px); }
.mobile-nav.is-open .mobile-nav__list li,
.mobile-nav.is-open .mobile-nav__cta,
.mobile-nav.is-open .mobile-nav__foot {
  opacity: 1;
  transform: none;
  transition: opacity .35s var(--ease), transform .35s var(--ease);
}
.mobile-nav.is-open .mobile-nav__list li:nth-child(1) { transition-delay: .04s; }
.mobile-nav.is-open .mobile-nav__list li:nth-child(2) { transition-delay: .08s; }
.mobile-nav.is-open .mobile-nav__list li:nth-child(3) { transition-delay: .12s; }
.mobile-nav.is-open .mobile-nav__list li:nth-child(4) { transition-delay: .16s; }
.mobile-nav.is-open .mobile-nav__list li:nth-child(5) { transition-delay: .20s; }
.mobile-nav.is-open .mobile-nav__cta  { transition-delay: .24s; }
.mobile-nav.is-open .mobile-nav__foot { transition-delay: .28s; }

/* Compact Book Now button */
.mobile-nav__cta {
  width: 100%;
  margin-top: .95rem;
  padding: .78rem 1.4rem;
  font-size: .72rem;
}

.mobile-nav__foot {
  margin-top: 1.05rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line-dark);
  display: grid;
  gap: .45rem;
  font-size: .84rem;
  color: var(--c-on-dark-dim);
}
.mobile-nav__contact {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  color: var(--c-on-dark);
  transition: color var(--t-fast);
}
.mobile-nav__contact:hover { color: var(--c-accent-light); }
.mobile-nav__contact svg {
  width: 13px; height: 13px;
  flex: none;
  stroke: var(--c-accent-light);
  fill: none;
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.mobile-nav__social {
  display: flex;
  gap: .55rem;
  margin-top: .45rem;
}
.mobile-nav__social a {
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  border: 1px solid var(--line-dark);
  border-radius: 50%;
  color: var(--c-on-dark);
  transition: color var(--t-fast), border-color var(--t-fast), background var(--t-fast);
}
.mobile-nav__social a:hover {
  color: #fff;
  border-color: var(--c-accent-light);
  background: rgba(96, 165, 250, .14);
}
.mobile-nav__social svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 1.4; }
body.is-locked { overflow: hidden; }

/* --------------------------------------------------------------------------
   LIGHT-HERO PAGES
   Inner pages open on a cream band instead of a dark one, so the header needs
   dark text from the very top. Add class="light-hero" to <body>.
   Placed after the burger rules so the open-drawer state still wins.
   -------------------------------------------------------------------------- */
body.light-hero .logo,
body.light-hero .header__phone { color: var(--c-navy); }
body.light-hero .nav__link { color: var(--c-body); }
body.light-hero .nav__link:hover { color: var(--c-navy); }
body.light-hero .nav__link.is-active { color: var(--c-accent); }
body.light-hero .nav__link::after { background: var(--c-accent); }
body.light-hero .header__phone-label { color: var(--c-muted); }
body.light-hero .header__phone svg { stroke: var(--c-accent); }
body.light-hero .burger__bar { background: var(--c-navy); }
/* The compact panel hangs below the header, so the open X stays on the
   header's own ground — no colour flip needed any more. */

/* --------------------------------------------------------------------------
   OPAQUE HEADER
   For pages where content runs underneath the header — the Services hero
   image starts at y=0 — so the picture is concealed by the navbar instead of
   showing through it. Add class="header-solid" to <body>.

   The fill is --c-bg, the page ground, so the bar reads as part of the page
   rather than a separate strip. The scrolled state now uses the same colour,
   so this is only about covering the hero picture before the first scroll.
   -------------------------------------------------------------------------- */
body.header-solid .header:not(.is-solid) { background: var(--c-bg); }


/* ==========================================================================
   7. IMAGE PLACEHOLDERS
   Shows a soft gradient with the target filename until the real photo is
   dropped in. Usage: <div class="ph" data-file="images/your-file.jpg"></div>
   ========================================================================== */
.ph {
  position: relative;
  overflow: hidden;
  background: linear-gradient(135deg, var(--c-blue-mist) 0%, var(--c-tint-deep) 46%, var(--c-tint) 100%);
  border-radius: var(--radius-lg);
}
.ph::after {
  content: attr(data-file);
  position: absolute;
  left: 50%; bottom: 1.15rem;
  transform: translateX(-50%);
  padding: .5rem 1rem;
  /* Truncate rather than run past the edge — the filename is wider than the
     placeholder in the floated mobile layout. Wrapping it instead would stack
     it one letter per line, because of the wide tracking. */
  max-width: calc(100% - 1.4rem);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: .6rem;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--c-navy);
  background: rgba(255, 255, 255, .84);
  border: 1px dashed rgba(37, 99, 235, .45);
  border-radius: var(--radius-pill);
  backdrop-filter: blur(4px);
}
.ph::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(120% 90% at 22% 18%, rgba(255, 255, 255, .9), transparent 58%),
    radial-gradient(80% 70% at 84% 88%, rgba(37, 99, 235, .16), transparent 62%);
}
/* A placeholder filling a card's picture slot until the artwork exists. It
   goes INSIDE .card__media rather than on it: that figure already spends its
   ::after on the navy fade under the icon chip, and .ph needs the same
   pseudo-element to print the filename. Nesting gives each one its own.

   object-fit is here for later: section 5b of main.js swaps the placeholder
   for an <img> carrying these same classes the moment the file exists, and
   without it the poster would stretch to fill the card's 4:5 box. */
.card__ph {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 0;
}

/* The price card's thumbnail is 104px wide. The filename would truncate to
   "IMAG…" there and collide with the icon chip, so the label is dropped on
   that one — the services page and the home page both print it in full. */
.price-card__media.ph::after { content: none; }

.ph--dark { background: linear-gradient(150deg, #071A33 0%, #0B2545 38%, #14396B 74%, #2B5FA8 100%); }
.ph--dark::before {
  background:
    radial-gradient(110% 80% at 70% 12%, rgba(96, 165, 250, .28), transparent 60%),
    radial-gradient(90% 70% at 12% 92%, rgba(255, 255, 255, .1), transparent 62%);
}
.ph--dark::after {
  color: var(--c-on-dark);
  background: rgba(11, 37, 69, .55);
  border-color: rgba(96, 165, 250, .45);
}


/* ==========================================================================
   8. HERO — home page only
   ========================================================================== */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding-block: calc(var(--header-h) + 3rem) clamp(4rem, 10vh, 7rem);
  overflow: hidden;
  isolation: isolate;
}
.hero__media {
  position: absolute;
  inset: -12% 0 -12% 0;   /* extra bleed so parallax never exposes an edge */
  z-index: -2;
  border-radius: 0;
  object-fit: cover;
  will-change: transform;
}
/* Legibility scrim over the image */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(180deg, rgba(7, 22, 45, .74) 0%, rgba(7, 22, 45, .32) 38%, rgba(7, 22, 45, .80) 100%),
    linear-gradient(90deg, rgba(7, 22, 45, .62) 0%, transparent 62%);
}
.hero__inner { max-width: 900px; }
/* This one names five regions rather than two words, and reads as a single
   line under the rule. The shared .28em tracking is what would break it, so
   it is set tighter here and the type steps down with the viewport.

   From 600px up the line is held with `nowrap` — it measures about 400px in
   the worst case, against 540px of room, so nothing is being forced. Below
   that it is allowed to wrap: sixty-one characters across a 360px phone comes
   out at six pixels a letter, and a line nobody can read is not a line. */
.hero__eyebrow {
  margin-bottom: clamp(.9rem, 2vw, 1.4rem);
  font-size: clamp(.58rem, .48rem + .3vw, .74rem);
  letter-spacing: clamp(.1em, .04em + .12vw, .2em);
}
@media (min-width: 600px) {
  .hero__eyebrow { white-space: nowrap; }
}
/* Wrapped, it needs the rule pinned to the first line rather than floating to
   the middle of the block, and two even lines rather than a long one and an
   orphan. */
@media (max-width: 599.98px) {
  .hero__eyebrow { align-items: flex-start; line-height: 1.65; text-wrap: balance; }
  .hero__eyebrow::before { margin-top: .55em; }
}

/* The promise line — sits between the eyebrow and the headline and carries
   the same idea as the two beacons on the map behind it. Display serif, so it
   belongs to the headline rather than to the small uppercase label above it,
   but a third of its size: it introduces the h1, it does not compete with it.
   The diamond is a rotated square rather than a character, which keeps it
   optically centred at every size. It wraps as a whole phrase on narrow
   screens, where the divider is dropped. */
.hero__promise {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: clamp(.6rem, 1.5vw, 1.05rem);
  margin-bottom: clamp(1rem, 2.2vw, 1.7rem);
  font-family: var(--font-display);
  font-size: clamp(1.45rem, 1.1rem + 1.4vw, 2.6rem);
  font-weight: 300;
  line-height: 1.16;
  letter-spacing: .008em;
  color: var(--c-on-dark);
  text-shadow: 0 1px 20px rgba(4, 16, 31, .5);
}
.hero__promise em {
  font-style: italic;
  color: var(--c-accent-light);
}
.hero__promise i {
  flex: none;
  width: .42em;
  height: .42em;
  border: 1px solid var(--c-accent-light);
  transform: rotate(45deg);
  opacity: .75;
}
@media (max-width: 400px) {
  .hero__promise i { display: none; }
  .hero__promise { gap: .35rem; }
}

.hero h1 { font-size: var(--fs-h1); color: var(--c-on-dark); line-height: .98; }
.hero h1 em { display: block; font-style: italic; color: var(--c-accent-light); }
.hero__sub {
  max-width: 54ch;
  margin-top: clamp(1.5rem, 3vw, 2.1rem);
  font-size: var(--fs-lead);
  font-weight: 300;
  color: var(--c-on-dark-dim);
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: .9rem;
  margin-top: clamp(2.2rem, 4vw, 3rem);
}
.hero__scroll {
  position: absolute;
  left: 50%;
  bottom: clamp(1.4rem, 3vh, 2.4rem);
  transform: translateX(-50%);
  display: none;
  flex-direction: column;
  align-items: center;
  gap: .8rem;
  font-size: .58rem;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--c-on-dark-dim);
}
.hero__scroll span.bar {
  width: 1px; height: 54px;
  background: linear-gradient(to bottom, var(--c-accent-light), transparent);
  animation: drift 2.6s var(--ease) infinite;
}
@keyframes drift {
  0%   { transform: scaleY(.3); transform-origin: top; opacity: .2; }
  45%  { transform: scaleY(1);  transform-origin: top; opacity: 1; }
  46%  { transform: scaleY(1);  transform-origin: bottom; }
  100% { transform: scaleY(0);  transform-origin: bottom; opacity: .1; }
}
/* Mobile only: img's `height: auto` collapses the picture to its ratio on a
   narrow screen, so it must be forced to fill the hero's full height. Desktop
   is untouched. 124% covers the -12% top/bottom parallax bleed. */
@media (max-width: 899.98px) {
  .hero__media { height: 124%; }
}


/* ==========================================================================
   8b. MAP HERO — the world map behind the home page, and the two places on it

   The markup is one inline SVG in index.html; read the comment above it first,
   because it explains why the beacons are drawn inside the picture's own
   coordinate system rather than positioned over it in percentages.

   The order things are painted in, back to front:

       wall        the lit plaster the map hangs on, drawn far past the
                   photograph so an off-16:9 window fills with more wall
       photo       images/global-presence.jpg, cooled and dimmed
       veil ×4     vertical for legibility, horizontal for the headline,
                   a warm spot over the Gulf-to-Coral-Sea quarter, a vignette
       beacons     halo, two expanding waves, rim, dot
       labels      UAE / AUSTRALIA, over land, hidden on small screens

   The whole thing is one colour away from a re-skin: --map-beacon.
   ========================================================================== */
.hero--map {
  /* Gold rather than the site's blue on purpose. Both beacons sit on navy
     land, where the palette's own accent would disappear into it; this is the
     one warm note on the page and it is what makes the two points read as lit
     rather than printed. Change it here and the halo, the waves, the rim and
     the dot all follow. */
  --map-beacon: #F6C15A;

  /* The beacons are drawn at the map's scale, and the map shrinks to fit the
     window. Below, the narrow breakpoints scale them back up so a dot on a
     phone is still a dot. */
  --pin-s: 1;

  /* What fills the frame for the moment before the SVG paints, and behind the
     hero if the picture never arrives at all. */
  background: #0A1526;
}
.hero__map {
  position: absolute;
  /* Same bleed as .hero__media, and for the same reason: the parallax in
     js/main.js drifts this box, and without the overhang the drift would show
     the page behind it along the top edge. */
  inset: -12% 0;
  z-index: -2;
  /* The SVG is allowed to be wider than this box on a phone — see the bottom
     of the section — and nothing of the sort may reach the page's scrollbar. */
  overflow: hidden;
  will-change: transform;
}
.hero__map-svg {
  display: block;
  width: 100%;
  height: 100%;
}
.map__photo {
  /* The photograph is a warm, brightly lit wall — beautiful, and far too loud
     to put a headline on. Cooled and dimmed here rather than in an image
     editor so the file itself stays reusable elsewhere. */
  filter: saturate(.62) brightness(.82) contrast(1.04);
}
.map__beacon-stop { stop-color: var(--map-beacon); }

/* ---- The beacons ---- */
/* fill-box, and it is load-bearing: an SVG transform-origin defaults to the
   viewBox, so `scale()` here would swing the marker across the Indian Ocean
   instead of growing it where it stands. */
.map__pin {
  transform: translate(var(--px), var(--py)) scale(var(--pin-s));
  transform-box: fill-box;
  transform-origin: center;
}
.map__halo {
  fill: url(#hm-beacon);
  transform-box: fill-box;
  transform-origin: center;
  animation: map-breathe 6s ease-in-out infinite;
}
@keyframes map-breathe {
  0%, 100% { opacity: .6; transform: scale(.92); }
  50%      { opacity: 1;  transform: scale(1.06); }
}
.map__wave {
  fill: none;
  stroke: var(--map-beacon);
  stroke-width: 2.4;
  vector-effect: non-scaling-stroke;
  transform-box: fill-box;
  transform-origin: center;
  animation: map-wave 3.8s cubic-bezier(.2, .62, .3, 1) infinite;
}
/* Half a cycle behind the first, so the pair reads as a rhythm rather than a
   single ring that keeps restarting. */
.map__wave--late { animation-delay: 1.9s; }
@keyframes map-wave {
  0%   { transform: scale(.55); opacity: 0; }
  14%  { opacity: .7; }
  100% { transform: scale(4.6); opacity: 0; }
}
.map__rim {
  fill: none;
  stroke: var(--map-beacon);
  stroke-width: 1.5;
  stroke-opacity: .3;
  vector-effect: non-scaling-stroke;
}
.map__dot {
  fill: var(--map-beacon);
  /* A near-white edge on the gold. It is what makes the point read as a lit
     bead rather than a printed circle. */
  stroke: rgba(255, 246, 228, .85);
  stroke-width: 1.6;
  vector-effect: non-scaling-stroke;
}

/* ---- The labels ---- */
.map__label {
  font-family: var(--font-body);
  font-size: 19px;          /* user units — 19 of the picture's own pixels */
  font-weight: 500;
  letter-spacing: .22em;
  fill: #FFF6E4;
  fill-opacity: .93;
  /* The halo. paint-order puts the stroke behind the fill instead of over it,
     which is the difference between an outline and a smudge. */
  stroke: rgba(4, 12, 26, .7);
  stroke-width: 4;
  stroke-linejoin: round;
  paint-order: stroke fill;
}

/* ---- Narrow screens ----
   The map is drawn whole, so the narrower the window the smaller everything on
   it gets — markers included, since they are drawn at the map's own scale.
   They are scaled back up about their own centres, which is the one transform
   that moves neither of them off its coast. */
@media (max-width: 1099.98px) { .hero--map { --pin-s: 1.5; } }
@media (max-width: 899.98px) {
  .hero--map { --pin-s: 1.8; }
  /* Too small to letter: at this width a 19px label prints at about eight. */
  .map__label { display: none; }
  /* And the band is nudged down the frame, out from behind the headline. */
  .hero__map { inset: -6% 0 -22%; }
}

/* Below 640px, fitting all seven continents into the frame leaves a strip
   about 230px tall with two beacons the size of a full stop in it. So the
   picture stops being fitted and is widened past the frame and slid left
   instead: the window looks at the Gulf, the Indian Ocean and Australia — the
   half of the world this hero is actually about — at two and a half times the
   scale, and the beacons come up with it.

   The two figures are one sum, and because both are percentages of the same
   box the window they cut holds at every phone width: 1672 ÷ 760 = 2.20 puts
   760 of the picture's own pixels across the frame, and 892 × (2.20 ÷ 1672) =
   1.17 makes the first of them column 892 — the Red Sea. That leaves the two
   beacons at a fifth and four fifths of the way across, clear of both edges. */
@media (max-width: 639.98px) {
  /* max-width has to be undone by name: section 2 caps every img and svg on
     the site at the width of its box, and 220% of the box is the whole point
     of this rule. */
  .hero__map-svg { width: 220%; max-width: none; margin-left: -117%; }
  .hero--map { --pin-s: 1.3; }
  .hero__map { inset: -6% 0 -14%; }
}

/* Section 25 stops every animation on the site for motion-sensitive visitors
   by freezing it at its first frame. A wave's first frame is a ring at half
   size and no opacity — nothing at all — so the marker is rebuilt here as a
   still one instead. */
@media (prefers-reduced-motion: reduce) {
  .map__wave { display: none; }
  .map__rim  { stroke-opacity: .55; }
  .map__halo { opacity: .85; }
}


/* ==========================================================================
   9. PAGE HERO — every page except home
   ========================================================================== */
/* Light, airy opening band — the deep green is saved for the CTA, so each
   page has exactly one dark focal moment rather than three. */
.page-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding-block: calc(var(--header-h) + clamp(.6rem, 1.4vw, 1rem)) clamp(2.75rem, 6vw, 4.5rem);
  color: var(--c-body);
  background:
    radial-gradient(70% 120% at 88% -15%, rgba(228, 237, 251, .95), transparent 62%),
    radial-gradient(55% 95% at 0% 115%, rgba(210, 226, 250, .5), transparent 60%),
    var(--c-tint);
  border-bottom: 1px solid var(--line-soft);
}
.page-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: var(--grain);
  opacity: .04;
}
/* Hairline flourish in the top-right corner */
.page-hero::after {
  content: "";
  position: absolute;
  z-index: -1;
  top: -180px; right: -180px;
  width: 420px; height: 420px;
  border: 1px solid var(--c-accent-pale);
  border-radius: 50%;
  opacity: .7;
}
.page-hero h1 {
  font-size: var(--fs-h1-inner);
  color: var(--c-ink);
  max-width: 18ch;
}
.page-hero h1 em { font-style: italic; color: var(--c-accent); }
.page-hero p {
  max-width: 56ch;
  margin-top: 1.15rem;
  font-size: clamp(1rem, .95rem + .3vw, 1.14rem);
  color: var(--c-body);
}
.page-hero .eyebrow { margin-bottom: 1.05rem; }
/* Used where the section directly below has no top padding of its own
   (paired with .section--flush-top), so the two don't add up to a gap */
.page-hero--flush { padding-bottom: clamp(1rem, 2.5vw, 1.6rem); }

/* Optional copy + image split — opt in with .page-hero__grid around the copy */
.page-hero__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(1.75rem, 4vw, 2.75rem);
  align-items: center;
}
.page-hero__figure {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}
@media (min-width: 800px) {
  .page-hero__grid { grid-template-columns: 1.15fr .85fr; }
}

/* Buttons under the inner-page heading — opt in, only the pricing hero uses
   it so far. Sits a little clear of the paragraph above it. */
.page-hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: .7rem;
  margin-top: clamp(1.4rem, 2.6vw, 2rem);
}

.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .55rem;
  margin-bottom: .95rem;
  font-size: .64rem;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  color: var(--c-muted);
}
.breadcrumb a { transition: color var(--t-fast); }
.breadcrumb a:hover { color: var(--c-accent); }
.breadcrumb span[aria-hidden] { opacity: .5; }

/* Breadcrumb row with the in-page services menu on its right */
.crumb-row {
  /* Positioned so it paints above the hero picture. The figure is absolutely
     positioned, which puts it above any static sibling — without this the
     menu pill disappears behind the image once it reaches the right-hand
     side on desktop. Safe to position: it is the figure's sibling, not its
     ancestor, so the figure's containing block is unaffected. */
  position: relative;
  /* Above .svc-hero__copy (z-index:1), not equal to it. The open jump menu
     hangs down over the headline, and at equal z-index the copy wins on DOM
     order — it painted over the menu and ate its taps on mobile. */
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: .95rem;
}
.crumb-row .breadcrumb { margin-bottom: 0; }

/* --------------------------------------------------------------------------
   Jump menu — the six services, reachable without the navbar dropdown.
   A <details> element, so it opens and closes with no JavaScript at all;
   main.js only closes it on choose / Escape / outside tap.
   -------------------------------------------------------------------------- */
.jump { position: relative; }
.jump summary {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .44rem 1rem;
  font-size: .61rem;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--c-navy);
  background: var(--c-surface);
  border: 1px solid var(--c-accent-pale);
  border-radius: var(--radius-pill);
  cursor: pointer;
  user-select: none;
  list-style: none;                      /* default triangle — Firefox */
  transition: border-color var(--t-fast), color var(--t-fast);
}
.jump summary::-webkit-details-marker { display: none; }   /* — Safari */
.jump summary:hover,
.jump[open] summary { border-color: var(--c-accent); color: var(--c-accent); }
.jump__caret { width: 13px; height: 13px; stroke: currentColor; fill: none; transition: transform var(--t-fast); }
.jump[open] .jump__caret { transform: rotate(180deg); }

.jump__menu {
  position: absolute;
  /* Hangs below the pill and aligns to its right edge, which is where the
     pill sits in the breadcrumb row. Desktop re-anchors it to the left. */
  right: 0;
  top: calc(100% + .55rem);
  z-index: 6;
  min-width: 216px;
  display: grid;
  padding: .5rem;
  background: var(--c-surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
}
.jump__menu a {
  display: flex;
  align-items: center;
  gap: .7rem;
  padding: .55rem .7rem;
  font-size: .85rem;
  color: var(--c-ink);
  border-radius: calc(var(--radius) - 6px);
  transition: background var(--t-fast), color var(--t-fast);
}
.jump__menu a:hover { background: var(--c-blue-mist); color: var(--c-accent); }
.jump__menu svg { width: 17px; height: 17px; flex: none; stroke: var(--c-accent); fill: none; stroke-width: 1.4; }

/* Docked copy of the pill. main.js clones the menu into <body> and shows it
   once the breadcrumb row scrolls away, so the next service stays one tap
   away from anywhere down the page. It has to be a sibling of the hero, not a
   child of .crumb-row: that row carries .reveal's `will-change: translate`,
   which makes it the containing block for anything fixed inside it. */
.jump-dock {
  position: fixed;
  /* Meets the scrolled header, which is the shorter of the two heights */
  top: calc(var(--header-h-solid) + .6rem);
  /* Lines up with the container's right edge, so the docked pill lands where
     the real one sat rather than drifting to the window edge on wide screens */
  right: max(var(--gutter), calc((100% - var(--container)) / 2 + var(--gutter)));
  /* Under the header (900), the drawer (950) and the modal (1200), above the
     page and the bottom call bar (880). */
  z-index: 700;
  opacity: 0;
  translate: 0 -.5rem;
  pointer-events: none;
  transition: opacity var(--t-fast), translate var(--t-fast);
}
.jump-dock.is-visible { opacity: 1; translate: 0; pointer-events: auto; }
/* The drawer owns this corner while it's open — a pill glowing behind the
   glass reads as a bug. */
body.is-locked .jump-dock { opacity: 0; pointer-events: none; }
/* Off the page ground now, so it needs its own lift to stay readable over
   whatever section happens to be scrolling underneath. */
.jump-dock summary { box-shadow: var(--shadow-lg); }
/* Seven items plus padding is taller than a short phone in landscape */
.jump-dock .jump__menu {
  max-height: calc(100vh - var(--header-h-solid) - 4rem);
  overflow-y: auto;
}


/* ==========================================================================
   9b. SERVICES PAGE HERO
   Split hero (copy left / image right) with a floating credential card, an
   overlapping card row, and a feature strip beneath.
   ========================================================================== */
.svc-hero {
  position: relative;
  overflow: hidden;
  /* Mobile: the picture is the background, the copy stretches the full height
     of it — heading and intro at the top, buttons pinned to the bottom edge.
     Desktop overrides all three. */
  display: flex;
  align-items: stretch;
  min-height: clamp(36rem, 92vh, 46rem);
  padding-block: var(--header-h) clamp(1.4rem, 4vw, 2rem);
  /* Both glows are centred low on purpose. That leaves the top strip as a flat
     --c-tint, so the opaque navbar can match it exactly and disappear into the
     page instead of leaving a faint seam along its bottom edge. */
  background:
    radial-gradient(55% 70% at 18% 78%, rgba(210, 226, 250, .55), transparent 62%),
    radial-gradient(45% 60% at 95% 92%, rgba(228, 237, 251, .85), transparent 60%),
    var(--c-tint);
}
/* Two rows: the breadcrumb strip, then the copy. The picture is absolutely
   positioned at every size, so it is never a grid item and there is no
   column gap to set — row spacing lives on .crumb-row's own margin. */
.svc-hero__grid { width: 100%; display: grid; grid-template-rows: auto 1fr; gap: 0; }
/* min-width:0 stops the long headline forcing the grid column wider than its track.
   The stacking context goes here and NOT on .svc-hero__grid — a positioned grid
   would become the containing block for the absolutely positioned figure and
   collapse it to the height of the copy. */
.svc-hero__copy { position: relative; z-index: 1; min-width: 0; }

/* Badge above the headline */
.pill {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  margin-bottom: 1.1rem;
  padding: .44rem 1rem;
  font-size: .61rem;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--c-navy);
  background: var(--c-surface);
  border: 1px solid var(--c-accent-pale);
  border-radius: var(--radius-pill);
}
.pill svg { width: 14px; height: 14px; flex: none; stroke: var(--c-accent); fill: none; stroke-width: 1.2; }

.svc-hero h1 {
  font-size: clamp(2.25rem, 1.6rem + 2.8vw, 4rem);
  font-weight: 700;          /* the reference headline is a bold serif */
  line-height: 1.02;
  letter-spacing: -.02em;
  color: var(--c-ink);
  max-width: 13ch;
}
.svc-hero h1 em { display: block; font-style: normal; color: var(--c-accent); }
.svc-hero__sub {
  max-width: 44ch;
  margin-top: 1.05rem;
  font-size: clamp(.98rem, .94rem + .22vw, 1.08rem);
}
.svc-hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(1.1rem, 2.6vw, 1.8rem);
  margin-top: clamp(1.5rem, 3vw, 2.1rem);
}

/* Secondary action — circular button with a two-line label */
.play-link { display: inline-flex; align-items: center; gap: .9rem; }
.play-link__circle {
  display: grid;
  place-items: center;
  width: 46px; height: 46px;
  flex: none;
  background: var(--c-navy);
  border-radius: 50%;
  transition: background var(--t-fast), transform var(--t-fast);
}
.play-link__circle svg { width: 13px; height: 13px; margin-left: 2px; fill: var(--c-accent-light); stroke: none; }
.play-link:hover .play-link__circle { background: var(--c-accent); transform: scale(1.06); }
.play-link:hover .play-link__circle svg { fill: #fff; }
.play-link b {
  display: block;
  font-size: .78rem;
  font-weight: 400;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--c-navy);
}
/* Was --c-muted, but this line sits over the hero photograph — the darker
   body colour keeps "Our three-step process" readable on a busy ground. */
.play-link i { display: block; font-style: normal; font-size: .8rem; color: var(--c-navy-mid); }

/* ---- Mobile: the picture becomes the hero's background -------------------
   It fills the section edge to edge and top to bottom, running up behind the
   header, with the copy laid over it. ONE large circular arc cuts the
   top-left corner — the same single sweep as desktop, not a symmetrical dome —
   and the pale page shows through the corner it vacates.

   A single length, never a percentage: percentages resolve against width and
   height separately and would flatten the circle into an ellipse. ---------- */
.svc-hero__figure {
  position: absolute;
  inset: 0;
  z-index: 0;
  margin: 0;
  /* The radius lives on the figure, not the image, so the wash below is
     clipped to the same shape. Put it on the image instead and the wash
     spills into the corner the arc cut away, erasing the curve. */
  border-radius: 76vw 0 0 0;
  overflow: hidden;
}
.svc-hero__media {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 56%;   /* tall crop of a landscape source — hold the room */
  border-radius: 0;
  box-shadow: none;
}
/* On mobile the copy lies over the photograph, so it needs a ground of its own.
   A glow halo alone only works where the picture happens to be pale — over the
   darker parts of the room the light strokes vanish. A pale wash across the
   whole picture gives every line the same predictable ground, and navy on it
   clears WCAG AA everywhere rather than in the light corners only.
   Desktop (≥900px) skips all of it: there the copy is beside the image on
   the plain page and needs no help. */
@media (max-width: 899.98px) {
  /* One pair of tokens drives both the wash and the glow, so the midnight
     override in section 26 only has to flip these two lines rather than repeat
     the whole block. Daylight: a pale wash with navy copy over it. */
  .svc-hero {
    --svc-scrim: 235, 242, 252;
    --svc-halo:  255, 255, 255;
    --svc-em:    #123A80;   /* deeper than --c-accent, which softens over a photo */
  }

  /* Everything hugs the top; only the buttons drop to the bottom edge. The
     copy takes the grid's 1fr row, so its auto margin has room to push. */
  .svc-hero__copy { display: flex; flex-direction: column; }
  .svc-hero__actions { margin-top: auto; }

  /* The wash goes on the figure, not the section, so it is clipped by the same
     corner arc as the picture — put it on .svc-hero and it would square the
     curve off again. Heaviest at the top, where the headline and the intro sit;
     it eases toward the bottom where only the buttons are, so the room still
     reads through. Tinted to the page ground rather than flat white or black. */
  .svc-hero__figure::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(
      180deg,
      rgba(var(--svc-scrim), .93) 0%,
      rgba(var(--svc-scrim), .88) 48%,
      rgba(var(--svc-scrim), .80) 80%,
      rgba(var(--svc-scrim), .78) 100%
    );
  }

  /* The halo stays, trimmed, as a second line of defence on the busiest
     patches — it carries the scrim's colour, so it reads as more of the same
     ground rather than an outline. Skipped on the solid button, which has a
     ground of its own. */
  .svc-hero .breadcrumb,
  .svc-hero h1,
  .svc-hero__sub,
  .play-link b,
  .play-link i {
    text-shadow:
      0 0 4px  rgba(var(--svc-halo), .95),
      0 0 12px rgba(var(--svc-halo), .8);
  }
  /* Weight and colour do the rest, not size — the body font is Jost 300, and
     hairline strokes are what actually fail over a photograph. Stepping to 500
     thickens every stem without the text growing a pixel. Font smoothing back
     to `auto` so the browser keeps the extra weight instead of thinning it
     away again. */
  .svc-hero__grid { -webkit-font-smoothing: auto; -moz-osx-font-smoothing: auto; }
  .svc-hero h1      { color: var(--c-navy); }
  .svc-hero h1 em   { color: var(--svc-em); }
  .svc-hero__sub    { color: var(--c-ink); font-weight: 500; }
  .svc-hero .breadcrumb { color: var(--c-navy); font-weight: 500; }
  .play-link b { font-weight: 500; color: var(--c-navy); }
  .play-link i { font-weight: 500; color: var(--c-ink); }
}

/* --------------------------------------------------------------------------
   Card row — sits across the seam below the hero
   -------------------------------------------------------------------------- */
.svc-cards-wrap { position: relative; z-index: 5; }
.svc-cards { display: grid; grid-template-columns: 1fr; gap: clamp(.9rem, 1.6vw, 1.2rem); }

/* Icon sits to the left of the copy, which keeps the cards low and wide */
.svc-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: clamp(.85rem, 1.6vw, 1.1rem);
  align-content: start;
  padding: clamp(1rem, 1.8vw, 1.25rem);
  background: var(--c-surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  transition: transform var(--t-med), box-shadow var(--t-med), border-color var(--t-med);
}
.svc-card:hover { transform: translateY(-5px); box-shadow: var(--shadow); border-color: var(--c-accent-pale); }

.svc-card h3 {
  grid-column: 2;
  grid-row: 1;
  font-family: var(--font-body);
  font-size: .98rem;
  font-weight: 400;
  line-height: 1.35;
  letter-spacing: .005em;
  color: var(--c-ink);
}
/* Copy and arrow stay in column 2, indented under the title */
.svc-card p {
  grid-column: 2;
  grid-row: 2;
  margin: .3rem 0 .45rem;
  font-size: .85rem;
  line-height: 1.5;
}

/* Spanning all three rows and centring does two jobs: the icon sits lower
   than the title, and it stops forcing row 1 to be 46px tall — which is
   most of the height the card just lost. */
.icon-tile {
  grid-column: 1;
  grid-row: 1 / span 3;
  align-self: center;
  display: grid;
  place-items: center;
  width: 42px; height: 42px;
  background: var(--c-blue-mist);
  border-radius: 12px;
  transition: background var(--t-med);
}
.icon-tile svg {
  width: 21px; height: 21px;
  stroke: var(--c-navy);
  fill: none;
  stroke-width: 1;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: stroke var(--t-med);
}
.svc-card:hover .icon-tile { background: var(--c-accent-pale); }
.svc-card:hover .icon-tile svg { stroke: var(--c-accent); }

.arrow {
  grid-column: 2;
  grid-row: 3;
  width: 18px; height: 18px;
  stroke: var(--c-accent);
  fill: none;
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform var(--t-fast);
}
.svc-card:hover .arrow { transform: translateX(5px); }

/* Arrow inside a solid button */
.btn__arrow {
  width: 16px; height: 16px;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform var(--t-fast);
}
.btn:hover .btn__arrow { transform: translateX(3px); }

/* --------------------------------------------------------------------------
   Feature strip — tinted panel with hairline-divided items
   -------------------------------------------------------------------------- */
.feature-strip {
  display: grid;
  grid-template-columns: 1fr;
  padding: clamp(1.2rem, 2.4vw, 1.8rem);
  background: var(--c-blue-mist);
  border-radius: var(--radius-lg);
}
.feature {
  position: relative;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1.05rem clamp(.6rem, 2vw, 1.7rem);
}
/* Dividers only at the 4-across breakpoint, where every gap is a real one */
.feature + .feature::before {
  content: "";
  display: none;
  position: absolute;
  left: 0; top: 50%;
  transform: translateY(-50%);
  width: 1px; height: 46px;
  background: var(--line);
}
.feature svg {
  width: 30px; height: 30px;
  flex: none;
  stroke: var(--c-accent);
  fill: none;
  stroke-width: 1;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.feature b { display: block; font-size: .9rem; font-weight: 400; color: var(--c-ink); line-height: 1.4; }
.feature span { display: block; font-size: .82rem; color: var(--c-muted); line-height: 1.5; }


/* ==========================================================================
   10. TRUST BAR
   ========================================================================== */
/* Sits on the page ground; only the hairline rules separate the four items */
.trust { border-bottom: 1px solid var(--line-soft); }
.trust__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1px;
  background: var(--line-soft);
}
.trust__item {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1.6rem clamp(.5rem, 2vw, 1.6rem);
  /* Page ground, not surface — these tile edge to edge, so a card colour
     here would paint a pale band right across the page. The 1px grid gaps
     let the container's line colour through as dividers. */
  background: var(--c-bg);
}
.trust__item svg {
  width: 26px; height: 26px;
  flex: none;
  stroke: var(--c-accent);
  stroke-width: 1;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.trust__item span {
  font-size: .72rem;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  color: var(--c-navy);
  line-height: 1.5;
}


/* ==========================================================================
   11. SERVICES — card grid (home + services page)
   ========================================================================== */
.services__top {
  display: grid;
  gap: clamp(2rem, 4vw, 3.5rem);
  align-items: center;
  margin-bottom: clamp(2.4rem, 4.5vw, 3.5rem);
}
.services__all { margin-top: 2.2rem; }
/* The editorial photo beside the section intro — works as a .ph placeholder
   now, and as a real <img> once images/home-services-intro.jpg exists */
.services__figure {
  width: 100%;
  aspect-ratio: 16 / 11;
  object-fit: cover;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}
.services__grid {
  display: grid;
  grid-template-columns: 1fr;
  /* Wider than the site's usual card gap. Each card carries a mount ring 6px
     out on every side, so 1rem of gutter would leave neighbouring frames 4px
     apart — close enough to read as one crowded line instead of two. */
  gap: clamp(1.5rem, 2.5vw, 2.1rem);
}
.card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: clamp(1.8rem, 3.4vw, 2.6rem);
  background: var(--c-surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: transform var(--t-med), box-shadow var(--t-med), border-color var(--t-med);
}
/* Brass hairline that draws across the top on hover */
.card::before {
  content: "";
  position: absolute;
  top: 0; left: 0;
  z-index: 2;
  width: 100%; height: 2px;
  background: linear-gradient(90deg, var(--c-accent), var(--c-accent-light));
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .6s var(--ease);
}
.card:hover { transform: translateY(-6px); border-color: transparent; box-shadow: var(--shadow); }
.card:hover::before { transform: scaleX(1); }
.card__icon {
  width: 46px; height: 46px;
  margin-bottom: 1.7rem;
  stroke: var(--c-navy);
  stroke-width: 1;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: stroke var(--t-med), transform var(--t-med);
}
.card:hover .card__icon { stroke: var(--c-accent); transform: translateY(-3px); }
.card h3 { margin-bottom: .8rem; }
.card p { margin-bottom: 1.9rem; font-size: .96rem; }
.card .link { margin-top: auto; }

/* ---- Media cards (home) — poster beside the copy ----
   Every service artwork on disk is portrait: 1122×1402 mostly, and none of it
   wider than 3:4. The old 16:10 box cropped those posters down to a band
   across the middle, throwing away the inclusion list and the guarantee panel
   that the artwork is largely made of. The box is 4:5 now, which the majority
   of the files match exactly.

   Full height and full width at the same time would make a card in a
   two-column grid about 700px tall, so past 600px the card turns on its side
   and the poster sits beside the copy instead of above it. */
.card--media { padding: 0; }

/* ---- The edge ----
   Each artwork is a print, so each card is mounted like one: a hairline on the
   card itself, a few pixels of the page's own ground, then a second hairline
   outside that. Two 1px lines with air between them read as more considered
   than one heavier line, and nothing here is thicker than 1px.

   Both rings are box-shadow spreads rather than extra elements. That buys
   three things: the corners follow the card's 22px radius exactly with no
   second radius to keep in sync, the frame lifts with the card on hover
   instead of staying behind, and it costs no markup.

   Every colour is a theme token. The mount is painted in `--c-bg` — the page
   ground itself — so the gap stays invisible on midnight and graphite too,
   where a hardcoded white would show as a bright halo. */
.card--media {
  border-color: var(--line);
  box-shadow:
    0 0 0 5px var(--c-bg),
    0 0 0 6px var(--line-soft),
    var(--shadow-sm);
}
/* .card:hover drops the border to transparent and swaps the shadow wholesale,
   which would take the frame with it. Same specificity, so this wins on source
   order — it has to stay below that rule. The outer line warms to the accent
   rather than disappearing. */
.card--media:hover {
  border-color: var(--c-accent-pale);
  box-shadow:
    0 0 0 5px var(--c-bg),
    0 0 0 6px var(--c-accent-pale),
    var(--shadow);
}

.card__media {
  position: relative;
  margin: 0;
  aspect-ratio: 4 / 5;
  overflow: hidden;
}
.card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.03);
  transition: transform 1.2s var(--ease);
}
.card--media:hover .card__media img { transform: scale(1.09); }
/* The navy fade that used to sit here is gone. It existed to keep the icon
   chip legible against a photograph, and the artwork is not a photograph —
   every poster ends in a printed band of icons and a guarantee panel, which
   the fade was dimming now that the whole of it is visible. The chip carries
   its own opaque disc and shadow, so it never needed the help. */
.card__chip {
  position: absolute;
  /* Top right, not bottom left: bottom left is where each poster prints its
     own row of icons, and the chip landed square on the first one. */
  right: 1.15rem; top: 1.15rem;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 48px; height: 48px;
  background: rgba(248, 251, 255, .92);
  border-radius: 50%;
  box-shadow: var(--shadow-sm);
  backdrop-filter: blur(6px);
  transition: transform var(--t-med), background var(--t-med);
}
.card__chip svg {
  width: 24px; height: 24px;
  stroke: var(--c-navy);
  stroke-width: 1.1;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: stroke var(--t-med);
}
.card--media:hover .card__chip { transform: translateY(-4px); background: #fff; }
.card--media:hover .card__chip svg { stroke: var(--c-accent); }
.card__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: 1.5rem clamp(1.4rem, 2.4vw, 1.8rem) 1.7rem;
  min-width: 0;
}

/* Sideways at every width, phones included.

   `align-items: center` is doing the load-bearing work here, not the alignment
   it appears to be doing.

   A flex row stretches its items to the row's height by default. The row's
   height is whichever of the two is taller — and on a narrow card with a long
   description, that is the paragraph, not the picture. The figure would then
   be stretched past its 4:5 ratio and `object-fit: cover` would crop the
   poster to fit, which is the exact fault being fixed.

   Any value other than `stretch` switches that off: the figure keeps the
   height its aspect-ratio asks for and the poster is never cut, whatever the
   copy does. `center` is the one that also reads well — the copy sits level
   with the middle of the picture rather than hanging off its top. */
.card--media { flex-direction: row; align-items: center; }

/* Sized so the picture is normally the taller of the two and fills the card
   edge to edge. When a long description does outgrow it the card simply gets a
   band of its own surface above and below the poster — whitespace, not a
   cropped image. */
.card__media { flex: 0 0 clamp(112px, 42%, 265px); }
.card--media p { margin-bottom: 1.35rem; }

/* ---- Phones ----
   The same card, scaled to the width available. A 42% column on a 360px phone
   is 134px wide and 168px tall, and the copy at desktop sizing needs nearer
   290px in the 140px-wide track left over — the picture would end up a small
   thing floating in the middle of a tall card.

   So the copy is sized to the picture rather than the other way round: smaller
   type, tighter padding, and the description held to three lines. Three lines
   is the most that fits beside a poster this tall, and the full paragraph is
   one tap away behind Learn more. */
@media (max-width: 599.98px) {
  .card--media .card__body {
    padding: 1rem 1.05rem 1rem 1.15rem;
    gap: 0;
  }
  .card--media h3 {
    margin-bottom: .45rem;
    font-size: clamp(1.02rem, .86rem + .7vw, 1.22rem);
    line-height: 1.18;
  }
  .card--media p {
    margin-bottom: .8rem;
    font-size: clamp(.78rem, .68rem + .42vw, .87rem);
    line-height: 1.5;
    /* -webkit- prefixed and unprefixed both: line-clamp is only just
       unprefixed, and the prefixed trio is what actually ships everywhere. */
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
            line-clamp: 3;
    overflow: hidden;
  }
  .card--media .link { font-size: .64rem; }

  /* The chip is 48px against a poster only ~143px wide here — a third of it,
     sitting on the artwork. Scaled down and tucked further into the corner. */
  .card--media .card__chip { width: 34px; height: 34px; top: .6rem; right: .6rem; }
  .card--media .card__chip svg { width: 17px; height: 17px; }
}

/* Two columns, and the feature card that keeps seven of them square.
   Not at the usual 900px: a sideways card in a 405px track leaves the copy
   about 26 characters to the line, and the description runs to six of them —
   taller than the poster beside it. 1080px is where the arithmetic comes
   right. Below it the cards are sideways but full width, which is roomier
   still. */
@media (min-width: 1080px) {
  .services__grid { grid-template-columns: repeat(2, 1fr); }

  /* Seven cards over two columns would leave an orphan on the last row. The
     first one taking the full width makes it 1 + 3×2 — the grid closes, and
     the leading service gets the emphasis it was owed anyway. */
  .card--feature { grid-column: 1 / -1; }
  .card--feature .card__media { flex: 0 0 clamp(230px, 24%, 290px); }
  .card--feature .card__body  { padding-inline: clamp(2rem, 3.6vw, 3.2rem); }
  /* The description is two lines long against a full-width card, so the type
     carries the space rather than the copy being asked to fill it. */
  .card--feature h3 { font-size: clamp(1.7rem, 1.2rem + 1.3vw, 2.35rem); }
  .card--feature p  { max-width: 52ch; font-size: 1.08rem; line-height: 1.62; }
}


/* ==========================================================================
   12. SERVICE DETAIL — services page
   ========================================================================== */
/* Most of the separation lives in padding-bottom. A big padding-top would be
   scrolled to — not past — when a jump link fires, leaving the visitor
   staring at empty space above the service they picked.

   No scroll-margin-top here on purpose: html's scroll-padding-top already
   clears the header, and the two would ADD, pushing the target twice as far
   down as intended. */
.svc {
  padding-block: clamp(.6rem, 1.2vw, .85rem) clamp(3.5rem, 7vw, 6rem);
  border-top: 1px solid var(--line);
}
.svc:first-of-type { border-top: 0; }
/* The last block's bottom padding used to stack with the section's own and
   the CTA's — three paddings compounding to ~296px of blank page. It only
   needs to separate itself from the next service, and there isn't one. */
.svc:last-of-type { padding-bottom: 0; }

/* ---- Mobile layout ------------------------------------------------------
   Heading across the top, picture floated to one side with the copy running
   beside it, inclusion list clearing underneath.

   A float — not a grid column — because the paragraphs are far taller than
   the picture on a phone. Text flows back to full width once it passes the
   image; a grid column would leave a tall empty gap there instead.

   The figure must stay AFTER the heading in the markup: a float attaches to
   the line box it appears on, so a figure written first would pull the
   heading up alongside it. ------------------------------------------------ */
.svc__grid { display: block; }
.svc__grid::after { content: ""; display: block; clear: both; }  /* contain the float */

/* One editorial image per category, alternating side down the page */
.svc__figure {
  position: relative;
  float: left;
  /* Half the width for the picture, half for the copy. The gap comes out of
     the text's half so the image is a true 50%. */
  width: 50%;
  margin: .3rem clamp(.8rem, 2.4vw, 1.1rem) .7rem 0;
}
.svc:nth-of-type(even) .svc__figure {
  float: right;
  margin-right: 0;
  margin-left: clamp(.8rem, 2.4vw, 1.1rem);
}
.svc__media {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}
/* Icon, heading and price pill share one line — so a jump link lands directly
   on the service name, and the price reads as part of the title rather than a
   separate row. Wraps to a second line when the column gets tight. */
.svc__title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: clamp(.8rem, 1.6vw, 1.1rem);
  margin-bottom: 1.05rem;
}
.svc__title .card__icon { width: 44px; height: 44px; margin-bottom: 0; flex: none; }
.svc__title h2 { font-size: clamp(1.75rem, 1.3rem + 1.5vw, 2.5rem); margin-bottom: 0; }
.svc__from {
  display: inline-flex;
  align-items: baseline;
  gap: .5rem;
  margin-bottom: 0;
  padding: .44rem 1rem;
  font-size: .66rem;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  white-space: nowrap;              /* keep the tag on one line */
  color: var(--c-accent);
  background: var(--c-surface);
  border: 1px solid var(--c-accent-pale);
  border-radius: var(--radius-pill);
}
/* Hyphenation only earns its keep in the narrow column beside the picture;
   it's switched back off at the desktop breakpoint. */
.svc__body { hyphens: auto; }
/* No max-width while the picture is floated: capping the block would pull its
   right edge in behind the float and squeeze the lines into a narrow gutter
   even though there is empty space beyond the cap. Reinstated at ≥900px. */
.svc__body > p { text-wrap: pretty; }
.svc__body > p.lead {
  font-size: clamp(1.02rem, .97rem + .3vw, 1.15rem);
  line-height: 1.6;
}
.svc__body > p:not(.lead) { font-size: .95rem; line-height: 1.62; }
.svc__body > p + p { margin-top: .85rem; }

/* Full width under the picture at every size. clear + padding rather than a
   margin, so the clearance and the gap can't collapse into each other. */
.svc__includes { clear: both; padding-top: 1.6rem; }

/* Small caps label trailed by a hairline — keeps the section break legible
   at much tighter spacing than a plain heading would need. */
.svc__incl-title {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin: 0 0 1.05rem;
  font-size: .64rem;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--c-accent);
}
.svc__incl-title::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

/* Blue-ticked inclusion list, used on services + pricing */
.incl { display: grid; gap: .58rem; }
.incl li {
  display: grid;
  grid-template-columns: 15px 1fr;
  gap: .7rem;
  align-items: start;
  font-size: .9rem;
  line-height: 1.5;
}
.incl li::before {
  content: "";
  width: 15px; height: 15px;
  margin-top: .3em;
  background: var(--tick) no-repeat center / contain;
}


/* ==========================================================================
   13. WHY CHOOSE US
   ========================================================================== */
.why {
  background-image: radial-gradient(70% 60% at 88% 4%, rgba(222, 234, 250, .7), transparent 60%);
}
.why__layout { display: grid; gap: clamp(2.5rem, 6vw, 5rem); align-items: center; }

/* The figure is the positioning context, so the plaque anchors correctly
   whether the media is a placeholder block or a real <img>. */
.why__figure { position: relative; margin: 0; }
.why__media {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  /* Source is landscape, slot is 4:5 portrait — anchor the crop right of centre
     so the cleaner stays in frame. Retune if you swap in a portrait photo
     (a true 4:5 source needs no object-position at all). */
  object-position: 85% center;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
/* Floating credential plaque over the image */
.why__badge {
  position: absolute;
  right: clamp(-.5rem, -1vw, 1rem);
  bottom: clamp(1.2rem, 4vw, 2.2rem);
  z-index: 2;
  padding: 1.4rem 1.7rem;
  text-align: center;
  background: var(--c-surface);
  border: 1px solid var(--c-accent-pale);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.why__badge b {
  display: block;
  font-family: var(--font-display);
  font-size: 2.6rem;
  font-weight: 400;
  line-height: 1;
  color: var(--c-navy);
}
.why__badge span {
  display: block;
  margin-top: .5rem;
  font-size: .58rem;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--c-accent);
}
/* The vertical rhythm in this column is deliberately tighter than the site
   default: the copy has to start and finish level with the photograph beside
   it, and four numbered points at the usual spacing overshoot it badly. */
.why .section-head .eyebrow { margin-bottom: 1rem; }
.why .section-head h2 {
  font-size: clamp(1.9rem, 1.35rem + 1.5vw, 2.75rem);
  line-height: 1.12;
}
.why .section-head p.lead {
  margin-top: .9rem;
  font-size: clamp(1rem, .96rem + .22vw, 1.1rem);
  line-height: 1.6;
}

.why__points { margin-top: 1.6rem; display: grid; gap: 0; }
.point {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: clamp(1.1rem, 2.4vw, 1.9rem);
  padding-block: 1.05rem;
  border-top: 1px solid var(--line);
}
.point:last-child { border-bottom: 1px solid var(--line); }
.point__num {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 400;
  letter-spacing: .06em;
  color: var(--c-accent);
  padding-top: .2rem;
}
.point h3 {
  font-size: 1.08rem;
  font-family: var(--font-body);
  font-weight: 400;
  letter-spacing: .005em;
  color: var(--c-ink);
  margin-bottom: .2rem;
}
.point p { font-size: .92rem; line-height: 1.6; }

.why__more { margin-top: 1.4rem; }


/* ==========================================================================
   14. HOW IT WORKS
   ========================================================================== */
.how__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(2.5rem, 4vw, 2rem);
  margin-top: clamp(3rem, 6vw, 4.5rem);
}
.step { position: relative; padding-right: clamp(0rem, 2vw, 2rem); }
.step__num {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(3.6rem, 7vw, 5.4rem);
  font-weight: 300;
  line-height: 1;
  color: transparent;
  -webkit-text-stroke: 1px rgba(37, 99, 235, .5);
  margin-bottom: 1.1rem;
  transition: color var(--t-slow);
}
.step:hover .step__num { color: rgba(37, 99, 235, .12); }
.step h3 { margin-bottom: .7rem; }
.step p { font-size: .96rem; max-width: 38ch; }
/* Connector hairline between steps (desktop only) */
.step:not(:last-child)::after {
  content: "";
  display: none;
  position: absolute;
  top: 2.2rem; right: 0;
  width: 1px; height: calc(100% - 1rem);
  background: var(--line);
}


/* ==========================================================================
   14b. CORE VALUES — home page + about page
   --------------------------------------------------------------------------
   The four values the business states in its own words. Same material as
   .card and .value elsewhere (off-white surface, hairline, 22px radius, lift
   on hover) so they read as part of the set; the icon tile is .icon-tile from
   the services page at a slightly larger size.

   Every colour here is a token, so midnight (26) and graphite (28) inherit it
   with nothing to restate — the one thing that could have needed a per-theme
   line, the hover wash .value::before uses, is deliberately not repeated.
   ========================================================================== */
.corevals__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(.75rem, 1.6vw, 1.15rem);
  margin-top: clamp(2rem, 4vw, 3rem);
}
.coreval {
  display: flex;
  flex-direction: column;
  padding: clamp(1.4rem, 2.6vw, 1.9rem);
  background: var(--c-surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  transition: transform var(--t-med), box-shadow var(--t-med), border-color var(--t-med);
}
.coreval:hover { transform: translateY(-5px); border-color: transparent; box-shadow: var(--shadow); }

.coreval__ico {
  display: grid;
  place-items: center;
  width: 46px; height: 46px;
  margin-bottom: 1.15rem;
  background: var(--c-blue-mist);
  border-radius: 14px;
  transition: background var(--t-med);
}
.coreval__ico svg {
  width: 23px; height: 23px;
  stroke: var(--c-navy);
  fill: none;
  stroke-width: 1;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: stroke var(--t-med);
}
.coreval:hover .coreval__ico { background: var(--c-accent-pale); }
.coreval:hover .coreval__ico svg { stroke: var(--c-accent); }

.coreval h3 { font-size: 1.2rem; margin-bottom: .55rem; }
.coreval p  { font-size: .92rem; line-height: 1.62; color: var(--c-muted); }
/* The stated value leads the card in the darker ink; the line explaining how
   it is kept follows in the muted body colour. Not bolder — heavier weight in
   Jost at this size reads as a second heading under the real one. */
.coreval p b { font-weight: 400; color: var(--c-ink); }


/* ==========================================================================
   15. TESTIMONIALS
   ========================================================================== */
.testimonials { border-top: 1px solid var(--line-soft); overflow: hidden; }
.quotes {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(min(84vw, 380px), 1fr);
  gap: clamp(1rem, 2vw, 1.6rem);
  margin-top: clamp(2.8rem, 5vw, 4rem);
  padding-bottom: .5rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  /* bleed the track to the screen edge on mobile */
  margin-inline: calc(var(--gutter) * -1);
  padding-inline: var(--gutter);
}
.quotes::-webkit-scrollbar { display: none; }
.quote {
  scroll-snap-align: center;
  display: flex;
  flex-direction: column;
  padding: clamp(1.9rem, 3.4vw, 2.7rem);
  /* Surface like every other card — it used to be --c-bg, which now equals
     the page ground and left these showing as a bare outline. */
  background: var(--c-surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  transition: transform var(--t-med), box-shadow var(--t-med);
}
.quote:hover { transform: translateY(-4px); box-shadow: var(--shadow); }
.stars { display: flex; gap: .28rem; margin-bottom: 1.5rem; }
.stars svg { width: 14px; height: 14px; fill: var(--c-accent); }
.quote blockquote {
  flex: 1;
  font-family: var(--font-display);
  font-size: clamp(1.16rem, 1rem + .5vw, 1.42rem);
  font-style: italic;
  font-weight: 300;
  line-height: 1.5;
  color: var(--c-ink);
  text-wrap: pretty;
}
.quote figcaption {
  display: flex;
  align-items: center;
  gap: .95rem;
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line);
}
.quote__mono {
  display: grid;
  place-items: center;
  width: 42px; height: 42px;
  flex: none;
  font-size: .78rem;
  letter-spacing: .06em;
  color: var(--c-blue);
  background: var(--c-blue-mist);
  border-radius: 50%;
}
.quote__who b { display: block; font-weight: 400; font-size: .94rem; color: var(--c-ink); line-height: 1.4; }
.quote__who span {
  display: block;
  font-size: .66rem;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  color: var(--c-muted);
}
.dots { display: flex; justify-content: center; gap: .55rem; margin-top: 2rem; }
.dots button {
  width: 26px; height: 2px;
  background: var(--line);
  border-radius: 2px;
  transition: background var(--t-fast), width var(--t-fast);
}
.dots button[aria-current="true"] { background: var(--c-accent); width: 40px; }


/* ==========================================================================
   16. STATS
   ========================================================================== */
/* Light band with forest numerals — keeps the page airy and leaves the
   CTA as the only deep-green moment. */
.stats {
  padding-block: clamp(3.5rem, 7vw, 5.5rem);
  color: var(--c-body);
  background:
    radial-gradient(60% 130% at 50% 0%, rgba(228, 237, 251, .8), transparent 65%),
    var(--c-tint);
  border-block: 1px solid var(--line-soft);
}
.stats__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(2rem, 4vw, 3rem) 1rem;
  text-align: center;
}
/* Hairline separators between figures on wide screens */
.stat { position: relative; }
.stat + .stat::before {
  content: "";
  display: none;
  position: absolute;
  left: 0; top: 50%;
  width: 1px; height: 56px;
  transform: translateY(-50%);
  background: var(--line);
}
.stat b {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(2.6rem, 6vw, 4.2rem);
  font-weight: 300;
  line-height: 1;
  color: var(--c-navy);
  font-variant-numeric: tabular-nums;
}
.stat span {
  display: block;
  margin-top: .9rem;
  font-size: .62rem;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--c-accent);
}


/* ==========================================================================
   17. CTA BAND
   ========================================================================== */
.cta { padding-block: clamp(2rem, 5vw, 3.5rem); }
/* The single deep-navy moment on each page — so it gets the richest, most
   layered treatment rather than a flat fill. */
.cta__band {
  position: relative;
  overflow: hidden;
  padding: clamp(1.75rem, 4.5vw, 3rem) clamp(1.4rem, 4vw, 3.25rem);
  text-align: center;
  color: var(--c-on-dark);
  background:
    radial-gradient(115% 145% at 6% -10%, #1D4ED8 0%, transparent 55%),
    radial-gradient(85% 120% at 98% 105%, rgba(96, 165, 250, .40) 0%, transparent 58%),
    linear-gradient(158deg, #061731 0%, #0B2545 46%, #14396B 100%);
  border-radius: clamp(var(--radius-lg), 3vw, 32px);
  box-shadow:
    inset 0 0 0 1px rgba(96, 165, 250, .24),
    var(--shadow-lg);
}
.cta__band::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--grain);
  opacity: .07;
  pointer-events: none;
}
.cta__band h2 {
  max-width: 22ch;
  margin: .5rem auto 0;
  font-size: clamp(1.65rem, 1.2rem + 1.9vw, 2.6rem);
  color: var(--c-bg);
}
.cta__band h2 em { font-style: italic; color: var(--c-accent-light); }
/* :not() rather than a class, so the lead is styled without touching the
   eyebrow or the note — both of which are also <p> in this block. */
.cta__band > p:not(.eyebrow):not(.cta__note) {
  max-width: 46ch;
  margin: .7rem auto 0;
  font-size: .93rem;
  line-height: 1.6;
  color: var(--c-on-dark-dim);
}
.cta__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: .7rem;
  margin-top: 1.35rem;
}
/* Hairline above rather than a wide margin — it separates the fine print
   from the buttons in 1px instead of 35. */
.cta__note {
  margin-top: 1.35rem;
  padding-top: 1.1rem;
  border-top: 1px solid rgba(96, 165, 250, .18);
  font-size: .62rem;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  line-height: 1.6;
  color: rgba(234, 242, 255, .5);
}

/* Past 1040px the band goes two-up: copy on the left, buttons on the right,
   fine print across the foot. Same five elements, roughly half the height,
   and it reads as a considered strip rather than a stack of centred lines.
   Placed by selector, so no wrapper element is needed on any of the pages.

   Not lower than 1040: the button pair is a fixed ~420px, so below this the
   copy column is squeezed and the paragraph runs to four cramped lines —
   the centred stack is the better layout down there. */
@media (min-width: 1040px) {
  .cta__band {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    column-gap: clamp(1.75rem, 4vw, 3.5rem);
    text-align: left;
  }
  .cta__band > .eyebrow  { grid-area: 1 / 1; }
  /* Wider caps than the centred layout uses — the column is already narrowed
     by the buttons beside it, and tighter measures just leave a hole in the
     middle of the band while adding lines to the copy. */
  .cta__band > h2        { grid-area: 2 / 1; max-width: 26ch; margin-inline: 0; }
  .cta__band > p:not(.eyebrow):not(.cta__note) {
    grid-area: 3 / 1;
    max-width: 58ch;
    margin-inline: 0;
  }
  .cta__actions          { grid-area: 1 / 2 / 4 / 3; margin-top: 0; flex-wrap: nowrap; }
  .cta__note             { grid-area: 4 / 1 / 5 / 3; }

  /* The eyebrow's centring rule adds a trailing hairline; drop it so the
     rule sits only on the leading side, matching every other left-aligned
     eyebrow on the site. */
  .cta__band > .eyebrow--center { justify-content: flex-start; }
  .cta__band > .eyebrow--center::after { display: none; }
}


/* ==========================================================================
   18. PRICING PAGE
   ========================================================================== */
/* Compact, image-led price cards — built from CONFIG.pricing at runtime.
   A thumbnail rather than a full portrait photo keeps six of these dense
   enough to sit two or three across without the page turning into a scroll. */
.price-cards {
  display: grid;
  grid-template-columns: 1fr;
  gap: .9rem;
  margin-top: clamp(1.75rem, 3.5vw, 2.5rem);
}
@media (min-width: 620px) {
  .price-cards { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1060px) {
  .price-cards { grid-template-columns: repeat(3, 1fr); }
}

.price-card {
  position: relative;
  display: flex;
  gap: 1rem;
  padding: .8rem;
  background: var(--c-surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  transition: transform var(--t-med), box-shadow var(--t-med), border-color var(--t-med);
}
.price-card:hover { transform: translateY(-4px); box-shadow: var(--shadow); border-color: var(--c-accent-pale); }
.price-card--featured { border-color: var(--c-accent-pale); box-shadow: var(--shadow-sm); }

.price-card__flag {
  position: absolute;
  top: -.55rem;
  left: .9rem;
  z-index: 2;
  padding: .3rem .7rem;
  font-size: .55rem;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--c-navy);
  background: var(--c-accent-pale);
  border: 1px solid var(--c-accent-light);
  border-radius: var(--radius-pill);
}

.price-card__media {
  position: relative;
  flex: 0 0 104px;
  width: 104px;
  margin: 0;
  border-radius: var(--radius);
  overflow: hidden;
}
.price-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transform: scale(1.02);
  transition: transform 1s var(--ease);
}
.price-card:hover .price-card__media img { transform: scale(1.1); }
.price-card__chip {
  position: absolute;
  left: .35rem;
  bottom: .35rem;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  background: rgba(248, 251, 255, .92);
  border-radius: 50%;
  box-shadow: var(--shadow-sm);
}
.price-card__chip svg { width: 14px; height: 14px; stroke: var(--c-navy); fill: none; stroke-width: 1.3; stroke-linecap: round; stroke-linejoin: round; }

.price-card__body { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.price-card__top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: .45rem .6rem;
}
.price-card__top h3 { font-size: 1.02rem; margin-bottom: 0; }

/* Where the figure used to sit — and the card's one and only link. Every job
   is quoted individually, so the card carries an invitation instead of a
   number, and ::after below stretches that invitation over the whole card so
   a tap anywhere on it opens the quote dialog. */
.price-card__tag {
  flex: none;
  align-self: flex-start;
  padding: .38rem .7rem;
  font-size: .56rem;
  font-weight: 600;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--c-accent);
  background: var(--c-blue-mist);
  border: 1px solid var(--c-accent-pale);
  border-radius: var(--radius-pill);
  transition: color var(--t-fast), background-color var(--t-fast), border-color var(--t-fast);
}
.price-card__tag::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;   /* over the photo and its icon chip; nothing else here is clickable */
}
/* Hovering anywhere on the card fills the pill, so it's clear the whole
   thing is the button and not just the label in the corner. */
.price-card:hover .price-card__tag,
.price-card__tag:focus-visible {
  color: #fff;
  background: var(--c-accent);
  border-color: var(--c-accent);
}
/* The link fills the card, so the focus ring belongs on the card, not on a
   pill in the corner of it. */
.price-card:focus-within {
  outline: 2px solid var(--c-accent);
  outline-offset: 3px;
}
.price-card__tag:focus-visible { outline: none; }

.price-card__desc { margin-top: .35rem; font-size: .84rem; line-height: 1.55; color: var(--c-body); flex: 1; }

/* Detail cards — what a quote covers, paired side by side on desktop.
   These used to be price tables; the figures came out, the scope stayed. */
.rate-tables {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(1.1rem, 2.5vw, 1.6rem);
  margin-top: clamp(2rem, 4vw, 3rem);
  align-items: stretch;
}
@media (min-width: 900px) {
  .rate-tables { grid-template-columns: repeat(2, 1fr); }
}

.rate-card {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--c-surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  padding: clamp(1.4rem, 2.6vw, 1.9rem) clamp(1.3rem, 2.4vw, 1.7rem);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--t-med), border-color var(--t-med);
}
.rate-card:hover { box-shadow: var(--shadow); border-color: var(--c-accent-pale); }

.rate-card__body {
  min-height: 0; /* allow children to shrink inside flex */
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  flex: 1 1 auto;
}
@media (min-width: 900px) {
  .rate-card__body { max-height: clamp(220px, 40vh, 480px); padding-right: .25rem; }
}

/* Thin, subtle scrollbar */
.rate-card__body::-webkit-scrollbar { width: 8px; height: 8px; }
.rate-card__body::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.10); border-radius: 8px; }
.rate-card__body::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.16); }
.rate-card__body { scrollbar-width: thin; scrollbar-color: rgba(0,0,0,0.10) transparent; }

.rate-card__title {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding-bottom: 1.1rem;
  margin-bottom: .3rem;
  font-family: var(--font-body);
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  line-height: 1.5;
  color: var(--c-navy);
  border-bottom: 1px solid var(--line);
}
.rate-card__foot {
  margin-top: 1.1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line-soft);
  font-size: .82rem;
  line-height: 1.6;
  color: var(--c-muted);
}
.rate-card__icon {
  flex: none;
  width: 17px;
  height: 17px;
  stroke: var(--c-accent);
  fill: none;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* Named rows — a title and a line of detail, no third column of numbers */
.spec-list { list-style: none; padding: 0; margin: 0; }
.spec-list li {
  position: relative;
  padding: .9rem 0 .9rem 1.6rem;
}
.spec-list li + li { border-top: 1px solid var(--line-soft); }
.spec-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1.15rem;
  width: 14px;
  height: 14px;
  background: var(--tick) center / contain no-repeat;
}
.spec-list b { display: block; font-weight: 500; color: var(--c-ink); }
.spec-list small { display: block; margin-top: .18rem; font-size: .82rem; line-height: 1.5; color: var(--c-muted); }

/* The extras, as pills — a long single-column list of add-ons reads as a
   price list with the prices sawn off; this reads as a menu. */
.spec-tags {
  list-style: none;
  padding: 0;
  margin: .35rem 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: .45rem;
}
.spec-tags li {
  padding: .55rem .9rem;
  font-size: .8rem;
  line-height: 1.3;
  color: var(--c-ink);
  background: var(--c-blue-mist);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-pill);
  transition: border-color var(--t-fast), color var(--t-fast);
}
.spec-tags li:hover { border-color: var(--c-accent-pale); color: var(--c-accent); }

.note-panel {
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  padding: clamp(1.6rem, 3vw, 2.2rem);
  background: var(--c-blue-mist);
  border-left: 2px solid var(--c-accent);
  border-radius: 0 var(--radius) var(--radius) 0;
}
.note-panel h3 { font-size: 1.25rem; margin-bottom: .6rem; }
.note-panel p { font-size: .94rem; max-width: 70ch; }


/* --------------------------------------------------------------------------
   18b. FREE QUOTE — the invitation panel, and the three-question dialog
   The pricing page prints no figures, so this is what stands where they were.
   -------------------------------------------------------------------------- */
.quote-invite__panel {
  position: relative;
  overflow: hidden;
  padding: clamp(2rem, 5vw, 3.4rem) clamp(1.4rem, 4vw, 3.25rem);
  text-align: center;
  background: var(--c-surface);
  border: 1px solid var(--line-soft);
  border-radius: clamp(var(--radius-lg), 3vw, 32px);
  box-shadow: var(--shadow);
}
/* A wash of accent light behind the top corners, so the panel lifts off the
   page without going the whole way to a dark band — the CTA below already is
   one, and two in a row flattens both. */
.quote-invite__panel::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(90% 120% at 8% -20%, rgba(96, 165, 250, .16) 0%, transparent 60%),
    radial-gradient(70% 110% at 100% 0%, rgba(37, 99, 235, .10) 0%, transparent 58%);
  pointer-events: none;
}
.quote-invite__panel > * { position: relative; }
.quote-invite__panel h2 {
  max-width: 20ch;
  margin: .5rem auto 0;
  font-size: clamp(1.75rem, 1.2rem + 2.1vw, 2.75rem);
}
.quote-invite__panel h2 em { font-style: italic; color: var(--c-accent); }
.quote-invite__lead {
  max-width: 48ch;
  margin: .9rem auto 0;
  font-size: .96rem;
  line-height: 1.65;
  color: var(--c-body);
}

.quote-steps {
  list-style: none;
  counter-reset: none;
  padding: 0;
  margin: clamp(1.8rem, 3.5vw, 2.6rem) 0 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.1rem;
  text-align: left;
}
@media (min-width: 780px) {
  .quote-steps { grid-template-columns: repeat(3, 1fr); gap: 1.4rem; }
}
.quote-step {
  padding: 1.15rem 1.2rem;
  background: var(--c-bg);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  transition: border-color var(--t-med), transform var(--t-med);
}
.quote-step:hover { border-color: var(--c-accent-pale); transform: translateY(-3px); }
.quote-step__num {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  margin-bottom: .7rem;
  font-family: var(--font-display);
  font-size: .95rem;
  color: var(--c-accent);
  background: var(--c-blue-mist);
  border-radius: 50%;
}
.quote-step b { display: block; font-weight: 500; font-size: .94rem; color: var(--c-ink); }
.quote-step small { display: block; margin-top: .3rem; font-size: .82rem; line-height: 1.55; color: var(--c-muted); }

.quote-invite__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: .8rem;
  margin-top: clamp(1.6rem, 3vw, 2.2rem);
}
.quote-invite__note {
  margin-top: 1.3rem;
  font-size: .62rem;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  color: var(--c-muted);
}

/* ---- The dialog itself ---- */
/* Above the header (900), the burger (1000) and the mobile drawer (950) —
   a dialog with the sticky header punched through it is worse than no dialog. */
.quote-modal {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: grid;
  place-items: center;
  padding: clamp(1rem, 4vw, 2rem);
  overflow-y: auto;
}
.quote-modal[hidden] { display: none; }
.quote-modal__backdrop {
  position: fixed;
  inset: 0;
  background: rgba(6, 23, 49, .55);
  backdrop-filter: blur(3px);
  opacity: 0;
  transition: opacity var(--t-fast);
}
.quote-modal.is-open .quote-modal__backdrop { opacity: 1; }

.quote-modal__dialog {
  position: relative;
  width: min(30rem, 100%);
  max-height: 100%;
  overflow-y: auto;
  padding: clamp(1.6rem, 4vw, 2.4rem);
  background: var(--c-surface);
  border: 1px solid var(--line-soft);
  border-radius: clamp(var(--radius), 2.5vw, 26px);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transform: translateY(14px) scale(.98);
  transition: opacity var(--t-fast), transform var(--t-med);
}
.quote-modal.is-open .quote-modal__dialog { opacity: 1; transform: none; }

.quote-modal__dialog h2 {
  margin-top: .45rem;
  font-size: clamp(1.5rem, 1.2rem + 1.1vw, 2rem);
}
.quote-modal__dialog h2 em { font-style: italic; color: var(--c-accent); }
.quote-modal__lead {
  margin-top: .6rem;
  font-size: .89rem;
  line-height: 1.6;
  color: var(--c-body);
}
.quote-modal__form { margin-top: 1.4rem; gap: 1rem; }
.quote-modal__note {
  margin: 0;
  font-size: .78rem;
  line-height: 1.6;
  text-align: center;
  color: var(--c-muted);
}
.quote-modal__note a { color: var(--c-accent); border-bottom: 1px solid var(--c-accent-pale); }
.quote-modal__note a:hover { border-bottom-color: var(--c-accent); }

/* Same two crossed bars as the mobile drawer's close button */
.quote-modal__close {
  position: absolute;
  top: .9rem;
  right: .9rem;
  width: 36px;
  height: 36px;
  padding: 0;
  background: none;
  border: 1px solid var(--line-soft);
  border-radius: 50%;
  cursor: pointer;
  transition: border-color var(--t-fast), transform var(--t-fast);
}
.quote-modal__close:hover { border-color: var(--c-accent-pale); transform: rotate(90deg); }
.quote-modal__close span {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 13px;
  height: 1px;
  background: var(--c-navy);
}
.quote-modal__close span:first-child { transform: translate(-50%, -50%) rotate(45deg); }
.quote-modal__close span:last-child  { transform: translate(-50%, -50%) rotate(-45deg); }


/* ==========================================================================
   19. FAQ — native <details>, works with JavaScript disabled
   ========================================================================== */
.faq { margin-top: clamp(2.5rem, 5vw, 3.5rem); border-bottom: 1px solid var(--line); }
.faq details { border-top: 1px solid var(--line); }
.faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding-block: 1.5rem;
  cursor: pointer;
  list-style: none;
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 1rem + .45vw, 1.4rem);
  color: var(--c-ink);
  transition: color var(--t-fast);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--c-accent); }
.faq summary::after {
  content: "";
  width: 13px; height: 13px;
  flex: none;
  background:
    linear-gradient(var(--c-accent), var(--c-accent)) center / 100% 1px no-repeat,
    linear-gradient(var(--c-accent), var(--c-accent)) center / 1px 100% no-repeat;
  transition: transform var(--t-med);
}
.faq details[open] summary::after { transform: rotate(135deg); }
.faq details > div { padding-bottom: 1.7rem; }
.faq details p { max-width: 74ch; font-size: .97rem; }
.faq details p + p { margin-top: .9rem; }
.faq details a { color: var(--c-accent); border-bottom: 1px solid var(--c-accent-pale); }
.faq details a:hover { border-bottom-color: var(--c-accent); }


/* Sits under a short FAQ list and points at the full page (pricing) */
.faq-more { margin-top: 1.6rem; font-size: .9rem; color: var(--c-muted); }
.faq-more a { color: var(--c-accent); border-bottom: 1px solid var(--c-accent-pale); }
.faq-more a:hover { border-bottom-color: var(--c-accent); }


/* ==========================================================================
   19b. FAQ PAGE — search, topic rail and grouped questions
   Everything here degrades: with JavaScript off the field is inert, the rail
   is a list of anchors, and all the answers are on the page already.
   ========================================================================== */

/* ---- Hero ----
   "Questions, answered properly" is meant to read as one line at every width.
   Two things were breaking that: .page-hero's 18ch cap on the heading, and a
   44rem cap on this block — 704px, where the line needs 733px at full size.

   Both are lifted, and the size is set from measurement rather than guessed:
   the line is 10.42× its own font-size wide, so it fits the 92vw the gutters
   leave whenever the size stays under 8.8vw. 8.2vw holds with room to spare
   and still fits the narrower 100vw − 40px gutter below 500px, all the way
   down to a 274px screen. No `nowrap` on purpose — if this wording is ever
   made longer it should wrap rather than run off the side, and the 8.2 would
   need recomputing anyway. */
.faq-hero h1 {
  max-width: none;
  font-size: clamp(1.5rem, 8.2vw, 4.4rem);
}
/* The block no longer has a width of its own, so the field takes one — full
   container width would be a 1240px search box. */
.faq-search {
  position: relative;
  max-width: 34rem;
  margin-top: clamp(1.6rem, 3.5vw, 2.2rem);
}
.faq-search input {
  width: 100%;
  padding: 1rem 3.4rem 1rem 3.2rem;
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 300;
  color: var(--c-ink);
  background: var(--c-surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.faq-search input::placeholder { color: var(--c-muted); }
.faq-search input:focus {
  outline: none;
  border-color: var(--c-accent);
  box-shadow: 0 0 0 3px rgba(37, 99, 235, .14);
}
/* Safari draws its own clear button inside type="search" — it can't be styled
   and it sits under ours, so it goes. */
.faq-search input::-webkit-search-cancel-button,
.faq-search input::-webkit-search-decoration { -webkit-appearance: none; display: none; }
.faq-search__icon {
  position: absolute;
  left: 1.15rem; top: 50%;
  transform: translateY(-50%);
  width: 19px; height: 19px;
  stroke: var(--c-muted);
  fill: none;
  pointer-events: none;                /* clicks fall through to the field */
  transition: stroke var(--t-fast);
}
.faq-search:focus-within .faq-search__icon { stroke: var(--c-accent); }
.faq-search__clear {
  position: absolute;
  right: .58rem; top: 50%;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  border-radius: 50%;
  color: var(--c-muted);
  transition: background var(--t-fast), color var(--t-fast);
}
.faq-search__clear:hover { background: var(--c-blue-mist); color: var(--c-accent); }
.faq-search__clear svg { width: 15px; height: 15px; stroke: currentColor; fill: none; }

/* Scoped to .faq-hero so it outranks `.page-hero p`, which would otherwise
   give the running count the intro paragraph's size and top margin. */
.faq-hero .faq-count {
  min-height: 1.1em;
  margin-top: .9rem;
  font-size: .64rem;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  color: var(--c-muted);
}

/* ---- Layout ----
   minmax(0, 1fr) rather than a bare single column: an `auto` track is sized to
   its max-content, and the chip strip's max-content is every chip laid end to
   end — about 1570px. The track took that width and pushed the whole page into
   a sideways scroll. Capping the track's minimum at 0 lets the strip do its own
   scrolling, which is what its overflow-x was for. */
.faq-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(1.2rem, 3vw, 2rem);
  align-items: start;
}

/* ---- Topic rail ----
   Phone: a sticky strip of chips that scrolls sideways under the header.
   It can afford an opaque background only because the whole site now sits on
   one ground — anything scrolling beneath it disappears cleanly. */
.faq-side {
  position: sticky;
  /* A pixel of overlap under the scrolled header — a hairline of gap here
     shows a moving sliver of question text above the chips. */
  top: calc(var(--header-h-solid) - 1px);
  z-index: 5;
  padding-block: .75rem;
  background: var(--c-bg);
  border-bottom: 1px solid var(--line-soft);
}
.faq-side__title { display: none; }
.faq-nav {
  display: flex;
  gap: .45rem;
  overflow-x: auto;
  scrollbar-width: none;
  /* Bleeds to both screen edges so the first and last chip can scroll fully
     into view instead of being clipped by the container's gutter. */
  margin-inline: calc(-1 * var(--gutter));
  padding-inline: var(--gutter);
}
.faq-nav::-webkit-scrollbar { display: none; }
.faq-nav a {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .44rem .9rem;
  font-size: .62rem;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--c-navy);
  background: var(--c-surface);
  border: 1px solid var(--c-accent-pale);
  border-radius: var(--radius-pill);
  transition: color var(--t-fast), background var(--t-fast), border-color var(--t-fast);
}
.faq-nav a span { color: var(--c-muted); font-size: .58rem; }
.faq-nav a:hover { border-color: var(--c-accent); color: var(--c-accent); }
.faq-nav a.is-current { background: var(--c-navy); border-color: var(--c-navy); color: var(--c-bg); }
.faq-nav a.is-current span { color: rgba(234, 242, 255, .6); }

.faq-tools { display: none; }
.faq-tools button {
  padding: .38rem .85rem;
  font-size: .58rem;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  color: var(--c-muted);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  transition: color var(--t-fast), border-color var(--t-fast);
}
.faq-tools button:hover { color: var(--c-accent); border-color: var(--c-accent-pale); }

@media (min-width: 900px) {
  .faq-layout {
    grid-template-columns: 236px minmax(0, 1fr);
    gap: clamp(2.5rem, 5vw, 4.5rem);
    /* The section is flush to the hero so the phone-width chip strip can sit
       tight under it; with the strip gone, the columns need air of their own. */
    padding-top: clamp(1.25rem, 2.5vw, 2.25rem);
  }
  .faq-side {
    top: calc(var(--header-h-solid) + 1.6rem);
    padding-block: 0;
    background: none;
    border-bottom: 0;
  }
  .faq-side__title {
    display: block;
    /* 1px of border + the links' own indent, so the label starts on the same
       vertical as the topic names rather than on the rail itself. */
    padding-left: calc(1.05rem + 1px);
    margin-bottom: 1.1rem;
    font-size: .6rem;
    letter-spacing: var(--track-wide);
    text-transform: uppercase;
    color: var(--c-muted);
  }
  .faq-nav {
    flex-direction: column;
    gap: 0;
    overflow: visible;
    margin-inline: 0;
    padding-inline: 0;
    border-left: 1px solid var(--line);
  }
  .faq-nav a {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    /* Pulled a pixel left so its own left border sits exactly on the rail's,
       replacing it rather than doubling it. */
    margin-left: -1px;
    padding: .6rem 0 .6rem 1.05rem;
    font-size: .74rem;
    font-weight: 300;
    letter-spacing: .02em;
    text-transform: none;
    color: var(--c-body);
    background: none;
    border: 0;
    border-left: 1px solid transparent;
    border-radius: 0;
  }
  .faq-nav a span { font-size: .68rem; font-variant-numeric: tabular-nums; }
  .faq-nav a:hover { color: var(--c-accent); border-left-color: var(--c-accent-pale); }
  .faq-nav a.is-current { color: var(--c-navy); background: none; border-left-color: var(--c-accent); }
  .faq-nav a.is-current span { color: var(--c-accent); }
  .faq-tools { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: 1.5rem; padding-left: 1.05rem; }
}

/* ---- Groups ---- */
.faq-group { scroll-margin-top: calc(var(--header-h-solid) + 4.5rem); }
.faq-group + .faq-group { margin-top: clamp(2.75rem, 5vw, 4.25rem); }
.faq-group__head { display: flex; align-items: flex-start; gap: 1rem; }
.faq-group__icon {
  flex: none;
  width: 32px; height: 32px;
  stroke: var(--c-accent);
  stroke-width: 1.1;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.faq-group__head h3 {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(1.45rem, 1.2rem + 1vw, 1.9rem);
  line-height: 1.15;
  color: var(--c-ink);
}
.faq-group__head p { margin-top: .35rem; max-width: 52ch; font-size: .88rem; color: var(--c-muted); }
/* Tighter than the standalone .faq on the pricing page — here a heading sits
   directly above every list. */
.faq-group .faq { margin-top: 1.15rem; }
.faq-group .faq details { scroll-margin-top: calc(var(--header-h-solid) + 4.5rem); }

@media (min-width: 900px) {
  .faq-group,
  .faq-group .faq details { scroll-margin-top: calc(var(--header-h-solid) + 2rem); }
}

/* Search hits inside a question */
.faq mark {
  padding: 0 .12em;
  color: var(--c-navy);
  background: rgba(37, 99, 235, .16);
  border-radius: 3px;
}

/* Arrived here from a shared link — fades out on its own after a moment */
.faq details.is-target {
  background: linear-gradient(90deg, rgba(37, 99, 235, .07), transparent 65%);
}
.faq details.is-target summary { color: var(--c-accent); }

/* ---- No results ---- */
.faq-empty {
  display: grid;
  gap: 1rem;
  justify-items: start;
  padding: clamp(1.8rem, 4vw, 2.75rem);
  background: var(--c-surface);
  border: 1px dashed var(--c-accent-pale);
  border-radius: var(--radius-lg);
}
.faq-empty h3 { font-size: 1.35rem; }
.faq-empty p { max-width: 48ch; font-size: .95rem; }
.faq-empty__actions { display: flex; flex-wrap: wrap; gap: .7rem; margin-top: .3rem; }

/* ---- Still stuck panel ---- */
.faq-help {
  display: grid;
  gap: clamp(1.5rem, 3vw, 2.5rem);
  padding: clamp(1.7rem, 4vw, 2.75rem);
  background: var(--c-surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.faq-help h2 { font-size: clamp(1.6rem, 1.25rem + 1.4vw, 2.2rem); }
.faq-help p { margin-top: .8rem; max-width: 44ch; font-size: .95rem; }
.faq-help__routes { display: grid; gap: .6rem; align-content: center; }
.faq-help__route {
  display: flex;
  align-items: center;
  gap: .9rem;
  padding: .85rem 1.05rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: border-color var(--t-fast), background var(--t-fast), transform var(--t-fast);
}
.faq-help__route:hover { border-color: var(--c-accent); background: var(--c-blue-mist); transform: translateX(3px); }
.faq-help__route svg { flex: none; width: 19px; height: 19px; stroke: var(--c-accent); fill: none; stroke-width: 1.3; }
.faq-help__route b {
  display: block;
  font-weight: 400;
  font-size: .93rem;
  color: var(--c-ink);
  /* Long email addresses would otherwise push the panel wider than the grid */
  overflow-wrap: anywhere;
}
.faq-help__route small { font-size: .71rem; color: var(--c-muted); }

@media (min-width: 760px) {
  .faq-help { grid-template-columns: 1.05fr .95fr; align-items: center; }
}


/* ==========================================================================
   20. ABOUT PAGE
   ========================================================================== */

/* --------------------------------------------------------------------------
   Page rhythm
   The about page is a single continuous story rather than five separate
   slabs, so it runs tighter than --section-y. Scoped to the page so nothing
   else inherits it.
   -------------------------------------------------------------------------- */
.about .section { padding-block: clamp(2.25rem, 4.5vw, 3.5rem); }
.about .section--flush-top { padding-top: 0; }
.about .page-hero--flush { padding-bottom: clamp(1.75rem, 3.5vw, 2.75rem); }
.about .section-head p.lead { margin-top: 1rem; }

/* --------------------------------------------------------------------------
   Hero — picture left, story right, both exactly as tall as each other
   -------------------------------------------------------------------------- */
/* Phone: a float, not a grid. Two real columns on a 375px screen would leave
   the copy about twenty characters to the line for four paragraphs. Floated,
   the picture sits left of the opening lines and the text closes underneath
   it — beside the picture where it matters, full width where it needs to be. */
.about-hero { display: block; }
.about-hero::after { content: ""; display: block; clear: both; }
.about-hero__figure {
  position: relative;
  float: left;
  /* Deliberately under half: the copy keeps a readable measure, and a shorter
     picture reads better on a phone than a tall one that pushes the facts row
     off the bottom of the screen. */
  width: 43%;
  margin: .35rem 1rem .8rem 0;
  aspect-ratio: 4 / 5;
}
/* The leaf: two opposite corners swept away, the other two barely rounded.
   It's the same arc language as the services hero, closed into a shape. */
.about-hero__media {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 42%;
  border-radius: clamp(3.5rem, 11vw, 8rem) var(--radius-lg)
                 clamp(3.5rem, 11vw, 8rem) var(--radius-lg);
  box-shadow: var(--shadow);
}
/* A hairline of the same shape, offset behind it — depth without a drop
   shadow doing all the work. Hidden from the placeholder state, where it
   would just read as a stray outline. */
.about-hero__figure::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: clamp(.7rem, 1.6vw, 1.3rem) clamp(.7rem, 1.6vw, 1.3rem)
         calc(-1 * clamp(.7rem, 1.6vw, 1.3rem)) calc(-1 * clamp(.7rem, 1.6vw, 1.3rem));
  border: 1px solid var(--c-accent-pale);
  border-radius: inherit;
}
.about-hero__figure {
  border-radius: clamp(3.5rem, 11vw, 8rem) var(--radius-lg)
                 clamp(3.5rem, 11vw, 8rem) var(--radius-lg);
  isolation: isolate;
}

/* Plain block on a phone, and that matters: a flex container establishes its
   own block formatting context, which by definition never overlaps a float.
   As a flex box the copy sat entirely beside the picture in a narrow column
   and left the space under it empty, however far past the picture it ran.
   Desktop turns the flex back on, where there is no float to avoid. */
.about-hero__copy { display: block; min-width: 0; }
.about-hero__copy h1 {
  font-size: var(--fs-h1-inner);
  line-height: 1.02;
  color: var(--c-ink);
  max-width: 14ch;
}
.about-hero__copy h1 em { font-style: italic; color: var(--c-accent); }
/* .page-hero p sets a 56ch measure and 1.15rem; the lead wants to sit a step
   above the body copy under it, so both are stated rather than inherited. */
.about-hero .about-hero__lead {
  margin-top: clamp(1rem, 2vw, 1.4rem);
  max-width: 46ch;
  /* Carries the hero on its own now that the display heading is gone, so it
     is sized as a statement rather than as body copy. Held down on a phone,
     where it shares the line with a floated picture. */
  font-size: clamp(1.02rem, .92rem + .55vw, 1.42rem);
  line-height: 1.58;
  color: var(--c-ink);
}
.about-hero .about-hero__body {
  margin-top: .9rem;
  max-width: 50ch;
  font-size: .95rem;
  color: var(--c-muted);
}

/* Goes side by side at 700 rather than 880. Stacked, the 4:5 portrait takes
   the full column width, and on a tablet that made it 880px tall — 357px
   taller than the copy, pushing the whole story below the fold. Side by side
   it takes its height from the text instead, which is the point. */
@media (min-width: 700px) {
  .about-hero {
    display: grid;
    grid-template-columns: .78fr 1.22fr;
    gap: clamp(1.6rem, 3.2vw, 2.5rem);
    /* stretch, not start: the figure has no intrinsic height of its own once
       the ratio is released below, so it takes the row's — which the copy
       sets. That is the whole trick behind the two finishing on one line. */
    align-items: stretch;
  }
  .about-hero::after { display: none; }          /* clearfix is float-only */
  /* Placed by area rather than source order, which the float dictates. */
  .about-hero__figure {
    grid-area: 1 / 1;
    float: none;
    width: auto;
    margin: 0;
    /* Released so the copy's height wins; without this the ratio keeps
       overriding it and the columns end at different points. */
    aspect-ratio: auto;
    /* No floor either. Any min-height here is a second thing competing with
       the copy to size the row, and whenever it wins the picture finishes
       lower than the text. */
    min-height: 0;
  }
  /* Taken out of flow so the picture contributes no height of its own: the
     row is measured from the copy alone, and align-items: stretch then gives
     the figure exactly that height. In flow, the image's own 4:5 height went
     into the measurement and the column it sat in ended up the taller of the
     two — which is the whole thing this avoids. */
  .about-hero__media {
    position: absolute;
    inset: 0;
  }
  .about-hero__copy {
    grid-area: 1 / 2;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
}
@media (min-width: 1024px) {
  .about-hero { grid-template-columns: .82fr 1.18fr; gap: clamp(2.5rem, 5vw, 4.5rem); }
}

/* --------------------------------------------------------------------------
   Story + pull quote — same trick, so the card finishes level with the text
   -------------------------------------------------------------------------- */
/* Floated pull quote, the classic magazine treatment — the card sits in the
   text column at its own natural height and the story closes around and
   beneath it. Two rigid columns here would give four paragraphs a
   twenty-character measure. Desktop switches to a grid (section 24). */
.story { display: block; }
.story::after { content: ""; display: block; clear: both; }
.story__copy { min-width: 0; }
/* The h1 the hero gave up. h2 carries the display size in the type scale;
   h1 has no size of its own, so it is stated here. */
.story__copy h1 { font-size: var(--fs-h2); }
.story .prose { margin-top: 1.5rem; }

.pull {
  position: relative;
  overflow: hidden;
  float: right;
  width: 46%;
  margin: .3rem 0 .9rem 1.1rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(1rem, 3.2vw, 2.75rem);
  background: var(--c-navy);
  background-image:
    radial-gradient(90% 120% at 12% 0%, var(--c-blue), transparent 62%),
    linear-gradient(160deg, var(--c-navy) 0%, var(--c-navy-mid) 100%);
  border-radius: var(--radius-lg);
  box-shadow: inset 0 0 0 1px rgba(96, 165, 250, .22), var(--shadow);
}
/* Oversized quote mark, mostly cropped by the card's own overflow */
.pull::before {
  content: "\201C";
  position: absolute;
  top: -.28em;
  right: .04em;
  font-family: var(--font-display);
  font-size: 12rem;
  line-height: 1;
  color: rgba(96, 165, 250, .13);
  pointer-events: none;
}
.pull blockquote {
  position: relative;
  font-family: var(--font-display);
  /* The card is sized by this text at every width, so the quote can't run
     away with it — held down on a phone, where an oversized quote makes the
     float swallow the column, and allowed to grow into a proper display
     quote once there is a column of its own to sit in. */
  font-size: clamp(1rem, .62rem + 1.5vw, 2.3rem);
  font-style: italic;
  font-weight: 300;
  line-height: 1.4;
  color: var(--c-bg);
  text-wrap: pretty;
}
.pull figcaption {
  margin-top: clamp(.8rem, 2vw, 1.5rem);
  padding-top: clamp(.7rem, 1.8vw, 1.3rem);
  border-top: 1px solid var(--line-dark);
  font-size: .6rem;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  line-height: 1.5;
  color: var(--c-accent-light);
}
/* The oversized quote glyph is a nice flourish on a full-height panel and
   just noise inside a narrow one. */
@media (max-width: 899.98px) { .pull::before { display: none; } }

/* The card is sized by its own quote at every width. Below 900 it is a float
   with the story closing underneath it; from 900 up (section 24) it is a
   grid item aligned to the top of its column. Levelling it with the story
   column has been tried and reverted twice — the four paragraphs run to
   700–900px, the quote fills about a fifth of that, and the card becomes a
   tall empty slab of navy with the attribution stranded near the top. */

/* --------------------------------------------------------------------------
   Values — six cards
   Same material as .card elsewhere on the site (off-white surface, soft
   hairline, 22px radius, lift on hover) so they read as part of the set,
   with a numbered rule instead of an icon.
   -------------------------------------------------------------------------- */
.values {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(.75rem, 1.6vw, 1.15rem);
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
}
.value {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  padding: clamp(1.4rem, 2.8vw, 2rem);
  background: var(--c-surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  transition: transform var(--t-med), box-shadow var(--t-med), border-color var(--t-med);
}
/* Accent hairline that draws across the top — the same gesture .card makes,
   so a hover feels like the same site. */
.value::after {
  content: "";
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 2px;
  background: linear-gradient(90deg, var(--c-accent), var(--c-accent-light));
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .6s var(--ease);
}
/* Faint wash in the far corner, so the surface isn't flat on hover */
.value::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0;
  background: radial-gradient(70% 90% at 100% 0%, rgba(210, 226, 250, .55), transparent 62%);
  opacity: 0;
  transition: opacity var(--t-med);
}
.value:hover { transform: translateY(-5px); border-color: transparent; box-shadow: var(--shadow); }
.value:hover::after  { transform: scaleX(1); }
.value:hover::before { opacity: 1; }

/* "01 ————————" — the numeral, then a rule running out to the card's edge */
.value__num {
  display: flex;
  align-items: center;
  gap: .75rem;
  margin-bottom: .95rem;
  font-family: var(--font-display);
  font-size: 1.55rem;
  font-weight: 300;
  line-height: 1;
  letter-spacing: 0;
  text-transform: none;
  color: var(--c-accent);
  font-variant-numeric: tabular-nums;
}
.value__num::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--c-accent-pale), transparent);
  transition: background var(--t-med);
}
.value:hover .value__num::after {
  background: linear-gradient(90deg, var(--c-accent), transparent);
}

.value h3 { font-size: 1.2rem; margin-bottom: .55rem; }
.value p { font-size: .92rem; line-height: 1.62; max-width: 46ch; color: var(--c-muted); }

/* Timeline — no longer used on any page; the about page tells the story in
   prose instead. Kept so the component is there if it's ever wanted back. */
.timeline { margin-top: clamp(2.5rem, 5vw, 4rem); }
.milestone {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: clamp(1.2rem, 3vw, 2.5rem);
  padding-block: 1.8rem;
  border-top: 1px solid var(--line);
}
.milestone:last-child { border-bottom: 1px solid var(--line); }
.milestone__year {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 1.2rem + 1vw, 2.1rem);
  font-weight: 300;
  line-height: 1.2;
  color: var(--c-accent);
  font-variant-numeric: tabular-nums;
}
.milestone h3 {
  font-family: var(--font-body);
  font-size: 1.1rem;
  font-weight: 400;
  color: var(--c-ink);
  margin-bottom: .4rem;
}
.milestone p { font-size: .95rem; max-width: 60ch; }


/* ==========================================================================
   21. CONTACT PAGE
   ========================================================================== */
/* --------------------------------------------------------------------------
   Page rhythm — the contact page is one task, not five chapters, so it runs
   tighter than --section-y. Scoped so nothing else inherits it.
   -------------------------------------------------------------------------- */
.contact-page .section { padding-block: clamp(2.25rem, 4.5vw, 3.5rem); }
.contact-page .section--flush-top { padding-top: 0; }
/* .cta isn't a .section, so it kept the site-wide padding and stacked its own
   56px on top of the suburb list's — 112px of nothing between the two. */
.contact-page .cta { padding-top: 0; }
/* This hero holds only the breadcrumb, no heading, so its usual bottom
   padding and the breadcrumb's own margin just leave dead space beneath it. */
.contact-page .page-hero--flush { padding-bottom: 0; }
.contact-page .breadcrumb { margin-bottom: 0; }
/* Hero's too short here for the hairline bottom border to read as a section
   divider — it just sits right under the breadcrumb like a stray underline. */
.contact-page .page-hero { border-bottom: none; }
/* Hero's too short here for the corner-circle flourish to read as a circle —
   it just clips into a stray diagonal line, so this page skips it. */
.contact-page .page-hero::after { display: none; }
.contact-page .section-head p { margin-top: .9rem; }
.contact__intro { max-width: 46rem; }

.form { display: grid; gap: 1.25rem; }
.form__row { display: grid; gap: 1.25rem; }
.field { display: grid; gap: .55rem; }
.field label {
  font-size: .64rem;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  color: var(--c-muted);
}
.field label .req { color: var(--c-accent); }
.field input,
.field select,
.field textarea {
  width: 100%;
  padding: .95rem 1.1rem;
  font-family: var(--font-body);
  font-size: .96rem;
  font-weight: 300;
  color: var(--c-ink);
  background: var(--c-surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.field textarea { min-height: 150px; resize: vertical; line-height: 1.6; }
.field select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 2.6rem;
  background-image: var(--chevron);
  background-repeat: no-repeat;
  background-position: right 1rem center;
  background-size: 16px;
  cursor: pointer;
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--c-accent);
  box-shadow: 0 0 0 3px rgba(37, 99, 235, .14);
}
.field input::placeholder, .field textarea::placeholder { color: #A3AFC0; }
/* Invalid state is only shown after a submit attempt, never while typing */
.form.is-validated .field input:invalid,
.form.is-validated .field select:invalid,
.form.is-validated .field textarea:invalid { border-color: #D14343; }

.field--check {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: .8rem;
  align-items: start;
}
.field--check input { width: 17px; height: 17px; margin-top: .3rem; accent-color: var(--c-navy); }
.field--check label {
  font-size: .86rem;
  letter-spacing: 0;
  text-transform: none;
  color: var(--c-body);
  line-height: 1.6;
}

/* The spam trap. Off-screen rather than display:none — some bots skip hidden
   fields, but almost all of them fill anything they can find in the DOM. */
.form input[name="_honey"] {
  position: absolute;
  left: -9999px;
  width: 1px; height: 1px;
  opacity: 0;
  pointer-events: none;
}

.form__submit {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .8rem 1.4rem;
  margin-top: .4rem;
}
.form__submit .btn { flex: none; }
.form__note { flex: 1 1 16rem; margin: 0; font-size: .82rem; line-height: 1.6; color: var(--c-muted); }
.form__note a { color: var(--c-accent); border-bottom: 1px solid var(--c-accent-pale); }
.form__note a:hover { border-bottom-color: var(--c-accent); }

.form__status {
  display: none;
  gap: .9rem;
  padding: 1.1rem 1.3rem;
  font-size: .92rem;
  line-height: 1.6;
  border-radius: var(--radius);
}
.form__status.is-shown { display: flex; }
.form__status--ok {
  color: var(--c-navy);
  background: var(--c-blue-mist);
  border: 1px solid var(--c-accent-pale);
}
.form__status--error {
  color: #7A2E22;
  background: #FBEAE7;
  border: 1px solid #F0C4BC;
}
.form__status--error .form__status-icon { stroke: #C4432F; }
.form__status b { display: block; font-weight: 400; margin-bottom: .15rem; }
.form__status a { color: var(--c-accent); border-bottom: 1px solid var(--c-accent-pale); }
.form__status-icon {
  flex: none;
  width: 22px; height: 22px;
  margin-top: .1rem;
  stroke: var(--c-accent);
  stroke-width: 1.3;
  fill: none;
}
/* Once it's sent, the fields are just noise — the confirmation is the message */
.form.is-sent > *:not(.form__status) { display: none; }

/* ---- Arriving from the free-quote dialog on the pricing page ----
   Their three answers are already in the fields below; saying so is kinder
   than letting them wonder who typed it. Hidden until js/main.js finds a
   hand-off waiting in sessionStorage. */
.form__prefill {
  display: flex;
  gap: .8rem;
  margin-bottom: 1.5rem;
  padding: .95rem 1.1rem;
  font-size: .85rem;
  line-height: 1.6;
  color: var(--c-body);
  background: var(--c-blue-mist);
  border: 1px solid var(--c-accent-pale);
  border-radius: var(--radius);
}
.form__prefill[hidden] { display: none; }
.form__prefill b { display: block; font-weight: 500; color: var(--c-ink); }
.form__prefill-icon {
  flex: none;
  width: 20px;
  height: 20px;
  margin-top: .1rem;
  stroke: var(--c-accent);
  fill: none;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* A field that filled itself in, marked quietly so it's obvious which ones
   came across and which still want an answer. */
.field input.is-prefilled,
.field select.is-prefilled { border-color: var(--c-accent-pale); background-color: var(--c-blue-mist); }

/* --------------------------------------------------------------------------
   Contact split — a deep-navy info panel beside the quote form, rather than
   a full-width "reach us" row stacked above it. Side by side, the two halves
   finish on roughly the same line and the page needs one scroll less to
   reach the footer than four cards plus a separate form section did.
   -------------------------------------------------------------------------- */
.contact-grid {
  display: grid;
  gap: clamp(1.4rem, 3vw, 1.8rem);
  align-items: stretch;
}
@media (min-width: 900px) {
  .contact-grid { grid-template-columns: minmax(0, .86fr) minmax(0, 1.14fr); gap: clamp(1.8rem, 3.4vw, 3rem); }
}

.contact-panel {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  padding: clamp(1.8rem, 3.4vw, 2.5rem);
  color: var(--c-on-dark);
  background:
    radial-gradient(115% 140% at 8% -8%, #1D4ED8 0%, transparent 55%),
    radial-gradient(85% 110% at 100% 108%, rgba(96, 165, 250, .38) 0%, transparent 58%),
    linear-gradient(158deg, #061731 0%, #0B2545 46%, #14396B 100%);
  border-radius: var(--radius-lg);
  box-shadow: inset 0 0 0 1px rgba(96, 165, 250, .22), var(--shadow-lg);
}
.contact-panel::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--grain);
  opacity: .06;
  pointer-events: none;
}
.contact-panel > * { position: relative; z-index: 1; }
.contact-panel h2 { font-size: var(--fs-h3); color: var(--c-on-dark); }
.contact-panel__lead {
  margin-top: .6rem;
  max-width: 30ch;
  font-size: .92rem;
  line-height: 1.55;
  color: var(--c-on-dark-dim);
}

.contact-panel__actions {
  display: grid;
  gap: .5rem;
  margin-top: clamp(1.1rem, 2.2vw, 1.5rem);
}
.contact-btn {
  display: flex;
  align-items: center;
  gap: .7rem;
  padding: .55rem .75rem;
  background: rgba(255, 255, 255, .05);
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: var(--radius);
  transition: background var(--t-fast), border-color var(--t-fast), transform var(--t-fast);
}
.contact-btn:hover { background: rgba(255, 255, 255, .1); border-color: rgba(255, 255, 255, .3); transform: translateX(4px); }
.contact-btn__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  background: rgba(255, 255, 255, .1);
  border-radius: 50%;
  transition: background var(--t-fast);
}
.contact-btn__icon svg { width: 15px; height: 15px; stroke: var(--c-on-dark); fill: none; stroke-width: 1.35; }
.contact-btn--wa .contact-btn__icon { background: rgba(37, 211, 102, .22); }
.contact-btn--wa .contact-btn__icon svg { stroke: #3EE68A; }
.contact-btn:hover .contact-btn__icon { background: rgba(255, 255, 255, .18); }
.contact-btn--wa:hover .contact-btn__icon { background: rgba(37, 211, 102, .32); }
.contact-btn__text { flex: 1 1 auto; display: flex; flex-direction: column; gap: .08rem; min-width: 0; }
.contact-btn__label {
  font-size: .56rem;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  color: var(--c-on-dark-dim);
}
.contact-btn__text b {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1rem;
  color: var(--c-on-dark);
  overflow-wrap: anywhere;
}
.contact-btn__go {
  flex: none;
  width: 15px; height: 15px;
  stroke: var(--c-on-dark-dim);
  fill: none;
  stroke-width: 1.3;
  opacity: .65;
  transition: transform var(--t-fast), opacity var(--t-fast);
}
.contact-btn:hover .contact-btn__go { transform: translateX(3px); opacity: 1; }

.contact-panel__email {
  display: flex;
  align-items: center;
  gap: .7rem;
  margin-top: 1.5rem;
  padding-top: 1.4rem;
  border-top: 1px solid rgba(255, 255, 255, .14);
  font-size: .88rem;
  color: var(--c-on-dark-dim);
  overflow-wrap: anywhere;
  transition: color var(--t-fast);
}
.contact-panel__email svg { flex: none; width: 17px; height: 17px; stroke: var(--c-accent-light); fill: none; stroke-width: 1.3; }
.contact-panel__email:hover { color: var(--c-on-dark); }

/* The offices. Deliberately not more .contact-btns — those are one-line
   actions and each of these is a place, three lines deep. They sit below the
   buttons as a quiet plate, hairline-separated from them, and each detail
   still dials, mails or opens the map on a tap.

   The panel is navy in every finish, so the shared .office rules above are
   re-pointed at the on-dark colours here rather than re-written. */
.contact-panel__offices {
  margin-top: 1.5rem;
  padding-top: 1.4rem;
  border-top: 1px solid rgba(255, 255, 255, .14);
}
.contact-panel__offices > .contact-btn__label { margin-bottom: .9rem; }
.offices--panel { gap: 1.5rem; }
.offices--panel .office { border-left-color: rgba(255, 255, 255, .16); }
.offices--panel .office__label { color: var(--c-accent-light); }
.offices--panel .office__name { color: var(--c-on-dark); overflow-wrap: anywhere; }
.offices--panel .office__row { color: var(--c-on-dark-dim); }
.offices--panel .office__row svg { stroke: var(--c-accent-light); }
.offices--panel .office__row:hover { color: var(--c-on-dark); }

/* `margin-top: auto` rather than a fixed gap, because the panel is a flex
   column stretched to the height of the form beside it. With a short list of
   buttons — social switched off, say — a fixed gap leaves the facts floating
   mid-card above a pool of empty navy; auto sends them to the foot where they
   read as a footer. On a phone the panel is content-height, auto resolves to
   zero, and the padding below supplies the spacing on its own. */
.contact-panel__facts {
  display: flex;
  flex-wrap: wrap;
  gap: 1.3rem 2rem;
  margin-top: auto;
  padding-top: 2.4rem;
}
.contact-panel__facts dt {
  font-size: .56rem;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  color: rgba(234, 242, 255, .5);
}
.contact-panel__facts dd { margin: .25rem 0 0; font-size: .86rem; color: var(--c-on-dark-dim); }
/* Five region names are a line, not a fact — this one takes the full width of
   the row so the short facts beside it aren't squeezed into a corner. The
   panel is navy in every finish, so its accent is the light one. */
.contact-panel__facts-wide { flex: 1 1 100%; }
.contact-panel__facts .areas__more { color: var(--c-accent-light); }

/* --------------------------------------------------------------------------
   Quote form — lifted onto its own card so it reads as a companion to the
   panel rather than bare fields floating in the column.
   -------------------------------------------------------------------------- */
.contact-form-card {
  padding: clamp(1.8rem, 3.4vw, 2.5rem);
  padding-top: clamp(1.1rem, 2vw, 1.5rem);
  background: var(--c-surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.contact-form-card .section-head p { margin-top: .6rem; font-size: .88rem; }
/* This card's section-head is eyebrow-only (no heading beneath it), so the
   shared .section-head .eyebrow bottom margin was just dead space here. */
.contact-form-card .section-head .eyebrow { margin-bottom: 0; }
.contact-form-card .form { margin-top: clamp(1.4rem, 2.6vw, 1.8rem); gap: 1.1rem; }

/* The careers page's application card is the one exception: it carries a
   heading and a line of copy under the eyebrow, not an eyebrow alone. Both
   shared rules above assume no heading — the eyebrow needs its margin back,
   and the h2 needs to come off the section-heading scale, which tops out at
   3.6rem and would dwarf the card it sits in. */
.careers .contact-form-card .section-head { max-width: none; }
.careers .contact-form-card .section-head .eyebrow { margin-bottom: .85rem; }
.careers .contact-form-card .section-head h2 { font-size: clamp(1.45rem, 1.15rem + .95vw, 2rem); }

.areas { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1.4rem; }
.areas li {
  padding: .42rem 1rem;
  font-size: .78rem;
  color: var(--c-navy);
  background: var(--c-surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-pill);
}


/* ==========================================================================
   22. FOOTER
   ========================================================================== */
.footer {
  /* Half a shade under the page so the foot grounds without becoming a slab */
  background: var(--c-tint-deep);
  border-top: 1px solid var(--line);
  padding-block: clamp(3.5rem, 7vw, 5.5rem) 2rem;
}
.footer__grid { display: grid; grid-template-columns: 1fr; gap: clamp(2.5rem, 5vw, 3.5rem); }
.footer__col h4 {
  font-family: var(--font-body);
  font-size: .66rem;
  font-weight: 400;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--c-accent);
  margin-bottom: 1.5rem;
}
.footer__about .logo { color: var(--c-navy); font-size: clamp(1.4rem, 1.1rem + 1vw, 1.7rem); margin-bottom: 1.2rem; }
/* Bigger than the header cut — the footer column has the width to spare, and
   this is where the "cleaning solutions" line gets to be legible. */
.footer__about .logo__mark { height: clamp(60px, 3.4vw + 44px, 78px); }
.footer__about p { font-size: .94rem; max-width: 38ch; }
.footer__abn {
  margin-top: 1.4rem;
  font-size: .7rem;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  color: var(--c-muted);
}
.footer__list { display: grid; gap: .75rem; }
/* .areas is in this list because the line it draws is not only spans: the
   "& Surrounds" at the end is an <em> and the dividers are <i>, and without
   this both inherit the body colour, which on the graphite footer is a dark
   grey on a dark grey — present, correctly placed and invisible. */
.footer__list a, .footer__contact a, .footer__contact span, .footer__contact .areas {
  font-size: .94rem;
  color: var(--c-body);
  transition: color var(--t-fast);
}
.footer__list a:hover { color: var(--c-accent); }
.footer__contact { display: grid; gap: 1.1rem; }
.footer__contact div { display: grid; gap: .15rem; }
.footer__contact dt {
  font-size: .6rem;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  color: var(--c-muted);
}
/* A grid rather than plain flow, so a cell holding more than one line stacks
   them. `justify-items: start` keeps each link's hover target the width of its
   own text rather than the whole column. */
.footer__contact dd { display: grid; gap: .35rem; justify-items: start; margin: 0; }
/* The offices sit above this list; the gap belongs to whichever comes first. */
.footer .offices { margin-bottom: 1.9rem; }

/* WhatsApp, with its own mark beside the words — the one row in this column
   that is a conversation rather than a detail, and the mark is what says so
   at a glance. Inline-flex so the icon rides the text's own baseline. */
.chat-link {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
}
.chat-link svg {
  width: 15px; height: 15px;
  flex: none;
  fill: none;
  stroke: var(--c-accent);
  stroke-width: 1.35;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: stroke var(--t-fast);
}
.footer__contact dd a:hover { color: var(--c-accent); }

/* --------------------------------------------------------------------------
   THE SERVICE AREAS
   The regions written out, filled by js/main.js into any [data-service-areas].
   Set as a line of names divided by a hairline diamond rather than commas: a
   comma list of five places reads as a sentence someone forgot to finish,
   where separated names read as a coverage map. "& Surrounds" closes it in the
   display italic, so it is plainly the summary and not a sixth place.
   -------------------------------------------------------------------------- */
.areas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .1rem .55rem;
  line-height: 1.55;
}
/* A rotated hairline square — the same mark the hero's promise line is divided
   by, at the size this type asks for. */
.areas__sep {
  width: 3px; height: 3px;
  border: 1px solid currentColor;
  transform: rotate(45deg);
  opacity: .4;
}
.areas__more {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.08em;
  color: var(--c-accent);
}

/* --------------------------------------------------------------------------
   THE OFFICES
   Written by js/main.js into any [data-offices] — the footer of every page and
   the panel on the contact page. Both grounds are dark in graphite and light
   in the other two finishes, so everything here is set in theme variables and
   the whole block follows the finish without a second copy of itself.

   Each office is a plate hung off a hairline spine: the country small and
   spaced at the top, the name on the door in the display serif under it, then
   one row per way of reaching them. The rows are what carry the detail, and
   each is a link — tap the number to dial it, the address to open the map.
   -------------------------------------------------------------------------- */
.offices { display: grid; gap: 1.35rem; }

.office {
  display: grid;
  gap: .12rem;
  padding-left: 1rem;
  /* The spine. It is what makes two addresses read as two places rather than
     one long column of small print — and it costs no vertical room, which the
     footer's fourth column has none of to spare. */
  border-left: 1px solid var(--line);
}
.office__label {
  margin-bottom: .3rem;
  font-size: .58rem;
  font-weight: 500;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--c-accent);
}
.office__name {
  margin-bottom: .45rem;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1rem;
  line-height: 1.3;
  color: var(--c-navy);
}

/* Icon, then detail. The icon column is a fixed width so the number, the
   address and the street all start on the same vertical line however long
   each one is — the thing that makes a stack of details look set rather
   than typed. */
.office__row {
  display: grid;
  grid-template-columns: 15px 1fr;
  align-items: start;
  gap: .55rem;
  padding-block: .18rem;
  font-size: .9rem;
  line-height: 1.5;
  color: var(--c-body);
  transition: color var(--t-fast);
  overflow-wrap: anywhere;
}
.office__row svg {
  width: 15px; height: 15px;
  margin-top: .28em;          /* sits on the first line's baseline, not its top */
  fill: none;
  stroke: var(--c-accent);
  stroke-width: 1.35;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: .85;
  transition: opacity var(--t-fast);
}
.office__row:hover { color: var(--c-accent); }
.office__row:hover svg { opacity: 1; }
/* One line of the address to a line, as it would be on an envelope. */
.addr__line { display: block; }
.socials { display: flex; gap: .6rem; margin-top: 1.7rem; }
.socials a {
  display: grid;
  place-items: center;
  width: 38px; height: 38px;
  border: 1px solid var(--line);
  border-radius: 50%;
  transition: background var(--t-fast), border-color var(--t-fast), transform var(--t-fast);
}
.socials svg {
  width: 16px; height: 16px;
  fill: none;
  stroke: var(--c-navy);
  stroke-width: 1.2;
  transition: stroke var(--t-fast);
}
.socials a:hover { background: var(--c-navy); border-color: var(--c-navy); transform: translateY(-2px); }
.socials a:hover svg { stroke: var(--c-accent-light); }

.footer__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: clamp(3rem, 6vw, 4.5rem);
  padding-top: 1.8rem;
  border-top: 1px solid var(--line-soft);
  font-size: .74rem;
  letter-spacing: .04em;
  color: var(--c-muted);
}
.footer__bar a:hover { color: var(--c-accent); }


/* ==========================================================================
   23. SCROLL REVEAL
   ========================================================================== */
/* Animated with the independent `translate` and `scale` properties rather
   than `transform`, and that is not a style preference — it is a bug fix.

   Nearly every card on this site is a .reveal AND has a `:hover` that lifts it
   with `transform: translateY(...)`. Both selectors weigh exactly 0,2,0
   (`.reveal.is-in` and, say, `.card:hover`), so the cascade fell to source
   order — and this block sits at the bottom of the file. `transform: none`
   therefore beat every one of them, and the hover lift on .card, .svc-card,
   .quote, .price-card and .value silently did nothing once the element had
   revealed. It only ever worked in the split second before it scrolled in.

   Moving the reveal onto `translate`/`scale` removes the collision entirely:
   different properties, so nothing overrides anything, and the browser
   composes the two — a revealed card can sit at translate 0 and still be
   lifted -6px by its hover. */
.reveal {
  opacity: 0;
  translate: 0 26px;
  transition:
    opacity 1s var(--ease) var(--d, 0s),
    translate 1s var(--ease) var(--d, 0s),
    scale 1s var(--ease) var(--d, 0s);
  will-change: opacity, translate;
}
.reveal--left  { translate: -28px 0; }
.reveal--right { translate: 28px 0; }
.reveal--scale { translate: 0; scale: .97; }
.reveal.is-in  { opacity: 1; translate: 0; scale: 1; }


/* ==========================================================================
   24. RESPONSIVE
   Mobile-first: everything above is the small-screen layout.
   ========================================================================== */
/* Phones lose the six category cards — three screens of cards between the
   hero and the first service is a lot of thumb travel, and the "All Services"
   menu in the breadcrumb row reaches the same six places in one tap. */
@media (max-width: 599.98px) {
  .svc-cards-wrap { display: none; }
  .price-card { padding: .65rem; gap: .75rem; }
  .price-card__media { flex-basis: 82px; width: 82px; }
  .price-card__top h3 { font-size: .94rem; }

  /* --- Footer, two-up on phones -------------------------------------------
     One column stacked four blocks into about a screen and a half of scrolling
     to reach the copyright line. Two columns halves that. The about block and
     the contact list still run the full width — a 38ch paragraph and a
     33-character email address have nowhere to go in half a phone screen —
     so it's the two link lists that sit side by side, and the contact facts
     pair up underneath them instead. */
  .footer__grid { grid-template-columns: repeat(2, 1fr); gap: 2.25rem 1.4rem; }
  .footer__about,
  .footer__grid > .footer__col:last-child { grid-column: 1 / -1; }
  .footer__contact { grid-template-columns: repeat(2, 1fr); gap: 1.1rem 1.4rem; }
  /* The offices stay one under the other even where the rows beside them pair
     up: a 33-character email address at half a phone screen is a column of
     broken words. */
  .footer .offices { margin-bottom: 1.5rem; }
  /* The email is one unbroken 33-character word; without this it runs out
     past the edge of its column and takes the page's width with it. */
  .footer__contact dd { overflow-wrap: anywhere; }
  .footer__col h4 { margin-bottom: 1.1rem; }
  .footer__list { gap: .7rem; }
}

/* Service detail on phones: the picture holds a full half of the screen, so
   the copy beside it runs about ten words to the line less — the type steps
   down a notch to compensate. Only below the 640px grid switch. */
@media (max-width: 639.98px) {
  .svc__title h2 { font-size: clamp(1.55rem, 1.25rem + 1.5vw, 1.9rem); }
  .svc__title .card__icon { width: 38px; height: 38px; }
  .svc__body > p.lead { font-size: .97rem; line-height: 1.52; }
  .svc__body > p:not(.lead) { font-size: .88rem; line-height: 1.56; }
  .svc__body > p + p { margin-top: .7rem; }
}

@media (min-width: 600px) {
  .trust__grid    { grid-template-columns: repeat(2, 1fr); }
  /* .services__grid deliberately stays at one column here. Its cards go
     sideways at this same width, and a sideways card in a half-width track
     has nowhere to put the copy. It takes its second column at 900px. */
  .footer__grid   { grid-template-columns: repeat(2, 1fr); }
  .hero__scroll   { display: flex; }
  .incl--2col     { grid-template-columns: repeat(2, 1fr); column-gap: 2rem; }
  .form__row--2   { grid-template-columns: repeat(2, 1fr); }
  .form__row--3   { grid-template-columns: repeat(2, 1fr); }
  .values         { grid-template-columns: repeat(2, 1fr); }
  .corevals__grid { grid-template-columns: repeat(2, 1fr); }
  .svc-cards      { grid-template-columns: repeat(2, 1fr); }
  .feature-strip  { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 860px) {
  .form__row--3   { grid-template-columns: repeat(3, 1fr); }
}

/* Service detail switches to two columns well before the nav does. Past about
   640px the floated layout starts leaving a wide band of white beside the
   picture — the copy runs out long before the image does — so the grid takes
   over here rather than at 900px. */
@media (min-width: 640px) {
  /* The float gives way to two real columns: picture in the narrow track,
     heading / copy / inclusions stacked in the wide one.

     The picture spans all three text rows, and it is normally taller than the
     copy. The last row is `1fr` so that surplus height lands there — below the
     list, out of sight. With three `auto` rows the browser shares the surplus
     between them instead, opening phantom gaps of up to 33px under the heading
     and under the copy. Measured, not guessed. */
  .svc__grid {
    display: grid;
    grid-template-columns: .7fr 1.3fr;
    grid-template-rows: auto auto 1fr;
    column-gap: clamp(2rem, 4vw, 3.8rem);
    row-gap: 0;
    align-items: start;
  }
  .svc__grid::after { display: none; }   /* clearfix would become a grid item */

  .svc__figure   { float: none; width: auto; margin: 0; grid-column: 1; grid-row: 1 / -1; }
  .svc__title    { grid-column: 2; grid-row: 1; }
  .svc__body     { grid-column: 2; grid-row: 2; hyphens: manual; }
  .svc__body > p { max-width: 64ch; }   /* comfortable measure in the wide track */
  .svc__includes { grid-column: 2; grid-row: 3; }

  /* Flip every second block so the images alternate left / right.
     The track widths must be swapped as well as the column assignment —
     column 2 keeps its own width, so without this the right-hand images
     render in the WIDE track and balloon. */
  .svc:nth-of-type(even) .svc__grid   { grid-template-columns: 1.3fr .7fr; }
  .svc:nth-of-type(even) .svc__figure { float: none; margin: 0; grid-column: 2; }
  .svc:nth-of-type(even) .svc__title,
  .svc:nth-of-type(even) .svc__body,
  .svc:nth-of-type(even) .svc__includes { grid-column: 1; }
}

/* In the two-column layout below 900px the copy track is too narrow to split
   the inclusion list in two — it drops to about 20 characters a line. */
@media (min-width: 640px) and (max-width: 899.98px) {
  .svc .incl--2col { grid-template-columns: 1fr; }
}

@media (min-width: 900px) {
  :root { --header-h: 58px; }

  .nav            { display: block; }
  .burger         { display: none; }
  .header__cta    { display: inline-flex; }

  /* From here the bar carries the wordmark, six nav links and the button all
     on one line, so the wordmark stops growing with the viewport and settles
     smaller than it runs on mobile. A long business name then fits whole. */
  .logo           { font-size: clamp(1.02rem, .58rem + .82vw, 1.3rem); }
  .nav__list      { gap: clamp(1.1rem, 1.9vw, 2.1rem); }

  .trust__grid    { grid-template-columns: repeat(4, 1fr); }
  /* .services__grid takes its second column at 1080px, with the media-card
     rules in section 11 — see the note there. */
  .services__top  { grid-template-columns: 1.1fr .9fr; }
  .why__layout    { grid-template-columns: 1fr 1.05fr; align-items: stretch; }
  /* Side by side, the photo takes its height from the copy column rather than
     from a fixed 4:5 ratio, so the two columns end exactly level however the
     text rewraps. Absolute positioning is what lets the stretched figure keep
     the row height instead of being sized by the image's own aspect ratio. */
  .why__figure    { min-height: 32rem; }
  .why__media     { position: absolute; inset: 0; height: 100%; aspect-ratio: auto; }
  .how__grid      { grid-template-columns: repeat(3, 1fr); }
  .step:not(:last-child)::after { display: block; }

  .quotes {
    grid-auto-columns: 1fr;
    grid-template-columns: repeat(3, 1fr);
    grid-auto-flow: row;
    overflow: visible;
    margin-inline: 0;
    padding-inline: 0;
  }
  .dots { display: none; }

  .stats__grid    { grid-template-columns: repeat(4, 1fr); }
  .footer__grid   { grid-template-columns: 1.5fr 1fr 1fr 1.2fr; }

  .stat + .stat::before { display: block; }

  /* --- Services page hero -------------------------------------------------
     The image is lifted out of the grid and pinned to the top-right of the
     section. top:var(--header-h) makes it meet the navbar with no gap;
     right:0 bleeds it off the side of the screen. --------------------------- */
  /* Taller section gives the image room; the copy centres inside it so the
     text block stays compact while the picture grows. */
  .svc-hero {
    display: flex;
    align-items: center;
    min-height: min(80vh, 740px);
    padding-bottom: clamp(5rem, 9vw, 7.5rem);
    overflow: visible;          /* lets the image run down behind the cards */
  }
  .svc-hero__grid  { width: 100%; grid-template-columns: minmax(0, 1fr); }
  /* Copy and image are sized so they never collide, at any width from 900px up */
  .svc-hero__copy  { max-width: min(44%, 560px); }

  .svc-hero__figure {
    position: absolute;
    /* Runs to the very top of the page, behind the header. Safe because the
       circular arc removes the top-left, so only the solid Book Now button
       ever sits over the picture — the nav links stay on the cream ground. */
    top: 0;
    right: 0;
    /* Mobile pins all four sides; release the left one or the picture stays
       full width and sits under the copy. */
    left: auto;
    /* Extends past the section so it sits behind the overlapping card row */
    bottom: calc(-1 * clamp(2.5rem, 5vw, 4.5rem));
    width: min(52%, 940px);
    margin: 0;
    border-radius: 0;
    overflow: visible;
  }
  .svc-hero__media {
    height: 100%;
    aspect-ratio: auto;         /* fill the pinned box instead */
    object-position: center;
    /* A single length (not a percentage) keeps the arc a true circle rather
       than an ellipse — percentages resolve against width and height
       separately, which is what made the old 50% sweep look oval. */
    border-radius: clamp(18rem, 38vw, 40rem) 0 0 0;
    box-shadow: none;
    /* The source is a very high-key interior and washes out at this size.
       Only on desktop — on mobile the copy sits ON the picture, where
       darkening would work against the navy text. */
    filter: brightness(.9) contrast(1.05) saturate(1.12);
  }
  /* The breadcrumb row spans the full container here, so the pill lands at
     the far right — over the picture, which its solid ground handles fine.
     The menu keeps hanging from the pill's right edge (the mobile default),
     opening inward rather than off the side of the page. */


  /* Pull the card row up so it straddles the hero's bottom edge */
  .svc-cards-wrap  { margin-top: calc(-1 * clamp(4rem, 7vw, 6rem)); }
  .svc-cards       { grid-template-columns: repeat(3, 1fr); }
  .feature-strip   { grid-template-columns: repeat(4, 1fr); }
  .feature + .feature::before { display: block; }

  .tiers          { grid-template-columns: repeat(3, 1fr); align-items: stretch; }
  /* Lift the recommended tier clear of its neighbours */
  .tier--featured { transform: translateY(-14px); }
  .tier--featured:hover { transform: translateY(-19px); }

  /* Out of float mode and into two real columns. Placement is by grid area
     because the source order is quote-then-story, which the phone float
     requires; here the story has to come back to the left. */
  /* Out of float mode and into two real columns. Placement is by grid area
     because the source order is quote-then-story, which the phone float
     requires; here the story has to come back to the left. */
  .story          { display: grid; grid-template-columns: 1.15fr .85fr;
                    gap: clamp(1.75rem, 4vw, 3.5rem); align-items: start; }
  .story::after   { display: none; }
  .story__copy    { grid-area: 1 / 1; }
  /* align-self: start, and no height: 100%. The card used to stretch to the
     full height of the story beside it, which on four paragraphs meant a
     700px panel holding a 175px quote — mostly empty navy with the
     attribution stranded halfway up. It now sits at the top of its column at
     the height its own quote needs. */
  .pull           { grid-area: 1 / 2; float: none; width: auto; margin: 0;
                    align-self: start; }
  .values         { grid-template-columns: repeat(3, 1fr); }
}

@media (min-width: 1100px) {
  .hero__inner { max-width: 940px; }
  /* Four across only once each card can still hold its two sentences in three
     or four lines. At 900px they'd be columns of seven lines apiece. */
  .corevals__grid { grid-template-columns: repeat(4, 1fr); }
}

/* Only wide enough for the phone number alongside the Book Now button here */
@media (min-width: 1280px) {
  .header__phone { display: flex; }
}


/* ==========================================================================
   25. REDUCED MOTION
   Motion-sensitive users get the content, not the choreography.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  /* translate/scale, matching what .reveal actually animates now. `transform:
     none` here would no longer neutralise the offset — and would have wiped
     out component hover transforms as a side effect. */
  .reveal { opacity: 1 !important; translate: 0 !important; scale: 1 !important; }
}


/* ==========================================================================
   26. MIDNIGHT THEME
   The second of the site's two finishes, chosen in js/config.js. Everything
   below is scoped to html[data-theme="midnight"], so the light theme above is
   untouched — nothing here runs unless the attribute is on the page.

   It works by re-pointing the design tokens rather than restyling components:
   the ground goes deep navy, and --c-navy — which the stylesheet uses as its
   ink on 40-odd occasions — becomes the near-white that reads against it.
   Where a component paired the two the other way round (a navy chip on a pale
   card), it is fixed by hand in "Inversion fixes" at the end.
   ========================================================================== */
html[data-theme="midnight"] {
  /* — Ground —
     Deep midnight blue, deliberately not black: the photography on this site
     is all cool interiors and daylight, and a true black ground makes them
     look like cut-outs. A navy that shares the brand hue lets each photo sit
     in the page as though the room were simply lit late in the day. */
  --c-bg:           #0D1E33;   /* page base */
  --c-tint:         #0D1E33;   /* alias — bands stay seamless, as in the light theme */
  --c-tint-deep:    #0A1728;   /* footer and gradients: half a shade *under* the page */
  --c-surface:      #16304C;   /* cards and panels — lifts off the ground by ~8% */

  /* — Ink —
     --c-navy stops being navy here. It is the stylesheet's strong-contrast
     colour, so on a dark ground it has to be the pale one; every heading,
     icon stroke and small-caps label that reads var(--c-navy) follows. */
  --c-navy:         #EAF2FF;
  --c-navy-mid:     #B7CAE6;   /* the quieter of the two inks */
  --c-blue:         #3E7BE0;
  --c-blue-mist:    #17314E;   /* icon tiles — a deep tint, not a pale one */

  /* — Accent —
     Lifted from #2563EB: the light theme's accent is tuned for white paper and
     goes muddy on a dark ground. This one clears 6.7:1 on the page base, so it
     can still carry eyebrows and links at small sizes. */
  --c-accent:       #6AA5FF;
  --c-accent-light: #A8CBFF;
  --c-accent-pale:  #1B3557;

  /* — Type — */
  --c-ink:          #F2F7FF;
  --c-body:         #AFC1D8;   /* 9.1:1 on the ground */
  --c-muted:        #8CA0BC;   /* 6.1:1 — still clears AA on a raised card */
  --c-on-dark:      #EAF2FF;
  --c-on-dark-dim:  rgba(234, 242, 255, .70);

  /* — Lines —
     Light at low alpha rather than dark: a navy hairline is invisible here. */
  --line:           rgba(168, 203, 255, .15);
  --line-soft:      rgba(168, 203, 255, .08);
  --line-dark:      rgba(234, 242, 255, .20);

  /* — Depth —
     Shadow on a dark ground can't be a tinted navy; it has to be closer to
     black, and deeper, or cards read as flat rectangles. */
  --shadow-sm:      0 2px 10px rgba(0, 0, 0, .28);
  --shadow:         0 18px 50px -28px rgba(0, 0, 0, .70);
  --shadow-lg:      0 40px 90px -50px rgba(0, 0, 0, .85);

  /* — The two inline SVGs carry their colour in the markup, so they need
       re-stating: a #2563EB tick and a slate chevron both disappear here. — */
  --tick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%236AA5FF' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 8.5l3.2 3.2L13 4.5'/%3E%3C/svg%3E");
  --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23AFC1D8' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5l4 4 4-4'/%3E%3C/svg%3E");
}

/* Grain — multiply over a dark ground only makes mud. Screened at a whisper
   it does what it does in the light theme: takes the flatness off. */
html[data-theme="midnight"] body::before {
  mix-blend-mode: screen;
  opacity: .05;
}

/* --------------------------------------------------------------------------
   Inversion fixes
   The short list of places where the stylesheet uses --c-navy as a *fill*
   with a pale foreground on it. Flipping the token turns those inside out, so
   each one is restated here. If you add a navy-filled component later, this is
   where it goes.
   -------------------------------------------------------------------------- */

/* Pale chip, dark lettering — the mirror of the light theme, not a repaint. */
html[data-theme="midnight"] .skip-link { color: var(--c-bg); }

/* The hover wipe is a bright blue; white text on it sits at 2.8:1, so the ink
   goes dark instead. Reads sharper than white here in any case. */
html[data-theme="midnight"] .btn:hover { color: var(--c-bg); }

/* Sits on dark ground in both themes, so it must stay a light button. */
html[data-theme="midnight"] .btn--light {
  --btn-bg: var(--c-ink);
  --btn-fg: var(--c-bg);
  --btn-bd: var(--c-ink);
}

/* Navy disc + pale glyph becomes a deep disc + bright glyph. */
html[data-theme="midnight"] .play-link__circle { background: var(--c-blue-mist); }
html[data-theme="midnight"] .play-link__circle svg { fill: var(--c-accent); }
html[data-theme="midnight"] .play-link:hover .play-link__circle svg { fill: var(--c-bg); }

html[data-theme="midnight"] .socials a:hover {
  background: var(--c-accent-pale);
  border-color: var(--c-accent);
}
html[data-theme="midnight"] .socials a:hover svg { stroke: var(--c-accent-light); }

/* The pull-quote is the one deliberately dark object in the light theme. It
   can't simply invert — it would turn into a pale slab in the middle of a dark
   page — so it stays dark and instead deepens, keeping its lit top corner. */
html[data-theme="midnight"] .pull {
  background: #0B1A2E;
  background-image:
    radial-gradient(90% 120% at 12% 0%, #22508F, transparent 62%),
    linear-gradient(160deg, #102844 0%, #16355C 100%);
  box-shadow: inset 0 0 0 1px rgba(168, 203, 255, .16), var(--shadow);
}

/* Placeholder text on a dark field was a light-grey that vanished. */
html[data-theme="midnight"] .field input::placeholder,
html[data-theme="midnight"] .field textarea::placeholder { color: #7488A5; }
html[data-theme="midnight"] .field input:focus,
html[data-theme="midnight"] .field select:focus,
html[data-theme="midnight"] .field textarea:focus {
  box-shadow: 0 0 0 3px rgba(106, 165, 255, .22);
}

/* The page's decorative glows are pale washes tuned for white paper. Left
   alone each one becomes a milky cloud floating on the navy; re-tinted to the
   accent at low alpha they do the same job — lifting the flat ground where a
   section opens — and read as light in the room rather than fog. */
html[data-theme="midnight"] .page-hero {
  background:
    radial-gradient(70% 120% at 88% -15%, rgba(106, 165, 255, .16), transparent 62%),
    radial-gradient(55% 95% at 0% 115%, rgba(62, 123, 224, .12), transparent 60%),
    var(--c-tint);
}
html[data-theme="midnight"] .svc-hero {
  background:
    radial-gradient(55% 70% at 18% 78%, rgba(62, 123, 224, .13), transparent 62%),
    radial-gradient(45% 60% at 95% 92%, rgba(106, 165, 255, .14), transparent 60%),
    var(--c-tint);
  /* Mobile only — the copy lies over the photograph there, and section 9b lays
     a wash under it. Daylight uses a pale one because the copy goes navy; here
     every one of those lines is already the pale ink, so the wash has to go the
     other way and the picture darkens under the text instead. The accent line
     follows the theme's lifted accent rather than the deep blue. */
  --svc-scrim: 8, 19, 33;
  --svc-halo:  8, 19, 33;
  --svc-em:    var(--c-accent-light);
}
html[data-theme="midnight"] .stats {
  background:
    radial-gradient(60% 130% at 50% 0%, rgba(106, 165, 255, .12), transparent 65%),
    var(--c-tint);
}
html[data-theme="midnight"] .value::before {
  background: radial-gradient(70% 90% at 100% 0%, rgba(106, 165, 255, .12), transparent 62%);
}

/* Two headings are set in --c-bg because they sit *on* something dark — the
   CTA band and the pull-quote. Both objects stay dark here, so the ink that
   was "the page colour" has to become the on-dark ink instead, or it turns
   into navy lettering on a navy card. */
html[data-theme="midnight"] .cta__band h2,
html[data-theme="midnight"] .pull blockquote { color: var(--c-on-dark); }

/* Both chips are a near-white disc laid over a photograph — they stay white,
   because a dark disc would disappear into the darker corners of the image.
   It is the glyph inside that has to flip back to dark. */
html[data-theme="midnight"] .card__chip svg,
html[data-theme="midnight"] .price-card__chip svg { stroke: var(--c-bg); }
html[data-theme="midnight"] .card--media:hover .card__chip svg { stroke: var(--c-blue); }

/* Pale chip, so the count inside it reads dark like the label beside it. */
html[data-theme="midnight"] .faq-nav a.is-current span { color: rgba(13, 30, 51, .55); }

/* Image placeholder — only ever seen on a slot with no photo in it yet. */
html[data-theme="midnight"] .ph {
  background: linear-gradient(135deg, var(--c-blue-mist) 0%, var(--c-surface) 46%, var(--c-tint) 100%);
}
html[data-theme="midnight"] .ph::before {
  background:
    radial-gradient(120% 90% at 22% 18%, rgba(106, 165, 255, .18), transparent 58%),
    radial-gradient(80% 70% at 84% 88%, rgba(37, 99, 235, .22), transparent 62%);
}
html[data-theme="midnight"] .ph::after {
  color: var(--c-on-dark);
  background: rgba(13, 30, 51, .72);
  border-color: rgba(106, 165, 255, .45);
}

/* The date field's picker button is drawn by the browser in a fixed dark
   slate; on a dark field it all but disappears. Inverting it is the only
   handle CSS has on that shadow-DOM button. */
html[data-theme="midnight"] input[type="date"]::-webkit-calendar-picker-indicator {
  filter: invert(1) brightness(1.6);
  opacity: .8;
}


/* ==========================================================================
   27. LEGAL PAGES — the privacy policy, and anything else like it
   --------------------------------------------------------------------------
   A document someone reads top to bottom rather than a page they scan, so
   the display type scale is wrong here: an h2 at --fs-h2 is a headline, and
   a policy with eight headlines in it is unreadable. Everything below steps
   the hierarchy down to something closer to a printed page, and leans on the
   theme variables so the midnight palette (section 26) still applies.
   ========================================================================== */
/* Capped here rather than by .container--narrow, so the document lines up with
   the left edge of the page heading above it instead of floating in from a
   centred column. */
.legal { max-width: 68ch; }
.legal h2 {
  margin-top: clamp(2.2rem, 4vw, 3rem);
  font-size: clamp(1.3rem, 1.15rem + .5vw, 1.65rem);
  line-height: 1.25;
  color: var(--c-ink);
}
.legal > h2:first-child { margin-top: 0; }
.legal h3 {
  margin-top: 1.9rem;
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 500;
  color: var(--c-ink);
}
.legal p,
.legal li { color: var(--c-body); line-height: 1.75; }
.legal ul {
  margin-top: 1rem;
  padding-left: 1.2rem;
  list-style: disc;
}
.legal li + li { margin-top: .5rem; }
.legal li::marker { color: var(--c-accent-pale); }
.legal a {
  color: var(--c-accent);
  border-bottom: 1px solid var(--c-accent-pale);
}
.legal a:hover { border-bottom-color: currentColor; }

/* Standing note at the foot of the document */
.legal__updated {
  margin-top: clamp(2.4rem, 5vw, 3.2rem);
  padding-top: 1.4rem;
  border-top: 1px solid var(--line-soft);
  font-size: .88rem;
  color: var(--c-muted);
}


/* ==========================================================================
   28. GRAPHITE THEME
   The third of the site's finishes, chosen in js/config.js. Everything below
   is scoped to html[data-theme="graphite"], so nothing here runs unless the
   attribute is on the page — daylight and midnight are untouched.

   Where midnight inverts the page, graphite keeps a light ground and changes
   the *architecture* of the frame around it:

     · a solid charcoal bar across the top, opaque from the first pixel on
       every page rather than a pane the hero shows through;
     · the home page opening on a picture carousel that sits BELOW that bar
       instead of a full-bleed image behind it, with the words on a solid
       charcoal panel beside it — no glass, no scrim laid over the type;
     · a charcoal footer, so the page is bookended rather than fading out;
     · a fixed Email Us / Contact Us strip along the bottom edge.

   The palette is charcoal and cyan — no navy anywhere — which is what keeps it
   from reading as a third shade of the other two.

   The carousel and the bottom strip are both built by js/main.js when this
   theme is on, so no page's HTML carries markup for a finish it isn't wearing.
   ========================================================================== */
html[data-theme="graphite"] {
  /* — Ground —
     A cool paper grey rather than the blue-white of daylight. It has just
     enough warmth in it that the white cards laid on top read as lit. */
  --c-bg:           #F1F4F6;
  --c-tint:         #F1F4F6;
  --c-tint-deep:    #E4E9EC;
  --c-surface:      #FFFFFF;

  /* — Ink —
     --c-navy is the stylesheet's strong-contrast colour and its dark fill, on
     40-odd occasions. Here it simply stops being blue: charcoal does both jobs
     unchanged, which is why this theme needs none of midnight's inversion
     fixes — nothing is turned inside out, only re-hued. */
  --c-navy:         #151A1F;
  --c-navy-mid:     #333D46;
  --c-blue:         #0A7388;
  --c-blue-mist:    #DEF1F6;   /* pale icon tiles, cyan side of neutral */

  /* — Accent —
     Two cyans doing two jobs. --c-accent is the deep one, and it is deep on
     purpose: it carries eyebrows, links and 11px small-caps, where the bright
     cyan below would sit at about 2.4:1 on the page ground. This one measures
     4.98:1 there, and takes white at 5.5:1 for the button hover wipe.

     It is #0A7388 rather than the #0A7C94 it started as for exactly that
     reason — the prettier, greener cyan came out at 4.40:1, which is a pass
     for a heading and a fail for a 10px uppercase eyebrow. Darkening the green
     channel is what buys the last 0.1 of contrast without cooling the hue.

     --c-accent-light is the bright cut, and it is only ever used on charcoal. */
  --c-accent:       #0A7388;
  --c-accent-light: #2FC6E8;
  --c-accent-pale:  #C6E8F1;

  /* — Type — */
  --c-ink:          #12171C;
  --c-body:         #4C5762;   /* 7.4:1 on the ground */
  --c-muted:        #5A6570;   /* 5.9:1 — still clears AA at small sizes */
  --c-on-dark:      #F4F7F9;
  --c-on-dark-dim:  rgba(244, 247, 249, .74);

  /* — Lines — */
  --line:           rgba(21, 26, 31, .13);
  --line-soft:      rgba(21, 26, 31, .07);
  --line-dark:      rgba(244, 247, 249, .22);

  /* — Depth — neutral rather than tinted, so shadow reads as shadow, not haze */
  --shadow-sm:      0 2px 10px rgba(21, 26, 31, .06);
  --shadow:         0 18px 50px -28px rgba(21, 26, 31, .30);
  --shadow-lg:      0 40px 90px -50px rgba(21, 26, 31, .45);

  /* — The two inline SVGs carry their colour in the markup, so a blue tick and
       a slate chevron both have to be restated in the theme's own colours. — */
  --tick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%230A7388' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 8.5l3.2 3.2L13 4.5'/%3E%3C/svg%3E");
  --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%234C5762' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5l4 4 4-4'/%3E%3C/svg%3E");

  /* — Theme-only tokens, read by nothing outside this section — */
  --g-bar:      #12171C;   /* the solid charcoal of the panel and the foot */
  --g-bar-soft: #1C232A;   /* one step up, for anything laid on --g-bar */
  --g-cyan:     #17B6D8;   /* the bright cyan of the buttons and the bottom strip */
  --g-cyan-ink: #05222A;   /* what reads on that cyan — 8.9:1, where white is 2.1 */
  --g-hairline: rgba(244, 247, 249, .12);
  --cbar-h:     58px;      /* height of the fixed bottom strip; body clears it */

  /* — The top bar's own three, because it is the one piece of furniture that
       changes colour as the page scrolls (28a-i). Each is overwritten on <html>
       by js/theme.js when CONFIG.navbar names a colour, so these are the
       fallbacks rather than the last word.

       The navy and the royal are sampled from the service posters themselves —
       #122F6B is the band across the foot of each one, #2F6FE4 the blue of the
       headings. White measures 12.8:1 on the first and 4.7:1 on the second. — */
  --g-bar-rest:   #12171C;                    /* at rest — over the map, the type, the photographs */
  --g-bar-blue:   #122F6B;                    /* over the blue artwork */
  --g-bar-accent: #2F6FE4;                    /* the accent that goes with the navy */
  --g-bar-hair:   rgba(211, 226, 255, .20);   /* the hairline under it, in that state */
}

/* Grain over a light ground behaves as it does in daylight — left alone. */


/* --------------------------------------------------------------------------
   28a. THE BAR — solid charcoal, from the first pixel, on every page
   The stylesheet has four states that colour the header (default, .is-solid,
   body.light-hero and body.header-solid) and graphite collapses all four into
   one. The selector lists below are long for exactly that reason: each has to
   out-weigh the state rule it replaces, not merely follow it in the file.
   -------------------------------------------------------------------------- */
html[data-theme="graphite"] body .header,
html[data-theme="graphite"] body .header.is-solid,
html[data-theme="graphite"] body.light-hero .header,
html[data-theme="graphite"] body.header-solid .header,
html[data-theme="graphite"] body.header-solid .header:not(.is-solid) {
  /* --g-bar-rest, not --g-bar: the bar is the one thing here that changes
     colour on the way down the page (28a-i), so it holds its own token and the
     panel, the drawer and the footer keep theirs. */
  background: var(--g-bar-rest);
  border-bottom: 1px solid var(--g-hairline);
  box-shadow: none;
}
/* Scrolled, it lifts off the page with a shadow rather than a colour change —
   there is no colour change left to make. */
html[data-theme="graphite"] body .header.is-solid {
  box-shadow: 0 14px 34px -22px rgba(0, 0, 0, .85);
}

/* The wordmark is near-black, which disappears on the charcoal bar — and on
   the charcoal footer too, so the light cut is used in both places. */
html[data-theme="graphite"] .logo__mark { content: url("../images/logo-light.png"); }

html[data-theme="graphite"] .header .logo,
html[data-theme="graphite"] .header .header__phone { color: var(--c-on-dark); }
html[data-theme="graphite"] .header .header__phone svg { stroke: var(--g-cyan); }
html[data-theme="graphite"] .header .header__phone-label { color: var(--c-on-dark-dim); }
html[data-theme="graphite"] .header .header__phone:hover { color: var(--g-cyan); }

html[data-theme="graphite"] .header .nav__link { color: rgba(244, 247, 249, .74); }
html[data-theme="graphite"] .header .nav__link:hover,
html[data-theme="graphite"] .header .nav__link.is-active { color: #FFFFFF; }
html[data-theme="graphite"] .header .nav__link::after,
html[data-theme="graphite"] .header .nav__link.is-active::after { background: var(--g-cyan); }

html[data-theme="graphite"] .header .burger__bar { background: var(--c-on-dark); }
html[data-theme="graphite"] .header .burger[aria-expanded="true"] {
  background: var(--g-cyan);
  border-color: var(--g-cyan);
  box-shadow: 0 10px 26px rgba(0, 0, 0, .45);
}
html[data-theme="graphite"] .header .burger[aria-expanded="true"] .burger__bar { background: var(--g-cyan-ink); }

/* Book Now sits on charcoal in every state, so it is the one bright thing in
   the bar rather than a dark button that vanishes into it. The selector has to
   carry `body:not(.light-hero) .header:not(.is-solid)` weight to beat the
   home-page rule that flips this button to white. */
html[data-theme="graphite"] body .header .header__cta,
html[data-theme="graphite"] body:not(.light-hero) .header:not(.is-solid) .header__cta {
  --btn-bg: var(--g-cyan);
  --btn-fg: var(--g-cyan-ink);
  --btn-bd: var(--g-cyan);
}
html[data-theme="graphite"] .header .header__cta:hover { color: #FFFFFF; border-color: var(--c-accent); }

/* The drawer is frosted glass in the other two finishes. Solid here, like
   everything else this theme lays over the page. */
html[data-theme="graphite"] .mobile-nav {
  background: var(--g-bar);
  border-color: var(--g-hairline);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
html[data-theme="graphite"] .mobile-nav__close {
  background: var(--g-bar-soft);
  border-color: var(--g-hairline);
  box-shadow: none;
}
html[data-theme="graphite"] .mobile-nav__close:hover { background: var(--g-cyan); }
html[data-theme="graphite"] .mobile-nav__close:hover span { background: var(--g-cyan-ink); }
html[data-theme="graphite"] .mobile-nav__link::before,
html[data-theme="graphite"] .mobile-nav__link::after { color: var(--g-cyan); }
html[data-theme="graphite"] .mobile-nav__link:hover,
html[data-theme="graphite"] .mobile-nav__link.is-active { color: var(--g-cyan); }
html[data-theme="graphite"] .mobile-nav__cta {
  --btn-bg: var(--g-cyan);
  --btn-fg: var(--g-cyan-ink);
  --btn-bd: var(--g-cyan);
}
html[data-theme="graphite"] .mobile-nav__contact:hover { color: var(--g-cyan); }
html[data-theme="graphite"] .mobile-nav__social a:hover {
  background: var(--g-cyan);
  border-color: var(--g-cyan);
  color: var(--g-cyan-ink);
}
html[data-theme="graphite"] body.is-locked::after { background: rgba(9, 12, 15, .55); }


/* --------------------------------------------------------------------------
   28a-i. THE BAR OVER THE BLUE ARTWORK
   The page opens on the world map — charcoal, cream, not a trace of blue in it
   — and the bar above it is black to match. Further down, the service posters
   arrive, and every one of them is printed in a strong royal blue and navy. A
   black bar laid across those reads as a strip cut from a different site.

   So it doesn't stay black. `body.bar-blue` is switched on by block 5c of
   js/main.js for exactly as long as one of those sections is passing under the
   bar, and everything below follows it: the fill, the hairline, the underlines,
   the phone icon, Book Now and the open burger. The fade is the header's own
   `transition: background var(--t-med)` from section 6 — half a second, the
   same easing as every other state change in the bar, so it reads as the bar
   catching up with the page rather than a light being switched.

   The colours are the three in CONFIG.navbar, published onto <html> by
   js/theme.js before the first paint. Setting `adaptive: 'no'` there stops the
   class ever being added and the bar keeps --g-bar-rest the whole way down —
   which is the whole of the static option, no other edit needed.

   The selectors are long for the same reason 28a's are: each has to out-weigh
   the state rule it replaces, not merely follow it in the file.
   -------------------------------------------------------------------------- */
html[data-theme="graphite"] body.bar-blue .header,
html[data-theme="graphite"] body.bar-blue .header.is-solid,
html[data-theme="graphite"] body.bar-blue.light-hero .header,
html[data-theme="graphite"] body.bar-blue.header-solid .header,
html[data-theme="graphite"] body.bar-blue.header-solid .header:not(.is-solid) {
  background: var(--g-bar-blue);
  border-bottom-color: var(--g-bar-hair);
}

/* The menu warms by a hair — 244,247,249 is a neutral off-white that goes
   slightly green on navy, where a blue-white sits in the bar's own family. */
html[data-theme="graphite"] body.bar-blue .header .nav__link { color: rgba(226, 235, 255, .76); }
html[data-theme="graphite"] body.bar-blue .header .nav__link:hover,
html[data-theme="graphite"] body.bar-blue .header .nav__link.is-active { color: #FFFFFF; }

/* Everywhere the charcoal bar spends its bright cyan, the navy one spends the
   posters' royal instead. Cyan on navy is the one pairing this theme can't
   make work — two saturated blues a step apart, which reads as a mistake. */
html[data-theme="graphite"] body.bar-blue .header .nav__link::after,
html[data-theme="graphite"] body.bar-blue .header .nav__link.is-active::after { background: var(--g-bar-accent); }
html[data-theme="graphite"] body.bar-blue .header .header__phone svg { stroke: var(--g-bar-accent); }
html[data-theme="graphite"] body.bar-blue .header .header__phone:hover { color: var(--g-bar-accent); }

/* Book Now. White type rather than the dark ink the cyan chip carries: on this
   royal, white measures 4.7:1 and --g-cyan-ink only 3.8. */
html[data-theme="graphite"] body.bar-blue .header .header__cta,
html[data-theme="graphite"] body.bar-blue:not(.light-hero) .header:not(.is-solid) .header__cta {
  --btn-bg: var(--g-bar-accent);
  --btn-fg: #FFFFFF;
  --btn-bd: var(--g-bar-accent);
}
/* The hover wipe is --c-accent, the theme's deep cyan, which is the one colour
   this button must not turn while it is sitting on navy. It wipes to white and
   takes the bar's own colour as its type, so the pair stays a pair whatever
   colours config names — the only assumption is that the bar is dark, which is
   the one thing that block asks for. */
html[data-theme="graphite"] body.bar-blue .header .header__cta::after { background: #FFFFFF; }
html[data-theme="graphite"] body.bar-blue .header .header__cta:hover {
  color: var(--g-bar-blue);
  border-color: #FFFFFF;
}

html[data-theme="graphite"] body.bar-blue .header .burger[aria-expanded="true"] {
  background: var(--g-bar-accent);
  border-color: var(--g-bar-accent);
}
html[data-theme="graphite"] body.bar-blue .header .burger[aria-expanded="true"] .burger__bar { background: #FFFFFF; }

/* The drawer hangs off the bar with a few pixels of page between them, so a
   charcoal panel under a navy bar shows the seam. It follows too. */
html[data-theme="graphite"] body.bar-blue .mobile-nav {
  background: var(--g-bar-blue);
  border-color: var(--g-bar-hair);
}
html[data-theme="graphite"] body.bar-blue .mobile-nav__close { background: rgba(255, 255, 255, .09); }
html[data-theme="graphite"] body.bar-blue .mobile-nav__close:hover { background: var(--g-bar-accent); }
html[data-theme="graphite"] body.bar-blue .mobile-nav__close:hover span { background: #FFFFFF; }
html[data-theme="graphite"] body.bar-blue .mobile-nav__link::before,
html[data-theme="graphite"] body.bar-blue .mobile-nav__link::after,
html[data-theme="graphite"] body.bar-blue .mobile-nav__link:hover,
html[data-theme="graphite"] body.bar-blue .mobile-nav__link.is-active,
html[data-theme="graphite"] body.bar-blue .mobile-nav__contact:hover { color: #93B8FF; }
html[data-theme="graphite"] body.bar-blue .mobile-nav__cta {
  --btn-bg: var(--g-bar-accent);
  --btn-fg: #FFFFFF;
  --btn-bd: var(--g-bar-accent);
}
html[data-theme="graphite"] body.bar-blue .mobile-nav__social a:hover {
  background: var(--g-bar-accent);
  border-color: var(--g-bar-accent);
  color: #FFFFFF;
}


/* --------------------------------------------------------------------------
   28b. THE HOME CAROUSEL
   Built by js/main.js out of the hero that is already in index.html — the
   photograph that was the background becomes the first slide, and the copy
   keeps its own markup and moves onto a solid panel beneath it.

   Two full-bleed bands, stacked, edge to edge:

       ┌──────────────────────────────┐  ← the fixed bar
       │        the carousel          │  takes whatever height is left
       ├──────────────────────────────┤
       │  the words, on solid charcoal│  takes the height its copy needs
       └──────────────────────────────┘  ← the fold

   The pair measures exactly one screen. The panel is sized by its own content
   and the carousel is the 1fr that absorbs the rest, so on a tall desktop the
   pictures get a cinematic band and on a short phone they compress instead of
   pushing the headline under the fold. `min-height: 100svh` on the section is a
   floor rather than a fixed height: hand it a viewport too short for the copy
   and the hero grows rather than clipping it, and --gcar-min stops the pictures
   being squeezed to a sliver on the way.
   -------------------------------------------------------------------------- */
html[data-theme="graphite"] .hero.hero--graphite {
  min-height: 100svh;
  display: flex;
  align-items: stretch;
  /* The bar is fixed, so the top padding IS the bar. box-sizing: border-box
     means it comes out of the 100svh rather than adding to it — bar plus
     carousel plus panel is one screen, not one screen and a bar. Nothing on
     the sides or the bottom: both bands run to the edge of the glass. */
  padding: var(--header-h) 0 0;
  background: var(--c-bg);
  overflow: visible;
}
/* The scrim and the scroll cue belong to the full-screen hero. main.js takes
   both off the page; this is the belt to that pair of braces. */
html[data-theme="graphite"] .hero--graphite .hero__scrim,
html[data-theme="graphite"] .hero--graphite .hero__scroll { display: none; }

/* --------------------------------------------------------------------------
   28b-i. THE MAP HERO IN GRAPHITE
   The home page now opens on the world map in every finish (section 8b), so
   the carousel above stands down here — main.js block 2b checks for
   .hero--map and returns. What graphite has never had before is a full-screen
   hero with its own copy laid over a picture, so the handful of colours the
   charcoal panel used to carry are restated on it.

   The map's beacons stay gold. They are the one warm thing on a charcoal page
   and they are meant to be; --map-beacon in section 8b is where to argue with
   that.
   -------------------------------------------------------------------------- */
html[data-theme="graphite"] .hero--map h1 em,
html[data-theme="graphite"] .hero--map .hero__eyebrow,
html[data-theme="graphite"] .hero--map .eyebrow--light { color: var(--g-cyan); }

html[data-theme="graphite"] .hero--map .btn {
  --btn-bg: var(--g-cyan);
  --btn-fg: var(--g-cyan-ink);
  --btn-bd: var(--g-cyan);
}
html[data-theme="graphite"] .hero--map .btn--ghost-light {
  --btn-bg: transparent;
  --btn-fg: var(--c-on-dark);
  --btn-bd: rgba(244, 247, 249, .32);
}
html[data-theme="graphite"] .hero--map .btn:hover { color: #FFFFFF; }

/* The fixed Email Us / Contact Us strip sits over the foot of every page. The
   hero is the one block on the site that centres its copy in a whole screen,
   so it is the one place that has to pay for the strip in padding rather than
   trusting the page to scroll clear of it. The scroll cue would land under the
   strip whatever we do, so it goes. */
html[data-theme="graphite"] .hero--map {
  padding-bottom: calc(clamp(4rem, 10vh, 7rem) + var(--cbar-h) + env(safe-area-inset-bottom, 0px));
}
html[data-theme="graphite"] .hero--map .hero__scroll { display: none; }

.ghero__grid {
  /* Declared here rather than on .gcar because the grid is the element that
     reads it — custom properties inherit downwards, and a value set on the
     child would be invisible to the parent's track list. */
  --gcar-min: clamp(9rem, 26svh, 21rem);
  width: 100%;
  display: grid;
  grid-template-columns: 1fr;
  /* Pictures take the slack, words take what they need. */
  grid-template-rows: minmax(var(--gcar-min), 1fr) auto;
  /* No gap: the two bands meet on a cyan seam instead of a strip of page. */
  gap: 0;
}

/* ---- The pictures ---- */
.gcar {
  position: relative;
  overflow: hidden;
  background: var(--g-bar);
  /* Full bleed, so no card: no radius, no shadow and no ratio of its own —
     the height is whatever the grid row hands it. */
  min-height: var(--gcar-min);
}
/* Absolutely positioned, and that is load-bearing rather than tidy. In flow the
   track contributes its tallest photograph's intrinsic height to .gcar, .gcar
   hands that up to the `1fr` grid row, and the row stops being "whatever is
   left over" and becomes "as tall as the biggest JPEG" — which pushed the whole
   panel below the fold on a desktop. Out of flow, .gcar has no content height
   at all: it takes exactly the height the row gives it, and the slides take
   theirs from a track that now has a definite one. */
.gcar__track {
  position: absolute;
  inset: 0;
  display: flex;
  transition: transform .7s var(--ease);
  will-change: transform;
}
.gcar__slide {
  position: relative;
  flex: 0 0 100%;
  height: 100%;
  overflow: hidden;
}
.gcar__slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* A slow drift on whichever slide is showing. It is the only motion in the
     band, and it is what stops three still photographs reading as a slideshow.
     Reduced motion switches it off at the foot of this section. */
  transform: scale(1.06);
  transition: transform 7s linear;
}
.gcar__slide.is-current img { transform: scale(1); }

/* Foot of the frame darkened, so the dots stay legible over a bright ceiling
   or a white wall rather than only over the dark half of a photograph. */
.gcar::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 45%;
  pointer-events: none;
  background: linear-gradient(to top, rgba(9, 12, 15, .74), transparent);
}

/* ---- Arrows ---- */
.gcar__nav {
  position: absolute;
  top: 50%;
  z-index: 3;
  display: grid;
  place-items: center;
  width: 42px; height: 42px;
  margin-top: -21px;
  border-radius: 50%;
  color: var(--c-on-dark);
  background: rgba(18, 23, 28, .55);
  border: 1px solid var(--g-hairline);
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.gcar__nav--prev { left: clamp(.5rem, 1.4vw, .9rem); }
.gcar__nav--next { right: clamp(.5rem, 1.4vw, .9rem); }
.gcar__nav svg {
  width: 19px; height: 19px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.gcar__nav--prev svg { transform: rotate(180deg); }
.gcar__nav:hover {
  background: var(--g-cyan);
  border-color: var(--g-cyan);
  color: var(--g-cyan-ink);
}
.gcar__nav:focus-visible { outline: 2px solid var(--g-cyan); outline-offset: 3px; }

/* ---- Dots ---- */
.gcar__dots {
  position: absolute;
  left: 0; right: 0;
  bottom: clamp(.8rem, 2vw, 1.15rem);
  z-index: 3;
  display: flex;
  justify-content: center;
  gap: .5rem;
}
.gcar__dot {
  width: 26px; height: 4px;
  padding: 0;
  border-radius: 999px;
  background: rgba(244, 247, 249, .38);
  transition: background var(--t-fast), width var(--t-fast);
}
.gcar__dot.is-current { width: 40px; background: var(--g-cyan); }
.gcar__dot:focus-visible { outline: 2px solid var(--g-cyan); outline-offset: 4px; }

/* ---- The words, on a solid panel ---- */
.ghero__panel {
  position: relative;
  display: flex;
  align-items: center;
  /* Vertical only. The sides are the .container inside it, so the band runs to
     the edge of the screen while the copy still lines up with every other
     section on the page.

     The bottom carries the contact strip's height as well as its own. The panel
     is the one block on the site whose last line sits at the bottom edge of the
     viewport, so it is the one place the fixed strip would land on top of
     something — without this the second button is half a button. The charcoal
     still runs to the bottom of the screen; only the words move up off it. */
  padding-top: clamp(1.5rem, 4vw, 2.6rem);
  padding-bottom: calc(clamp(1.5rem, 4vw, 2.6rem) + var(--cbar-h) + env(safe-area-inset-bottom, 0px));
  background: var(--g-bar);
  overflow: hidden;
}
/* A cyan seam along the top edge, where the pictures end and the words begin.
   It was a bar down the left side when the panel was a card; full bleed, the
   left edge is the edge of the glass, and a 3px stripe there reads as a
   rendering fault rather than an ornament. */
.ghero__panel::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: linear-gradient(to right, var(--g-cyan), rgba(23, 182, 216, .15) 62%, transparent);
}
/* .hero__inner keeps the .container class it was written with, so the copy
   sits on the site's own measure. Its own 900px cap has to give way to the
   container's 1240 — same weight, and it wins on source order otherwise. */
html[data-theme="graphite"] .ghero__panel .hero__inner {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
}
/* Off the display scale the full-screen hero needed — --fs-h1 tops out at
   96px, which over a band this shallow is two lines of headline and no room
   for anything under them. */
html[data-theme="graphite"] .ghero__panel h1 { font-size: clamp(2.1rem, 1.55rem + 2.1vw, 3.4rem); }
html[data-theme="graphite"] .ghero__panel h1 em { color: var(--g-cyan); }
html[data-theme="graphite"] .ghero__panel .hero__sub {
  max-width: 46ch;
  font-size: clamp(.98rem, .93rem + .25vw, 1.08rem);
  color: rgba(244, 247, 249, .78);
}
html[data-theme="graphite"] .ghero__panel .hero__eyebrow { margin-bottom: clamp(.9rem, 2vw, 1.3rem); }
html[data-theme="graphite"] .ghero__panel .hero__eyebrow,
html[data-theme="graphite"] .ghero__panel .eyebrow--light { color: var(--g-cyan); }
html[data-theme="graphite"] .ghero__panel .hero__actions { margin-top: clamp(1.5rem, 3vw, 2.1rem); }
/* The default button is a charcoal fill, which is this panel's own colour. */
html[data-theme="graphite"] .ghero__panel .btn {
  --btn-bg: var(--g-cyan);
  --btn-fg: var(--g-cyan-ink);
  --btn-bd: var(--g-cyan);
}
html[data-theme="graphite"] .ghero__panel .btn--ghost-light {
  --btn-bg: transparent;
  --btn-fg: var(--c-on-dark);
  --btn-bd: rgba(244, 247, 249, .32);
}
html[data-theme="graphite"] .ghero__panel .btn:hover { color: #FFFFFF; }

/* One column at every width — the stack IS the layout, not a phone fallback.
   All that changes with room is how much of it the pictures are allowed to
   take, and how wide the paragraph runs before it wraps. */
@media (min-width: 900px) {
  .ghero__grid { --gcar-min: clamp(13rem, 32svh, 27rem); }
  html[data-theme="graphite"] .ghero__panel .hero__sub { max-width: 62ch; }
  /* Up here the strip floats clear of the bottom edge, so it costs that much
     more room. */
  html[data-theme="graphite"] .ghero__panel {
    padding-bottom: calc(clamp(1.5rem, 4vw, 2.6rem) + var(--cbar-h) + 1.5rem + env(safe-area-inset-bottom, 0px));
  }
}

/* A short viewport — a small phone, or a laptop with a lot of browser chrome.
   The pair still owes one screen, and on 640px of it the copy is what has to
   give: the paragraph steps down a notch and the rhythm tightens, rather than
   the pictures being squeezed to a letterbox. Keyed off height rather than
   width because that is the axis that actually ran out. */
@media (max-height: 720px) {
  .ghero__grid { --gcar-min: clamp(6rem, 22svh, 12rem); }
  html[data-theme="graphite"] .ghero__panel { padding-top: clamp(.9rem, 2.4vh, 1.6rem); }
  html[data-theme="graphite"] .ghero__panel h1 { font-size: clamp(1.8rem, 1.4rem + 1.7vw, 2.6rem); }
  html[data-theme="graphite"] .ghero__panel .hero__eyebrow { margin-bottom: .7rem; }
  html[data-theme="graphite"] .ghero__panel .hero__sub {
    margin-top: 1rem;
    font-size: .92rem;
    line-height: 1.6;
  }
  html[data-theme="graphite"] .ghero__panel .hero__actions { margin-top: 1.1rem; }
}

/* Past 1040 the band is wide enough that a left-aligned column of copy leaves
   two thirds of it empty charcoal. The buttons move out to the right and the
   band reads as one composition instead of a paragraph with a lot of room
   after it. Same trick, same breakpoint and the same reasoning as the CTA band
   in section 17 — this is that pattern, not a new one.

   Placed by grid area rather than by re-ordering the markup, so index.html
   keeps reading eyebrow, heading, paragraph, buttons. */
@media (min-width: 1040px) {
  html[data-theme="graphite"] .ghero__panel .hero__inner {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    column-gap: clamp(2.5rem, 6vw, 5rem);
  }
  html[data-theme="graphite"] .ghero__panel .hero__eyebrow { grid-area: 1 / 1; }
  html[data-theme="graphite"] .ghero__panel h1            { grid-area: 2 / 1; }
  html[data-theme="graphite"] .ghero__panel .hero__sub    { grid-area: 3 / 1; max-width: 52ch; }
  html[data-theme="graphite"] .ghero__panel .hero__actions {
    grid-area: 1 / 2 / 4 / 3;
    margin-top: 0;
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    min-width: 15rem;
  }
}


/* --------------------------------------------------------------------------
   28c. THE FIXED CONTACT STRIP
   Email Us / a dial button / Contact Us, pinned to the bottom edge of every
   page. Built by js/main.js, and each of the three parts takes itself off if
   the detail behind it is blank in config — an empty `phone` AND `whatsapp`
   removes the dial button, exactly as it removes every other Call button.

   z-index 880 puts it under the drawer's dimmer (890), so opening the menu
   covers it instead of leaving it floating over a dimmed page.
   -------------------------------------------------------------------------- */
/* The room the strip needs is given to the FOOTER rather than to the body, and
   that is a look rather than a technicality: pad the body and the charcoal foot
   stops short, leaving a pale band the height of the strip beneath it — the
   page appears to end twice. Padding the footer runs it to the bottom edge of
   the screen instead, and the strip floats on it.

   env(safe-area-inset-bottom) is the home-indicator strip on a modern iPhone.
   Without it the labels sit under the indicator and the last line of the footer
   ends up behind the strip. It resolves to 0 everywhere else. */
html[data-theme="graphite"] .footer {
  padding-bottom: calc(2rem + var(--cbar-h) + env(safe-area-inset-bottom, 0px));
}

.cbar {
  position: fixed;
  z-index: 880;
  inset: auto 0 0 0;
  display: flex;
  align-items: stretch;
  height: calc(var(--cbar-h) + env(safe-area-inset-bottom, 0px));
  padding-bottom: env(safe-area-inset-bottom, 0px);
  box-shadow: 0 -14px 34px -22px rgba(9, 12, 15, .8);
}
.cbar__side {
  flex: 1 1 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding-inline: 1rem;
  font-size: .74rem;
  font-weight: 500;
  letter-spacing: var(--track-mid);
  text-transform: uppercase;
  text-align: center;
  line-height: 1.2;
  transition: filter var(--t-fast);
}
/* The extra room on the inner edge is the disc's. Kept on both halves whether
   or not the disc is there, so a config with no phone and no WhatsApp gets two
   labels sitting a shade wide of centre rather than a gap where a button was. */
.cbar__side--mail    { background: var(--g-cyan); color: var(--g-cyan-ink); padding-right: 2.3rem; }
/* --g-bar-soft, not --g-bar: the strip spends the bottom of every page floating
   over the charcoal footer, and at --g-bar its right-hand half disappears into
   that footer completely — a cyan button with a hole beside it. One step up is
   the whole fix, and on a light page the step is imperceptible. */
.cbar__side--contact { background: var(--g-bar-soft); color: var(--c-on-dark); padding-left: 2.3rem; }
.cbar__side:hover { filter: brightness(1.12); }
.cbar__side:focus-visible { outline: 2px solid var(--c-ink); outline-offset: -4px; }

/* The dial button straddles the seam between the two halves and lifts clear of
   the strip, which is what makes it read as the one thing to press. The row
   around it is what carries the position, so the disc itself is free to move
   on hover without having to restate where it sits. */
.cbar__dials {
  position: absolute;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom, 0px) + var(--cbar-h) * .28);
  z-index: 1;
  display: flex;
  gap: .5rem;
  transform: translateX(-50%);
}
.cbar__dial {
  display: grid;
  place-items: center;
  width: 52px; height: 52px;
  border-radius: 50%;
  background: var(--c-surface);
  box-shadow: 0 6px 18px rgba(9, 12, 15, .32);
  transition: transform var(--t-fast), background var(--t-fast);
}
.cbar__dial svg {
  width: 22px; height: 22px;
  fill: none;
  stroke: var(--g-cyan);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: stroke var(--t-fast);
}
.cbar__dial:hover { transform: translateY(-3px); background: var(--g-cyan); }
.cbar__dial:hover svg { stroke: var(--c-surface); }
.cbar__dial:focus-visible { outline: 2px solid var(--g-cyan); outline-offset: 3px; }

/* Desktop: off the edges and into a floating strip, so it stops being a band
   across a 1600px screen and becomes a control the width of its own words. */
@media (min-width: 900px) {
  /* Up here the strip floats clear of the bottom edge, so the foot owes it its
     own height plus that gap. */
  html[data-theme="graphite"] .footer {
    padding-bottom: calc(2rem + var(--cbar-h) + 1.5rem + env(safe-area-inset-bottom, 0px));
  }
  .cbar {
    inset: auto auto calc(1.25rem + env(safe-area-inset-bottom, 0px)) 50%;
    transform: translateX(-50%);
    width: min(30rem, calc(100vw - 3rem));
    /* Off the screen edges, so the inset no longer has to be reserved inside
       the strip — the disc centres on a plain 58px box again. */
    height: var(--cbar-h);
    padding-bottom: 0;
    border-radius: var(--radius-pill);
    overflow: visible;
    /* The hairline is the ring, not a border — a border would sit inside the
       radius on each half separately and show as two arcs. It is what keeps the
       pill's outline readable where it floats over the charcoal footer. */
    box-shadow: 0 0 0 1px rgba(244, 247, 249, .14), 0 18px 44px -20px rgba(9, 12, 15, .55);
  }
  .cbar__side--mail    { border-radius: var(--radius-pill) 0 0 var(--radius-pill); }
  .cbar__side--contact { border-radius: 0 var(--radius-pill) var(--radius-pill) 0; }
  .cbar__dials { bottom: 50%; transform: translateX(-50%) translateY(50%); }
}


/* --------------------------------------------------------------------------
   28d. THE PAGES EITHER SIDE OF THE HOME ONE
   The inner heroes are pale washes tuned to the blue palette; re-tinted to
   cyan they do the same job in the theme's own colours. Layout untouched.
   -------------------------------------------------------------------------- */
html[data-theme="graphite"] .page-hero {
  background:
    radial-gradient(70% 120% at 88% -15%, rgba(23, 182, 216, .16), transparent 62%),
    radial-gradient(55% 95% at 0% 115%, rgba(10, 115, 136, .09), transparent 60%),
    var(--c-tint);
}
html[data-theme="graphite"] .page-hero::after { border-color: var(--c-accent-pale); }
html[data-theme="graphite"] .svc-hero {
  background:
    radial-gradient(55% 70% at 18% 78%, rgba(23, 182, 216, .14), transparent 62%),
    radial-gradient(45% 60% at 95% 92%, rgba(10, 115, 136, .10), transparent 60%),
    var(--c-tint);
  /* On a phone the copy lies over the photograph and section 9b lays a wash
     under it. Daylight's wash is a blue-white and its heading a navy; both are
     stated as raw values there rather than tokens, so both have to be restated
     — otherwise "One Standard" stays cobalt on a page with no cobalt in it. */
  --svc-scrim: 241, 244, 246;
  --svc-em:    #07566A;
}
html[data-theme="graphite"] .stats {
  background:
    radial-gradient(60% 130% at 50% 0%, rgba(23, 182, 216, .13), transparent 65%),
    var(--c-tint);
}
html[data-theme="graphite"] .value::before {
  background: radial-gradient(70% 90% at 100% 0%, rgba(23, 182, 216, .14), transparent 62%);
}

/* The one dark focal object on each page. Charcoal and cyan, matching the bar
   and the foot, instead of the three-blue navy wash. */
html[data-theme="graphite"] .cta__band {
  background:
    radial-gradient(115% 145% at 6% -10%, rgba(10, 115, 136, .85) 0%, transparent 55%),
    radial-gradient(85% 120% at 98% 105%, rgba(23, 182, 216, .34) 0%, transparent 58%),
    linear-gradient(158deg, #0C1114 0%, #12171C 46%, #1E2830 100%);
  box-shadow: inset 0 0 0 1px rgba(23, 182, 216, .26), var(--shadow-lg);
}
html[data-theme="graphite"] .cta__band h2 { color: var(--c-on-dark); }
html[data-theme="graphite"] .cta__band h2 em { color: var(--g-cyan); }
html[data-theme="graphite"] .cta__note {
  border-top-color: rgba(23, 182, 216, .2);
  color: rgba(244, 247, 249, .55);
}
html[data-theme="graphite"] .cta__band .btn--light {
  --btn-bg: var(--g-cyan);
  --btn-fg: var(--g-cyan-ink);
  --btn-bd: var(--g-cyan);
}
html[data-theme="graphite"] .cta__band .btn--light:hover { color: #FFFFFF; }

/* The pull-quote is deliberately the one dark card in a light theme, and it
   stays dark here — it just stops being navy. */
html[data-theme="graphite"] .pull {
  background-image:
    radial-gradient(90% 120% at 12% 0%, rgba(10, 115, 136, .75), transparent 62%),
    linear-gradient(160deg, #12171C 0%, #232E36 100%);
  box-shadow: inset 0 0 0 1px rgba(23, 182, 216, .2), var(--shadow);
}
html[data-theme="graphite"] .pull blockquote { color: var(--c-on-dark); }
html[data-theme="graphite"] .pull::before { color: rgba(47, 198, 232, .13); }


/* --------------------------------------------------------------------------
   28e. THE LAST OF THE BLUES
   Six places where the base stylesheet writes the brand blue as a raw value
   instead of reading a token. Re-pointing --c-accent doesn't reach any of them,
   so left alone they are the only cobalt left on a charcoal-and-cyan site —
   which reads as a mistake rather than a choice. Each is restated here in the
   same shape, same opacity, same position; only the hue moves.

   If you add another hardcoded colour to the stylesheet later, this is where
   its graphite counterpart goes.
   -------------------------------------------------------------------------- */

/* The contact page's panel — a whole navy slab, and the largest of the six. */
html[data-theme="graphite"] .contact-panel {
  background:
    radial-gradient(115% 140% at 8% -8%, rgba(10, 115, 136, .85) 0%, transparent 55%),
    radial-gradient(85% 110% at 100% 108%, rgba(23, 182, 216, .32) 0%, transparent 58%),
    linear-gradient(158deg, #0C1114 0%, #12171C 46%, #1E2830 100%);
}

/* The outlined 01 / 02 / 03 numerals on "How it works" — hollow until hover,
   which is when the fill fades in behind them. */
html[data-theme="graphite"] .step__num { -webkit-text-stroke: 1px rgba(10, 115, 136, .5); }
html[data-theme="graphite"] .step:hover .step__num { color: rgba(10, 115, 136, .12); }

/* The soft wash behind the free-quote panel on the pricing page. */
html[data-theme="graphite"] .quote-invite__panel::before {
  background:
    radial-gradient(90% 120% at 8% -20%, rgba(23, 182, 216, .16) 0%, transparent 60%),
    radial-gradient(70% 110% at 100% 0%, rgba(10, 115, 136, .10) 0%, transparent 58%);
}

/* What the FAQ search paints behind a matched word. */
html[data-theme="graphite"] .faq mark { background: rgba(23, 182, 216, .22); }

/* The dark image placeholder, seen only on a slot with no photograph in it. */
html[data-theme="graphite"] .ph--dark {
  background: linear-gradient(150deg, #0A0E11 0%, #12171C 38%, #1E2830 74%, #2E5F6D 100%);
}
html[data-theme="graphite"] .ph--dark::before {
  background:
    radial-gradient(110% 80% at 70% 12%, rgba(47, 198, 232, .28), transparent 60%),
    radial-gradient(90% 70% at 12% 92%, rgba(255, 255, 255, .1), transparent 62%);
}


/* --------------------------------------------------------------------------
   28f. THE FOOT
   Charcoal, so the page is closed by the same bar it was opened with. It is
   the only place in this theme where the ground goes dark for a whole band,
   and every colour in it has to be restated because the footer was written
   for a pale one.
   -------------------------------------------------------------------------- */
html[data-theme="graphite"] .footer {
  background: var(--g-bar);
  border-top: 1px solid var(--g-hairline);
}
html[data-theme="graphite"] .footer__col h4 { color: var(--g-cyan); }
html[data-theme="graphite"] .footer__about .logo { color: var(--c-on-dark); }
html[data-theme="graphite"] .footer__about p { color: rgba(244, 247, 249, .72); }
html[data-theme="graphite"] .footer__list a,
html[data-theme="graphite"] .footer__contact a,
html[data-theme="graphite"] .footer__contact span,
html[data-theme="graphite"] .footer__contact .areas { color: rgba(244, 247, 249, .78); }
/* The summary at the end of the regions, set apart from the names it follows
   the way the accent sets apart every other closing word on the page. */
html[data-theme="graphite"] .footer__contact .areas__more { color: var(--g-cyan); }
/* The name on the door is set in the navy of the light themes, which would all
   but vanish against the graphite bar — and the office plates need their
   hairline spine lifted off it for the same reason. */
html[data-theme="graphite"] .footer .office__name { color: var(--c-on-dark); }
html[data-theme="graphite"] .footer .office { border-left-color: rgba(244, 247, 249, .18); }
html[data-theme="graphite"] .footer .office__row { color: rgba(244, 247, 249, .78); }
html[data-theme="graphite"] .footer .office__label,
html[data-theme="graphite"] .footer .office__row svg { color: var(--g-cyan); stroke: var(--g-cyan); }
html[data-theme="graphite"] .footer .office__row:hover { color: var(--g-cyan); }
html[data-theme="graphite"] .footer .chat-link svg { stroke: var(--g-cyan); }
html[data-theme="graphite"] .footer__list a:hover,
html[data-theme="graphite"] .footer__contact dd a:hover,
html[data-theme="graphite"] .footer__bar a:hover { color: var(--g-cyan); }
html[data-theme="graphite"] .footer__abn,
html[data-theme="graphite"] .footer__contact dt,
html[data-theme="graphite"] .footer__bar { color: rgba(244, 247, 249, .55); }
html[data-theme="graphite"] .footer__bar { border-top-color: var(--g-hairline); }
html[data-theme="graphite"] .socials a { border-color: rgba(244, 247, 249, .2); }
html[data-theme="graphite"] .socials svg { stroke: var(--c-on-dark); }
html[data-theme="graphite"] .socials a:hover { background: var(--g-cyan); border-color: var(--g-cyan); }
html[data-theme="graphite"] .socials a:hover svg { stroke: var(--g-cyan-ink); }


/* --------------------------------------------------------------------------
   28g. REDUCED MOTION
   The carousel still changes slide — it just stops sliding and stops drifting,
   and main.js stops advancing it on its own. Section 25 covers the rest of the
   page; these two objects are new, so they are switched off here.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .gcar__track { transition: none; }
  .gcar__slide img,
  .gcar__slide.is-current img { transform: none; transition: none; }
  /* Only the transition goes. The dial's transform is doing two jobs — the
     3px hover lift AND the translateY(50%) that centres it on the desktop
     pill — so zeroing it here would knock the button off the strip. */
  .gcar__nav, .gcar__dot, .cbar__dial, .cbar__side { transition: none; }
}
