/* ── DBM Global · prototype ─────────────────────────────────────────
   Fluid canvas · content on a 1200 rail that shrinks with the viewport
   Type: Archivo (display/body) + Inter (labels)
   ------------------------------------------------------------------ */

:root {
  --navy-deep: #0A1A32;
  --navy-900:  #0D1F3B;
  --navy-700:  #12294D;
  --navy-rule: #24405E;

  --blue:      #007CBB;
  --blue-lt:   #7FC4EA;
  --blue-pale: #9FD4F0;
  --blue-soft: #68A6DB;

  --ink:   #12294D;
  --body:  #43505F;
  --muted: #8095AC;
  --dim:   #A9BCD0;

  --mist:  #E9EEF4;
  --mist2: #EDF2F8;
  --rule:  #C9D4E1;
  --rule-lt: #D5DEE9;

  --rail-max: 1200px;
  --gut: 120px;

  --f-display: 'Archivo', system-ui, sans-serif;
  --f-mono: 'Inter', ui-monospace, monospace;

  --ease: cubic-bezier(.22,.61,.36,1);
  /* the header fold travels 138px both ways, so it wants a slow both-ends curve
     rather than the snappy ease-out the rest of the interface uses */
  --hdr-dur: 620ms;
  --hdr-ease: cubic-bezier(.33,.02,.18,1);
}

@media (max-width: 1280px) { :root { --gut: 64px; } }
@media (max-width: 900px)  { :root { --gut: 24px; } }

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }

body {
  margin: 0;
  overflow-x: hidden;
  font-family: var(--f-display);
  font-weight: 400;
  color: var(--body);
  background: #fff;
  -webkit-font-smoothing: antialiased;
}

img { display: block; }
/* figure carries a 40px UA side margin that silently offset the carousel */
figure { margin: 0; }
a { text-decoration: none; color: inherit; }

.rail { width: min(var(--rail-max), 100% - var(--gut) * 2); margin-inline: auto; }

.mono { font-family: var(--f-mono); font-weight: 400; letter-spacing: .12em; font-size: 11px; line-height: 18px; }
.lbl { color: var(--muted); }

.h2 { margin: 0; font-weight: 300; font-size: clamp(28px, 2.8vw, 40px); line-height: 1.3; letter-spacing: -.02em; color: var(--ink); }
.h2--accent { color: var(--blue); }
.h2--onnavy { color: #fff; }
.h3 { margin: 0; font-weight: 300; font-size: 32px; line-height: 40px; letter-spacing: -.02em; color: var(--ink); }
.body { margin: 0; font-size: 16px; line-height: 28px; color: var(--body); }
.body--onnavy { color: var(--dim); }

.split { display: flex; align-items: flex-start; gap: 80px; }
.split__l { flex: 0 1 520px; }
.split__r { flex: 0 1 600px; display: flex; flex-direction: column; gap: 26px; }
/* Once the row stacks, flex-basis stops meaning width and starts meaning HEIGHT,
   so the 520/600 bases were padding every split section with ~1120px of nothing.
   Reset the basis and let both columns take the full width. */
@media (max-width: 1000px) {
  .split { flex-direction: column; gap: 32px; }
  .split__l, .split__r { flex: 0 0 auto; width: 100%; }
}

/* ── button ─────────────────────────────────────────────────────── */
.btn { display: inline-flex; align-items: center; gap: 8px; padding: 6px 6px 6px 24px; border-radius: 2px; width: max-content; }
.btn--white { background: #fff; }
.btn--white .btn__label { color: var(--ink); }
.btn__label { font-size: 14px; line-height: 18px; }
.btn__label.mono { font-size: 11px; line-height: 16px; font-weight: 500; }
.btn__tile {
  width: 40px; height: 40px; flex-shrink: 0; border-radius: 2px; background: var(--blue);
  display: flex; align-items: center; justify-content: center; color: #fff; font-size: 15px; line-height: 1;
  transition: background-color .18s var(--ease), transform .18s var(--ease);
}
.btn__tile--sm { width: 30px; height: 30px; font-size: 13px; }
.btn:hover .btn__tile { background: var(--navy-700); transform: translateX(3px); }

/* Unplated CTA — label, arrow, rule. No box, so it inherits whatever ground it
   sits on and reads as a peer to the nav rather than the only chrome on the page.
   The whole block is the target, not the words: 48px tall clears the touch
   minimum and gives the pointer somewhere to land above and below the text. */
.cta {
  display: inline-flex; flex-direction: column; justify-content: center;
  gap: 11px; height: 48px; width: max-content; color: #fff;
}
.cta__row { display: flex; align-items: center; gap: 14px; }
.cta__label { font-size: 15px; line-height: 22px; }
.cta__arrow { font-size: 14px; line-height: 22px; transition: transform .18s var(--ease); }
.cta__rule {
  display: block; height: 1px; width: 100%; background: currentColor; opacity: .5;
  transition: opacity .18s var(--ease), height .18s var(--ease);
}
.cta:hover .cta__arrow { transform: translateX(6px); }
.cta:hover .cta__rule { opacity: 1; height: 2px; }
/* keyboard focus gets its own outline rather than borrowing the rule, so it is
   never mistaken for a hover */
.cta:focus-visible { outline: 2px solid var(--blue); outline-offset: 6px; }

/* ── header ─────────────────────────────────────────────────────── */
/* Fixed from the first pixel. It used to start absolute and switch to fixed at
   200px, so scrolling from the top let it ride away with the page and then snap
   back — a disappear/reappear rather than a move. Same element throughout now;
   only the backdrop plate changes, and that just fades. */
.hdr { position: fixed; top: 0; left: 0; width: 100%; z-index: 60; }
/* Scrolling down folds it away; scrolling up or hovering the top edge brings it back.
   The IES / investor bar stays put through all of it: the header travels up by its own
   height and the bar is pushed back down by the same amount, so it never leaves. */
/* the fold is a live value now, not a binary state: it tracks scroll directly for
   the first nav-row's worth of travel, then the directional reveal drives it */
.hdr { transform: translateY(calc(-1 * var(--fold, 0px))); }
.hdr.is-eased { transition: transform var(--hdr-dur) var(--hdr-ease); }

.topbar { transform: translateY(var(--fold, 0px)); }
.hdr.is-eased .topbar { transition: transform var(--hdr-dur) var(--hdr-ease); }

/* A transformed element becomes the containing block for its position:fixed
   descendants. The header is transformed for the scroll fold, so the mobile
   takeover — fixed, inset 0, and a child of the header — was resolving against
   the header's own 138px box instead of the viewport. It opened as a sliver
   tucked under the folded bar with the page showing through everywhere else.
   Opening the menu drops the fold, which removes the transform and hands the
   takeover back to the viewport. The header belongs at the top with a menu
   open regardless. */
.hdr.is-open, .hdr.is-open .topbar { transform: none; }

/* invisible catcher along the very top so a hover can summon the folded header */
.peek { position: fixed; top: 0; left: 0; width: 100%; height: 24px; z-index: 59; pointer-events: none; }
.peek.is-armed { pointer-events: auto; }
.hdr__plate {
  position: absolute; inset: 0 0 auto 0; height: 138px; background: var(--navy-deep);
  opacity: 0; pointer-events: none; transition: opacity .22s var(--ease);
}
/* at the very top the bar floats over the hero; hovering it brings the plate in
   so the links stay legible, and it leaves again when the pointer does */
.hdr.is-stuck .hdr__plate, .hdr.is-peeking .hdr__plate { opacity: 1; }

/* the bar outranks the nav row so the fold slides away UNDER it, never across it */
.topbar { position: relative; z-index: 3; height: 34px; background: var(--navy-700); }
.topbar__inner { display: flex; align-items: center; justify-content: space-between; height: 34px; }
.topbar__lede { font-size: 9px; line-height: 12px; color: #8FA6BC; }
.topbar__links { display: flex; gap: 26px; }
.topbar__links a { font-size: 9px; line-height: 12px; color: #fff; opacity: .9; transition: color .16s var(--ease); }
.topbar__links a:hover { color: var(--blue-lt); }

.navrow { position: relative; z-index: 2; height: 104px; display: flex; align-items: center; justify-content: space-between; }
.navrow__logo img { width: 104px; height: 52px; }
.nav { display: flex; align-items: center; gap: 30px; }
.nav a { font-size: 15px; line-height: 18px; color: #fff; padding: 8px 0; position: relative; transition: color .16s var(--ease); }
.nav a::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: var(--blue);
  transform: scaleX(0); transform-origin: left; transition: transform .22s var(--ease);
}
/* the underline is a hover affordance — nothing opens from the nav any more */
.nav a:hover, .nav a.is-on { color: var(--blue-lt); }
.nav a:hover::after, .nav a.is-on::after { transform: scaleX(1); }
@media (max-width: 1100px) { .nav { display: none; } }


/* ── hero ───────────────────────────────────────────────────────── */
/* Full viewport, and pinned: the cover holds at the top of the screen while every
   section after it scrolls up and covers it. That is the whole effect — the copy
   and the logo strip stay put rather than travelling with the page. Sections are
   already z-index 0 and paint in document order, so each one rides over this. */
.hero { position: sticky; top: 0; width: 100%; height: 100vh; height: 100svh; min-height: 640px; overflow: hidden; background: var(--navy-900); }
.hero__stage {
  position: absolute; left: 0; top: -30%; width: 100%; height: 160%;
  background-size: cover; background-position: 50%;
  opacity: 0; transition: opacity .8s var(--ease);
  will-change: transform, opacity;
}
.hero__stage.is-on { opacity: 1; }
/* The cross-fade puts an inline z-index on the incoming layer. Everything that sits
   over the photograph therefore needs to outrank it explicitly, or the image paints
   over the headline, dots, credit and logo strip. */
.hero__scrim, .hero__topfade { z-index: 3; }
.hero__head, .hero__dots, .credit, .powered { z-index: 4; }
.hero__scrim {
  position: absolute; inset: 0;
  /* Taken from the Paper board, not approximated. The bottom stop is a LIGHT BLUE,
     not navy — that lift is what gives the carousel its cast. Rebuilding it in navy
     alone read as a grey wash and lost the colour entirely. */
  background: linear-gradient(0deg,
    rgba(23,108,168,.94) 0%,
    rgba(13,31,59,.62) 46%,
    rgba(13,31,59,.30) 66%,
    rgba(13,31,59,.10) 84%,
    rgba(13,31,59,0) 100%);
  background: linear-gradient(0deg,
    oklab(44.9% -0.035 -0.130 / 94%) 0%,
    oklab(21.9% -0.011 -0.051 / 62%) 46%,
    oklab(21.9% -0.011 -0.051 / 30%) 66%,
    oklab(21.9% -0.011 -0.051 / 10%) 84%,
    oklab(21.9% -0.011 -0.051 / 0%) 100%);
}
.hero__topfade { position: absolute; left: 0; top: 0; width: 100%; height: 190px; background: linear-gradient(180deg, rgba(13,31,59,.58) 0%, rgba(13,31,59,.22) 60%, rgba(13,31,59,0) 100%); }
.hero__head { position: absolute; left: 50%; translate: -50%; top: 30%; }
.hero__title { margin: 0; font-weight: 200; font-size: clamp(64px, 7.8vw, 112px); line-height: 1.07; letter-spacing: -.025em; color: #fff; }
/* the two lines are deliberately far apart in weight — the qualifier recedes, the word carries it */
.hero__title--bold, .ftr__we--bold { font-weight: 500; }
.hero__body { margin: 20px 0 0; max-width: 620px; min-height: 84px; font-size: 17px; line-height: 28px; color: #fff; }

/* Slide changes are a real out-then-in: the copy leaves completely before it is
   replaced, so two slides' text is never on screen at once, then it rises back. */
.hero__body, .credit { transition: opacity .26s var(--ease), transform .26s var(--ease); }
.hero.is-out .credit { opacity: 0; transform: translateY(-10px); }
/* the sub-copy's words now rise out of their own masks, so a block-level rise on
   top of that would be two animations doing one job — the credit keeps it */
.hero.is-in .credit { animation: copyIn .52s var(--ease) both; }
.hero.is-in .credit { animation-delay: 90ms; }
@keyframes copyIn { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
.hero__dots { position: absolute; left: 50%; translate: -50%; bottom: 168px; display: flex; align-items: center; gap: 12px; }
.hero__dots button { width: 34px; height: 3px; border: 0; padding: 0; cursor: pointer; background: rgba(255,255,255,.34); transition: background-color .25s var(--ease); }
.hero__dots button.is-active { background: #fff; }

/* label above, logos as a seamless marquee below */
/* no backplate — the logos ride on the photograph, with only enough tonal
   protection under them to keep white marks legible */
.powered {
  position: absolute; left: 0; bottom: 0; width: 100%; height: 158px;
  background: linear-gradient(180deg, rgba(10,26,50,0) 0%, rgba(10,26,50,.30) 34%, rgba(10,26,50,.42) 100%);
  padding-top: 26px; overflow: hidden;
}
.powered__label { display: block; color: var(--blue-lt); letter-spacing: .14em; }
.marquee { position: relative; margin-top: 30px; overflow: hidden; }
/* the track holds the sequence twice; translating by exactly one copy loops seamlessly */
.marquee__track { display: flex; align-items: center; width: max-content; animation: marquee var(--dur, 42s) linear infinite; }
.marquee__track img { width: 108px; height: 36px; margin-right: 132px; object-fit: contain; opacity: .92; }
@keyframes marquee { to { transform: translate3d(calc(var(--loop) * -1), 0, 0); } }
.powered:hover .marquee__track { animation-play-state: paused; }
/* No edge fades on the logo scroll. They were meant to soften where the marquee
   leaves the viewport, but over a photograph they read as two navy panels sitting
   on the image rather than as a fade. The track just clips at the edge. */

/* subtle project credit on the hero photograph */
.credit {
  position: absolute; right: var(--gut); bottom: 186px; margin: 0; z-index: 3;
  text-align: right; font-family: var(--f-mono); font-size: 10px; line-height: 16px; letter-spacing: .12em;
  transition: opacity .5s var(--ease);
}
.credit b { display: block; font-weight: 400; color: rgba(255,255,255,.72); }
.credit span { display: block; color: rgba(255,255,255,.4); }

/* ── knockout quote: scroll-linked word highlight ───────────────── */
/* Sections layer in document order. Without this a positioned child — a portfolio
   cell, which needs position for its hover reveal — paints over the section that
   follows it, so the grid animated in on top of Markets instead of under it. */
.page > section { position: relative; z-index: 0; }
/* .page > section outranks .hero on specificity, so the pin has to match it */
.page > .hero { position: sticky; top: 0; }

.statement { background: var(--blue); padding: clamp(64px, 8vw, 110px) 0; }
.statement__copy { margin: 0; max-width: 900px; font-weight: 300; font-size: clamp(28px, 3.4vw, 46px); line-height: 1.35; letter-spacing: -.02em; }
.statement__copy w {
  color: rgba(255,255,255,.28);
  transition: color .32s var(--ease);
}
.statement__copy w.is-lit { color: #fff; }

/* ── integrated delivery · pinned scroll ────────────────────────── */
.delivery { position: relative; background: var(--mist); }
.delivery__pin { position: sticky; top: 0; height: 100vh; min-height: 720px; display: flex; flex-direction: column; justify-content: center; overflow: hidden; }
.delivery__head { padding-bottom: 48px; }
/* scroll-linked handoff at both ends of the pin — see the delivery module */
.delivery__head, .delivery__stage {
  opacity: var(--enter, 1);
  transform: translate3d(0, var(--shift, 0px), 0);
  will-change: opacity, transform;
}
.delivery__stage {
  position: relative; display: flex; align-items: center;
  padding-right: max(var(--gut), calc((100% - var(--rail-max)) / 2));
}
/* The phase names line up with the body copy above them, and the photograph stops
   90px short of the rule so the two never crowd each other. */
.delivery__visuals { position: relative; flex: 1 1 auto; min-width: 0; margin-right: 90px; aspect-ratio: 5 / 4; max-height: 54vh; overflow: hidden; }
.delivery__visuals figure { position: absolute; inset: 0; margin: 0; background-size: cover; background-position: 50%; opacity: 0; transform: scale(1.06); transition: opacity .55s var(--ease), transform .8s var(--ease); }
.delivery__visuals figure.is-on { opacity: 1; transform: none; }
/* 600, not 630 — the stage is padded to the rail on the right, so a 600 column
   puts the rule exactly on the viewport centre line at every width. It was landing
   30px left of centre, which put it out of step with the services rail below. */
.delivery__phases { flex: 0 1 600px; display: flex; flex-direction: column; }

.phase__list { position: relative; padding-left: 30px; }
.phase__rule { position: absolute; left: 0; top: 0; bottom: 0; width: 2px; background: var(--rule); }
/* the marker spans the open phase: its name plus the copy beneath it */
.phase__marker {
  position: absolute; left: 0; top: 0; width: 2px; background: var(--blue);
  height: calc(var(--row, 67px) + var(--dh, 0px));
  transition: transform .58s var(--ease), height .5s var(--ease);
}
.phase { cursor: pointer; border-bottom: 1px solid var(--rule-lt); }
.phase:first-child { border-top: 1px solid var(--rule-lt); }
.phase__row { display: flex; align-items: center; height: 66px; }
.phase__t {
  font-size: 26px; line-height: 34px; letter-spacing: -.02em; color: #8095AC;
  transition: color .3s var(--ease);
}
.phase.is-on .phase__t { color: var(--ink); }
.phase:hover .phase__t { color: var(--ink); }

/* every phase opens to the same reserved height, set at runtime from the longest
   paragraph, so the list is one size and each opens at one rate */
.phase__d {
  height: 0; overflow: hidden; opacity: 0;
  transition: height .5s var(--ease), opacity .32s var(--ease);
}
.phase.is-on .phase__d { height: var(--dh, 0px); opacity: 1; }
.phase__inner { padding-bottom: 0; }
.phase__d p { margin: 0; max-width: 500px; font-size: 16px; line-height: 28px; color: var(--body); }
.phase__pending { display: block; font-size: 10px; letter-spacing: .12em; color: var(--blue); }

/* measuring must see the real content height, not the collapsed state */
.phase__list.is-measuring .phase__d { height: auto; opacity: 1; transition: none; }

@media (max-width: 1000px) {
  /* The pin holds here too — image and rail lock to the screen while the phases
     step through, same as desktop. It used to go static, which is why the section
     just scrolled past on a phone. The stage stacks instead of splitting, and the
     photograph is capped so image + list still fit one screen. --dh stays reserved
     (see lockHeight) so the pinned block is one height and never reflows. */
  /* The sticky header sits over the top of the pin, and now that the photograph
     takes every spare pixel the head is pushed right under it — the first line of
     the title was hidden. Reserve the folded header's height. */
  .delivery__pin { height: 100svh; min-height: 0; padding: 46px 0 0; }
  .delivery__stage { flex-direction: column; align-items: stretch; padding-right: 0; }
  .delivery__phases { flex: 0 0 auto; margin-left: 0; padding-top: 24px; }
  .delivery__visuals { margin-right: 0; aspect-ratio: 16 / 11; max-height: 28svh; }
}

/* ── portfolio wall ─────────────────────────────────────────────── */
.portfolio { background: var(--navy-900); padding-top: 96px; }
.split--onnavy { padding-bottom: 56px; }
.wall { display: grid; grid-template-columns: repeat(4, 1fr); }
@media (max-width: 1000px) { .wall { grid-template-columns: repeat(2, 1fr); } }
.wall__cell { position: relative; aspect-ratio: 1 / 1; overflow: hidden; }
.wall__bg { position: absolute; inset: 0; background-size: cover; background-position: 50%; transform: scale(1); transition: transform .7s var(--ease); }
.wall__cell:hover .wall__bg { transform: scale(1.06); }
/* rest gradient — light, only where the logo and location sit */
.wall__cell::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(0deg, rgba(10,26,50,.92) 0%, rgba(10,26,50,.72) 30%, rgba(10,26,50,.12) 66%, rgba(10,26,50,0) 84%);
  transition: opacity .4s var(--ease);
}
/* hover gradient — deeper and taller, so the revealed copy has something to sit on */
.wall__cell::before {
  content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none; opacity: 0;
  background: linear-gradient(0deg, rgba(10,26,50,.95) 0%, rgba(10,26,50,.88) 44%, rgba(10,26,50,.5) 74%, rgba(10,26,50,.18) 100%);
  transition: opacity .4s var(--ease);
}
.wall__cell:hover::before { opacity: 1; }
.wall__cell:hover::after { opacity: 0; }

.wall__copy { position: absolute; left: 32px; bottom: 32px; right: 32px; z-index: 2; display: flex; flex-direction: column; gap: 14px; }
.wall__copy img { width: 150px; height: 34px; object-fit: contain; object-position: left center; }
.wall__loc { font-size: 10px; color: #B9CBDD; }

/* the block is bottom-anchored, so the reveal grows upward without shifting the logo lane */
.wall__reveal {
  display: grid; grid-template-rows: 0fr; opacity: 0;
  transition: grid-template-rows .42s var(--ease), opacity .3s var(--ease);
}
.wall__cell:hover .wall__reveal,
.wall__cell:focus-within .wall__reveal { grid-template-rows: 1fr; opacity: 1; }
.wall__revealed { min-height: 0; overflow: hidden; display: flex; flex-direction: column; gap: 14px; }
.wall__desc { margin: 0; font-size: 15px; line-height: 24px; color: #D3DFEC; }
.wall__cta { display: inline-flex; align-items: center; gap: 10px; width: max-content; cursor: pointer; }
.wall__cta:hover .mono { color: var(--blue-lt); }
.wall__cta:hover i { transform: translate(2px, -2px); }
.wall__cta .mono { font-size: 10px; font-weight: 500; color: #fff; }
/* no tile behind the arrow — it was the only filled block left on the wall and
   read as a button pasted onto the photograph */
.wall__cta i {
  flex-shrink: 0; font-style: normal; font-size: 13px; color: #fff;
  transition: transform .2s var(--ease);
}
.wall__cell:hover .wall__cta i { transform: translate(2px, -2px); }

/* ── markets ────────────────────────────────────────────────────── */
.markets { background: var(--mist2); padding: clamp(72px, 8vw, 118px) 0 clamp(72px, 8vw, 110px); overflow: hidden; }
.markets__head { padding-bottom: 64px; }
/* the copy column narrows so the photography can run wider, and both start level */
.markets__body { display: flex; align-items: flex-start; gap: 56px; padding-left: max(var(--gut), calc((100% - var(--rail-max)) / 2)); }
.markets__col { width: 344px; flex-shrink: 0; display: flex; flex-direction: column; }

.switch { display: flex; align-items: flex-end; gap: 36px; margin-top: 26px; }
.switch__tab {
  border: 0; background: none; padding: 0; cursor: pointer; display: flex; flex-direction: column; gap: 12px;
  font-family: var(--f-display); font-size: 22px; line-height: 30px; letter-spacing: -.01em; color: var(--muted); font-weight: 400;
  transition: color .2s var(--ease);
}
.switch__tab i { display: block; height: 2px; background: var(--rule); transition: background-color .2s var(--ease); }
.switch__tab:hover { color: var(--ink); }
.switch__tab.is-active { color: var(--ink); font-weight: 300; }
.switch__tab.is-active i { background: var(--blue); }

.markets__project { margin: -6px 0 26px; font-size: 24px; line-height: 32px; }
.spec { margin: 0; display: flex; flex-direction: column; transition: opacity .25s var(--ease); }
.spec__row { display: flex; flex-direction: column; gap: 5px; padding: 15px 0; border-top: 1px solid var(--rule-lt); }
.spec__row:last-child { border-bottom: 1px solid var(--rule-lt); }
.spec__row dt { font-family: var(--f-mono); font-size: 10px; line-height: 18px; letter-spacing: .12em; color: var(--muted); flex-shrink: 0; }
.spec__row dd { margin: 0; font-size: 15px; line-height: 24px; color: var(--ink); }
/* project copy: out, replace, then rise back with the rows staggered */
/* the counter holds still — only the project copy transitions */
.markets__project, .spec__row { transition: opacity .26s var(--ease), transform .26s var(--ease); }
.markets__col.is-out .markets__project,
.markets__col.is-out .spec__row { opacity: 0; transform: translateY(-10px); }
.markets__col.is-in .markets__project,
.markets__col.is-in .spec__row { animation: copyIn .52s var(--ease) both; animation-delay: var(--d, 0ms); }

.pager { display: flex; align-items: center; gap: 16px; margin-top: 32px; }
.circ {
  width: 48px; height: 48px; flex-shrink: 0; border-radius: 24px; border: 1px solid #B9C7D6; background: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center; font-size: 15px; color: var(--ink);
  transition: background-color .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease);
}
.circ:hover { background: var(--navy-700); border-color: var(--navy-700); color: #fff; }
.circ--fill { background: var(--blue); border-color: var(--blue); color: #fff; }
.circ--fill:hover { background: var(--navy-700); border-color: var(--navy-700); }
.pager__count { margin-left: 12px; color: var(--muted); }

/* carousel — a real track that slides, with the next slide bleeding off-canvas */
.carousel { flex: 1; min-width: 0; overflow: hidden; cursor: grab; }
.carousel.is-dragging { cursor: grabbing; }
.carousel__track { display: flex; gap: 20px; will-change: transform; transition: transform .68s var(--ease); }
.carousel__slide {
  position: relative; width: 760px; height: 500px; flex-shrink: 0; overflow: hidden;
  background-size: cover; background-position: 50%;
  filter: saturate(.92); transition: filter .5s var(--ease);
}
.carousel__slide.is-on { filter: none; }
.carousel__slide figcaption {
  position: absolute; left: 28px; bottom: 24px; z-index: 2;
  font-family: var(--f-mono); font-size: 10px; letter-spacing: .12em; color: #fff;
  opacity: 0; transform: translateY(6px); transition: opacity .4s var(--ease), transform .4s var(--ease);
}
.carousel__slide::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(0deg, rgba(10,26,50,.7) 0%, rgba(10,26,50,0) 42%);
  opacity: 0; transition: opacity .4s var(--ease);
}
.carousel__slide.is-on figcaption { opacity: 1; transform: none; }
.carousel__slide.is-on::after { opacity: 1; }
/* stretch again once stacked, or flex-start collapses the column to its content */
@media (max-width: 1000px) { .markets__body { flex-direction: column; align-items: stretch; padding-right: var(--gut); } .markets__col { width: 100%; } }

/* ── fast facts ─────────────────────────────────────────────────── */
.facts { display: grid; grid-template-columns: 1fr 1fr; background: var(--navy-900); }
@media (max-width: 1000px) { .facts { grid-template-columns: 1fr; } }
.facts__hero { position: relative; min-height: 620px; overflow: hidden; }
.facts__img { position: absolute; left: 0; top: -30%; width: 100%; height: 160%; background-size: cover; background-position: 50%; transition: opacity .5s var(--ease); will-change: transform; }
.facts__scrim { position: absolute; inset: 0; background: linear-gradient(0deg, rgba(10,26,50,.94) 0%, rgba(10,26,50,.82) 32%, rgba(10,26,50,.2) 58%, rgba(10,26,50,0) 78%); }
/* sits on the page gutter like every other block, not on its own 60px inset */
.facts__herocopy { position: absolute; left: var(--gut); right: 60px; bottom: 56px; display: flex; flex-direction: column; gap: 16px; }
.facts__grid { display: grid; grid-template-columns: 1fr 1fr; }
.facts__cell {
  padding: 44px; display: flex; flex-direction: column; gap: 12px; cursor: pointer;
  border-bottom: 1px solid var(--navy-rule); border-right: 1px solid var(--navy-rule);
  transition: background-color .28s var(--ease);
}
/* the outer column stops on the rail rather than running to the page edge */
.facts__cell:nth-child(2n) { border-right: 0; padding-right: var(--gut); }
.facts__cell:nth-child(n+3) { border-bottom: 0; }
.facts__cell:hover { background: rgba(255,255,255,.04); }
.facts__cell:hover .num { color: var(--blue-pale); }

.num { margin: 0; font-weight: 300; letter-spacing: -.04em; color: #fff; font-size: 72px; line-height: 80px; display: flex; align-items: baseline; gap: 8px; transition: color .25s var(--ease); }
.num--xl { font-size: clamp(88px, 9vw, 140px); line-height: 1.06; gap: 10px; }
.num__unit { font-weight: 400; font-size: 26px; line-height: 34px; color: var(--blue-lt); letter-spacing: -.02em; }
.num--xl .num__unit { font-size: 44px; line-height: 52px; }
.sub { color: #8FA6BC; }
.facts .lbl--bright { color: var(--blue-lt); }
.facts__herocopy .lbl--bright { color: var(--blue-pale); }
.facts__herocopy .sub { color: #B9CBDD; }
.facts__herocopy > * { animation: fadeUp .5s var(--ease); }
@keyframes fadeUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* ── safety ─────────────────────────────────────────────────────── */
.safety { background: #fff; }
.safety__band { position: relative; width: 100%; height: clamp(420px, 46vw, 560px); overflow: hidden; }
/* the layer overhangs its frame top and bottom — that surplus is the parallax travel */
.safety__img { position: absolute; left: 0; top: -35%; width: 100%; height: 170%; background: url('assets/img/safety.jpg') 50% / cover; will-change: transform; }
.safety__scrim { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(18,41,77,.16) 0%, rgba(18,41,77,.22) 34%, rgba(18,41,77,.62) 66%, rgba(18,41,77,.76) 100%); }
.safety__copy { position: absolute; left: 50%; translate: -50%; bottom: 60px; }
.safety__t1, .safety__t2 { margin: 0; font-size: clamp(34px, 3.7vw, 52px); line-height: 1.2; letter-spacing: -.02em; color: #fff; }
.safety__t1 { font-weight: 300; }
.safety__t2 { font-weight: 200; }
.safety__detail { padding: 88px 0 96px; }

/* ── footer ─────────────────────────────────────────────────────── */
/* the page sits above the footer; scrolling down slides it off and uncovers the footer */
.page { position: relative; z-index: 2; background: #fff; }

.ftr {
  position: fixed; left: 0; bottom: 0; width: 100%; z-index: 1;
  background: var(--navy-900); color: #fff; overflow: hidden;
}
.ftr__bg { position: absolute; inset: 0; overflow: hidden; }
.ftr__img {
  position: absolute; left: 0; top: -18%; width: 100%; height: 136%;
  background: url('assets/img/field-crew.jpg') 50% 40% / cover;
  will-change: transform;
}
.ftr__inner, .ftr__legal { will-change: transform, opacity; }
.ftr::before {
  content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(180deg, rgba(10,26,50,.86) 0%, rgba(13,31,59,.94) 44%, rgba(18,41,77,.985) 100%);
}
/* five proportional columns that fill the 1200 rail exactly, so the last one ends
   on the rail edge instead of wherever its content happened to run to */
/* Four columns per the board: brand lockup, one link column, companies, address.
   Connect sits under the brand and Follow under the address, on a second row. */
.ftr__inner { position: relative; z-index: 2; display: grid; grid-template-columns: 1.7fr .8fr 1.05fr 1.25fr; gap: 60px 48px; padding: 88px 0 64px; }
/* The lockup sits at the TOP of the row, not centred in it — otherwise the mark
   floats below the columns beside it. Its own three parts stay centred on each
   other, so the logo top lines up with the labels on the right. */
.ftr__brand { grid-column: 1; grid-row: 1; align-self: start; display: flex; align-items: center; gap: 29px; }
.ftr__links { grid-column: 2; grid-row: 1; }
.ftr__cos   { grid-column: 3; grid-row: 1; }
.ftr__addr  { grid-column: 4; grid-row: 1; }
.ftr__links a { white-space: nowrap; }
/* Hairline between the mark and the statement. Measured off the board: 103px,
   the height of the statement block — not the full column. Stretching it made it
   read as a long thin ruler running past both sides of the lockup. */
.ftr__divide { width: 1px; height: 103px; flex-shrink: 0; background: rgba(255,255,255,.38); }
@media (max-width: 1100px) { .ftr__inner { grid-template-columns: repeat(2, 1fr); } }
/* The reveal parks the footer with position: fixed. Once it is taller than the
   viewport that clips its top off with no way to reach it, so below the desktop
   layout it goes back to being an ordinary block that scrolls. */
/* The reveal is gated on FIT, not on width: app.js parks the footer only when
   it is shorter than the viewport, and falls back to a plain block when it is
   not. `.ftr.is-flow` is what it sets in that case. */
.ftr.is-flow { position: static; }
.page.is-flow { margin-bottom: 0 !important; }
.ftr__we { margin: 0; font-weight: 200; font-size: 42px; line-height: 50px; letter-spacing: -.02em; color: #fff; }
/* the mark tops out with the labels on the right rather than centring against
   the taller statement beside it */
.ftr__logo { width: 183px; flex-shrink: 0; align-self: flex-start; }
.ftr__logo img { width: 183px; height: 88px; }
/* The other columns carry a mono label above their content. The links have none,
   so they get that label's height plus its gap as padding — otherwise the column
   sits one line proud of everything beside it. */
.ftr__links { display: flex; flex-direction: column; gap: 6px; padding-top: 30px; }
.ftr__links a { font-size: 14px; line-height: 20px; color: #C6CACE; transition: color .16s var(--ease); }
.ftr__links a:hover { color: #fff; }
.ftr__addr p, .ftr__cos li { margin: 0; font-size: 14px; line-height: 20px; color: #C6CACE; }
.ftr__addr { display: flex; flex-direction: column; gap: 12px; }
.ftr__cos { display: flex; flex-direction: column; gap: 12px; }
.ftr__cos ul { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 6px; }
.ftr__connect { grid-column: 1; grid-row: 2; display: flex; flex-direction: column-reverse; align-items: flex-start; gap: 14px; }
.ftr__connectline { margin: 0; width: 298px; font-size: 14px; line-height: 20px; color: #C6CACE; }
/* footer sits on navy, so the CTA inherits white from .cta and needs no variant */
.ftr__cta { width: 170px; }
.ftr__follow { grid-column: 4; grid-row: 2; display: flex; flex-direction: column; gap: 16px; }
.ftr__social { display: flex; gap: 12px; }
.ftr__social a { width: 34px; height: 34px; border: 1px solid rgba(255,255,255,.28); display: flex; align-items: center; justify-content: center; color: #fff; transition: background-color .18s var(--ease), border-color .18s var(--ease); }
.ftr__social a:hover { background: var(--blue); border-color: var(--blue); }
.ftr__legal { position: relative; z-index: 2; height: 68px; border-top: 1px solid rgba(255,255,255,.14); }
.ftr__legalrow { display: flex; align-items: center; justify-content: space-between; height: 67px; font-family: var(--f-mono); font-size: 11px; letter-spacing: .12em; color: #8FA6BC; }

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition-duration: .01ms !important; }
}

/* ═══ MOBILE ═══════════════════════════════════════════════════════
   Matches the Paper artboard `DBMG — Landing page · MOBILE @390`
   ================================================================ */

.burger { display: none; }
.mobnav { display: none; }

@media (max-width: 760px) {
  :root { --gut: 20px; }

  /* header ------------------------------------------------------- */
  .topbar__links { display: none; }
  .topbar__lede { font-size: 8px; }
  .topbar, .topbar__inner { height: 30px; }
  .navrow { height: 82px; }
  .navrow__logo img { width: 84px; height: 42px; }
  .navrow__cta { display: none; }
  .hdr__plate { height: 112px; }

  .burger {
    display: flex; flex-direction: column; justify-content: space-between;
    width: 36px; height: 22px; padding: 0; border: 0; background: none; cursor: pointer;
  }
  .burger i { display: block; height: 2px; background: #fff; transition: transform .3s var(--ease), opacity .2s var(--ease), width .3s var(--ease); }
  .burger i:nth-child(1) { width: 36px; }
  .burger i:nth-child(2) { width: 36px; }
  .burger i:nth-child(3) { width: 24px; }
  .burger[aria-expanded="true"] i:nth-child(1) { transform: translateY(10px) rotate(45deg); }
  .burger[aria-expanded="true"] i:nth-child(2) { opacity: 0; }
  .burger[aria-expanded="true"] i:nth-child(3) { width: 36px; transform: translateY(-10px) rotate(-45deg); }

  /* Full-screen takeover, matching the Paper board: hero photograph under a heavy
     navy wash, five rules, then the contact CTA and the standing details. */
  .mobnav {
    position: fixed; inset: 0; z-index: 1;
    display: flex; flex-direction: column; justify-content: flex-end;
    background: var(--navy-deep); overflow: hidden;
  }
  .mobnav[hidden] { display: none; }

  .mobnav__bg {
    position: absolute; inset: 0;
    background: url('assets/img/hero.jpg') 60% 40% / cover;
  }
  .mobnav__scrim {
    position: absolute; inset: 0;
    background: linear-gradient(180deg, rgba(10,26,50,.97) 0%, rgba(10,26,50,.93) 42%, rgba(10,26,50,.86) 72%, rgba(10,26,50,.94) 100%);
  }
  /* the topbar and logo stay above the takeover, and the burger itself is the
     close control — it already morphs into the X the board shows */
  .topbar { z-index: 3; }
  .navrow { z-index: 2; }
  .navrow { position: relative; }

  .mobnav__inner {
    position: relative; z-index: 2;
    padding: 136px var(--gut) 34px; display: flex; flex-direction: column;
    height: 100%; overflow-y: auto;
  }
  .mobnav__list { list-style: none; margin: 0; padding: 0; }
  .mobnav__list a {
    display: block; padding: 20px 0; border-bottom: 1px solid var(--navy-rule);
    font-size: 32px; line-height: 40px; font-weight: 300; letter-spacing: -.02em; color: #fff;
  }
  /* scaled up in the takeover so it reads as a peer to the 32px nav items and
     keeps a comfortable touch target */
  .mobnav__cta { margin-top: 34px; align-self: flex-start; height: 52px; min-width: 170px; }
  .mobnav__cta .cta__label { font-size: 19px; line-height: 26px; }
  .mobnav__cta .cta__arrow { font-size: 17px; line-height: 26px; }

  .mobnav__media { margin-top: 40px; display: flex; flex-direction: column; gap: 14px; }
  .mobnav__lbl { color: var(--blue-lt); font-size: 10px; line-height: 16px; }
  .mobnav__sub { display: flex; flex-wrap: wrap; column-gap: 28px; row-gap: 10px; }
  .mobnav__sub a { font-size: 15px; line-height: 24px; color: var(--dim); }

  .mobnav__foot {
    margin-top: auto; padding-top: 22px; border-top: 1px solid var(--navy-rule);
    display: flex; align-items: flex-end; justify-content: space-between; gap: 20px;
  }
  .mobnav__where { display: flex; flex-direction: column; gap: 6px; }
  .mobnav__where .mono, .mobnav__lang .mono { font-size: 10px; line-height: 16px; color: var(--muted); }
  .mobnav__lang { display: flex; align-items: center; gap: 14px; }
  .mobnav__lang .mono.is-on { color: #fff; font-weight: 500; }
  .mobnav__lang i { width: 1px; height: 12px; background: #3A5878; }

  /* hero --------------------------------------------------------- */
  .hero__head { top: auto; bottom: 208px; }
  .hero__title { font-size: 52px; line-height: 58px; }
  .hero__body { margin-top: 18px; font-size: 15px; line-height: 26px; min-height: 104px; }
  .hero__dots { left: var(--gut); translate: none; bottom: 158px; gap: 8px; }
  .credit { bottom: 166px; }
  .powered { height: 140px; padding-top: 20px; }
  .marquee__track img { width: 92px; height: 30px; margin-right: 56px; }
  .hero__dots button { width: 28px; }


  /* statement ---------------------------------------------------- */
  .statement { padding: 52px 0; }
  .statement__copy { font-size: 28px; line-height: 38px; }

  /* delivery ----------------------------------------------------- */
  /* the runway set in JS has to survive here — clearing the height was what
     unpinned the section on phones */
  .delivery__pin { min-height: 0; }
  /* Everything in the pin has to clear one phone screen: head, photograph, the
     four rows and the open paragraph. The head goes compact and the photograph
     becomes a band rather than a panel so the rail keeps its room. */
  .delivery__head { padding-bottom: 18px; gap: 18px; }
  .delivery__head .h2 { font-size: 24px; }
  .delivery__head .split__r { font-size: 14px; line-height: 22px; }
  .delivery__stage { padding-right: 0; }
  /* Edge to edge, and the height is measured in JS from whatever the head and the
     rail leave behind — a fixed svh cap turned the photograph into a letterbox on
     every phone. The value here is only the pre-measurement fallback. */
  .delivery__visuals { margin-right: 0; aspect-ratio: 3 / 2; max-height: 26svh; }
  .delivery__phases { padding: 16px var(--gut) 0; }
  .phase__row { height: 52px; }
}

/* The fourth phase was running off the bottom on every phone shorter than 844
   once the real copy arrived — the reserved open height grew with it. This trades
   image height and type size, never the number of rows, so all four stay on one
   screen. Threshold raised from 700 to 860 because almost no phone cleared it. */
@media (max-width: 760px) and (max-height: 860px) {
  .delivery__head .h2 { font-size: 21px; }
  .delivery__head .split__r { font-size: 13px; line-height: 20px; }
  .delivery__head { padding-bottom: 12px; gap: 12px; }
  .delivery__phases { padding-top: 12px; }
  .phase__row { height: 46px; }
  /* These two were declared twice, and the looser pair won — which is why the
     tightening this block exists for never actually happened. */
  .phase__t { font-size: 19px; line-height: 26px; }
  .phase__d p { font-size: 13px; line-height: 21px; }

  /* portfolio ---------------------------------------------------- */
  .portfolio { padding-top: 56px; }
  .split--onnavy { padding-bottom: 36px; }
  .wall { grid-template-columns: repeat(2, 1fr); }
  .wall__cell { aspect-ratio: 1 / 1; }
  .wall__copy { left: 20px; right: 20px; bottom: 20px; gap: 10px; }
  .wall__copy img { width: 104px; height: 26px; }
  .wall__loc { font-size: 9px; }
  .wall__desc { font-size: 13px; line-height: 21px; }
  .wall__cta .mono { font-size: 9px; }

  /* markets ------------------------------------------------------ */
  .markets { padding: 56px 0; }
  .markets__head { padding-bottom: 32px; }
  .markets__body { flex-direction: column; gap: 28px; padding-left: var(--gut); padding-right: 0; }
  .markets__col { width: auto; padding-right: var(--gut); }
  .switch { margin-top: 18px; margin-bottom: 0; gap: 28px; }
  .switch__tab { font-size: 19px; line-height: 26px; }
  .markets__project { font-size: 22px; line-height: 30px; }
  .spec__row dt { font-size: 9px; }
  .spec__row dd { font-size: 14px; line-height: 22px; }
  .circ { width: 44px; height: 44px; }
  .carousel__slide { width: 300px; height: 360px; }
  .carousel__track { gap: 12px; }

  /* facts -------------------------------------------------------- */
  .facts { grid-template-columns: 1fr; }
  .facts__hero { min-height: 340px; }
  .facts__herocopy { left: var(--gut); right: var(--gut); bottom: 28px; gap: 10px; }
  .num--xl { font-size: 82px; line-height: 88px; }
  .num--xl .num__unit { font-size: 28px; line-height: 34px; }
  .facts__cell { padding: 24px var(--gut); gap: 8px; }
  .num { font-size: 44px; line-height: 50px; }
  .num__unit { font-size: 18px; line-height: 24px; }
  .facts .mono { font-size: 9px; }

  /* safety ------------------------------------------------------- */
  .safety__band { height: 320px; }
  .safety__copy { bottom: 28px; }
  .safety__t1, .safety__t2 { font-size: 32px; line-height: 40px; }
  .safety__detail { padding: 48px 0 56px; }

  /* footer ------------------------------------------------------- */
  .ftr__inner { grid-template-columns: 1fr 1fr; gap: 36px 40px; padding: 52px 0 40px; }
  .ftr__logo { width: 152px; }
  .ftr__logo img { width: 152px; height: 73px; }
  .ftr__statement { grid-column: 1 / -1; }
  .ftr__we { font-size: 34px; line-height: 42px; }
  .ftr__addr, .ftr__cos { grid-column: 1 / -1; }
  .ftr__connect { grid-column: 1 / -1; }
  .ftr__follow { grid-column: 1 / -1; }
  .ftr__legalrow { font-size: 9px; }
}

@media (max-width: 380px) {
  .carousel__slide { width: 260px; height: 320px; }
  .hero__title { font-size: 46px; line-height: 52px; }
}

/* ── services accordion ─────────────────────────────────────────── */
/* Text on the rail's left edge, photograph bleeding to the right edge of the
   viewport, and the rule sitting in the gutter between them — 30px off the copy,
   which puts it on the frame's centre line at 1440. */
.services { background: var(--mist); padding: 96px 0 104px; overflow: hidden; }
.services__inner {
  display: flex; align-items: flex-start;
  padding-left: max(var(--gut), calc((100% - var(--rail-max)) / 2));
}
/* the column, not the section, owns the rail — so the rule is exactly as tall as
   the list and never runs on past the last row */
.services__col { position: relative; flex: 0 0 570px; min-width: 0; }
.services__rail {
  position: absolute; top: 0; left: calc(100% + 30px);
  width: 2px; height: 100%; background: var(--rule); display: block;
}
/* the lit segment belongs to its row, so it is exactly as tall as that row with
   no measurement at all */
.sv { position: relative; }
.sv__mark {
  position: absolute; top: 0; left: calc(100% + 30px);
  width: 2px; height: 100%; background: var(--blue); display: block;
  opacity: 0; transition: opacity .34s var(--ease);
}
.sv.is-on .sv__mark { opacity: 1; }
.services__visual {
  flex: 1 1 auto; margin-left: 150px; align-self: stretch; min-height: 482px;
  background-size: cover; background-position: 50%;
}

.sv { border-bottom: 1px solid var(--rule); cursor: pointer; }
.sv:first-child { border-top: 1px solid var(--rule); }
.sv__t {
  display: block; padding: 22px 0; font-size: 26px; line-height: 34px;
  letter-spacing: -.02em; color: #5A6C82;
  transition: color .3s var(--ease), font-size .3s var(--ease);
}
.sv.is-on .sv__t { font-size: 32px; color: var(--ink); font-weight: 500; padding-bottom: 0; }
.sv:hover .sv__t { color: var(--ink); }
/* every row opens to the same reserved height, taken from the longest entry, so
   the rail and the rows beneath it move the same distance whichever you pick */
.sv__d { height: 0; overflow: hidden; opacity: 0; transition: height .5s var(--ease), opacity .32s var(--ease); }
.sv.is-on .sv__d { height: var(--sh, 0px); opacity: 1; }
.sv__d p { margin: 16px 0 30px; font-size: 16px; line-height: 28px; color: var(--body); }
.sv__list.is-measuring .sv__d { height: auto; opacity: 1; transition: none; }

@media (max-width: 1000px) {
  .services { padding: 64px 0 72px; }
  .services__inner { flex-direction: column; gap: 32px; padding-left: 0; }
  .services__col { flex: 0 0 auto; width: 100%; padding: 0 var(--gut); }
  .services__rail { display: none; }
  .services__visual { width: 100%; margin-left: 0; min-height: 0; aspect-ratio: 5 / 4; order: -1; }
  .sv__t { font-size: 22px; line-height: 30px; }
  .sv.is-on .sv__t { font-size: 26px; }
  .sv__d p { font-size: 15px; line-height: 26px; }
}
/* measuring pass: no transitions, so the reserved open height can be read in one
   frame instead of guessed from padding arithmetic */
.sv__list.is-snap .sv__d, .sv__list.is-snap .sv__t { transition: none; }

/* ══════════════════════════════════════════════════════════════════
   V2 · VAR F adjustments — applied on top of the V1 system.
   New sections get new class names; V1 rules stay untouched for alt.html.
   ══════════════════════════════════════════════════════════════════ */

/* ── platform band: lit words, "Delivered." in navy when lit ────── */
.statement--band { padding: 78px 0; }
.statement--band .statement__copy { max-width: 760px; font-weight: 500; font-size: clamp(30px, 3vw, 42px); line-height: 1.28; }
.statement--band .statement__copy w:last-child.is-lit { color: var(--navy-deep); }

/* ── intro photo + copy ─────────────────────────────────────────── */
.introphoto { position: relative; height: 680px; overflow: hidden; }
.introphoto__img { position: absolute; inset: 0; background: url("assets/img/site-crane.jpg") center / cover no-repeat; }
.introphoto__panel { position: absolute; inset: auto 0 0 0; background: linear-gradient(180deg, transparent, rgba(10,26,50,.92) 38%); padding: 220px 0 64px; }
.introphoto__copy { display: block; max-width: 900px; font-size: 18px; line-height: 32px; color: rgba(255,255,255,.94); }

/* ── mission split ──────────────────────────────────────────────── */
.mission { display: grid; grid-template-columns: 1fr 1fr; min-height: 600px; }
.mission__l {
  background: var(--navy-900); color: #fff;
  display: flex; flex-direction: column; justify-content: center;
  padding: 72px 72px 72px max(24px, calc((100vw - var(--rail-max, 1200px)) / 2 + 24px));
}
.mission__label { color: var(--blue-lt, #7FC4EA); margin-bottom: 28px; }
.mission__title { margin: 0; font-size: clamp(26px, 2.6vw, 36px); line-height: 1.33; font-weight: 500; letter-spacing: -0.015em; max-width: 460px; }
.mission__r { background: url("assets/img/h-plant.jpg") center / cover no-repeat; min-height: 320px; }

/* ── services, VAR F layout: visual left, accordion right + head ── */
.services__inner--varf { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); gap: clamp(40px, 6vw, 96px); align-items: center; }
.services__inner--varf .services__visual { order: 0; }
.services__inner--varf .services__col { order: 1; }
.services__head { margin-bottom: 28px; }
.services__title { margin: 0; font-size: 28px; font-weight: 600; letter-spacing: -0.01em; }
.services__sub { margin: 12px 0 0; font-size: 15px; line-height: 26px; color: var(--body, #43505F); }
.services__inner--varf .services__visual { position: relative; }
.services__inner--varf .services__visual::after { content: ""; position: absolute; inset: 0; background: rgba(0,124,187,.25); mix-blend-mode: multiply; pointer-events: none; }

/* ── blue quote band ────────────────────────────────────────────── */
.bandquote { background: var(--blue); padding: 88px 0; }
.bandquote__copy { display: block; margin: 0; max-width: 1013px; font-size: clamp(22px, 2.2vw, 30px); line-height: 1.45; font-weight: 500; letter-spacing: -0.01em; color: #fff; }

/* ── markets: index row + sector card rail ──────────────────────── */
.markets__indexrow { display: flex; justify-content: space-between; align-items: center; gap: 32px; margin-top: 56px; padding-bottom: 18px; border-bottom: 1px solid var(--line, #D9E2EC); }
.mindex { display: flex; gap: 26px; overflow-x: auto; scrollbar-width: none; }
.mindex::-webkit-scrollbar { display: none; }
.mindex button { white-space: nowrap; font-size: 13px; font-weight: 500; color: var(--muted, #8095AC); padding: 0 0 8px; border: 0; border-bottom: 2px solid transparent; background: none; cursor: pointer; transition: color .15s; }
.mindex button:hover { color: var(--ink, #12294D); }
.mindex button.is-active { color: var(--ink, #12294D); border-color: var(--blue); }
.markets__scroll { overflow-x: auto; margin-top: 36px; scrollbar-width: none; cursor: grab; }
.markets__scroll::-webkit-scrollbar { display: none; }
.markets__scroll:active { cursor: grabbing; }
.mcards { display: flex; gap: 20px; width: max-content; padding: 0 max(24px, calc((100vw - var(--rail-max, 1200px)) / 2 + 24px)) 80px; }
.mcard { width: 420px; flex-shrink: 0; }
.mcard__img { height: 300px; background-size: cover; background-position: center; }
.mcard__name { margin: 20px 0 0; font-size: 19px; font-weight: 600; letter-spacing: -0.01em; }
.mcard__desc { margin: 8px 0 0; font-size: 14px; line-height: 23px; color: var(--body, #43505F); }

/* ── fast facts: simple centered row ────────────────────────────── */
.facts--row { display: block; background: #fff; padding: 60px 0; border-top: 1px solid var(--line, #D9E2EC); }
.facts__rowgrid { display: grid; grid-template-columns: repeat(4, 1fr); }
.factcell { text-align: center; }
.factcell__num { margin: 0; font-size: clamp(36px, 3.6vw, 52px); font-weight: 500; letter-spacing: -0.02em; color: var(--ink, #12294D); }
.factcell__lbl { display: block; margin-top: 10px; font-size: 11px; letter-spacing: .12em; color: var(--muted, #8095AC); line-height: 18px; }

/* ── safety: headline on white above the band (VAR F order) ─────── */
.safety__headline { padding: 72px 24px 48px; }
.safety__headline .safety__t1 { color: var(--ink, #12294D); font-weight: 500; }
.safety__headline .safety__t2 { color: var(--muted, #8095AC); font-weight: 400; font-size: clamp(20px, 2vw, 26px); margin-top: 10px; }

/* ── careers ────────────────────────────────────────────────────── */
.careers { background: #fff; padding: 72px 0 120px; border-top: 1px solid var(--line, #D9E2EC); }
.careers__grid { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: clamp(48px, 6vw, 96px); }
.careers__l .h2 { margin-bottom: 36px; }
.careers__img { height: 400px; background: url("assets/img/field-crew.jpg") center / cover no-repeat; }
.careers__head { margin: 0; font-size: clamp(24px, 2.2vw, 30px); line-height: 1.33; font-weight: 500; letter-spacing: -0.015em; }
.careers__r .body { margin-top: 22px; }
.careers__link { display: inline-flex; gap: 10px; margin-top: 32px; font-size: 15px; font-weight: 600; color: var(--blue); }
.careers__link:hover { text-decoration: underline; }

/* ── V2 mobile ──────────────────────────────────────────────────── */
@media (max-width: 1000px) {
  .mission { grid-template-columns: 1fr; min-height: 0; }
  .mission__l { padding: 72px 24px; }
  .services__inner--varf { grid-template-columns: 1fr; }
  .services__inner--varf .services__visual { min-height: 320px; }
  .mcard { width: 320px; }
  .mcard__img { height: 240px; }
  .careers__grid { grid-template-columns: 1fr; }
  .facts__rowgrid { grid-template-columns: repeat(2, 1fr); gap: 36px 0; }
  .introphoto { height: 540px; }
}

@media (max-width: 760px) {
  .mcard { width: 280px; }
  .mcard__img { height: 210px; }
  .markets__indexrow { flex-direction: column; align-items: flex-start; gap: 18px; }
  .safety__headline { padding: 56px 24px 36px; }
  .bandquote { padding: 64px 0; }
  .introphoto { height: 460px; }
  .introphoto__copy { font-size: 16px; line-height: 28px; }
}

/* ══════════════════════════════════════════════════════════════════
   V2 · VAR F FIDELITY PASS — exact values from the Paper selection.
   Wins over the earlier V2 block by cascade order.
   ══════════════════════════════════════════════════════════════════ */

/* ── hero composition (static composition per the board) ────────── */
.hero__head { padding-top: 217px; }
.hero__dots { display: none; }
.hero__scrim { background: linear-gradient(0deg, rgba(18,84,157,.94) 0%, rgba(13,31,59,.62) 46%, rgba(13,31,59,.30) 66%, rgba(13,31,59,.10) 84%, rgba(13,31,59,0) 100%); }
.powered { border-top: 0.5px solid rgba(255,255,255,.37); padding-bottom: 0; height: 158px; display: flex; flex-direction: column; justify-content: center; }
.powered__label { color: #7FC4EA; font-family: "Inter", sans-serif; font-size: 11px; letter-spacing: .14em; margin-bottom: 18px; }
.marquee { border-top: 0; padding-top: 0; }

/* ── platform band: 60px light + semibold "Delivered." + watermark ── */
.statement--band { position: relative; padding: 78px 0; overflow: clip; }
.statement--band .statement__copy { max-width: 681px; font-weight: 300; font-size: clamp(34px, 4.2vw, 60px); line-height: 1.18; letter-spacing: -0.02em; }
.statement--band .statement__copy w.is-lit { color: #fff; }
.statement--band .statement__copy w:last-child.is-lit { color: #fff; font-weight: 600; }
.band__watermark { position: absolute; left: 322px; top: -62px; width: 1191px; opacity: .1; pointer-events: none; }

/* ── intro photo + statement: left-darkened overlay, 36px light ─── */
.introphoto__img { background: url("assets/img/site-crane.jpg") 50% 40% / cover no-repeat; }
.introphoto__panel { background: linear-gradient(0deg, rgba(13,31,59,.94) 0%, rgba(13,31,59,.80) 34%, rgba(13,31,59,.24) 62%, rgba(13,31,59,.04) 86%); padding: 280px 0 96px; }
.introphoto__copy { max-width: 900px; font-weight: 300; font-size: clamp(24px, 2.5vw, 36px); line-height: 1.39; letter-spacing: -0.02em; color: #fff; padding: 0 24px; margin: 0 auto; }

/* ── mission split: 40px regular, no eyebrow ────────────────────── */
.mission__title { font-weight: 400; font-size: clamp(28px, 2.8vw, 40px); line-height: 1.4; letter-spacing: -0.02em; }

/* ── preconstruction services: board-exact rows ─────────────────── */
.services--varf { position: relative; }
.services__inner--varf { grid-template-columns: 658px minmax(0, 1fr); gap: clamp(48px, 6vw, 124px); padding-block: 89px 78px; align-items: start; }
.services__inner--varf .services__visual { height: 482px; }
.svc__rule { position: absolute; left: calc(50% - 600px + 599px); width: 2px; pointer-events: none; }
.svc__rule--blue { top: 78px; height: 186px; background: #007CBB; }
.svc__rule--gray { top: 147px; height: 434px; background: #C9D4E1; left: calc(50% - 600px + 599px + 20px); }
.services__inner--varf .services__head { display: none; } /* head copy lives in the open row now */
.services__inner--varf .sv__t { font-weight: 400; font-size: 26px; line-height: 34px; letter-spacing: -0.02em; color: #5A6C82; }
.services__inner--varf .sv.is-on .sv__t { font-weight: 500; font-size: 32px; color: #12294D; }
.services__inner--varf .sv { border-bottom: 1px solid #C9D4E1; }
.services__inner--varf .sv__d p { font-size: 16px; line-height: 28px; color: #43505F; }

/* ── portfolio: 96px head, 32px copy, 4×360 tiles ───────────────── */
.portfolio__head { padding-top: 50px; padding-bottom: 48px; }
.portfolio__title { font-weight: 300; font-size: clamp(56px, 6.7vw, 96px); line-height: 1.04; letter-spacing: -0.035em; color: #fff; }
.portfolio__copy { font-size: clamp(20px, 2.2vw, 32px); line-height: 1.25; color: #fff; max-width: 555px; }
.wall { grid-template-columns: repeat(4, 1fr); }
.wall__cell { aspect-ratio: 1 / 1; height: auto; }

/* ── quote band: 42px light + watermark ─────────────────────────── */
.bandquote { position: relative; padding: 60px 0; overflow: clip; }
.bandquote__copy { display: block; margin: 0 auto; max-width: 900px; padding: 0 24px; font-weight: 300; font-size: clamp(26px, 2.9vw, 42px); line-height: 1.43; letter-spacing: -0.02em; color: #fff; }
.band__watermark--quote { left: 427px; top: -62px; width: 1013px; }

/* ── markets head: 96px title + 60px tabs + 20px copy ───────────── */
.markets__title { margin: 0; font-weight: 300; font-size: clamp(56px, 6.7vw, 96px); line-height: 1.04; letter-spacing: -0.035em; color: #1A3768; }
.markets__r { max-width: 555px; padding-top: 8px; }
.markets__copy { font-size: 20px; line-height: 24px; color: #43505F; }
.switch { gap: 36px; border-bottom: 0; margin-bottom: 22px; }
.switch__tab { font-weight: 300; font-size: clamp(34px, 4.2vw, 60px); line-height: 1.26; letter-spacing: -0.035em; color: #D6E2F0; padding-bottom: 0; }
.switch__tab.is-active { color: #1A3768; }
.switch__tab.is-active::after { display: none; }

/* ── market index: 17px tabs + progress rule ────────────────────── */
.mindex button { font-size: 17px; line-height: 28px; font-weight: 400; color: rgba(26,55,104,.35); border-bottom: 0; padding-bottom: 16px; }
.mindex button.is-active { color: #1A3768; }
.markets__indexrow { position: relative; padding-bottom: 0; border-bottom: 0; margin-top: 64px; }
.markets__indexrow::before { content: ""; position: absolute; left: 24px; right: 24px; bottom: 0; height: 1px; background: #C9D4E1; }
.markets__indexrow::after { content: ""; position: absolute; left: 24px; bottom: 0; height: 2px; width: 246px; background: #007CBB; }

/* ── market cards: 420×520 dark card, chip + title + body ───────── */
.mcards { gap: 24px; padding-block: 24px 80px; }
.mcard { position: relative; width: 420px; height: 520px; background: #14161A; overflow: clip; cursor: pointer; }
.mcard__img { position: absolute; inset: 0; height: 100%; }
.mcard__scrim { position: absolute; left: 0; right: 0; top: 150px; height: 370px; background: linear-gradient(180deg, rgba(20,22,26,0) 0%, rgba(20,22,26,.58) 42%, rgba(20,22,26,.95) 100%); }
.mcard__chip { position: absolute; left: 24px; bottom: 158px; max-width: 372px; display: inline-flex; align-items: center; height: 32px; padding-inline: 16px; background: rgba(255,255,255,.20); border: 1px solid rgba(255,255,255,.36); border-radius: 2px; font-size: 12px; line-height: 16px; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mcard__text { position: absolute; left: 24px; right: 24px; bottom: 34px; }
.mcard__name { margin: 0 0 12px; font-size: 20px; line-height: 28px; font-weight: 500; letter-spacing: -0.01em; color: #fff; }
.mcard__desc { margin: 0; font-size: 12px; line-height: 19px; color: rgba(255,255,255,.68); }

/* ── fast facts: 68px + suffix, two-tone labels, column rules ───── */
.factcell { padding: 32px 24px 34px; }
.factcell + .factcell { border-left: 1px solid #C9D4E1; }
.factcell__num { font-weight: 300; font-size: clamp(44px, 4.7vw, 68px); line-height: 1.03; letter-spacing: -0.035em; color: #1A3768; }
.factcell__suffix { font-size: 30px; letter-spacing: -0.02em; color: #007CBB; margin-left: 4px; }
.factcell__l1, .factcell__l2 { display: block; font-family: "Inter", sans-serif; font-weight: 500; font-size: 13px; line-height: 18px; letter-spacing: .11em; }
.factcell__l1 { color: #5A6C82; margin-top: 14px; }
.factcell__l2 { color: #1A3768; }

/* ── safety: three bands per the board ──────────────────────────── */
.safety__bandA { background: linear-gradient(180deg, #0E8ED0, #0A6EAE); padding: 72px 0 48px; }
.safety__bandA .safety__t1 { color: #fff; font-weight: 300; font-size: clamp(56px, 6.7vw, 96px); line-height: 1.04; letter-spacing: -0.035em; }
.safety__copy .safety__t2 { color: #fff; font-weight: 300; font-size: clamp(34px, 3.6vw, 52px); line-height: 1.19; letter-spacing: -0.02em; }
.safety__bandC { background: #E9EEF4; }
.safety__bandC .safety__detail { background: linear-gradient(180deg, #0A6EAE, #084E80); padding-top: 88px; padding-bottom: 96px; }
.safety__claim { font-weight: 300; font-size: clamp(28px, 2.8vw, 40px); line-height: 1.3; letter-spacing: -0.02em; color: #fff; }
.safety__copyr p { font-size: 18px; line-height: 28px; color: #fff; }

/* ── careers: board-exact ───────────────────────────────────────── */
.careers { padding: 72px 0 120px; }
.careers__title { margin: 0 0 40px; font-weight: 300; font-size: clamp(56px, 6.7vw, 96px); line-height: 1.04; letter-spacing: -0.035em; color: #1A3768; }
.careers__head { font-weight: 400; font-size: clamp(28px, 2.8vw, 40px); line-height: 1.3; color: #1A3768; }
.careers__body { margin-top: 34px; display: grid; gap: 20px; }
.careers__body p { font-size: 18px; line-height: 24px; color: #43505F; }
.careers__link { position: relative; display: inline-flex; gap: 14px; margin-top: 54px; font-size: 17px; line-height: 24px; font-weight: 400; color: #1A3768; }
.careers__link i { position: absolute; left: 0; right: 0; bottom: -8px; height: 1px; background: rgba(26,55,104,.5); }
.careers__link:hover { text-decoration: none; }
.careers__r .body { display: none; } /* replaced by careers__body */

/* ── footer: #131517 board colors ───────────────────────────────── */
.ftr { background: #131517; }
.ftr__legal { background: #0B182E; }

/* ── V2 fidelity mobile ─────────────────────────────────────────── */
@media (max-width: 1000px) {
  .services__inner--varf { grid-template-columns: 1fr; }
  .svc__rule { display: none; }
  .wall { grid-template-columns: repeat(2, 1fr); }
  .mcard { width: 320px; height: 420px; }
  .mcard__chip { bottom: 130px; }
  .factcell + .factcell { border-left: 0; }
}
@media (max-width: 760px) {
  .wall { grid-template-columns: 1fr; }
  .mcard { width: 280px; height: 380px; }
  .switch__tab { font-size: 34px; }
}

/* mobile: chip rides the top of the card so it can't collide with wrapped titles */
@media (max-width: 1000px) {
  .mcard__chip { bottom: auto; top: 16px; }
}

/* ── VAR F reconciliation pass ──────────────────────────────────── */
/* page ground is light blue-gray; sections carry their own fills */
.page { background: #E9EEF4; }
.services--varf { background: #fff; }
.facts--row { border-top: 0; }
.introphoto__panel { margin-bottom: 0; }

/* services: photo bleeds to the canvas left edge per the board */
.services__inner--varf { max-width: none; padding-left: 0; grid-template-columns: 658px minmax(0, 570px); justify-content: start; column-gap: 124px; padding-right: 24px; }

/* footer: darker navy blue, legal bar a step darker */
.ftr { background: #0B182E; }
.ftr__legal { background: #060F1F; }
.ftr__divide, .ftr__follow { border-color: rgba(255,255,255,.12); }

/* ══════════════════════════════════════════════════════════════════
   V2 · VAR F fix pass 2 — principal review items
   ══════════════════════════════════════════════════════════════════ */

/* platform band: rises once via motion split — no scroll-lit words ── */
.statement--band .statement__copy { color: #fff; }

/* intro photo: lit words live here; parallax surplus; exact metrics ─ */
.introphoto__img { inset: -18% 0; height: 136%; }
.introphoto__panel { padding: 280px 0 96px; }
.introphoto__copy { padding: 0; }
.introphoto__copy w { color: rgba(255,255,255,.32); transition: color .28s ease; }
.introphoto__copy w.is-lit { color: #fff; }

/* safety: bands per the board ────────────────────────────────────── */
.safety__bandA { padding: 72px 0 48px; }
.safety__bandA .rail { padding-inline: 0; }
.safety__copy { left: 0; right: 0; translate: none; bottom: 64px; }
.safety__copy .safety__t2 { text-align: left; }
.safety__bandC { background: #E9EEF4; padding: 56px 0; }
.safety__bandC .safety__detail {
  max-width: none; margin: 0; padding: 88px 120px 96px;
  background: linear-gradient(180deg, #0A6EAE, #084E80);
}
.safety__bandC .safety__detail .split__l,
.safety__bandC .safety__detail .split__r { padding: 0; }

/* careers: spec rails — 120 left / 162 right, 560 + 555 columns ──── */
.careers { padding: 72px 0 120px; }
.rail.careers__grid { max-width: none; margin: 0; padding: 0 162px 0 120px; display: flex; justify-content: space-between; gap: 0; }
.careers__l { width: 560px; flex-shrink: 0; }
.careers__r { width: 555px; padding-top: 8px; }
.careers__img { height: 400px; }

/* footer: no photo, board gradient ───────────────────────────────── */
.ftr__bg { display: none; }
.ftr::before { background: linear-gradient(180deg, rgba(0,0,0,.36) 0%, rgba(18,84,157,.84) 100%); }

/* powered-by strip: board metrics + edge fades ───────────────────── */
.powered { height: 158px; }
.powered__label { margin-bottom: 30px; }
.marquee__track img { height: 36px; }
.marquee::before, .marquee::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: 120px; z-index: 2; pointer-events: none;
}
.marquee::before { left: 0; background: linear-gradient(90deg, rgba(13,31,59,.62), rgba(13,31,59,0)); }
.marquee::after { right: 0; background: linear-gradient(270deg, rgba(13,31,59,.62), rgba(13,31,59,0)); }
.marquee { position: relative; }

/* header: persistent compact mode — scales down, never hides ─────── */
.hdr .navrow { transition: height .25s ease; }
.hdr .navrow__logo img { transition: width .25s ease, height .25s ease; }
.hdr .nav a { transition: font-size .25s ease, color .16s ease; }
/* The plate is the header's own ground. It was a fixed 138 (34 bar + 104 row),
   so once the row shrank to 60 it hung 44px below the bar it was backing. It
   tracks the compact height now, and the compact row went 60 → 76 because at
   60 the logo had 12px above and below it and read pinched. */
.hdr__plate { transition: opacity .22s var(--ease), height .25s ease; }
.hdr.is-compact .navrow { height: 76px; }
.hdr.is-compact .hdr__plate { height: 110px; }   /* 34 bar + 76 row */
.hdr.is-compact .navrow__logo img { width: 80px; height: 40px; }
.hdr.is-compact .nav a { font-size: 14px; }
.hdr.is-compact .cta { font-size: 14px; }

/* ══════════════════════════════════════════════════════════════════
   V2 · VAR F fix pass 3 — principal review, 2026-08-07
   Numbers are read off the Paper build, not estimated: hero head
   ZE6-0, powered strip 119K-0, intro photo ZQI-0/ZQN-0, safety
   10KK-0/ZKY-0, careers 10RX-0/10SD-0.
   ══════════════════════════════════════════════════════════════════ */

/* ── a control is not a paragraph ─────────────────────────────────
   <button> does not inherit font-family, so the market index and the
   carousel arrows were rendering in Arial next to Archivo everywhere
   else. This is the "JetBrains is still showing up" report: it was
   never JetBrains, it was the UA default. */
button, input, select, textarea { font: inherit; letter-spacing: inherit; }
.mindex button, .circ, .switch__tab { font-family: var(--f-display); }

/* one label face, one fallback chain — the strip and the fact cells
   had each declared their own */
:root { --f-mono: 'Inter', system-ui, sans-serif; }

/* ── the rail origin, as a value ──────────────────────────────────
   --gut is a fixed 120 and only equals the rail on a 1440 canvas. The
   board IS 1440, so anything positioned at var(--gut) looked correct
   here and drifted the moment the window was wider: at 1920 the rail
   centres at 360 while a hard 120 stays at 120. Everything that has to
   sit on the rail but cannot inherit .rail's auto margins — absolute
   boxes, section padding, full-bleed plates — reads this instead. */
:root { --rail-x: max(var(--gut), calc((100vw - var(--rail-max)) / 2)); }
.powered__label, .factcell__l1, .factcell__l2 { font-family: var(--f-mono); }

/* ── hero: head clears the logo strip ─────────────────────────────
   The board puts the head at top 217 of a 775-tall hero with 88px of
   air above the strip. Ours is 100svh, so top-anchoring at 217 (as a
   padding, on top of the existing top: 30%) drove the sub-copy straight
   through "POWERED BY". Anchor from the bottom instead and the 88px
   gap holds at any viewport height. */
.hero__head {
  left: 0; right: 0; translate: none;     /* .rail's own auto margins place it */
  top: auto; bottom: 246px;               /* 158 strip + 88 air */
  padding-top: 0;
}
/* The board sets this at 17/28 on a 620 measure. Against a 112 display it
   reads undersized on screen, so it is up to 20/32 on a 730 measure, which
   keeps the three-line wrap the board has. Director's call over the board. */
.hero__body { max-width: 730px; font-size: 20px; line-height: 32px; }

/* "Builders." at 500 carries a 9.1px left sidebearing where "We Are" at 200
   carries 1.6px, so setting both flush to the rail leaves the second line
   optically indented by ~7.5px at 112. Correct in em so it holds down the
   clamp. Same pair, same correction, in the footer. */
.hero__title--bold, .ftr__we--bold { margin-left: -0.067em; }

/* ── markets: index rule travels, strip starts on the rail ────────
   Both the rule and the card strip were inset by a leftover 24px from
   before the rail moved to 120, so the index read at 120 while the blue
   rule and the first card sat at 144. The rule is also no longer a fixed
   246px — app.js writes the active item's box into --mk-x / --mk-w. */
.markets__indexrow::before { left: 0; right: 0; }
.markets__indexrow::after {
  left: var(--mk-x, 0px); width: var(--mk-w, 246px);
  transition: left .42s var(--ease, cubic-bezier(.22,.61,.36,1)), width .42s var(--ease, cubic-bezier(.22,.61,.36,1));
}
.mcards { padding-left: var(--rail-x); }

/* ── preconstruction: anchored to the centre, not to x=0 ──────────
   On the board the photo bleeds off the canvas edge to 658, the rule sits
   at 719 and the accordion starts at 782 — all of which read against the
   1440 centre at 720. The grid was laid from x=0 with fixed tracks, so
   past 1440 the rule (already centre-based) walked away from the column
   it belongs to. Track one now ends 62 short of centre and the 124 gap
   puts the accordion 62 past it, at any width. */
.services__inner--varf { grid-template-columns: calc(50% - 62px) 570px; column-gap: 124px; }
.services__inner--varf .services__visual { width: 100%; margin: 0; }

/* ── powered-by strip: board metrics ──────────────────────────────
   label at 26 from the strip top, logos at 82, which leaves 40px of
   air under the logos instead of the 8px the centred flex column gave. */
.powered { display: block; padding: 0; }
.powered::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: linear-gradient(180deg, rgba(13,31,59,0) 0%, rgba(13,31,59,.34) 30%, rgba(13,31,59,.46) 100%);
}
.powered__label {
  position: absolute; left: var(--rail-x); top: 26px; width: auto; margin: 0;
  font-size: 11px; line-height: 16px; letter-spacing: .14em;
}
.powered .marquee { position: absolute; left: 0; right: 0; top: 82px; height: 36px; margin: 0; }
.marquee__track img { margin-right: 72px; }

/* ── intro photo: copy on the rail, gradient over the whole frame ──
   ZQJ-0 is a full-frame 1440×680 overlay and ZQN-0 sits at left 120 /
   bottom 96 / width 900. The copy was centred by an inherited
   margin-inline: auto, which put it 270 off the rail. */
.introphoto__img { background-image: url("assets/img/intro-deck.jpg"); background-position: 50% 46%; }
.introphoto__panel { position: absolute; inset: 0; padding: 0; }

/* ── intro photo pins while the copy lights ───────────────────────
   The section is a scroll track; the frame inside it sticks to the top.
   The words light over the first stretch of that track, then the mission
   is pulled up by exactly one screen so it rises over the held frame
   instead of pushing it off. app.js drives both off the same progress. */
.introphoto__pin { position: relative; height: 100%; overflow: clip; }
@media (min-width: 901px) {
  /* overflow: hidden on the track would make it the sticky child's scroll
     container, and a container that never scrolls never lets it stick.
     The clipping belongs on the frame now, which is where the photo is. */
  .introphoto { height: 260svh; overflow: visible; }
  .introphoto__pin { position: sticky; top: 0; height: 100svh; }
  .mission { position: relative; z-index: 1; margin-top: -100svh; }
}
.introphoto__copy {
  position: absolute; left: var(--rail-x); bottom: 96px;
  width: 900px; max-width: calc(100% - var(--rail-x) * 2);
  margin: 0; padding: 0;
}

/* ── safety: band A gets its height, band C its full-bleed plate ───
   .safety__detail carries .rail, and .rail is width + auto margins.
   Zeroing the margin alone left the plate 1200 wide pinned to x=0. */
.safety__bandA { min-height: 261px; }
/* ZKX-0 and ZKY-0 are both 1440×434 — the plate fills the wrapper and the
   #E9EEF4 never shows, so the 56px band was a seam the board doesn't have */
.safety__bandC { padding: 0; }
.safety__bandC .safety__detail {
  width: auto; max-width: none; margin: 0;
  padding: 88px var(--rail-x) 96px;
  justify-content: space-between; gap: 0;
}
.safety__bandC .safety__detail .split__l { flex: 0 0 645px; }
.safety__bandC .safety__detail .split__r { flex: 0 0 555px; gap: 26px; }

/* ── careers: on the rail ─────────────────────────────────────────
   The previous selector was .rail.careers__grid and the element never
   carried .rail, so none of it applied and the section ran edge to
   edge. The photograph is an absolute 580×400 at left 120 / top 273
   on the board, not a flow item under the title. */
.careers { padding: 0; }
.careers__grid {
  position: relative; max-width: none; width: 100%; margin: 0;
  display: flex; align-items: flex-start; justify-content: space-between; gap: 0;
  /* the board's 162 right rail is the 120 gutter plus 42, so it stays
     42 inside the rail rather than 162 off the window at any width */
  padding: 72px calc(var(--rail-x) + 42px) 120px var(--rail-x);
  /* the photograph is out of flow, so the section has to reserve its box:
     273 top + 400 tall + the board's 56 below it */
  min-height: 729px;
}
/* intrinsic, not 645 — 645 + 555 exceeds the 1158 content box and the
   space-between collapses, pushing the copy column off the right rail */
.careers__l { flex: 0 1 auto; }
.careers__r { width: 555px; flex-shrink: 0; padding-top: 8px; }
.careers__img {
  position: absolute; left: var(--rail-x); top: 273px;
  width: 580px; height: 400px;
  background-image: url("assets/img/careers-crew.jpg"); background-position: 50% 44%;
}
.careers__body { gap: 26px; }
.careers__body p { margin: 0; }

/* ── mobile parity for the pass above ─────────────────────────────── */
@media (max-width: 1100px) {
  .careers__grid { flex-direction: column; padding: 56px var(--gut) 88px; gap: 40px; }
  .careers__l, .careers__r { width: 100%; }
  .careers__img { position: static; width: 100%; height: clamp(240px, 46vw, 400px); margin-top: 36px; }
  .safety__bandC .safety__detail { flex-direction: column; padding: 56px var(--gut) 64px; gap: 32px; }
  .safety__bandC .safety__detail .split__l,
  .safety__bandC .safety__detail .split__r { flex: 0 0 auto; width: 100%; }
  .safety__bandA { min-height: 0; padding: 56px 0 36px; }
}
@media (max-width: 900px) {
  /* NOT width: 100%. .hero__head carries .rail, and .rail is a width plus auto
     margins — overriding the width killed the inset outright, so the hero title
     and its sub-copy sat flush at x=0 on a phone while every other block held
     the 20px gutter. Let .rail size it. */
  .hero__head { bottom: 200px; }
  .hero__body { max-width: none; font-size: 17px; line-height: 28px; }
  .powered { height: 132px; }
  .powered__label { top: 20px; }
  /* 30px flat clipped the descenders on the stacked wordmarks */
  .powered .marquee { top: 60px; height: 34px; }
  .marquee__track img { height: 28px; margin-right: 44px; }
  /* the copy is ten lines on a 350 rail — at 460 it ate the whole frame and
     the photograph read as a texture behind text rather than a photograph */
  .introphoto { height: 640px; }
  .introphoto__copy { bottom: 40px; width: 100%; font-size: clamp(19px, 5.1vw, 24px); line-height: 1.42; }
}

/* ══════════════════════════════════════════════════════════════════
   V2 · VAR F fix pass 4 — principal review, 2026-08-07 (second batch)
   ══════════════════════════════════════════════════════════════════ */

/* ── preconstruction pins and steps through its rows ──────────────
   Same track/frame pattern as the intro photo: the section is a scroll
   track, the frame sticks, and scroll position picks the open row rather
   than a click. The stage inside the frame is the board's own 663-tall
   composition, so the rail coordinates stay board-exact instead of being
   re-derived against a viewport-height box. */
.services__pin { position: relative; }
.services__stage { position: relative; }

@media (min-width: 901px) {
  /* 100svh longer than the five row-steps need: app.js reserves that last
     screen so the portfolio can rise over the still-held frame rather than
     arriving exactly as it releases, which reads as a push. */
  .services--varf { height: 450svh; padding: 0; overflow: visible; }
  .portfolio { margin-top: -100svh; }
  .services__pin {
    position: sticky; top: 0; height: 100svh; overflow: clip;
    display: grid; align-content: center; background: #fff;
  }
  .services__stage { width: 100%; }
}

/* The rail is one gray rule down the accordion with a blue marker over the
   open row. The board draws both at the same x (719 on a 1440 frame); ours
   had nudged the gray 20px clear of the blue, which read as two rails. */
/* measuring pass: every transition off so the marker can be given each row's
   destination box instead of a frame from the middle of the animation */
.sv__list.is-nofx, .sv__list.is-nofx * { transition: none !important; }
.svc__rule { left: calc(50% - 1px); }
.svc__rule--gray { left: calc(50% - 1px); top: 89px; height: 482px; background: #C9D4E1; }
.svc__rule--blue {
  top: 0; height: 186px; background: #007CBB; z-index: 2;
  transition: transform .42s var(--ease), height .42s var(--ease);
}

/* ── portfolio: the board's rhythm, not ours ──────────────────────
   1273 tall against the board's 1046. The section carried a 96px top pad
   the board does not have, the head an extra 56 on top of its own 48, and
   the wall was missing the board's -36 pull into the head. The copy also
   sat top-aligned against a two-line 96px headline, which is the unevenness
   — the board centres the two columns against each other. */
.portfolio { padding-top: 0; }
.portfolio .portfolio__head { align-items: center; padding: 50px 0 48px; }
.portfolio .wall { margin-top: -36px; }

/* Display heads and section copy were still carrying the UA's em-based
   block margins — 79.7px above and below a 96px h2, 31.7 around a 24px p.
   That is where 160 of the portfolio's excess height came from. */
.portfolio__title, .portfolio__copy,
.markets__title, .markets__copy,
.mission__title, .careers__title, .careers__head,
.safety__t1, .safety__t2, .safety__claim, .safety__copyr p,
.statement__copy, .bandquote__copy { margin: 0; }

/* the board's left column is a 264 box whichever way its title wraps */
.portfolio .split__l, .markets__head .split__l { min-height: 264px; }

/* ── markets: same story, 1401 against the board's 1050 ───────────
   All of the excess was section padding the board puts inside its own
   blocks: head 72/28, index row a flat 118 with the hairline at 46 from
   its top, card strip 24 above and below. */
.markets { padding: 0; }
.markets__head { padding: 72px 0 28px; }
.markets__indexrow { height: 118px; align-items: flex-start; margin-top: 0; padding: 0; }
.mindex { column-gap: 34px; }
.markets__indexrow::before { top: 46px; bottom: auto; }
.markets__indexrow::after { top: 45px; bottom: auto; }
.markets__pager { position: absolute; right: 0; top: 0; }
.markets__scroll { margin-top: 0; }
.mcards { padding-block: 24px; }

/* A card opens up as you point at it; the strip is a scroll rail, so the
   others keep their width and the rail simply gets longer. The selector has
   to out-specify motion.css's [data-m-rise], which loads after this file and
   replaces the whole transition shorthand — width would never animate. */
.markets__scroll .mcards .mcard {
  transition-property: opacity, transform, width;
  transition-duration: 1.15s, 1.15s, .42s;
  transition-timing-function: cubic-bezier(.16,.84,.24,1), cubic-bezier(.16,.84,.24,1), cubic-bezier(.22,.61,.36,1);
}
/* Gated on a real pointer. :hover latches on touch — a tap left the card stuck
   at 520 with no way to release it — and the earlier mobile override was a
   two-class selector losing to this one's five, so it never applied at all.
   Scoping the rule itself is the fix; overriding it afterwards was not. */
@media (hover: hover) and (pointer: fine) and (min-width: 901px) {
  .markets__scroll:not(:active) .mcards .mcard:hover { width: 520px; }
}
/* nothing to animate where nothing can hover */
@media (hover: none), (pointer: coarse) {
  .markets__scroll .mcards .mcard { transition-property: opacity, transform; }
}

@media (max-width: 900px) {
  .services--varf { height: auto; }
  .services__pin { position: relative; height: auto; display: block; }
  .markets__indexrow { height: auto; padding-bottom: 18px; }
  .markets__indexrow::before, .markets__indexrow::after { top: auto; bottom: 0; }
  .markets__pager { position: static; }
}

/* the board draws one rail beside the accordion, not two — the column's own
   right-hand rule is a leftover from the pre-VAR-F layout */
.services--varf .services__rail { display: none; }

/* ══════════════════════════════════════════════════════════════════
   V2 · VAR F fix pass 5 — principal review, 2026-08-07 (third batch)
   ══════════════════════════════════════════════════════════════════ */

/* the accordion's old right-hand lit segment. The board's rail and its
   travelling marker are on the LEFT of the column now, so this drew a second
   blue line down the right of every open row. */
.services--varf .sv__mark { display: none; }

/* intro statement runs the full rail rather than the board's 900 — at 900 it
   stopped 300 short of the grid it sits in */
.introphoto__copy { width: var(--rail-max); max-width: calc(100% - var(--rail-x) * 2); }

/* ── the two watermarked bands ────────────────────────────────────
   .bandquote had no inline padding at all, so its copy started at x=0 while
   every other section began on the rail. Both ghosted marks are pushed off
   the right edge instead of sitting mid-frame over the copy. */
.bandquote { padding-inline: var(--rail-x); }
.bandquote__copy { margin: 0; max-width: 900px; padding: 0; }
/* Halfway between the board (quote mark at 427, band mark at 322) and the
   first correction, which read too far right. Right-anchored so both keep the
   same crop off the edge whatever the window is doing. */
.band__watermark { left: auto; right: -165px; }
.band__watermark--quote { left: auto; right: -110px; }

/* ── sections layer over one another ──────────────────────────────
   Each band holds at the top while the next one rises over it. Every one of
   them needs an opaque ground or the section beneath shows through, and the
   z-order has to climb in document order so later always covers earlier. */
/* `.page > section` sets position and z-index at (0,1,1), so these have to be
   written the same way or none of it lands — the same trap the hero pin hit. */
/* top is NOT 0. These bands are taller than the viewport, and a 0 stick would
   hold each one from its first screen — the rest of it would be covered by the
   next section before it ever scrolled into view. app.js writes
   min(0, viewport - height) into --stick-top, so a tall band scrolls through
   normally and only holds once its LAST screen is on show.

   A section is held only when the NEXT one should rise over it. Two are
   deliberately free:
   · portfolio — the wall and the quote band travel as one pass, so the quote
     must not cover the wall. The quote is what gets held instead, and Markets
     is what rises over it.
   · markets   — fast facts belongs to Markets We Serve and reads as its last
     row. Holding markets would make facts arrive as a layer on top of it
     rather than locking to the bottom of the card strip. Facts is held, so
     Safety still layers over the pair. */
.page > .mission, .page > .bandquote, .page > .markets,
.page > .safety, .page > .careers {
  position: sticky; top: var(--stick-top, 0px);
}
/* facts rides WITH markets: it holds at the foot of the screen, where the held
   band ends, so the pair reads as one view. app.js writes its --stick-top. */
.page > .facts { position: sticky; top: var(--stick-top, 0px); }
.page > .mission        { z-index: 1; }
.page > .services--varf { z-index: 2; }
.page > .portfolio      { position: relative; z-index: 3; }
.page > .bandquote      { z-index: 4; }
.page > .markets        { z-index: 5; background: #E9EEF4; }
.page > .facts          { z-index: 6; background: #fff; }
.page > .safety         { z-index: 7; background: #fff; }
.page > .careers        { z-index: 8; background: #fff; }

@media (max-width: 900px) {
  .introphoto__copy { width: 100%; }
  .bandquote { padding-inline: var(--gut); }
  .band__watermark, .band__watermark--quote { right: -40%; }
  /* the layering runs on a phone too — superseded by the --stick-top block
     further down, which holds a tall band only once its last screen shows */
}

/* ── logo strip back to V1's spacing ──────────────────────────────
   Measured off localhost:4321, not guessed: strip 158 with 26 of padding, the
   label on the rail at 26, the marquee at 74 (26 + an 18 label + 30), logos
   108×36 on a 132 gap for a 240 pitch, and no edge fades — V1 dropped those
   deliberately. The board's 72 gap packed the marks together and the fades ate
   the first and last one. */
.powered {
  border-top: 0;
  background: linear-gradient(180deg, rgba(10,26,50,0) 0%, rgba(10,26,50,.30) 34%, rgba(10,26,50,.42) 100%);
}
.powered::before { content: none; }
.powered .marquee { top: 74px; }
.marquee::before, .marquee::after { content: none; }
.marquee__track img { width: 108px; height: 36px; margin-right: 132px; opacity: .92; }

@media (max-width: 900px) {
  .powered { height: 140px; }
  .powered__label { top: 20px; }
  .powered .marquee { top: 68px; height: 30px; }
  .marquee__track img { width: 92px; height: 30px; margin-right: 56px; }
}

/* ── ticker marks link out ────────────────────────────────────────
   Spacing moves onto the anchor so the track's loop measurement — one
   sequence's width, read off the first child of the second copy — is unchanged. */
.marquee__link { display: inline-flex; align-items: center; flex-shrink: 0; margin-right: 132px; }
.marquee__link img { margin-right: 0; opacity: .92; transition: opacity .2s ease; }
.marquee__link:hover img { opacity: 1; }

/* ── preconstruction photo cross-fades ────────────────────────────
   background-image cannot be transitioned, so rewriting it cut straight from one
   photograph to the next. Two stacked layers fade instead. */
.services__visual { position: relative; overflow: hidden; }
.sv__layer {
  position: absolute; inset: 0; display: block;
  background-size: cover; background-repeat: no-repeat;
  opacity: 0; transition: opacity .55s var(--ease);
}
.sv__layer.is-on { opacity: 1; }

/* intro statement steps up — 36 read small on a 1200 measure */
.introphoto__copy { font-size: clamp(22px, 2.6vw, 36px); line-height: 1.38; }

@media (max-width: 900px) {
  .marquee__link { margin-right: 56px; }
  .introphoto__copy { font-size: clamp(21px, 5.6vw, 26px); line-height: 1.4; }
}

/* ── the lit sweep, continuous ────────────────────────────────────
   app.js writes a 0–1 --lit per word and the colour is mixed from it, so the
   edge of the sweep moves with the scroll rather than snapping a word at a
   time. No transition: the value already changes every frame, and easing it
   would only make the sweep lag the scroll it is supposed to follow. */
.introphoto__copy w {
  transition: none;
  color: color-mix(in oklab, #fff calc(var(--lit, 0) * 100%), rgba(255,255,255,.28));
}
.introphoto__copy w.is-lit { color: inherit; }

/* ── mission: the photograph slides in from the right ─────────────
   Was a 50/50 grid where both halves rose 42px on the same curve, which read
   as two blocks nudging up together. The photograph now takes the larger share
   and travels horizontally into place while the copy still rises, so the two
   move on different axes and the panel reads as arriving rather than settling.
   The frame clips, so the image is oversized inside it and only its own drift
   is visible — the slide never exposes an edge. */
.mission { grid-template-columns: minmax(0, 42fr) minmax(0, 58fr); min-height: 620px; }
.mission__r { position: relative; overflow: clip; background: none; }
.mission__r::before {
  content: ""; position: absolute; inset: 0 -12% 0 0;
  background: url("assets/img/h-plant.jpg") 50% 44% / cover no-repeat;
}
/* Two motions, not one wipe. A clip-path uncovers a panel that was always
   there; the backplate is meant to arrive, so it TRAVELS — the whole navy
   block slides in from the left with its copy aboard. The photograph does not
   move at all: it scales down into its frame from the right edge, so the two
   halves read as different gestures meeting in the middle rather than one
   effect applied twice.

   The section carries the page's own ground so there is something for the
   plate to slide across. Without it the plate would be sliding over whatever
   the mission is currently covering, which is the pinned intro photo. */
.mission { background: #E9EEF4; }

.m-ready .mission__l[data-m-rise] {
  opacity: 1; clip-path: none;
  transform: translateX(-100%);
  transition: transform 1.25s cubic-bezier(.16,.84,.24,1) var(--m-delay, 0ms);
}
.m-ready .mission__l[data-m-rise].is-in { transform: none; }

.m-ready .mission__r[data-m-rise] { opacity: 1; clip-path: none; transform: none; }
.mission__r::before { transform: scale(1.18); transform-origin: right center; }
.m-ready .mission__r[data-m-rise]::before {
  transition: transform 1.6s cubic-bezier(.16,.84,.24,1) var(--m-delay, 0ms);
}
.m-ready .mission__r[data-m-rise].is-in::before { transform: scale(1); }
.mission__title { max-width: 520px; }

@media (max-width: 900px) {
  .mission { grid-template-columns: 1fr; }
  .mission__r::before { inset: 0 -8% 0 0; transform: scale(1.1); }
}

/* ── preconstruction: the composition stops recentring ────────────
   The frame centres its stage, and the stage was sized by its content. Open
   rows are not all the same height — row one's title wraps at 32 where the
   others do not, so the list ran 215 to 249 — and every change re-centred the
   whole composition by half the difference. That is the jump between rows.
   The stage is a fixed box now: the list still grows inside it, nothing moves
   around it. */
.services__stage { height: 663px; }
.services__inner--varf { align-items: start; height: 100%; }

/* photograph sits above the accordion's top line rather than level with it */
.services__inner--varf .services__visual { align-self: start; margin-top: -56px; }

@media (max-width: 900px) {
  .services__stage { height: auto; }
  .services__inner--varf { height: auto; }
  .services__inner--varf .services__visual { margin-top: 0; }
}

/* The mission panel starts translated 18% right, which pushed the page's scroll
   width out by 150px before it revealed. Clipping at the section contains the
   travel and gives the panel somewhere to slide in from. */
.mission { overflow: clip; }

/* the bar travels as one plate; .ftr already clips, so it rises from under its
   own bottom edge without opening a gap */

/* ══════════════════════════════════════════════════════════════════
   V2 · VAR F fix pass 6 — mobile parity + section rhythm
   ══════════════════════════════════════════════════════════════════ */

/* ── careers: one rhythm instead of three margins ─────────────────
   The board's copy frame is a flex column on a single 54 gap. Ours stacked a
   34 margin on the body and a 54 on the link, so the head sat closer to the
   copy than the copy sat to the CTA and the column read lopsided. */
.careers__r { display: flex; flex-direction: column; align-items: flex-start; gap: 54px; }
.careers__r > * { margin: 0; }
.careers__body { display: grid; gap: 26px; margin: 0; }
.careers__link { margin: 0; }
.careers__l { display: flex; flex-direction: column; gap: 39px; }

/* ── markets: the right column on the board's 32 ──────────────────
   The switch carried a 22 bottom margin and the copy its own leading, which
   put the body closer to the tabs than the board's 32 and left the column
   feeling top-heavy against a 96 display head. */
.markets__r { display: flex; flex-direction: column; align-items: flex-start; gap: 32px; }
.markets__r > * { margin: 0; }
.switch { margin: 0; gap: 36px; }
.markets__copy { font-size: 20px; line-height: 30px; max-width: 555px; }

/* ── mobile ───────────────────────────────────────────────────────
   Preconstruction was still running the desktop grid on a phone: a fixed 570
   second track on a 390 screen pushed the whole accordion off the right edge.
   It is a plain stack there. */
@media (max-width: 900px) {
  .services__inner--varf {
    display: block; grid-template-columns: none;
    max-width: none; padding: 0 0 56px; height: auto;
  }
  .services__inner--varf .services__visual {
    width: 100%; height: clamp(230px, 58vw, 340px); margin: 0 0 34px;
  }
  .services--varf .services__col { width: 100%; padding: 0 var(--gut); }
  .services--varf .svc__rule { display: none; }

  /* the 264 column box is a desktop measure; on a phone it opened a dead
     screen-third between the head and the switch */
  .portfolio .split__l, .markets__head .split__l { min-height: 0; }
  .markets__head { padding: 56px 0 24px; }
  .markets__r { gap: 24px; }
  .careers__r { gap: 36px; }
  .careers__l { gap: 24px; }
}

/* Careers photograph breaks the rail to the left, the way the preconstruction
   photo does. It kept its right edge where the board put it (rail + 580) and
   runs off the canvas edge instead of starting flush with the title. */
.careers__img { left: 0; width: calc(var(--rail-x) + 580px); }
@media (max-width: 1100px) { .careers__img { left: auto; width: 100%; } }

/* ── carousel controls: square, and out of the index's way ────────
   Round buttons were the one curve left in a build whose whole rule is square
   corners. They were also positioned absolutely, so the index list ran
   underneath them and the last label sat behind the arrows. Back in flow with
   the index as the flexible track, so the two can never collide. */
.markets__indexrow { display: flex; align-items: flex-start; gap: 32px; }
.mindex { flex: 1 1 auto; min-width: 0; }
.markets__pager { position: static; flex: 0 0 auto; margin: 0; gap: 12px; }
.circ {
  width: 44px; height: 44px; border-radius: 0;
  display: inline-flex; align-items: center; justify-content: center;
}

/* two lines, as written: 520 was narrower than the first line needs at 40px,
   so the <br> gave three lines rather than two */
.mission__title { max-width: 660px; }

/* ── preconstruction: photo matches the column it sits beside ─────
   It was 482 tall and pulled 56 above the accordion, so the two columns
   started and finished at different heights. It is now the same box as the
   list's tallest state and starts on the same line, which is what "equally
   aligned" asks for. Padding comes in to keep the stage at the board's 663. */
.services__inner--varf { padding-block: 48px; }
.services__inner--varf .services__visual { align-self: start; margin-top: 0; height: 566px; }
.svc__rule--gray { top: 48px; height: 566px; }

@media (max-width: 900px) {
  .services__inner--varf { padding-block: 0 56px; }
  .services__inner--varf .services__visual { height: clamp(230px, 58vw, 340px); }
}

/* The copy panel's 144 left padding left the title only 389px to wrap in, so
   the <br> produced four lines rather than two. Back to the board's 50/50
   split with the copy starting on the rail. */
.mission { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.mission__l { padding: 72px 64px 72px var(--rail-x); }
.mission__title { max-width: none; }

/* portfolio wall marks up a step — 150 read undersized in a 360 cell */
.wall__cell img { width: 172px; height: 40px; }

/* At 40px the first line measures 566 against 536 of column, so the <br> still
   bought a third line. 38 on a 576 column clears it with room to spare, and two
   points off a 40px head is not a size anyone reads as different. */
.mission__l { padding-right: 24px; }
.mission__title { font-size: clamp(26px, 2.6vw, 38px); line-height: 1.4; }

@media (max-width: 900px) {
  /* The 50/50 split is a desktop rule and, being written later in the file, it
     was beating the old single-column mobile rule — the copy panel was 195px
     wide on a 390 screen and the title ran to six lines. */
  .mission { grid-template-columns: 1fr; }
  .mission__l { padding: 56px var(--gut) 56px var(--rail-x); }
  .mission__title { font-size: 26px; line-height: 1.38; }
}

/* ── platform band: a line per sentence, the last one carrying the weight ──
   The 681 measure existed to force the wrap; the breaks are explicit now, so
   it only risked wrapping a line that is meant to be whole. The bold sits on
   its own element rather than on a :last-child of the lit words, which is a
   hook that stopped existing when the band moved off the scroll-lit treatment. */
.statement--band .statement__copy { max-width: none; }
.statement__strong { font-weight: 600; }

/* ── two-column sections all split 645 / 555 ──────────────────────
   The design system is explicit: 1200 rail, left 645, right 555 starting at
   765. Neither of these obeyed it. Markets ran on the generic .split defaults
   (520 + an 80 gap) so its right column began at 720, and careers carried the
   board's 162 right padding, so both columns stopped ~42 short of the rail's
   right edge while every head above them ran the full 1200. Same split, same
   765, same 1320 for all of them now. */
.split { gap: 0; justify-content: space-between; }
.split .split__l { flex: 0 0 645px; }
.split .split__r { flex: 0 0 555px; max-width: none; }
.markets__r { flex: 0 0 555px; max-width: none; }
.markets__copy { max-width: none; }

.careers__grid { padding-right: var(--rail-x); }
.careers__l { flex: 0 0 645px; }
.careers__r { width: 555px; flex: 0 0 555px; }

@media (max-width: 1100px) {
  .split { gap: 32px; }
  .split .split__l, .split .split__r, .markets__r { flex: 0 0 auto; width: 100%; }
  .careers__grid { padding-right: var(--gut); }
  .careers__l, .careers__r { flex: 0 0 auto; width: 100%; }
}

/* Careers photograph gains 62px of height and is bottom-anchored rather than
   dropped from a fixed 273. It ran 315→715 against a section whose content
   bottom is 609, so it overshot its own padding while still reading short.
   Anchoring from the base keeps the board's 56 of clearance whatever the
   title does above it. */
.careers__img { top: auto; bottom: 56px; height: 462px; }
@media (max-width: 1100px) { .careers__img { bottom: auto; height: clamp(240px, 46vw, 400px); } }

/* ── footer ───────────────────────────────────────────────────────
   The footer Connect had no rule of its own at all — it rendered as raw inline
   text with the arrow jammed against the word and its <i> invisible, while the
   header's identical markup got the full label · gap · arrow · rule treatment.
   It is the same component; it now reads as one. */
/* It now carries .cta and uses the header's own markup, so it inherits the
   tested component rather than reimplementing it. A second implementation is
   what left it unstyled in the first place. */

/* and the block itself tightens: 88/64 of padding and a 60 row gap left the
   follow row stranded 60px below columns that had already run out of content */
.ftr__inner { padding: 64px 0 48px; gap: 40px 48px; }
.ftr__follow { gap: 12px; }
.ftr__social { gap: 10px; }
.ftr__social a { width: 32px; height: 32px; }

/* FOLLOW was parked in column 4 against the right rail while Connect sat in
   column 1, so the second row stretched the full 1200 with nothing between
   them. Moving it in a column pulls the block narrower and brings the social
   marks closer at the same time, and a tighter row gap lifts them. */
.ftr__inner { gap: 28px 48px; }
.ftr__follow { grid-column: 3; }
@media (max-width: 1100px) { .ftr__follow { grid-column: 1 / -1; } }

/* the investor lede reads as a standing statement, not a caption */
.topbar__lede { font-weight: 700; }

/* ── markets switch: the rule belongs to the tab above it ─────────
   At 1.26 line-height on 60px type the rule sat in a pocket of descender space
   roughly as deep as the gap below it, so it read as floating between the tabs
   and the copy rather than underlining either. Tighter leading pulls it up
   under the letterforms; the copy gains a little air so the two gaps are
   clearly unequal and the grouping reads. */
.switch { gap: 44px; }
.switch__tab { line-height: 1.06; padding-bottom: 16px; }
.markets__r { gap: 44px; }
@media (max-width: 900px) {
  .switch { gap: 28px; }
  .switch__tab { padding-bottom: 10px; }
  .markets__r { gap: 28px; }
}

/* ── portfolio wall stays a grid on a phone ───────────────────────
   Below 760 it collapsed to one column, so the eight companies became eight
   full-width photographs and the section ran to 3553px. Two up × four keeps it
   readable as a grid and the marks scale with the cell. */
@media (max-width: 760px) {
  .wall { grid-template-columns: repeat(2, 1fr); }
  .wall__cell { aspect-ratio: 1 / 1; }
  .wall__cell img { width: 96px; height: 22px; }
  .wall__copy { left: 12px; right: 12px; bottom: 14px; gap: 6px; }
  .wall__loc { font-size: 8px; letter-spacing: .1em; }
  .wall__desc { display: none; }        /* no room for it in a half-width cell */
}

/* ── mobile menu slides ───────────────────────────────────────────
   It used to be a 0.3s opacity keyframe fired by the `hidden` attribute coming
   off, so it had no motion and no exit at all. The panel travels in from the
   right; its contents follow on a short stagger so the menu assembles rather
   than arriving as one slab. Everything keys off `.is-on`, which app.js adds a
   frame after the panel is displayed and removes before it is hidden again. */
@media (max-width: 760px) {
  .mobnav {
    transform: translateX(100%);
    transition: transform .46s cubic-bezier(.16,.84,.24,1);
    will-change: transform;
  }
  .mobnav.is-on { transform: none; }

  .mobnav__scrim, .mobnav__bg {
    opacity: 0; transition: opacity .5s ease .06s;
  }
  .mobnav.is-on .mobnav__scrim, .mobnav.is-on .mobnav__bg { opacity: 1; }

  .mobnav__list li, .mobnav__cta, .mobnav__media, .mobnav__foot {
    opacity: 0; transform: translateX(26px);
    transition: opacity .4s ease var(--d, 0ms), transform .5s cubic-bezier(.16,.84,.24,1) var(--d, 0ms);
  }
  .mobnav.is-on .mobnav__list li,
  .mobnav.is-on .mobnav__cta,
  .mobnav.is-on .mobnav__media,
  .mobnav.is-on .mobnav__foot { opacity: 1; transform: none; }

  .mobnav__list li:nth-child(1) { --d: 120ms; }
  .mobnav__list li:nth-child(2) { --d: 165ms; }
  .mobnav__list li:nth-child(3) { --d: 210ms; }
  .mobnav__list li:nth-child(4) { --d: 255ms; }
  .mobnav__list li:nth-child(5) { --d: 300ms; }
  .mobnav__cta   { --d: 345ms; }
  .mobnav__media { --d: 390ms; }
  .mobnav__foot  { --d: 430ms; }
}

@media (prefers-reduced-motion: reduce) {
  .mobnav, .mobnav__scrim, .mobnav__bg,
  .mobnav__list li, .mobnav__cta, .mobnav__media, .mobnav__foot {
    transition: none; transform: none; opacity: 1;
  }
}

/* ── mobile footer ────────────────────────────────────────────────
   The desktop layout places every block by explicit grid-column AND grid-row.
   The old mobile block reset some of the columns and none of the rows, so four
   blocks stayed pinned to row 1 and two to row 2 and the whole footer printed
   on top of itself. A flex column has no placement to leak — the blocks simply
   follow DOM order. */
@media (max-width: 760px) {
  .ftr__inner {
    display: flex; flex-direction: column; align-items: stretch;
    gap: 36px; padding: 48px 0 36px;
  }
  .ftr__brand, .ftr__links, .ftr__cos, .ftr__addr,
  .ftr__connect, .ftr__follow { grid-column: auto; grid-row: auto; width: 100%; }
  .ftr__brand { display: flex; align-items: center; gap: 20px; }
  .ftr__logo, .ftr__logo img { width: 128px; height: auto; }
  .ftr__we { font-size: 28px; line-height: 34px; }
  .ftr__links { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 20px; }
  .ftr__connect { gap: 18px; }
  .ftr__connectline { width: 100%; }
}

/* ── sections layer on a phone too ────────────────────────────────
   These were held to `position: relative` under 900 on the assumption that
   stacking tall sections would bury their lower halves. That was true of a
   `top: 0` stick; it is not true of --stick-top, which is min(0, viewport −
   height) and so holds a tall band only once its LAST screen is showing.
   app.js recomputes it per viewport, so the phone gets the same behaviour. */
@media (max-width: 900px) {
  .page > .mission, .page > .bandquote, .page > .markets,
  .page > .safety, .page > .careers {
    position: sticky; top: var(--stick-top, 0px);
  }
}

/* the investor lede is a standing statement, set as one */
.topbar__lede { text-transform: uppercase; letter-spacing: .09em; }

/* ── card text holds still while the card grows ───────────────────
   The chip and copy were pinned left AND right, so widening the card from 420
   to 520 rewrapped every line under the cursor. They are now a fixed 372
   measure anchored to the left edge: the frame and the photograph grow, the
   words do not move. */
.mcard__text { right: auto; width: 372px; }
.mcard__chip { max-width: 372px; }
@media (max-width: 1000px) { .mcard__text { width: auto; right: 24px; } }

/* Connect leads, the line supports it. column-reverse put the CTA under its own
   supporting sentence, which reads as a footnote rather than an invitation. */
.ftr__connect { flex-direction: column; }

/* FOLLOW back on the right rail under the address it belongs with, and both
   rows pulled in — 64/48 of padding on a 28 row gap left the second row
   floating well clear of columns that had already run out of content. */
.ftr__follow { grid-column: 4; }
.ftr__inner { gap: 18px 48px; padding: 56px 0 40px; }
@media (max-width: 1100px) { .ftr__follow { grid-column: 1 / -1; } }

/* The two tabs already measured 516 of a 555 column, so the 44 gap was pushing
   the switch 6px past its own rail — there was no room left to open it up.
   54px buys 91px of headroom and the pair sits at 72 apart, comfortably inside
   the column. Two points under the board's 60, in exchange for the separation. */
.switch { gap: 72px; }
.switch__tab { font-size: clamp(32px, 3.8vw, 54px); }
@media (max-width: 900px) { .switch { gap: 32px; } .switch__tab { font-size: 30px; } }

/* The board's −36 pull of the wall into the head is a desktop move: there the
   copy sits in the right column, clear of the grid below. Stacked on a phone
   the copy lands directly above the cells, so the pull put the last line hard
   against the first photograph. */
@media (max-width: 900px) {
  .portfolio .wall { margin-top: 0; }
  .portfolio .portfolio__head { padding: 48px 0 40px; }
  .portfolio__copy { font-size: 17px; line-height: 26px; }
}

/* ── markets category dropdown ────────────────────────────────────
   Replaces the horizontally scrolling index. Square corners throughout, the
   trigger set in the same 17px the list items used, and the panel sized to its
   own content rather than a fixed drawer. */
.mksel { position: relative; flex: 0 0 auto; }
.mksel__trigger {
  display: inline-flex; align-items: center; gap: 14px;
  padding: 0 0 16px; border: 0; background: none; cursor: pointer;
  font-family: var(--f-display); font-size: 17px; line-height: 28px;
  color: #1A3768; transition: color .15s var(--ease);
}
.mksel__trigger:hover { color: #007CBB; }
.mksel__chev {
  width: 9px; height: 9px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: translateY(-3px) rotate(45deg); transition: transform .28s var(--ease);
}
.mksel.is-open .mksel__chev { transform: translateY(1px) rotate(-135deg); }

.mksel__list {
  position: absolute; left: 0; top: calc(100% + 8px); z-index: 20;
  margin: 0; padding: 8px 0; list-style: none; min-width: 300px;
  background: #fff; border: 1px solid #C9D4E1;
  box-shadow: 0 18px 44px rgba(13,31,59,.14);
  opacity: 0; transform: translateY(-8px);
  transition: opacity .22s var(--ease), transform .26s var(--ease);
}
.mksel.is-open .mksel__list { opacity: 1; transform: none; }
.mksel__list[hidden] { display: none; }
.mksel__list button {
  display: block; width: 100%; text-align: left;
  padding: 9px 22px; border: 0; background: none; cursor: pointer;
  font-family: var(--f-display); font-size: 17px; line-height: 26px;
  color: rgba(26,55,104,.62); transition: color .14s var(--ease), background-color .14s var(--ease);
}
.mksel__list button:hover { color: #1A3768; background: #F2F6FA; }
.mksel__list button.is-active { color: #1A3768; }

/* the rule now reads as carousel progress rather than an item marker */
.markets__indexrow::after { transition: width .42s var(--ease); }

@media (max-width: 900px) {
  .mksel__trigger { font-size: 16px; padding-bottom: 12px; }
  .mksel__list { min-width: min(300px, calc(100vw - 40px)); }
}

/* ── a card cycles its own projects ───────────────────────────────
   Markets that hold more than one project stack their photographs in the same
   card and cross-fade between them, chip included, instead of taking a second
   slot in the strip and repeating the title. The fade is long on purpose: this
   is the card breathing, not a slideshow. */
.mcard__img { opacity: 0; transition: opacity 1.4s var(--ease); }
.mcard__img.is-on { opacity: 1; }
.mcard__chip { opacity: 0; pointer-events: none; transition: opacity .7s var(--ease); }
.mcard__chip.is-on { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .mcard__img, .mcard__chip { transition: none; }
}

/* ── mobile footer, compacted so it can be revealed ───────────────
   Two problems at once. The brand lockup is a horizontal one — logo, rule,
   statement — and on a 350 rail that left "We Are Builders." 98px to live in,
   so it wrapped to nothing. And at 1025 tall the footer could not be parked
   behind the page on an 844 screen without burying its own top, which is why
   the reveal was switched off below 1000 in the first place. Pairing the four
   list blocks into two rows brings it inside the viewport and the reveal comes
   back on its own. */
@media (max-width: 760px) {
  .ftr__inner { display: grid; grid-template-columns: 1fr 1fr; gap: 30px 24px; padding: 40px 0 32px; }

  .ftr__brand {
    grid-column: 1 / -1; flex-direction: column; align-items: flex-start; gap: 14px;
  }
  .ftr__divide { display: none; }
  .ftr__logo, .ftr__logo img { width: 150px; height: auto; }
  .ftr__we { font-size: 30px; line-height: 36px; }

  .ftr__links { grid-column: 1; display: flex; flex-direction: column; gap: 7px; }
  .ftr__cos   { grid-column: 2; }
  .ftr__cos ul { display: flex; flex-direction: column; gap: 7px; }
  .ftr__addr  { grid-column: 1; }
  .ftr__follow{ grid-column: 2; align-self: start; }
  .ftr__connect { grid-column: 1 / -1; }

  .ftr__links a, .ftr__cos li, .ftr__addr p { font-size: 14px; line-height: 21px; }
  .ftr__addr p { margin: 0 0 10px; }
  .ftr__legalrow { height: 52px; }
}

/* the safety photograph carries the brand blue rather than a neutral navy
   scrim — the band above and the plate below are both blue, and a grey-navy
   wash between them read as a different section wearing the same headline */
.safety__scrim {
  background: linear-gradient(180deg,
    rgba(0,124,187,.30) 0%,
    rgba(0,124,187,.44) 46%,
    rgba(10,110,174,.74) 100%);
}

/* "Builders." carries the weight in the footer as it does in the hero. .ftr__we
   sets 200 after the shared bold rule, so the bold needs to come later. */
.ftr__statement .ftr__we--bold { font-weight: 500; }

/* The ghosted mark is sized to overrun its band top and bottom rather than
   sitting inside it with air above and below — it reads as a watermark the
   band is cut out of, not a graphic placed on it. */
.band__watermark, .band__watermark--quote {
  width: auto; height: 168%; top: -34%; bottom: auto;
}

@media (max-width: 760px) {
  /* rows sized to their content, not stretched to the tallest cell — that is
     what left FOLLOW stranded at the bottom of a row the address block sized */
  .ftr__inner { align-content: start; }
  .ftr__inner > * { align-self: start; }
  .ftr__addr p { margin: 0 0 8px; }
  .ftr__cos .lbl, .ftr__addr .lbl { margin-bottom: 10px; display: block; }
  .ftr__follow { gap: 10px; }
  .ftr__social { gap: 8px; }
  .ftr__social a { width: 30px; height: 30px; }
}

@media (max-width: 760px) {
  /* Explicit rows. Naming only the column let auto-placement drop FOLLOW into
     a row of its own below the address rather than beside it, which is where
     ~180px of the footer's height was going. */
  .ftr__brand   { grid-row: 1; }
  .ftr__links   { grid-row: 2; }
  .ftr__cos     { grid-row: 2; }
  .ftr__addr    { grid-row: 3; }
  .ftr__follow  { grid-row: 3; }
  .ftr__connect { grid-row: 4; }
}

/* the board's own mission photograph (11JU-0), not the plant shot that was
   standing in for it */
.mission__r::before { background-image: url("assets/img/mission-crew.jpg"); background-position: 50% 50%; }

/* ── accordion: weight carries the state, not size ────────────────
   Opening a row took its title from 26 to 32, which wrapped the two long ones
   to a second line and changed the row's height with them — the rail, the
   marker and every row below it all moved for a reason that had nothing to do
   with the content. Same size in both states, weight and colour do the work,
   and the titles stay on one line. */
/* Padding was landing three times over: 22 top and bottom on .sv__t, another
   22 on the row I had added, and then the open row dropped its bottom 22 — so
   a closed row carried 88px of air around 34px of type and changed height when
   it opened. One source now, on the title, and the same in both states.

   Colour is the only thing transitioned. font-size no longer changes, and
   font-weight is not smoothly interpolable — Chrome re-synthesises and re-lays
   out the text every frame, which is the glitch. */
.services__inner--varf .sv__t {
  padding: 15px 0; font-size: 26px; line-height: 34px; font-weight: 400; color: #5A6C82;
  transition: color .26s var(--ease);
}
.services__inner--varf .sv.is-on .sv__t { font-size: 26px; font-weight: 500; color: #12294D; padding-bottom: 15px; }
.services__inner--varf .sv { padding-block: 0; }

/* the drawer is what animates, and it eases the same way in both directions */
.services__inner--varf .sv__d { transition: height .52s var(--ease), opacity .3s var(--ease); }
.services__inner--varf .sv__d p { margin: 4px 0 22px; font-size: 16px; line-height: 28px; color: #43505F; }

/* ── mobile footer brand keeps its horizontal lockup ──────────────
   Stacking it was the wrong fix for the statement being crushed to 98px — the
   answer was a smaller mark, not a different lockup. Logo 110 leaves the rail
   ~220 for "We Are Builders.", which is more than the 150 it needs. Connect
   follows directly under the brand; the four list blocks come after it. */
@media (max-width: 760px) {
  .ftr__brand {
    grid-row: 1; grid-column: 1 / -1;
    flex-direction: row; align-items: center; gap: 16px;
  }
  .ftr__divide { display: block; width: 1px; align-self: stretch; background: rgba(255,255,255,.18); }
  .ftr__logo, .ftr__logo img { width: 110px; height: auto; }
  .ftr__we { font-size: 26px; line-height: 32px; }

  .ftr__connect { grid-row: 2; grid-column: 1 / -1; }
  .ftr__links   { grid-row: 3; grid-column: 1; }
  .ftr__cos     { grid-row: 3; grid-column: 2; }
  .ftr__addr    { grid-row: 4; grid-column: 1; }
  .ftr__follow  { grid-row: 4; grid-column: 2; }
}

/* copy sits on the same 15px rhythm as the title above it, so the space over
   the paragraph matches the space under it */
.services__inner--varf .sv__d p { margin: 12px 0 15px; }

/* hero head lifts a little off the logo strip */
.hero__head { bottom: 272px; }
@media (max-width: 900px) { .hero__head { bottom: 216px; } }

/* ── menu groups + focused card ───────────────────────────────────
   The menu carries both markets, so it needs to say which is which. Picking
   one opens that card wide and knocks the rest back, which is the only way a
   selection reads when the card you asked for is mid-strip. */
.mksel__group {
  padding: 14px 22px 6px; font-family: var(--f-mono);
  font-size: 11px; line-height: 16px; letter-spacing: .11em; text-transform: uppercase;
  color: #8FA6BC; pointer-events: none;
}
.mksel__list li:first-child .mksel__group, .mksel__group:first-child { padding-top: 6px; }

.markets__scroll .mcards .mcard.is-focus { width: 520px; }
.markets__scroll .mcards .mcard.is-back { opacity: .42; }
.mcard { transition-property: opacity, transform, width; }

/* The title's own 15px of bottom padding sits inside the gap, so a 12px
   paragraph margin on top of it read as ~34 between the header and its copy.
   The padding is the whole gap now. */
.services__inner--varf .sv__d p { margin: 0 0 15px; }

/* ── preconstruction: breathing room and a directional swap ───────
   15px of row padding was tighter than the section wanted once the titles
   stopped resizing. 19 gives it back. The stage and the photograph are sized
   to the list's tallest state rather than to an inherited 566, which is where
   ~100px of slack under the list was coming from. */
.services__inner--varf .sv__t { padding: 19px 0; }
.services__inner--varf .sv.is-on .sv__t { padding-bottom: 19px; }
.services__inner--varf .sv__d p { margin: 0 0 19px; }
.services__inner--varf .services__visual { height: 498px; }
.svc__rule--gray { height: 498px; }
.services__stage { height: 594px; }

/* the incoming frame travels in from the direction the list moved */
.sv__layer {
  transform: translateY(var(--from, 0));
  transition: opacity .5s var(--ease), transform .78s var(--ease);
}
.sv__layer.is-on { transform: none; }

@media (max-width: 900px) {
  .services__stage { height: auto; }
  .services__inner--varf .services__visual { height: clamp(230px, 58vw, 340px); }
}

/* held across a re-split so the rebuild cannot replay the rise */
.hero__body.m-nofx .m-line > span { transition: none !important; }

/* ── mission: the two halves are offset, not simultaneous ─────────
   The photograph starts wider and holds a beat behind the plate, so the plate
   arrives first and the image is still settling behind it. Both moving on the
   same clock read as one effect applied twice. */
.mission__r::before { transform: scale(1.28); transform-origin: right center; }
.m-ready .mission__r[data-m-rise]::before {
  transition: transform 1.9s cubic-bezier(.16,.84,.24,1) 260ms;
}

.mksel__all { font-weight: 500; color: #1A3768 !important; }
.mksel__list li:first-child { border-bottom: 1px solid #E4EAF1; padding-bottom: 4px; margin-bottom: 4px; }

/* a wider open — 520 next to 420 read as a nudge rather than a focus */
.markets__scroll .mcards .mcard.is-focus { width: 620px; }
@media (hover: hover) and (pointer: fine) and (min-width: 901px) {
  .markets__scroll:not(:active) .mcards .mcard:hover { width: 620px; }
}

/* The index row carried a hairline and a blue progress segment from when the
   categories were a scrolling rail. With a dropdown there is no rail for them
   to measure, so they are gone rather than left as decoration. */
.markets__indexrow::before, .markets__indexrow::after { content: none; }

/* The project cross-fade inside a card runs slower — at 1.4s against a 2.4s
   dwell the card was settled for barely a second before the next swap started,
   which read as churn rather than a slow reveal. */
.mcard__img { transition: opacity 2.2s var(--ease); }
.mcard__chip { transition: opacity 1.1s var(--ease); }

/* ── a sliver of the next section under the held frame ────────────
   The pin filled the viewport, so while preconstruction was locked there was
   no hint of what came next. It is 88px short of the screen now and the track's
   own ground is the portfolio's navy, so that band reads as the next section
   waiting underneath — and when the portfolio actually rises it lands exactly
   on the colour that was already there, with no seam. */
@media (min-width: 901px) {
  .services--varf { background: var(--navy-900); }
  .services__pin { height: calc(100svh - 88px); background: #fff; }
}

/* ── the panel is a sibling of the header, not a child ────────────
   .hdr carries transform: translateY(--fold), and a transformed ancestor
   becomes the containing block for position: fixed. So the panel was being
   sized to the header's 137px box — it filled the top bar — until `is-open`
   set transform: none, at which point it snapped to the viewport. Moving it
   out of the header is the fix; no ancestor can contain it now. It sits just
   under the header in the stack so the logo and the burger stay on top. */
.mobnav { z-index: 55; }
.hdr { z-index: 60; }

/* ── controls sit together under the head ─────────────────────────
   Arrows first, then the market menu beside them, both on the left rail
   directly beneath "Markets We Serve". They were split to opposite ends of
   the rail by the space-between left over from when a scrolling index sat
   between them — with the index gone there is nothing to hold apart. */
.markets__indexrow {
  justify-content: flex-start; align-items: center; gap: 24px; height: auto;
  padding: 0 0 8px;
}
.markets__pager { order: 0; }
.mksel { order: 1; }
/* the trigger's 16px was clearance for the underline that has been removed */
.mksel__trigger { padding-bottom: 0; }
.mksel__list { top: calc(100% + 14px); }

@media (max-width: 900px) { .markets__indexrow { gap: 16px; } }

/* Markets sits on white rather than the page's blue-grey ground */
.page > .markets { background: #fff; }

/* The stage was centred inside the PIN, and the pin is 88px short of the screen
   so the next section's sliver can show. That put 157 of air above the
   composition and 245 below it. Centring against the full viewport instead —
   the sliver is part of what you see, so it has to be part of the sum. */
@media (min-width: 901px) {
  .services__pin { align-content: start; padding-top: calc((100svh - 594px) / 2); }
}

/* The ghosted mark carries whitespace under the lockup in its own artwork, so
   bleeding it evenly top and bottom still left a visible gap at the base.
   Dropped lower and grown so the letterforms themselves run past both edges. */
.band__watermark, .band__watermark--quote { height: 190%; top: -14%; }

/* ── markets head, tightened ──────────────────────────────────────
   364 tall for 200px of title. The 264 minimum on the left column was doing
   nothing except holding a floor no content reached, the switch sat 44 clear
   of its own copy, and the block carried 100px of padding. Nothing here needed
   that much room. */
.markets__head { padding: 56px 0 18px; }
.markets__head .split__l { min-height: 0; }
.markets__r { gap: 30px; }
.markets__indexrow { padding: 0 0 6px; }
@media (max-width: 900px) { .markets__head { padding: 44px 0 14px; } }

/* ── mobile preconstruction: the photograph locks, the list runs under it ──
   Desktop pins the whole composition and steps the rows in place. A phone has
   no room for that, so the photograph is what sticks — cropped to a band at the
   top — and the list scrolls beneath it, each row changing the image as it
   passes under. A white gradient rides with the photo so the type fades out
   rather than being guillotined by its edge. */
@media (max-width: 900px) {
  .services--varf { overflow: visible; }
  .services__pin { position: static; height: auto; display: block; }
  .services__stage { height: auto; }
  .services__inner--varf { display: block; padding: 0 0 40px; }

  .services__inner--varf .services__visual {
    position: sticky; top: 0; z-index: 2;
    width: 100%; height: 42svh; margin: 0;
  }
  /* No fade under the photo. A sticky pseudo-element with a negative margin
     re-composites against moving content every frame, and the jitter cost more
     than the softening was worth — the photo's own edge does the job. */
  .services--varf .services__col { width: 100%; padding: 56px var(--gut) 0; }
  .services--varf .svc__rule { display: none; }
  .services__inner--varf .sv__t { padding: 17px 0; font-size: 22px; line-height: 30px; }
  .services__inner--varf .sv.is-on .sv__t { padding-bottom: 17px; font-size: 22px; }
}

/* The head and the numbers sit on white; only the rail the cards ride on
   carries the off-white, so the strip reads as a tray rather than the whole
   section changing colour. */
.page > .markets { background: #fff; }
.markets__scroll { background: #E9EEF4; }
.page > .facts { background: #fff; }

/* ── markets head on the grid ─────────────────────────────────────
   Three things were floating. The switch was 537 wide in a 555 column, so its
   right edge stopped 18px short of the copy beneath it and the two underlines
   bracketed nothing. The right column carried an 8px top pad that pushed it off
   the title's line. And the tab gap was a fixed 72 rather than whatever it
   takes to reach the rail. Both tabs now sit on the column's own edges, so the
   underlines, the copy and the rail all agree. */
.markets__r { padding-top: 0; }
.switch { width: 100%; justify-content: space-between; gap: 24px; }
.switch__tab { flex: 0 0 auto; }
@media (max-width: 900px) { .switch { justify-content: flex-start; gap: 28px; } }

/* Mobile controls: one row, arrows left and menu right, not a centred stack.
   An older narrow-screen rule was still turning this row into a column. */
@media (max-width: 900px) {
  .markets__indexrow {
    flex-direction: row; align-items: center; justify-content: space-between;
    gap: 16px; padding: 0 0 6px;
  }
  .markets__pager { order: 0; margin: 0; }
  .mksel { order: 1; }
  .mksel__list { left: auto; right: 0; }
}

/* Building Safely carries the 270 Park erection frame — an ironworker
   harnessed on the beam guiding a hoisted girder, which is the section's whole
   argument in one picture. */
.safety__img { background-image: url("assets/img/safety-270park.jpg"); background-position: 50% 44%; }

/* The rail scrolls sideways only. Declaring overflow on one axis makes the
   browser compute the other as auto, so the strip could be nudged vertically
   inside its own box and the cards drifted off their line. */
.markets__scroll { overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; }

@media (max-width: 900px) {
  /* the controls were pinched between the copy above and the rail below */
  .markets__indexrow { padding: 18px 0 22px; }
}

@media (max-width: 900px) {
  /* the copy's line box was running past the head's own padding, so the
     controls started five pixels above it */
  .markets__head { padding: 44px 0 28px; }
  .markets__indexrow { padding: 8px 0 24px; }
}

/* Desktop controls had the same pinch as mobile: 17px above and sitting flush
   on the card rail, so the row read as part of the strip rather than as the
   thing that drives it. */
.markets__head { padding-bottom: 30px; }
.markets__indexrow { padding: 10px 0 32px; }

/* Building Safely's photograph carries the navy scrim, not the blue one — it
   matches the board's photo band and lets the two blue plates above and below
   it read as the blue, rather than three blues in a row. */
.safety__scrim {
  background: linear-gradient(180deg,
    rgba(13,31,59,.16) 0%,
    rgba(13,31,59,.24) 34%,
    rgba(18,41,77,.64) 66%,
    rgba(18,41,77,.80) 100%);
}

/* ── Building Safely: both headlines over the photograph ──────────
   The board dropped the blue plate that used to carry "Building Safely" and
   set it on the picture with "Raising the Standard, Nationwide" beneath — one
   statement in two sizes rather than a heading in one band and a subhead in
   the next. Frame ZKU-0: left 120, bottom 64, 26 between them. */
.safety__bandA { display: none; }
.safety__band { height: 480px; }
.safety__copy {
  display: flex; flex-direction: column; align-items: flex-start; gap: 26px;
  bottom: 64px;
}
.safety__copy .safety__t1 {
  margin: 0; font-weight: 300; font-size: clamp(48px, 6.7vw, 96px);
  line-height: 1.04; letter-spacing: -0.035em; color: #fff;
}
.safety__copy .safety__t2 {
  margin: 0; font-weight: 300; font-size: clamp(28px, 3.6vw, 52px);
  line-height: 1.19; letter-spacing: -0.02em; color: #fff; text-align: left;
}
@media (max-width: 900px) {
  .safety__band { height: 420px; }
  .safety__copy { bottom: 40px; gap: 16px; }
}

/* On a phone the careers photograph breaks the rail and runs edge to edge,
   the way the preconstruction and careers photos do on desktop. Negative
   margins against the column's own padding rather than a computed width, so
   it stays correct whatever the gutter is at that breakpoint. */
@media (max-width: 1100px) {
  .careers__img {
    width: auto; margin-inline: calc(var(--gut) * -1);
    height: clamp(260px, 62vw, 420px);
  }
}

/* ── Building Safely, second board pass ───────────────────────────
   The board grew the photograph to 929 inside a 480 band and hid the old
   scrim in favour of a bottom-up navy wash (11WR-0). The oversize is what
   makes the picture travel under the section above it as you scroll, so the
   site matches it — centred rather than top-offset, because the parallax pass
   moves this layer and a static offset would fight the transform. */
.safety__img { top: -47%; height: 194%; background-position: 50% 44%; }
.safety__scrim {
  background: linear-gradient(0deg,
    rgba(13,31,59,.94) 0%,
    rgba(13,31,59,.80) 24%,
    rgba(13,31,59,.24) 73%,
    rgba(13,31,59,.04) 86%,
    rgba(13,31,59,0) 100%);
}

/* ── markets: a wider right column, tighter controls ──────────────
   The 645/555 split leaves the title's column two-thirds empty while the tabs
   and the paragraph are squeezed into 555. Markets carries two elements on the
   right where the other three sections carry one, so it gets 620 against 520 —
   the tabs and the copy both gain 65 and the two halves finally weigh the same.
   The gap under the head comes down with it; the arrows were sitting 64 below
   the title for no reason. */
.markets__head .split__l { flex: 0 0 520px; min-height: 0; }
.markets__r { flex: 0 0 620px; }
.markets__head { padding-bottom: 12px; }
.markets__indexrow { padding: 0 0 32px; }

@media (max-width: 1100px) {
  .markets__head .split__l, .markets__r { flex: 0 0 auto; width: 100%; }
}

/* `.split .split__r` is a two-class selector and .markets__r is one, so the
   555 kept winning. Same weight, later in the file. */
.markets__head .markets__r { flex: 0 0 620px; max-width: none; }
.markets__head .markets__copy { max-width: none; }

/* The intro statement sits on the shop floor now — a grinder throwing sparks
   off a pipe flange, which is the "self-perform" claim in one frame. Board
   crop, 50% 40%. */
.introphoto__img { background-image: url("assets/img/intro-grind.jpg"); background-position: 50% 40%; }

/* The industrial chips are a solid blue plate with Inter Medium, not the glass
   treatment the commercial project chips use — the board draws the two
   differently because one names a market and the other names a building. */
.mcard__chip--solid {
  background: #007CBB; border-color: #007CBB;
  font-family: var(--f-mono); font-weight: 500;
}

/* ── markets, facts and safety scroll ─────────────────────────────
   Nothing holds through this stretch any more: the cards scroll up, the
   numbers follow them, and Building Safely comes up underneath rather than
   layering over. Only mission and the quote band stay held, because those are
   the two that something is meant to rise over. */
.page > .markets { position: relative; top: auto; }
.page > .facts   { position: relative; top: auto; }
.page > .safety  { position: relative; top: auto; }

/* the safety detail plate is navy, matching the scrim above it — three blues
   stacked was one blue too many */
.safety__bandC .safety__detail {
  background: linear-gradient(180deg, #12294D 0%, #0D1F3B 100%);
}

/* The board leads with the portfolio line in Medium and follows with the long
   paragraph in Light — the order and the weights both changed. Board gap 28. */
.introphoto__lead, .introphoto__body { display: block; }
.introphoto__lead { font-weight: 300; }
.introphoto__body { font-weight: 300; margin-top: 28px; }

/* Flat navy, no gradient. The plate is a ground, not a wash — the gradient was
   carried over from when it was blue and reads as a seam against the scrim
   above it once both are navy. */
.safety__bandC .safety__detail { background: #12294D; }

/* ── mission: no horizontal move ──────────────────────────────────
   The plate travelling in from the left and the photograph scaling from the
   right are both off. Both halves fall back to the same fade-and-rise every
   other block on the page uses, so nothing here is a special case any more.
   The clip stays because the photo still overhangs its frame by 12%. */
.m-ready .mission__l[data-m-rise],
.m-ready .mission__r[data-m-rise] {
  opacity: 0; clip-path: none; transform: translateY(42px);
  transition: opacity 1s var(--m-ease, cubic-bezier(.16,.84,.24,1)) var(--m-delay, 0ms),
              transform 1s var(--m-ease, cubic-bezier(.16,.84,.24,1)) var(--m-delay, 0ms);
}
.m-ready .mission__l[data-m-rise].is-in,
.m-ready .mission__r[data-m-rise].is-in { opacity: 1; transform: none; }

.mission__r::before { transform: none; }
.m-ready .mission__r[data-m-rise]::before,
.m-ready .mission__r[data-m-rise].is-in::before { transform: none; transition: none; }

/* ── safety photo: keep the worker in frame ───────────────────────
   At 194% the layer had 225px of slack each way, which means the band that is
   visible at BOTH extremes of the travel is only 30px tall — everything else
   scrolls off at one end or the other, including the ironworker. 125% leaves
   60px of slack, so a 360px band is on screen throughout, and the crop is set
   on him rather than on the middle of the plate. */
.safety__img { top: -12.5%; height: 125%; background-position: 50% 58%; }

/* The ghosted mark fades up with its band rather than being there from the
   start. It rests at 10%, so the shared rise rule's is-in opacity of 1 has to
   be overridden — and it holds its position, since the mark is placed against
   the band edges and any translate would break that. */
.m-ready .band__watermark[data-m-rise] {
  opacity: 0; transform: none;
  transition: opacity 1.6s cubic-bezier(.16,.84,.24,1);
}
.m-ready .band__watermark[data-m-rise].is-in { opacity: .1; transform: none; }
