/* Reference composition for the opening sequence. Loaded after the generated
   brand tokens; all colours continue to come from client.tokens.json. */
:root { --gut: clamp(20px, 5vw, 110px); scroll-padding-top: 18px; }
.hdr .inner, .hero .inner {
  width: 100%;
  max-width: none;
  margin-inline: 0;
  justify-self: stretch;
  padding-inline: var(--gut);
}

/* A compact brand prelude leaves room for the photographic proposition. */
.intro {
  min-height: clamp(310px, 32vw, 460px);
  padding: 38px var(--gut) 40px;
  gap: 0;
  align-content: center;
}
.intro-glow {
  background: radial-gradient(48% 70% at 50% 42%,
    color-mix(in srgb, var(--brand-2) 12%, transparent), transparent 74%);
}
.intro-mark { width: clamp(142px, 16vw, 190px); }
.intro-mark img {
  animation: intro-fall 1500ms cubic-bezier(.16,.84,.16,1) both,
    intro-breathe 7.2s ease-in-out 1700ms infinite;
}
.intro-copy { margin-top: -4px; }
.intro-word {
  font-size: clamp(17px, 1.85vw, 23px);
  line-height: 1.42;
  letter-spacing: .31em;
}
.intro-tag {
  margin-top: 15px;
  font-size: clamp(9px, .95vw, 11.5px);
  letter-spacing: .28em;
}
.intro-cue { right: var(--gut); bottom: 65px; font-size: 10px; gap: 9px; }
.intro-cue-arrow { height: 34px; }

/* The existing image already contains the architectural threshold in the
   reference. Let its dark left wall carry a narrower, quieter text column. */
.hero { min-height: clamp(570px, 55vw, 750px); }
.hero .inner { padding-block: clamp(60px, 7vw, 95px); }
.hero .copy { max-width: min(550px, 100%); }
.hero .hero-title {
  font-size: clamp(38px, 4.5vw, 64px);
  line-height: 1.06;
  letter-spacing: -.025em;
  margin-top: 16px;
}
.hero .lede {
  font-size: clamp(16px, 1.35vw, 19px);
  line-height: 1.48;
  max-width: 43ch;
  margin-block: 23px 16px;
}
.hero .posline { letter-spacing: .12em; font-size: 10px; white-space: nowrap; }
.hero .cta { margin-top: 23px; }
.hero .btn.outline { border-radius: 0; padding: 14px 18px; }
/* Give the first white section enough depth to hold its own between the two
   full-bleed image sequences. */
.reality { background: var(--paper); color: var(--ink); }
.reality__inner {
  width: 100%;
  padding-inline: var(--gut);
  display: grid;
  align-items: center;
  grid-template-columns: minmax(0, 1.55fr) minmax(250px, .85fr);
  gap: clamp(36px, 5vw, 90px);
}
.reality__inner {
  min-height: clamp(430px, 30vw, 500px);
  padding-block: clamp(92px, 8vw, 128px);
}
.reality h2 {
  margin: 15px 0 0;
  font-size: clamp(29px, 3.5vw, 48px);
  line-height: 1.08;
  letter-spacing: -.025em;
  font-weight: 300;
}
.reality h2 span { display: block; color: var(--ink-40); }
.reality p {
  border-left: 1px solid var(--line-2);
  margin: 0;
  padding-left: clamp(24px, 3vw, 42px);
  color: var(--ink-55);
  font-size: clamp(15px, 1.25vw, 17px);
  line-height: 1.62;
  max-width: 37ch;
}

/* One panoramic argument takes the place of three bordered diagrams. */
.intelligence-band {
  position: relative;
  isolation: isolate;
  height: clamp(300px, 29vw, 440px);
  overflow: hidden;
  background: var(--ground);
}
.intelligence-band__image, .intelligence-band__wash {
  position: absolute;
  inset: 0;
  display: block;
}
.intelligence-band__image img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The port at dusk: refinery and harbour on the left, open water and the
     rigs under the sun on the right - which is the direction the figure
     travels, so it is NOT mirrored the way the mountain panorama was. The
     sky is dark cloud, so the captions sit on it without help. */
  object-position: 50% 50%;   /* must stay 50% 50%: the figure's xMidYMid slice matches it */
}
.intelligence-band__wash {
  background:
    linear-gradient(to bottom,
      color-mix(in srgb, var(--ground-3) 40%, transparent), transparent 45%),
    linear-gradient(to top,
      color-mix(in srgb, var(--ground-3) 45%, transparent), transparent 65%);
  pointer-events: none;
}
.intelligence-band__network {
  position: absolute;
  z-index: 2;
  inset: 0;
  width: 100%;
  height: 100%;
  /* --rk scales the ripple's px growth to the figure's unit size: one unit
     is now about 0.66px at 1440 where it was 1.15, so the rings would
     otherwise be small and tight. */
  --rk: 1.74;
  filter: drop-shadow(0 1px 5px color-mix(in srgb, var(--ground-3) 42%, transparent));
}
.intelligence-band__network-mobile { display: none; }
.intelligence-band__threads path {
  fill: none;
  stroke: color-mix(in srgb, var(--surface) 48%, transparent);
  stroke-width: .9;
}
.intelligence-band__network .intelligence-band__threads path { stroke-width: 1.6; }
/* The merged line from the gathering point to the rig: heavier and a
   little brighter than the hairlines that feed it. */
.intelligence-band__network .intelligence-band__trunk { stroke-width: 4; stroke: color-mix(in srgb, var(--surface) 62%, transparent); }
.intelligence-band__network .intelligence-band__flow path { stroke-width: 3.1; }
.intelligence-band__network .intelligence-band__destination,
.intelligence-band__network .intelligence-band__ripple { --r0: 14px; }
.intelligence-band__threads path {
  stroke-linecap: round;
}
/* band-gravity.js moves dots with translate and shrinks them with scale;
   fill-box + center is what makes the scale happen about the dot's own
   centre rather than the SVG's origin. Inert without the script. */
.intelligence-band__points circle, .intelligence-band__cluster circle,
.intelligence-band__destination, .intelligence-band__ripple { transform-box: fill-box; transform-origin: center; }
/* The left dots are white, and turn blue only while the pulse is on them.
   The dash covers path positions [100p, 100p+14] at cycle progress p, and
   wraps, so it sits over the START of the route from p = .86 until it has
   fully left at p = .14 of the next cycle. The dot lights as the pulse is
   born and goes white again as it leaves; same 5.2s cycle and the same
   per-route delay as its own flow path, so the two cannot drift apart. */
.intelligence-band__points circle {
  fill: var(--surface);
  animation: band-point 5.2s linear infinite;
  animation-delay: var(--fd, 0s);
}
@keyframes band-point {
  0%, 10%  { fill: var(--brand-light); filter: drop-shadow(0 0 5px var(--brand-light)) }
  16%, 82% { fill: var(--surface); filter: none }
  88%      { fill: var(--brand-light); filter: drop-shadow(0 0 5px var(--brand-light)) }
  100%     { fill: var(--brand-light); filter: drop-shadow(0 0 5px var(--brand-light)) }
}
.intelligence-band__cluster { display: none; }
.intelligence-band__cluster circle {
  fill: var(--brand-pale);
  opacity: .9;
}
.intelligence-band__cluster circle:nth-child(3n) { fill: var(--surface); }
.intelligence-band__cluster circle:nth-child(8) { fill: var(--brand-light); }
.intelligence-band__destination {
  fill: var(--surface);
  filter: drop-shadow(0 0 7px var(--surface));
}

/* --- the routes carry something ------------------------------------------
   The one piece of motion in this band, and it is the argument rather than
   decoration: knowledge held in separate places travels, converges, and
   arrives somewhere as a single direction.

   A short dash chasing a long gap, its offset run 100 -> 0 for ever. Every
   path declares pathLength="100", which is what makes one pattern fit all
   nine: they are different real lengths, so a dasharray in user units would
   travel at nine different speeds and none would loop seamlessly.

   THREE SURGES, NOT NINE EVENLY SPACED ARRIVALS. Spread evenly across the
   cycle the pulses land every 0.58s and the destination reads as a flicker.
   Grouped into three waves of three - with a little jitter inside each wave
   so it is a surge and not a synchronised triple - arrivals come at 1.73s,
   which is a rhythm rather than a strobe. */
.intelligence-band__flow path {
  fill: none;
  stroke: var(--brand-light);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-dasharray: 14 86;
  animation: band-flow 5.2s linear infinite;
  animation-delay: var(--fd, 0s);
}
@keyframes band-flow {
  from { stroke-dashoffset: 100 }
  to   { stroke-dashoffset: 0 }
}
/* wave A */
.intelligence-band__network :is(.intelligence-band__flow path, .intelligence-band__points circle):nth-child(1) { --fd: 0s }
.intelligence-band__network :is(.intelligence-band__flow path, .intelligence-band__points circle):nth-child(4) { --fd: -.10s }
.intelligence-band__network :is(.intelligence-band__flow path, .intelligence-band__points circle):nth-child(7) { --fd: -.19s }
/* wave B */
.intelligence-band__network :is(.intelligence-band__flow path, .intelligence-band__points circle):nth-child(2) { --fd: -1.733s }
.intelligence-band__network :is(.intelligence-band__flow path, .intelligence-band__points circle):nth-child(5) { --fd: -1.84s }
.intelligence-band__network :is(.intelligence-band__flow path, .intelligence-band__points circle):nth-child(8) { --fd: -1.92s }
/* the two ships, one in wave A and one in wave B */
.intelligence-band__network :is(.intelligence-band__flow path, .intelligence-band__points circle):nth-child(10) { --fd: -.28s }
.intelligence-band__network :is(.intelligence-band__flow path, .intelligence-band__points circle):nth-child(11) { --fd: -2.0s }
/* wave C */
.intelligence-band__network :is(.intelligence-band__flow path, .intelligence-band__points circle):nth-child(3) { --fd: -3.467s }
.intelligence-band__network :is(.intelligence-band__flow path, .intelligence-band__points circle):nth-child(6) { --fd: -3.57s }
.intelligence-band__network :is(.intelligence-band__flow path, .intelligence-band__points circle):nth-child(9) { --fd: -3.65s }
/* Four routes on the narrow figure, so they simply take a quarter of the
   cycle each - three waves of one would be the same thing said twice. */
.intelligence-band__network-mobile :is(.intelligence-band__flow path, .intelligence-band__points circle):nth-child(2) { --fd: -1.3s }
.intelligence-band__network-mobile :is(.intelligence-band__flow path, .intelligence-band__points circle):nth-child(3) { --fd: -2.6s }
.intelligence-band__network-mobile :is(.intelligence-band__flow path, .intelligence-band__points circle):nth-child(4) { --fd: -3.9s }

/* --- the arrival ---------------------------------------------------------
   The destination turns blue as each wave lands, and throws a ring outward.
   The 58% is not a taste decision, it is where the light actually arrives:
   the dash covers path positions [100p, 100p+14], so its leading edge
   reaches the end at p = 0.86 of the 5.2s cycle, and three waves means the
   arrival period is 5.2/3 = 1.733s. frac(3 x 0.86) = 0.58. The three paths
   inside a wave are jittered 0.1-0.19s apart, which spreads their arrivals
   over phases 47-58%, so the ramp starts at 40% and covers all three.

   The narrow figure has four routes on a 1.3s period, where the same
   arithmetic gives frac(4 x 0.86) = 0.44. Rather than a second set of
   keyframes saying the same thing, it is shifted onto these ones with a
   negative delay of (0.58 - 0.44) x 1.3s. */
.intelligence-band__destination {
  animation: band-arrive 1.733s linear infinite;
  --r0: 8px;
}
.intelligence-band__network-mobile .intelligence-band__destination {
  animation-duration: 1.3s;
  animation-delay: -.182s;
  --r0: 32px;
}
@keyframes band-arrive {
  0%, 40% { r: var(--r0); fill: var(--surface);
            filter: drop-shadow(0 0 7px var(--surface)) }
  58%     { r: calc(var(--r0) + 2.4px * var(--rk, 1)); fill: var(--brand-light);
            filter: drop-shadow(0 0 13px var(--brand-light)) }
  100%    { r: var(--r0); fill: var(--surface);
            filter: drop-shadow(0 0 7px var(--surface)) }
}

/* The ring. Timed linear with the shape written into the stops rather than
   with ease-out, because an ease applies across the whole cycle and this
   one has to sit still for the first 46% of it and only then decelerate
   outward. Thinning as it goes, so it dissipates rather than just fading. */
.intelligence-band__ripple {
  fill: none;
  stroke: var(--brand-light);
  animation: band-ripple 1.733s linear infinite;
  --r0: 8px;
}
.intelligence-band__network-mobile .intelligence-band__ripple {
  animation-duration: 1.3s;
  animation-delay: -.182s;
  --r0: 32px;
}
@keyframes band-ripple {
  0%, 46% { r: var(--r0);                            opacity: 0;   stroke-width: calc(2.4 * var(--rk, 1)) }
  53%     { r: calc(var(--r0) + 7px * var(--rk, 1));  opacity: .75; stroke-width: calc(2 * var(--rk, 1)) }
  66%     { r: calc(var(--r0) + 14px * var(--rk, 1)); opacity: .42; stroke-width: calc(1.4 * var(--rk, 1)) }
  82%     { r: calc(var(--r0) + 20px * var(--rk, 1)); opacity: .15; stroke-width: calc(.9 * var(--rk, 1)) }
  100%    { r: calc(var(--r0) + 24px * var(--rk, 1)); opacity: 0;   stroke-width: calc(.6 * var(--rk, 1)) }
}

@media (max-width: 820px) {
  .intro {
    min-height: min(65svh, 540px);
    padding-block: 48px 66px;
  }
  .intro-mark { width: clamp(150px, 42vw, 200px); }
  .intro-word { font-size: clamp(17px, 5vw, 22px); }
  .intro-tag { font-size: 10px; }
  .intro-cue { right: 50%; translate: 50% 0; bottom: 25px; }
  .intro-cue-arrow { height: 24px; }
  .hero { min-height: 0; }
  .hero .inner { padding-block: 32px 50px; }
  .hero .copy { max-width: none; }
  .hero .hero-title { font-size: clamp(34px, 7vw, 52px); }
  .hero .lede { font-size: 17px; }
  .hero .posline { white-space: normal; }
  .reality__inner {
    grid-template-columns: 1fr;
    gap: 24px;
    padding-block: 46px;
  }
  .reality__inner {
    min-height: 480px;
    padding-block: 70px;
  }
  .reality h2 br { display: none; }
  .reality h2 { font-size: clamp(30px, 6vw, 42px); }
  .reality p {
    max-width: 56ch;
    padding-left: 20px;
    font-size: 16px;
  }
  .intelligence-band { height: 570px; }
  .intelligence-band__image img { object-position: 50% 50%; }   /* the phone figure's slice matches this */
  .intelligence-band__wash {
    background:
      linear-gradient(to bottom,
        color-mix(in srgb, var(--ground-3) 78%, transparent), transparent 27%),
      linear-gradient(to top,
        color-mix(in srgb, var(--ground-3) 66%, transparent), transparent 42%);
  }
  .intelligence-band__network { display: none; }
  .intelligence-band__network-mobile {
    display: block;
    position: absolute;
    z-index: 2;
    inset: 0;
    width: 100%;
    height: 100%;
    /* one unit is about 0.43px on a 400px phone */
    --rk: 2.35;
  }
  .intelligence-band__network-mobile .intelligence-band__threads path { stroke-width: 3.8; }
  .intelligence-band__network-mobile .intelligence-band__flow path { stroke-width: 7.2; }
}

@media (max-width: 480px) {
  .intro { min-height: 435px; }
  .hero .hero-title { font-size: clamp(31px, 8vw, 38px); }
  .hero .line { overflow: visible; }
  .hero .line span { transform: none; animation: none; }
  .intelligence-band { height: 540px; }

}

@media (prefers-reduced-motion: reduce) {
  .intro-mark img { animation: none; }
  .intelligence-band__network, .intelligence-band__network-mobile { animation: none; }
  /* The pulse is removed outright rather than paused: a stopped dash leaves
     nine bright stubs scattered along the routes, which is a worse figure
     than the one without it. !important because the delay and destination
     rules above are more specific selectors. */
  .intelligence-band__flow { display: none; }
  .intelligence-band__flow path,
  .intelligence-band__points circle,
  .intelligence-band__destination { animation: none !important; }
  .intelligence-band__ripple { display: none; }
  /* The cursor gravity checks this query before it starts, so this is a
     second lock rather than the only one - it also beats the inline style
     the script writes, which a plain declaration would not. */
  .intelligence-band__cluster circle { translate: none !important; }
}

/* The opening now lives entirely in the pinned video hero. */
.heroscroll .hero { min-height: 100svh; }
html.hero-enhanced .heroscroll { height: 270svh; }
html.hero-enhanced .heroscroll .hero {
  position: sticky;
  top: 0;
  height: 100svh;
  min-height: 0;
}
.hero .inner { width: 100%; align-self: center; padding-block: 42px; }
/* 700, not 620. The copy column is the room the frosted plate is allowed to
   take, and the plate is fit-content: the moment a headline wraps for want of
   room, fit-content resolves to the whole column and the plate stands 100px
   wider than the longest line it holds. "A stronger way forward." measures
   635px at the top of the type ramp, and the plate's own padding comes out
   of the column, so the column has to clear 635 + 66 with a little slack for
   the box to sit tight against the words. */
.hero .copy { max-width: min(720px, 100%); }
.hero-messages { display: grid; transition: transform .25s ease-out; }
.hero-a, .hero-b { grid-area: 1 / 1; transition: opacity .55s ease, transform .55s ease, visibility .55s; }
.hero-b { opacity: 0; visibility: hidden; transform: translateY(24px); }
.hero-later .hero-a { opacity: 0; visibility: hidden; transform: translateY(-24px); }
.hero-later .hero-b { opacity: 1; visibility: visible; transform: translateY(0); }
.hero-b h2 {
  color: var(--on-ground);
  margin: 16px 0 0;
  font: 300 clamp(38px, 4.5vw, 64px)/1.07 var(--font-display);
  letter-spacing: -.025em;
}
.hero-b .eyebrow { opacity: 1; animation: none; }
.hero-b .lede { margin-bottom: 26px; }

/* The film is clear on a desktop too. The horizontal 94%-82% wash turned the
   left third of the frame into a grey ground rather than a photograph; the
   copy now sits on the same tight frosted plate the phone uses, so the words
   stay crisp over whatever frame is showing and the picture reads whole
   behind them. Legibility is the plate's job now, not the wash's - all that
   is left of the scrim is the short fade at the foot that carries the CTA. */
.hero .scrim {
  background: linear-gradient(to top,
    color-mix(in srgb, var(--ground-3) 60%, transparent), transparent 38%);
}
/* The plate is on each message, not on the wrapper: the two share one grid
   cell and crossfade, and a wrapper plate sized to the taller of them leaves
   the shorter message sitting in empty frost. */
.hero-messages { width: fit-content; max-width: 100%; margin-left: -12px; }
.hero-a, .hero-b {
  width: fit-content;
  max-width: 100%;
  align-self: start;
  padding: clamp(26px, 2.1vw, 32px) clamp(28px, 2.3vw, 36px) clamp(26px, 2.2vw, 34px);
  border-radius: 2px;
  background: color-mix(in srgb, var(--ground-3) 56%, transparent);
  -webkit-backdrop-filter: blur(16px) saturate(1.1);
  backdrop-filter: blur(16px) saturate(1.1);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--on-ground) 10%, transparent);
}

@media (min-width: 821px) and (max-height: 700px) {
  .hero .inner { padding-block: 18px; }
  .hero .hero-title, .hero-b h2 { font-size: clamp(34px, 3.4vw, 50px); }
  .hero .lede { font-size: 16px; margin-block: 14px 10px; }
  .hero .posline { margin-bottom: 16px; }
  .hero .cta { margin-top: 14px; }
}

@media (max-width: 820px) {
  /* THE PHONE HERO IS TWO HALVES, NOT A PLATE OVER A FILM.
     The film used to fill the whole viewport with the copy floating on a
     frosted plate over it, and on a phone that failed twice: the film was
     covering a 390x844 box from a 640x360 encode, a 2.3x upscale that read
     as mush, and the copy sat on top of the softest part of it. Splitting
     the screen fixes both at once. The film gets the top half, where a
     1152x648 encode covers 390x~46svh at about 1.3x, and the words get the
     bottom half on the flat ground where nothing is blurred behind them.
     The scrub and the message change are unchanged - both are driven by
     scroll position, and neither cares how the box is divided. */
  .heroscroll .hero { display: grid; align-items: center; }
  html.hero-enhanced .heroscroll { height: 250svh; }
  html.hero-enhanced .heroscroll .hero {
    display: grid;
    /* 3:2, NOT A FRACTION OF THE SCREEN, and the difference is the whole
       complaint. The source film is 1280x720 and that is the ceiling: no
       encoder setting invents detail above it. At 46svh the band was
       390x388 css, which on a 3x phone is 1170x1164 device pixels - so
       object-fit: cover scaled a 720-tall film by 1.80x and threw away 43%
       of its width. That is what "blurry" was. A 3:2 band is 390x260 css,
       1170x780 device, and the same film covers it at 1.08x with almost
       nothing cropped. Measured, not judged by eye. Taller than 3:2 and the
       upscale climbs again; 2:1 would be sharper still and too letterboxed
       to carry the opening. */
    grid-template-rows: calc(100vw / 1.5) 1fr;
    align-items: stretch;
    min-height: 0;
    height: 100svh;
    background: var(--ground-3);
  }
  /* In the flow now, not absolute: it is a row of the grid, so it defines
     the top half rather than covering the whole hero. */
  .hero .heromedia { position: relative; grid-row: 1; inset: auto; height: 100%; min-height: 0; overflow: hidden; }
  /* 42%, not centre: cover crops 16% of the width and the film's own lower
     third is the concrete floor, so a centred crop spends a quarter of the
     band on grey. Lifting the window puts the mountains and the light in
     the middle of the band, which is what the frame is for. */
  .hero .heromedia img, .hero .heromedia video { height: 100%; object-fit: cover; object-position: 50% 42%; }
  /* The scrim is the seam between the two halves rather than a wash over
     the frame: the film fades into the ground the copy sits on, so the
     join reads as one surface instead of a photograph stuck above a box. */
  .hero .scrim {
    inset: 0 0 auto 0;
    height: calc(100vw / 1.5);
    min-height: 0;
    z-index: 1;
    /* A shorter seam than the first attempt. At 62% the fade was washing
       out the middle of a band that is now only 260px tall; the join only
       needs to sell the last few centimetres. */
    background: linear-gradient(to top,
      var(--ground-3) 0%,
      color-mix(in srgb, var(--ground-3) 78%, transparent) 7%,
      color-mix(in srgb, var(--ground-3) 18%, transparent) 20%,
      transparent 38%);
  }
  /* NO PLATE DOWN HERE. The frost existed to keep words legible over a
     moving picture; on the flat ground it would be frosting over nothing. */
  .hero .inner { grid-row: 2; align-self: center; padding-block: 26px; }
  .hero-messages { width: auto; max-width: 100%; margin-left: 0; }
  .hero-a, .hero-b {
    width: auto;
    max-width: 100%;
    align-self: start;
    padding: 0;
    background: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: none;
  }
  /* The change between the two messages carries further now that it is not
     happening over a film: a longer travel, and the outgoing message leaves
     upward as the incoming one arrives from below. */
  .hero-a, .hero-b { transition: opacity .5s ease, transform .6s cubic-bezier(.16, 1, .3, 1), visibility .6s; }
  .hero-b { transform: translateY(34px); }
  .hero-later .hero-a { transform: translateY(-34px); }
  .hero-b h2 { font-size: clamp(34px, 7vw, 52px); }
}
@media (max-width: 480px) {
  .hero .inner { padding-block: 22px; }
  .hero .hero-title { font-size: clamp(31px, 8vw, 38px); }
  .hero-b h2 { font-size: clamp(31px, 8vw, 38px); }
  .hero .lede { font-size: 16px; }
}
@media (prefers-reduced-motion: reduce) {
  html.hero-enhanced .heroscroll { height: auto; }
  html.hero-enhanced .heroscroll .hero { position: relative; min-height: 100svh; height: auto; }
  .hero-a, .hero-b, .hero-messages { transition: none; }
}
/* No scroll-driven swap - reduced motion, or no JavaScript at all - means
   nothing will ever reveal the second message. Stack it under the first
   instead, so every visitor can read both and reach its button. */
html:not(.hero-enhanced) .hero-b {
  grid-area: 2 / 1;
  margin-top: 18px;
  opacity: 1;
  visibility: visible;
  transform: none;
}

/* How we work: one continuous panorama carries the copy and four stages. */
.approach-section {
  display: grid;
  /* The quote/compass row and the applications marquee are both gone, so
     the photographic journey takes the whole section. */
  grid-template-rows: minmax(0, 1fr);
  /* This section opens the page and the header is fixed over it, taking
     no layout: a full viewport, with the copy and the panel numbers pushed
     down by the header's measured height so nothing starts under the bar. */
  height: 100svh;
  min-height: 570px;
  scroll-margin-top: 0;
  background: var(--ground-3);
}
.approach-gallery {
  position: relative;
  display: flex;
  width: 100%;
  height: 100%;
  min-height: 0;
  overflow: hidden;
}
.approach-gallery__image {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.approach-gallery__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}
.approach-gallery::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    linear-gradient(90deg,
      color-mix(in srgb, var(--ground-3) 58%, transparent) 0%,
      color-mix(in srgb, var(--ground-3) 36%, transparent) 27%,
      transparent 62%),
    linear-gradient(to top,
      color-mix(in srgb, var(--ground-3) 68%, transparent) 0%,
      color-mix(in srgb, var(--ground-3) 18%, transparent) 42%,
      transparent 72%);
  pointer-events: none;
}
.approach-intro {
  position: relative;
  z-index: 2;
  flex: 0 0 clamp(300px, 28vw, 410px);
  overflow: hidden;
  color: var(--on-ground);
}
.approach-panel button::before {
  content: none;
}
.approach-intro::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  /* STRENGTHENED AND CARRIED FURTHER RIGHT, 21 September, for the headline
     rather than for the look. The opening line became "Increasing
     performance through transition." and `performance` is the longest word
     the heading has ever carried: at 43px it runs 226px, far enough that its
     last letters land on the sunlit window mullion, which is almost white.
     contrast_check.py measured the pixels under the glyphs at 1.56:1 against
     the 3:1 large text needs - the old wording read 5.65:1 because it stopped
     short of the frame.

     Three strengths were measured rather than guessed: 30% reached 2.21:1 and
     45% reached 2.84:1, both still failing, and this one reads 4.18:1. The
     stops past 55% are what actually do the work; the left end is over dark
     stone where the extra wash barely registers. Weaken any of this and
     re-run contrast_check.py - at 1440 specifically, since the phone has
     always been fine (12.82:1) and would not notice the regression. */
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--ground-3) 62%, transparent) 0%,
    color-mix(in srgb, var(--ground-3) 50%, transparent) 55%,
    color-mix(in srgb, var(--ground-3) 26%, transparent) 78%,
    transparent 94%);
}
.approach-intro__copy {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  height: 100%;
  padding: clamp(26px, 2.8vw, 40px) clamp(24px, 2.6vw, 46px);
  padding-top: calc(var(--hdr-h, 78px) + 14px);
}
.approach-intro .eyebrow { color: var(--brand-light); }
.approach-intro h1 {
  margin: 0;
  color: var(--on-ground);
  font: 400 clamp(33px, 3.1vw, 43px)/.99 Georgia, serif;
  letter-spacing: -.035em;
}
.approach-intro p {
  max-width: 28ch;
  margin: 16px 0 20px;
  color: var(--on-ground);
  font-size: clamp(12px, 1vw, 14px);
  line-height: 1.5;
}
.approach-intro .btn { border-radius: 0; padding: 11px 16px; font-size: 10px; }
.approach-intro .btn:hover .approach-arrow { transform: translateX(3px); }
.approach-panels-wrap { position: relative; z-index: 2; flex: 1 1 0; min-width: 0; }
.approach-panels {
  display: flex;
  width: 100%;
  height: 100%;
  min-width: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}
.approach-route {
  position: absolute;
  z-index: 3;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  filter: drop-shadow(0 1px 2px color-mix(in srgb, var(--ground-3) 65%, transparent));
}
.approach-route path {
  fill: none;
  stroke: color-mix(in srgb, var(--on-ground) 72%, transparent);
  stroke-width: 1.3;
  vector-effect: non-scaling-stroke;
}
.approach-route circle {
  fill: var(--on-ground);
  stroke: var(--ground-3);
  stroke-width: 1.2;
  vector-effect: non-scaling-stroke;
}
.approach-route circle.is-active { fill: var(--brand-light); stroke: var(--on-ground); }
.approach-panel {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  border-left: 1px solid color-mix(in srgb, var(--on-ground) 22%, transparent);
  transition: flex-grow .7s cubic-bezier(.2,.7,.2,1);
}
.approach-panel button {
  display: block;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--on-ground);
  text-align: left;
  font: inherit;
  cursor: pointer;
}
.approach-panel button::after {
  content: none;
}
.approach-panel__number,
.approach-panel__copy { position: absolute; z-index: 2; left: clamp(16px, 1.7vw, 30px); right: 16px; }
.approach-panel__number {
  top: calc(var(--hdr-h, 78px) + 14px);
  color: var(--on-ground);
  font: 500 11px/1 var(--font-display);
  letter-spacing: .22em;
}
.approach-panel__copy { top: 59%; bottom: auto; text-align: center; }
.approach-panel__copy strong {
  display: block;
  margin-bottom: 6px;
  font: 500 clamp(10px, .85vw, 12px)/1.2 var(--font-display);
  letter-spacing: .19em;
  text-transform: uppercase;
}
.approach-panel__copy > span {
  display: block;
  max-width: 19ch;
  margin-inline: auto;
  opacity: 1;
  color: var(--on-ground);
  font-size: clamp(11px, .95vw, 13px);
  font-weight: 400;
  line-height: 1.36;
}
.approach-panel:is(.is-active, :focus-within) { flex-grow: 3.25; }
@media (hover: hover) {
  .approach-panel:hover { flex-grow: 3.25; }
}
.approach-panel button:focus-visible { outline: 3px solid var(--brand-light); outline-offset: -5px; }

body.approach-in-view .totop { opacity: 0; visibility: hidden; pointer-events: none; }
@media (min-width: 821px) and (max-width: 900px) {
  .approach-intro h1 { font-size: 32px; }
  .approach-panel__copy { left: 12px; right: 8px; }
  .approach-panel__copy strong { font-size: 9px; letter-spacing: .04em; }
  .approach-panel__copy > span { font-size: 10px; }
}

@media (max-width: 820px) {
  .approach-section { display: block; height: auto; min-height: 0; }
  .approach-gallery { display: block; height: auto; overflow: visible; }
  .approach-intro { height: min(70svh, 520px); min-height: 380px; }
  .approach-intro__copy { padding: 32px var(--gut); padding-top: calc(var(--hdr-h, 78px) + 20px); }
  .approach-intro h1 { font-size: clamp(35px, 8vw, 50px); }
  .approach-intro p { margin-top: auto; }
  .approach-panels { display: block; }
  .approach-route { display: none; opacity: .72; }
  .approach-panels-wrap.route-ready .approach-route { display: block; }
  /* 179 AND 416 ARE 138 AND 320 PLUS 30%, and the reason is time rather than
     size. On a phone the open panel is chosen by approach-panels.js: whichever
     panel's centre is nearest the reading line at 55% of the viewport. So how
     long a panel stays open is not a duration anywhere - it is how far the
     page scrolls while that panel is still the nearest one, which is its own
     height. Measured at 390px before this change, the three middle panels
     held for 130, 140 and 140px of scroll: about a seventh of a second at a
     normal flick, which is why they read as a flicker rather than as five
     places. Scaling both heights by the same 1.3 scales every dwell by 1.3
     and leaves the proportions, the route and the crossfade untouched. */
  .approach-panel { height: 179px; border-left: 0; border-top: 1px solid color-mix(in srgb, var(--on-ground) 22%, transparent); transition: height .55s ease; }
  .approach-panel:is(.is-active, :focus-within) { height: 416px; }
  .approach-panels.scroll-driven .approach-panel { height: 179px; }
  .approach-panels.scroll-driven .approach-panel.is-active { height: 416px; }
  .approach-panel__number { top: 22px; left: var(--gut); }
  .approach-panel__copy { left: var(--gut); top: auto; bottom: 22px; text-align: left; }
  .approach-panel__copy strong { font-size: 18px; }
  .approach-panel__copy > span { margin: 0; font-size: 15px; }
}
@media (prefers-reduced-motion: reduce) {
  .approach-panel, .approach-panel__copy > span, .approach-route { transition: none; }
}

/* --- the opening curtain --------------------------------------------------
   One timeline, --pt-total long, declared entirely here so it completes with
   or without a script (load-intro.js measures the header and replays it for
   navigation).
   Every animation fills both ways and the
   overlay's last keyframe is visibility:hidden, so nothing is ever left
   covering the page.

   Beats, as fractions of 2300ms:
     0 - 20%    approved lockup arrives at the centre
     20 - 40%   hold
     40 - 70%   lockup travels to the header position
     67 - 100%  the curtain wipes upward as the lockup fades out
     100%       overlay hidden, header lockup shown in the same spot */
:root { --pt-total: 2300ms; }
html.pt-locked { overflow: hidden; }
html.pt-locked .pt { pointer-events: auto; }
html.pt-handoff .hdr .brand-logo-stack { visibility: hidden; }
.pt {
  position: fixed;
  inset: 0;
  z-index: 200;
  pointer-events: none;
  animation: pt-done var(--pt-total) step-end both;
}
html.pt-skip .pt { display: none; }
.pt-band {
  position: absolute;
  inset: 0;
  background: var(--brand-accent-strong, #007A83);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
  animation: pt-reveal var(--pt-total) cubic-bezier(.65, 0, .35, 1) both;
}
.pt-content {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: grid;
  align-content: center;
  justify-items: center;
}
.pt-mark {
  display: block;
  width: clamp(420px, 64vw, 960px);
  max-width: calc(100vw - 24px);
  transform-origin: center;
  will-change: transform, opacity;
  animation: pt-mark-fade var(--pt-total) cubic-bezier(.65, 0, .35, 1) both;
}
html.pt-js .pt-mark { animation-name: pt-mark-travel; }
.pt-mark img { display: block; width: 100%; height: auto; }
@media (max-width: 820px) {
  .pt-mark { width: calc(100vw - 24px); }
}
@keyframes pt-done {
  0%, 99.9% { visibility: visible; }
  100% { visibility: hidden; }
}
@keyframes pt-reveal {
  0%, 67% { clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%); }
  100% { clip-path: polygon(0 0, 100% 0, 100% 0, 0 0); }
}
/* With a script: arrive, hold, then go to the measured header position. */
@keyframes pt-mark-travel {
  0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.94); }
  20%, 40% { opacity: 1; transform: translate3d(0, 0, 0) scale(1); }
  67% { opacity: 1; }
  70% { transform: translate3d(var(--pt-x, 0px), var(--pt-y, 0px), 0) scale(var(--pt-s, 1)); }
  100% { opacity: 0; transform: translate3d(var(--pt-x, 0px), var(--pt-y, 0px), 0) scale(var(--pt-s, 1)); }
}
/* Without a measured target, the mark stays centred and fades with the wipe. */
@keyframes pt-mark-fade {
  0% { opacity: 0; transform: scale(.94); }
  20%, 45% { opacity: 1; transform: scale(1); }
  67% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .pt { display: none; }
  html.pt-handoff .hdr .brand-logo-stack { visibility: visible; }
}

/* --- the boardroom and the evidence, as one ------------------------------
   The photograph is sticky for the height of the section, and the steps -
   the title, then each engagement - scroll up over it on frosted plates.
   Each step is a full viewport tall with its plate centred, so a plate
   holds the middle of the screen for a moment, then gives way to the next. */
.evidence-scroll { position: relative; padding: 0; background: var(--ground-3); color: var(--ink); }
.evidence-scroll__bg { position: sticky; top: 0; height: 100svh; overflow: hidden; z-index: 1; margin-top: calc(-1 * var(--hdr-h, 78px)); }
.evidence-scroll__lid { position: sticky; top: 0; z-index: 3; height: var(--hdr-h, 78px); overflow: hidden; pointer-events: none; }
.evidence-scroll__lid .grain, .evidence-scroll__lid .evidence-scroll__scrim { height: 100svh; inset: auto; top: 0; left: 0; right: 0; }
.evidence-scroll__bg .grain, .evidence-scroll__lid .grain { position: absolute; inset: 0; display: block; --grain: .055; }
.evidence-scroll__bg .grain img, .evidence-scroll__lid .grain img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 45%; }
.evidence-scroll__scrim {
  position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(to bottom, color-mix(in srgb, var(--ground-3) 55%, transparent), transparent 30%),
    linear-gradient(to top, color-mix(in srgb, var(--ground-3) 40%, transparent), transparent 45%);
}
.evidence-scroll__steps { position: relative; z-index: 2; margin-top: -100svh; padding-bottom: 12svh; }
.evidence-scroll .cases { margin: 0; padding: 0; list-style: none; border: 0; }
.evidence-step { min-height: 86svh; display: grid; align-content: center; justify-items: start; padding-block: 8svh; }
.evidence-step > * {
  width: min(100%, 760px);
  padding: clamp(28px, 3.2vw, 44px) clamp(28px, 3.4vw, 48px);
  border-radius: 4px;
  background: color-mix(in srgb, var(--paper) 90%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(1.15);
  backdrop-filter: blur(18px) saturate(1.15);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--paper) 40%, transparent),
              0 30px 60px -30px color-mix(in srgb, var(--ground-3) 70%, transparent);
}
.evidence-scroll .evidence-head { max-width: 640px; margin: 0; }
.evidence-scroll .evidence-head .lede { max-width: 48ch; }
.evidence-scroll .case { margin: 0; border: 0; }
/* The step nearest the centre is the one being read; the others step back.
   Only under the script (evidence-steps.js adds .stepping), and only with
   motion consent. Without it every plate is simply visible. */
@media (prefers-reduced-motion: no-preference) {
  .evidence-scroll.stepping .evidence-step > * {
    opacity: .28; transform: translateY(22px) scale(.985);
    transition: opacity .55s ease, transform .7s cubic-bezier(.16, 1, .3, 1);
  }
  .evidence-scroll.stepping .evidence-step.is-current > * { opacity: 1; transform: none; }
}
@media (max-width: 700px) {
  .evidence-scroll__bg .grain img, .evidence-scroll__lid .grain img { object-position: 50% 42%; }
  .evidence-step > * { padding: 24px 20px; }
}

/* --- the contact form -----------------------------------------------------
   UI-CTF-001 on the dark close: a paper card in the second column, the
   fields the house sans, the radii from the tokens (which are near-square
   here, as the whole site is). Errors twice over - a focusable summary and
   an inline line under the field. The honeypot is off-screen, not
   display:none, which some bots skip. Classes are cf-prefixed because the
   chassis already has .field (floating labels) and the page has .intro. */
.closegrid { align-items: start; }
@media (min-width: 900px) { .closegrid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); gap: clamp(40px, 5vw, 80px); } }
.formcard { background: var(--paper); color: var(--ink); border-radius: var(--r, 2px); padding: clamp(24px, 3vw, 40px); }
.formcard > h3 { margin: 0 0 8px; font-size: clamp(20px, 2vw, 25px); font-weight: 500; letter-spacing: -.015em; }
.formcard > .cf-intro { margin: 0 0 24px; color: var(--ink-55); font-size: 15px; max-width: 50ch; }
.formcard .summary { display: none; margin: 0 0 22px; padding: 14px 18px; border: 2px solid var(--danger, #b3261e); background: color-mix(in srgb, var(--danger, #b3261e) 7%, transparent); }
.formcard .summary.on { display: block; }
.formcard .summary:focus-visible { outline: 3px solid var(--danger, #b3261e); outline-offset: 3px; }
.formcard .summary h4 { margin: 0 0 6px; font-size: 15px; font-weight: 600; color: var(--danger, #b3261e); }
.formcard .summary ul { margin: 0; padding-left: 20px; }
.formcard .summary li { margin-bottom: 4px; font-size: 14px; }
.formcard .summary a { color: var(--danger, #b3261e); }
.formcard .grid { display: grid; gap: 0 16px; grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); }
.formcard .grid > * { min-width: 0; }
.formcard .cf-field { margin: 0 0 16px; min-width: 0; }
.formcard .cf-field label { display: block; font-size: 13.5px; font-weight: 600; margin-bottom: 6px; color: var(--ink); }
.formcard .cf-field .hint { font-weight: 400; color: var(--ink-45); }
.formcard .req { color: var(--danger, #b3261e); margin-left: 2px; }
.formcard .cf-field input, .formcard .cf-field textarea {
  width: 100%; padding: 11px 13px; font: 15.5px/1.5 var(--font-body, inherit); color: var(--ink);
  background: var(--surface); border: 1px solid var(--line-2); border-radius: var(--r-sm, 1px); outline: none;
  transition: border-color .2s ease, box-shadow .2s ease;
}
.formcard .cf-field input { height: 46px; }
.formcard .cf-field textarea { min-height: 130px; resize: vertical; }
.formcard .cf-field input:focus, .formcard .cf-field textarea:focus { border-color: var(--brand); box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 18%, transparent); }
.formcard .cf-field.bad input, .formcard .cf-field.bad textarea { border-color: var(--danger, #b3261e); }
.formcard .cf-err { display: none; margin: 6px 0 0; font-size: 13px; font-weight: 500; color: var(--danger, #b3261e); }
.formcard .cf-field.bad .cf-err { display: block; }
.formcard .trap { position: absolute !important; left: -9999px !important; width: 1px !important; height: 1px !important; overflow: hidden !important; }
.formcard .turnstile:empty { display: none; }
.formcard .turnstile { margin: 0 0 14px; }
.formcard .consent { display: flex; gap: 11px; align-items: flex-start; margin: 4px 0 20px; font-size: 13.5px; line-height: 1.55; color: var(--ink-55); cursor: pointer; }
.formcard .consent input { flex: 0 0 auto; width: 18px; height: 18px; margin: 2px 0 0; accent-color: var(--brand); cursor: pointer; }
.formcard .consent a { color: var(--brand); }
.formcard .consent.bad { color: var(--danger, #b3261e); }
.formcard .consent.bad input { outline: 2px solid var(--danger, #b3261e); outline-offset: 2px; }
.formcard button.send {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px; min-height: 48px; padding: 0 28px;
  border: 0; border-radius: var(--r-sm, 1px); cursor: pointer; background: var(--brand); color: var(--on-brand, #fff);
  font: 600 14px/1 var(--font-display, inherit); letter-spacing: .06em; text-transform: uppercase;
  transition: filter .25s ease, transform .25s ease;
}
.formcard button.send:hover:not(:disabled) { filter: brightness(1.08); transform: translateY(-1px); }
.formcard button.send:disabled { opacity: .6; cursor: progress; transform: none; }
.formcard button.send:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
.formcard button.send .spin { width: 14px; height: 14px; border: 2px solid currentColor; border-radius: 50%; border-top-color: transparent; display: none; animation: cf-spin .7s linear infinite; }
.formcard button.send.busy .spin { display: block; }
@keyframes cf-spin { to { transform: rotate(360deg); } }
.formcard .foot { margin: 14px 0 0; font-size: 12.5px; color: var(--ink-45); max-width: 56ch; }
/* end states: shown by the script, or by :target after a no-script post */
.formcard .done, .formcard .failed { display: none; text-align: center; padding: 14px 0 6px; }
.formcard .done.on, .formcard .failed.on, .formcard .done:target, .formcard .failed:target { display: block; animation: cf-rise .5s ease both; }
.formcard:has(.done:target) form, .formcard:has(.failed:target) form, .formcard form.gone { display: none; }
@keyframes cf-rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.formcard .done svg, .formcard .failed svg { width: 48px; height: 48px; margin-bottom: 14px; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.formcard .done svg { stroke: var(--success, #1e8e5a); }
.formcard .failed svg { stroke: var(--danger, #b3261e); }
.formcard .done h4, .formcard .failed h4 { margin: 0 0 8px; font-size: 20px; font-weight: 500; letter-spacing: -.01em; }
.formcard .done p, .formcard .failed p { margin: 0 auto 12px; color: var(--ink-55); font-size: 15px; max-width: 44ch; }
.formcard .failed a { color: var(--brand); }
.formcard .failed button.send { margin-top: 6px; }
@media (prefers-reduced-motion: reduce) {
  .formcard .done.on, .formcard .failed.on, .formcard .done:target, .formcard .failed:target { animation: none; }
  .formcard button.send { transition: none; }
  .formcard button.send .spin { animation-duration: 1.6s; }
}
