/* =================================================================
   Sleek Title
   -----------------------------------------------------------------
   DARK PRIVATE OFFICE

   A full redesign. The previous build was a white page set in Source
   Serif 4, in the vocabulary of cws-code/module-5. Nothing of it
   survives now but the brand's two golds.

   The direction: near-black canvas, type-led rather than photo-led, and
   gold demoted from a decorative accent to a structural one. It marks
   position (rules, numerals, the process line) rather than ornamenting.

   ONE TYPEFACE
   Geist, for titles and body alike. There is no second family: metadata
   is set apart by case and tracking (.meta), not by a monospace. Buttons
   are white rather than gold, so gold now appears only in type accents
   and structural marks.

   THEME
   One theme, locked. The whole page is dark; no section inverts. This
   is the reason the redesign works with the client's logo set, which
   is drawn gold-and-silver for dark grounds.

   CARDS
   Almost none. Services carries them because a service is a discrete
   offer; Why is arches cut out of the ground. Everything else is rules,
   scale and space — the testimonials in particular, which were boxed and
   are now a spread, since a border around a quotation makes a review look
   like a feature tile.

   MOTION
   Carried over from the previous build and unchanged in principle:
   every effect is a one-shot, driven by two IntersectionObservers, with
   no animation library. The single exception is Our Approach, which pins
   to the viewport and scrubs its copy from the scroll position — a
   rAF-throttled scroll listener, the only one on the page. The whole
   layer collapses under prefers-reduced-motion, and nothing is ever
   hidden unless JavaScript ran.
   ================================================================= */

:root {
  /* Brand golds, preserved from the previous build. --gold is the
     accent on dark, which is now the entire page. --gold-deep is kept
     for the rare light surface: --gold at body size on white measures
     about 2.6:1 and fails WCAG AA. */
  /* Taken straight from sleek_logo_gold.svg's own gradient stops, so the
     accent is literally a colour out of the logo rather than an approximation
     of it. The rendered logo reads as a warm champagne gold (sampled median
     #fde395); the mustard #c9a227 this replaced did not match it.
       --gold      = the gradient's base stop
       --gold-lift = its lighter stop, used for hover
       --gold-deep = its deep stop, the only one dark enough for text on
                     white (5.09:1); currently unused, the page has no light
                     surface. */
  --gold: #f5d286;
  --gold-lift: #ffe7a7;
  --gold-deep: #926600;

  /* A solid fill needs a deeper gold than small text does. --gold is light
     because it has to carry hairline rules and 11px numerals on a near-black
     ground; spread across a whole button it washes out to cream. These two
     are the same base stop blended 40% and 20% toward --gold-deep, so they
     are still mixed only from the logo's own colours. Dark text on
     --gold-fill measures 8.6:1. */
  --gold-fill: #cda750;
  --gold-fill-lift: #e1bc6b;

  /* Off-black, never #000. The two raised values are for the few
     surfaces that lift off the page at all. */
  --void: #121215;
  --surface: #1a1a1e;
  --surface-2: #202025;

  /* The light ground. Off-white for the same reason --void is off-black, and
     the value the theme switch has always used for <body> — named here so the
     switch and the one section that is permanently light read from one
     place. */
  --paper: #f4f4f2;
  /* Ink for that ground, at the two weights the dark side keeps in --text and
     --text-2. Every html.theme-light rule in this file already spells these
     two values out by hand; new ones should use these. */
  --ink: #121215;
  --ink-2: rgba(18, 18, 21, 0.72);
  /* 0.62, not the 0.58 that matches the dark side's --text-3. The two grounds
     are not symmetrical: 0.58 ink on paper measures 4.43:1, which misses the
     4.5 small text is held to, where 0.55 white on --void clears it easily. */
  --ink-3: rgba(18, 18, 21, 0.62);

  --line: rgba(244, 244, 242, 0.10);
  --line-strong: rgba(244, 244, 242, 0.20);
  /* The 3px rules. Dimmer than --line-strong on purpose: see .hero-proof. */
  --rule-heavy: rgba(244, 244, 242, 0.12);

  --text: #f4f4f2;
  --text-2: rgba(244, 244, 242, 0.68);
  --text-3: rgba(244, 244, 242, 0.55);

  /* One family across the whole page. Titles and body are both Geist. A
     Bodoni Moda display face was tried against the logo's Didone wordmark
     and reverted: see the README. */
  --sans: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  --max-w: 1440px;
  --pad-x: clamp(20px, 5vw, 68px);

  /* One easing for the whole page. Everything decelerates the same
     way, which is most of what makes a motion layer read as a system. */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  /* The theme crossfade gets its own curve and duration. --ease is an
     expo-out: roughly 80% of the change lands in the first quarter of the
     time, which is right for something arriving on screen but reads as a
     lurch-then-crawl when it is a whole viewport changing colour. This one is
     near-linear through the middle with soft ends, so the ground moves at an
     even rate and only settles at the edges. */
  --ease-theme: cubic-bezier(0.4, 0, 0.3, 1);
  --theme-dur: 1.15s;
  --nav-h: 128px;
  --hero-pad-b: clamp(40px, 5vw, 72px);

  /* Control height. Both .btn and the rail's round arrows read from this, so
     a CTA and the arrow buttons standing next to it in the About section
     cannot drift out of alignment. */
  --btn-h: 56px;
}

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

html.theme-light body { background-color: var(--paper); }

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 20px);
  background: var(--void);
}

body {
  margin: 0;
  font-family: var(--sans);
  /* One body size for the whole page. Every prose rule that used to set its
     own value (15px in the steps, 15.5px in the rows and Why items, 16px in
     the Why note) now inherits this instead, so paragraphs read at the same
     size wherever they appear. Headings, labels, nav and controls keep their
     own sizes — they are not body copy. */
  font-size: 18px;
  line-height: 1.65;
  color: var(--text-2);
  /* The page ground, and the thing the theme switch actually animates. Every
     section that does not set its own background sits on this. */
  background-color: var(--void);
  transition: background-color var(--theme-dur) var(--ease-theme);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4 {
  font-family: var(--sans);
  color: var(--text);
  font-weight: 500;
  line-height: 1.1;
  margin: 0;
  letter-spacing: -0.03em;
  text-wrap: balance;
}

p { margin: 0 0 1.15em; text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

a { color: inherit; }

img { max-width: 100%; display: block; }

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

.container {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: var(--container-pad-t, 28px) var(--pad-x) 0;
}

/* A token so a section that needs to depart from the page's interval can say
   so against it rather than against a number copied out of this line. */
:root { --section-pad: clamp(88px, 12vw, 152px); }
.section { padding: var(--section-pad) 0; }

/* ---------- A section on paper ----------
   A full-bleed white band inside the dark page. Used on /about, /services and
   /testimonials.

   This is NOT the page's data-theme-light switch, and the difference is worth
   spelling out because the note over .about calls "a white box scrolling into
   view" the very thing that mechanism exists to avoid. Two reasons it is the
   right tool here and the switch is not:

   - The switch turns the WHOLE page ground, and it starts turning it when the
     lit section's top crosses 85% of the window. These sections open about a
     screen down a page whose hero is ~780px tall, so the ground would go white
     with the hero still filling the screen. .page-hero has no light ink, so
     the headline would go white on white. The homepage pays for the switch
     with an html.theme-light rule for every component its chapter crosses;
     here that bill is a hero, a nav and a band, for one section.
   - There, light is a chapter the reader travels through. Here it is a panel
     that has to separate itself from the dark either side of it. A hard edge
     is what does that, and it is also what closes off the hero: the
     photograph's bottom runs into the white rather than into a drawn rule.

   The ink below is spelled out rather than inherited, because the page around
   it stays dark and the colour tokens are the dark set. Anything added to one
   of these sections that sets its own colour needs a line here too — if new
   text shows up invisible on white, this is the list that is missing it. */
.section--paper {
  background: var(--paper);
  /* Edge to edge on its own: .section is a full-width block and only
     .container inside it sits on the measure. */
  color: var(--ink-2);
}
.section--paper h1,
.section--paper h2,
.section--paper h3,
.section--paper .h-section,
.section--paper .feature-title,
.section--paper .service-title,
.section--paper .tstm-name,
.section--paper .tstm-quote { color: var(--ink); }
.section--paper .proof-value { color: var(--ink); }
.section--paper .proof-label { color: var(--ink-3); }
.section--paper p,
.section--paper .lede,
.section--paper .feature-body,
.section--paper .service-body,
.section--paper .focus-points li { color: var(--ink-2); }
.section--paper .meta,
.section--paper .tstm-role { color: var(--ink-3); }
/* --gold carries hairlines and small text on near-black and washes out on
   paper; --gold-deep is the stop dark enough to read on it. Same swap every
   html.theme-light rule in this file makes. */
.section--paper .eyebrow,
.section--paper .service-icon { color: var(--gold-deep); }
/* .btn--primary is an off-white fill with dark text. On paper that is white
   on white. Every action inside one of these sections uses the gold fill
   instead, which is the page's one accent and measures 8.6:1 against the dark
   text it carries, on either ground. */
.section--paper .btn--primary {
  background: var(--gold-fill);
  /* The button's own fill, so it reads as one shape rather than as a gold
     field inside a darker gold ring.

     ⚠️ ON THE RECORD, because this was a deliberate reversal: the border used
     to be --gold-deep. --gold-fill against paper measures 2.06:1, so with the
     edge gone the button's boundary is under the 3:1 WCAG asks of a control,
     and on a white section the shape is carried by the fill alone. The label
     is unaffected - dark text on this fill is 8.2:1 either way - and the
     button is never the only route to anything on these pages. If the ring
     has to come back, --gold-deep at 4.8:1 is the value that was there. */
  border-color: var(--gold-fill);
  color: var(--void);
}
.section--paper .btn--primary:hover { background: var(--gold-fill-lift); border-color: var(--gold-fill-lift); }
.section--paper .btn--secondary {
  color: var(--ink);
  border-color: rgba(18, 18, 21, 0.28);
}
.section--paper .btn--secondary:hover {
  border-color: var(--ink);
  background: rgba(18, 18, 21, 0.05);
}

/* ---------- The column grid, drawn ----------
   Five hairlines on the container's own measure — its two edges and the three
   divisions of four equal columns. Used by Why Sleek only: one section stands
   on a visible grid, which reads as that section's structure, where a grid
   running the length of the page would read as wallpaper.

   The overlay is a flex row only so its inner box can take the container's
   exact content width: `max-width` on the item plus `margin-inline` for the
   gutter lands on the same measure as .container at every width, including
   below --max-w where the gutter is all there is. Absolutely positioned
   children resolve against the padding box, so a padded box here would put
   the outer lines in the gutter instead of on the text edge. */
.grid-lines {
  position: absolute;
  inset: 0;
  /* Behind the section's own copy, which is lifted to 1 below. */
  z-index: 0;
  pointer-events: none;
  display: flex;
  justify-content: center;
  /* Both ends dissolve instead of stopping. A hairline that ends on a hard
     edge draws a horizontal line the eye reads as a border of the section;
     faded out, the grid belongs to the page rather than boxing the section
     off from its neighbours. The stops are generous because the section's
     own padding is large — the lines are at full strength only across the
     cards themselves. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 22%, #000 78%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 22%, #000 78%, transparent 100%);
}
.grid-lines-inner {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  max-width: calc(var(--max-w) - var(--pad-x) * 2);
  margin-inline: var(--pad-x);
}
.grid-lines i {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  /* Fainter than --line: that token is for a border a reader is meant to
     register as an edge, and these are meant to be felt rather than read. */
  background: rgba(244, 244, 242, 0.055);
  /* On the theme's clock, like every other ink on the page. */
  transition: background-color var(--theme-dur) var(--ease-theme);
}
/* The two outer lines are the measure's own edges, which the outer cards
   already stand on. The three inner ones run down the middle of the gaps
   between cards, which is NOT the quarter points: with four columns and three
   gutters of --why-gap, a column is (W - 3g) / 4, so the first gutter's centre
   lands a quarter of a gutter short of 25%, the middle one exactly on 50%,
   and the last a quarter past 75%. Each is nudged back by half its own width
   so the line is centred on that point rather than starting at it. */
.grid-lines i:nth-child(1) { left: 0; }
.grid-lines i:nth-child(2) { left: calc(25% - var(--why-gap) / 4); transform: translateX(-50%); }
.grid-lines i:nth-child(3) { left: 50%; transform: translateX(-50%); }
.grid-lines i:nth-child(4) { left: calc(75% + var(--why-gap) / 4); transform: translateX(-50%); }
/* Pulled back onto the measure: at left:100% the line would sit one pixel
   outside the content edge the first line sits on. */
.grid-lines i:nth-child(5) { left: 100%; margin-left: -1px; }
html.theme-light .grid-lines i { background: rgba(18, 18, 21, 0.07); }

/* ---------- Type utilities ---------- */

/* Indices, labels and units. Same family as everything else on the page:
   the uppercase and the wide tracking are what set it apart from prose,
   not a third typeface. */
.meta {
  font-family: var(--sans);
  font-size: 11.5px;
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-3);
}

/* Section and column labels. Deliberately NOT .meta: an eyebrow here is body
   type in sentence case, and the gold is the only thing setting it apart.
   .meta stays as it is for what it is actually for — indices, numerals and
   the small print, where the tracked-out caps are the point. */
.eyebrow {
  margin: 0;
  font-family: var(--sans);
  font-size: 18px;
  font-weight: 400;
  line-height: 1.65;
  letter-spacing: normal;
  text-transform: none;
  color: var(--gold);
}

/* Section label. Deliberately rare: three on the page, not one per
   section, so the rhythm does not template itself. */
.h-display {
  /* Uppercase and larger. Caps are roughly 25% wider than sentence case at
     the same size, so this headline sets on three lines; holding it to two
     would mean dropping to about 54px, which is smaller than it was. */
  text-transform: uppercase;
  font-size: clamp(30px, 4.6vw, 68px);
  letter-spacing: -0.022em;
  line-height: 1.02;
}
.h-section {
  font-size: clamp(30px, 3.9vw, 52px);
  letter-spacing: -0.032em;
}

.lede {
  max-width: 620px;
  font-size: 18px;
  color: var(--text-2);
  margin-top: 24px;
}

/* ---------- Scroll reveal ----------
   Inert until <html> carries .js-reveal, which the inline head script
   adds. Without JavaScript nothing is hidden, so the page degrades to
   fully visible rather than blank. */
.js-reveal .reveal {
  opacity: 0;
  transform: translateY(22px);
  transition:
    opacity 0.8s var(--ease),
    transform 0.8s var(--ease);
  transition-delay: calc(var(--i, 0) * 70ms);
}
.js-reveal .reveal.is-in { opacity: 1; transform: none; }

/* ---------- Buttons ---------- */

/* .btn is used on both <a> and <button>. CTAs that point at pages
   which do not exist yet render as <button> (see components/Cta.astro),
   so the base rule neutralises UA button styling and pins line-height,
   or the two come out different heights. */
/* Three tiers: primary is gold, secondary is a white fill, tertiary is a
   white stroke on nothing. Only primary carries an arrow. */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  /* Same type as the navigation links: sans, 16px, sentence case. */
  font-family: var(--sans);
  font-size: 16px;
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.005em;
  height: var(--btn-h);
  padding: 0 26px;
  /* Pill. The page's one shape exception: buttons are fully round, every
     panel and image stays at 3px. */
  border-radius: 999px;
  text-decoration: none;
  background: transparent;
  border: 1px solid transparent;
  cursor: pointer;
  transition: padding 0.4s var(--ease);
}
/* Hover changes colour only. No lift, no press, no size change, and the
   arrow does not travel: the button must not shift under the cursor. */

/* Only the primary button carries an arrow, and on hover the arrow and the
   label trade sides.

   The circle is absolutely positioned rather than laid out in the flex row,
   because that is what makes the swap animate. `left` resolves against the
   button (the positioned ancestor), so it can travel the button's full width
   as a single transition; a `flex-direction: row-reverse` or an `order` swap
   would jump instantly, and a `transform` percentage would resolve against
   the 52px circle rather than the button.

   The button is 56px tall with a 1px border, so its padding box is 54px.
   1px of inset there plus the border puts the circle exactly 2px from the
   button's outer edge, top and bottom, and a 52px circle fills the rest.

   The two padding values sum to the same total in both states (92px), so the
   button does not change width while the contents move. */
.btn--primary { padding: 0 66px 0 26px; }
.btn--primary:hover { padding: 0 26px 0 66px; }
.btn-icon {
  position: absolute;
  top: 1px;
  bottom: 1px;
  left: calc(100% - 53px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  border-radius: 50%;
  transition: left 0.4s var(--ease);
}
.btn--primary:hover .btn-icon { left: 1px; }
.btn-arrow { width: 18px; height: 18px; }

.btn--primary {
  background: var(--text);
  color: var(--void);
  border-color: var(--text);
}
.btn--primary .btn-icon { background: var(--void); color: var(--text); }

/* Brand-gold primary. Hero only: in the header the white button reads
   better against the gold logo sitting beside it. */
.btn--brand {
  background: var(--gold-fill);
  color: var(--void);
  border-color: var(--gold-fill);
}
.btn--brand .btn-icon { background: var(--void); color: var(--text); }

.btn--secondary {
  background: transparent;
  color: var(--text);
  border-color: rgba(244, 244, 242, 0.45);
}
.btn--secondary {
  transition: border-color 0.3s var(--ease), background-color 0.3s var(--ease);
}
.btn--secondary:hover { border-color: var(--text); background: rgba(244, 244, 242, 0.07); }

/* Text-weight CTA for inside a row, where a bordered button would
   compete with the row itself. The arrow travels on hover. */
.btn--quiet {
  height: auto;
  padding: 0;
  border: 0;
  border-radius: 0; /* text link, not a control */
  color: var(--gold);
}
.btn--quiet { transition: color 0.3s var(--ease); }
.btn--quiet:hover { color: var(--gold-lift); }
/* The arrow travels on hover — every quiet link, wherever it sits. Both of
   them stretch a ::before over their whole card, so hovering anywhere in the
   box hovers the link and the arrow moves with the rest of the card's state
   rather than only when the pointer finds the words. */
.btn--quiet::after {
  content: "\2192";
  font-size: 13px;
  transition: transform 0.3s var(--ease);
}
.btn--quiet:hover::after { transform: translateX(4px); }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  align-items: center;
}
.btn-row--center { justify-content: center; }

/* ---------- Nav ---------- */

/* The header is part of the hero, not a bar above it: absolutely
   positioned over the top of the page with no background, no blur and no
   border, so the hero photograph runs unbroken behind it. It scrolls away
   with the hero rather than sticking, which is the only way a header with
   no background of its own stays readable. */
.nav {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  background: none;
  border: 0;
}
.nav-inner {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: 28px var(--pad-x) 0;
  height: var(--nav-h);
  /* 1fr auto 1fr, not auto 1fr auto. The logo and the action buttons are
     different widths, so a middle track that merely absorbs the slack sits
     off-centre; equal flexible tracks either side pin the link list to the
     centre of the bar itself. */
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 24px;
}

/* The client's lockup, used as supplied: mark, wordmark and tagline in
   one SVG, uncropped and unrecomposed. It is close to square, so the
   header is sized around the artwork rather than the artwork being cut
   down to fit a shorter bar. */
.brand {
  display: flex;
  align-items: center;
  text-decoration: none;
  flex: none;
}
.brand-logo {
  height: 102px;
  width: auto;
}

/* No box of its own on desktop: the list and the actions drop through to
   .nav-inner's grid. Below 900px this is overridden and it becomes the
   stacked drawer. */
.nav-right { display: contents; }
.nav-actions { display: flex; align-items: center; justify-content: flex-end; gap: 10px; }
.nav-links {
  list-style: none;
  display: flex;
  align-items: center;
  justify-self: center;
  gap: 44px;
  margin: 0;
  padding: 0;
}
/* Sentence case at 16px. Dropped out of mono and off the wide tracking
   with it: 0.16em exists to open up all-caps, and on sentence case at this
   size it just looks loose. */
.nav-links a {
  position: relative;
  font-family: var(--sans);
  font-size: 16px;
  font-weight: 400;
  letter-spacing: -0.005em;
  color: var(--text-2);
  text-decoration: none;
  padding: 5px 0;
  cursor: pointer;
  transition: color 0.3s var(--ease);
}
.nav-links li > *::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.35s var(--ease);
}
.nav-links li > *:hover { color: var(--text); }
.nav-links li > *:hover::after { transform: scaleX(1); }

/* Nav items whose page has not been built yet. Same type, no affordance. */
.nav-cta { flex: none; }   /* height inherited: 56px */

.nav-toggle {
  display: none;
  background: none;
  border: 0;
  padding: 8px;
  cursor: pointer;
  color: var(--text);
}
.nav-toggle svg { width: 24px; height: 24px; }

/* ---------- Hero ---------- */

/* The section is taller than the viewport on purpose: .hero-inner takes
   the full 100dvh, and the proof strip sits underneath it, below the fold.
   Scrolling past the headline is what reveals the numbers. */
.hero {
  position: relative;
  overflow: hidden;
}
/* Bottom fade into the proof band. .hero-scrim cannot do this job: it sits
   under .hero-inner, so it darkens the facade but not the cut-out figure,
   and the figure's feet would still end on a hard horizontal edge. As the
   last child of .hero this paints over both, and both it and .hero-inner
   have z-index auto, so DOM order is enough — no stacking context needed. */
.hero::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: clamp(90px, 16vh, 200px);
  background: linear-gradient(180deg, rgba(18, 18, 21, 0) 0%, rgba(18, 18, 21, 0.72) 58%, var(--void) 100%);
  pointer-events: none;
}
.hero-media {
  position: absolute;
  inset: 0;
  background-image: url('/brand/hero-facade.jpg');
  background-size: cover;
  background-position: 50% 42%;
  /* The facade is a cool blue; pulled most of the way to neutral so it
     sits in the black-and-gold palette instead of fighting it, and lifted
     rather than crushed so the hero reads lighter than the old one. */
  filter: grayscale(0.62) brightness(1.16) contrast(0.94);
  /* One-shot establishing move as the copy arrives. Not a loop, not
     tied to scroll. */
  animation: heroSettle 2s var(--ease) both;
}
/* The photograph is a texture here, not the subject: pushed well back
   so the type carries the page. Also hides that it is generic stock. */
.hero-scrim {
  position: absolute;
  inset: 0;
  /* Lighter than the previous hero overall, but still weighted to the
     left, where the headline sits. The right stays open so the facade
     and the figure read. */
  background:
    linear-gradient(180deg, rgba(18,18,21,0.78) 0%, rgba(18,18,21,0.30) 40%, rgba(18,18,21,0.66) 80%, var(--void) 100%),
    linear-gradient(90deg, rgba(18,18,21,0.90) 0%, rgba(18,18,21,0.58) 46%, rgba(18,18,21,0.16) 100%);
}
.hero-inner {
  position: relative;
  width: 100%;
  max-width: var(--max-w);
  margin: 0 auto;
  min-height: 100dvh;
  padding: calc(var(--nav-h) + clamp(16px, 3vw, 44px)) var(--pad-x) var(--hero-pad-b);
  display: grid;
  grid-template-columns: 1.06fr 0.94fr;
  grid-template-areas: 'copy figure';
  align-items: end;
  /* The figure stays pinned to the bottom; the copy is centred in the
     remaining height so it does not crowd the fold. */
  column-gap: clamp(20px, 3vw, 48px);
}
.hero-copy {
  grid-area: copy;
  min-width: 0;
  align-self: center;
  padding-bottom: clamp(24px, 6vh, 76px);
}
.hero h1 { max-width: 19ch; margin-bottom: 30px; }

/* Cut-out figure, bottom-aligned so he stands on the proof rule. The mask
   fades his cropped waist into the hero instead of ending on a hard
   horizontal edge, which is what would give the composite away. */
.hero-figure {
  grid-area: figure;
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  min-width: 0;
  align-self: end;
}
/* Full-body figure: he stands on the bottom edge of the section rather
   than floating in it, so the negative margin cancels the hero's bottom
   padding. No mask on the feet - the previous figure was cropped at the
   waist and needed one, this one is a complete standing figure and a fade
   would just amputate the shoes. The scrim is at its darkest down there,
   so the base grounds itself. */
/* No bottom fade: the figure is cropped square at the fold. The negative
   margin cancels the hero's bottom padding so that cut lands exactly on the
   100dvh line rather than floating above it. */
.hero-figure {
  margin-bottom: calc(var(--hero-pad-b) * -1);
}
.hero-figure img {
  width: auto;
  max-width: 100%;
  max-height: min(84vh, 880px);
  object-fit: contain;
  object-position: bottom;
  animation: figureIn 1.1s var(--ease) 0.35s both;
}
@keyframes figureIn {
  from { opacity: 0; transform: translateY(24px); }
  to   { opacity: 1; transform: none; }
}
.hero-sub {
  max-width: 46ch;
  font-size: 18px;
  /* Deliberately brighter than --text-2 used elsewhere. This paragraph sits
     directly on the photograph, and over the brightest patch of the facade
     --text-2 measures 3.89:1, under AA. At 0.88 the worst pixel clears it. */
  color: rgba(244, 244, 242, 0.88);
  margin-bottom: 34px;
}

/* Proof points sit on a gold rule at the very bottom of the hero, so
   the first thing below the fold is already the next section. */
/* One hairline grid, cells flush against each other. The borders are split
   between container and cell — container takes top and left, each cell takes
   right and bottom — so shared edges are drawn exactly once at 1px instead of
   doubling to 2px. That split is layout-agnostic: it stays correct when the
   grid folds to 2x2 and to a single column. */
/* The proof row. No cell strokes at all — the only rule here is the 3px one
   above it, which separates this row from the service cards and carries 64px
   of air on each side of itself. margin-top is the air above the rule,
   padding-top the air below it. */
.hero-proof {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-template-rows: auto auto auto;
  /* A real column gap rather than padding inside each cell. With padding the
     fourth column's content stopped short of the page's right edge while the
     3px rule above ran the full width, which is what made the row look
     off-axis. row-gap stays 0: .proof is a row subgrid, so any row gap here
     is also applied inside every cell. */
  column-gap: clamp(24px, 3vw, 48px);
  row-gap: 0;
  margin-top: 64px;
  padding-top: 64px;
  /* --line-strong (0.20) read as a bright bar across the page at 3px: at that
     weight the rule wants less ink than a 1px hairline does, not more. This
     is the same white held at 0.12, which is heavy enough to divide and dark
     enough to sit back. Shared with .section-rule below, which is the same
     rule doing the same job at the foot of the reviews. */
  border-top: 3px solid var(--rule-heavy);
}
/* Subgrid so the gold bars and labels sit on shared baselines. Every value is
   two lines by construction, but this also holds the row together if one of
   them wraps to a third at an awkward width. */
.proof {
  position: relative; /* anchors the .proof-cta stretched hit area */
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3;
  justify-items: start;
  align-content: start;
  min-width: 0;
  /* No strokes and no padding: the grid's column gap does the separating, so
     the first cell starts on the page's left measure and the last one ends on
     its right. */
  padding: 0;
}
/* Sized for short phrases, not single numerals: at the old 82px display size
   "Residential + Commercial" wrapped to three lines and dwarfed the section
   it was introducing. Held below .h-section so the strip reads as supporting
   proof rather than as a heading, and narrowed again for the fourth column. */
.proof-value {
  font-size: clamp(24px, 2.3vw, 32px);
  font-weight: 500;
  letter-spacing: -0.025em;
  color: var(--text);
  line-height: 1.12;
}
/* Each line its own block: the two-line break is authored in the data, not
   left to the wrap point, so all four columns break at the same place. */
.proof-value span {
  display: block;
}

/* Sentence case, not the uppercase .meta treatment: at four cells across,
   tracked-out caps under a two-line title made every box read as a header
   block with no body to it. */
.proof-label {
  margin: 14px 0 0;
  line-height: 1.45;
  color: var(--text-3);
}

/* The CTA carries the whole cell: ::before stretches over the box so the
   entire card is the hit area, while the visible target stays a text link.
   It is pinned to the bottom of the cell so the four line up regardless of
   how tall each label sets. */
.proof-cta {
  margin-top: clamp(28px, 3vw, 44px);
  align-self: end;
  /* .btn is position:relative for the primary variant's absolutely positioned
     arrow circle. The quiet variant has no icon, so this can go back to static
     — which is what makes ::before below resolve against .proof and cover the
     whole cell rather than just the link's own box. */
  position: static;
}
.proof-cta::before {
  content: "";
  position: absolute;
  inset: 0;
}
/* The travel on the arrow is the base .btn--quiet behaviour; ::before above
   is what makes the whole cell trigger it. */

.fade-in { animation: fadeUp 0.9s var(--ease) both; }
.fade-in--2 { animation-delay: 0.1s; }
.fade-in--3 { animation-delay: 0.2s; }
.fade-in--4 { animation-delay: 0.32s; }
.fade-in--5 { animation-delay: 0.44s; }

@keyframes fadeUp {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: none; }
}
@keyframes heroSettle {
  from { transform: scale(1.08); }
  to   { transform: scale(1); }
}

/* ---------- Who we are ---------- */

/* 100vh split: a column of copy on the left, photograph on the right. Held to
   the page's own measure rather than run edge to edge — same max-width and
   gutter as .container, so it lines up with every other section above and
   below it. */
.about {
  /* One height token drives the cards and the left column both, so "match the
     card height" cannot drift apart as one or the other is tuned. */
  --card-h: min(66vh, 520px);

  /* The left gutter the heading and the copy column line up on. It is the
     page's own gutter, plus whatever empty page sits outside --max-w once the
     viewport is wider than it. Deliberately a percentage rather than vw:
     50% resolves against this element's width, which excludes the scrollbar,
     where 100vw would include it and overshoot the right edge by its width. */
  --gutter-l: max(var(--pad-x), calc(50% - var(--max-w) / 2 + var(--pad-x)));

  display: flex;
  flex-direction: column;
  justify-content: center;
  /* The section's only gap: it sits between the heading and the body, so it
     is the breathing room under the title. */
  gap: clamp(96px, 10vh, 128px);
  min-height: 100dvh;
  /* Full bleed. The measure moves onto the children instead, so the rail can
     run all the way to the right edge of the page while the heading and the
     copy column stay on the same line as every other section. */
  padding: clamp(40px, 6vh, 80px) 0;
}
.about-body {
  display: grid;
  /* Was 32%, which now measures against the bled width and would widen the
     copy column on anything above --max-w. This tracks the old value at every
     width the design is checked at, and stops growing past it. */
  grid-template-columns: clamp(280px, 29vw, 420px) 1fr;
  gap: 24px;
  padding-left: var(--gutter-l);
}

/* Sits on the page's own ground, so it inherits the page's colours — no ink
   overrides, and .btn--secondary works here as it does everywhere else. No
   horizontal padding either: the copy aligns flush with the section gutter,
   which is what keeps it in line with the proof grid above. The 24px column
   gap is doing the separating from the photograph. */
/* Matched to the cards beside it, not to the section: the column and the rail
   read as one band only if they start and end on the same two lines. */
.about-panel {
  height: var(--card-h);
  display: flex;
  flex-direction: column;
  /* Eyebrow at the top, everything else at the foot. */
  justify-content: space-between;
  padding: 0;
}
.about-panel-bottom {
  display: flex;
  flex-direction: column;
  gap: 48px;
}
.about-panel-copy {
  display: flex;
  flex-direction: column;
  gap: 18px;
}
/* Spans both columns, so it keeps the full .h-section size the rest of the
   page's section headings use. */
.about-title {
  /* Left margin rather than padding: max-width is a border-box measure here,
     so padding would eat into the 24ch measure instead of offsetting it. */
  margin: 0 var(--pad-x) 0 var(--gutter-l);
  max-width: 24ch;
}

/* CTA left, rail arrows right, both on the section's bottom edge. */
.about-panel-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
}
.rail-nav { display: flex; gap: 8px; }
.rail-btn {
  /* Matched to the CTA beside it: both are --btn-h tall, so the pair sits on
     the same cap line as "Meet the Team" instead of floating small next to it. */
  width: var(--btn-h);
  height: var(--btn-h);
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  background: none;
  color: var(--text);
  cursor: pointer;
  /* colour is on the theme's clock, the rest on hover's. border-color is
     driven by both and can only carry one duration — hover wins, since it is
     the interactive one. */
  transition:
    border-color 0.3s var(--ease),
    background-color 0.3s var(--ease),
    opacity 0.3s var(--ease),
    color var(--theme-dur) var(--ease-theme);
}
.rail-btn svg { width: 18px; height: 18px; }
.rail-btn:hover:not(:disabled) { border-color: var(--gold); color: var(--gold); }
/* Disabled at the ends of the track rather than hidden, so the control pair
   does not reflow the row it sits in. */
.rail-btn:disabled { opacity: 0.3; cursor: default; }
/* No-op at this column width, where the measure is already narrower than
   this. It earns its place once the split stacks and the panel runs full
   width. */
.about-panel p { max-width: 60ch; }

/* ---------- Team rail ----------
   A horizontal scroller, not a carousel: the cards live in a real overflow
   container, so a trackpad, a touch swipe and the arrow buttons all drive the
   same thing and the script only has to call scrollBy. It never advances on
   its own.

   The negative right margin pulls the rail out past the section's gutter so
   the track runs off the edge of the page rather than stopping inside it. It
   is var(--pad-x) rather than a vw calculation on purpose: at every viewport
   up to --max-w that lands exactly on the viewport edge, and it cannot
   disagree with the scrollbar the way 100vw does. */
/* The rail is a flex container purely to centre the track vertically. Do not
   be tempted back to a percentage height on the cards: the grid row is sized
   by its content, so a percentage there has nothing definite to resolve
   against, the cards fall back to the image's intrinsic 1080px, and the
   "100dvh" section silently becomes 1080px tall.

   No negative margin any more: .about is full-bleed and .about-body is padded
   on the left only, so this column's right edge already is the page's. */
.team-rail {
  display: flex;
  align-items: center;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -ms-overflow-style: none;
  overscroll-behavior-x: contain;
}
.team-rail::-webkit-scrollbar { display: none; }
.team-rail:focus-visible { outline: 2px solid var(--gold); outline-offset: 4px; }

.team-track {
  display: flex;
  align-items: center;
  /* Never shrink to the rail's width: the track is as wide as its cards, and
     that overflow is what there is to scroll. */
  flex: 0 0 auto;
  min-width: max-content;
  gap: 16px;
  margin: 0;
  /* No trailing gutter: the last card finishes flush with the page edge, the
     same way the cards meet it while scrolling. */
  padding: 0;
  list-style: none;
}
.team-card {
  position: relative;
  flex: 0 0 auto;
  height: var(--card-h);
  aspect-ratio: 2 / 3;
  overflow: hidden;
  border-radius: 3px;
  scroll-snap-align: start;
  /* Silhouettes stand in until real staff photography arrives, so the card's
     own ground is all there is behind the figure. It lifts very slightly
     toward the top, where the head sits — flat, the figure floats on nothing.
     --surface-2 rather than --surface so the card reads as a lighter shade of
     the section ground, not a hole in it. Drop the gradient when photos land. */
  background-color: var(--surface-2);
  background-image: radial-gradient(120% 62% at 50% 22%, rgba(244, 244, 242, 0.05), rgba(244, 244, 242, 0) 72%);
}
/* The gold frame. Inset 12px from the left, top and right, open at the
   bottom, and masked so the two side strokes fade out before they reach the
   name plate — a frame that closes itself would box the name in. Painted
   last in the card, so it sits over the figure and the blurred plate. */
.team-card::after {
  content: "";
  position: absolute;
  inset: 12px 12px 0 12px;
  border: 1px solid var(--gold);
  border-bottom: 0;
  border-radius: 2px 2px 0 0;
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 45%, transparent 88%);
  mask-image: linear-gradient(180deg, #000 0%, #000 45%, transparent 88%);
  pointer-events: none;
  transition: border-color var(--theme-dur) var(--ease-theme);
}
/* The silhouette is applied as a MASK, not as an <img>: the PNG supplies only
   the shape (alpha), and background-color supplies the tone. That keeps the
   figure a token colour rather than a baked-in one — it stays correct if the
   card ever sits on a light surface, and it lets hover animate the tone. */
.team-figure {
  width: 100%;
  height: 100%;
  background-color: rgba(244, 244, 242, 0.10);
  -webkit-mask: url('/brand/team-silhouette.png') center / cover no-repeat;
  mask: url('/brand/team-silhouette.png') center / cover no-repeat;
  transition: transform 1.2s var(--ease), background-color 0.6s var(--ease);
}
.team-card:hover .team-figure {
  transform: scale(1.04);
  background-color: rgba(244, 244, 242, 0.155);
}

/* Individual framing keeps eyes near 30% of the card and similar head sizes.
   The source photographs stay intact. Matching background tones and feathered
   edges let tighter portraits zoom out without hard rectangular seams. */
.team-portrait {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: var(--portrait-bg);
}
.team-portrait[data-person="Eliezer Shaffren"] {
  --portrait-scale: 1;
  --portrait-y: 0%;
  --portrait-bg: #97989c;
}
.team-portrait[data-person="David Markstein"] {
  --portrait-scale: 1.12;
  --portrait-y: 0%;
  --portrait-bg: #8e9179;
}
.team-portrait[data-person="Shimon Singer"] {
  --portrait-scale: 1.14;
  --portrait-y: 6%;
  --portrait-bg: #65524b;
}
.team-portrait[data-person="Moshe Linkenberg"] {
  --portrait-scale: .72;
  --portrait-y: -5%;
  --portrait-bg: #e5d9c6;
}
.team-portrait[data-person="Noa Baron"] {
  --portrait-scale: .9;
  --portrait-y: 0%;
  --portrait-bg: #b9b8b6;
}
.team-photo {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform-origin: top center;
  transform: translateY(var(--portrait-y)) scale(var(--portrait-scale));
  mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent),
    linear-gradient(to bottom, transparent, #000 4%, #000 75%, transparent 100%);
  mask-composite: intersect;
}
.team-portrait::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, transparent 42%, #101012 88%);
  pointer-events: none;
}
.team-portrait { transition: transform 1.2s var(--ease); }
.team-card:hover .team-portrait { transform: scale(1.04); }

/* The name plate. The panel itself carries no background and no edge — the
   ::before below is the whole treatment. */
.team-card-meta {
  position: absolute;
  inset: auto 0 0 0;
  padding: 20px 22px 22px;
}
/* Darkening and blur that fade in from nothing rather than starting on a
   line. The mask is what does it: it feathers the backdrop-filter itself, so
   the blur dissolves upward instead of ending on the hard edge a plain
   rectangle would leave. The plate is also taller than the text it holds, so
   the fade has room to happen well above the words. */
/* --plate is the plate's tint: the card's own fill, so it darkens the card on
   dark and lightens it on light. Registered as a colour so it can crossfade
   with the theme — a gradient cannot transition, but a colour it reads can.
   The plain rgba declarations are the fallback for browsers without
   color-mix(); they just snap instead of fading. */
@property --plate {
  syntax: '<color>';
  inherits: false;
  initial-value: rgb(18, 18, 21);
}
.team-card-meta::before {
  content: "";
  position: absolute;
  inset: -170px 0 0 0;
  --plate: rgb(18, 18, 21);
  background: linear-gradient(180deg, rgba(18, 18, 21, 0) 0%, rgba(18, 18, 21, 0.26) 44%, rgba(18, 18, 21, 0.66) 76%, rgba(18, 18, 21, 0.88) 100%);
  background: linear-gradient(180deg, transparent 0%, color-mix(in srgb, var(--plate) 26%, transparent) 44%, color-mix(in srgb, var(--plate) 66%, transparent) 76%, color-mix(in srgb, var(--plate) 88%, transparent) 100%);
  pointer-events: none;
  transition: --plate var(--theme-dur) var(--ease-theme);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .team-card-meta::before {
    background: linear-gradient(180deg, rgba(18, 18, 21, 0) 0%, rgba(18, 18, 21, 0.14) 44%, rgba(18, 18, 21, 0.42) 76%, rgba(18, 18, 21, 0.62) 100%);
    background: linear-gradient(180deg, transparent 0%, color-mix(in srgb, var(--plate) 14%, transparent) 44%, color-mix(in srgb, var(--plate) 42%, transparent) 76%, color-mix(in srgb, var(--plate) 62%, transparent) 100%);
    -webkit-backdrop-filter: blur(18px) saturate(1.15);
    backdrop-filter: blur(18px) saturate(1.15);
    -webkit-mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.22) 34%, rgba(0, 0, 0, 0.72) 66%, rgba(0, 0, 0, 1) 86%);
    mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.22) 34%, rgba(0, 0, 0, 0.72) 66%, rgba(0, 0, 0, 1) 86%);
  }
}
/* Cards carrying a photograph get a heavier plate, and — unlike every other
   team rule — it does NOT flip with the theme.

   Two reasons it has to be its own treatment. The plate above is tuned to
   darken a flat card fill; a photograph is busier and lighter, so the same
   values leave the name sitting on detail. And both portraits end in dark
   suiting at exactly the height the name sits, so on the light ground, where
   .team-name turns near-black, the type would be dark-on-dark. Photo cards
   therefore keep a dark scrim and light type in both states: here the plate
   belongs to the photograph underneath it, not to the page's ground.

   The tint is stated outright rather than going through --plate, since there
   is nothing to crossfade to. */
.team-card--photo .team-card-meta::before {
  inset: -210px 0 0 0;
  background: linear-gradient(180deg,
    rgba(10, 10, 12, 0) 0%,
    rgba(10, 10, 12, 0.34) 38%,
    rgba(10, 10, 12, 0.74) 70%,
    rgba(10, 10, 12, 0.93) 100%);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .team-card--photo .team-card-meta::before {
    background: linear-gradient(180deg,
      rgba(10, 10, 12, 0) 0%,
      rgba(10, 10, 12, 0.26) 38%,
      rgba(10, 10, 12, 0.64) 70%,
      rgba(10, 10, 12, 0.86) 100%);
    -webkit-backdrop-filter: blur(14px) saturate(1.05);
    backdrop-filter: blur(14px) saturate(1.05);
    -webkit-mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.30) 34%, rgba(0, 0, 0, 0.85) 64%, rgba(0, 0, 0, 1) 82%);
    mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.30) 34%, rgba(0, 0, 0, 0.85) 64%, rgba(0, 0, 0, 1) 82%);
  }
}

/* Above the plate: ::before is a child of .team-card-meta and would otherwise
   paint over both lines. */
.team-name,
.team-role {
  position: relative;
  transition: color var(--theme-dur) var(--ease-theme);
}
.team-name {
  margin: 0;
  font-size: clamp(20px, 1.5vw, 24px);
  font-weight: 500;
  color: var(--text);
  letter-spacing: -0.02em;
  line-height: 1.25;
}
.team-role {
  margin: 3px 0 0;
  font-size: 15px;
  line-height: 1.35;
  color: var(--text-2);
}
/* The opening paragraph of the About section. It used to run up to 20.5px to
   set itself apart from the paragraph below it; at a flat 18px that job falls
   to the brighter --text colour instead. */
.about-statement {
  font-size: 18px;
  font-weight: 400;
  letter-spacing: -0.012em;
  line-height: 1.5;
  color: var(--text-2);
  margin-bottom: 18px;
}

/* ---------- Services ---------- */

.services-head,
.services-lede {
  text-align: center;
  margin-left: auto;
  margin-right: auto;
}
.services-head { max-width: 20ch; }
/* .lede is left-aligned and left-anchored everywhere else on the page, so the
   centring has to be restated here rather than inherited. */
.services-lede { max-width: 56ch; }

/* Three bordered boxes, stepped down across the row. Not a collapsed grid any
   more: each card is its own object with its own stroke, so the cells need a
   gap between them and the shared-edge trick no longer applies. */
.service-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px;
  align-items: start;
  margin-top: clamp(48px, 6vw, 76px);
}
.service-card {
  /* z-index 0 rather than auto: it makes the card a stacking context, which is
     what keeps the hover glow's negative z-index scoped to this box — above
     the card's own ground, below its type — instead of falling behind the
     page. Same idiom the old services rows used. */
  position: relative;
  z-index: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  min-width: 0;
  padding: clamp(26px, 2.4vw, 38px);
  border: 1px solid var(--line);
  /* The gold top edge on hover, drawn as an inset shadow rather than a border
     so it can go from 1px hairline to 2px gold without moving the card's
     content by a pixel. It lies over the card's own stroke on that edge. */
  box-shadow: inset 0 2px 0 rgba(245, 210, 134, 0);
  transition: box-shadow 0.45s var(--ease);
}
.service-card:hover { box-shadow: inset 0 2px 0 var(--gold); }
/* The hover glow: a light haze that rises out of the bottom edge, arched so
   it sits higher through the middle of the card than at the sides.

   Two layers, because one cannot do both jobs. The radial ellipse is the
   arch — anchored at bottom centre, taller than it is wide, so it peaks in
   the middle and falls away toward the sides. On its own it leaves the two
   bottom corners dark, since its falloff reaches zero before it gets there,
   so a flat linear wash underneath carries the full width of the bottom edge
   into the corners. Gradients rather than a blurred shape: the falloff has to
   reach zero before it meets the card's stroke, and a blur would leave a
   rectangular edge where overflow clips it. */
.service-card::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: 0;
  right: 0;
  bottom: 0;
  height: 58%;
  background:
    radial-gradient(
      ellipse 58% 116% at 50% 100%,
      rgba(244, 244, 242, 0.125) 0%,
      rgba(244, 244, 242, 0.048) 46%,
      rgba(244, 244, 242, 0) 76%
    ),
    linear-gradient(
      to top,
      rgba(244, 244, 242, 0.055) 0%,
      rgba(244, 244, 242, 0.022) 38%,
      rgba(244, 244, 242, 0) 100%
    );
  opacity: 0;
  transform: translateY(16%);
  pointer-events: none;
  transition:
    opacity 0.45s var(--ease),
    transform 0.65s var(--ease);
}
/* The same haze at the top edge, but laid flat rather than arched. The ellipse
   is wider than the card and short, so it reads as a horizontal band across
   the whole top edge instead of peaking over the middle the way the bottom
   one does — the two edges answer each other without repeating. Dimmer too:
   the bottom stays the lit edge, this is the echo. */
.service-card::after {
  content: "";
  position: absolute;
  z-index: -1;
  left: 0;
  right: 0;
  top: 0;
  height: 44%;
  background:
    radial-gradient(
      ellipse 108% 62% at 50% 0%,
      rgba(244, 244, 242, 0.085) 0%,
      rgba(244, 244, 242, 0.036) 52%,
      rgba(244, 244, 242, 0) 82%
    ),
    linear-gradient(
      to bottom,
      rgba(244, 244, 242, 0.042) 0%,
      rgba(244, 244, 242, 0.016) 40%,
      rgba(244, 244, 242, 0) 100%
    );
  opacity: 0;
  transform: translateY(-16%);
  pointer-events: none;
  transition:
    opacity 0.45s var(--ease),
    transform 0.65s var(--ease);
}
.service-card:hover::before,
.service-card:hover::after {
  opacity: 1;
  transform: translateY(0);
}
/* The resting highlight. With nothing hovered or focused, the first card
   wears the full hover state, so the row always has one lit card; point at
   another and the state moves there, leave the row and it comes back.
   The 0.12s delay is on the way back in only (a transition takes the delay
   of the state it is heading to), so sweeping across the gap between two cards
   does not flash the first one. Without :has() these rules drop out whole and
   the cards fall back to plain hover. */
.service-card:has(:focus-visible) { box-shadow: inset 0 2px 0 var(--gold); }
.service-card:has(:focus-visible)::before,
.service-card:has(:focus-visible)::after {
  opacity: 1;
  transform: translateY(0);
}
.service-card:has(:focus-visible) .btn--quiet { color: var(--gold-lift); }
.service-card:has(:focus-visible) .btn--quiet::after { transform: translateX(4px); }

.service-grid:not(:has(.service-card:hover, :focus-visible)) .service-card:first-child {
  box-shadow: inset 0 2px 0 var(--gold);
  transition-delay: 0.12s;
}
.service-grid:not(:has(.service-card:hover, :focus-visible)) .service-card:first-child::before,
.service-grid:not(:has(.service-card:hover, :focus-visible)) .service-card:first-child::after {
  opacity: 1;
  transform: translateY(0);
  transition-delay: 0.12s;
}
.service-grid:not(:has(.service-card:hover, :focus-visible)) .service-card:first-child .btn--quiet {
  color: var(--gold-lift);
  transition-delay: 0.12s;
}
.service-grid:not(:has(.service-card:hover, :focus-visible)) .service-card:first-child .btn--quiet::after {
  transform: translateX(4px);
  transition-delay: 0.12s;
}

/* Services is the section on screen while the page turns light — About opens
   the light chapter now — so it takes over the job About used to do: its ink
   inverts on the theme's clock, and the ground changes behind it with no edge.
   Same treatment and the same trap as .about: .js-reveal .reveal replaces the
   transition list on reveal targets, so the cards' own box-shadow travel has to
   be carried across with the theme properties. */
.services-head,
.service-title,
.service-body,
.service-icon,
.proof-value,
.proof-label {
  transition: color var(--theme-dur) var(--ease-theme);
}
.hero-proof { transition: border-color var(--theme-dur) var(--ease-theme); }
.service-card {
  transition:
    box-shadow 0.45s var(--ease),
    border-color var(--theme-dur) var(--ease-theme);
}
.js-reveal .services .reveal {
  transition:
    opacity 0.8s var(--ease),
    transform 0.8s var(--ease),
    color var(--theme-dur) var(--ease-theme),
    border-color var(--theme-dur) var(--ease-theme),
    box-shadow 0.45s var(--ease);
}
html.theme-light .services-head,
html.theme-light .service-title,
html.theme-light .proof-value { color: var(--void); }
html.theme-light .services-lede,
html.theme-light .service-body { color: rgba(18, 18, 21, 0.72); }
html.theme-light .proof-label { color: rgba(18, 18, 21, 0.6); }
html.theme-light .service-card { border-color: rgba(18, 18, 21, 0.10); }
html.theme-light .hero-proof { border-top-color: rgba(18, 18, 21, 0.12); }
html.theme-light .service-icon,
html.theme-light .services .btn--quiet { color: var(--gold-deep); }
/* --gold is too pale to hold a 2px edge or a lit link on a light ground. */
html.theme-light .service-card:hover,
html.theme-light .service-card:has(:focus-visible),
html.theme-light .service-grid:not(:has(.service-card:hover, :focus-visible)) .service-card:first-child {
  box-shadow: inset 0 2px 0 var(--gold-fill);
}
html.theme-light .services .btn--quiet:hover,
html.theme-light .service-card:has(:focus-visible) .btn--quiet,
html.theme-light .service-grid:not(:has(.service-card:hover, :focus-visible)) .service-card:first-child .btn--quiet {
  color: #6f4e00;
}
/* The step. Each card starts lower than the one before it, so the row reads
   as a descent across the reading direction rather than as three things that
   failed to line up. The cards are unequal in height, so the offsets are on
   the tops only and the feet stay ragged on purpose. */
.service-card:nth-child(2) { margin-top: clamp(32px, 4.5vw, 64px); }
.service-card:nth-child(3) { margin-top: clamp(64px, 9vw, 128px); }

.service-icon {
  width: clamp(42px, 3.4vw, 50px);
  height: clamp(42px, 3.4vw, 50px);
  color: var(--gold);
  /* 1.5px on screen, whatever size the icon is drawn at. The paths live in a
     24 viewBox, so a plain stroke-width scales with the box — 1.9 reads right
     at 24px and renders near 4px at 50. non-scaling-stroke takes the width out
     of user space, so this number is literally the rendered thickness and does
     not have to be re-derived if the icon is resized. */
  stroke-width: 1.5;
  /* Deliberately more air than the numerals had: an icon is a heavier mark
     and needs the separation to stop reading as part of the title. */
  margin-bottom: clamp(34px, 3.4vw, 48px);
}
.service-icon path { vector-effect: non-scaling-stroke; }
.service-title {
  margin: 0 0 12px;
  font-size: clamp(22px, 2vw, 27px);
  letter-spacing: -0.03em;
}
.service-body {
  margin: 0;
  line-height: 1.62;
  color: var(--text-2);
}
/* Pushed to the foot so the three CTAs line up however long the copy runs. */
.service-cta {
  margin-top: auto;
  padding-top: clamp(24px, 2.6vw, 36px);
  position: static; /* lets ::before below cover the whole card */
}
.service-cta::before {
  content: "";
  position: absolute;
  inset: 0;
}
/* ---------- Why Sleek ---------- */

/* The page's one light section — but the colour does not live on the section.
   It lives on <body>, and an observer flips <html>.theme-light while this
   section crosses the middle of the viewport. That is what makes the switch
   immersive rather than a box scrolling past: the whole viewport changes
   while the previous section is still on screen, so by the time the boundary
   between them passes, both sides already match and there is no edge to see.

   The section itself stays transparent so the page ground shows through. Its
   neighbours do not — see .about / .approach below. */
/* position: relative because the drawn grid is absolute to this section.
   --why-gap is the gutter between the cards, declared here rather than on the
   list because the drawn lines need it too: they sit in the middle of these
   gaps, and the two have to be the same number or the alignment is a
   coincidence that breaks at the next breakpoint. */
.why {
  background: none;
  position: relative;
  --why-gap: clamp(14px, 1.6vw, 26px);
}
.why .container { position: relative; z-index: 1; }
.why,
.why .why-note,
.why .why-statement-term,
.why .why-statement-body,
.why h2 {
  transition: color var(--theme-dur) var(--ease-theme);
}
/* The card fill flips with the theme too, so it has to travel on the same
   clock as the ground it sits on or the panels blink mid-fade. */
.why .why-statement {
  transition: background-color var(--theme-dur) var(--ease-theme);
}
/* The reveal rule sets its own transition list and outranks the one above
   (.js-reveal .reveal is 0,2,0; .why h2 is 0,1,1), so any element in here that
   is also a reveal target needs colour added back or its ink snaps while the
   ground fades. */
.js-reveal .why .reveal {
  transition:
    opacity 0.8s var(--ease),
    transform 0.8s var(--ease),
    color var(--theme-dur) var(--ease-theme),
    background-color var(--theme-dur) var(--ease-theme);
}
html.theme-light .why,
html.theme-light .why .why-note,
html.theme-light .why .why-statement-body { color: rgba(18, 18, 21, 0.72); }
html.theme-light .why h2,
html.theme-light .why .why-statement-term { color: var(--void); }

/* ---- Why Sleek: four statements ----
   Display-size terms with their bodies beneath, held apart by space alone.
   Nothing to step through here, so nothing pins and nothing dims: these are
   four parallel claims, not a sequence. */
.why-title { max-width: 20ch; margin-inline: auto; text-align: center; }
.why-note { max-width: 62ch; margin-top: 26px; margin-inline: auto; text-align: center; }

/* Four arch-topped panels in a row, every other one dropped. The stagger is
   the point: four identical domes on one baseline read as a fence, and the
   drop is what turns them into an arcade. */
.why-statements {
  margin: clamp(64px, 8vw, 112px) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--why-gap);
  align-items: start;
}
.why-statement {
  /* How far the low corner sits below the high one — the pitch of the roof.
     Also the extra top padding the copy needs to clear it. */
  --roof: clamp(26px, 3.2vw, 54px);
  --medal: clamp(58px, 5.4vw, 84px);
  position: relative;
  /* Keeps the fill's negative z-index scoped to this card — same reason the
     service cards isolate their glow. */
  isolation: isolate;
  margin: 0;
  /* Clears the roof and the part of the medallion that hangs into the card,
     with air under it. Derived rather than eyeballed, so the copy stays
     clear of both when either size changes. */
  padding:
    calc(var(--roof) / 2 + var(--medal) * 0.68 + clamp(20px, 2vw, 34px))
    clamp(18px, 1.8vw, 30px)
    clamp(30px, 3vw, 46px);
  text-align: center;
}
/* The fill is a pseudo-element rather than the card's own background because
   the clip has to cut the panel WITHOUT cutting the medallion: clip-path
   clips descendants, so a card that clipped itself would slice the icon off
   at the roofline. */
.why-statement::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  /* Square at every corner. The roof is cut instead: the top edge runs from a
     high corner down to a low one, so each card reads as a building rather
     than a panel, and the four of them as a skyline. The slope alternates
     below, which — with the every-other drop — is what keeps the ridge from
     stepping the same way four times. */
  clip-path: polygon(0 var(--roof), 100% 0, 100% 100%, 0 100%);
  /* Translucent rather than a colour, so the one fill sits correctly on both
     grounds while the theme crosses this section. */
  background: rgba(244, 244, 242, 0.05);
  transition: background-color var(--theme-dur) var(--ease-theme);
}
/* Mirrored roof, so adjacent cards lean into each other rather than all
   pitching the same way. */
.why-statement:nth-child(even) { margin-top: clamp(48px, 7vw, 120px); }
.why-statement:nth-child(even)::before {
  clip-path: polygon(0 0, 100% var(--roof), 100% 100%, 0 100%);
}
html.theme-light .why-statement::before { background: rgba(18, 18, 21, 0.05); }

/* The medallion. Centred on the card and lifted so it crosses the roofline
   with roughly two fifths of it outside — enough to break the edge, not so
   much that it floats free of the card it belongs to. The offset is measured
   from the middle of the slope (--roof / 2), which is where the cut edge
   actually passes under the circle's centre. */
.why-icon {
  position: absolute;
  left: 50%;
  top: calc(var(--roof) / 2 - var(--medal) * 0.32);
  transform: translateX(-50%);
  width: var(--medal);
  height: var(--medal);
  border-radius: 50%;
  display: grid;
  place-items: center;
  /* Opaque, and the ground's own colour: a tint here would let the cut roof
     edge show through the circle, which reads as a mistake rather than as a
     plate. */
  background: var(--void);
  border: 1px solid var(--line);
  color: var(--gold);
  transition:
    background-color var(--theme-dur) var(--ease-theme),
    border-color var(--theme-dur) var(--ease-theme),
    color var(--theme-dur) var(--ease-theme);
}
.why-icon svg {
  width: 46%;
  height: 46%;
}
/* Same trick as the service icons: the stroke is taken out of user space, so
   1.5px is 1.5px whatever size the medallion is drawn at. */
.why-icon path { vector-effect: non-scaling-stroke; stroke-width: 1.5px; }
html.theme-light .why-icon {
  background: #f4f4f2;
  border-color: rgba(18, 18, 21, 0.14);
  color: var(--gold-deep);
}
.why-statement-term {
  margin: 0 0 10px;
  font-family: var(--sans);
  /* Body size. Weight and ink are the only things separating a term from its
     definition now, so both have to carry the distinction on their own. */
  font-weight: 600;
  font-size: clamp(17px, 1.35vw, 20px);
  letter-spacing: -0.012em;
  line-height: 1.4;
  color: var(--text);
  text-wrap: balance;
}
/* The cards end at four different heights — their copy is four different
   lengths — so the button hangs off the deepest one rather than off a line.
   Centred, with the section's own interval above it. */
.why-cta {
  margin-top: clamp(56px, 6vw, 88px);
  display: flex;
  justify-content: center;
}
.why-statement-body {
  margin: 0;
  font-size: clamp(15px, 1.15vw, 17px);
  line-height: 1.55;
  color: var(--text-2);
  text-wrap: pretty;
}

/* ---- Focus pull ----
   A reusable block, not a section's own layout. Used by Our Approach.

   Base — no JS, and every width below 900px — it is a plain stack: four steps,
   image over copy, read top to bottom. The sticky column is in the min-width
   block further down, behind .is-focus, so the stack is the layout rather than
   a state to recover from. */
.focus {
  position: relative;
  /* One column. The pinned title column is gone — each step carries its own
     heading — so the steps take the full measure and the image column can be
     sized to the screen rather than to what was left over. */
  margin-top: clamp(24px, 3vw, 48px);
}

.focus-panes {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  /* Only the stack ever sees this: side by side, the rows are a screen tall
     each and set their own spacing. */
  gap: clamp(56px, 7vh, 104px);
}
.focus-pane {
  /* Image left, copy right. The image column is fixed rather than fractional
     so all four line up down the section however long the copy runs, and it
     takes the larger share now that no pinned column is holding the left of
     the page. */
  display: grid;
  grid-template-columns: clamp(300px, 42vw, 620px) minmax(0, 1fr);
  column-gap: clamp(28px, 3.4vw, 56px);
  align-items: center;
}
/* The photograph and the monogram over it, as one box. It is the frame that
   the sticky column pins and the frame that sets the height, so the mark can
   be measured against the picture — full height of it, flush to its left edge
   — rather than against a column that is taller than the picture inside it. */
.focus-frame {
  position: relative;
  width: 100%;
}
/* The logo's S, at the height of the photograph and hard against its left
   edge. It carries the logo's own gradient rather than a flat --gold, so it is
   the mark itself sitting on the picture rather than a silhouette of it. */
.focus-mark {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  width: auto;
  pointer-events: none;
}
/* Carries the gradient and nothing else: no box, no space in the layout. */
.focus-mark-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}
.focus-img {
  display: block;
  width: 100%;
  /* A fixed ratio rather than stretching to the copy. The steps differ in
     height because their copy does, and images that changed size with the
     paragraph beside them read as an accident. */
  aspect-ratio: 4 / 5;
  /* Capped against the viewport as well as the column, so a tall image cannot
     run past the fold on a short laptop. */
  max-height: 66svh;
  height: auto;
  object-fit: cover;
  border-radius: 3px;
  /* Held just off neutral so four photographs from four sources read as one
     set, and so they sit in a page whose only colour is a gold accent. */
  filter: grayscale(0.34) contrast(1.02);
}
/* The step's own heading, sitting above the paragraph it introduces. Set close
   to the size the pinned title used to run at — one step is on screen at a
   time, so this is the stage's headline rather than an entry in a list — but
   held under the section h2 above it. */
.focus-pane-title {
  /* The gap under the title is the section's only real interval: paragraph and
     points sit together as one block beneath it, so the space has to say which
     of the two divisions matters. */
  margin: 0 0 clamp(20px, 2vw, 30px);
  font-family: var(--sans);
  font-weight: 500;
  font-size: clamp(30px, 3.3vw, 50px);
  letter-spacing: -0.032em;
  line-height: 1.1;
  /* Bounded by its column rather than by a measure: at this size the four
     titles set on one or two lines either way, and balance is what keeps a
     wrapped one from dropping a single word onto the second line. */
  text-wrap: balance;
  color: var(--text);
}
.focus-text {
  margin: 0;
  max-width: 42ch;
  /* Page body size. The title carries the size contrast now, so the paragraph
     does not have to, and at 18px it sets at the same measure as the points
     under it — which is what lets the two read as one block. */
  font-size: 18px;
  line-height: 1.55;
  color: var(--text-2);
}
/* The specifics under each statement. Still one block with the paragraph
   above — they are the same thought at a finer grain, not a second section —
   but with enough room that the eye takes them as a list rather than as a
   fourth and fifth line of the paragraph. */
.focus-points {
  margin: clamp(22px, 2vw, 30px) 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 9px;
  max-width: 42ch;
}
.focus-points li {
  display: grid;
  grid-template-columns: 24px 1fr;
  gap: 12px;
  align-items: start;
  line-height: 1.5;
  color: var(--text-3);
}
/* One CTA, on the last step. Set off from the points above it by more than
   the points are set off from the paragraph, because it is a different kind of
   thing — the end of the sequence, not another line of it. */
.focus-cta { margin-top: clamp(30px, 3vw, 44px); }

.focus-check {
  width: 24px;
  height: 24px;
  /* Nudged onto the first line's optical centre rather than its box top. */
  margin-top: 0.1em;
  color: var(--gold);
  transition: color var(--theme-dur) var(--ease-theme);
}
.focus-check circle { fill: currentColor; }
/* The tick is knocked out of the disc, so it takes the ground's colour and
   has to flip with the theme — gold disc on dark, deep gold on paper. */
.focus-check path { stroke: var(--void); transition: stroke var(--theme-dur) var(--ease-theme); }

/* ---- Focus pull: the sticky frame ----
   Everything above is the stack the section falls back to. This is what it
   becomes once the script has run and there is width to hold two columns: the
   photograph pins to the middle of the viewport for the length of the section
   and the four steps scroll past it in the ordinary way.

   Nothing here animates the copy, on purpose. Each step comes up off the
   bottom edge of the window at exactly the speed the reader is scrolling,
   because it is the scroll — no transition to run at its own pace, nothing to
   catch up on after a flick, and it goes back down again if they scroll back.
   The only thing the script does is dissolve the photograph as each step
   arrives, and that too is read off scroll position rather than a clock.

   `display: contents` on the step is what makes the frame possible: it takes
   the <li> box out of the layout, so all four images land in the same cell of
   the list's grid and their sticky containing block becomes the full height of
   the section rather than one step of it. */
@media (min-width: 901px) {
  .focus.is-focus .focus-panes {
    display: grid;
    grid-template-columns: clamp(300px, 42vw, 620px) minmax(0, 1fr);
    /* One row per step, each at least a screen tall — that is what gives every
       step a full screen to come up through, and what sets how far apart they
       arrive. `auto` past that, so a step with copy too long to fit is not
       clipped on a short window. */
    grid-template-rows: repeat(var(--steps), minmax(100svh, auto));
    column-gap: clamp(28px, 3.4vw, 56px);
    gap: 0 clamp(28px, 3.4vw, 56px);
  }
  .focus.is-focus .focus-pane { display: contents; }
  /* All four photographs in one cell, spanning every row: one frame, held
     still, with the shots dissolving inside it. A fixed height rather than a
     ratio, because the offset that centres it in the viewport has to be the
     other half of the same number. */
  .focus.is-focus .focus-frame {
    grid-column: 1;
    grid-row: 1 / -1;
    position: sticky;
    top: 17svh;
    align-self: start;
    height: 66svh;
  }
  .focus.is-focus .focus-img {
    height: 100%;
    max-height: none;
    aspect-ratio: auto;
    /* The script sets these. Fading in only — never out — is what keeps the
       dissolve clean: the shot underneath stays opaque until the new one has
       covered it, so the pale ground never shows through between the two. */
    opacity: 0;
  }
  /* Four frames land in the one cell, so four identical S's land on top of
     each other. Only the last is kept: a sticky element paints as its own
     layer, so the last frame's mark is the one that sits above all four
     photographs rather than under the three that come after it. */
  .focus.is-focus .focus-pane:not(:last-child) .focus-mark { display: none; }
  /* A screen per step, with the copy centred in it, so it crosses the middle
     of the window at the same height the photograph is held at. */
  .focus.is-focus .focus-copy {
    grid-column: 2;
    display: grid;
    align-content: center;
  }
}

/* The sections either side are transparent too, so the ground changes behind
   them rather than stopping at their edge — that is the difference between an
   immersive switch and a white box scrolling into view. The cost is that About
   is on screen, in full, while the page turns white, so its ink has to invert
   with the theme or it goes white-on-white. Approach needs no such treatment:
   it is only ever visible while the theme is on its way back to dark, and its
   ink is already light.

   About now opens the light chapter itself (it carries data-theme-light), so
   the team cards invert too: see the html.theme-light .team-* rules below. */
.about,
.about-title,
.about .eyebrow {
  transition:
    color var(--theme-dur) var(--ease-theme),
    background-color var(--theme-dur) var(--ease-theme),
    border-color var(--theme-dur) var(--ease-theme);
}
/* Controls need their own lists, spelled out in full. A `transition`
   shorthand REPLACES the list it inherits rather than extending it, and these
   selectors outrank the component's own (.about .btn is 0,2,0 against .btn's
   0,1,0). Leaving `padding` and `left` out of these two is exactly what
   stopped the primary button's arrow from travelling on hover — if you add a
   theme property to a control here, carry its own animated properties across
   with it. */
.about .btn {
  transition:
    padding 0.4s var(--ease),
    background-color var(--theme-dur) var(--ease-theme),
    border-color var(--theme-dur) var(--ease-theme),
    color var(--theme-dur) var(--ease-theme);
}
.about .btn-icon {
  transition:
    left 0.4s var(--ease),
    background-color var(--theme-dur) var(--ease-theme),
    color var(--theme-dur) var(--ease-theme);
}

/* Same specificity trap as in .why: .js-reveal .reveal (0,2,0) replaces the
   transition list on anything that is also a reveal target. */
.js-reveal .about .reveal {
  transition:
    opacity 0.8s var(--ease),
    transform 0.8s var(--ease),
    color var(--theme-dur) var(--ease-theme),
    background-color var(--theme-dur) var(--ease-theme);
}
html.theme-light .about,
html.theme-light .about-statement,
html.theme-light .about-panel p { color: rgba(18, 18, 21, 0.72); }
html.theme-light .about-title { color: var(--void); }
html.theme-light .about .eyebrow { color: var(--gold-deep); }
/* The primary button is a white fill, which vanishes on the light ground, so
   it inverts with the theme — fill and icon circle both, or the circle would
   be white-on-white inside a dark button. */
html.theme-light .about .btn--primary,
html.theme-light .why .btn--primary,
html.theme-light .approach .btn--primary {
  background-color: var(--void);
  border-color: var(--void);
  color: var(--text);
}
html.theme-light .about .btn--primary .btn-icon,
html.theme-light .why .btn--primary .btn-icon,
html.theme-light .approach .btn--primary .btn-icon {
  background-color: var(--text);
  color: var(--void);
}
html.theme-light .rail-btn {
  border-color: rgba(18, 18, 21, 0.25);
  color: var(--void);
}
html.theme-light .rail-btn:hover:not(:disabled) {
  border-color: var(--gold-deep);
  color: var(--gold-deep);
}
/* Team cards on the light ground. A lighter shade than white is not there to
   have, so the card goes a step darker than the ground instead — still one
   quiet step off it, the same relationship it has on dark. The frame drops to
   --gold-fill: --gold is pale enough to vanish on a near-white card. */
html.theme-light .team-card { background-color: #e6e6e3; }
html.theme-light .team-card::after { border-color: var(--gold-fill); }
html.theme-light .team-card-meta::before { --plate: #e6e6e3; }
html.theme-light .team-figure { background-color: rgba(18, 18, 21, 0.10); }
html.theme-light .team-card:hover .team-figure { background-color: rgba(18, 18, 21, 0.16); }
html.theme-light .team-name { color: var(--void); }
html.theme-light .team-role { color: rgba(18, 18, 21, 0.68); }
/* Except on a photo card, where the name sits over the photograph's own dark
   foot rather than over the card fill. See .team-card--photo above: the plate
   there stays dark in both themes, so the type has to as well. */
html.theme-light .team-card--photo .team-name { color: #f4f4f2; }
html.theme-light .team-card--photo .team-role { color: rgba(244, 244, 242, 0.74); }


/* ---------- Our approach ---------- */

/* Raised off the dark page, flush with the light one. On paper a lifted
   surface would cut the light chapter in half, so it goes transparent and
   rides the ground the way Why does.

   No rules top or bottom. The band used to be bracketed by two hairlines; the
   page separates its sections with space and with the ground changing under
   them, and a stroke on top of that is a third statement of a boundary the
   reader has already been given twice. If this ever starts to look undivided,
   add space, not a line — same rule as the proof band. */
.approach {
  background-color: var(--surface);
  transition: background-color var(--theme-dur) var(--ease-theme);
}
html.theme-light .approach { background-color: transparent; }

/* The only centred intro on the page, and it earns it here: what follows is a
   two-column sequence rather than a column of prose, so a left-aligned heading
   would be hanging off the side of a layout that has no left edge to hold it.
   The lede centres with it — a centred heading over a flush-left paragraph
   reads as a mistake — and both are held to a measure, since centred text at
   full container width is unreadable. */
.approach h2 {
  text-align: center;
  max-width: 20ch;
  margin-inline: auto;
}
.approach .lede {
  text-align: center;
  max-width: 60ch;
  margin-inline: auto;
  /* Air under the heading, which is the section's own break: everything below
     this point is the sequence. */
  margin-top: clamp(26px, 2.6vw, 40px);
}

/* Ink, on the theme's clock. Same shape as the Why block above. */
.approach h2,
.approach .lede,
.approach .focus-pane-title,
.approach .focus-text,
.approach .focus-points li {
  transition: color var(--theme-dur) var(--ease-theme);
}
/* .js-reveal .reveal (0,2,0) replaces the transition list on reveal targets,
   so colour has to be put back for the heading and the lede. */
.js-reveal .approach .reveal {
  transition:
    opacity 0.8s var(--ease),
    transform 0.8s var(--ease),
    color var(--theme-dur) var(--ease-theme);
}
html.theme-light .approach h2,
html.theme-light .approach .focus-pane-title { color: var(--void); }
html.theme-light .approach .lede,
html.theme-light .approach .focus-text { color: rgba(18, 18, 21, 0.72); }
html.theme-light .approach .focus-points li { color: rgba(18, 18, 21, 0.60); }
html.theme-light .approach .focus-check { color: var(--gold-deep); }
html.theme-light .approach .focus-check path { stroke: #f4f4f2; }

/* ---------- Testimonials ---------- */

/* ---------- Testimonials ----------
   A twelve column spread, not a row of cards. The heading and the rating hold
   the left five columns; the right six carry one review at a time.

   The boxes are gone: a border around a quotation says "component", and these
   are the only words on the page the client did not write. */
.tstm-spread {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: clamp(20px, 2.4vw, 40px);
  /* Only reached when the spread stacks; side by side there is one row. */
  row-gap: clamp(26px, 2.8vw, 40px);
  align-items: start;
}
/* The heading column spans both rows, so the photograph sits directly under
   the reviews rather than under whichever of the two columns runs longer. */
.tstm-intro { grid-column: 1 / span 5; }

/* A rating, not an ornament: see the warning in the markup. Sized to the
   .meta labels rather than to the heading over it, since it is a caption to
   the reviews on the right, not a display element of its own. */
.tstm-stars {
  display: flex;
  gap: 5px;
  margin-bottom: clamp(18px, 1.8vw, 26px);
  color: var(--gold);
}
.tstm-star { width: 17px; height: 17px; }

/* Quote across the top, then the name and the photograph on one line under
   it: attribution left, image in the right corner, both on the same bottom
   edge. The image is inside the slide, so it changes with the review without
   the script knowing it exists. */
.tstm {
  margin: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "quote quote"
    "attr  shot";
  column-gap: clamp(20px, 2.4vw, 36px);
  align-items: end;
}
.tstm-quote { grid-area: quote; }
.tstm-attr { grid-area: attr; }
.tstm-shot {
  grid-area: shot;
  width: clamp(104px, 10.5vw, 148px);
  /* Off square and off the grid line: dropped below the attribution's baseline
     and pulled left of the measure, tilted a few degrees. A transform rather
     than margins, so the layout still reserves the upright box and nothing
     under it moves. */
  transform: translate(
      clamp(-64px, -4.5vw, -24px),
      clamp(28px, 4.2vw, 60px)
    )
    rotate(-3.5deg);
  /* Without height:auto the markup's height attribute wins - it maps to the
     height property, an explicit height beats aspect-ratio, and the image
     renders 900px tall. Same reason .focus-img sets it. */
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  /* Same grade as every other photograph on the page. */
  filter: grayscale(0.34) contrast(1.02);
}
.tstm-quote {
  margin: 0;
  color: var(--text);
  font-size: clamp(24px, 2.5vw, 34px);
  line-height: 1.26;
  letter-spacing: -0.03em;
  text-wrap: balance;
}
/* Hung punctuation: the opening mark sits out in the gutter so the first
   letter of the quote starts on the column, where every other line does. The
   closing mark stays inline. Real typographic quotes, in the page's one
   accent - the mark is the only ornament this section gets. */
.tstm-quote::before,
.tstm-quote::after {
  color: var(--gold);
  font-size: 1.15em;
  line-height: 0;
  /* The marks are drawn on the cap line; this drops them onto the text. */
  vertical-align: -0.12em;
}
.tstm-quote::before { content: "\201C"; margin-left: -0.48em; }
.tstm-quote::after { content: "\201D"; }
.tstm-attr { margin-top: clamp(24px, 2.4vw, 34px); }
.tstm-name {
  display: block;
  font-size: 14px;
  font-weight: 500;
  color: var(--text);
}
.tstm-role { display: block; margin-top: 6px; }

/* ---- The gallery ----
   One frame in the six columns opposite the heading, showing one review at a
   time. What is written below the .is-live rules is the no-JS state: three
   quotations stacked, all of them readable. The script adds .is-live, which
   is what turns the stack into a frame. */
.tstm-gallery { grid-column: 7 / span 6; }
.tstm-slides {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: clamp(36px, 4vw, 56px);
}
/* Live: all three slides share one grid cell, so the frame is as tall as the
   longest review and nothing moves as they change. Absolute positioning would
   have needed that height hard-coded. */
.tstm-gallery.is-live .tstm-slides { gap: 0; }
.tstm-gallery.is-live .tstm-slide {
  grid-area: 1 / 1;
  opacity: 0;
  /* Six pixels, not sixty. The review is replaced, not thrown in. */
  transform: translateY(6px);
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
  pointer-events: none;
}
.tstm-gallery.is-live .tstm-slide.is-on {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

/* The arrows. Hidden until the script runs: with no script every review is
   already on the page, so there is nothing for them to do. */
.tstm-nav { display: none; margin-top: clamp(28px, 3vw, 44px); }
.tstm-gallery.is-live .tstm-nav { display: flex; }

/* The heavy rule that closes a section. Same weight, colour and measure as
   the one over the proof row - it sits inside .container, so it lines up with
   the text above it rather than with the page edge. The air over it matches
   that rule's too; under it the section's own padding and the next section's
   carry the interval. */
.section-rule {
  height: 3px;
  background: var(--rule-heavy);
  margin-top: clamp(48px, 6vw, 76px);
  /* The containers it sits in are not all plain blocks - the reviews' one is
     a twelve column grid, where a bare div would take a single column and
     draw a 90px stub. */
  grid-column: 1 / -1;
}

/* ---------- Final CTA ---------- */

/* Centred on purpose: it is the one moment on the page where the
   message is the whole composition. */
.finalcta {
  text-align: center;
  /* Both: the silhouettes are absolute to this section, and the page edge is
     what crops them. */
  position: relative;
  overflow: hidden;
}

.finalcta h2 { max-width: 18ch; margin: 0 auto; }
.finalcta .lede { margin: 26px auto 0; text-align: center; }
.finalcta .btn-row { margin-top: 44px; }
/* The copy sits over the skyline. */
.finalcta .container { position: relative; z-index: 1; }

/* ---- The mark's skyline, borrowed ----
   A window onto the logo file rather than a copy of part of it: the <img> is
   the whole lockup, scaled to a bit over twice this box and pushed left, so
   what shows through the box is the tower cluster and nothing else. The
   numbers are read off the artwork — the towers occupy x 250-665 and y 0-490
   of the 893x829 viewBox — so 893/415 = 215.2% wide, offset by 250/415 =
   60.2% of this box, and the box itself is 415:490.

   Cropping this way rather than lifting the paths out into their own file
   keeps one source of truth for the mark: if the client re-cuts the logo, the
   background here re-cuts with it. */
.finalcta-skyline {
  position: absolute;
  bottom: 0;
  width: clamp(280px, 33vw, 540px);
  aspect-ratio: 415 / 490;
  overflow: hidden;
  pointer-events: none;
  /* Faint enough to be massing rather than a picture: it should read as the
     ground having a shape, and the eye should find the headline first. */
  opacity: 0.07;
  /* Standing on the section's bottom edge and dissolving upward, so the
     towers have a base and no top — cut off square at the top, they would
     read as a cropped image. */
  -webkit-mask-image: linear-gradient(to top, #000 8%, transparent 92%);
  mask-image: linear-gradient(to top, #000 8%, transparent 92%);
}
.finalcta-skyline img {
  position: absolute;
  top: 0;
  left: -60.2%;
  width: 215.2%;
  /* The global img rule caps images at their container; this one is meant to
     be larger than its container — that is what makes the box a window. */
  max-width: none;
  height: auto;
  display: block;
}
/* Each is pushed off its own edge, so neither cluster is whole — the page
   edge finishes the drawing. */
.finalcta-skyline--l { left: 0; transform: translateX(-24%); }
/* Mirrored, so the two lean away from the copy instead of marching the same
   way across it. */
.finalcta-skyline--r { right: 0; transform: translateX(24%) scaleX(-1); }

/* ---------- Footer ---------- */

.footer {
  background: var(--void);
  border-top: 1px solid var(--line);
  color: var(--text-3);
  /* One size for the whole footer, links and labels alike. 14.5px was small
     enough that the address and the service list read as fine print. */
  font-size: 16px;
}
.footer-inner {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: clamp(56px, 6vw, 84px) var(--pad-x);
  display: grid;
  grid-template-columns: 1.5fr 1fr 1fr 1.2fr;
  gap: clamp(32px, 4vw, 64px);
}
/* The footer has the room the header does not, so it carries the full
   client lockup: mark, wordmark and tagline, unmodified. */
.footer-logo {
  display: block;
  width: auto;
  height: clamp(96px, 11vw, 128px);
  margin-bottom: 4px;
}
.footer a {
  color: var(--text-2);
  text-decoration: none;
  transition: color 0.3s var(--ease);
}
.footer a:hover { color: var(--gold); }
.footer ul { list-style: none; margin: 0; padding: 0; }
.footer li { margin-bottom: 11px; }
.footer li:last-child { margin-bottom: 0; }
/* Column headings. Not .meta any more: tracked-out caps at 11.5px are the
   page's treatment for indices and small print, and three of them standing
   over three columns of 16px text read as the smallest thing in the footer
   rather than as its structure. Sentence case, body size, gold — the gold is
   what sets them apart, the same way it does for .eyebrow. */
.footer-head {
  margin: 0 0 20px;
  font-family: var(--sans);
  font-size: 16px;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  color: var(--gold);
}
.footer-address {
  font-style: normal;
  line-height: 1.6;
}
/* The contact lines and the services, both mark-then-text. One grid rather
   than two, because the two columns stand side by side and their text has to
   start on the same measure in each. */
.footer-contact li,
.footer-services li {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: 11px;
  align-items: start;
}
.footer-icon,
.footer-tick {
  width: 20px;
  height: 20px;
  /* Onto the first line's optical centre rather than its box top. */
  margin-top: 0.15em;
  color: var(--gold);
  flex: none;
}
.footer-icon { opacity: 0.85; }
/* Same tick as the approach points, at footer size: gold disc, ground knocked
   out of it, so it flips with the theme like everything else drawn in gold. */
.footer-tick circle { fill: currentColor; }
.footer-tick path { stroke: var(--void); }
.footer-services li { color: var(--text-2); }
.footer-base {
  border-top: 1px solid var(--line);
  padding: 24px 0;
}
.footer-base-inner {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 24px;
  justify-content: space-between;
  align-items: center;
  font-size: 15px;
}
.footer-legal { margin: 0; }
/* The Privacy Policy sits inside the copyright line rather than beside it:
   it is the same small print, not a second item competing with the credit on
   the other end of the row. */
.footer-legal a {
  margin-left: 6px;
  border-bottom: 1px solid var(--line-strong);
  padding-bottom: 1px;
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease);
}
.footer-legal a:hover { border-bottom-color: var(--gold); }
/* Build credit. The mark runs at the cap height of the line beside it and
   takes the line's own colour, so the two read as one lockup and both lift
   together on hover. */
.footer-cws {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--text-3);
}
.footer-cws svg {
  width: 17px;
  height: 17px;
  flex: none;
}
.footer-cws:hover { color: var(--gold); }

/* ---------- 404 ---------- */

/* The header is absolutely positioned, so a page with no hero behind it
   has to reserve the space itself or the content sits under the logo. */
.notfound {
  padding-top: calc(var(--nav-h) + clamp(40px, 8vw, 96px));
  min-height: 72vh;
  display: flex;
  align-items: center;
  text-align: center;
}
.notfound h1 { font-size: clamp(58px, 10vw, 104px); margin-bottom: 18px; }
.notfound p { margin-bottom: 34px; }

/* ---------- Subpage hero ----------
   Every page that is not the homepage opens with this instead of the 100dvh
   photo hero. It is deliberately NOT that hero at a smaller size: the homepage
   hero is a full-bleed photograph with a cut-out figure standing in it, and
   repeating that five times would make the whole site one gesture. This is a
   two-column spread on the page's own measure instead.

   The padding-top is the same reservation .notfound and .legal make: the
   header is absolutely positioned, so a page with no hero has to leave room
   for it or its first line renders under the logo.

   There is no padding-bottom and no closing hairline. The photograph and the
   mark both run to the section's bottom edge and the photograph's own edge is
   what separates the hero from whatever follows — on /services and
   /testimonials that is a white section, so the boundary is a colour change
   rather than a drawn line. The copy column carries its own bottom padding,
   since it has no edge of its own to stand on. */
/* The height of this section is the height of its LEFT column and nothing
   else: 100px of air, the eyebrow, the heading, the lede, 100px of air. It is
   not a viewport-height hero and it must not become one - that is the
   homepage's device, and repeating it on five more pages would make every
   route open the same way.

   The 100px at the top is measured from under the header, not from the top of
   the page: the header is absolutely positioned over this section, so the
   first line has to clear --nav-h before the spacing starts or the eyebrow
   renders behind the logo. */
.page-hero {
  /* .container adds 28px of its own above everything it wraps, so that comes
     out here rather than being added to the 100: the number that matters is
     the space the reader sees between the header and the eyebrow, and it is
     100px exactly. */
  --container-pad-t: 0px;
  padding-top: calc(var(--nav-h) + 100px);
  padding-bottom: 0;
  /* Both: the skyline is absolute to this section, and the page edge is what
     crops it. */
  position: relative;
  overflow: hidden;
}
/* Everything the reader reads sits over the mark. This is also what puts the
   photograph in front of it: the photograph is inside this element, so it
   inherits the stacking context rather than needing a z-index of its own. */
.page-hero .container { position: relative; z-index: 1; }
.page-hero .eyebrow { margin-bottom: 18px; }
.page-hero h1 {
  font-size: clamp(38px, 6.2vw, 78px);
  font-weight: 400;
  line-height: 1.02;
  letter-spacing: -0.035em;
  max-width: 18ch;
}
.page-hero .lede {
  max-width: 62ch;
  margin-top: clamp(20px, 2.4vw, 30px);
}

/* ---- The two-column opening ----
   Copy left, photograph right, both standing on the section's bottom edge.
   The copy column is given the larger share because it carries three elements
   to the photograph's one, and the measures above (18ch on the heading, 62ch
   on the lede) still fit inside it at every width down to the stack.

   align-items: stretch, and this is what keeps the section off the height of
   a screen. It used to be `end`, with the photograph at its own 4:5 ratio and
   therefore taller than the copy on every page - which made the PICTURE the
   thing setting the section's height, and ran the hero to most of a viewport.
   Stretched, the row is as tall as the copy column and the photograph is cut
   to whatever that leaves. The two still share a bottom edge, because the
   section has no bottom padding and the copy carries its own. */
.page-hero--media .container {
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(300px, 34%, 460px);
  column-gap: clamp(32px, 4vw, 72px);
  align-items: stretch;
}
.page-hero-copy {
  min-width: 0;
  /* The copy's substitute for an edge, and the bottom half of the section's
     own measurement: the section is 100px, this column, 100px. Without it the
     eyebrow, heading and lede would sit on the section boundary with the
     photograph. */
  padding-bottom: 100px;
}
.page-hero-media {
  min-width: 0;
  align-self: stretch;
  /* The frame is a box the photograph is poured into, not a box the
     photograph sizes. The <img> is taken out of flow below for exactly that
     reason: left in flow, `height: 100%` against an auto-height parent
     resolves to auto, the image falls back to its intrinsic 720x900, and the
     picture is setting the row's height again - which is the thing this
     whole arrangement exists to stop. */
  position: relative;
  min-height: 0;
  /* No radius, because the bottom two corners are not there to round: the
     frame runs off the section's edge. Rounding only the top two would be a
     different shape language from the rest of the page. */
  overflow: hidden;
  /* No ratio here any more, on purpose. The frame used to be cut to the
     source (4:5, the shape every one of these photographs is) and that is
     what made the picture decide how tall the section was. The column decides
     now and the frame takes what is left, so these ARE cropped - see the
     object-position below. The stacked frame further down does set a ratio,
     because at that width there is no column beside it to take one from. */
}
.page-hero-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* Biased up, because the frame is now shorter than the source and every one
     of these photographs has its subject in the top half - the same crop the
     stacked frame has always used. */
  object-position: 50% 28%;
}

/* The right column when a page passes its own markup instead of a photograph
   — /contact's form. Unlike the photograph it does NOT run to the bottom
   edge: a form is a thing to fill in, and one whose last field is cut off by
   the end of a section reads as broken rather than as bled. It shares the
   copy column's bottom padding instead. */
.page-hero-aside {
  min-width: 0;
  align-self: start;
}
/* The aside variant re-sets two things the photograph variant gets right and
   a form gets wrong.

   Width first: the photograph column is about a third, which is correct for a
   4:5 portrait and far too narrow for a form. At 443px the fields' own
   minmax(190px, 1fr) could only ever fit one per row, so the panel came out
   1036px tall and the hero ran to nearly 1400. Close to half gives the short
   fields two-up and roughly halves the height.

   Alignment second: the columns share a bottom edge because the photograph
   stands on the section's floor. A form is taller than the copy beside it, so
   bottom-aligning them pushed the eyebrow, the heading and the contact
   details down to the foot of the section with a screen of empty space above
   them. They share a top edge instead, and the section gets its bottom
   padding back, since nothing bleeds into what follows. */
.page-hero--aside .container {
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.92fr);
  align-items: start;
}
/* No tower cluster behind the aside variant. Everywhere else it stands at the
   page edge beside a photograph, which is a picture and can hold a faded mark
   behind it; here the aside is the contact form, and a decorative drawing
   showing through the gaps between a panel's fields reads as something
   rendering wrong rather than as a watermark. */
.page-hero--aside .page-hero-skyline { display: none; }
.page-hero--aside { padding-bottom: clamp(56px, 7vw, 104px); }
.page-hero--aside .page-hero-copy { padding-bottom: 0; }
/* Between the laptop widths and the stack, the form gets the larger share.
   The heading shrinks with the viewport and the copy column needs less than
   half by the time the window is this narrow, while the form needs a fixed
   amount: at an even-ish split a 1024 laptop left the panel 422px wide, which
   is eight pixels short of holding First and Last name side by side, so the
   whole form fell to one column and the hero grew by a hundred pixels to pay
   for it. */
@media (max-width: 1200px) {
  .page-hero--aside .container { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); }
}

/* A compact block under the hero copy: /contact's phone, email and address.
   Set at label scale rather than body scale, because the form beside it is
   what the page is for and this is the alternative to it, not the headline. */
.page-hero-info { margin-top: clamp(30px, 3.4vw, 44px); }

/* ---- The mark's skyline, borrowed ----
   A window onto the logo file rather than a copy of part of it: the <img> is
   the whole lockup, scaled to a bit over twice this box and pushed left, so
   what shows through the box is the tower cluster and nothing else. The
   numbers are read off the artwork — the towers occupy x 250-665 and y 0-490
   of the 893x829 viewBox — so 893/415 = 215.2% wide, offset by 250/415 =
   60.2% of this box, and the box itself is 415:490.

   Cropping this way rather than lifting the paths out into their own file
   keeps one source of truth for the mark: if the client re-cuts the logo, the
   background here re-cuts with it.

   Sized deliberately taller than the photograph next to it. Standing on the
   same bottom edge, a cluster this size clears the top of the frame by around
   a sixth of its height, so what reads is towers rising BEHIND the picture
   plus a band of them down the page edge beside it. Make it shorter than the
   photograph and the only thing left visible is that band, which looks like
   something showing through a gap rather than like a decision. */
.page-hero-skyline {
  position: absolute;
  bottom: 0;
  width: clamp(300px, 40vw, 620px);
  aspect-ratio: 415 / 490;
  overflow: hidden;
  pointer-events: none;
  /* Stronger than the closing CTA's 7%, because the part of this one the
     reader actually sees is the masked, faded top — by the time the gradient
     has taken its cut, the towers above the photograph land at about the same
     weight the CTA's do on bare ground. */
  opacity: 0.16;
  /* Dissolving upward so the towers have a footing and no top; cut off square
     they would read as a cropped image. The fade starts higher up than the
     CTA's (55% rather than 8%) because the bottom half is behind the
     photograph anyway — spending the gradient down there would leave nothing
     for the part that shows. */
  -webkit-mask-image: linear-gradient(to top, #000 55%, transparent 100%);
  mask-image: linear-gradient(to top, #000 55%, transparent 100%);
  /* Pushed off its own edge, so the cluster is never whole — the page edge
     finishes the drawing, exactly as it does on the CTA. Mirrored so it leans
     off the page rather than back across the copy. */
  right: 0;
  transform: translateX(24%) scaleX(-1);
}
.page-hero-skyline img {
  position: absolute;
  top: 0;
  left: -60.2%;
  width: 215.2%;
  /* The global img rule caps images at their container; this one is meant to
     be larger than its container — that is what makes the box a window. */
  max-width: none;
  height: auto;
  display: block;
}

/* Stacked at the same width .feature stacks, and for the same reason: below
   it neither column has a measure. The frame turns landscape on the way down
   — a 4:5 portrait at full width is most of a phone screen before the heading
   has been read — and the crop is biased upward, since every one of these
   photographs has its subject in the top half. Nothing runs to the section's
   bottom edge any more: stacked, the photograph is the last thing in the
   column and bleeding it would leave the heading floating above a picture
   that has fallen out of the page. */
@media (max-width: 860px) {
  .page-hero--media .container {
    grid-template-columns: 1fr;
    row-gap: clamp(28px, 5vw, 44px);
  }
  .page-hero {
    padding-bottom: clamp(40px, 6vw, 72px);
    /* Not the desktop 100. Stacked, the column carries the photograph as well
       as the copy, so 100px of air over a 128px header pushed the whole hero
       past the height of a phone screen - which is the thing the desktop
       measurement exists to avoid. */
    padding-top: calc(var(--nav-h) + clamp(44px, 7vw, 76px));
  }
  .page-hero-copy,
  .page-hero-aside { padding-bottom: 0; }
  .page-hero-media {
    /* Explicit, and not redundant. Once max-height makes the height definite,
       a box with an aspect-ratio derives its width from it and stops
       stretching to the column — the frame came out 450 wide in a 630 column.
       Pinning the width puts the ratio back to being the thing that yields. */
    width: 100%;
    aspect-ratio: 5 / 4;
    /* A ratio alone still ran the hero past a full screen on a tablet — 630
       wide is 504 tall, and the heading, lede and buttons are above it. The
       cap only bites on a wide-but-short window; on a phone the ratio is
       still the smaller of the two. svh, not vh: the mobile toolbars must not
       change the frame as they collapse. */
    max-height: 40svh;
  }
  .page-hero-media img { object-position: 50% 28%; }
}


/* ---------- Pillars ----------
   An editorial term-and-definition run: a small gold index and a title on the
   left, the paragraph on the right, a hairline between each.

   Not the Why Sleek component, on purpose. That one is four short parallel
   claims at display size with nothing between them; these are four longer
   explanations that need a reading measure and benefit from being ruled apart.
   Reusing Why's markup here would have meant either shrinking its type until
   it was a different component anyway, or running four paragraphs at display
   size, which is a wall.

   Collapses to one column below 820px, where a 38/62 split leaves neither side
   a usable measure. */
/* Two columns of grouped statements, not a ruled row per item.
   The previous version put a hairline above every row and a numeral beside
   every term. Both are tells: a rule under each row of a short list is the
   laziest way to separate things, and numbering four parallel claims counts
   something the reader was never going to lose their place in. The grid and
   the space do that work now, and the numerals are gone. */
/* .pillars / .pillar / .pillar-term / .pillar-body lived here: a two-or-three
   column run of a term over a sentence, with a short gold keyline above each
   term. Its last user was the Testimonials themes run, which became three
   .service-card boxes on 20 September 2026, and nothing on the site rendered
   it afterwards. Recover it from git history if a page needs that shape
   again - .section--paper and html.theme-light both carried ink rules for it,
   and those went with it. */

/* ---------- Full-bleed image band ----------
   A photograph edge to edge with a statement over it. Used once per page at
   most: it is the loudest device the inner pages have, and its job is to break
   a run of type, so two of them in one page would defeat it.

   The scrim is a real element rather than a filter on the image, because the
   text needs a predictable floor of contrast no matter what the photograph is
   doing behind it. */
.band {
  position: relative;
  isolation: isolate;
  min-height: clamp(320px, 46vw, 520px);
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  margin: clamp(56px, 7vw, 104px) 0;
}
.band-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -2;
}
.band-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(180deg,
    rgba(10, 10, 12, 0.32) 0%,
    rgba(10, 10, 12, 0.52) 46%,
    rgba(10, 10, 12, 0.88) 100%);
}
/* The container is a flex item here, which means it is sized by its content
   and not by the band. Without the width it collapses to a max-content block
   and `margin-inline: auto` centres it in the viewport, so the band's copy is
   the one thing on the page that does not start on the page's left margin. */
.band .container { width: 100%; padding-bottom: clamp(32px, 4vw, 56px); }
.band-quote {
  margin: 0;
  max-width: 20ch;
  /* The band's section heading, so it is set at .h-section's size. It reads
     as a statement rather than a heading because of where it sits, not
     because it is smaller than one. */
  font-size: clamp(30px, 3.9vw, 52px);
  font-weight: 400;
  line-height: 1.14;
  letter-spacing: -0.03em;
  /* Fixed light, not themed: it sits on the photograph, not on the page. */
  color: #f4f4f2;
}
.band-note {
  margin: 18px 0 0;
  max-width: 46ch;
  color: rgba(244, 244, 242, 0.76);
}

/* ---- The flush variant ----
   Used where the band has to read as the continuation of the section above it
   rather than as a picture set into the page: no top margin, so the
   photograph starts on the white section's own bottom edge, and a scrim that
   arrives at the page's ground colour by the bottom edge, so the band has no
   bottom edge at all and simply becomes the section below.

   The last stop is --void rather than a themed ground, and that is deliberate:
   the band is a dark photographic object in both themes (see .band-quote's
   fixed colour). In the light theme the page underneath is paper and the
   bottom of the band stays a real edge, which is what it is today. */
.band--flush { margin-top: 0; }
.band--flush .band-scrim {
  background: linear-gradient(180deg,
    rgba(18, 18, 21, 0.26) 0%,
    rgba(18, 18, 21, 0.52) 40%,
    rgba(18, 18, 21, 0.86) 74%,
    rgba(18, 18, 21, 0.98) 92%,
    var(--void) 100%);
}
/* The photograph is cropped to 2.4:1 in the file and the band runs nearer
   3:1, so `cover` only trims it. Centred: the corridor is a one-point
   perspective and its vanishing point is the middle of the frame. */
.band--flush .band-img { object-position: 50% 50%; }

/* ---- Copy left, action right ----
   The band is one wide line, so its two halves sit at opposite ends of the
   measure on a shared bottom edge. A button under the note would read as
   belonging to the note rather than to the statement over it. */
.band--flush .container {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: clamp(24px, 4vw, 64px);
}
.band-copy { min-width: 0; }
/* The button is on the photograph, not on the page, so it keeps the dark
   page's white fill in both themes - the same reasoning as .band-quote's
   fixed colour. --i is unset here, so it arrives with the copy rather than
   after it: one block, one movement. */
.band-action { flex: none; padding-bottom: 6px; }

/* Under the fold of the copy the two stop being a line and become a stack.
   The action goes to the left edge with everything else rather than staying
   at the right, which at this width is nowhere in particular. */
@media (max-width: 860px) {
  .band--flush .container {
    display: block;
  }
  .band-action { margin-top: clamp(22px, 4vw, 32px); padding-bottom: 0; }
}

/* ---------- Alternating feature row ----------
   Image one side, copy the other. Capped at two consecutive uses: a third
   would be a zigzag, which is the most templated shape a marketing page has. */
.feature {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(28px, 5vw, 80px);
  align-items: center;
  padding: clamp(36px, 5vw, 72px) 0;
}
.feature--flip .feature-media { order: 2; }
.feature-media {
  position: relative;
  border-radius: 3px;
  overflow: hidden;
  aspect-ratio: 4 / 3;
}
.feature-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.feature-title {
  margin: 0;
  font-size: clamp(24px, 2.4vw, 34px);
  font-weight: 400;
  line-height: 1.16;
  letter-spacing: -0.028em;
  color: var(--text);
  transition: color var(--theme-dur) var(--ease-theme);
}
.feature-body {
  margin: 16px 0 0;
  max-width: 54ch;
  color: var(--text-2);
  transition: color var(--theme-dur) var(--ease-theme);
}
@media (max-width: 860px) {
  .feature { grid-template-columns: 1fr; gap: 24px; }
  .feature--flip .feature-media { order: 0; }
}
html.theme-light .feature-title { color: var(--void); }
html.theme-light .feature-body { color: rgba(18, 18, 21, 0.72); }

/* ---------- About's core values ----------
   The .feature row again, four times, under a centred heading, on white.

   The white is a plain background on the section rather than the page's
   data-theme-light switch, and that is a departure worth explaining, because
   the note over .about calls a "white box scrolling into view" the thing that
   mechanism exists to avoid. Two reasons it is right here and not there:

   - The switch turns the whole page ground, and it starts turning it when the
     lit section's top crosses 85% of the window. This section begins about
     950px down a page whose hero is 785px tall, so the ground would go white
     with the hero still filling the screen — and .page-hero has no light ink,
     so the headline would go white on white. The homepage pays for the switch
     with an html.theme-light rule for every component the chapter crosses;
     here that bill is a hero, a nav and a band, for one section.
   - There, light is a chapter the reader moves through. Here it is one panel
     that has to stand out from the dark either side of it. An edge is what
     does that.

   Ink is spelled out below rather than inherited, since the page around it
   stays dark and the tokens are the dark set. Three departures from the
   Services version of these rows, all scoped here so that page is untouched.

   Centred, because the rows below alternate: with the image on the left of one
   row and the right of the next, there is no single edge for a left-aligned
   heading to line up with, and it would read as belonging to the first row
   rather than to all four. The closing CTA is the only other centred type on
   the page, and it is a full section away. */
.values-head {
  text-align: center;
  margin-bottom: clamp(16px, 2.4vw, 36px);
}
.values-head h2 { margin-inline: auto; max-width: 20ch; }
/* .lede is a left-aligned measure by default; centring the block means
   centring the box as well as the text. */
.values-head .lede { margin-inline: auto; }

/* Bigger than the Services rows'. There the title shares the column with a
   four-point detail list and has to sit above it as a heading; here it has a
   single paragraph under it and carries the row on its own, so it is set as
   near to the section heading as it can go without matching it — .h-section
   tops out at 52px and this at 46px, which keeps the order of the two
   readable at every width. The 24px floor is doing the same job at the other
   end: .h-section bottoms out at 30px, and a 28px floor put the two within
   two pixels of each other on a phone, which is not a step. */
.values .feature-title { font-size: clamp(24px, 3.2vw, 46px); }
.values .feature-body { margin-top: 20px; }

/* Three of the four photographs here are 720x900 portraits borrowed from the
   homepage, and .feature-media is a 4:3 landscape frame. Covering one with
   the other overflows by 40% of the height, and centred that lands the top of
   the crop below everyone's hairline — all three read as headless. Biasing
   the crop upward keeps the faces and takes the excess off the floor instead,
   which is where the empty desk is anyway. No effect on the fourth: that one
   is landscape and only overflows sideways. */
.values .feature-media img { object-position: 50% 20%; }

/* ---------- The services rows ----------
   Three .feature rows on paper. Two departures from the component's defaults,
   both scoped here.

   The top padding is small on purpose. The hero above ends on a photograph
   that runs to its own bottom edge, so this section's white starts hard
   against the bottom of that picture; a full --section-pad would have opened
   a band of dark ground between the two and turned a join into a stripe. The
   rows carry their own clamp(36px, 5vw, 72px) of padding, so the first title
   is still clear of the boundary. */
.services-rows {
  /* The join with the hero is the photograph's own bottom edge, so this is
     white space under a picture rather than a dark stripe between two
     sections - it can be generous without losing the join, and it was too
     tight to breathe at the old 24px. */
  padding-top: clamp(48px, 5.4vw, 84px);
  padding-bottom: clamp(56px, 7vw, 104px);
}
/* Bigger, and the same scale as /about's value rows, so the two pages set
   their rows at one size. Held under .h-section's 52px ceiling. */
.services-rows .feature-title { font-size: clamp(24px, 3.2vw, 46px); }
/* The row's own action. Set off from the detail list by more than the list is
   set off from the paragraph above it, because it ends the row rather than
   continuing it - the same interval .focus-cta uses for the same reason. */
.service-cta-row { margin-top: clamp(30px, 3vw, 44px); }

/* ---------- Escrow panel ----------
   Kept, though nothing uses it as a service any more: /contact's form sits in
   one. See the layout note in services.astro for what it used to do here and
   the conditions under which it should come back. */
.panel {
  margin-top: clamp(36px, 5vw, 72px);
  padding: clamp(28px, 4vw, 56px);
  border-radius: 3px;
  background: var(--surface);
  border: 1px solid var(--line);
  transition:
    background-color var(--theme-dur) var(--ease-theme),
    border-color var(--theme-dur) var(--ease-theme);
}
/* .panel-head and .panel-cols lived here: the escrow panel's icon-and-title
   row and its two-column detail list. Both went when escrow became a feature
   row; the form that uses .panel now has no use for either. */
html.theme-light .panel {
  background: #ffffff;
  border-color: rgba(18, 18, 21, 0.10);
}

/* ---------- Audience rail ----------
   Who the work is for: eight cards on a manual horizontal scroller. This used
   to be a wrapped row of pills, and the reason it is not any more is that
   eight pills is a paragraph of nouns - the reader takes it as one shape and
   reads none of it. A card with a photograph and a heading is eight things.

   The scroller is a real overflow container, not a transform track. That is
   what gets touch, trackpad, keyboard and the scrollbar for free and leaves
   the arrows (see the rail script in Layout.astro) as the only part any
   JavaScript owns: with the script dead the row still scrolls and every card
   is still reachable. */
/* The arrows that used to sit at the far end of this row are gone, on request
   - see the note in about.astro for what the rail is driven by instead and
   what that costs in the states where it does not pin. What is left is a
   heading and a lede, so .aud-head is now only the box they share. */
.aud-head-copy { min-width: 0; }

.aud-rail {
  margin-top: clamp(30px, 3.6vw, 48px);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  /* The scrollbar is the rail's own affordance on a trackpad and a nuisance
     under a row of photographs on everything else. The arrows and the cut-off
     card at the right edge carry the affordance instead. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.aud-rail::-webkit-scrollbar { display: none; }
/* The focus ring is on the scroller because the scroller is what the keyboard
   drives: tab to the rail, then arrow along it. */
.aud-rail:focus-visible { outline: 2px solid var(--gold); outline-offset: 4px; }

.aud-track {
  display: flex;
  gap: clamp(14px, 1.6vw, 22px);
  margin: 0;
  padding: 0;
  list-style: none;
}
.aud-card {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  flex: none;
  width: clamp(224px, 21vw, 300px);
  aspect-ratio: 3 / 4;
  border-radius: 3px;
  scroll-snap-align: start;
  display: flex;
  align-items: flex-end;
  padding: clamp(20px, 1.8vw, 26px);
}
.aud-shot {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Same grade as every other photograph on the site. It is doing more work
     here than anywhere else: eight stock photographs from eight different
     shoots sit side by side in this row, and pulling the colour out of all of
     them is most of what makes the eight read as one set. */
  filter: grayscale(0.34) contrast(1.02);
  transition: transform 0.6s var(--ease);
}
.aud-card:hover .aud-shot { transform: scale(1.04); }
/* The dark half of the card. Opaque enough at the floor to carry a heading
   whatever the photograph is doing, gone by the middle so the picture is
   still a picture. A real element rather than a filter, for the same reason
   .band-scrim is one.

   ON THE RECORD, because this was briefly something else: the veil was
   replaced by a gold plate at the foot of the card - --gold-fill, dark ink,
   inset 8px on three sides - and then put back. The plate brought its own
   ground instead of darkening the photograph, which cost the cards their one
   soft edge and made the row read as eight pictures with eight labels stuck
   on them. If it is ever wanted again, that is the whole of it: this element
   out of about.astro, `padding: 8px` on .aud-card, and .aud-title given
   `flex: 1`, a --gold-fill background and --ink type. */
.aud-veil {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(to top,
    rgba(12, 12, 14, 0.92) 0%,
    rgba(12, 12, 14, 0.72) 26%,
    rgba(12, 12, 14, 0.28) 58%,
    rgba(12, 12, 14, 0.08) 100%);
}
/* Fixed light, like every other label that sits on a photograph rather than
   on the page. */
.aud-title {
  position: relative;
  margin: 0;
  font-size: clamp(19px, 1.6vw, 24px);
  font-weight: 500;
  line-height: 1.16;
  letter-spacing: -0.025em;
  color: #f4f4f2;
  text-wrap: balance;
}

/* The CTA under the rail keeps the interval the pills used to have above it. */
.aud-rail + .btn-row { margin-top: clamp(30px, 3.4vw, 44px); }

/* ---------- The rail pinned to the scroll ----------
   On a desktop viewport the audience rail is driven by the page's own vertical
   scroll: the block sticks to the top of the window, the reader's next scroll
   runs the eight cards past under the heading, and the page releases and
   carries on once the last card has landed.

   This is not autoplay and it is not a timer. Every pixel the cards move is a
   pixel the reader scrolled - the direction is remapped, the pace is not - so
   it does not break the rule that nothing on this site moves under whoever is
   looking at it. Stop scrolling and it stops; scroll back up and it runs back.

   ALL OF IT IS OFF BY DEFAULT. Nothing below applies until the rail script adds
   .is-pinned to the section, and the script refuses to add it under reduced
   motion, under 860px, or on any viewport too short to hold the whole block -
   a pin that pushes its own heading off the top is worse than a plain row. In
   every one of those cases this section stays what it was: an overflow
   scroller with arrows over it, which is also what a reader with no JavaScript
   gets. .aud-stage and .aud-stage-inner are bare divs until then.

   The height of .aud-stage is set in the script, not here, because it is one
   viewport plus the rail's own scrollWidth overflow and only the browser knows
   the second number. */
.aud-section.is-pinned .aud-stage-inner {
  position: sticky;
  top: 0;
  min-height: 100vh;
  display: flex;
  align-items: center;
}
/* Snap is the right behaviour for a rail a reader flicks and the wrong one for
   a rail whose scrollLeft is written every frame: the snap would fight the
   write and the cards would stutter against the scroll. */
.aud-section.is-pinned .aud-rail {
  scroll-snap-type: none;
  overscroll-behavior-x: contain;
}
/* The rail's top margin is the interval between the lede and the cards on the
   plain layout. Pinned, the block is centred in the viewport and that interval
   is doing the work of holding the whole thing off the heading, so it comes
   down a little to buy the cards height on a laptop screen. */
.aud-section.is-pinned .aud-rail { margin-top: clamp(24px, 2.6vw, 36px); }

/* Pinned, a card is sized off the window rather than off the measure: the
   whole block - heading, lede, cards, button - has to fit inside one viewport
   or the pin is refused and this section goes back to being a plain scroller.
   Sizing the cards to the window is what gets a 13-inch laptop over that line.
   The first term is the plain layout's own size expressed as a height, so on a
   tall window nothing moves; the 48vh is what takes over on a short one, and
   the width follows from the 3/4 the card already keeps. */
.aud-section.is-pinned .aud-card {
  width: auto;
  height: min(clamp(299px, 28vw, 400px), 48vh);
}

/* ---------- Next steps ----------
   Three stages, numbered, strung on one line.

   ON THE RECORD, because this reverses what was here: each stage used to
   carry the 26px gold keyline every other supporting list on this site uses,
   with a note arguing that the reading order was sequence enough and that
   "Stage 1 / Stage 2" labels were decoration. That holds for a set of
   parallel claims - .pillars, .why-statements - and it does not hold here,
   because these three are not parallel. They happen in an order, and the
   question this section answers is what happens first.

   The numeral sits in a ring, and a hairline runs from one ring to the next.
   The line is the part that makes it a sequence rather than three numbered
   cards: it is drawn by each item except the last (:not(:last-child)), so it
   never runs off the end into a fourth stage that does not exist. It is also
   off below 760px, where the three stack and a horizontal line between them
   would point nowhere. */
.steps-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(24px, 3.4vw, 52px);
  margin-top: clamp(38px, 4.4vw, 60px);
  padding: 0;
  list-style: none;
}
.steps-row li {
  position: relative;
  min-width: 0;
}
/* The connecting rule, on the ring's own centre line (half of 42px). It
   starts 14px clear of this item's ring and stops 14px short of the next
   one's, which is 56px in from this cell's left edge and 56px short of the
   next cell's: a column, a gap, less both clearances and the ring's width.
   The gap value is the grid's own, repeated - the two have to move together
   or the line misses. */
.steps-row li:not(:last-child)::after {
  content: "";
  position: absolute;
  top: 21px;
  left: 56px;
  width: calc(100% + clamp(24px, 3.4vw, 52px) - 70px);
  height: 1px;
  background: var(--line-strong);
  transition: background-color var(--theme-dur) var(--ease-theme);
}
.step-mark {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  margin-bottom: clamp(20px, 2.2vw, 28px);
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--gold);
  transition:
    color var(--theme-dur) var(--ease-theme),
    border-color var(--theme-dur) var(--ease-theme);
}
.steps-row h3 {
  margin: 0 0 10px;
  font-size: clamp(18px, 1.6vw, 22px);
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--text);
  transition: color var(--theme-dur) var(--ease-theme);
}
.steps-row p { margin: 0; color: var(--text-2); font-size: 16px; }
@media (max-width: 760px) {
  .steps-row { grid-template-columns: 1fr; gap: 26px; }
  .steps-row li:not(:last-child)::after { display: none; }
  .step-mark { margin-bottom: 16px; }
}
/* The last stage is the end of the sequence, so its marker is the only one
   that is filled: a check on the page's one accent instead of a numeral in a
   ring. There is no "04" to count on to, and the three read as two steps and
   an arrival rather than as a list that stops.

   Dark ink on --gold-fill measures 8.6:1, which is why the fill is the deep
   stop rather than --gold. */
.step-mark--done {
  background: var(--gold-fill);
  border-color: var(--gold-fill);
  color: var(--void);
}
.step-check { width: 19px; height: 19px; }

html.theme-light .steps-row h3 { color: var(--void); }
html.theme-light .step-mark {
  color: var(--gold-deep);
  border-color: rgba(18, 18, 21, 0.20);
}
html.theme-light .step-mark--done {
  background: var(--gold-fill);
  border-color: var(--gold-fill);
  color: var(--void);
}
html.theme-light .steps-row li:not(:last-child)::after { background: rgba(18, 18, 21, 0.16); }

/* ---------- Services page ---------- */
.service-detail { margin-top: 20px; }

/* ---- The four statements, as cards ----
   The homepage's proof strip, on white, in boxes. The homepage sets the same
   four in a bare subgrid divided by a 3px rule; here they open a white
   section with nothing above them to be divided from, so each one is given
   its own edge instead.

   .proof-value and .proof-label are the homepage's classes, reused rather
   than re-declared: the two pages say the same four things and they should
   set them at the same size. Only the box and the mark are new. */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(14px, 1.4vw, 20px);
  margin-bottom: clamp(56px, 6vw, 88px);
}
.stat-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  min-width: 0;
  padding: clamp(22px, 2vw, 30px);
  border: 1px solid rgba(18, 18, 21, 0.12);
  border-radius: 3px;
}
/* --gold-deep, not --gold: the pale stop is for hairlines and small text on
   near-black and washes out on paper. Same swap as every other mark here. */
.stat-icon {
  width: 30px;
  height: 30px;
  margin-bottom: clamp(20px, 2.2vw, 32px);
  color: var(--gold-deep);
}
/* The label is pushed to the floor of the card so all four sit on one line
   however many lines the value above it takes. */
.stat-card .proof-label { margin-top: auto; padding-top: 12px; }

@media (max-width: 980px) {
  .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .stat-grid { grid-template-columns: 1fr; }
  .stat-card .proof-label { padding-top: 6px; }
}


/* ---------- Testimonials page ----------
   Three runs of quotations, and what separates them is the ground and the
   weight rather than the shape: three cards on paper, six quieter cards on
   the dark, and the themes. A page that is entirely quotations has to vary
   how it sets them or it reads as one long list - but the variation is
   between the runs, never inside one. All three cards in a run are peers. */

/* Opens close under the hero, for the same reason .services-rows does: the
   hero's photograph runs to the section boundary, so the white starts against
   the bottom of that picture and a full --section-pad would put a dark stripe
   between them. */
.reviews {
  /* Same as .services-rows: white under the hero photograph's own edge, so
     the join holds and the room is free. */
  padding-top: clamp(48px, 5.4vw, 84px);
  padding-bottom: clamp(56px, 7vw, 104px);
}

/* ---- The bento ----
   Exactly three cells for exactly three reviews. The lead spans both rows on
   the left; the other two stack beside it. Nothing here is generated, so the
   cell count cannot drift away from the content count without somebody
   noticing in the markup.

   The 1.25/1 split rather than an even one: the lead carries a photograph and
   the longest quotation, and the two beside it are single sentences that do
   not need the width. */
.bento {
  margin-top: clamp(28px, 3.4vw, 48px);
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  grid-template-rows: auto auto;
  gap: clamp(16px, 1.8vw, 26px);
}
.bento-cell {
  margin: 0;
  border-radius: 3px;
  padding: clamp(26px, 3vw, 44px);
  display: flex;
  flex-direction: column;
  /* Attribution to the floor, stars and quotation to the top, whatever the
     cell's height turns out to be. Without this the two short cells would
     centre their content and the three attributions would sit at three
     heights. */
  justify-content: space-between;
}
/* The lead: a photograph with the quotation over it, spanning both rows. It
   is the only cell with its own ground, which is what makes this a bento
   rather than three cards in a row. */
.bento-cell--lead {
  grid-row: 1 / span 2;
  position: relative;
  isolation: isolate;
  overflow: hidden;
  /* A floor rather than a ratio: the height that matters is the one the two
     cells beside it add up to, and those are set by their own copy. */
  min-height: clamp(360px, 40vw, 520px);
  justify-content: flex-end;
}
.bento-shot {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Same grade as every other photograph on the site. */
  filter: grayscale(0.34) contrast(1.02);
}
/* A real element rather than a filter on the image, because the quotation
   needs a predictable floor of contrast no matter what the photograph is
   doing behind it. Weighted to the bottom, which is where the type is. */
.bento-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(to top, rgba(18, 18, 21, 0.92) 12%, rgba(18, 18, 21, 0.52) 62%, rgba(18, 18, 21, 0.28) 100%);
}
/* The lead's ink is the dark set even though the section is paper: it sits on
   a photograph, not on the page. */
.bento-cell--lead .bento-quote,
.bento-cell--lead .tstm-name { color: var(--text); }
.bento-cell--lead .tstm-role { color: var(--text-3); }
.bento-cell--lead .bento-quote { font-size: clamp(24px, 2.6vw, 38px); }
.section--paper .bento-cell--lead .rev-stars { color: var(--gold); }
.section--paper .bento-cell--lead .rev-person { border-top-color: rgba(244, 244, 242, 0.22); }
.section--paper .bento-cell--lead .rev-avatar {
  background: rgba(245, 210, 134, 0.20);
  color: var(--gold);
}
/* The lead's contents are a column of their own, because the cell also holds
   the photograph and the scrim and those are not in the flow. */
.bento-body {
  position: relative;
  display: flex;
  flex-direction: column;
}
.bento-quote { font-size: clamp(19px, 1.7vw, 24px); }

/* The second: nothing but a hairline, so the paper itself is the ground. The
   lightest of the three weights. */
.bento-cell--plain { border: 1px solid rgba(18, 18, 21, 0.13); }

/* The third: the page's one accent, at the weight it can carry behind text.
   Dark ink on this wash measures well past AA, so the cell reads as a tint
   rather than as a second colour scheme. */
.bento-cell--gold { background: rgba(205, 167, 80, 0.20); }
/* The role label is 11.5px, and --ink-3 over this wash measures 3.4:1 - fine
   for a graphic, short of the 4.5:1 small text needs. The next weight up
   clears it. Nothing else on the page needs this because nothing else sets
   small type on a tint. */
.bento-cell--gold .tstm-role { color: var(--ink-2); }
/* The rule over the attribution has to darken on the tint: the paper value
   disappears into it. Both of these have to out-specify .section--paper's own
   pair, which is why they carry it. */
.section--paper .bento-cell--gold .rev-person { border-top-color: rgba(18, 18, 21, 0.22); }
.section--paper .bento-cell--gold .rev-avatar { background: rgba(146, 102, 0, 0.16); }

/* One column below 860px. A 1.25/1 split at that width leaves the lead too
   narrow to carry a photograph and the pair beside it too narrow to hold a
   sentence, and the lead's row span has to go with it or the order scrambles. */
@media (max-width: 860px) {
  .bento { grid-template-columns: 1fr; }
  .bento-cell--lead { grid-row: auto; min-height: clamp(300px, 62vw, 420px); }
}

/* ---- What every quotation on this page is made of ----
   Stars, the quotation, then a rule with the monogram, name and role under
   it. Three cells of the bento and six cards below it share the whole of it;
   only the ground and the type size change. */
.rev-stars {
  display: flex;
  gap: 5px;
  color: var(--gold);
  transition: color var(--theme-dur) var(--ease-theme);
}
.section--paper .rev-stars { color: var(--gold-deep); }

.rev-quote {
  margin: clamp(20px, 2vw, 28px) 0 clamp(30px, 3vw, 42px);
  font-weight: 400;
  line-height: 1.34;
  letter-spacing: -0.02em;
  text-wrap: pretty;
}
/* Hung punctuation: the opening mark sits out in the gutter so the first
   letter starts on the same line everything else does. */
.rev-quote::before { content: '\201C'; margin-left: -0.42em; }
.rev-quote::after { content: '\201D'; }
.review-card .rev-quote { font-size: 17px; line-height: 1.5; color: var(--text); }

/* ---- The attribution ----
   Ruled off from the quotation and pushed to the floor of the card.

   The space above the rule is the quotation's OWN bottom margin, not this
   element's top margin, and that is the whole fix for a bug this had: the
   attribution is pushed down by `margin-top: auto`, and an auto margin is
   free space, which is zero on the card whose quotation is longest. The
   longest review is exactly the one that needs the room, and it was the one
   getting none of it - the rule sat a few pixels under the last line. A
   margin on the quote cannot be absorbed, so it is a floor. */
.rev-person {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: auto;
  padding-top: clamp(18px, 1.8vw, 24px);
  border-top: 1px solid var(--line);
}
.section--paper .rev-person { border-top-color: rgba(18, 18, 21, 0.12); }

/* The monogram. A circle of the page's one accent at the weight it can carry
   behind text, with the initials in it - see the note in the markup for why
   this is not a photograph. Decorative: the name is right beside it. */
.rev-avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: rgba(205, 167, 80, 0.18);
  color: var(--gold);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.04em;
}
.section--paper .rev-avatar { background: rgba(205, 167, 80, 0.26); color: var(--gold-deep); }
.rev-who {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

/* ---- The card grid ----
   The second run of quotations, on the dark ground, at one weight.

   Three to a row, fixed. This was auto-fit at minmax(270px, 1fr), which put
   four on the first row and two on the second on a wide screen - a ragged
   last row reads as a missing card. Six items want two rows of three. */
.review-cards {
  margin: clamp(36px, 4.5vw, 64px) 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(16px, 1.8vw, 26px);
}
.review-card {
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: clamp(24px, 2.6vw, 34px);
  display: flex;
  flex-direction: column;
  transition: border-color 0.4s var(--ease);
}
.review-card:hover { border-color: var(--line-strong); }
.review-card .rev-person { margin-top: auto; }

@media (max-width: 1000px) {
  .review-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .review-cards { grid-template-columns: 1fr; }
}

/* ---- The themes ----
   Three .service-card boxes on the dark ground - the homepage's card, since
   these three are the same anatomy as the services it holds: a mark, a term
   and one sentence. They were three bare .pillar items with an icon where
   the gold keyline goes.

   The heading is centred because the three below are parallel and equal: a
   left-aligned heading would belong to the first of them.

   The card's own step is left in place here, unlike the reviews above. These
   three are read in the order the hero's lede names them, so a descent across
   the row is saying something true. */
.themes-head { text-align: center; margin-inline: auto; max-width: 24ch; }
.themes { margin-top: clamp(34px, 4vw, 56px); }

/* ---------- Team page ----------
   One grid of the homepage rail's portrait cards: five across on a desktop,
   three then two on a tablet, one column on a phone. Flex rather
   than grid so a short last row centers under the row above instead of
   hanging off the left edge.

   The page used to be five rows, card left and biography right. The
   biographies came out on 6 October 2026 at the client's request, and a row
   with nothing beside the picture was mostly empty space. */
.team-section { padding-top: clamp(32px, 4vw, 60px); }
.team-grid {
  --team-gap: clamp(14px, 1.6vw, 24px);
  --team-cols: 5;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--team-gap);
  margin: 0;
  padding: 0;
  list-style: none;
}
.team-grid .team-card {
  /* Height-driven and 2:3 in the homepage rail; here the column sets the
     width and the ratio does the rest. */
  height: auto;
  aspect-ratio: 2 / 3;
  flex: 0 0 calc((100% - (var(--team-cols) - 1) * var(--team-gap)) / var(--team-cols));
}
/* The cards are narrower here than in the rail, so the plate's type comes
   down with them: the longest title has to stay on one line at five across. */
.team-grid .team-name { font-size: clamp(17px, 1.35vw, 21px); }
.team-grid .team-role { font-size: 13.5px; }

@media (max-width: 1100px) {
  .team-grid { --team-cols: 3; }
  .team-grid .team-name { font-size: 20px; }
  .team-grid .team-role { font-size: 14.5px; }
}
/* One column on a phone, at the rail's own card size. Two across was tried
   and is wrong: the name plate's blur is a fixed height, so on a card that
   small it climbs over the face. */
@media (max-width: 640px) {
  .team-grid { --team-gap: 16px; }
  .team-grid .team-card { flex-basis: min(100%, 300px); }
}

/* ---------- Contact page ----------
   The form takes the column the other four subpages give a photograph, and
   the phone, email and address sit under the hero copy at label scale. Both
   decisions come from the same place: the form is what this page is for, so
   nothing next to it should compete with it. Three rows of 26px gold - what
   the contact details used to be - read as a second headline.

   ---- The details block ---- */
/* One under the other, not side by side. They used to run as an auto-fit row
   that put Call and Email on one line with Visit under them, which made the
   address look like a footnote to the other two rather than the third of
   three ways to reach the office. A column also gives the address its own
   measure instead of a third of the copy column's. */
.hero-contact {
  margin: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(18px, 2vw, 26px);
}
.hero-contact-item { min-width: 0; }
.hero-contact-label { display: block; margin-bottom: 8px; }
.hero-contact-value { margin: 0; }
.hero-contact-value a,
.hero-contact-value address {
  font-style: normal;
  font-size: 17px;
  line-height: 1.45;
  color: var(--text);
  text-decoration: none;
  /* Not --gold. Gold at 17px across three items put three coloured blocks in
     a column that is meant to be quiet; the accent is spent on the form's
     button, which is the thing being pointed at. */
  border-bottom: 1px solid transparent;
  transition: border-color 0.3s var(--ease), color 0.3s var(--ease);
}
.hero-contact-value a:hover { color: var(--gold); border-bottom-color: var(--gold); }

/* ---- The form panel ----
   A .panel, the raised surface the escrow service used to use. It is the one
   component on the site that already reads as a thing you interact with
   rather than a thing you read. */
.form-panel { margin-top: 0; }
.form-title {
  margin: 0;
  font-size: clamp(22px, 2vw, 28px);
  font-weight: 400;
  letter-spacing: -0.025em;
  color: var(--text);
}
.form-note {
  margin: 10px 0 clamp(22px, 2.4vw, 30px);
  font-size: 15px;
  color: var(--text-3);
}

/* Two columns for the short fields, one for the message. The floor was 190px,
   which is comfortable but meant the panel fell to a single column anywhere
   under about 1150px - including a 1024 laptop, where there was room for two.
   165px still holds a label and a usable input, and keeps the pair side by
   side down to the width where the hero itself stacks. Below that one column
   is right anyway. */
.cf {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(165px, 1fr));
  gap: clamp(16px, 1.8vw, 22px);
}
.cf-field { min-width: 0; }
.cf-field-full,
.cf-submit,
.cwform-verify,
.form-success { grid-column: 1 / -1; }
/* The spam check is added by the form script as its own row. It needs the
   full row: the widget is at least 300px wide and gets cut off in one column. */
.cwform-verify { min-width: 0; }
.cwform-verify iframe { width: 300px; max-width: 100%; }

/* Label above input, always, and never a placeholder standing in for one.
   These inputs carry no placeholder at all: with a real label above the box,
   a placeholder inside it is a second label that vanishes when you start
   typing. */
.cf label {
  display: block;
  margin-bottom: 8px;
  font-size: 14px;
  color: var(--text-2);
}
.cf-optional {
  color: var(--text-3);
  font-size: 12.5px;
  letter-spacing: 0.04em;
}
.cf input,
.cf textarea {
  width: 100%;
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.5;
  color: var(--text);
  background: rgba(244, 244, 242, 0.04);
  /* A real border rather than a bottom rule. 0.22 looked right and measured
     1.96:1 against the panel, under the 3:1 a control's own boundary is held
     to - an input box you can only just make out is an input box people miss.
     0.38 puts it at 3.2:1 and still reads as a hairline rather than a frame. */
  border: 1px solid rgba(244, 244, 242, 0.38);
  border-radius: 3px;
  padding: 13px 14px;
  transition: border-color 0.25s var(--ease), background-color 0.25s var(--ease);
}
.cf textarea { resize: vertical; min-height: 118px; }
.cf input:hover,
.cf textarea:hover { border-color: rgba(244, 244, 242, 0.55); }
/* Gold, 2px, and outside the box. The default focus ring is a thin
   system-coloured line that is nearly invisible on this ground, and a field
   whose focus you cannot see is a field people lose their place in. */
.cf input:focus-visible,
.cf textarea:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  border-color: var(--gold);
  background: rgba(244, 244, 242, 0.06);
}
.cf-help {
  margin: 8px 0 0;
  font-size: 13.5px;
  color: var(--text-3);
}

/* The bridge injects Cloudflare's Turnstile widget at the top of this block,
   which is why the submit button is not the first thing in it. The widget is
   ~300x65 and must be left alone: see the note in contact.astro. */
.cf-submit {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 18px;
  margin-top: 4px;
}

/* The bridge's own hooks. Error above the button, since that is where the
   reader's eye is when a submit fails. */
.form-error {
  margin: 0;
  font-size: 14.5px;
  line-height: 1.5;
  /* Never colour alone: the copy says what is wrong, and this is the second
     signal rather than the only one. */
  color: #f0a9a0;
}
.form-success {
  margin-top: 4px;
  padding: 16px 18px;
  border: 1px solid var(--gold);
  border-radius: 3px;
  color: var(--text);
  font-size: 15.5px;
}
/* Fields the bridge marks as failing. */
.cf-field.has-error input,
.cf-field-full.has-error textarea { border-color: #f0a9a0; }

/* The not-connected state. See the guard script in contact.astro - this is
   only ever on the page when the CWForm client has not been provisioned, and
   it goes away on its own once it has. */
.cf--unwired input,
.cf--unwired textarea { opacity: 0.55; }
.cf--unwired button[type="submit"] { opacity: 0.45; cursor: not-allowed; }

/* Below the hero's own stacking width the form is the whole column, so the
   two-up fields have room again only once the column is full width. Nothing
   to change but the panel's inset. */
@media (max-width: 620px) {
  .form-panel { padding: clamp(22px, 5vw, 30px); }
}

/* ---------- Legal pages ---------- */

/* One long column of prose, set on a reading measure. Same reservation for
   the absolutely positioned header as the 404. */
.legal { padding-top: calc(var(--nav-h) + clamp(32px, 5vw, 64px)); }
.legal .lede { margin-top: 14px; }
.legal-body {
  max-width: 68ch;
  margin-top: clamp(32px, 4vw, 56px);
  color: var(--text-2);
  line-height: 1.65;
}
.legal-body h2 {
  margin: clamp(36px, 4vw, 52px) 0 14px;
  font-size: 22px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--text);
}
.legal-body p { margin: 0 0 16px; }
.legal-body a {
  color: var(--gold);
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
}
.legal-body a:hover { border-bottom-color: var(--gold); }
.legal-contact { line-height: 1.8; }

/* ---------- Responsive ---------- */

@media (max-width: 1040px) {
  /* Four columns get too narrow to hold "Title, Settlement" on one line here,
     so the proof row folds to 2x2. */
  .hero-proof { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 44px; }
  /* The cards fold to two, and the step comes off with the third one: a card
     that spans the row cannot also be offset from the two above it. */
  .service-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .service-card:nth-child(3) { grid-column: span 2; margin-top: 0; }
  .footer-inner { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 900px) {
  /* Two arches to a row. Four across at this width narrows each dome past the
     point where a two-word term fits under it. */
  .why-statements { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .why-statement:nth-child(even) { margin-top: clamp(32px, 6vw, 64px); }
  /* Two columns leaves one gutter, and the middle line is already on it. The
     other two would fall down the middle of a card. */
  .grid-lines i:nth-child(2),
  .grid-lines i:nth-child(4) { display: none; }

  /* The sticky frame is a desktop mechanism: it works because the photograph
     stands beside the copy, and there is no second column to hold at this
     width. So the section stays the plain stack — four steps read top to
     bottom, image over copy. Those rules are all inside a min-width query, so
     there is nothing to undo here.

     One column: image on top, copy beneath. Side by side at this width the
     image would be a thumbnail and the copy a 20ch column. */
  .focus-pane {
    grid-template-columns: 1fr;
    row-gap: 20px;
  }
  .focus-img { aspect-ratio: 4 / 3; max-height: none; height: auto; }
  .focus-pane-title {
    margin: 0 0 12px;
    max-width: none;
    font-size: clamp(22px, 5.6vw, 26px);
    letter-spacing: -0.03em;
    line-height: 1.15;
  }

  /* The 32% column is unreadable at this width, so the split stacks: heading
     and copy first, the rail under them. 100dvh comes off with it — two full
     viewports of scrolling is not the same section. */
  .about {
    --card-h: min(64vh, 460px);
    min-height: 0;
  }
  .about-body { grid-template-columns: 1fr; row-gap: 36px; }
  /* Height stops matching the cards once the two are stacked rather than side
     by side, and space-between has nothing left to distribute, so the column
     sizes to its content and a gap does the spacing. The right gutter is the
     panel's own here: .about-body has none, so that the rail below it can
     still reach the page edge. */
  .about-panel { height: auto; gap: 30px; padding-right: var(--pad-x); }

  /* The spread stacks: heading, lead, then the two notes, each still with
     its rule and its own size. Nothing is indented — with one column there
     is nothing for an indent to read against. */
  .tstm-spread { grid-template-columns: 1fr; row-gap: clamp(40px, 6vw, 64px); }
  .tstm-intro,
  .tstm-gallery { grid-column: 1 / -1; }

  .hero-inner {
    min-height: auto;
    grid-template-columns: 1fr;
    grid-template-areas: 'copy' 'figure';
    padding-top: calc(var(--nav-h) + clamp(28px, 7vw, 64px));
    row-gap: clamp(28px, 5vw, 48px);
  }
  .hero-figure { justify-content: flex-start; }
  .hero-figure { margin-bottom: 0; }
  .hero-figure img { max-height: min(52vh, 520px); }

  /* Six nav items plus a CTA stop fitting beside the wordmark here. */
  .nav-toggle { display: block; }
  .nav-inner { display: flex; justify-content: space-between; }
  .nav-right {
    display: none;
    position: absolute;
    top: var(--nav-h);
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: var(--void);
    border-bottom: 1px solid var(--line);
    padding: 10px var(--pad-x) 22px;
  }
  .nav-right.is-open { display: flex; }
  .nav-links { flex-direction: column; align-items: stretch; justify-self: stretch; gap: 0; }
  .nav-actions { flex-direction: column; align-items: stretch; gap: 10px; margin-top: 18px; }
  .nav-links li { width: 100%; }
  .nav-links a {
    display: block;
    width: 100%;
    padding: 15px 0;
    border-bottom: 1px solid var(--line);
  }
  .nav-links li > *::after { display: none; }
  .nav-links li:last-child a,
  .nav-links li:last-child .nav-pending { border-bottom: 0; }
  .nav-cta { justify-content: center; }
}

@media (max-width: 720px) {
  /* No body font-size step-down here any more: 18px is 18px on every
     breakpoint, which is what "18px across the site" means. */
  .hero-proof,
  .service-grid { grid-template-columns: 1fr; }
  /* One column: no gutters left for an inner line to sit in, so the grid is
     its two edges. */
  .grid-lines i:nth-child(3) { display: none; }
  /* One to a row: stacked, there is nothing beside a card for the drop to
     read against. The roofs stay — mirrored, they still alternate down the
     column — but flatter, since a full-width card would otherwise carry a
     slope deep enough to read as a wedge. */
  .why-statements { grid-template-columns: 1fr; gap: 20px; }
  .why-statement { --roof: 22px; }
  .why-statement:nth-child(even) { margin-top: 0; }
  /* Stacked, so there is no row left to step across. */
  .service-card:nth-child(3) { grid-column: auto; }
  .service-card:nth-child(2),
  .service-card:nth-child(3) { margin-top: 0; }
  .hero-proof { margin-top: 48px; padding-top: 48px; }
  .hero-figure img { max-height: 42vh; }
  /* No room either side of centred copy at this width: the silhouettes would
     sit under the headline rather than beside it. */
  .finalcta-skyline { display: none; }
  /* Same reason in the hero, where the headline and the buttons now run the
     full measure and the cluster would be behind them rather than beside. */
  .page-hero-skyline { display: none; }
  .footer-inner { grid-template-columns: 1fr; }
  /* One column down here, so the notice and the link stack. On its own line
     the link starts at the margin rather than at the indent that sets it off
     from the notice when the two share a line. */
  .footer-legal a { display: inline-block; margin-left: 0; margin-top: 8px; }
}

/* ---------- Reduced motion ----------
   Everything collapses to a static page: reveals resolve, rules are
   already drawn, the photograph does not settle, hover displaces
   nothing. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  .js-reveal .reveal,
  .js-reveal .reveal.is-in { opacity: 1; transform: none; transition: none; }
  .team-card:hover .team-figure { transform: none; }
  .team-rail { scroll-behavior: auto; }

  .hero-media,
  .hero-figure img,
  .fade-in { animation: none; }

  .btn--primary:hover { padding: 0 66px 0 26px; }
  .btn--primary:hover .btn-icon { left: calc(100% - 53px); }

  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
}
