/* ---------- fonts ----------
   Self-hosted from /fonts/. The site makes no third-party request of any kind,
   so there is no Google Fonts link and no preconnect on any page.

   Three faces, latin only. The latin-ext subset was dropped: every character
   painted anywhere on the site was scanned, and not one falls in that range,
   so the three latin-ext files were 120 KB nobody would ever download a glyph
   from. Anything outside latin — the → in .arrow is the only one in use —
   falls back to the system font, exactly as it did under Google Fonts.

   Both families are variable, so one file covers a whole weight range. The
   ranges below are the ones the site actually renders: Inter 400 body, 500 nav,
   600 UI, 700 for <strong>; Playfair 400 in the mobile menu and footer tag,
   600 headings, 700 the wordmark and step numbers.

   Playfair italic ships at 500 only. Google serves a wider italic file for a
   400-600 range, but it costs 16 KB more, and 500 is what every italic run on
   the site rendered at before this was self-hosted. font-display:swap keeps
   text readable on the first paint. */
@font-face{
  font-family:'Inter';font-style:normal;font-weight:400 700;font-display:swap;
  src:url('/fonts/inter-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Playfair Display';font-style:normal;font-weight:400 700;font-display:swap;
  src:url('/fonts/playfair-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Playfair Display';font-style:italic;font-weight:500;font-display:swap;
  src:url('/fonts/playfair-italic-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

:root{
  --navy:#0B1F4D;
  --navy-deep:#071335;
  --gold:#C8A14D;
  --gold-soft:#D9BC7A;
  /* --gold and --gold-soft are decorative only on light grounds: 2.26:1 and
     1.72:1 on --paper, both far under AA. --gold-ink is the same hue carried
     down until it passes — 4.71:1 on --paper, 5.04:1 on white. Any gold text,
     focus ring or icon that has to be read on paper or white uses this one.
     On navy the original two already pass, so they keep those jobs. */
  --gold-ink:#8A6A22;
  --paper:#F9F7F2;
  --ink:#1B2438;
  --muted:#5C6478;
  --hairline:#E5E1D6;
  --white:#FFFFFF;
  --error:#B4442C;

  /* Header height, so the breadcrumb bar can sit directly under the nav
     without either of them knowing a pixel value about the other.
     Derived, not guessed: the logo box measures 45px (1.7rem wordmark on a
     1 line-height, plus the 8px sub-label with its 5px margin, 4px padding and
     1px rule), and #nav adds 18px above and below. 45 + 36 = 81.
     --nav-h-max is the same number frozen. --nav-h shrinks on scroll, so
     anything that reserves layout space has to use the constant or the page
     would jump every time the header compressed. */
  --nav-h:81px;
  --nav-h-max:81px;
  --crumb-h:38px;

  /* Two curves do all of the motion work.
     --ease-out is a long decelerate with no overshoot, for anything arriving:
     it should look like it stopped, not like it landed.
     --ease-pop is the same shape with a small overrun, used only on the
     process markers, which are the one place a little snap is wanted. */
  --ease-out:cubic-bezier(.16,1,.3,1);
  --ease-pop:cubic-bezier(.34,1.26,.64,1);
}
/* scrolled: the padding drops to 12px, so 45 + 24 = 69 */
html.scrolled{--nav-h:69px}
*{margin:0;padding:0;box-sizing:border-box}
/* text-size-adjust: iOS Safari inflates body text when the phone is rotated to
   landscape unless it is told not to.

   overflow-x:clip is load-bearing since the motion pass. Several resting states
   park an element outside its own column — .rv-right sits 40px to its right, a
   .wipe sits a full width to its right — and a transform still contributes to
   the scrollable overflow region even though it contributes nothing to layout.
   At 1440 the 1200px wrap has slack to absorb that; at 360 it is a horizontal
   scrollbar on every page. clip, not hidden: hidden would make <html> a scroll
   container, which costs smooth scrolling to anchors. */
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%;text-size-adjust:100%;overflow-x:clip}
body{
  font-family:'Inter',system-ui,sans-serif;
  color:var(--ink);
  background:var(--paper);
  line-height:1.6;
  font-size:16px;
  -webkit-font-smoothing:antialiased;
}
h1,h2,h3,.serif{font-family:'Playfair Display',Georgia,serif;font-weight:600;line-height:1.15;color:var(--navy)}
/* height:auto is not optional here. Every <img> carries explicit width and
   height attributes for CLS, and those attributes set the used height unless
   CSS overrides it — so max-width:100% alone scales the width down while the
   height stays pinned at the attribute value, stretching the image. */
img{display:block;max-width:100%;height:auto}
a{color:inherit}
.wrap{max-width:1200px;margin:0 auto;padding:0 24px}

/* ---------- reveal animation ---------- */
.rv{opacity:0;transform:translateY(28px);transition:opacity .8s cubic-bezier(.2,.6,.2,1),transform .8s cubic-bezier(.2,.6,.2,1)}
.rv.in{opacity:1;transform:none}
.rv.d1{transition-delay:.12s}.rv.d2{transition-delay:.24s}.rv.d3{transition-delay:.36s}.rv.d4{transition-delay:.48s}
/* Reduced motion turns all of it off, not just the reveals. The hero wipe and
   the scroll hint's pulse are animations rather than transitions, so the
   blanket rule is what actually catches them. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
    transition-delay:0s !important;
  }
  .rv{opacity:1;transform:none;transition:none;filter:none}
  html{scroll-behavior:auto}
  .hero-img{animation:none !important}
  .btn:hover,.svc:hover,.rel:hover,.insight:hover{transform:none}
}
/* Everything the motion pass added has its own reduced-motion block, and it is
   at the foot of this file rather than here. It has to be: these rules and the
   ones they switch off carry the same specificity, so whichever comes later
   wins, and a media query buys no weight at all. Put here, every one of them
   would have lost to the rule it was meant to override. */

/* ---------- logo ---------- */
.logo{display:inline-flex;flex-direction:column;align-items:flex-start;text-decoration:none;line-height:1}
.logo-word{font-family:'Playfair Display',serif;font-weight:700;font-size:1.7rem;letter-spacing:.02em;display:inline-flex;align-items:center;gap:.06em}
.logo-hex{width:.82em;height:.9em;margin:0 .03em;transform:translateY(.02em)}
.logo-sub{font-size:.5rem;letter-spacing:.34em;font-weight:600;margin-top:5px;padding-top:4px;border-top:1px solid var(--gold);text-transform:uppercase;white-space:nowrap}
.logo.on-dark .logo-word{color:var(--white)}
.logo.on-dark .logo-sub{color:rgba(255,255,255,.85)}
.logo.on-light .logo-word{color:var(--navy)}
.logo.on-light .logo-sub{color:var(--navy)}

/* ---------- nav ---------- */
/* #nav, not nav. This is the one fixed site header; the breadcrumb bar and the
   approach page's step rail are <nav> too, and an element selector here pins
   them to the top of the viewport on top of the logo.

   The scrolled state lives on <html>, not on #nav, because .crumb-bar is a
   sibling and has to react to it as well. js/main.js sets it. */
#nav{position:fixed;top:0;left:0;right:0;z-index:100;height:var(--nav-h);transition:background .35s,box-shadow .35s,backdrop-filter .35s,transform .4s cubic-bezier(.4,0,.2,1)}
/* -webkit-backdrop-filter as well: Safari only supported this unprefixed from
   18.0, so without it the blur silently does nothing on iOS 17 and earlier. */
html.scrolled #nav{background:rgba(11,31,77,.88);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:0 1px 0 rgba(255,255,255,.08)}
.nav-inner{display:flex;align-items:center;justify-content:space-between;height:100%}
.nav-links{display:flex;gap:34px;list-style:none;align-items:center}
.nav-links a{color:rgba(255,255,255,.85);text-decoration:none;font-size:.86rem;font-weight:500;letter-spacing:.02em;position:relative;padding:4px 0}
.nav-links a::after{content:"";position:absolute;left:0;bottom:0;height:1px;width:0;background:var(--gold);transition:width .3s}
.nav-links a:hover::after,.nav-links a:focus-visible::after{width:100%}
.nav-cta{border:1px solid var(--gold);color:var(--gold-soft) !important;padding:9px 20px !important;border-radius:2px;transition:background .3s,color .3s}
.nav-cta:hover{background:var(--gold);color:var(--navy) !important}
.nav-cta::after{display:none}
/* 44px, not 40: this is the only navigation control on a phone, and 44 is the
   iOS minimum target. The bars inside are unchanged. */
.burger{display:none;background:none;border:0;cursor:pointer;width:44px;height:44px;position:relative;z-index:210}
.burger span{display:block;width:24px;height:2px;background:#fff;margin:5px auto;transition:transform .3s,opacity .3s}
.burger.open span:nth-child(1){transform:translateY(7px) rotate(45deg)}
.burger.open span:nth-child(2){opacity:0}
.burger.open span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
/* visibility, not just opacity: without it the seven links stay in the tab
   order while the menu is invisible, including on desktop where the menu can
   never be opened at all. */
.mobile-menu{position:fixed;inset:0;background:var(--navy-deep);z-index:200;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:30px;opacity:0;visibility:hidden;pointer-events:none;transition:opacity .35s,visibility .35s}
.mobile-menu.open{opacity:1;visibility:visible;pointer-events:auto}
.mobile-menu a{color:#fff;text-decoration:none;font-family:'Playfair Display',serif;font-size:1.7rem}
@media(max-width:820px){.nav-links{display:none}.burger{display:block}}

/* ---------- hero ---------- */
/* 100vh first as the fallback: svh landed in Safari 15.4, and without it older
   iOS drops the declaration entirely and the hero collapses to content height. */
.hero{position:relative;min-height:100vh;min-height:100svh;display:flex;align-items:center;background:var(--navy-deep);overflow:hidden;color:#fff}
/* Three layers, and each one has exactly one job. .hero-img takes the parallax
   drift written by the rAF loop and owns the gradient wash. .hero-wipe is the
   frame that opens. .hero-photo is the picture.

   This used to be one element revealed with an animated clip-path. It looked
   the same, and it was the single most expensive thing on the page: clip-path
   is not a compositor property, so a full-viewport image layer repainted on
   every frame for the whole reveal, and lengthening that reveal — which is
   what the brief asked for — made it worse. The counter-translated frame below
   is the same picture with the same timing, on the compositor.

   inset:-80px 0 is the drift's headroom, so it can never pull the photograph's
   own edge into view. No will-change: applying a transform promotes these
   anyway, and the hint only pins layers that do not need pinning. */
.hero-img{position:absolute;inset:-80px 0}
.hero-wipe{position:absolute;inset:0;overflow:hidden;border-radius:0;
  transform:translate3d(0,-101%,0);animation:heroFrame 1.8s var(--ease-out) .15s forwards}
.hero-photo{position:absolute;inset:0;background-size:cover;background-position:center 30%;
  transform:translate3d(0,101%,0);animation:heroPhoto 1.8s var(--ease-out) .15s forwards}
@keyframes heroFrame{to{transform:translate3d(0,0,0)}}
@keyframes heroPhoto{to{transform:translate3d(0,0,0)}}
.hero-img::after{content:"";position:absolute;inset:0;background:linear-gradient(100deg,rgba(7,19,53,.94) 0%,rgba(11,31,77,.78) 45%,rgba(11,31,77,.35) 100%)}
/* .hero-content is the flex item and now carries no gutter of its own: the two
   boxes inside it set their own, because they are deliberately different widths.

   width:100% rather than the shrink-to-fit it used to be. As a flex item with
   width:auto it sized to its widest child and margin:0 auto centred it, which
   is why the hero copy sat 298px right of the logo at 1440 and 82px right of it
   at 768. Filling the line hands the alignment to .hero-copy's own .wrap, and
   the two gutters then agree at every width by construction rather than by
   coincidence.

   min-width:0 is the guard on the 360 case. A flex item's min-width is auto,
   which floors it at min-content, and that floor is what pushed the hero column
   off both edges of a phone once — the eyebrow's tracked-out caps set a
   min-content wider than the viewport. .eyebrow{flex-wrap:wrap} lowered that
   floor and fixed it, but the floor still exists and any long unbroken word in
   the h1 would find it again. min-width:0 removes the floor instead of ducking
   under it. */
.hero-content{position:relative;z-index:2;width:100%;min-width:0;padding:140px 0 110px}
/* 604px is the width .hero-content used to shrink-to-fit to, and the buttons
   are meant to stay exactly where that put them. It is written as a constant
   rather than left to fall out of the copy, which is the point: the old number
   was set by the standfirst's 52ch measure, so every future edit to that
   sentence would have moved the buttons. Now nothing moves them but this line.
   Below 604px the box is viewport-width and the 24px gutter takes over, which
   is why the two columns converge on the same edge at 360. */
.hero-actions{max-width:604px;margin:0 auto;padding:0 24px}
/* The eyebrow sits on a light ground on every interior page and on a dark one
   only inside the home page's full-bleed bands, so --gold-ink is the default
   and the dark grounds opt back into --gold-soft. The leading dash stays --gold
   in both places; it is a rule, not text. */
/* flex-wrap is not cosmetic. An inline-flex box holds its text in a single
   anonymous item that has no break opportunity, so at .28em tracking the
   longest eyebrow ("Construction Consulting in Kenya") measures wider than a
   360px viewport. That set the min-content width of .hero .wrap, which is a
   flex item of .hero with margin:0 auto, so the whole hero column could not
   shrink and hung off both edges of the phone. Allowing the row to wrap gives
   the text somewhere to break and the column collapses to the viewport again. */
.eyebrow{display:inline-flex;flex-wrap:wrap;align-items:center;gap:10px;font-size:.78rem;letter-spacing:.28em;text-transform:uppercase;color:var(--gold-ink);font-weight:600;margin-bottom:26px}
.eyebrow::before{content:"";width:34px;height:1px;background:var(--gold)}
.hero .eyebrow,.band .eyebrow,.sustain .eyebrow,.cta .eyebrow,.cta-band .eyebrow,.page-header .eyebrow{color:var(--gold-soft)}
.hero h1{color:#fff;font-size:clamp(2.5rem,6vw,4.6rem);max-width:15ch;margin-bottom:24px}
.hero h1 em{font-style:italic;color:var(--gold-soft)}
.hero p{max-width:52ch;color:rgba(255,255,255,.82);font-size:1.06rem;margin-bottom:40px}
.btn-row{display:flex;gap:16px;flex-wrap:wrap}
.btn{display:inline-block;text-decoration:none;font-weight:600;font-size:.9rem;letter-spacing:.03em;padding:15px 32px;border-radius:2px;transition:transform .25s,background .25s,color .25s,box-shadow .25s}
.btn:hover{transform:translateY(-2px)}
.btn-gold{background:var(--gold);color:var(--navy)}
.btn-gold:hover{box-shadow:0 10px 30px rgba(200,161,77,.35)}
.btn-ghost{border:1px solid rgba(255,255,255,.4);color:#fff}
.btn-ghost:hover{border-color:var(--gold);color:var(--gold-soft)}
.hero-hex{position:absolute;right:-140px;bottom:-160px;width:520px;height:580px;z-index:1;opacity:.16}
.scroll-hint{position:absolute;bottom:28px;left:50%;transform:translateX(-50%);z-index:2;color:rgba(255,255,255,.55);font-size:.7rem;letter-spacing:.3em;text-transform:uppercase;display:flex;flex-direction:column;align-items:center;gap:8px}
.scroll-hint::after{content:"";width:1px;height:38px;background:linear-gradient(var(--gold),transparent);animation:drip 2s ease-in-out infinite}
@keyframes drip{0%,100%{opacity:.3}50%{opacity:1}}

/* ---------- sections ---------- */
section{padding:110px 0}
.sec-head{max-width:640px;margin-bottom:64px}
.sec-head.center{margin-left:auto;margin-right:auto;text-align:center}
.sec-head.center .eyebrow{justify-content:center}
.sec-head h2{font-size:clamp(1.9rem,3.6vw,2.9rem);margin-bottom:18px}
.sec-head p{color:var(--muted)}

/* story intro */
/* overflow:hidden contains the image wipe, which parks a full column width to
   the right until it reveals. The other two bands that hold one, .band and
   .sustain, already clip. The hexagon badge overhangs .story-media by 34px, not
   this section, so nothing visible is cut. */
.story{background:var(--paper);overflow:hidden}
.story-grid{display:grid;grid-template-columns:1fr 1fr;gap:70px;align-items:center}
.story-copy h2{font-size:clamp(1.9rem,3.4vw,2.7rem);margin-bottom:22px}
.story-copy p{color:var(--muted);margin-bottom:18px}
.mission{border-left:2px solid var(--gold);padding:6px 0 6px 22px;margin-top:28px;font-family:'Playfair Display',serif;font-size:1.2rem;font-style:italic;color:var(--navy)}
/* The shadow sits on the container, not on the <img>. The image is inside a
   .wipe that translates itself out of frame until it reveals, and a shadow on
   a moving layer slides in with it and gives the trick away. */
.story-media{position:relative;border-radius:3px;box-shadow:0 30px 60px rgba(11,31,77,.18)}
.story-media img{border-radius:3px}
.story-media .hex-badge{position:absolute;top:-34px;right:-24px;width:110px;height:122px}
@media(max-width:900px){.story-grid{grid-template-columns:1fr;gap:44px}.story-media .hex-badge{right:0}}

/* services */
.services{background:var(--white);border-top:1px solid var(--hairline);border-bottom:1px solid var(--hairline)}
/* min(300px,100%) rather than a bare 300px: below about 350px of viewport the
   fixed minimum is wider than the column it sits in and the grid pushes the
   page sideways. */
.svc-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(300px,100%),1fr));gap:26px}
.svc{background:var(--paper);border:1px solid var(--hairline);border-radius:3px;padding:38px 32px;position:relative;transition:transform .35s,box-shadow .35s,border-color .35s}
/* No border-color here any more: the gold border is drawn and swept in by the
   ::after in the MOTION block at the foot of this file, on focus as well as
   hover. Switching the real border too would double it. */
.svc:hover{transform:translateY(-6px);box-shadow:0 22px 48px rgba(11,31,77,.12)}
.svc-icon{width:46px;height:46px;color:var(--gold);margin-bottom:22px}
.svc h3{font-size:1.28rem;margin-bottom:12px}
.svc p{color:var(--muted);font-size:.94rem}

/* band (image + narrative) */
.band{background:var(--navy);color:#fff;position:relative;overflow:hidden}
.band-grid{display:grid;grid-template-columns:1.05fr 1fr;gap:70px;align-items:center}
.band h2{color:#fff;font-size:clamp(1.9rem,3.4vw,2.7rem);margin-bottom:20px}
.band h2 em{font-style:italic;color:var(--gold-soft)}
.band p{color:rgba(255,255,255,.78);margin-bottom:16px}
.band img{border-radius:3px}
.band .eyebrow{color:var(--gold-soft)}
.checks{list-style:none;margin-top:26px}
.checks li{display:flex;gap:14px;align-items:flex-start;padding:10px 0;color:rgba(255,255,255,.88);font-size:.96rem}
.checks svg{flex:0 0 auto;width:20px;height:22px;margin-top:2px;color:var(--gold)}
.checks strong{color:#fff}
@media(max-width:900px){.band-grid{grid-template-columns:1fr;gap:44px}}

/* industries strip */
.industries .ind-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:1px;background:var(--hairline);border:1px solid var(--hairline)}
.ind{background:var(--white);padding:34px 22px;text-align:center;transition:background .3s}
.ind:hover{background:var(--paper)}
.ind svg{width:30px;height:32px;color:var(--gold);margin:0 auto 14px}
.ind span{display:block;font-weight:600;font-size:.92rem;color:var(--navy);letter-spacing:.02em}

/* process */
.process{background:var(--white);border-top:1px solid var(--hairline)}
.steps{display:grid;grid-template-columns:repeat(5,1fr);gap:0;position:relative;margin-top:20px}
.steps::before{content:"";position:absolute;top:27px;left:8%;right:8%;height:1px;background:linear-gradient(90deg,transparent,var(--gold) 12%,var(--gold) 88%,transparent)}
.step{text-align:center;padding:0 14px;position:relative}
.step-hex{width:54px;height:58px;margin:0 auto 20px;position:relative;z-index:2}
.step-hex svg{width:100%;height:100%}
.step-hex .n{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-family:'Playfair Display',serif;font-weight:700;color:var(--navy);font-size:1.05rem}
.step h3{font-size:1.05rem;margin-bottom:8px}
.step p{font-size:.85rem;color:var(--muted)}
@media(max-width:900px){.steps{grid-template-columns:1fr;gap:34px}.steps::before{display:none}.step{display:grid;grid-template-columns:54px 1fr;gap:20px;text-align:left}.step-hex{margin:0}.step h3{margin-top:2px}}

/* sustainability */
/* The photograph moved off this element and onto .sustain-bg, below the banner,
   so it can be transformed for the drift and the wipe. overflow:hidden is what
   keeps that bleeding layer inside the band. The wash keeps an explicit z-index
   now, because .sustain-bg is a real first child and would otherwise paint
   over a ::before. */
.sustain{position:relative;color:#fff;padding:150px 0;overflow:hidden}
.sustain::before{content:"";position:absolute;inset:0;z-index:1;background:linear-gradient(rgba(7,19,53,.85),rgba(7,19,53,.85))}
.sustain .wrap{position:relative;z-index:2;max-width:820px;text-align:center}
.sustain .eyebrow{display:flex;justify-content:center}
.sustain h2{color:#fff;font-size:clamp(1.8rem,3.4vw,2.6rem);margin-bottom:20px}
.sustain h2 em{font-style:italic;color:var(--gold-soft)}
.sustain p{color:rgba(255,255,255,.8)}

/* cta */
.cta{background:var(--navy-deep);color:#fff;text-align:center;position:relative;overflow:hidden}
.cta h2{color:#fff;font-size:clamp(2rem,4.4vw,3.3rem);max-width:20ch;margin:0 auto 22px}
.cta h2 em{font-style:italic;color:var(--gold-soft)}
.cta p{color:rgba(255,255,255,.75);max-width:52ch;margin:0 auto 40px}
.cta-hex{position:absolute;left:-160px;top:-120px;width:480px;height:540px;opacity:.1}

/* contact */
.contact{background:var(--paper)}
.contact-grid{display:grid;grid-template-columns:1fr 1.1fr;gap:70px}
.c-item{display:flex;gap:18px;align-items:flex-start;padding:20px 0;border-bottom:1px solid var(--hairline)}
.c-item svg{width:22px;height:24px;color:var(--gold);flex:0 0 auto;margin-top:3px}
.c-item .lbl{font-size:.72rem;letter-spacing:.2em;text-transform:uppercase;color:var(--muted);font-weight:600}
.c-item a,.c-item span.v{display:block;font-weight:600;color:var(--navy);text-decoration:none;font-size:1.02rem;margin-top:2px}
.c-item a:hover{color:var(--gold-ink)}
.form{background:var(--white);border:1px solid var(--hairline);border-radius:3px;padding:40px;box-shadow:0 20px 50px rgba(11,31,77,.07)}
.form h2,.form h3{font-size:1.35rem;margin-bottom:6px}
.form>p{color:var(--muted);font-size:.9rem;margin-bottom:26px}
.field{margin-bottom:18px}
.field label{display:block;font-size:.78rem;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--muted);margin-bottom:7px}
/* 1rem is load-bearing, not a style choice: iOS Safari zooms the whole viewport
   when a text field under 16px takes focus, and never zooms back out. */
.field input,.field select,.field textarea{width:100%;border:1px solid var(--hairline);border-radius:2px;background:var(--paper);padding:13px 15px;font-family:inherit;font-size:1rem;color:var(--ink);transition:border-color .25s,box-shadow .25s}
.field input:focus,.field select:focus,.field textarea:focus{outline:none;border-color:var(--gold-ink);box-shadow:0 0 0 3px rgba(138,106,34,.18)}
.field .err{color:var(--error);font-size:.78rem;margin-top:5px;display:none}
.field.bad input,.field.bad select,.field.bad textarea{border-color:var(--error)}
.field.bad .err{display:block}
.form .btn{width:100%;border:0;cursor:pointer;font-family:inherit}
.form-note{font-size:.8rem;color:var(--muted);margin-top:14px;text-align:center}
.form-ok{display:none;text-align:center;padding:30px 0;color:var(--navy);font-weight:600}
@media(max-width:900px){.contact-grid{grid-template-columns:1fr;gap:44px}}

/* footer */
footer{background:var(--navy-deep);color:rgba(255,255,255,.7);padding:60px 0 34px}
.foot-top{display:flex;justify-content:space-between;align-items:flex-start;gap:40px;flex-wrap:wrap;padding-bottom:40px;border-bottom:1px solid rgba(255,255,255,.1)}
.foot-nav{display:flex;gap:28px;list-style:none;flex-wrap:wrap}
.foot-nav a{color:rgba(255,255,255,.7);text-decoration:none;font-size:.86rem}
.foot-nav a:hover{color:var(--gold-soft)}
/* .55 rather than the .45 this used to carry. White at .45 over --navy-deep
   composites to 4.44:1, which is under the 4.5:1 AA floor for text this size,
   and rule 4 does not have a "nearly" in it. .55 measures 6.07:1. */
.foot-bottom{display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap;padding-top:26px;font-size:.8rem;color:rgba(255,255,255,.55)}
.foot-bottom .tag{font-style:italic;font-family:'Playfair Display',serif}
/* The developer credit sits under the copyright line, smaller, but not fainter:
   it is subordinate by size and position, which is enough. Dropping the opacity
   as well is what would push it back under AA. */
.foot-dev{display:block;margin-top:7px;font-size:.72rem;color:rgba(255,255,255,.55)}

/* =========================================================
   MULTI-PAGE ADDITIONS (v2)
   Everything below supports interior pages. The v1 rules above
   are the home page and remain the source of truth for the
   design system. Do not fork colours or type here.
   ========================================================= */

/* skip link — first thing in the tab order on every page, off-screen until it
   takes focus. The nav is fixed and eight items deep, so without this a
   keyboard reader pays for it again on every page. */
.skip{
  position:fixed;left:16px;top:-80px;z-index:300;
  background:var(--gold);color:var(--navy);font-weight:600;font-size:.86rem;
  padding:12px 20px;border-radius:2px;text-decoration:none;
}
.skip:focus{top:16px}
main:focus{outline:none}

/* active nav state, set at runtime by js/main.js */
.nav-links a[aria-current="page"]{color:#fff}
.nav-links a[aria-current="page"]::after{width:100%}
.mobile-menu a[aria-current="page"]{color:var(--gold-soft)}

/* services dropdown */
.has-drop{position:relative}
.drop{
  position:absolute;top:calc(100% + 14px);left:-18px;min-width:270px;
  background:var(--navy-deep);border:1px solid rgba(255,255,255,.12);
  border-radius:3px;padding:10px 0;box-shadow:0 24px 50px rgba(0,0,0,.35);
  opacity:0;visibility:hidden;transform:translateY(-6px);
  transition:opacity .22s,transform .22s,visibility .22s;
}
.has-drop:hover .drop,.has-drop:focus-within .drop,.drop.open{opacity:1;visibility:visible;transform:none}
.drop::before{content:"";position:absolute;top:-14px;left:0;right:0;height:14px}
.drop a{display:block;padding:10px 22px;font-size:.86rem;color:rgba(255,255,255,.8);text-decoration:none;border-left:2px solid transparent}
.drop a:hover,.drop a:focus-visible{background:rgba(255,255,255,.05);color:#fff;border-left-color:var(--gold)}
.drop a::after{display:none}

/* interior page header */
/* Clears the nav and the breadcrumb bar, then leaves 58px before the h1.
   Built from --nav-h-max, never --nav-h: this is reserved layout space, and
   using the value that shrinks on scroll would shift the whole page every time
   the header compressed. The home page hero does not use these tokens at all,
   so it is unaffected. */
.page-header{position:relative;background:var(--navy-deep);color:#fff;padding:calc(var(--nav-h-max) + var(--crumb-h) + 58px) 0 66px;overflow:hidden}
/* no bar below 820px, so nothing to clear */
@media(max-width:820px){
  .page-header{padding-top:calc(var(--nav-h-max) + 58px)}
}
.page-header::after{
  content:"";position:absolute;right:-90px;bottom:-140px;width:340px;height:380px;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 110'%3E%3Cpath d='M50 4 L95 29 L95 81 L50 106 L5 81 L5 29 Z' fill='none' stroke='%23C8A14D' stroke-width='1.5'/%3E%3C/svg%3E") no-repeat center/contain;
  opacity:.18;pointer-events:none;
}
.page-header .wrap{position:relative;z-index:2}
.page-header h1{color:#fff;font-size:clamp(2rem,4.4vw,3.2rem);max-width:18ch;margin-bottom:16px}
.page-header h1 em{font-style:italic;color:var(--gold-soft)}
.page-header .standfirst{color:rgba(255,255,255,.78);max-width:56ch;font-size:1.04rem}

/* breadcrumb */
/* Breadcrumb bar. Fixed directly under the nav, so it reads as part of the
   site header rather than as the first line of the page.

   Transparent at rest, where it sits on the navy .page-header. Once scrolled
   it takes the same tinted, blurred panel as the nav, because from then on
   arbitrary page content passes underneath it, including the white bands.
   Text opacities are set for the scrolled case, which is the darker-on-lighter
   of the two and therefore the harder one. */
.crumb-bar{
  position:fixed;top:var(--nav-h);left:0;right:0;z-index:99;
  height:var(--crumb-h);font-size:.78rem;
  color:rgba(255,255,255,.72);
  transition:background .35s,box-shadow .35s,backdrop-filter .35s,top .35s,
             transform .4s cubic-bezier(.4,0,.2,1);
}
.crumb-bar .wrap{height:100%;display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.crumb-bar a{color:rgba(255,255,255,.88);text-decoration:none}
.crumb-bar a:hover{color:var(--gold-soft)}
.crumb-bar span[aria-hidden]{color:var(--gold)}
html.scrolled .crumb-bar{
  background:rgba(11,31,77,.88);
  -webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);
  box-shadow:0 1px 0 rgba(255,255,255,.08);
}

/* Under 820px the bar is dropped entirely: there is no room for a trail beside
   a 44px burger. js/main.js clones its text into the top of the fullscreen
   overlay instead, where it reads as a location label rather than a link. */
@media(max-width:820px){
  .crumb-bar{display:none}
}
.menu-crumb{
  color:rgba(255,255,255,.6);font-size:.72rem;letter-spacing:.18em;
  text-transform:uppercase;font-weight:600;text-align:center;
  max-width:84vw;line-height:1.6;
}

/* generic interior sections */
/* overflow:hidden is what contains the reveal variants. .rv-right rests 40px to
   the right of its own column until it is revealed, and a transform still
   contributes to the scrollable overflow region even though it contributes
   nothing to layout. At 1440 the wrap has 120px of slack either side and it
   never shows; at 360 and 768 the wrap fills the viewport and those 40px were a
   horizontal scrollbar on seven pages. The three home page bands that hold an
   image wipe clip for the same reason. Nothing on an interior page is designed
   to overhang its section, so this cuts nothing. */
.sec{padding:88px 0;overflow:hidden}
.sec.tight{padding:64px 0}
.sec.white{background:var(--white);border-top:1px solid var(--hairline);border-bottom:1px solid var(--hairline)}
.sec h2{font-size:clamp(1.6rem,3vw,2.3rem);margin-bottom:18px}
.sec h3{font-size:1.22rem;margin-bottom:10px}
.measure{max-width:70ch}
.measure p{color:var(--muted);margin-bottom:16px}
.measure p strong{color:var(--navy)}

/* hexagon-bulleted list, the site's standard list */
.hexlist{list-style:none}
.hexlist li{display:flex;gap:14px;align-items:flex-start;padding:9px 0;color:var(--muted)}
.hexlist li::before{
  content:"";flex:0 0 auto;width:11px;height:12px;margin-top:9px;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 110'%3E%3Cpath d='M50 6 L92 30 L92 80 L50 104 L8 80 L8 30 Z' fill='%23C8A14D'/%3E%3C/svg%3E") no-repeat center/contain;
}
.hexlist strong{color:var(--navy)}

/* two column split used on interior pages */
.split{display:grid;grid-template-columns:1fr 1fr;gap:64px;align-items:start}
.split.narrow-left{grid-template-columns:.85fr 1fr}
@media(max-width:900px){.split,.split.narrow-left{grid-template-columns:1fr;gap:40px}}

/* related links */
.related{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:20px;margin-top:14px}
.rel{display:block;text-decoration:none;background:var(--white);border:1px solid var(--hairline);border-radius:3px;padding:26px 24px;transition:transform .3s,border-color .3s,box-shadow .3s}
.rel:hover{transform:translateY(-4px);box-shadow:0 18px 40px rgba(11,31,77,.1)}
.rel .lbl{font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;color:var(--muted);font-weight:600}
/* h2 as well as h3, because the home page's three-door strip sits directly
   under the h1 with no section heading above it, so its cards are h2s. */
.rel h2,.rel h3{margin:8px 0 0;font-size:1.08rem}
.rel .arrow{color:var(--gold-ink);font-weight:600;font-size:.86rem;margin-top:10px;display:inline-block}
.rel:focus-visible{outline:2px solid var(--gold-ink);outline-offset:4px}

/* contextual CTA band, one per interior page */
.cta-band{background:var(--navy);color:#fff;padding:74px 0;text-align:center;position:relative;overflow:hidden}
.cta-band h2{color:#fff;font-size:clamp(1.6rem,3.2vw,2.4rem);max-width:22ch;margin:0 auto 16px}
.cta-band h2 em{font-style:italic;color:var(--gold-soft)}
.cta-band p{color:rgba(255,255,255,.75);max-width:52ch;margin:0 auto 30px}
.cta-band .btn-row{justify-content:center}

/* article body */
.prose{max-width:68ch;margin:0 auto}
.prose p{color:var(--ink);margin-bottom:22px;font-size:1.06rem;line-height:1.75}
.prose h2{font-size:1.6rem;margin:46px 0 14px}
.prose h3{font-size:1.18rem;margin:32px 0 10px}
.prose ul,.prose ol{margin:0 0 22px 20px;color:var(--ink)}
.prose li{margin-bottom:9px;line-height:1.7}
.prose a{color:var(--navy);text-decoration:underline;text-decoration-color:var(--gold);text-underline-offset:3px}
.prose a:hover{color:var(--gold-ink)}
.prose blockquote{border-left:2px solid var(--gold);padding-left:22px;margin:32px 0;font-family:'Playfair Display',serif;font-style:italic;font-size:1.2rem;color:var(--navy)}
.article-meta{display:flex;gap:16px;flex-wrap:wrap;align-items:center;font-size:.8rem;color:rgba(255,255,255,.6);margin-top:18px}
.article-meta .pill{border:1px solid rgba(200,161,77,.5);color:var(--gold-soft);padding:4px 12px;border-radius:2px;letter-spacing:.08em;text-transform:uppercase;font-size:.68rem;font-weight:600}

/* article header image
   Between the page header and the first paragraph on the three field notes.
   It takes .prose's own 68ch measure rather than the full .wrap, so the photo
   and the text it introduces share one column; a 1152px image over a 680px
   text column reads as two unrelated things stacked.

   Plain .rv in the markup, never .rv-left or .rv-right. The reading pages only
   ever rise things vertically, and a header photo arriving sideways drags the
   eye across the page at the moment the reader is trying to start the first
   sentence. */
.article-media{max-width:68ch;margin:0 auto 40px;border-radius:3px;overflow:hidden;box-shadow:0 24px 48px rgba(11,31,77,.15)}
.article-media img{width:100%;height:auto;aspect-ratio:3/2;object-fit:cover}

/* insight cards */
.insight-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(300px,100%),1fr));gap:24px}
.insight{display:flex;flex-direction:column;background:var(--white);border:1px solid var(--hairline);border-radius:3px;padding:32px 28px;text-decoration:none;transition:transform .3s,border-color .3s,box-shadow .3s}
.insight:hover{transform:translateY(-5px);box-shadow:0 20px 44px rgba(11,31,77,.1)}
.insight .cat{font-size:.68rem;letter-spacing:.2em;text-transform:uppercase;color:var(--gold-ink);font-weight:600;margin-bottom:14px}
.insight:focus-visible{outline:2px solid var(--gold-ink);outline-offset:4px}
.insight h2,.insight h3{font-size:1.2rem;margin-bottom:10px}
.insight p{color:var(--muted);font-size:.93rem;flex:1}
.insight .read{margin-top:18px;font-size:.8rem;color:var(--muted)}

/* services index — the home page .svc card, used as a whole-card link */
a.svc{display:block;text-decoration:none;color:inherit}
a.svc h2{font-size:1.28rem;margin-bottom:12px}
a.svc .arrow{display:inline-block;margin-top:18px;color:var(--gold-ink);font-weight:600;font-size:.86rem}
a.svc:focus-visible{outline:2px solid var(--gold-ink);outline-offset:4px}

/* sectors — the photo panel and the "what matters most here" line */
.sector-media{margin-top:26px}
.sector-media img{width:100%;height:auto;aspect-ratio:4/3;object-fit:cover;border-radius:3px;box-shadow:0 24px 48px rgba(11,31,77,.15)}
.matters{border-left:2px solid var(--gold);padding:2px 0 2px 22px;margin-top:30px}
.matters .lbl{display:block;font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;color:var(--gold-ink);font-weight:600;margin-bottom:8px}
.matters p{font-family:'Playfair Display',Georgia,serif;font-style:italic;font-size:1.14rem;line-height:1.5;color:var(--navy)}

/* approach — the five step process, set out in full on the one page that owns it.
   The rail is the horizontal timeline on desktop and doubles as in-page nav; the
   steps below it carry the detail. The rail is hidden on small screens, where
   scrolling the steps is quicker than jumping between them. */
.flow-rail{display:grid;grid-template-columns:repeat(5,1fr);position:relative;margin-bottom:64px}
.flow-rail::before{content:"";position:absolute;top:27px;left:10%;right:10%;height:1px;background:linear-gradient(90deg,transparent,var(--gold) 12%,var(--gold) 88%,transparent)}
.flow-rail a{display:block;position:relative;z-index:2;text-align:center;text-decoration:none;padding:0 12px}
.flow-rail .step-hex{display:block;margin:0 auto 14px}
.flow-rail .nm{display:block;font-family:'Playfair Display',Georgia,serif;font-weight:600;font-size:1.02rem;color:var(--navy);transition:color .25s}
.flow-rail a:hover .nm,.flow-rail a:focus-visible .nm{color:var(--gold-ink)}
.flow-rail a:focus-visible{outline:2px solid var(--gold-ink);outline-offset:6px;border-radius:2px}
@media(max-width:900px){.flow-rail{display:none}}

.flow{list-style:none}
.flow-step{display:grid;grid-template-columns:64px 1fr;gap:30px;padding-bottom:46px;position:relative;scroll-margin-top:120px}
.flow-step::before{content:"";position:absolute;left:31px;top:66px;bottom:8px;width:1px;background:var(--hairline)}
.flow-step:last-child{padding-bottom:0}
.flow-step:last-child::before{display:none}
.flow-step .step-hex{display:block;margin:0 auto}
.flow-step h3{font-size:1.3rem;margin-bottom:10px}
.flow-step p{color:var(--muted);max-width:62ch}
@media(max-width:620px){.flow-step{gap:20px;padding-bottom:38px}}

/* section level link, for a home page section that defers to the page owning it */
.sec-link{margin-top:46px;text-align:center}
/* except in the home page story column, where it belongs under the text */
.story-copy .sec-link{margin-top:28px;text-align:left}
.sec-link a{display:inline-block;color:var(--gold-ink);font-weight:600;font-size:.9rem;letter-spacing:.02em;text-decoration:none;padding-bottom:3px;border-bottom:1px solid transparent;transition:border-color .25s}
.sec-link a:hover,.sec-link a:focus-visible{border-bottom-color:var(--gold-ink)}
/* on a dark band --gold-ink drops to 3.16:1, so the dark grounds take the
   light gold, which clears 8.7:1 there */
.band .sec-link a,.sustain .sec-link a,.cta .sec-link a,.cta-band .sec-link a{color:var(--gold-soft)}
.band .sec-link a:hover,.sustain .sec-link a:hover,.cta .sec-link a:hover,.cta-band .sec-link a:hover,
.band .sec-link a:focus-visible,.sustain .sec-link a:focus-visible,.cta .sec-link a:focus-visible,.cta-band .sec-link a:focus-visible{border-bottom-color:var(--gold-soft)}

/* buttons rendered as <button> rather than <a>, and the light-background ghost.
   .btn-ghost is white-on-dark by design, so it disappears on a paper section;
   .btn-outline is the same shape for light bands. */
button.btn{border:0;cursor:pointer;font-family:inherit}
.btn.btn-outline{border:1px solid var(--navy);color:var(--navy);background:transparent}
.btn.btn-outline:hover{border-color:var(--gold-ink);color:var(--gold-ink)}
.btn:focus-visible{outline:2px solid currentColor;outline-offset:3px}

/* readiness check — /project-check/ only. Eight fieldsets, then the result that
   js/project-check.js renders into the live region under the submit button. */
.check-form{max-width:820px}
.check-note{color:var(--muted);font-size:.9rem;max-width:62ch;margin-bottom:30px}
.qgroup{margin-bottom:44px}
.qgroup h2{font-size:1.45rem;margin-bottom:20px}
.q{border:1px solid var(--hairline);border-radius:3px;background:var(--white);padding:26px 28px;margin-bottom:16px}
.q legend{padding:0;font-family:'Playfair Display',Georgia,serif;font-weight:600;font-size:1.08rem;line-height:1.4;color:var(--navy);margin-bottom:18px}
.opts{display:flex;gap:10px;flex-wrap:wrap}
.opt{position:relative}
.opt input{position:absolute;top:50%;left:14px;width:1px;height:1px;opacity:0;pointer-events:none}
.opt label{display:inline-block;border:1px solid var(--hairline);border-radius:2px;background:var(--paper);padding:10px 24px;font-size:.88rem;font-weight:600;color:var(--muted);cursor:pointer;transition:background .2s,border-color .2s,color .2s}
.opt label:hover{border-color:var(--gold);color:var(--navy)}
.opt input:checked+label{background:var(--navy);border-color:var(--navy);color:#fff}
.opt input:focus-visible+label{outline:2px solid var(--gold-ink);outline-offset:3px}
.check-error{color:var(--error);font-weight:600;font-size:.92rem;margin-top:18px}
.check-error:empty{display:none}

.result{margin-top:32px}
.result:empty{display:none}
.result:not(:empty){border-top:1px solid var(--hairline);padding-top:34px}
.result .score{font-size:.78rem;letter-spacing:.2em;text-transform:uppercase;color:var(--muted);font-weight:600;margin-bottom:10px}
.result h2{font-size:clamp(1.6rem,3vw,2.2rem);margin-bottom:14px}
.result h3{margin:34px 0 8px}
.result p{color:var(--muted);margin-bottom:16px;max-width:66ch}
.result .hexlist{max-width:70ch;margin-bottom:6px}
.result .hexlist a{color:var(--navy);font-weight:600;text-decoration:underline;text-decoration-color:var(--gold);text-underline-offset:3px}
.result .hexlist a:hover{color:var(--gold-ink)}
.result p.result-frame{border-left:2px solid var(--gold);padding:2px 0 2px 20px;margin-top:26px;font-style:italic;color:var(--navy)}
.check-actions{display:flex;gap:14px;flex-wrap:wrap;align-items:center;margin-top:30px}
.check-actions .copy-state{font-size:.85rem;color:var(--muted);margin:0}
.check-actions .copy-state:empty{display:none}

/* footer columns */
.foot-cols{display:grid;grid-template-columns:repeat(3,minmax(140px,1fr));gap:40px}
.foot-h{display:block;font-size:.72rem;letter-spacing:.2em;text-transform:uppercase;color:var(--gold-soft);font-weight:600;margin-bottom:14px}
.foot-cols ul{list-style:none}
.foot-cols li{margin-bottom:9px}
.foot-cols a{color:rgba(255,255,255,.65);text-decoration:none;font-size:.85rem}
.foot-cols a:hover{color:#fff}
@media(max-width:700px){.foot-cols{grid-template-columns:1fr 1fr;gap:28px}}

/* focus — the floor, so nothing interactive can be reached without showing it.
   Components that already draw their own ring (.btn, .rel, .insight, a.svc,
   .flow-rail a, .opt label) out-specify this and keep theirs. --gold-ink is
   the light-ground colour; the dark bands swap back to --gold-soft, which
   clears 9:1 on navy. */
a:focus-visible,button:focus-visible,summary:focus-visible,
input:focus-visible,select:focus-visible,textarea:focus-visible{
  outline:2px solid var(--gold-ink);outline-offset:3px;border-radius:2px;
}
#nav a:focus-visible,.mobile-menu a:focus-visible,.drop a:focus-visible,
.burger:focus-visible,.page-header a:focus-visible,.hero a:focus-visible,
.band a:focus-visible,.sustain a:focus-visible,.cta a:focus-visible,
.cta-band a:focus-visible,footer a:focus-visible{
  outline-color:var(--gold-soft);
}

/* =========================================================
   MOTION (v3)

   Two rules govern everything below.

   One: transform and opacity only. Nothing here animates a width, a height, a
   top or a left, so every frame is composited and none of it costs layout.
   There is exactly one deliberate exception, the blur on .rv-blur, and it is
   argued for where it is defined.

   Two: one loop. Every scroll-linked effect on the site — the nav's compressed
   state, the nav hiding on the way down, and both parallax layers — is driven
   by the single requestAnimationFrame loop in js/main.js. Nothing in this file
   attaches its own listener, and neither does anything else in the project.

   Everything runs once per element. The hexagon watermarks at the end are the
   single exception, and they are cut outright under reduced motion.
   ========================================================= */

/* ---- mobile menu close control ---------------------------------------
   The burger lives inside #nav, which is a stacking context at z-index 100, so
   it paints under this overlay whatever z-index it carries: while the menu is
   open the burger is neither visible nor clickable. It is therefore taken out
   of the tab order for the duration, and this button stands in for it, placed
   on the burger's own coordinates so the control does not appear to jump.
   Closing hands focus straight back to the burger, exactly as Escape does. */
.burger.open{visibility:hidden}
.menu-close{
  position:absolute;z-index:2;
  top:calc((var(--nav-h) - 44px) / 2);right:24px;
  width:44px;height:44px;padding:10px;
  background:none;border:0;cursor:pointer;color:#fff;
}
.menu-close svg{width:100%;height:100%;display:block}
.menu-close:focus-visible{outline:2px solid var(--gold-soft);outline-offset:2px;border-radius:2px}

/* ---- hero, on first paint ---------------------------------------------
   Not .rv. .rv waits on the IntersectionObserver, which means it waits on
   js/main.js, so with the script blocked the whole hero used to sit at
   opacity 0 forever. These are plain CSS animations and owe the script
   nothing.

   The h1 climbs out from behind one edge as a single block. There is no
   opacity in the keyframe on purpose: before it starts, the text is parked
   below the edge and genuinely out of frame.

   This was three masks, one per line, each on its own delay. It cost more
   than it was worth. Every .ln was an overflow:hidden box with a transform
   animating inside it, so each became its own compositing layer holding
   Playfair at 73.6px, and all three rasterised during the same window the
   page was still parsing and running its script. On the 4x throttle a mid
   range Android sees that as blocking time, and this site's traffic is
   mostly mid range Android.

   One mask, one layer, one raster. The gesture is the same and the stagger
   the sequence actually reads from — eyebrow, then h1, then standfirst, then
   buttons — is untouched below.

   Dropping the per-line boxes also drops the authored line breaks, so the
   heading wraps naturally now and .hero h1's max-width is what governs it. */
   .hero .hero-title, not .hero-title: .hero h1 above sets margin-bottom:24px
   at 0-1-1 and would out-specify a lone class. The padding is what stops
   overflow:hidden clipping the descenders on the last line, and the margin
   gives that padding back so the gap below the heading is still 24px. */
.hero .hero-title{overflow:hidden;padding-bottom:.08em;margin-bottom:calc(24px - .08em)}
.hero .hero-title>span{display:block;transform:translate3d(0,100%,0);animation:lineRise 1.15s var(--ease-out) .5s forwards}
@keyframes lineRise{to{transform:translate3d(0,0,0)}}

.hero .rise{opacity:0;transform:translate3d(0,20px,0);animation:heroRise .9s var(--ease-out) forwards}
@keyframes heroRise{to{opacity:1;transform:translate3d(0,0,0)}}
.hero .eyebrow.rise{animation-delay:.34s}
.hero p.rise{animation-delay:.98s}
.hero .btn-row.rise{animation-delay:1.1s}

/* ---- reveal variants --------------------------------------------------
   .rv is still the default and still does most of the work. These are
   modifiers: they need .rv beside them, so js/main.js observes them without
   knowing they exist. They are declared here, below the v1 .rv rule, which is
   how their resting transforms win — and why each one has to restate the
   .in state, since .rv.in sits earlier in the file at equal weight.

   Where each is used, and why:
     .rv-left / .rv-right  two-column sections, each column entering from its
                           own side, so a split reads as two halves closing.
     .rv-scale             tight grids of small items (the three-door strip,
                           the sectors strip) where a sideways slide is busy.
     .rv-blur              full-bleed statement bands, where a line resolving
                           into focus is the point. */
.rv.rv-left{transform:translate3d(-40px,0,0)}
.rv.rv-right{transform:translate3d(40px,0,0)}
.rv.rv-scale{transform:scale(.94)}
.rv.rv-left.in,.rv.rv-right.in,.rv.rv-scale.in{transform:none}
/* The one property here that is not transform or opacity. filter is
   composited in every engine that ships prefers-reduced-motion, the radius is
   small enough not to cost a large blur pass, and it is on four elements in
   the whole site. It stays a deliberate exception rather than a habit. */
.rv.rv-blur{filter:blur(7px);transition:opacity .8s var(--ease-out),transform .8s var(--ease-out),filter .9s var(--ease-out)}
.rv.rv-blur.in{filter:blur(0)}

/* ---- image wipe -------------------------------------------------------
   Two counter-translated layers. The frame moves one way and the picture the
   same distance the other, so the window opens across an image that never
   itself moves. A clip-path animation reads identically and is what this
   replaces: clip-path is not a compositor property, and a full-bleed layer
   repainting on every frame of a one-second reveal is exactly the cost this
   pass was meant to avoid.

   Direction follows the layout — an image sitting in the right column wipes
   in from its right edge.

   Because a .wipe parks itself a full width outside the viewport, its own
   bounding box is useless to an IntersectionObserver. js/main.js watches the
   static parent instead and puts .in on the wipe. */
.wipe{display:block;overflow:hidden;border-radius:3px;transition:transform 1s var(--ease-out)}
.wipe>*{display:block;transition:transform 1s var(--ease-out)}
.wipe-right{transform:translate3d(101%,0,0)}
.wipe-right>*{transform:translate3d(-101%,0,0)}
.wipe-left{transform:translate3d(-101%,0,0)}
.wipe-left>*{transform:translate3d(101%,0,0)}
.wipe.in,.wipe.in>*{transform:translate3d(0,0,0)}
.wipe.d1,.wipe.d1>*{transition-delay:.12s}
.wipe.d2,.wipe.d2>*{transition-delay:.24s}
/* the home page band image, whose shadow moved off the <img> for the same
   reason the story image's did */
.band-media{border-radius:3px;box-shadow:0 30px 60px rgba(0,0,0,.4);line-height:0}

/* ---- sustainability band background -----------------------------------
   Three layers so no element carries two transforms at once: .sustain-bg
   takes the parallax drift written by the rAF loop, .wipe takes the reveal,
   .sustain-photo is the picture. The 80px bleed is the drift's headroom. */
/* No will-change. The rAF loop writes a transform here on every frame it runs,
   which promotes the layer for exactly as long as it is needed; the hint only
   pins it permanently, and a full-bleed layer that never comes down is the
   wrong thing to leave behind on a phone. */
.sustain-bg{position:absolute;inset:-80px 0;z-index:0}
.sustain-bg .wipe{position:absolute;inset:0;border-radius:0}
.sustain-photo{position:absolute;inset:0;background-size:cover;background-position:center}

/* ---- nav, hidden on the way down --------------------------------------
   A transform, so --nav-h is untouched and the compressed .scrolled state and
   the crumb bar's positioning carry on working exactly as they did. The crumb
   bar is a separate fixed element sitting at top:var(--nav-h), so clearing the
   viewport costs it its own height as well as the nav's.

   Never while the overlay is open — js/main.js will not set the class then —
   and never while focus is inside the header, or a keyboard reader would be
   tabbing through a bar that is not on screen. */
html.nav-hidden #nav{transform:translate3d(0,-100%,0)}
html.nav-hidden .crumb-bar{transform:translate3d(0,calc(-1 * (var(--nav-h) + var(--crumb-h))),0)}
html.nav-hidden #nav:focus-within,
html.nav-hidden #nav:focus-within ~ .crumb-bar{transform:none}

/* ---- /approach/, the process timeline ---------------------------------
   The gold rail is a gradient on a pseudo element, so it draws in with a
   scaleX from its left end. The five markers follow behind it on a stagger,
   with the one bit of overrun on the site: a marker arriving dead flat looks
   like a bug rather than a beat. */
.flow-rail::before{transform:scaleX(0);transform-origin:left center;transition:transform 1.1s var(--ease-out) .15s}
.flow-rail.in::before{transform:scaleX(1)}
.flow-rail a .step-hex{opacity:0;transform:scale(.4);transition:opacity .3s linear,transform .55s var(--ease-pop)}
.flow-rail a .nm{opacity:0;transition:opacity .4s linear}
.flow-rail.in a .step-hex,.flow-rail.in a .nm{opacity:1;transform:none}
.flow-rail.in a:nth-child(1) .step-hex,.flow-rail.in a:nth-child(1) .nm{transition-delay:.40s}
.flow-rail.in a:nth-child(2) .step-hex,.flow-rail.in a:nth-child(2) .nm{transition-delay:.52s}
.flow-rail.in a:nth-child(3) .step-hex,.flow-rail.in a:nth-child(3) .nm{transition-delay:.64s}
.flow-rail.in a:nth-child(4) .step-hex,.flow-rail.in a:nth-child(4) .nm{transition-delay:.76s}
.flow-rail.in a:nth-child(5) .step-hex,.flow-rail.in a:nth-child(5) .nm{transition-delay:.88s}
/* Under 900px the rail is display:none and there is nothing to draw. The steps
   under it are already a vertical list, each its own .rv, so the reveal there
   is simply one step arriving at a time as it scrolls past — the simpler
   sequential version, with the hexagon popping alongside its own step. */
.flow-step .step-hex{opacity:0;transform:scale(.6);transition:opacity .3s linear .1s,transform .5s var(--ease-pop) .1s}
.flow-step.in .step-hex{opacity:1;transform:none}

/* ---- card border sweep ------------------------------------------------
   The gold border arrives instead of switching on. It is a pseudo element
   scaled from the left rather than a border-color change, which keeps it a
   single composited transform, and it is bound to :focus-visible as well as
   :hover so it is not a pointer-only affordance. inset:-1px lands it exactly
   over the hairline border rather than 1px inside it. */
.svc,.rel,.insight{position:relative}
.svc::after,.rel::after,.insight::after{
  content:"";position:absolute;inset:-1px;pointer-events:none;
  border:1px solid var(--gold);border-radius:3px;
  transform:scaleX(0);transform-origin:left center;
  transition:transform .5s var(--ease-out);
}
.svc:hover::after,.svc:focus-visible::after,
.rel:hover::after,.rel:focus-visible::after,
.insight:hover::after,.insight:focus-visible::after{transform:scaleX(1)}

/* ---- page transitions -------------------------------------------------
   Native cross-document view transitions, opted into from CSS alone. A browser
   without support ignores the at-rule and navigates exactly as it does today.
   No polyfill, no script, no fallback to maintain. */
@view-transition{navigation:auto}
::view-transition-old(root),::view-transition-new(root){
  animation-duration:.26s;animation-timing-function:cubic-bezier(.4,0,.2,1);
}

/* ---- hexagon watermarks ----------------------------------------------
   The one thing on the site that repeats, and the stated exception to running
   once. Slow enough, and small enough, to read as the page breathing rather
   than as an animation; the four durations are deliberately unrelated so they
   never fall into step. Cut outright, not shortened, under reduced motion. */
/* Translate only, no rotation. A rotation on a vector mark forces it to be
   re-rastered rather than just re-composited, and this is the one animation on
   the site that never stops — it was showing up in total blocking time for a
   movement nobody can consciously see. The vertical drift alone reads the
   same. */
@keyframes hexDrift{
  from{transform:translate3d(0,0,0)}
  to{transform:translate3d(0,-26px,0)}
}
.hero-hex{animation:hexDrift 24s ease-in-out infinite alternate}
.cta-hex{animation:hexDrift 29s ease-in-out infinite alternate}
.page-header::after{animation:hexDrift 26s ease-in-out infinite alternate}
/* .cta-band had no watermark at all. It is the interior twin of the home page's
   .cta, so it takes the same mark from the same data URI the page header uses,
   rather than anything new. The .wrap needs lifting above it. */
.cta-band::after{
  content:"";position:absolute;left:-150px;top:-110px;width:420px;height:470px;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 110'%3E%3Cpath d='M50 4 L95 29 L95 81 L50 106 L5 81 L5 29 Z' fill='none' stroke='%23C8A14D' stroke-width='1.5'/%3E%3C/svg%3E") no-repeat center/contain;
  opacity:.12;pointer-events:none;
  animation:hexDrift 31s ease-in-out infinite alternate;
}
.cta-band .wrap{position:relative;z-index:2}

/* ---- reduced motion, for everything above ------------------------------
   This has to sit at the end of the file. The blanket block near the top
   collapses every duration to nothing, which is enough for anything that
   animates from a displaced state to a resting one — the hero lines and the
   hero fades land on their forwards fill instantly and read correctly.

   It is not enough for anything whose *resting* state is the displaced one.
   A .wipe parks a full width out of frame and waits for .in; a marker sits at
   scale(.4). Those have to be put back explicitly, and an override only lands
   if it comes after the rule it is overriding. A media query adds no
   specificity, so placing these next to the block at the top of the file —
   above every selector they name — would have silently done nothing.

   The hexagon drift is the one loop on the site, and it is cut outright here
   rather than shortened: at .001ms with a forwards fill it would otherwise
   snap to the end of its keyframe and sit there, rotated four degrees. */
@media (prefers-reduced-motion:reduce){
  .hero .rise,.hero .hero-title>span{opacity:1;transform:none;animation:none}
  .hero-wipe,.hero-photo{transform:none;animation:none}
  .wipe,.wipe>*{transform:none;transition:none}
  .rv.rv-left,.rv.rv-right,.rv.rv-scale{transform:none}
  .rv.rv-blur{filter:none;transition:none}
  .hero-hex,.cta-hex,.page-header::after,.cta-band::after{animation:none}
  .flow-rail::before,.flow-rail a .step-hex,.flow-rail a .nm,
  .flow-step .step-hex{opacity:1;transform:none;transition:none}
  .svc::after,.rel::after,.insight::after{transition:none}
  /* the nav stays put: a header that jumps out of the viewport on scroll is
     motion whether or not it is animated */
  #nav,.crumb-bar{transform:none !important}
  @view-transition{navigation:none}
  ::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none !important}
}
