/* ==========================================================================
   Kalinotech — the journey
   ONE pinned stage. The five shots — yard, road, customs, sea, sky — are
   layers of it, each loading over the last as the page scrolls, so the reader
   never leaves the frame. Every shot owns a window of the section's --p and
   runs on its own local progress --lp (0 → 1 inside that window).

   window map (section --p)       yard .00–.19   road .18–.35   customs .34–.51
                                  sea  .50–.72   sky  .66–1.00
   ========================================================================== */

.journey {
  position: relative;
  height: 600vh;
  background: var(--night);
  z-index: 2;
}
.journey__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  min-height: 620px;
  overflow: hidden;
  isolation: isolate;
  /* A phone's address bar sits over the bottom of a 100vh box — vh is the
     LARGE viewport there, the one you only get once the bar has slid away.
     Anything the shot puts near its bottom edge therefore ends up under the
     bar, which is why the headlines and the note cards were reading as "too
     far down and outside the frame". --safe-b is exactly the height of that
     bar, and the shots keep clear of it. */
  --safe-b: 0px;
  /* the night of the customs shot is the opaque base every layer sits on */
  background:
    radial-gradient(70% 50% at 50% 40%, rgba(22, 119, 255, .12), transparent 70%),
    linear-gradient(to bottom, #0B0F16, #121924 55%, #0B0F16);
}

/* ------------------------------------ backgrounds — they only fade IN over
   something solid (sea over night, sky over sea), and daylight only fades
   OUT, so no moment of the hand-over is ever see-through. */
.jbg { position: absolute; inset: 0; pointer-events: none; }
.jbg--sea {
  background: radial-gradient(60% 46% at 50% 46%, #14539E 0%, #0A3576 44%, #041634 84%);
  opacity: clamp(0, calc((var(--p, 0) - .495) / .025), 1);
}
.jbg--sky {
  background: linear-gradient(to bottom, #CFE3F8 0%, #8DBDEE 38%, #3877D0 78%, #164A9A 100%);
  opacity: clamp(0, calc((var(--p, 0) - .74) / .06), 1);
}
.jbg--day {
  background: var(--paper);
  opacity: calc(1 - clamp(0, calc((var(--p, 0) - .322) / .026), 1));
}
/* the bottom edge darkens at the end, so the night of the next section
   meets night instead of a hard sky-blue line */
.jbg--sky::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, transparent 46%, var(--night) 100%);
  opacity: clamp(0, calc((var(--p, 0) - .93) / .07), 1);
}

@supports (height: 100svh) {
  .journey__stage { --safe-b: calc(100vh - 100svh); }
}

/* ---------------------------------------------------------- the shots  */
/* The backgrounds fill the whole stage; the shots stop short of the browser
   chrome, so everything they lay out stays inside what the reader can see. */
.jshot {
  --lp: clamp(0, calc((var(--p, 0) - var(--a)) / var(--s)), 1);
  position: absolute;
  inset: 0 0 var(--safe-b, 0px) 0;
  pointer-events: none;
}
/* Outside its window a shot is switched off completely: the browser then
   skips its style, layout and paint, so changing --p only costs the one or
   two shots actually on screen. motion.js adds the class. */
.jshot.is-off {
  content-visibility: hidden;
  contain: strict;
  /* with contain:strict the box has no size of its own; telling the browser
     up front how big it will be saves it guessing and re-laying out the page
     each time a shot is switched on or off */
  contain-intrinsic-size: 100vw 100vh;
  opacity: 0;
}
/* motion.js marks the shot in front; only its links take clicks */
.jshot.is-live a, .jshot.is-live button { pointer-events: auto; }

/* Static artwork does not read the scroll value, so stop it inheriting:
   a change to --p then never invalidates these (large) subtrees. */
.ship-top > *, .truck-top > *, .truck-side > *, .plane > *, .ybox-art > *,
.swell > *, .wake > * {
  --p: initial;
  --lp: initial;
}

.jshot--yard {
  --a: 0; --s: .19;
  z-index: 1;
  opacity: calc(1 - clamp(0, calc((var(--p, 0) - .178) / .018), 1));
}
/* the road shot has fully gone before the customs road appears, so the two
   roads never overlap into a crossroads */
.jshot--road {
  --a: .18; --s: .17;
  z-index: 2;
  opacity: calc(clamp(0, calc((var(--p, 0) - .18) / .016), 1)
                * (1 - clamp(0, calc((var(--p, 0) - .33) / .015), 1)));
}
.jshot--customs {
  --a: .34; --s: .17;
  z-index: 3;
  color: #fff;
  opacity: calc(clamp(0, calc((var(--p, 0) - .345) / .017), 1)
                * (1 - clamp(0, calc((var(--p, 0) - .495) / .02), 1)));
}
.jshot--sea {
  --a: .50; --s: .22;
  z-index: 4;
  color: #fff;
  opacity: calc(clamp(0, calc((var(--p, 0) - .495) / .02), 1)
                * (1 - clamp(0, calc((var(--p, 0) - .78) / .02), 1)));
}
.jshot--sky { --a: .66; --s: .34; z-index: 5; }

/* shared frame for the two "daylight" shots: head / art / foot */
.jshot__in {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  align-content: center;
  gap: clamp(10px, 2.2vh, 26px);
  padding-block: clamp(84px, 12vh, 120px) clamp(18px, 3.6vh, 36px);
}

/* ======================================================= SHOT 1 — yard */
.logistics__head {
  width: min(100% - (var(--gutter) * 2), var(--shell));
  margin-inline: auto;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 24px;
  align-items: end;
  opacity: calc(1 - clamp(0, calc((var(--lp) - .9) / .1), 1));
}
.logistics__head .lead { max-width: 44ch; }

.yard {
  position: relative;
  width: min(100% - (var(--gutter) * 2), var(--shell), 150vh);
  margin-inline: auto;
  align-self: center;

  /* the six legs of the stacker's cycle, shared by boom, load and truck */
  --gA: clamp(0, calc((var(--lp) - .14) / .18), 1);   /* lower onto the stack */
  --gB: clamp(0, calc((var(--lp) - .36) / .09), 1);   /* lift away empty      */
  --gC: clamp(0, calc((var(--lp) - .54) / .05), 1);   /* lower to re-grab     */
  --gD: clamp(0, calc((var(--lp) - .615) / .055), 1); /* lift with the load   */
  --gE: clamp(0, calc((var(--lp) - .76) / .05), 1);   /* lower onto the bed   */
  --gF: clamp(0, calc((var(--lp) - .845) / .06), 1);  /* lift clear           */
  --boom: calc(12deg - var(--gA) * 12deg + var(--gB) * 12deg - var(--gC) * 12deg
               + var(--gD) * 12deg - var(--gE) * 20.5deg + var(--gF) * 16deg);
}
.yard-art { display: block; width: 100%; height: auto; overflow: visible; }

/* the stack assembles, one container at a time */
.ybox {
  --t: clamp(0, calc((var(--lp) - (.02 + var(--i, 0) * .026)) / .14), 1);
  --e: calc(1 - (1 - var(--t)) * (1 - var(--t)));
  transform-box: fill-box;
  transform-origin: 50% 50%;
  opacity: var(--t);
  transform:
    translate(calc((1 - var(--e)) * var(--fx, 0px)), calc((1 - var(--e)) * var(--fy, -140px)))
    rotate(calc((1 - var(--e)) * var(--fr, -6deg)));
}
/* the third-row container exists only between "set down" and "picked up" */
.ybox-placed {
  opacity: calc(clamp(0, calc((var(--lp) - .33) / .03), 1)
                * (1 - clamp(0, calc((var(--lp) - .59) / .025), 1)));
}
/* the reach stacker drives in, then travels down the row to the truck */
.ycrane {
  --cIn: clamp(0, calc(var(--lp) / .10), 1);
  --cGo: clamp(0, calc((var(--lp) - .67) / .09), 1);
  --eGo: calc(1 - (1 - var(--cGo)) * (1 - var(--cGo)));
  transform: translateX(calc((1 - var(--cIn)) * 360px - var(--eGo) * 232px));
  opacity: var(--cIn);
}
/* origins in user units, so the pivot is exact rather than bbox-relative */
.ycrane__boom {
  transform-box: view-box;
  transform-origin: 900px 296px;
  transform: rotate(var(--boom));
}
/* the spreader keeps its load level however the boom swings */
.ycarry {
  transform-box: view-box;
  transform-origin: 504px 221px;
  transform: rotate(calc(-1 * var(--boom)));
  opacity: calc(
      clamp(0, calc((var(--lp) - .06) / .04), 1) * (1 - clamp(0, calc((var(--lp) - .33) / .03), 1))
    + clamp(0, calc((var(--lp) - .59) / .025), 1) * (1 - clamp(0, calc((var(--lp) - .81) / .025), 1)));
}
/* the truck arrives empty, is loaded, and leaves — out of the left edge,
   which is where the road shot picks it up from the right */
.ytruck {
  --tIn:  clamp(0, calc((var(--lp) - .40) / .14), 1);
  --eIn:  calc(1 - (1 - var(--tIn)) * (1 - var(--tIn)));
  --tOut: clamp(0, calc((var(--lp) - .90) / .10), 1);
  --eOut: calc(var(--tOut) * var(--tOut));
  transform: translateX(calc((1 - var(--eIn)) * 960px - var(--eOut) * 1320px));
  opacity: var(--tIn);
}
.ypayload { opacity: clamp(0, calc((var(--lp) - .81) / .025), 1); }

.logistics__foot {
  width: min(100% - (var(--gutter) * 2), var(--shell));
  margin-inline: auto;
  display: grid;
  gap: 14px;
  justify-items: center;
}
.logistics__chain {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: center;
  direction: ltr;
}
.logistics__chain b {
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  padding: 8px 15px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: #fff;
  color: var(--ink-3);
  transition: color .4s var(--ease), border-color .4s var(--ease), background-color .4s var(--ease);
}
.logistics__chain b.is-hot { color: var(--ink); border-color: var(--amber); background: #FFF6F0; }
.logistics__note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px 22px;
  text-align: center;
}
.logistics__note p { color: var(--ink-2); font-size: .92rem; max-width: 64ch; line-height: 1.8; }

/* ======================================================= SHOT 2 — road */
.hway__head {
  width: min(100% - (var(--gutter) * 2), var(--shell));
  margin-inline: auto;
  opacity: calc(clamp(0, calc(var(--lp) / .12), 1)
                * (1 - clamp(0, calc((var(--lp) - .78) / .14), 1)));
}
.hway__head h2 { margin-top: 14px; }

.hway__scene { position: relative; align-self: center; height: clamp(200px, 32vh, 320px); }
/* the camera cranes over the asphalt at the end of the shot; the customs
   shot opens from the same angle and levels out */
.hway__crane {
  --tilt: clamp(0, calc((var(--lp) - .8) / .2), 1);
  position: absolute;
  inset: 0;
  transform: perspective(1400px) rotateX(calc(var(--tilt) * 62deg)) scale(calc(1 - var(--tilt) * .16));
  transform-origin: 50% 88%;
}
.hway__horizon {
  position: absolute;
  inset-inline: 0;
  bottom: 34%;
  height: 1px;
  background: linear-gradient(to left, transparent, var(--line), transparent);
}
.hway__road {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 34%;
  z-index: 1;
  background: linear-gradient(to bottom, #E6E6DF, #D7D7CE);
  border-top: 2px solid rgba(17, 17, 17, .14);
  overflow: hidden;
}
.hway__dashes {
  position: absolute;
  inset-inline: -60%;
  top: 48%;
  height: 5px;
  /* the truck runs right to left, so the asphalt streams the other way */
  background: repeating-linear-gradient(to right,
      rgba(17, 17, 17, .3) 0 54px, transparent 54px 118px);
  transform: translateX(calc(var(--lp) * 1180px));
}
.hway__truck {
  --drive: clamp(0, calc(var(--lp) / .26), 1);
  --ease-drive: calc(1 - (1 - var(--drive)) * (1 - var(--drive)));
  /* pulls away to the left as the camera cranes up at the end */
  --leave: clamp(0, calc((var(--lp) - .74) / .22), 1);
  position: absolute;
  bottom: 12%;
  left: 0;
  z-index: 3;                        /* on the asphalt, never behind it */
  width: clamp(240px, 36vw, 500px);
  transform: translateX(calc(22vw + (1 - var(--ease-drive)) * 88vw
                             - var(--leave) * var(--leave) * 70vw));
  filter: drop-shadow(0 16px 20px rgba(20, 30, 50, .18));
}
/* the partial is drawn nose-right; the journey runs nose-left */
.hway__truck .truck-side { display: block; width: 100%; height: auto; transform: scaleX(-1); }

.hway__data {
  width: 100%;
  overflow: hidden;
  border-block: 1px solid var(--line);
  background: rgba(255, 255, 255, .55);
  opacity: calc(clamp(0, calc((var(--lp) - .1) / .15), 1)
                * (1 - clamp(0, calc((var(--lp) - .8) / .14), 1)));
}
.hway__track {
  display: flex;
  width: max-content;
  transform: translateX(calc(var(--lp) * 58%));
}
.hway__cell {
  flex: none;
  width: clamp(210px, 22vw, 300px);
  padding: 16px clamp(16px, 2vw, 28px);
  border-inline-start: 1px solid var(--line-2);
  display: grid;
  gap: 4px;
  align-content: center;
}
.hway__cell b { font-size: .68rem; letter-spacing: .18em; color: var(--amber); }
.hway__cell span { font-size: .98rem; font-weight: 700; }
.hway__cell i { font-style: normal; font-size: .82rem; color: var(--ink-2); line-height: 1.7; }

/* ==================================================== SHOT 3 — customs */
.desc__crane {
  --tilt: calc(1 - clamp(0, calc(var(--lp) / .14), 1));
  position: absolute;
  inset: 0;
  transform: perspective(1400px) rotateX(calc(var(--tilt) * -58deg)) scale(calc(1 - var(--tilt) * .12));
  transform-origin: 50% 12%;
}
.desc__road {
  position: absolute;
  inset-block: -10%;
  left: 50%;
  width: clamp(200px, 22vw, 330px);
  translate: -50% 0;
  background: linear-gradient(to right, #2A303B, #343B48 50%, #2A303B);
  box-shadow: 0 0 60px rgba(0, 0, 0, .5);
}
.desc__edge {
  position: absolute;
  inset-block: 0;
  width: 4px;
  background: repeating-linear-gradient(to bottom,
      rgba(255, 255, 255, .5) 0 40px, transparent 40px 80px);
}
.desc__edge--a { left: 12px; }
.desc__edge--b { right: 12px; }
.desc__dashes {
  position: absolute;
  inset-block: -20%;
  left: 50%;
  width: 6px;
  translate: -50% 0;
  background: repeating-linear-gradient(to bottom,
      rgba(255, 214, 138, .8) 0 52px, transparent 52px 128px);
  transform: translateY(calc(var(--lp) * -900px));
}
.desc__truck {
  position: absolute;
  left: 50%;
  top: 0;
  width: clamp(92px, 11vw, 150px);
  translate: -50% 0;
  transform: translateY(calc(-36vh + var(--lp) * 130vh));
  filter: drop-shadow(0 22px 26px rgba(0, 0, 0, .5));
}
.desc__truck .truck-top { display: block; width: 100%; height: auto; }

.desc__head {
  position: absolute;
  inset-inline: var(--gutter);
  top: clamp(86px, 13vh, 130px);
  text-align: center;
  z-index: 3;
  opacity: calc(clamp(0, calc((var(--lp) - .06) / .1), 1)
                * (1 - clamp(0, calc((var(--lp) - .7) / .24), 1)));
}
.desc__head h2 { margin-top: 12px; }
/* a soft pool of night behind the headline, so the lane markings and the
   passing truck never run through the letters */
.desc__head::before {
  content: "";
  position: absolute;
  inset: -40% -8% -46%;
  z-index: -1;
  background: radial-gradient(50% 50% at 50% 50%, var(--scrim, rgba(11, 15, 22, .9)) 0%,
                              var(--scrim-mid, rgba(11, 15, 22, .55)) 48%, transparent 76%);
  pointer-events: none;
}
.desc__head { isolation: isolate; }

/* The file as a timeline. Six stages on screen at once was a wall of text;
   now the rail lists them all in one line each and only the stage the truck
   has reached is open, in the same place every time. --a0/--a1 are the slice
   of the shot each stage owns, written on the element by the view. */
.desc__stop, .desc__card {
  /* 1 inside this stage's slice, 0 outside, with a short cross-fade */
  --on: calc(clamp(0, calc((var(--lp) - var(--a0)) / .035), 1)
             * (1 - clamp(0, calc((var(--lp) - var(--a1)) / .035), 1)));
}

.desc__rail {
  position: absolute;
  inset-inline-start: clamp(12px, 5vw, 86px);
  top: 50%;
  translate: 0 -50%;
  z-index: 4;
  width: clamp(170px, 20vw, 260px);
  display: grid;
  gap: clamp(10px, 1.8vh, 20px);
  padding-inline-start: 22px;
  opacity: clamp(0, calc((var(--lp) - .06) / .08), 1);
}
.desc__rail-line {
  position: absolute;
  inset-block: 6px;
  inset-inline-start: 4px;
  width: 2px;
  background: rgba(255, 255, 255, .14);
  border-radius: 2px;
}
/* how far down the file has got */
.desc__rail-line i {
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: calc(clamp(0, calc((var(--lp) - .1) / .74), 1) * 100%);
  background: linear-gradient(to bottom, var(--amber), rgba(10, 123, 255, .35));
  border-radius: 2px;
}
.desc__stop {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 24px;
}
.desc__stop b {
  font-size: .86rem;
  font-weight: 600;
  line-height: 1.5;
  color: rgba(255, 255, 255, calc(.34 + var(--on) * .66));
  transition: color .3s var(--ease);
}
.desc__dot {
  position: absolute;
  inset-inline-start: -22px;
  top: 50%;
  width: 10px;
  height: 10px;
  margin-top: -5px;
  border-radius: 50%;
  background: #1A2434;
  border: 2px solid rgba(255, 255, 255, calc(.2 + var(--on) * .8));
  box-shadow: 0 0 0 calc(var(--on) * 5px) rgba(10, 123, 255, .16);
  scale: calc(1 + var(--on) * .25);
}
.desc__stop b::before {
  content: "";
  position: absolute;
  inset-inline-start: -19px;
  top: 50%;
  width: 4px;
  height: 4px;
  margin-top: -2px;
  border-radius: 50%;
  background: var(--amber);
  opacity: var(--on);
}

/* the open stage — every card sits in the same box, one visible at a time */
.desc__detail {
  position: absolute;
  inset-inline-end: clamp(12px, 5vw, 86px);
  top: 50%;
  translate: 0 -50%;
  z-index: 4;
  width: clamp(230px, 26vw, 340px);
  min-height: 190px;
  display: grid;
}
.desc__card {
  grid-area: 1 / 1;
  align-self: center;
  padding: 18px 20px;
  text-align: start;
  opacity: var(--on);
  transform: translateY(calc((1 - var(--on)) * 14px));
}
.desc__num {
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .14em;
  color: var(--amber);
}
.desc__card b { display: block; margin-top: 8px; font-size: 1.02rem; font-weight: 700; color: #fff; }
.desc__card p { margin-top: 8px; font-size: .86rem; color: var(--on-night-2); line-height: 1.85; }

/* ======================================================== SHOT 4 — sea */
/* Open water. Each crest is a drawn curve with a white foam edge and its own
   shadow under it, so the swell rolls instead of reading as a ruled hatch.
   Two sets at different scales cross each other; each set is one tall tile
   stamped twice and slid by exactly one tile height, which loops with no
   seam and costs one composited transform rather than a repaint. */
.voy__waves { position: absolute; inset: 0; overflow: hidden; }
/* where the light lies on the water */
.voy__sheen {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(56% 36% at 28% 30%, rgba(146, 206, 255, .2), transparent 70%),
    radial-gradient(48% 32% at 76% 66%, rgba(120, 180, 250, .15), transparent 72%);
}
/* The sets run across the frame on a slant, as swell does when you look down
   on it, and at slightly different angles so they cross. The tilt lives in
   the transform together with the drift: the drift is then measured along the
   crests' own axis, so one tile still lands exactly on the next. */
.voy__swell {
  position: absolute;
  inset-inline: -26%;
  top: -24%;
  height: 248%;
  will-change: transform;
}
.voy__swell .swell { display: block; width: 100%; height: 100%; }
.voy__swell--far {
  opacity: .5;
  transform: rotate(-9deg);
  animation: swellFar 34s linear infinite;
}
.voy__swell--near {
  opacity: .78;
  transform: rotate(-5deg);
  animation: swellNear 21s linear infinite;
}
/* the crests fade out where the headline and the notes sit, so the type is
   always read against calm water */
.voy__waves::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom, rgba(5, 26, 62, .72) 0%, rgba(5, 26, 62, .18) 34%, transparent 46%),
    linear-gradient(to top, rgba(4, 22, 52, .6) 0%, transparent 32%);
}
/* one tile is half the stamped height, so -50% lands the second copy exactly
   where the first one started */
@keyframes swellFar {
  from { transform: rotate(-9deg) translate3d(0, 0, 0); }
  to   { transform: rotate(-9deg) translate3d(1.5%, -50%, 0); }
}
@keyframes swellNear {
  from { transform: rotate(-5deg) translate3d(0, 0, 0); }
  to   { transform: rotate(-5deg) translate3d(-2%, -50%, 0); }
}

/* the last of the quay road, carried over from the customs shot */
.voy__quay {
  position: absolute;
  left: 50%;
  top: 0;
  z-index: 2;
  width: clamp(200px, 22vw, 330px);
  height: 44vh;
  translate: -50% 0;
  background:
    repeating-linear-gradient(to bottom, rgba(255,214,138,.75) 0 52px, transparent 52px 128px)
      50% 0 / 6px 100% no-repeat,
    linear-gradient(to bottom, #2A303B 0%, #2A303B 58%, rgba(42,48,59,0) 100%);
  opacity: calc(1 - clamp(0, calc(var(--lp) / .2), 1));
  transform: translateY(calc(var(--lp) * -46vh));
}

.voy__fleet {
  --lift: clamp(0, calc((var(--lp) - .24) / .66), 1);
  --ease-lift: calc(1 - (1 - var(--lift)) * (1 - var(--lift)));
  position: absolute;
  left: 50%;
  /* sits low, so once the camera has climbed the ship lies under the
     headline instead of behind it */
  top: 66%;
  width: clamp(150px, 19vw, 260px);
  translate: -50% -50%;
  transform: scale(calc(var(--ship-near, 1.7) - var(--ease-lift) * var(--ship-span, 1.2)))
             rotate(calc(var(--ease-lift) * -7deg));
}
.voy__ship { position: relative; z-index: 2; filter: drop-shadow(0 24px 40px rgba(0, 12, 34, .6)); }
.voy__ship .ship-top { display: block; width: 100%; height: auto; }

/* The water the ship makes. The drawing is three ship-widths across with the
   hull centred in it, so it lines up with the ship at any size; it only
   breathes, which keeps it on the compositor. */
.voy__wake {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 300%;
  z-index: 1;
  translate: -50% -50%;
  animation: wakeBreathe 7s ease-in-out infinite alternate;
}
.voy__wake .wake { display: block; width: 100%; height: auto; }
@keyframes wakeBreathe {
  from { transform: scale(1) translateY(0); opacity: .9; }
  to   { transform: scale(1.045) translateY(1.4%); opacity: 1; }
}
/* foam running out from the hull and away across the water */
.voy__ripple {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 210%;
  aspect-ratio: 1.1;
  translate: -50% -50%;
  border-radius: 50%;
  /* a soft collar of foam rather than a drawn circle */
  background: radial-gradient(closest-side,
      transparent 0 72%,
      rgba(255, 255, 255, .34) 84%,
      rgba(255, 255, 255, .5) 90%,
      transparent 100%);
  opacity: 0;
  animation: ripple 6.3s linear infinite;
  animation-delay: var(--d, 0s);
  will-change: transform, opacity;
}
@keyframes ripple {
  0%   { transform: scale(.42); opacity: 0; }
  14%  { opacity: .55; }
  100% { transform: scale(1.5); opacity: 0; }
}
/* the truck comes down off the quay onto the deck and is stowed */
.voy__boarding {
  --board: clamp(0, calc(var(--lp) / .2), 1);
  --eb: calc(1 - (1 - var(--board)) * (1 - var(--board)));
  position: absolute;
  left: 50%;
  top: 24%;
  width: 30%;
  translate: -50% 0;
  z-index: 3;
  transform: translateY(calc((1 - var(--eb)) * -84vh));
  opacity: calc(1 - clamp(0, calc((var(--lp) - .24) / .08), 1));
}
.voy__boarding .truck-top { display: block; width: 100%; height: auto; }

/* Type over moving water needs a surface of its own to sit on: a pane of
   glass, rounded, with a hairline edge. It is a plain translucent fill rather
   than a backdrop blur — a blur here would have to read back the pixels of
   the animating swell on every frame. */
.glass-plate {
  border-radius: var(--r-l);
  border: 1px solid rgba(255, 255, 255, .1);
  background:
    linear-gradient(to bottom, rgba(255, 255, 255, .07), rgba(255, 255, 255, .02)),
    rgba(5, 24, 56, .5);
  box-shadow: 0 18px 44px rgba(2, 12, 36, .3);
}

.voy__type {
  position: absolute;
  inset-inline: var(--gutter);
  top: clamp(84px, 12vh, 124px);
  text-align: center;
  z-index: 5;
  opacity: calc(clamp(0, calc((var(--lp) - .28) / .18), 1)
                * (1 - clamp(0, calc((var(--lp) - .8) / .12), 1)));
  text-shadow: 0 2px 20px rgba(2, 12, 40, .7);
  isolation: isolate;
}
.voy__type .voy__plate {
  width: min(100%, 760px);
  margin-inline: auto;
  padding: clamp(18px, 2.6vh, 30px) clamp(20px, 3vw, 44px);
}
.voy__type .lead { margin-inline: auto; color: rgba(255,255,255,.8); }

.voy__rail {
  position: absolute;
  top: 0;
  bottom: 0;
  width: clamp(150px, 20vw, 280px);
  z-index: 4;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(18px, 4vh, 40px);
  padding-block: 20vh 10vh;
}
.voy__rail--start { inset-inline-start: clamp(12px, 4vw, 72px); }
.voy__rail--end { inset-inline-end: clamp(12px, 4vw, 72px); text-align: end; }
.voy__card {
  --t: clamp(0, calc((var(--lp) - (.36 + var(--i) * .1)) / .14), 1);
  opacity: calc(var(--t) * (1 - clamp(0, calc((var(--lp) - .8) / .12), 1)));
  transform: translateY(calc((1 - var(--t)) * 42px));
}
.voy__card {
  padding: 14px 16px;
  text-align: start;
}
.voy__rail--end .voy__card { text-align: end; }
.voy__card b { display: block; font-size: .94rem; font-weight: 700; color: #fff; }
.voy__card span { font-size: .82rem; color: rgba(255,255,255,.72); line-height: 1.8; }

/* ======================================================== SHOT 5 — sky */
/* Cloud shapes are built from soft-edged gradient lumps of different sizes
   rather than a blur filter: a blur has to be re-rasterised on every frame
   of the climb, while these only ever move. */
.bridge__deck {
  --rise: clamp(0, calc(var(--lp) / .3), 1);
  position: absolute;
  inset: -20% -10%;
  z-index: 1;
  background-image:
    radial-gradient(26% 30% at 14% 74%, rgba(255,255,255,.96), rgba(255,255,255,.7) 38%, transparent 68%),
    radial-gradient(20% 24% at 28% 66%, rgba(255,255,255,.94), rgba(255,255,255,.6) 40%, transparent 70%),
    radial-gradient(30% 34% at 47% 80%, #fff, rgba(255,255,255,.76) 40%, transparent 70%),
    radial-gradient(22% 26% at 64% 68%, rgba(255,255,255,.93), rgba(255,255,255,.6) 40%, transparent 70%),
    radial-gradient(26% 30% at 84% 76%, rgba(255,255,255,.95), rgba(255,255,255,.66) 38%, transparent 68%),
    radial-gradient(34% 22% at 38% 44%, rgba(255,255,255,.7), transparent 72%),
    radial-gradient(30% 20% at 72% 36%, rgba(255,255,255,.62), transparent 74%),
    linear-gradient(to bottom, rgba(242,246,251,0) 0%, rgba(242,246,251,.82) 58%, #F4F7FB 100%);
  /* rises from below the frame and thickens until the sea is gone */
  transform: translate3d(0, calc((1 - var(--rise)) * 74%), 0);
  opacity: calc(var(--rise) * (1 - clamp(0, calc((var(--lp) - .4) / .3), 1) * .5));
  will-change: transform, opacity;
}
.bridge__clouds { position: absolute; inset: -16%; pointer-events: none; will-change: transform, opacity; }
/* the high, thin deck the aircraft flies over */
.bridge__clouds--far {
  z-index: 2;
  background-image:
    radial-gradient(24% 16% at 16% 56%, rgba(255,255,255,.8), rgba(255,255,255,.4) 46%, transparent 76%),
    radial-gradient(30% 18% at 42% 62%, rgba(255,255,255,.72), rgba(255,255,255,.34) 46%, transparent 78%),
    radial-gradient(22% 14% at 72% 50%, rgba(255,255,255,.66), transparent 78%),
    radial-gradient(34% 20% at 88% 64%, rgba(255,255,255,.7), transparent 76%),
    radial-gradient(44% 26% at 50% 94%, rgba(255,255,255,.88), transparent 70%);
  opacity: calc(clamp(0, calc((var(--lp) - .18) / .14), 1)
                * (1 - clamp(0, calc((var(--lp) - .5) / .4), 1) * .6));
  transform: translate3d(calc(var(--lp) * -7%), calc(var(--lp) * 13%), 0)
             scale(calc(1.1 + var(--lp) * .3));
}
/* wisps that pass in front of the aircraft */
.bridge__clouds--near {
  z-index: 4;
  background-image:
    radial-gradient(26% 12% at 84% 78%, rgba(255,255,255,.9), rgba(255,255,255,.45) 44%, transparent 74%),
    radial-gradient(22% 10% at 18% 72%, rgba(255,255,255,.86), rgba(255,255,255,.4) 46%, transparent 76%),
    radial-gradient(16% 8% at 52% 86%, rgba(255,255,255,.8), transparent 74%);
  opacity: calc(clamp(0, calc((var(--lp) - .26) / .14), 1)
                * (1 - clamp(0, calc((var(--lp) - .62) / .2), 1)) * .9);
  transform: translate3d(calc(var(--lp) * 14%), calc(var(--lp) * 24%), 0)
             scale(calc(1.15 + var(--lp) * .35));
}

/* A wide-body fills the frame above the cloud deck: in from the right,
   across the centre, out past the left edge. The message only settles once
   it has cleared the shot. */
.bridge__flight {
  --fa: clamp(0, calc((var(--lp) - .22) / .18), 1);
  --fb: clamp(0, calc((var(--lp) - .40) / .10), 1);
  --fc: clamp(0, calc((var(--lp) - .50) / .16), 1);
  --fly-x: calc((104 - var(--fa) * 78 - var(--fb) * 26 - var(--fc) * 124) * 1vw);

  position: absolute;
  z-index: 3;
  left: 0;
  top: 50%;
  width: clamp(560px, 94vw, 1500px);
  aspect-ratio: 1;
  translate: 0 -50%;
  /* the tilt belongs here, on the box that only moves: putting it on the
     drawing below — which carries a drop shadow — would make the browser
     rasterise that shadow afresh on every frame of the fly-past */
  transform: translate3d(var(--fly-x), calc(6vh - var(--lp) * 12vh), 0)
             rotate(calc(-3deg + var(--lp) * 6deg));
  opacity: calc(clamp(0, calc((var(--lp) - .2) / .04), 1)
                * (1 - clamp(0, calc((var(--lp) - .64) / .04), 1)));
  pointer-events: none;
}
/* drawn nose-right; this leg of the journey flies right to left */
.bridge__flight .plane {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 30px 48px rgba(20, 40, 80, .28));
  transform: scaleX(-1);
}
.bridge__trail {
  position: absolute;
  left: 78%;
  top: 50%;
  width: 60vw;
  height: 10px;
  translate: 0 -50%;
  border-radius: 99px;
  background: linear-gradient(to right, rgba(255,255,255,.75), rgba(255,255,255,0));
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 45% 55%, transparent);
  mask-image: linear-gradient(to bottom, transparent, #000 45% 55%, transparent);
  opacity: calc(clamp(0, calc((var(--lp) - .26) / .1), 1)
             * (1 - clamp(0, calc((var(--lp) - .56) / .12), 1)) * .85);
}

.bridge__type {
  position: absolute;
  inset-inline: var(--gutter);
  top: 50%;
  translate: 0 -50%;
  z-index: 6;
  text-align: center;
  color: #fff;
  opacity: clamp(0, calc((var(--lp) - .62) / .14), 1);
  transform: translateY(calc((1 - clamp(0, calc((var(--lp) - .62) / .14), 1)) * 28px));
}
.bridge__type .display-2 { color: #fff; text-shadow: 0 6px 34px rgba(4, 16, 44, .5); }
.bridge__type .lead {
  margin-inline: auto;
  margin-top: 14px;
  color: rgba(255, 255, 255, .86);
  text-shadow: 0 2px 18px rgba(4, 16, 44, .45);
}

/* ------------------------------------------------- journey responsive  */
@media (max-width: 860px) {
  .journey { height: 560vh; }
  .logistics__head { grid-template-columns: 1fr; gap: 10px; }
  .logistics__head .lead { font-size: .9rem; }
  .logistics__note p { font-size: .84rem; }
  .hway__cell { width: 62vw; padding-block: 12px; }
  .hway__cell span { font-size: .9rem; }
  .hway__cell i {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    font-size: .76rem;
  }
  .hway__scene { height: clamp(160px, 26vh, 240px); }
  .hway__truck {
    width: 76vw;
    transform: translateX(calc(12vw + (1 - var(--ease-drive)) * 96vw - var(--leave) * var(--leave) * 110vw));
  }
  /* the road narrows so the two data columns keep a lane of their own */
  .desc__road { width: 34vw; }
  .desc__truck { width: 20vw; }
  .desc__head { top: clamp(66px, 9vh, 96px); }
  .desc__head .display-2, .desc__head .display-3 { font-size: clamp(1.5rem, 7.4vw, 2.1rem); }
  /* the rail turns on its side: one row of stops under the headline, with
     only the number of the stage in hand spelled out in the card below */
  .desc__rail {
    inset-inline: clamp(14px, 4vw, 24px);
    top: clamp(150px, 21vh, 196px);
    translate: 0 0;
    width: auto;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: 0;
    padding-inline-start: 0;
    padding-block: 12px 0;
  }
  .desc__rail-line {
    inset-block: auto;
    top: 0;
    inset-inline: 4px;
    width: auto;
    height: 2px;
  }
  .desc__rail-line i {
    inset-inline: 0 auto;      /* fills from the side the reading starts on */
    top: 0;
    width: calc(clamp(0, calc((var(--lp) - .1) / .74), 1) * 100%);
    height: 100%;
    background: linear-gradient(to left, var(--amber), rgba(10, 123, 255, .35));
  }
  .desc__stop { justify-content: center; min-height: 0; }
  .desc__stop b { display: none; }
  .desc__dot { position: relative; inset-inline-start: auto; top: -17px; margin-top: 0; }
  .desc__detail {
    inset-inline: clamp(14px, 4vw, 24px);
    top: auto;
    bottom: clamp(14px, 3vh, 26px);
    translate: 0 0;
    width: auto;
    min-height: 0;
  }
  .desc__card { align-self: end; padding: 14px 16px; }
  .desc__card b { font-size: .92rem; }
  .desc__card p {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    font-size: .78rem;
    line-height: 1.75;
  }
  .voy__quay { width: 34vw; }
  /* On a phone there is no room for columns beside the ship. The shot is read
     as three bands instead: headline at the top, ship in the middle, the four
     notes as a 2×2 block at the bottom. The bands are given explicit heights
     so none of them can grow into the next one. */
  .voy__type { top: clamp(64px, 8.5vh, 88px); }
  .voy__type .voy__plate { padding: 14px 16px; }
  .voy__type .display-2 { font-size: clamp(1.38rem, 6.8vw, 1.9rem); }
  .voy__type .eyebrow { font-size: .6rem; }
  .voy__type .lead {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-top: 8px;
    font-size: .78rem;
  }
  /* a narrow screen cannot take the full close-up, so the ship starts nearer
     its cruising size, and it rides in the band between the two text blocks */
  .voy__fleet { top: 54%; width: 21vw; --ship-near: 1.08; --ship-span: .58; }
  /* The two rails become the two halves of one 2×2 block. Giving both the
     same height and the same two rows is what keeps the left pair level with
     the right pair — as free-flowing columns they ended up at different
     heights and the last card ran off the bottom of the screen. */
  .voy__rail {
    top: auto;
    bottom: clamp(10px, 2vh, 18px);
    width: calc(50% - clamp(12px, 3.4vw, 20px));
    height: clamp(140px, 23.5vh, 190px);
    display: grid;
    grid-template-rows: repeat(2, minmax(0, 1fr));
    align-content: end;
    padding-block: 0;
    gap: 8px;
  }
  .voy__card { overflow: hidden; align-self: start; }
  .voy__rail--start { inset-inline-start: clamp(10px, 3vw, 16px); }
  .voy__rail--end { inset-inline-end: clamp(10px, 3vw, 16px); }
  .voy__card { padding: 11px 13px; }
  .voy__card b { font-size: .76rem; line-height: 1.5; }
  .voy__card span {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-top: 3px;
    font-size: .64rem;
    line-height: 1.6;
  }
  /* the ripple reaches past a small ship far enough to look wrong */
  .voy__ripple { width: 150%; }
}

/* Without motion the journey reads as five plain, stacked panels. */
@media (prefers-reduced-motion: reduce) {
  .journey { height: auto !important; }
  .journey__stage { position: static; height: auto; min-height: 0; overflow: visible; }
  .jbg, .voy__quay, .voy__boarding, .bridge__deck, .bridge__clouds,
  .bridge__flight, .ycarry, .ybox-placed { display: none; }
  .jshot {
    position: relative; inset: auto; opacity: 1 !important; pointer-events: auto;
    min-height: 80vh; padding-block: var(--section-y);
  }
  /* here the shots are a plain stack of panels, so none of them is ever
     "off screen" in the sense the scrolling version means */
  .jshot.is-off { content-visibility: visible; contain: none; }
  .jshot--yard, .jshot--road { background: var(--paper); }
  .jshot--customs { background: #0B0F16; }
  .jshot--sea { background: #0A3576; }
  .jshot--sky { background: #3877D0; min-height: 60vh; }
  .jshot__in { position: relative; inset: auto; }
  .hway__crane, .desc__crane, .hway__truck, .hway__dashes, .hway__track,
  .desc__truck, .desc__dashes, .voy__fleet, .ycrane, .ytruck, .ycrane__boom,
  .ybox { transform: none !important; }
  .logistics__head, .hway__head, .hway__data, .desc__head, .desc__stop,
  .desc__rail, .desc__card,
  .voy__type, .voy__card, .bridge__type, .ybox, .ycrane, .ytruck, .ypayload {
    opacity: 1 !important;
  }
  /* no scroll to follow, so every stage is simply listed */
  .desc__detail { display: grid; gap: 12px; min-height: 0; }
  .desc__card { grid-area: auto; transform: none !important; }
  .desc__stop b { color: #fff; }
  .desc__crane, .voy__fleet { position: relative; height: 50vh; }
  .desc__rail, .desc__detail, .voy__rail, .desc__head, .voy__type, .bridge__type {
    position: relative; inset: auto; translate: none; transform: none; padding-block: 0;
    width: auto; margin-inline: var(--gutter);
  }
  .desc__rail { padding-inline-start: 22px; }
  .voy__swell, .voy__wake, .voy__ripple { animation: none !important; }
  .voy__ripple { opacity: .3; }
}
